오도미터

Odometer

자동차 계기판처럼 숫자 자릿수마다 세로로 감긴 릴이 돌아가며 값이 바뀌는 카운터. 텍스트가 순간적으로 바뀌는 게 아니라 다음 숫자가 위(또는 아래)에서 밀려 올라오는 것처럼 보입니다.

다른 이름: Rolling counterDigit reel
···
html
<div class="odo" id="odo"></div>
css
.odo{display:flex;gap:2px;background:var(--fg);padding:8px 10px;border-radius:10px}
.reel{width:clamp(16px,6vmin,28px);height:clamp(24px,8vmin,40px);overflow:hidden;position:relative;background:var(--surface);border-radius:4px}
.strip{display:flex;flex-direction:column;transition:transform .5s cubic-bezier(.3,.85,.4,1);
  transform:translateY(calc(var(--d, 0) * -10%))}
.strip div{height:clamp(24px,8vmin,40px);display:grid;place-items:center;
  font:700 clamp(14px,5.4vmin,22px)/1 ui-monospace,monospace;color:var(--fg)}
js
const odo = document.getElementById('odo');
const DIGITS = [0, 0, 0];
const strips = DIGITS.map(() => {
  const reel = document.createElement('div');
  reel.className = 'reel';
  const strip = document.createElement('div');
  strip.className = 'strip';
  for (let n = 0; n < 10; n++) {
    const d = document.createElement('div');
    d.textContent = String(n);
    strip.appendChild(d);
  }
  reel.appendChild(strip);
  odo.appendChild(reel);
  return strip;
});

let value = 0;
function render() {
  const s = String(value).padStart(3, '0');
  [...s].forEach((ch, i) => strips[i].style.setProperty('--d', ch));
}
render();
setInterval(() => {
  value = (value + 1 + Math.floor(Math.random() * 6)) % 1000;
  render();
}, 650);

자릿수마다 0~9가 세로로 나열된 "릴(strip)"을 만들고, 그 릴을 담는 칸은 숫자 하나 높이만큼만 overflow: hidden으로 잘라 보여줍니다. 목표 숫자로 바뀔 때 릴 전체를 translateY로 이동시켜 목표 숫자가 칸의 창(window) 안으로 들어오게 합니다 — 릴의 각 숫자 간격이 동일하므로 이동 거리는 (목표 숫자 - 이전 숫자) × 한 칸 높이로 정확히 계산됩니다.

CSS 변수(--d)에 현재 자릿값을 넣고 transform: translateY(calc(var(--d) * -10%))처럼 퍼센트 기반으로 잡으면, 실제 픽셀 높이를 몰라도(반응형 clamp() 크기에도) 항상 정확한 자릿수 위치로 맞아떨어집니다. transition에 약간의 ease-out을 주면 릴이 "달칵" 멈추는 대신 부드럽게 정지합니다.

9에서 0으로 넘어갈 때(예: 19 → 20)는 릴이 반대 방향으로 9칸을 거슬러 올라가는 것처럼 보일 수 있습니다 — 숫자 목록을 0~9를 두 벌 이어 붙여두고 항상 "앞으로만" 이동하도록 목표 인덱스를 계산하면 자연스럽게 넘어갑니다.

언제 쓰나

카운터가 눈에 띄게 오르내리는 통계·점수판에 씁니다. 자릿수가 많거나(6자리 이상) 값이 매우 자주 바뀌면 릴이 계속 어지럽게 움직이니, 갱신 빈도를 낮추거나 count-up으로 대체하세요.