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

css リスト デザイン 9選 — マーカーが計算で動く箇条書きの装飾

9つのリストマーカーのうち、1つはダイヤル角度の値が項目ごとに積み、箇条書きの点が時計のように回り、1つはカウンター値が桁ごとに分解されて数字が走行計のように転がり、1つは素数判定の値によってその番号のマーカーだけが光を放ちます。

トリガーは9項目すべてclickリスナー1つずつです — ボタンの役割だけがそれぞれ違い、01は次の段階を進め、02は一位の値を上げ、03は倍速を切り替え、04は行進を止めてまた動かし、05は開始番号を動かし、06は集計を巡回させ、07は深さを公開し、09は全体を提出し、08だけがリストの各行のマーカーボタンが状態を切り替えます。自動デモはis-demoクラスが残っていてprefers-reduced-motionが偽のときだけ回り、最初のクリックで外れますが、04はreduceの判定を自動デモの条件の外に置いているため、人が操作し始めた後もその設定を守り続けます。マーカーは9つとも::markerにできないことをするため、list-style: noneでネイティブのマーカーを消して同じ場所に立て直したもので、01・02・05は<ol>、残りの6つは<ul>の上で回ります。

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

9つのパターンの解剖 — どの計算がマーカーを動かすか

項目ごとに画面をどれだけ塗り替えたか(累積面積)、フレーム間のピクセル差がどれだけ強かったか(強度)、24枚のキャプチャ中何枚で動いたか(動いたフレーム)を測ると、次の表になります。

項目 累積面積% 強度平均 動いたフレーム
01 2.81% 70.2 17/23
02 0.19% 115.5 19/23
03 0.93% 142.2 23/23
04 0.42% 124.9 23/23
05 2.42% 27.8 20/23
06 1.28% 98.1 15/23
07 0.93% 47.0 10/23
08 1.21% 109.4 11/23
09 0.37% 91.0 10/23

累積面積1位は01(2.81%)です — クリックのたびに番号に比例して複数の針が一斉に回り、アクティブなハイライトまで重なるため、ピクセルが変わる範囲が最も広くなります。最下位は02(0.19%)で、転がる数字が狭い枠の中だけを変えるためです。07は表では03と同じ0.93%に見えますが、測定の小数では03(0.931)がわずかに大きい値です。強度が140を超える項目は03(142.2)ひとつだけで、最低の05(27.8)はグローがアルファで広がる低コントラストな変化のためフレーム差が小さくなります。動いたフレームは03・04が23/24で最多、07・09が10/24で最少ですが、これは欠点ではなく段階の間に留まる静止区間の長さが違うだけです。

計算の軸で分けると4つの系統になります。

累積した値でマーカーを押す01・02・09

01はクリック1回がturns[i]に積み上がり、針の角度はそこに番号を掛けて大きくなります。02はカウンター値を10のべき乗で割って桁ごとに分解し、09は弾ける時刻をi * DELAY_STEP_MSで累積します。3つとも今の入力ではなく、通り過ぎた回数・順番がマーカーに刻まれる系統です。

流れる時間を入れる03・04

03はsin(t * speed + i * PHASE_RAD)で共通の時間に項目番号の分だけ位相をずらし、同じ瞬間でも違う高さを作ります。04はacc += dt/1000 * MARCH_RATE_DEGでオフセットを経過時間に定速で積分します。どちらも前の状態ではなく、そのフレームの時間が値を決める点が同じです。

判定式が点けたり消したりする05・07

05は番号ごとにisPrime()の割り算判定をやり直し、通過した項目だけにis-primeクラスを付けます。07はrevealed >= depthという比較式ひとつで全レールのis-openを即座に更新します。あらかじめ決めた一覧ではなく、値が変わるたびに判定が再び回る点がこの系統の性質です。

比例式が大きさを測る06・08

06はfactor = sqrt(v)/sqrt(max)の平方根比例で面積が値に比例するように半径を決めます。08はmax(1.5, BASE_AREA/size²・3)の面積保存式で、大きくなるマーカーほど線を細く逆算します。値の大きさがマーカーの大きさ・重さに換算される系統です。

パターン別コード

01ダイヤル回転マーカー — 項目番号に角度の値を積み、点が時計のように回る方式

クリック1回が項目の通り過ぎた回数(turns[i])に積み上がり、針の角度はそこに項目番号(i+1)とDIAL_STEP_DEG(30)を掛けた値になるため、番号が大きい項目ほど同じ1クリックでも大きく回ります。自動再生でもSTEP_MS(480)ごとに1段ずつ進みます。

数値が語ること

項目01は23フレーム中17枚で動き、累積面積2.81%は9項目中で最大です — クリックのたびに番号に比例した複数の針が同時に回り、アクティブなハイライトも重なるため、変化する範囲が最も広くなります。強度平均70.2は9項目の中間に位置します。

  // 計算層: 項目iの針の角度は、その項目がこれまで通り過ぎた回数(turns[i])に自分の番号(i+1)を掛けて積み上がる。
  // 下の項目ほど同じ1回の前進でも大きな角度を受け取る — 単純な遅延ではなく番号に比例した累積である。
  var turns = items.map(function () { return 0; });

  function render() {
    items.forEach(function (el, i) {
      var deg = turns[i] * DIAL_STEP_DEG * (i + 1);
      el.style.setProperty('--angle', (deg % 360) + 'deg');
    });
  }
ダイヤル回転マーカー — 項目番号に角度の値を積み、点が時計のように回る方式
新しいタブで開く

01 · ダイヤル回転マーカー — 項目番号に角度の値を積み、点が時計のように回る方式 — 計測 累積面積 2.805% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

DIAL_STEP_DEGrotate$subject-blue

02走行計カウンター — カウンター値を桁に分解し、数字マーカーが転がる方式

カウンター値をplace = 10^(ODO_DIGITS-1-d)で割って桁ごとに分解し、0から9までのストリップをtranslateY(-digit * DIGIT_H)で引き上げます。桁ごとの転換速度は260 + d * 220msで、高い桁(左, d=0)が260msで最も速く止まり、低い桁(右)ほど遅れて追いつきます。

数値が語ること

項目02は23フレーム中19枚で動きますが、累積面積0.19%は9項目中で最小です — 転がる数字が狭いストリップの枠の中だけを変えるためです。強度平均115.5は、面積は小さくてもピクセル差そのものは大きく出ることを示します。

  // 計算層: カウンター値を10のべき乗で割って桁に分解する。桁ごとの枠はそれぞれ違う転換速度を持ち
  // 低い桁から先に位置が決まり(速い)、高い桁が後から続く(遅い) — 単純なopacityフェードではなく桁ごとのロールである。
  function setDigits(odo, value) {
    var strips = odo.querySelectorAll('.odo-strip');
    for (var d = 0; d < ODO_DIGITS; d++) {
      var place = Math.pow(10, ODO_DIGITS - 1 - d);
      var digit = Math.floor(value / place) % 10;
      strips[d].style.transform = 'translateY(' + (-digit * DIGIT_H) + 'px)';
    }
  }

  function speedClass(odo) {
    var strips = odo.querySelectorAll('.odo-strip');
    strips.forEach(function (s, d) {
      // d=0(最も左=最も高い桁)が最も遅く、右(低い桁)ほど速い
      var ms = 260 + d * 220;
      s.style.transitionDuration = ms + 'ms';
    });
  }
  odos.forEach(speedClass);
走行計カウンター — カウンター値を桁に分解し、数字マーカーが転がる方式
新しいタブで開く

02 · 走行計カウンター — カウンター値を桁に分解し、数字マーカーが転がる方式 — 計測 累積面積 0.189% · 動いたフレーム 19/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ODO_DIGITStranslateY$subject-blue-light

03位相波マーカー — 項目ごとに位相の値をずらし、点が波打つ方式

点の縦位置は振幅AMPLITUDE_PX(6)のサイン値で決まり、位相を項目番号の分だけ(i * PHASE_RAD)ずらすため、同じ瞬間でもリスト全体が1本の波のように上下します。SPEEDS [1, 2, 0.5]のプリセットをクリックで巡回します。

数値が語ること

項目03は23フレーム中23枚で動き、04と並んで9項目中最多です。強度平均142.2は9項目の中で唯一140を超え最も高い値です — 小さな点が振幅6pxでコントラスト高く上下するため、フレーム間のピクセル差が大きくなります。

  // 計算層: 各点の縦位置は、サイン関数に項目番号の分だけ位相を押し込んだ値である。
  // t(共通の時間)は流れるが、位相が違えば同じ瞬間でも高さが変わる — これが「波」の原理である。
  function apply(t) {
    var speed = SPEEDS[speedIdx];
    items.forEach(function (el, i) {
      var phase = i * PHASE_RAD;
      var y = AMPLITUDE_PX * Math.sin(t * speed + phase);
      el.style.setProperty('--y', y.toFixed(2) + 'px');
    });
  }
位相波マーカー — 項目ごとに位相の値をずらし、点が波打つ方式
新しいタブで開く

03 · 位相波マーカー — 項目ごとに位相の値をずらし、点が波打つ方式 — 計測 累積面積 0.931% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

Math.sintranslateY$subject-blue-deep

04行進ダッシュマーカー — オフセットの値を流し、ダッシュが行進する方式

点の枠のダッシュのオフセット角を、毎フレームacc += dt/1000 * MARCH_RATE_DEG_S(140)で経過時間に定速で積分するため、点線の枠がそのまま流れ続けます。止めてまた動かしても値が引き継がれ、流れが途切れません。

数値が語ること

項目04は23フレーム中23枚で動き、03と並んで最多です。強度平均124.9は9項目の中でも上位で、累積面積は0.42%です — 太さの細い点線の枠だけが動くため、面積そのものは小さくまとまります。

  var last = 0;
  var acc = 0;
  function loop(ts) {
    if (!last) last = ts;
    var dt = ts - last;
    last = ts;
    if (reduce) {
      apply(90); // 計算は維持、時間短縮の代わりに4分の1回転の地点へ即座に固定(自動ループ・実使用共通)
    } else if (running) {
      acc += dt / 1000 * MARCH_RATE_DEG_S;
      apply(acc);
    }
    requestAnimationFrame(loop);
  }
行進ダッシュマーカー — オフセットの値を流し、ダッシュが行進する方式
新しいタブで開く

04 · 行進ダッシュマーカー — オフセットの値を流し、ダッシュが行進する方式 — 計測 累積面積 0.421% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

MARCH_RATE_DEG_Sdashed$subject-blue-light

05素数グローマーカー — 素数判定の値を計算し、該当番号だけが光る方式

項目番号ごとにisPrime()の割り算判定を直接やり直し、通過した番号のマーカーだけがグローを放ちます。開始番号を動かすたびに判定は最初から回り、番号はlist-style: noneで消してからcounter-reset・counter-incrementとcontent: counter(item)の値で::beforeに描き直します。

数値が語ること

項目05は23フレーム中20枚で動き、累積面積2.42%は9項目中で01に次いで2番目に大きい値です。強度平均27.8は9項目の中で最も低く、アルファで広がるグローの低コントラストな変化がフレーム間の差を小さくしています。

  // 計算層: 各項目番号に素数判定を直接計算する — あらかじめ決めた一覧ではなく、実行のたびに再び回る。
  function isPrime(n) {
    if (n < 2) return false;
    for (var d = 2; d * d <= n; d++) {
      if (n % d === 0) return false;
    }
    return true;
  }

  function recompute() {
    list.setAttribute('start', String(start));
    items.forEach(function (li, i) {
      var n = start + i;
      li.classList.toggle('is-prime', isPrime(n));
    });
  }
素数グローマーカー — 素数判定の値を計算し、該当番号だけが光る方式
新しいタブで開く

05 · 素数グローマーカー — 素数判定の値を計算し、該当番号だけが光る方式 — 計測 累積面積 2.419% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

counterbox-shadow$subject-blue-deep

06サイズ順位マーカー — 値に比例してマーカーの面積を計算する方式

マーカーの半径をfactor = Math.sqrt(v) / Math.sqrt(max)の平方根比例で決めるため、面積が値に比例して見えます — 線形比例だと大きな値のマーカーが誇張されて見えてしまいます。集計値はMath.randomなしの決定論的なプリセットをSTEP_MS(550)ごとに巡回します。

数値が語ること

項目06は23フレーム中15枚で動き、累積面積1.28%、強度平均98.1です。マーカーの大きさが変わる局所的な変化のため、面積・強度とも9項目の中間に位置します。

  // 計算層: マーカー半径は値の平方根に比例する(面積が値に比例するように) — 線形比例は大きな値が誇張されて見える。
  function apply(set) {
    var max = Math.max.apply(null, Object.keys(set).map(function (k) { return set[k]; }));
    items.forEach(function (li, i) {
      var key = li.getAttribute('data-key');
      var v = set[key];
      var factor = Math.sqrt(v) / Math.sqrt(max);
      markers[i].style.setProperty('--scale', factor.toFixed(3));
    });
  }
サイズ順位マーカー — 値に比例してマーカーの面積を計算する方式
新しいタブで開く

06 · サイズ順位マーカー — 値に比例してマーカーの面積を計算する方式 — 計測 累積面積 1.283% · 動いたフレーム 15/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

Math.sqrtscale$subject-blue

07階層レールマーカー — 深さの値でインデントレールを計算する方式

ネストした<ul>それぞれにdata-depthを持たせ、revealed >= depthという比較式1つで全レールの表示可否を即座に更新します — レールごとの個別タイマーではありません。レール線は::beforeをscaleY(0)からscaleY(1)へ伸ばします。

数値が語ること

項目07は23フレーム中10枚しか動かず、09と並んで9項目中最少です。累積面積は0.93%で表では03と同じ値に見えますが、測定の小数では03(0.931)がわずかに大きく、07は0.93という値です。強度平均47.0は9項目の中でも低めです。

  // 計算層: レールそれぞれの表示可否は「今公開されている深さ ≥ このレールの深さ」という比較式の結果である。
  // 値ひとつ(revealed)が変わると、それより浅いレールは全部即座に更新される — レールごとの個別タイマーではない。
  function apply() {
    tree.style.setProperty('--revealed', String(revealed));
    rails.forEach(function (rail) {
      var d = parseInt(rail.getAttribute('data-depth'), 10);
      rail.classList.toggle('is-open', revealed >= d);
    });
    button.setAttribute('aria-expanded', String(revealed >= MAX_DEPTH));
  }
階層レールマーカー — 深さの値でインデントレールを計算する方式
新しいタブで開く

07 · 階層レールマーカー — 深さの値でインデントレールを計算する方式 — 計測 累積面積 0.93% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

scaleYrevealed$subject-blue-light

08ハイブリッド入れ替えマーカー — 状態の値でマーカーの種類が変わる方式

状態ごとの直径SIZE_BY_STATE [14, 20, 24]に対してthicknessFor(size) = Math.max(1.5, BASE_AREA / (size * size) * 3)の面積保存式で太さを逆算するため、マーカーが大きくなるほど線が細くなり、状態が変わっても見た目の重さが噛み合います。マーカーの種類は状態ごとに点・ダイヤ・チェックへ切り替わります。

数値が語ること

項目08は23フレーム中11枚で動き、09に次いで少ない部類です。累積面積1.21%、強度平均109.4で、マーカーの大きさと形が同時に変わる局所的な変化がこの値を作ります。

  // 計算層: 太さは「面積保存」式(BASE_AREA / size)で逆算する — 大きくなるマーカーほど線が細くなり
  // 視覚的な重さが互いに噛み合う。任意の固定太さ3種を並べたものではない。
  function thicknessFor(size) {
    return Math.max(1.5, BASE_AREA / (size * size) * 3);
  }

  function apply(li) {
    var state = parseInt(li.getAttribute('data-state'), 10);
    var btn = li.querySelector('.marker');
    var size = SIZE_BY_STATE[state];
    var thick = thicknessFor(size);
    btn.style.setProperty('--size', size + 'px');
    btn.style.setProperty('--thick', thick.toFixed(2) + 'px');
    btn.className = 'marker state-' + state;
    btn.setAttribute('aria-label', '状態切替: ' + STATES[state]);
    btn.textContent = STATES[state];
  }
ハイブリッド入れ替えマーカー — 状態の値でマーカーの種類が変わる方式
新しいタブで開く

08 · ハイブリッド入れ替えマーカー — 状態の値でマーカーの種類が変わる方式 — 計測 累積面積 1.21% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

BASE_AREAthicknessFor$subject-mint

09連鎖チェックマーカー — 遅延の値を積み、チェックが連鎖で弾ける方式

項目iが弾ける時刻をi * DELAY_STEP_MS(110)の累積で決め、CSS変数--delayに載せてanimation-delayにそのまま渡します。JavaScriptはクラスのトグル1回だけを行い、連鎖のタイミングはCSSが計算します。

数値が語ること

項目09は23フレーム中10枚で動き、07と並んで9項目中最少です。累積面積0.37%、強度平均91.0で、チェックが刻まれる瞬間だけがピクセルを動かすため、面積は9項目の中でも下から2番目に小さい値です。

  // 計算層: 項目iが弾ける時刻は、遅延の値をi回累積した値(i * DELAY_STEP_MS)である — CSS変数に載せて
  // animation-delayにそのまま渡す。JavaScriptはトリガー1回(クラスのトグル)だけを行い、連鎖はCSSが計算する。
  items.forEach(function (li, i) {
    li.style.setProperty('--delay', (i * DELAY_STEP_MS) + 'ms');
  });

  function human() { if (isDemo) { wrap.classList.remove('is-demo'); isDemo = false; } }

  function burst() {
    list.classList.remove('is-bursting');
    // reflowを強制して、同じクラスを再び付けてもアニメーションが再スタートするようにする
    void list.offsetWidth;
    list.classList.add('is-bursting');
  }
連鎖チェックマーカー — 遅延の値を積み、チェックが連鎖で弾ける方式
新しいタブで開く

09 · 連鎖チェックマーカー — 遅延の値を積み、チェックが連鎖で弾ける方式 — 計測 累積面積 0.367% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

DELAY_STEP_MSanimation-delay$subject-mint

値がマーカーを変える基準 — 角度・桁・位相・判定

一歩を決める定数たち

各項目の表情は名前付きの定数が決めます — 01のDIAL_STEP_DEG(30)・STEP_MS(480)、02のODO_DIGITS(2)・DIGIT_H(18)、03のWAVE_PHASE_DEG(40)・AMPLITUDE_PX(6)、04のMARCH_RATE_DEG_S(140)、05のSTEP_MS(900)、06のR_MAX(15)、07のMAX_DEPTH(2)、08のBASE_AREA(400)、09のDELAY_STEP_MS(110)・CYCLE_MS(950)です。css リスト マーカー の 種類と言うと普通はlist-style-typeで不吉マークを入れ替える話を指しますが、この記事で値を触る場所は形の一覧ではなくこれらの定数です。なお05のグロー脈動の周期はJavaScriptの定数ではなく、scssの$dur-loop(2s)トークンが決めており、GLOW_PULSE_MS(1100)は宣言だけが残る未使用の変数です。

隣接する枝 — リスト軸の他の記事

項目を開閉する操作はアコーディオンリスト UI 9選 — チェックリストまでが扱っており、この記事は項目の前に立つマーカーの計算だけを扱います。項目を押し出して消したりページを送る操作はスワイプ削除リストUI 9選 — ページネーションまでで、数字を積み上げる動きはCSS 数字 カウントアップ アニメーション9選 — 一行でコピペOKで扱っています — この記事の02の走行計は値を桁に分解して転がす点で、単純な数字の積み上げとは分かれます。リストの文法そのものはlist-style文書とCSS counters文書に従います。

どこで壊れるか — list-styleの対応範囲、フォールバック、reduced-motion

::markerが直接受け取れるプロパティはcolor・content・fontの3つだけで、回転・移動・拡大・グロー・アニメーション・子要素はすべて不可能です。そのため9項目すべてがlist-style: noneでネイティブのマーカーを消し、同じ場所に立て直しています — 01・03・05・09はliの::before・::afterで立て直し、02・04・06・08は転がる数字ストリップやクリック可能なボタンを収める必要があるため実際のDOM要素を置き、07はネストした<ul>そのものに::beforeのレールを付けます。番号を描き直すのは05だけで、counter-reset・counter-incrementとcontent: counter(item)の値を使います。css リスト デザイン おしゃれを探してこのページに来た人にとって、その最初の一手はすでに9項目全部に入っています。

9項目のhtml・scss・js全体は6tdgyv9rの圧縮パスワードで受け取り、定数を直接書き換えながら開いて試せます。

reduced-motionが残すもの

reduce設定が入ると、01はturnsをi+1の終了状態へ、02は値を[42, 97, 5]の結果へ、03は位相をπ/2の山の頂点へ、04はオフセットを90度へ即座に固定します。05はパルスを止めて4pxの静的なリングを残し、06は2つ目のプリセットを固定し、07は完全に開き切り、08は行ごとに違う状態を、09はチェックが全部埋まった完了状態を見せます。CSS側にも項目ごとにreduced-motionのブロックがあり、遷移をすべて断ちます。

1ページに9つを詰め込むとrequestAnimationFrameのループが同時に9つ回ることになり、低スペック機ではフレームが詰まることがあります — 実サービスに載せるときは必要なマーカーをひとつだけ残すのが安全です。

3つのバリエーションとアクセシビリティ

バリエーション 値 感触
ダイヤル角度を細かくする 01 DIAL_STEP_DEG 30 → 15 DIAL_STEP_DEG を15に下げると1回の前進で針が受ける角度が小さくなり(turns[i] * DIAL_STEP_DEG * (i+1))、次の目盛りまでにより多くのクリックが要る — ダイヤルが細かい歩みで上がっていく
走行計の桁を増やす 02 ODO_DIGITS 2 → 3 ODO_DIGITS を3に上げると桁の分解が百の位まで広がり(Math.floor(value / place) % 10, place = 10^(ODO_DIGITS-1-d))、000から999までの3桁の走行計として転がる — 範囲は2倍ではなく10倍に広がる
波の位相を広げる 03 WAVE_PHASE_DEG 40 → 80 WAVE_PHASE_DEG を80に上げると隣接項目間の位相間隔が広がり(i * WAVE_PHASE_DEG * π/180)、同じ瞬間にリスト全体へより多くの波の山が入る — 緩やかに広がる波ではなく密な重なり波として見える

装飾マーカーはスクリーンリーダーが読まないよう、02の走行計・04のリング・06のチップがaria-hiddenで隠されており、番号を描き直す05の::beforeは内容がそのまま番号なので露出したままです。08のマーカーは状態をaria-labelで知らせる実際のbuttonで、04は行進トグルにaria-pressedを、07は深さ公開ボタンにaria-expandedを状態に合わせて変えます。操作がすべてbuttonなのでEnter・Spaceでそのまま操作でき、reduce環境では前節で見た通り項目ごとにそれぞれの最終形がアニメーションなしで残ります。

FAQ

css リスト 数字 デザイン のように並びを横に変えても、これらのマーカーはそのまま使えますか?

liの::before・::afterでマーカーを立てた01・03・05・09は、背景となるリストを横に流しても項目に合わせてマーカーがついていき、実際の要素を入れた02・04・06・08もインラインなので一緒に流れます。07のレールだけが縦方向の伸びを前提に描かれているため、横方向のレイアウトではレール軸を描き直す必要があります。

::markerに直接この効果をかけることはできませんか?

できません — color・content・font以外のプロパティは::markerが受け取れないため、回転・転がり・グローは最初から不可能です。9項目すべてがlist-style: noneでマーカーを消し同じ場所に立て直す構造になっているのもそのためで、マーカーがリストの文法の中にありながら動くようにするには、この置き換えが出発点になります。

9つの中でどの項目が画面に最も大きな影響を与えますか?

毎フレーム値を計算し直す忙しさでは03・04が23/24で最も先を行き、広がりでは累積面積2.81%の01が、ピクセル差の強さでは強度142.2の03が最大です。計算量そのものは9項目とも軽く — 05の割り算判定もこのデモの番号の範囲では回数が少なく負担になりません。

Sources: MDN — list-style · MDN — CSS counters · MDN — @counter-style · MDN — Web Animations API

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

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