GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

css countdown timer 9パターン — 残り時間がリング・砂・導火線で減るタイマー

css countdown timer の残り時間は、数字を読む前に形が縮む速さで先に感じ取られる — 01番のリングと03番の砂がその速さを計算で決める。九つの場面はどれも transition も @keyframes も使わず、JavaScript で計算した値だけで動きます。

九つのカウントダウンタイマーを画面でそのまま動かしてみる

自動再生 · タイルを押すとその項目へ · すべて1つのzip

css countdown timer 9パターン — 減衰・ばね・経路の長さで時間が見える

一つの場面は、時計の表示欄とボタンの行を8px間隔で縦に重ねたステージ(描画の撮影では480×300)です。ステージ内側の余白は clamp(8px, 4vw, 24px) なので幅480pxでは19.2pxになり、表示欄の高さは最大196px、320×200の画面では126pxまで縮みます。ボタンは「開始」(動作中は「一時停止」)と「リセット」の二つで、CSS で決めているのは高さ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の五つは倍速なしで、小数第1位までの秒を実際の長さのまま表示します。実演で見た拍と、自分で押して回したときの拍は、この四つの場面では違うということです。

並べる順は、縮んでいく面がどこから生まれるかで決めました。01は弧の長さが残り割合そのもの、02と05はばねの方程式を自前で積分し、03・04・09は種を固定した乱数で粒や星を動かします。06は桁の帯、08はセグメントの明るさ、07は水位の変化量から波の振幅を引き出す造りです。料理や試験の残り時間なら01から、イベントの開始なら02か09から、集中タイマーなら03から見るのが近道でしょう。数字が増えていく方向は CSS 数字 カウントアップ アニメーション9選 — 一行でコピペOK、値をゲージで見せる方向は CSS プログレスバー 円形ゲージ9選 — 数値を変えるだけ、期限つきの読み込み表示は qr コード アニメーション css 9選 — 残り時間 を横に開いておくと比べやすくなります。

表の値は、直近の描画で測った結果をそのまま移したものです。一つの場面につき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 7セグメント 46.8% 43.9 23/23
09 打ち上げ場面 47.1% 82.1 23/23

countdown timer design html css を技法別のコードで見る

コードの断片は、場面ごとの index.html の <script> の中から、つながった行だけをそのまま持ってきたもので、どれも25行以内です。countdown timer css codepen で見つけた断片と比べるときは、残り時間がどの変数から出てくるかを最初に見てください。ここでは remainMs() が elapsed から計算しています。定数の名前は各項目の文に書いたので、値を変える場所は const の行で探せます。

01残りの割合で色が移り、最後の区間で脈打つ減っていくリング

弧の長さは周の長さ301.6に残り割合を掛けた strokeDashoffset で縮め、同じ割合を三色(ミント・淡い黄・ピンク)の線形補間 blend に通して色を動かします。残り割合が PULSE_AT の0.4を下回ると、heartbeat の曲線が線の太さを9から最大15まで揺らします。調理タイマーや試験の残り時間の表示に向き、面積32.4%・強度59.4で20/23フレームが動きました。

残りの割合で色が移り、最後の区間で脈打つ減っていくリング
新しいタブで開く

01 · 残りの割合で色が移り、最後の区間で脈打つ減っていくリング — 計測 累積面積 32.376% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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を掛けて影の濃さにします。減衰は DAMPING = 2*0.55*sqrt(STIFFNESS) の式で決まり、その計算値ではばねは12.6%行き過ぎます。表示する角度は別に clamp(c.x, 0, 1.12)*180 で切られるので、最大は201.6°です。下の setTarget は、折れている途中で新しい数字が来ると前の折りを c.cur = c.nxt で確定して x を0に戻し、動きを減らす設定では折らずにその場で切り替えます。イベント開始のカウントダウンに合い、面積5.2%・強度110.0で22/23フレームが動きました。

折れる角度に合わせて影が回るフリップ数字カード
新しいタブで開く

02 · 折れる角度に合わせて影が回るフリップ数字カード — 計測 累積面積 5.244% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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 オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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 オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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%)行き過ぎ、減衰の周期は314.6msです。会議の時間表示や運動のインターバルに合い、面積15.0%・強度132.7で23/23フレームが動きました。

目盛りごとにひと跳ねして通った扇形が塗られる文字盤
新しいタブで開く

05 · 目盛りごとにひと跳ねして通った扇形が塗られる文字盤 — 計測 累積面積 15.03% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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 オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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 オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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消えたセグメントに残光が残る7セグLEDカウントダウン

セグメントごとに明るさ b を持たせ、点灯の瞬間に OVERDRIVE の1.45まで跳ね上げて approach で1まで落ち着かせます。消えたあとはフレームごとに DECAY の0.84倍へ減らし、蛍光体が冷めていく尾を残します。60fpsで数えると、DECAY から計算すると、消えてから明るさが0.05を下回るまで17.2フレーム、約286msかかります(0.05は計算上の基準で、ソースの実際の打ち切りは b < 0.004 です)。レトロな電光表示や家電の表示窓に合い、面積46.8%・強度43.9で23/23フレームが動きました。

消えたセグメントに残光が残る7セグLEDカウントダウン
新しいタブで開く

08 · 消えたセグメントに残光が残る7セグLEDカウントダウン — 計測 累積面積 46.838% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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 倍、つまり120に上げ、速さは1フレームあたり12%ずつ近づいて約300msで90%に届きます。製品発表のランディングやオープンイベントのヒーローに使え、面積47.1%・強度82.1で23/23フレームが動きました。

3・2・1ごとに衝撃波が広がり0で点火する打ち上げカウントダウンの場面
新しいタブで開く

09 · 3・2・1ごとに衝撃波が広がり0で点火する打ち上げカウントダウンの場面 — 計測 累積面積 47.081% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

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を下回ると、1フレーム最大12ステップの制限と50msの制限のせいで砂が時計より後ろに下がります。粒の数が上の部屋の収容量(首2マスで2302、4マスで2348)を超えると、許容量が実際の粒の数より大きくなります。格子は96×96で固定なので、窓の大きさが変わっても解像度は同じままです。

二桁・三桁を超える時間は桁が足りなくなる

08は COUNT_FROM が100以上だと MASKS[10] が undefined になり、mask.indexOf でエラーが出ます。二つの桁で表示できるのは99までです。06は三桁の表示なので、残り時間が9.99秒を超えると足りなくなるのは一桁分で、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 だけで足ります。JavaScript で組む側は、数字と一時停止が要る画面ではじめて意味が出ます。このカウントダウン九つは transition も @keyframes も0個で、値はすべて requestAnimationFrame の中で elapsed から計算しています。そうしておけば、一時停止とリセット、タブが戻ったときの飛び、実演の倍速を一か所で扱えるからです。カウントダウン タイマー css の考え方を数字が増える側へ広げるなら CSS 数字 カウントアップ アニメーション9選 — 一行でコピペOK、針で見せる時計なら css 時計 アナログ 9選 — 針の角度から歯車までが近く、03のように粒を格子で動かす造りは フォーリングサンド(falling sand simulator) 9種の作り方 へ続きます。キャンバスを使う03・04・07・09の描画の仕組みは キャンバスのアニメーションの文書に、タイムスタンプを受け取る側は requestAnimationFrame の文書にあります。

countdown timer css codepen のコードを使い回す前によくある質問

countdown timer design html css を JavaScript なしで使いたいときはどこを削りますか

この編の場面は JavaScript が時間を握っているので、そのままでは削れません。05の扇形の角度はすでに --a という CSS 変数で conic-gradient に渡されていますが、その値を毎フレーム計算しているのは JavaScript です。JavaScript なしで切り替える方法は、この記事では試していません。

全体の時間はどこで変えますか

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の格子が固定のため、マスが小さくなるだけで粒の数は同じです。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。