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

css countdown timer 9가지 — 링·모래·도화선으로 줄어드는 남은 시간

css countdown timer 의 남은 시간은 숫자로 읽기 전에 모양이 줄어드는 속도로 먼저 느껴진다 — 01번 링과 03번 모래가 그 속도를 계산으로 정한다.

아홉 가지 카운트다운 타이머를 화면에서 바로 돌려 본다

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

css countdown timer 9가지 — 감쇠·스프링·경로 길이로 시간이 눈에 보인다

장면 하나는 480×300 캡처 기준 무대 안쪽에 시계 보기 칸과 버튼 줄을 세로로 8px 간격으로 쌓은 모양이다. 무대 안쪽 여백은 clamp(8px, 4vw, 24px) 라서 480 폭에서 19.2px 이고, 보기 칸 높이는 최대 196px 이며 320×200 화면에서는 126px 로 줄어든다. 버튼은 시작·멈춤과 다시 둘이고 높이 36px, 프로브로 잰 폭은 각각 60px 이다.

아홉 장면 모두 시간을 날짜 객체에서 읽지 않고, requestAnimationFrame 이 건네는 타임스탬프 차이를 elapsed 에 쌓아 남은 시간을 만든다. 자동 재생은 전체 시간에 보류 구간을 더한 한 바퀴를 돌고 끝에서 0 을 잠깐 보여 준 뒤 처음으로 돌아가며, 사용자가 버튼을 하나라도 누르면 그 루프는 끝나고 이후에는 한 번만 돈다. 시연 한 바퀴는 01·03·04·06·07 이 1500ms 에 보류 300ms, 02·08 이 150ms 칸 12개, 05 가 150ms 칸 10개, 09 가 450ms 칸 3개다.

시간 표시에는 밝힐 것이 하나 있다. 02·05·08·09 는 시연 중에 「1초」 한 칸을 배속으로 줄여 돌리고 사용자가 버튼을 누르는 순간 1000ms 단위로 바뀌며, 01·03·04·06·07 은 배속 없이 소수 첫째 자리 초를 실제 길이 그대로 보인다. 그래서 시연 화면에서 본 박자와 직접 눌러 돌릴 때의 박자가 네 장면에서는 다르다.

순서는 줄어드는 면이 어디서 나오느냐로 잡았다. 01 은 호 길이가 남은 비율 그 자체이고, 02·05 는 스프링 방정식을 직접 적분하며, 03·04·09 는 시드가 고정된 난수로 입자와 별을 굴린다. 06 은 자릿수 띠, 08 은 세그먼트 밝기, 07 은 수위에서 파도 진폭을 끌어낸다. 요리나 시험 남은 시간이면 01 부터, 행사 오픈이면 02·09 부터, 집중 타이머면 03 부터 보면 된다. 숫자가 올라가는 쪽이 궁금하면 CSS 숫자 증가 애니메이션 9종 — 한 줄 복붙으로를, 값을 게이지로 보이는 쪽은 원형 프로그레스바 CSS 9종 — 값만 바꾸면 되는를, 만료 시각이 있는 로더는 qr 코드 만료 카운트다운 css 애니메이션 9가지를 같이 열어 두면 좋다.

표 값은 측정.json 에서 그대로 옮겼고, 한 장면당 24장을 찍어 앞뒤 프레임 차이로 움직임을 센 결과다. 면적이 10% 아래인 것은 02 와 03 이고, 움직인 프레임은 01 이 20/23, 02 가 22/23 이며 나머지 일곱은 23/23 이다.

항목 누적면적 강도 평균 움직인 프레임
01 링 32.4% 59.4 20/23
02 플립 카드 5.2% 110.0 22/23
03 모래시계 3.7% 177.8 23/23
04 도화선 57.0% 32.9 23/23
05 다이얼 15.0% 132.7 23/23
06 주행계 33.6% 101.1 23/23
07 액체 탱크 26.9% 137.4 23/23
08 칠세그먼트 46.8% 43.9 23/23
09 발사 장면 47.1% 82.1 23/23

countdown timer html css javascript 를 기법별 코드로 본다

조각은 장면별 index.html 의 <script> 안에서 이어진 줄만 그대로 가져왔고 하나가 25줄 이하다. countdown timer css codepen 에서 고른 조각과 견줄 때는 남은 시간이 어느 변수에서 나오는지부터 보면 되는데, 여기서는 remainMs() 가 elapsed 에서 계산된다. 상수 이름은 각 절의 문장에 적어 두었으니 값을 바꿀 자리는 const 줄에서 찾으면 된다.

01남은 비율로 색이 번지고 끝 구간에서 심박이 뛰는 줄어드는 링

호 길이는 strokeDashoffset 에 둘레 301.6 과 남은 비율을 곱해 넣어 줄이고, 같은 비율을 세 색(민트·연노랑·분홍) 사이 선형 보간 blend 에 넣어 호 색을 옮긴다. 남은 비율이 PULSE_AT 0.4 아래로 내려가면 heartbeat 곡선이 링 굵기를 9 에서 최대 15 까지 흔든다. 요리 타이머나 시험 남은 시간 표시에 어울리고, 면적 32.4%·강도 59.4 에 20/23 프레임이 움직였다.

남은 비율로 색이 번지고 끝 구간에서 심박이 뛰는 줄어드는 링
새 창에서 열기

01 · 남은 비율로 색이 번지고 끝 구간에서 심박이 뛰는 줄어드는 링 — 측정 누적면적 32.376% · 움직인 프레임 20/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

TOTAL_MSRING_RPULSE_ATHUE_STOPS
    function blend(u) {
        const s = clamp(u, 0, 1) * (stops.length - 1);
        const i = Math.min(stops.length - 2, Math.floor(s));
        const f = s - i;
        const a = stops[i];
        const b = stops[i + 1];
        return a.map((v, k) => Math.round(v + (b[k] - v) * f));
    }
    // 심박 곡선: 한 주기에 두 번 솟는다(쿵 쿵). 값은 0 에서 1 사이
    function heartbeat(ph) {
        const p = ph - Math.floor(ph);
        const lub = Math.exp(-Math.pow((p - 0.1) / 0.06, 2));
        const dub = 0.6 * Math.exp(-Math.pow((p - 0.3) / 0.07, 2));
        return Math.min(1, lub + dub);
    }

02접히는 각도에 맞춰 그림자가 따라 도는 플립 숫자 카드

윗 잎을 STIFFNESS 2200 의 스프링으로 접고 접힌 각도의 사인값에 SHADE_MAX 0.6 을 곱해 그림자 농도로 쓰며, 소스의 감쇠 식(2 * 0.55 * sqrt(STIFFNESS))으로 계산한 넘침은 12.6% 이고, 각도는 clamp(c.x, 0, 1.12) 로 잘려 최대 201.6°(180°×1.12)다. 아래 setTarget 은 접히는 중에 새 숫자가 오면 앞 접힘을 c.cur = c.nxt 로 굳히고 x 를 0 으로 되돌려 새로 접고, 움직임 줄이기에서는 접지 않고 바로 바꾼다. 이벤트 오픈 카운트다운에 어울리고, 면적 5.2%·강도 110.0 에 22/23 프레임이 움직였다.

접히는 각도에 맞춰 그림자가 따라 도는 플립 숫자 카드
새 창에서 열기

02 · 접히는 각도에 맞춰 그림자가 따라 도는 플립 숫자 카드 — 측정 누적면적 5.244% · 움직인 프레임 22/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

FLIP_MSSTIFFNESSSHADE_MAXDEPTH_PX
    function setTarget(c, d) {
        const shown = c.flipping ? c.nxt : c.cur;
        if (shown === d)
            return;
        if (c.flipping)
            c.cur = c.nxt;
        c.nxt = d;
        if (reduce) {
            c.cur = d;
            c.flipping = false;
            return;
        }
        c.flipping = true;
        c.x = 0;
        c.v = 0;
        c.age = 0;
    }

03모래알이 좁은 목을 지나 쌓이며 시간을 재는 모래시계

96×96 셀 격자에서 알갱이는 아래 칸이 비면 떨어지고 막히면 좌우 대각선 아래로 미끄러지며, 목 윗줄은 경과 시간에서 계산한 허용량 allowed 가 남았을 때만 통과시킨다. 목 너비 NECK_W 는 필요 유량(600 알갱이를 1.5초에, 초당 400)을 DROP_RATE 240 으로 나눈 칸 수를 짝수로 올린 2칸이다. 집중 타이머나 보드게임 제한 시간에 쓰고, 면적 3.7%·강도 177.8 에 23/23 프레임이 움직였다.

모래알이 좁은 목을 지나 쌓이며 시간을 재는 모래시계
새 창에서 열기

03 · 모래알이 좁은 목을 지나 쌓이며 시간을 재는 모래시계 — 측정 누적면적 3.686% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

GRAIN_COUNTNECK_WDROP_RATETOTAL_MS
    function step(allowed) {
        stepNo++;
        for (let r = GH - 2; r >= 0; r--) {
            const ltr = ((r + stepNo) & 1) === 0;
            for (let k = 0; k < GW; k++) {
                const c = ltr ? k : GW - 1 - k;
                if (grid[r * GW + c] === 0)
                    continue;
                if (canEnter(r + 1, c, allowed)) {
                    move(r, c, r + 1, c);
                    continue;
                }
                const first = rand() < 0.5 ? -1 : 1;
                if (canEnter(r + 1, c + first, allowed) && inside(r, c + first))
                    move(r, c, r + 1, c + first);
                else if (canEnter(r + 1, c - first, allowed) && inside(r, c - first))
                    move(r, c, r + 1, c - first);
            }
        }
    }
    function allowedAt(ms) {
        return Math.floor(GRAIN_COUNT * clamp(ms / totalMs, 0, 1) + 1e-6);
    }

04불붙은 점이 도화선 길이를 따라 걷고 불똥이 튀는 카운트다운

getPointAtLength 를 쓰지 않고 buildPath 가 181점을 직접 샘플링해 점마다 누적 길이 s 와 이웃 두 점 차이로 만든 법선을 두며, 불붙은 길이는 PATH_LEN 262.5 에 지난 비율을 곱한 값이다. 그 점에서 56개짜리 불똥 풀이 법선 방향 ±1.1 라디안 안으로 흩어지고, 이미 탄 구간은 어두운 선, 남은 줄은 점선으로 칠한다. 게임 시작 전 연출이나 퀴즈 제한 시간에 쓰며 면적 57.0% 로 아홉 가운데 가장 넓고, 강도는 32.9 에 23/23 프레임이 움직였다.

불붙은 점이 도화선 길이를 따라 걷고 불똥이 튀는 카운트다운
새 창에서 열기

04 · 불붙은 점이 도화선 길이를 따라 걷고 불똥이 튀는 카운트다운 — 측정 누적면적 57.0% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

PATH_LENEMBER_NBURN_MSSPARK_SPREAD
    function buildPath() {
        const N = 180;
        const pts = [];
        let len = 0;
        for (let i = 0; i <= N; i++) {
            const t = i / N;
            const x = 14 + t * 172;
            const y = 50 + 30 * Math.sin(t * Math.PI * 3.4) * (1 - 0.2 * t);
            if (i > 0)
                len += Math.hypot(x - pts[i - 1].x, y - pts[i - 1].y);
            pts.push({ x, y, nx: 0, ny: 0, s: len });
        }
        for (let i = 0; i <= N; i++) {
            const a = pts[Math.max(0, i - 1)];
            const b = pts[Math.min(N, i + 1)];
            const tx = b.x - a.x;
            const ty = b.y - a.y;
            const m = Math.hypot(tx, ty) || 1;
            pts[i].nx = -ty / m;
            pts[i].ny = tx / m;
        }
        return pts;
    }

05바늘이 눈금마다 한 박자 튕기며 지나온 부채꼴이 채워지는 다이얼

한 눈금이 지날 때마다 바늘 목표를 36° 옮기고, SPRING_K 520·DAMPING 22 의 스프링을 1/240초 단위로 적분해 바늘이 한 박자 튕기게 하며 지난 각도는 conic-gradient 층이 --a 만큼 채운다. SPRING_K·DAMPING 으로 계산한 감쇠비는 0.482 이고 36° 칸마다 6.4°(17.7%) 넘치며 감쇠 주기는 315ms 이므로, 150ms 간격의 시연에서는 바늘이 쉬지 못하고 실제 1000ms 칸에서만 눈금마다 자리를 잡는다. 회의 시간 표시나 운동 인터벌에 맞고, 면적 15.0%·강도 132.7 에 23/23 프레임이 움직였다.

바늘이 눈금마다 한 박자 튕기며 지나온 부채꼴이 채워지는 다이얼
새 창에서 열기

05 · 바늘이 눈금마다 한 박자 튕기며 지나온 부채꼴이 채워지는 다이얼 — 측정 누적면적 15.03% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

TICK_COUNTSPRING_KDAMPINGSECTOR_ALPHA
    function tick(dt) {
        passed = passedTicks();
        const target = passed * STEP_DEG;
        if (reduce) {
            angle = target;
            vel = 0;
            return;
        }
        let left = dt;
        while (left > 0) {
            const h = Math.min(left, 1 / 240);
            vel += (-SPRING_K * (angle - target) - DAMPING * vel) * h;
            angle += vel * h;
            left -= h;
        }
    }

06자릿수 띠가 빨리 돌수록 번지고 올림이 줄줄이 이어지는 주행계 숫자

남은 시간을 100분의 1초로 올림한 값의 세 자리를 띠 셋이 나눠 맡고, 띠마다 이동 속도(자리/초)를 프레임마다 재서 BLUR_GAIN 0.05 를 곱한 만큼 번짐을 주며 최대 5px 에서 막는다. 올림은 위 자리일수록 CARRY_DELAY 55ms 씩 늦게 도착해 줄줄이 굴러가고, 0.01초 자리는 초당 약 100자리라 번짐 5px·세로 늘림 1.4배에 붙는 반면 0.1초 자리는 약 10자리라 0.5px 에 그친다. 남은 재고나 세일 종료 초 표시에 어울리며 면적 33.6%·강도 101.1 에 23/23 프레임이 움직였다.

자릿수 띠가 빨리 돌수록 번지고 올림이 줄줄이 이어지는 주행계 숫자
새 창에서 열기

06 · 자릿수 띠가 빨리 돌수록 번지고 올림이 줄줄이 이어지는 주행계 숫자 — 측정 누적면적 33.637% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

DIGIT_COUNTSPIN_MSBLUR_GAINCARRY_DELAY
    function tick(dt) {
        cols.forEach((c, i) => {
            const tv = targetOf(i);
            if (reduce || dt <= 0) {
                c.pos = tv;
                c.vel = 0;
                return;
            }
            const before = c.pos;
            c.pos += (tv - c.pos) * (1 - Math.exp((-dt * 1000) / SPIN_MS));
            // 띠 속도: 프레임마다 위치 변화를 시간으로 나눠 잰다 (자리 수 / 1000ms)
            c.vel = Math.abs(c.pos - before) / dt;
        });
    }

07수위가 내려갈수록 출렁이고 멈추면 가라앉는 액체 탱크 타이머

수위는 남은 시간에 선형으로 내려가 MIN_LEVEL 0.16 밑으로는 가지 않고, sloshStep 이 수위가 변한 양에 GAIN 0.6 과 TANK_H 100 을 곱해 진폭을 키운 뒤 프레임마다 SLOSH_DAMP 배로 가라앉히며 AMP_MAX 18 에서 자른다. surfaceDy 가 정상파 네 모드를 겹쳐 수면을 그리고 파도 시계는 멈춤 중에도 흘러서, 멈추면 수위는 서고 파도만 남아 잦아든다. 물 마시기 알림이나 실험 실습 타이머에 쓰고, 면적 26.9%·강도 137.4 에 23/23 프레임이 움직였다.

수위가 내려갈수록 출렁이고 멈추면 가라앉는 액체 탱크 타이머
새 창에서 열기

07 · 수위가 내려갈수록 출렁이고 멈추면 가라앉는 액체 탱크 타이머 — 측정 누적면적 26.906% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

TANK_HWAVE_NSLOSH_DAMPDRAIN_MS
    function levelOf(rem) {
        return MIN_LEVEL + (1 - MIN_LEVEL) * (rem / totalMs);
    }
    // 수위가 변한 만큼 진폭을 키우고, 프레임마다 SLOSH_DAMP 배로 가라앉힌다 (60fps 기준 프레임 수로 환산)
    function sloshStep(a, dLevel, dt) {
        const next = a * Math.pow(SLOSH_DAMP, dt * 60) + GAIN * Math.abs(dLevel) * TANK_H;
        return Math.min(AMP_MAX, next);
    }
    // 정상파 합성: 모드 k 는 sin(kπx/W) 모양에 시간 위상이 따로 돈다
    function surfaceDy(x, a, t) {
        let y = 0;
        for (let k = 1; k <= WAVE_N; k++) {
            y += (a / k) * Math.sin((k * Math.PI * x) / TANK_W) * Math.cos(t * (9 + 5 * k) + k * 1.3);
        }
        return y;
    }

08꺼진 세그먼트에 잔광이 남는 칠세그먼트 LED 카운트다운

세그먼트마다 밝기 b 를 두고, 켜지는 순간 OVERDRIVE 1.45 로 치솟았다가 approach 로 1 까지 내려앉게 하며, 꺼지면 프레임마다 DECAY 0.84 배로 줄여 형광체가 식는 꼬리를 남긴다. DECAY 로 계산하면 켜진 밝기 1 이 60fps 에서 0.05 밑이 되기까지 17.2프레임, 약 286ms 가 걸리고(0.05 는 계산용 기준이고 소스에는 없다) 켜진 밝기의 합은 판 둘레 box-shadow 로도 번진다. 레트로 전광판이나 가전 표시창에 맞고, 면적 46.8%·강도 43.9 에 23/23 프레임이 움직였다.

꺼진 세그먼트에 잔광이 남는 칠세그먼트 LED 카운트다운
새 창에서 열기

08 · 꺼진 세그먼트에 잔광이 남는 칠세그먼트 LED 카운트다운 — 측정 누적면적 46.838% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

SEG_ONDECAYOVERDRIVEGLOW_PX
    // 밝기 b: 켜지는 순간 OVERDRIVE 로 치솟았다 SEG_ON 으로 내려앉고, 꺼지면 매 프레임 DECAY 배로 줄어든다
    function tick(dt) {
        const w = wanted();
        digits.forEach((segs, d) => {
            const mask = MASKS[w[d]];
            segs.forEach((s, k) => {
                const on = mask.indexOf(NAMES.charAt(k)) >= 0;
                if (reduce) {
                    s.b = on ? SEG_ON : 0;
                }
                else if (on) {
                    if (!s.on)
                        s.b = OVERDRIVE;
                    s.b = approach(s.b, SEG_ON, 0.16, dt);
                }
                else {
                    s.b *= Math.pow(DECAY, dt * 60);
                    if (s.b < 0.004)
                        s.b = 0;
                }
                s.on = on;
            });
        });
    }

093 2 1 마다 충격파가 퍼지고 0 에서 점화되는 발사 카운트다운 장면

숫자가 바뀔 때마다 WAVE_R 120 까지 커지며 옅어지는 충격파 링을 시간에서 바로 계산하고, 화면은 vnoise 값 노이즈로 SHAKE_AMP 5px 를 기준 삼아 흔든다. 0 이 되는 순간 tick 이 별 속도 목표를 40 의 STAR_BOOST 3 배인 120 으로 올리고, 속도는 프레임당 12% 씩 다가가 약 300ms 에 90% 에 닿는다. 제품 발표 랜딩이나 오픈 이벤트 히어로에 쓰고, 면적 47.1%·강도 82.1 에 23/23 프레임이 움직였다.

3 2 1 마다 충격파가 퍼지고 0 에서 점화되는 발사 카운트다운 장면
새 창에서 열기

09 · 3 2 1 마다 충격파가 퍼지고 0 에서 점화되는 발사 카운트다운 장면 — 측정 누적면적 47.081% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

COUNT_FROMWAVE_RSHAKE_AMPSTAR_BOOST
    function shownCount() {
        return Math.ceil(remainMs() / secondMs - 1e-6);
    }
    function resetState() {
        speed = 40;
    }
    function tick(dt) {
        if (reduce)
            return;
        const ignited = shownCount() === 0;
        const target = 40 * (ignited ? STAR_BOOST : 1);
        speed = approach(speed, target, 0.12, dt);
        for (let i = 0; i < STAR_N; i++) {
            sy[i] += (speed * sz[i] * dt) / 100;
            if (sy[i] > 1.05) {
                sy[i] -= 1.1;
                sx[i] = rand();
            }
        }
    }

값이 타이머의 인상을 바꾸는 곳 — 전체 시간·알갱이 수·출렁임 감쇠

이 타이머들에서 값 하나가 인상을 가장 크게 바꾸는 자리는 셋이다. 아래 숫자는 소스의 상수와 sloshStep 같은 함수를 노드에서 직접 불러 센 값이라, 화면을 눈대중으로 읽은 값이 아니다.

전체 시간을 절반으로 줄이면 박동이 한 번만 든다

01 의 TOTAL_MS 를 1500 에서 750 으로 줄이면 호가 줄어드는 속도는 둘레 301.6 기준 초당 201 에서 402 가 된다. 박동이 도는 마지막 구간은 600ms 에서 300ms 가 되는데 박동 주기 PULSE_MS 는 300 으로 고정이라 박동이 두 번에서 한 번으로 준다. 색은 같은 비율에서 같은 색을 지나지만 민트에서 분홍까지 가는 시간이 절반이다.

알갱이를 늘리면 목이 넓어지고 더미가 높아진다

03 의 GRAIN_COUNT 를 600 에서 1400 으로 올리면 필요 유량이 초당 400 에서 933 알갱이가 되어 목 너비가 2칸에서 4칸으로 계산되고, 아래 더미는 27줄에서 36줄로 자란다. 마지막 알갱이가 목 윗줄을 지나는 시각은 1504ms 에서 1533ms 로 늦어질 뿐이라 전체 시간 1500ms 에 거의 그대로 맞는다.

감쇠 계수를 올리면 파도가 상한 가까이 붙고 오래 간다

07 의 SLOSH_DAMP 를 0.92 에서 0.97 로 올리면 내려가는 동안의 진폭이 7.0 에서 17.9 로 커져 상한 18 바로 아래에 닿고, 수면 최대 편차는 11 에서 27 이 된다. 멈춘 뒤 진폭이 5% 밑으로 가라앉는 시간은 600ms 에서 1650ms 로 길어진다.

어디서 깨지는가 — 탭이 멈출 때·0 이하로 내려갈 때·움직임 줄이기 설정

탭이 멈췄다 돌아오면 시계는 건너뛰고 물리는 한 걸음만 간다

elapsed 는 돌아온 프레임의 간격을 통째로 더하므로 남은 시간은 곧바로 맞는 값으로 건너뛴다. 대신 물리 한 걸음은 Math.min(raw, 50)/1000 으로 잘라 한 번에 튀지 않게 하고, 02 는 접힘이 한 번만 돌고 숫자는 최종값으로 간다.

03 의 모래는 시계보다 늦어질 수 있다

모래는 600 알갱이에서 허용 시각보다 4ms, 1400 알갱이에서 33ms 늦게 목 윗줄을 지나고, 이 값과 아래 수용량은 노드에서 소스 구간을 직접 불러 센 것이다. 프레임이 20fps 밑으로 내려가면 한 프레임 최대 12스텝 제한과 50ms 제한 때문에 모래가 시계보다 뒤처지고, 알갱이 수가 위 방 수용량(목 2칸에서 2302, 4칸에서 2348)을 넘으면 허용량이 실제 알갱이 수보다 커진다. 격자는 96×96 고정이라 창 크기가 변해도 해상도는 그대로다.

두 자리와 세 자리를 넘는 시간은 자리가 모자란다

08 은 COUNT_FROM 이 100 이상이면 MASKS[10] 이 undefined 라 mask.indexOf 에서 오류가 나고 두 칸으로 99 까지만 표시한다. 06 은 남은 시간이 9.99초를 넘으면 1초 자리 위가 없어 자리가 하나 모자라니 DIGIT_COUNT 와 targetOf 의 자릿수 나눗셈을 같이 고쳐야 한다. 07 은 MIN_LEVEL 0.16 때문에 시간이 0 이어도 물이 16% 남는다.

시연 배속과 실제 1초 칸은 박자가 다르다

05 의 시연 눈금 간격 150ms 는 감쇠 주기 315ms 의 절반이라 바늘이 쉬지 못하는데, 실제 모드에서만 눈금마다 가라앉는다. 09 는 WAVE_MS 420 이 시연 칸 450ms 보다 짧아 링이 한 개씩 보이고, 실제 1000ms 칸에서는 링이 지난 뒤 580ms 가 비며, 끝난 뒤에도 별은 120 으로 계속 달린다. 02 는 FLIP_MS 140 이 칸 시간 150ms 와 10ms 차이뿐이라 칸 시간을 더 줄이면 접힘이 끝나기 전에 다음 숫자가 오고, 숫자를 건너뛴 것처럼 보인다.

상한과 풀 크기가 먼저 걸리는 값들

06 의 BLUR_GAIN 을 올려도 번짐은 5px 에서 막힌다. 07 의 진폭은 AMP_MAX 18 에서 잘려 감쇠 0.97 보다 더 올리면 파도 꼭대기가 평평해진다. 04 의 불똥은 56개 고정 풀이라 방출률이 EMBER_N 보다 높아지면 살아 있는 입자를 덮어쓰고, 멈춤 중에는 새 불똥이 나오지 않지만 떠 있던 것은 끝까지 떨어진다.

움직임 줄이기에서는 정지한 채 시작하고 그리기는 250ms 마다 한 번이다

prefers-reduced-motion: reduce 를 켜면 아홉 장면이 정지한 채 시작하고, 사용자가 시작하면 시간은 흐르되 그리기는 ts - lastDraw < 250 이면 건너뛴다. 02 는 접지 않고 숫자만 교체하고, 03 은 낙하 없이 허용량까지 step 을 한꺼번에 굴린 모양을 보이며, 04·09 는 입자와 충격파를 그리지 않는다. 05·06 은 바늘과 띠가 목표 위치로 바로 가고, 07 은 진폭이 0 이라 수면이 평평하며, 08 은 밝기가 켜짐 1 아니면 꺼짐 0 이다. 브라우저에서 이 설정을 켜고 시작 1.4초 뒤에 읽은 값은 01·03·04·07 이 1.5초에서 0.3초, 05 가 10에서 9, 09 가 3.0에서 1.8, 02 가 12에서 11 이었고, 06·08 은 글자가 아니라 띠 위치와 SVG 밝기로 그려져 화면 글자로는 읽지 못해 소스 줄(pos = tv, b = on ? 1 : 0)로만 확인했다.

직접 고쳐 보며 경계를 확인하는 법

01 의 TOTAL_MS, 03 의 GRAIN_COUNT, 07 의 SLOSH_DAMP 를 하나씩 바꿔 돌리면 위 숫자가 어디서 바뀌는지 손으로 확인된다. 내려받는 압축 파일은 pu69hb35 을 입력하면 풀리고, 안에는 vanilla/ 와 react/ 폴더, 그리고 prompt.md·README.md 가 들어 있다.

변형 세 가지와 접근성

이름 값 느낌
전체 시간 바꾸기 01 TOTAL_MS 1500 → 750 호가 줄어드는 속도가 초당 201 에서 402 로 두 배가 되고 박동은 두 번에서 한 번만 든다.
모래알 수 바꾸기 03 GRAIN_COUNT 600 → 1400 목이 2칸에서 4칸으로 넓어지고 더미가 27줄에서 36줄로 높아지며 마지막 알갱이는 33ms 늦게 지난다.
출렁임 감쇠 바꾸기 07 SLOSH_DAMP 0.92 → 0.97 진폭이 7.0 에서 17.9 로 커지고 멈춘 뒤 잦아드는 시간이 600ms 에서 1650ms 가 된다.

변형 표의 숫자도 위 세 절과 같은 노드 호출에서 나온 값이다.

컨트롤은 아홉 장면 모두 네이티브 <button> 둘이고 keydown·preventDefault·touch-action 은 한 줄도 없어서, Enter 와 Space 는 브라우저의 기본 버튼 동작에 맡긴다. 프로브로 Space 를 한 번 누르면 버튼 글자가 「멈춤」에서 「시작」으로 바뀌고 aria-pressed 가 true 에서 false 가 되며 상태 줄에 「멈췄습니다」가 적힌다. 숫자를 읽는 칸은 아홉 장면 모두 role="timer" 이고, 상태 줄 aria-live 는 장면마다 하나씩 있어 멈출 때와 끝날 때(「시간이 다 됐습니다」)에만 글을 쓰므로 매초 읽히지 않는다. role="img" 는 01·03·04·05·07·09 여섯 장면에 있고 02·06·08 은 role="timer" 칸의 aria-label 로 읽힌다. 포커스 표시는 :focus-visible 의 2px 윤곽선이며 320×200 에서 가로·세로 넘침은 아홉 장면 모두 0 이다. 움직임 줄이기 동작은 prefers-reduced-motion 문서의 미디어 쿼리를 그대로 쓴다.

css only countdown timer 만으로 충분한가?

남은 시간을 숫자로 보여 줄 필요가 없고 멈춤·다시 버튼도 없는 장식이면 CSS 만으로 충분하다. countdown timer html css javascript 로 직접 짜는 쪽은 숫자와 멈춤이 필요한 화면에서 비로소 값이 생긴다. 하지만 이 편의 아홉 장면은 transition 과 @keyframes 가 0 개이고 모든 값이 requestAnimationFrame 안에서 elapsed 로 계산되는데, 그래야 멈춤과 다시, 탭 복귀 때 건너뛰기, 시연 배속을 한 곳에서 다룰 수 있기 때문이다. 숫자가 올라가는 카운터는 CSS 숫자 증가 애니메이션 9종 — 한 줄 복붙으로가, 시계 바늘은 자바스크립트 아날로그 시계 9종 — 바늘 각도부터 톱니까지가 더 가깝고, 03 처럼 입자를 격자로 굴리는 쪽은 폴링 샌드(falling sand simulator) 9종 — 모래·물·불 셀 오토마타에서 이어진다. 캔버스를 쓰는 03·04·07·09 의 그리기 원리는 캔버스 애니메이션 문서에, 타임스탬프를 받는 쪽은 requestAnimationFrame 문서에 있다.

countdown timer css codepen 코드를 가져다 쓰기 전에 자주 묻는 질문

css only countdown timer 처럼 JS 없이 쓰고 싶으면 어디를 줄이나?

이 편의 장면은 JS 가 시간을 쥐므로 그대로는 줄일 수 없다. 05 의 부채꼴은 이미 --a 라는 CSS 변수 하나로 그려지지만, 그 값을 @keyframes 로 돌려 JS 없이 쓰는 방향은 이 편에서 해 보지 않았으니 직접 시험해 볼 몫이다.

전체 시간은 어디서 바꾸나?

01·03·06 은 TOTAL_MS, 04 는 BURN_MS, 07 은 DRAIN_MS 이고, 02·08·09 는 COUNT_FROM(초 개수), 05 는 TICK_COUNT 이다. 06 은 9.99초를 넘기지 말고 08 은 99 를 넘기지 않아야 한다.

화면이 작은 폰에서는 어떻게 보이나?

보기 칸 높이가 min(calc(100vh - 2 * clamp(8px, 4vw, 24px) - 48px), 196px) 이라 320×200 에서는 126px 로 줄고 넘침은 0 이다. 03 은 96×96 격자가 고정이라 칸만 작아지고 알갱이 수는 같다.

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

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