massi-world

this website of mine
Log | Files | Refs

the-world.ts (3903B)


      1 document.addEventListener("DOMContentLoaded", () => {
      2   const containerEl = document.querySelector(
      3     "#contains-the-world",
      4   ) as HTMLSpanElement;
      5   const worldEl = document.querySelector("#the-world") as HTMLDivElement;
      6   const spriteWidth = worldEl.getBoundingClientRect().width;
      7   const initForce = 1;
      8   const maxVelocity = 100;
      9   let force = initForce;
     10   const accel = 0.1;
     11   const degreesInRotation = 360;
     12   const frames = 12;
     13   const degreesInFrame = degreesInRotation / frames;
     14   const fps = 24;
     15   const tickTime = 1000 / fps;
     16 
     17   // targets 3 sprite frames per sec
     18   const steadyIncrease = (degreesInFrame * 3) / fps;
     19 
     20   const xOffset = (frame: number) => {
     21     return frame * spriteWidth;
     22   };
     23 
     24   let rotation = 0; // in degrees
     25   let frame = 0;
     26   let velocity = 0;
     27 
     28   const physicsTime = 3_000;
     29   const esplodeTime = 100_000;
     30 
     31   const isTimeFor = (ts, tTimer, tgtTime) => {
     32     return ts - tTimer > tgtTime;
     33   };
     34 
     35   const drag = (v: number) => Math.max(v / 10, 0.005 * v * v);
     36 
     37   const updatePosition = (v = velocity) => {
     38     rotation = (rotation + v) % degreesInRotation;
     39     frame = Math.floor(rotation / degreesInFrame);
     40   };
     41 
     42   const updateVelocity = (deltaV: number) => {
     43     velocity = Math.min(
     44       maxVelocity,
     45       velocity + deltaV - drag(Math.round(velocity)),
     46     );
     47   };
     48 
     49   const physicsTick = (isHovering: boolean) => {
     50     updateVelocity(isHovering ? force : 0);
     51     updatePosition();
     52 
     53     if (isHovering) {
     54       force += accel;
     55     } else {
     56       force = initForce;
     57     }
     58 
     59     if (velocity > 45) {
     60       shakeTick();
     61     } else {
     62       resetShake();
     63     }
     64 
     65     updateFrame();
     66   };
     67 
     68   const steadyTick = (isHovering: boolean) => {
     69     if (isHovering) {
     70       updatePosition(steadyIncrease);
     71       updateFrame();
     72     }
     73   };
     74 
     75   const updateFrame = () => {
     76     worldEl.style.backgroundPositionX = `${xOffset(frame)}px`;
     77   };
     78 
     79   const rBigPos = () =>
     80     (Math.random() > 0.5 ? 1 : -1) * (2000 + Math.random() * 2000);
     81 
     82   const esplode = () => {
     83     console.log("boom!");
     84     const pos = worldEl.getBoundingClientRect();
     85     worldEl.style.position = "fixed";
     86     worldEl.style.top = pos.y + "px";
     87     worldEl.style.left = pos.x + "px";
     88     const dest = `translate(${rBigPos()}px, ${rBigPos()}px) scale(${Math.random() > 0.5 ? 20 : 0.001})`;
     89     worldEl.style.transform = dest;
     90   };
     91 
     92   let shakeDir = 1;
     93   let shakeTimer: number | null = null;
     94   const shakeTick = () => {
     95     if (!shakeTimer) {
     96       shakeTimer = setTimeout(() => {
     97         esplode();
     98       }, esplodeTime);
     99     }
    100     shakeDir *= -1;
    101     worldEl.style.translate = `${shakeDir}px`;
    102   };
    103 
    104   const resetShake = () => {
    105     if (shakeTimer !== null) {
    106       clearTimeout(shakeTimer);
    107     }
    108     worldEl.style.translate = "";
    109   };
    110 
    111   let t = -Infinity;
    112   let tPhysics: number | null = null;
    113   let inPhysics = false;
    114   let doAnimation = false;
    115   const animate = (timestamp: DOMHighResTimeStamp = 0) => {
    116     if (!doAnimation) {
    117       return;
    118     }
    119 
    120     if (!tPhysics) {
    121       tPhysics = timestamp;
    122     }
    123     if (isTimeFor(timestamp, t, tickTime)) {
    124       t = timestamp;
    125 
    126       const worldHovering = worldEl.matches(":hover");
    127       const textHovering = containerEl.matches(":hover");
    128       const isHovering = worldHovering || textHovering;
    129 
    130       // physics timer only run while hovering on world.
    131       if (!worldHovering) {
    132         tPhysics = timestamp;
    133       }
    134 
    135       inPhysics =
    136         (worldHovering && isTimeFor(timestamp, tPhysics, physicsTime)) ||
    137         velocity > 0.5;
    138 
    139       // if (!inPhysics && !isHovering) {
    140       //   doAnimation = false;
    141       //   return;
    142       // }
    143 
    144       if (!inPhysics) {
    145         steadyTick(true);
    146       } else {
    147         physicsTick(worldHovering);
    148       }
    149     }
    150     requestAnimationFrame(animate);
    151   };
    152 
    153   doAnimation = true;
    154   animate();
    155 
    156   containerEl.addEventListener("mouseenter", () => {
    157     if (!doAnimation) {
    158       doAnimation = true;
    159       animate();
    160     }
    161   });
    162 });