GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

바운싱 볼 애니메이션 9가지 — 충돌·중력 물리 계산

01번 공은 반발계수 0.82로 서로 속도를 맞바꾸고, 04번 공은 같은 반발계수를 받고도 질량 차이 때문에 튀는 높이가 달라지며, 05번 공은 포인터가 가까워지면 그 계수 대신 반발력을 받아 흩어진다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

바운싱 볼 애니메이션 9가지 — 값 하나가 공의 움직임을 어떻게 바꾸는가

아홉 항목은 모두 FIXED_DT = 16 으로 고정한 시간 스텝 위에서 계산한다 — 프레임이 밀리든 빠르든 한 번의 stepSim 호출은 항상 16ms 분량만 움직인다. 여덟 항목은 seedRand() 로 씨앗을 고정한 난수를 시작 위치·속도에 써서 같은 순서로 재생되고, 유일한 예외는 뒤에서 다룬다. 공 하나에 적용되는 애니메이션 물리 법칙은 반발계수·질량·포인터 거리 세 값이 전부이고, 이 값들이 서로 다른 조합으로 섞이며 아홉 가지 다른 장면이 된다.

공 물리 종류별 코드

아래 아홉 개는 전체 코드가 아니라 값이 실제로 계산되는 구간만 발췌했고, 나머지는 압축 파일 안에 들어 있다. 아홉 항목 모두 애니메이션 물리 엔진 없이 반발계수·질량·포인터 거리 같은 값만 매 프레임 계산해서 움직인다.

01속도를 서로 주고받는 탄성 충돌 공

두 공이 겹치면 질량 비율만큼 서로 밀어내 위치를 갈라놓고, 그 자리의 법선 방향 속도를 반발계수 0.82로 교환해 튕겨 나간다. 겹침이 없으면 resolvePair 는 아무 계산도 하지 않고 즉시 반환한다. 23장 캡처 중 23장에서 공이 움직였고 누적면적은 40.8%, 충돌 강도 평균은 106.2였다.

속도를 서로 주고받는 탄성 충돌 공
새 창에서 열기

01 · 속도를 서로 주고받는 탄성 충돌 공 — 측정 누적면적 40.801% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

BALL_RESTITUTIONWALL_RESTITUTIONresolvePair
  function resolvePair(a, b) {
    var dx = b.x - a.x, dy = b.y - a.y;
    var dist = Math.hypot(dx, dy);
    var minDist = a.r + b.r;
    if (dist <= 0 || dist >= minDist) return;
    var nx = dx / dist, ny = dy / dist;
    var overlap = minDist - dist;
    var totalMass = a.mass + b.mass;
    a.x -= nx * overlap * (b.mass / totalMass);
    a.y -= ny * overlap * (b.mass / totalMass);
    b.x += nx * overlap * (a.mass / totalMass);
    b.y += ny * overlap * (a.mass / totalMass);

    var rvx = b.vx - a.vx, rvy = b.vy - a.vy;
    var vn = rvx * nx + rvy * ny;
    if (vn >= 0) return;
    var j = -(1 + BALL_RESTITUTION) * vn / (1 / a.mass + 1 / b.mass);
    var ix = j * nx, iy = j * ny;
    a.vx -= ix / a.mass; a.vy -= iy / a.mass;
    b.vx += ix / b.mass; b.vy += iy / b.mass;
    var strength = Math.min(1, Math.abs(vn) / 220);
    a.squash = Math.max(a.squash, strength);
    b.squash = Math.max(b.squash, strength);
    impacts.push({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2, born: elapsed, strength: strength });
  }

02떨어지다 바닥에서 잦아드는 중력 낙하 공

매 프레임 GRAVITY 만큼 아래로 속도를 더하다가 바닥에 닿으면 되튀지만, 튕겨 나가는 속도가 SLEEP_THRESHOLD 밑으로 떨어지면 그 공은 그대로 멈춘다. 멈춘 공은 squash 값만 서서히 줄어들 뿐 더는 위치가 바뀌지 않는다. 23장 중 20장에서만 움직임이 잡혔는데, 공이 일찍 잠들수록 뒤쪽 캡처는 정지 상태라 누적면적은 15.4%로 낮지만 강도 평균 103.7은 움직인 20장만 놓고 잰 값이라 다른 항목과 비슷한 수준이다.

떨어지다 바닥에서 잦아드는 중력 낙하 공
새 창에서 열기

02 · 떨어지다 바닥에서 잦아드는 중력 낙하 공 — 측정 누적면적 15.354% · 움직인 프레임 20/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

FLOOR_RESTITUTIONSLEEP_THRESHOLDallSettled
  function stepSim(dtMs, nowMs) {
    var dtS = dtMs / 1000;
    for (var i = 0; i < balls.length; i += 1) {
      var b = balls[i];
      if (nowMs < b.startAt) continue;
      if (b.settled) {
        b.squash = Math.max(0, b.squash - dtS * 6);
        continue;
      }
      b.vy += GRAVITY * dtS;
      b.y += b.vy * dtS;
      var floor = CALC_H - b.r;
      if (b.y >= floor) {
        b.y = floor;
        if (Math.abs(b.vy) < SLEEP_THRESHOLD) {
          b.vy = 0;
          b.settled = true;
          b.squash = 0.5;
          impacts.push({ x: b.x, y: floor, born: nowMs, strength: 0.35 });
        } else {
          impacts.push({ x: b.x, y: floor, born: nowMs, strength: Math.min(1, Math.abs(b.vy) / 320) });
          b.squash = Math.max(b.squash, Math.min(0.85, Math.abs(b.vy) / 300));
          b.vy = -b.vy * FLOOR_RESTITUTION;
        }
      }

03벽에 닿을 때마다 힘을 잃는 반발계수 벽 튕김 공

네 벽 중 하나에 닿을 때마다 그 축의 속도에 WALL_RESTITUTION 을 곱해 일부를 잃고, 속도가 MIN_SPEED 밑으로 느려지면 kick 이 새 각도와 KICK_SPEED 로 다시 쏴 보낸다. 공은 세 개뿐이라 서로 부딪히지 않고 각자 벽만 상대한다. 23장 모두 움직였고 누적면적 35.0%·강도 평균 96.4였다.

벽에 닿을 때마다 힘을 잃는 반발계수 벽 튕김 공
새 창에서 열기

03 · 벽에 닿을 때마다 힘을 잃는 반발계수 벽 튕김 공 — 측정 누적면적 35.015% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

KICK_SPEEDMIN_SPEEDkick
  function kick(b) {
    var ang = rand() * Math.PI * 2;
    var spd = KICK_SPEED * (0.7 + rand() * 0.5);
    b.vx = Math.cos(ang) * spd;
    b.vy = Math.sin(ang) * spd;
    b.hits = 0;
  }
  for (var i = 0; i < radii.length; i += 1) {
    var r = radii[i];
    var b = { x: r + rand() * (CALC_W - r * 2), y: r + rand() * (CALC_H - r * 2), r: r, color: COLORS[i], squash: 0, hits: 0 };
    kick(b);
    balls.push(b);
  }

  function stepSim(dtMs) {
    var dtS = dtMs / 1000;
    for (var i = 0; i < balls.length; i += 1) {
      var b = balls[i];
      b.x += b.vx * dtS;
      b.y += b.vy * dtS;
      var hit = false;
      if (b.x - b.r < 0) { b.x = b.r; b.vx = -b.vx * WALL_RESTITUTION; hit = true; }
      if (b.x + b.r > CALC_W) { b.x = CALC_W - b.r; b.vx = -b.vx * WALL_RESTITUTION; hit = true; }
      if (b.y - b.r < 0) { b.y = b.r; b.vy = -b.vy * WALL_RESTITUTION; hit = true; }
      if (b.y + b.r > CALC_H) { b.y = CALC_H - b.r; b.vy = -b.vy * WALL_RESTITUTION; hit = true; }

04질량 차이로 튀는 높이가 갈라지는 공

네 공은 같은 자리에서 같은 높이로 떨어지지만 restitutionFor 가 질량을 BASE_MASS_REST 와 BALL_MASS_BOUNCE_FACTOR 로 나눠 각자 다른 반발계수를 돌려준다. 질량이 클수록 반발계수가 작아져 무거운 공은 낮게, 가벼운 공은 높게 튄다. 23장 모두 움직였지만 누적면적은 11.1%로 아홉 항목 중 가장 낮은데, 화면을 채우는 건 매번 가장 가벼운 공 하나뿐이라 나온 결과다.

질량 차이로 튀는 높이가 갈라지는 공
새 창에서 열기

04 · 질량 차이로 튀는 높이가 갈라지는 공 — 측정 누적면적 11.113% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

BALL_MASS_BOUNCE_FACTORBASE_MASS_RESTrestitutionFor
  function restitutionFor(mass) {
    return Math.max(0.22, Math.min(0.95, BASE_MASS_REST * BALL_MASS_BOUNCE_FACTOR / mass));
  }

  var balls = [];
  function reset() {
    balls = [];
    for (var i = 0; i < MASSES.length; i += 1) {
      balls.push({
        x: CALC_W * XPOS[i],
        y: 26,
        vy: 0,
        mass: MASSES[i],
        r: MASSES[i] * 0.5,
        color: COLORS[i],
        rest: restitutionFor(MASSES[i]),
        squash: 0,
        settled: false
      });
    }
    impacts = [];
  }
  var impacts = [];

  function stepSim(dtMs, nowMs) {

05포인터가 다가오면 흩어지는 반발 공

포인터와의 거리가 POINTER_REPEL_RADIUS 안으로 들어오면 applyPointer 가 거리에 반비례하는 REPEL_FORCE 를 공에 더해 밀어내고, 포인터가 멀어지면 각 공은 DRIFT_SPEED 로 정해진 제자리로 천천히 돌아온다. 방향키도 같은 포인터 좌표를 옮기므로 마우스 없이도 흩어뜨릴 수 있다. 23장 모두 움직였고 누적면적 21.4%·강도 평균 88.1이었다.

포인터가 다가오면 흩어지는 반발 공
새 창에서 열기

05 · 포인터가 다가오면 흩어지는 반발 공 — 측정 누적면적 21.441% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

POINTER_REPEL_RADIUSREPEL_FORCEDRIFT_SPEED
  function applyPointer(px, py, dtS) {
    for (var i = 0; i < balls.length; i += 1) {
      var b = balls[i];
      var dx = b.x - px, dy = b.y - py;
      var dist = Math.hypot(dx, dy) || 0.001;
      if (dist < POINTER_REPEL_RADIUS) {
        var force = REPEL_FORCE * (1 - dist / POINTER_REPEL_RADIUS) / dist;
        b.vx += dx * force * dtS;
        b.vy += dy * force * dtS;
        b.squash = Math.min(1, b.squash + (1 - dist / POINTER_REPEL_RADIUS) * 0.6);
      }
    }
  }

  function stepSim(dtMs, nowMs, px, py) {
    var dtS = dtMs / 1000;
    applyPointer(px, py, dtS);
    for (var i = 0; i < balls.length; i += 1) {
      var b = balls[i];
      var homeX = b.hx + Math.cos(nowMs / 1000 * DRIFT_SPEED + b.phase) * 10;
      var homeY = b.hy + Math.sin(nowMs / 1000 * DRIFT_SPEED * 0.8 + b.phase) * 10;
      var pull = 0.9;
      b.vx += (homeX - b.x) * pull * dtS;
      b.vy += (homeY - b.y) * pull * dtS;
      b.vx -= b.vx * Math.min(1, DAMPING * dtS);

06포인터 쪽으로 끌려가 도는 인력 공

stepSim 은 포인터 방향 가속만 매 프레임 계산한다 — 거리에 반비례하는 힘으로 공을 포인터 쪽으로 끌어당기고 DAMPING 으로 속도를 조금씩 줄일 뿐, 옆으로 도는 힘을 여기서 새로 만들지는 않는다. 옆으로 도는 움직임은 initBalls 가 생성 시점에 한 번 준 접선 방향 초기속도가 감쇠 아래서도 남아 있는 결과라, 공은 포인터에 빨려 들어가지 않고 일정 거리에서 돈다. 23장 모두 움직였고 누적면적 20.4%·강도 평균 77.2였다.

포인터 쪽으로 끌려가 도는 인력 공
새 창에서 열기

06 · 포인터 쪽으로 끌려가 도는 인력 공 — 측정 누적면적 20.388% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

ATTRACT_KMIN_DISTorbitSpeed
  function stepSim(dtMs, px, py) {
    var dtS = dtMs / 1000;
    for (var i = 0; i < balls.length; i += 1) {
      var b = balls[i];
      var dx = px - b.x, dy = py - b.y;
      var dist = Math.max(MIN_DIST, Math.hypot(dx, dy));
      var accel = ATTRACT_K / dist;
      var ax = (dx / dist) * accel, ay = (dy / dist) * accel;
      b.vx += ax * dtS;
      b.vy += ay * dtS;
      b.vx -= b.vx * DAMPING * dtS;
      b.vy -= b.vy * DAMPING * dtS;
      b.x += b.vx * dtS;
      b.y += b.vy * dtS;
      var speed = Math.hypot(b.vx, b.vy);
      b.squash = Math.min(0.5, speed / 900);
    }
  }

07드래그한 속도 그대로 날아가는 던지기 공

드래그하는 동안 recordSample 이 최근 SAMPLE_WINDOW_MS 안의 좌표만 남기고, 손을 떼면 releaseVelocity 가 그 구간의 이동량으로 속도를 계산해 throwBall 에 넘긴다. 방향키를 누르면 같은 throwBall 에 KEY_IMPULSE 만큼의 속도를 직접 더해 드래그 없이도 던질 수 있다. 23장 모두 움직였지만 AIR_DRAG 가 매 프레임 속도를 깎아 강도 평균은 59.8로 아홉 항목 중 가장 낮았고 누적면적은 16.2%였다.

드래그한 속도 그대로 날아가는 던지기 공
새 창에서 열기

07 · 드래그한 속도 그대로 날아가는 던지기 공 — 측정 누적면적 16.193% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

SAMPLE_WINDOW_MSKEY_IMPULSEreleaseVelocity
  function recordSample(x, y, t) {
    samples.push({ x: x, y: y, t: t });
    while (samples.length && t - samples[0].t > SAMPLE_WINDOW_MS) samples.shift();
  }

  function releaseVelocity(nowT) {
    if (samples.length < 2) return { vx: 0, vy: 0 };
    var a = samples[0], b = samples[samples.length - 1];
    var dt = Math.max(16, b.t - a.t) / 1000;
    return { vx: (b.x - a.x) / dt, vy: (b.y - a.y) / dt };
  }

  function throwBall(vx, vy) {
    ball.vx = vx; ball.vy = vy;
    if (Math.hypot(vx, vy) > 1) ball.heading = Math.atan2(vy, vx);
  }

  function stepSim(dtMs) {
    var dtS = dtMs / 1000;
    if (dragging) return;

08위에서 쏟아져 바닥에 쌓이는 낙하 공

SPAWN_INTERVAL_MS 마다 새 공을 하나씩 떨어뜨리다가 MAX_BALLS 개에 닿으면 멈추고, 바닥에 쌓인 공들은 SEPARATE_ITERS 번 반복하는 겹침 보정으로 서로 밀어내며 자리를 잡는다. 이 보정은 공 하나마다 나머지 전부와 겹침을 확인하는 방식이라 공 수가 늘수록 계산량이 늘어난다. 23장 모두 움직였고 누적면적 33.3%·강도 평균 98.9였다.

위에서 쏟아져 바닥에 쌓이는 낙하 공
새 창에서 열기

08 · 위에서 쏟아져 바닥에 쌓이는 낙하 공 — 측정 누적면적 33.263% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

SPAWN_INTERVAL_MSSEPARATE_ITERSHOLD_MS
  function stepSim(dtMs, nowMs) {
    var dtS = dtMs / 1000;
    if (balls.length < MAX_BALLS && holdSince === null && nowMs - lastSpawn >= SPAWN_INTERVAL_MS) {
      spawn(nowMs);
      lastSpawn = nowMs;
    }
    for (var i = 0; i < balls.length; i += 1) {
      var b = balls[i];
      b.vy += GRAVITY * dtS;
      b.x += b.vx * dtS;
      b.y += b.vy * dtS;
      b.vx -= b.vx * 0.6 * dtS;
      if (b.x - b.r < 0) { b.x = b.r; b.vx = -b.vx * 0.5; }
      if (b.x + b.r > CALC_W) { b.x = CALC_W - b.r; b.vx = -b.vx * 0.5; }
      var floor = CALC_H - b.r;
      if (b.y > floor) {
        b.y = floor;
        if (Math.abs(b.vy) > 30) b.squash = Math.min(0.8, Math.abs(b.vy) / 320);
        b.vy = -b.vy * FLOOR_RESTITUTION;
        if (Math.abs(b.vy) < 22) b.vy = 0;
      }
      b.squash = Math.max(0, b.squash - dtS * 3);
    }
    for (var iter = 0; iter < SEPARATE_ITERS; iter += 1) {

09여러 공 물리를 한 화면에 합친 히어로 장면

다섯 공이 HERO_GRAVITY 로 떨어지면서 동시에 포인터가 HERO_POINTER_REPEL_RADIUS 안에 들어오면 HERO_POINTER_FORCE 로 밀려나고, 네 벽 중 하나에 닿으면 HERO_WALL_RESTITUTION 만큼만 속도를 남긴다. 중력·포인터 반발·벽 반발 세 계산이 한 stepSim 안에서 같은 공에 동시에 적용된다. 23장 모두 움직였고 누적면적은 39.7%, 한 장에서 가장 넓게 찍힌 면적은 16.7%로 아홉 항목 중 가장 컸다.

여러 공 물리를 한 화면에 합친 히어로 장면
새 창에서 열기

09 · 여러 공 물리를 한 화면에 합친 히어로 장면 — 측정 누적면적 39.741% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

HERO_POINTER_FORCEHERO_WALL_RESTITUTIONHERO_GRAVITY
  function stepSim(dtMs, nowMs, px, py) {
    var dtS = dtMs / 1000;
    for (var i = 0; i < balls.length; i += 1) {
      var b = balls[i];
      b.vy += HERO_GRAVITY * dtS;
      var dx = b.x - px, dy = b.y - py;
      var dist = Math.hypot(dx, dy) || 0.001;
      if (dist < HERO_POINTER_REPEL_RADIUS) {
        var force = HERO_POINTER_FORCE * (1 - dist / HERO_POINTER_REPEL_RADIUS) / dist;
        b.vx += dx * force * dtS;
        b.vy += dy * force * dtS;
      }
      b.x += b.vx * dtS;
      b.y += b.vy * dtS;
      var hit = false;
      if (b.x - b.r < 0) { b.x = b.r; b.vx = -b.vx * HERO_WALL_RESTITUTION; hit = true; }
      if (b.x + b.r > CALC_W) { b.x = CALC_W - b.r; b.vx = -b.vx * HERO_WALL_RESTITUTION; hit = true; }
      if (b.y - b.r < 0) { b.y = b.r; b.vy = -b.vy * HERO_WALL_RESTITUTION; hit = true; }
      if (b.y + b.r > CALC_H) {
        b.y = CALC_H - b.r;
        b.vy = -b.vy * HERO_WALL_RESTITUTION;
        if (Math.abs(b.vy) < 24) b.vy = 0;
        hit = true;
      }

잰 바운싱 볼 아홉 항목의 실제 값 — 24장 캡처 기준

항목 누적면적 평균 강도 움직인 프레임
01 속도를 서로 주고받는 탄성 충돌 공 40.8% 106.2 23/23
02 떨어지다 바닥에서 잦아드는 중력 낙하 공 15.4% 103.7 20/23
03 벽에 닿을 때마다 힘을 잃는 반발계수 벽 튕김 공 35.0% 96.4 23/23
04 질량 차이로 튀는 높이가 갈라지는 공 11.1% 108.9 23/23
05 포인터가 다가오면 흩어지는 반발 공 21.4% 88.1 23/23
06 포인터 쪽으로 끌려가 도는 인력 공 20.4% 77.2 23/23
07 드래그한 속도 그대로 날아가는 던지기 공 16.2% 59.8 23/23
08 위에서 쏟아져 바닥에 쌓이는 낙하 공 33.3% 98.9 23/23
09 여러 공 물리를 한 화면에 합친 히어로 장면 39.7% 99.9 23/23

값이 움직임을 바꾸는 곳 — 반발계수·질량·포인터 반경

BALL_RESTITUTION 은 01번 공이 부딪힐 때마다 잃는 속도의 비율을 정한다. 값을 낮추면 충돌한 두 공이 힘을 금방 잃고 서로 붙어 다니듯 느려지고, 값을 올리면 충돌마다 튕기는 세기가 오래 유지된다. BALL_MASS_BOUNCE_FACTOR 는 04번 공에서 질량과 반발계수를 연결하는 계수라, 값을 낮추면 무거운 공과 가벼운 공이 튀는 높이 차이가 줄고 값을 올리면 가벼운 공만 훨씬 높이 튄다. POINTER_REPEL_RADIUS 는 05번 공이 포인터를 감지하는 반경이라, 값을 낮추면 포인터에 바짝 다가가야만 반응하고 값을 올리면 멀리서부터 미리 흩어진다.

어디서 깨지는가 — 동작 줄임 설정·좁은 화면·공 개수가 많을 때

아홉 항목 모두 matchMedia('(prefers-reduced-motion: reduce)') 로 동작 줄임 설정을 읽어 자동 재생 루프를 멈춘다 — 다만 05·06·07·09 항목은 방향키 입력이 들어오면 그 프레임만 다시 계산해 화면을 갱신한다. 좁은 화면에서는 @media (max-width: 340px) 로 캡션 글자 크기만 10px 로 줄어들 뿐 캔버스 내부 계산 해상도는 그대로라, 화면이 작아질수록 공과 공 사이 간격이 시각적으로 더 좁아 보인다. 08번 항목은 MAX_BALLS 13개가 쌓일 때까지 공을 계속 늘리고 그때마다 SEPARATE_ITERS 번의 겹침 보정을 전체 공 쌍에 반복하므로, 공 수가 늘수록 계산량도 함께 늘어난다. 09번 히어로 장면도 다섯 공이 중력·포인터 반발·벽 반발을 동시에 계산해 좁은 화면일수록 공끼리 구분이 더 어려워진다. 이 아홉 개 원본은 압축 파일 안에 있고, 여는 비밀번호는 nqxed599 이다.

변형 세 가지와 접근성

이름 값 느낌
탄성 충돌 반발계수 조정 01 BALL_RESTITUTION 0.82 → 0.55 값을 줄이면 충돌한 두 공이 힘을 빨리 잃고 서로 붙어 다니듯 움직이고, 값을 늘리면 충돌마다 튕겨 나가는 세기가 오래 유지된다.
질량별 튕김 높이 계수 조정 04 BALL_MASS_BOUNCE_FACTOR 1.4 → 2.2 값을 줄이면 무거운 공과 가벼운 공이 튀는 높이 차이가 작아지고, 값을 늘리면 가벼운 공만 훨씬 높이 튀어 오른다.
포인터 반발 반경 조정 05 POINTER_REPEL_RADIUS 90 → 160 값을 줄이면 포인터에 아주 가까이 가야만 공들이 흩어지고, 값을 늘리면 포인터가 멀리 있어도 공들이 미리 자리를 비킨다.

입력 경로가 있는 05·06·09 항목은 방향키 네 개로 포인터 좌표를 옮길 수 있고, 07 항목은 같은 방향키로 공에 KEY_IMPULSE 만큼의 속도를 직접 더해 드래그 없이도 던지기를 재현한다. 나머지 항목은 입력 없이 정해진 계산만 반복하므로 prefers-reduced-motion 값이 켜지면 그대로 멈춘다.

마우스 따라다니는 효과는 물리 기반 애니메이션에서 무엇을 계산하는가?

계산 방식은 항목마다 다르다. 05번과 09번은 포인터와의 거리를 매 프레임 다시 재고 그 거리에 반비례하는 힘을 더하지만, 06번은 포인터 방향 가속만 매 프레임 새로 구하고 옆으로 도는 움직임은 공을 만들 때 한 번 준 초기 속도가 감쇠 아래서 남아 있는 결과라 계산 시점이 다르다. 벽에 부딪혀 각도가 바뀌는 계산은 벽돌깨기 게임 만들기 9가지 — 반사각은 맞은 위치가 정한다 가, 힘을 받다가 마찰로 멈추는 계산은 룰렛 돌리기 — 튕겨 돌리고 마찰로 멈추는 돌림판 캔버스 9종 이 각각 다룬다.

바운싱 볼 아홉 개에서 자주 나오는 질문

바운싱 볼 애니메이션에 외부 물리 엔진이 들어가나요?

아니다. 아홉 항목 모두 반발계수·질량·거리 같은 값을 매 프레임 자체 계산해 위치와 속도를 갱신할 뿐, 별도의 물리 엔진 라이브러리를 불러오지 않는다.

마우스 따라다니는 효과를 다른 화면 크기에 그대로 붙여도 되나요?

붙일 수 있다. 계산은 고정된 내부 해상도 위에서 이뤄지고 화면 크기에 맞춰 비율만 늘어나므로, 좁은 화면에서도 같은 반응이 나온다. 다만 앞서 다룬 것처럼 화면이 좁을수록 공 사이 간격은 시각적으로 더 좁아 보인다.

공 개수를 마음대로 늘려도 되나요?

소스에서 배열 길이나 상수를 바꾸면 늘어난다. 다만 08번처럼 공마다 나머지 전부와 겹침을 확인하는 방식이 들어간 항목은 공 수가 늘수록 프레임마다 계산량이 함께 늘어난다는 점을 감안해야 한다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.