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

todo リスト ui 9パターン — 進捗ゲージから巻き戻しまで一気に整理

このtodo リスト ui 9パターンのうち、1つはもっと見るを押すたびに読み込む件数が2倍に増え、1つは巻き戻しを押すと完了タイムラインが直前の順の逆順で消え、1つはチェックのたびに円形ゲージがその比率ぶんすぐ満ちます。

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

9つのパターンはどれもコンポーネントライブラリではなく、単体のHTML・CSS・JSファイルです。触れた瞬間に比率や座標差、インデックスといった小さな計算を一つ行うことで、それぞれの動きが成立しています。todoリスト アプリの画面サイズが変わっても同じ9つの計算層がそのまま機能するのは、どれも特定の画面幅に依存していないからです。

デモ

上のグリッドはすべて実際に操作できます。行をタップしたり、ボタンを長押ししたり、ハンドルをドラッグしたり、ヘッダーをスクロールしたりすると、それぞれのパターンが作り物のループ再生ではなく、その場の実際の計算で応答します。

9つのパターンの解剖 — 数値の出どころ

以下の数値はすべて、レンダリング後に実測した同じ23フレーム中のサンプルから計算しています。todoリスト アプリのFigmaデータからそのまま置き換える場合も考え方は同じで、累積面積は動いた部分がその23フレームにわたって画面をどれだけ覆ったかの合計、平均強度は動いたフレーム1枚あたりのピクセル変化の強さ、移動フレーム数(23フレーム中)は前のフレームと実際に差があった枚数です。

# パターン 累積面積 平均強度 移動フレーム数(23フレーム中)
01 サブタスク展開する行 43.151% 82.7 7
02 期限別グループ固定ヘッダー 8.496% 108.5 8
03 リスト→詳細カード拡大遷移 26.048% 149.9 7
04 もっと見る バッチ倍増ロード 6.275% 88.4 6
05 長押し完了取消予約リング 18.42% 148.1 16
06 並べ替え挿入線プレビュー 10.539% 176.9 12
07 インライン編集+文字数カウントダウン 7.756% 94.8 11
08 完了タイムライン巻き戻し 9.168% 79.8 22
09 チェックリスト進捗率 円形ゲージ 25.237% 70.4 22

9つの中で累積面積が最も大きいのは01の43.151%で、最も小さいのは04の6.275%です。サブタスク欄をまるごと開く行のほうが、リストを下に伸ばすだけのボタンより多くのピクセルを動かすためです。平均強度が最も高いのは06の176.9で、2行の間に点線の挿入線が一瞬で切り替わる変化は、ゆっくりしたフェードよりも1フレームあたりのピクセル変化が大きくなります。移動フレーム数は08と09がともに23フレーム中22で最多、04は23フレーム中6で最少でした。

クラスターA — 動く前に実寸を測る組(01・03・07)

01・03・07はどれも、スタイルを触る前にscrollHeightや2回のgetBoundingClientRect()の差、隠しミラー要素の幅といった実際のDOM測定値を読みます。推測した値や固定値へ向けてアニメーションすることはありません。

クラスターB — 一つの比率が大きさを決める組(04・05・09)

04・05・09はそれぞれ、クリック回数・経過時間の比率・チェック済み比率という一つの数値をそのまま指数やstrokeDashoffset、回転角度に変換します。見た目の大きさはその一つの比率の直接的な関数であり、固定のキーフレーム列ではありません。

クラスターC — 位置や順序をその都度計算し直す組(02・06・08)

02・06・08はほぼ毎回の操作フレームで位置や順序を計算し直します。ヘッダーの下にあるグループ、ポインターがどの行の近くにあるか、削除された点が何番目の逆順インデックスになるかがそれにあたります。レイアウトはあらかじめ一度だけ決めて放置されるものではありません。

パターン別コード

実際に動く計算層だけを、各パターンのソースから9本切り出しました。フレームワークもビルド手順もありません。

01サブタスク展開する行

todo行を押すと、まず進捗バッジが縮み、続いてその下にサブタスク一覧が実際のコンテンツ高さぶん伸びて広がります(231の矢印回転展開とは異なる実演で、ここでは進捗バッジ自体がトリガーです)。23フレーム中7フレームでバッジの縮小とパネルの展開が同時に動き、ピーク時には画面の43.151%を覆う、9つの中で最も大きい累積面積になっています。

サブタスク展開する行
新しいタブで開く

01 · サブタスク展開する行 — 計測 累積面積 43.151% · 動いたフレーム 7/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ratio() 배지 축소 계산--h scrollHeight 실측 max-heightaria-expanded
  function ratio(i) {
    var text = rows[i].badge.textContent; // "1/3" のような形 — done/total を解析して縮小率を計算する
    var parts = text.split('/');
    var done = parseInt(parts[0], 10);
    var total = parseInt(parts[1], 10);
    return total > 0 ? done / total : 0;
  }

  function measure(sub) {
    // 実際のコンテンツ高さを測ってCSS変数に渡す — 固定pxではなくサブタスクの件数分伸びる
    sub.style.setProperty('--h', sub.firstElementChild.scrollHeight + 'px');
  }

  function setOpen(i, open) {
    var r = rows[i];
    if (r.open === open) return;
    r.open = open;
    r.btn.setAttribute('aria-expanded', String(open));
    // バッジが先に縮む — 縮小率は完了比率から計算する(残りが多いほど縮んで目立つ)
    var shrink = 1 - (1 - ratio(i)) * BADGE_SHRINK_MAX;
    r.badge.style.transform = open ? 'scale(' + shrink + ')' : 'scale(1)';
    r.caret.style.transform = open ? 'rotate(180deg)' : 'rotate(0deg)';
    if (open) measure(r.sub);
    r.sub.classList.toggle('open', open);
  }

02期限別グループ固定ヘッダー

todoは期限(今日・今週・後で)でグループ化され、スクロール中は固定されたヘッダーがそのグループの残り件数をリアルタイムで数え直します(231の頭文字グループとは異なる実演で、ここでは期限グループと残数バッジを組み合わせています)。ヘッダーはスクロール・ポインター・キーボードのイベントにしか反応せず、独立したクリック処理は持ちません。グループ名はcurrentGroupAndRemain()が毎回のスクロールフレームでDOMを読み直して決めています。

期限別グループ固定ヘッダー
新しいタブで開く

02 · 期限別グループ固定ヘッダー — 計測 累積面積 8.496% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

currentGroupAndRemain() 스크롤 위치 판정overflow-y:scroll 분리 스크롤러그룹별 미완료 개수 매 프레임 재계산
  // 計算層: スクロール位置から「現在のグループ」を探し、そのグループ内の未完了件数をリアルタイムで数え直す。
  // 事前計算した表ではなく、スクロールのフレームごとにDOMを読み直して数える。
  function currentGroupAndRemain() {
    var top = scroller.scrollTop;
    var current = groups[0];
    for (var i = 0; i < groups.length; i++) {
      if (groups[i].offsetTop - scroller.offsetTop <= top + 4) current = groups[i];
    }
    var items = current.querySelectorAll('.items li');
    var remain = 0;
    for (var j = 0; j < items.length; j++) {
      if (items[j].getAttribute('data-done') === '0') remain++;
    }
    return { name: current.getAttribute('data-group'), remain: remain };
  }

  function paint() {
    var r = currentGroupAndRemain();
    headLabel.textContent = r.name;
    headCount.textContent = r.remain + '件';
  }

03リスト→詳細カード拡大遷移

todoカードを押すとそのカード自体が画面いっぱいに拡大して詳細画面になり、戻るとその場のサイズへ縮小して戻ります(231の左右スライド遷移とは異なる実演で、ここではカードがその場で拡大します)。カードはgetBoundingClientRect()で自分の開始位置を測ってInvertの状態に一瞬で切り替え、強制リフローを挟んでからPlay(反転の解除)がアニメーションします。詳細画面を閉じるときも同じFirst-Last-Invert-Playの手順が逆向きに実行されます。

リスト→詳細カード拡大遷移
新しいタブで開く

03 · リスト→詳細カード拡大遷移 — 計測 累積面積 26.048% · 動いたフレーム 7/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

flipFrom() getBoundingClientRect 좌표차translate()·scale() 좌표 역전overflow:hidden 컨테이너
  function openDetail(i) {
    var item = data[i];
    detailTitle.textContent = item.title;
    document.getElementById('detailList').innerHTML = item.items.map(function (t) { return '<li>' + t + '</li>'; }).join('');
    detail.setAttribute('aria-hidden', 'false');
    detail.style.transition = 'none';
    detail.style.display = 'flex';
    back = flipFrom(cards[i]);
    detail.style.transform = 'translate(' + back.dx + 'px,' + back.dy + 'px) scale(' + back.sx + ',' + back.sy + ')';
    detail.style.opacity = '1';
    // 強制リフロー — Invert状態を実際に描画してからでないとPlay(反転解除)アニメーションが成立しない
    detail.getBoundingClientRect();
    detail.style.transition = reduce ? 'none' : 'transform ' + DUR_MS + 'ms var(--flip-ease)';
    detail.style.transform = 'translate(0,0) scale(1,1)';
  }

04もっと見る バッチ倍増ロード

もっと見るを押すたびに次に読み込む件数が2倍になるバッチサイズで取得し、新しい項目は下から順に軽くバウンドしながら収まります(231の固定3件ずつのスライドとは異なる実演で、ここではバッチサイズが押すたびに大きくなります)。LOAD_MORE_MULTIPLIERが押すたびに倍になり、トリガーが標準の<button type="button">なので、EnterキーとSpaceキーは追加のコードなしですでに機能します。

もっと見る バッチ倍増ロード
新しいタブで開く

04 · もっと見る バッチ倍増ロード — 計測 累積面積 6.275% · 動いたフレーム 6/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

LOAD_MORE_MULTIPLIER 배치 배율 증가nextBatchSize() 지수 계산aria-live 항목 추가 알림
  // 計算層: 次のバッチサイズは固定値ではなく、クリック回数の指数関数になっている。
  function nextBatchSize() {
    var size = Math.round(BASE_BATCH * Math.pow(LOAD_MORE_MULTIPLIER, clicks));
    var remain = ALL.length - shown;
    return Math.min(size, remain);
  }

  function renderBatch(n) {
    var frag = document.createDocumentFragment();
    for (var i = 0; i < n; i++) {
      var idx = shown + i;
      var li = document.createElement('li');
      li.className = 'row';
      li.textContent = ALL[idx];
      // 計算層: バッチ内の順番(i)から入場ディレイを実測して割り当てる — 固定delayの羅列ではない。
      li.style.transitionDelay = reduce ? '0ms' : (i * STAGGER_MS) + 'ms';
      list.appendChild(li);
      frag.appendChild(li);
    }
    shown += n;
    // 強制リフロー後にクラスを付与 — 今追加した項目だけが開始状態から到達状態へ遷移する
    list.getBoundingClientRect();
    var added = list.querySelectorAll('.row:not(.in)');
    for (var j = 0; j < added.length; j++) added[j].classList.add('in');
  }

05長押し完了取消予約リング

todoを長押しすると円形の進捗リングが満ちる間に完了取消が予約され、リングが満ちる前に指を離すと予約は取り消されます(231の即座に飛び出すアイコンメニューとは異なる実演で、ここでは時間差の確認リングを使います)。リングはconic-gradientではなく、strokeDashoffsetがelapsed / LONGPRESS_HOLD_MSを毎フレーム追うただのSVGの円で、保持が完了する前に指を離すとカウントダウンはそのまま取り消されます。

長押し完了取消予約リング
新しいタブで開く

05 · 長押し完了取消予約リング — 計測 累積面積 18.42% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

LONGPRESS_HOLD_MS 경과비율 → strokeDashoffsetpointerdown/pointerup 조기 해제 취소elapsed/LONGPRESS_HOLD_MS 계산
  // 計算層: 実際の経過時間比率(elapsed / LONGPRESS_HOLD_MS)を円周の長さに掛け合わせ、
  // リングが実際に何秒押されたかを正確に反映させる。
  function paintRing(i, ratio) {
    var offset = CIRC * (1 - Math.min(1, Math.max(0, ratio)));
    rings[i].style.strokeDashoffset = offset.toFixed(2);
  }

  function startPress(i, startTime) {
    rows[i].classList.add('pressing');
    var raf;
    function tick(now) {
      var elapsed = now - startTime;
      var ratio = elapsed / LONGPRESS_HOLD_MS;
      paintRing(i, ratio);
      if (ratio >= 1) {
        openActions(i);
        cancelPress(i);
        return;
      }
      raf = requestAnimationFrame(tick);
    }
    raf = requestAnimationFrame(tick);
    rows[i]._raf = raf;
  }

06並べ替え挿入線プレビュー

ハンドルを押して上下に動かしても項目はリアルタイムには移動せず、置かれる位置に点線の挿入線だけが現れ、指を離した瞬間に一気に入れ替わります(231の青い空行・336のリアルタイムドラッグ移動とは異なる実演で、ここでは移動なしで判定だけを行う軽量プレビューです)。ハンドルは矢印キーのkeydown処理と、移動のたびに呼び出される.focus()を自前で持つネイティブボタンで、矢印キーでの並べ替えは通常のボタンには備わっていない機能です。

並べ替え挿入線プレビュー
新しいタブで開く

06 · 並べ替え挿入線プレビュー — 計測 累積面積 10.539% · 動いたフレーム 12/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

gapIndexFor() 포인터Y 위치 비교점선 삽입선 — 드롭 전 실이동 없음keydown 방향키 재정렬
  // 計算層: ポインターのY座標を各行の実測境界(top/bottom)と比較し、挿入位置を探す。
  // 要素をリアルタイムには動かさず、挿入線だけをその位置に移動する。
  function gapIndexFor(pointerY, excludeIndex) {
    var rows = Array.prototype.slice.call(list.querySelectorAll('.row'));
    var visible = rows.filter(function (r) { return Number(r.dataset.index) !== excludeIndex; });
    for (var i = 0; i < visible.length; i++) {
      var rect = visible[i].getBoundingClientRect();
      var mid = rect.top + rect.height / 2;
      if (pointerY < mid) return i;
    }
    return visible.length;
  }

07インライン編集+文字数カウントダウン

todoの文字をクリックすると入力欄に変わり、右側で残り文字数がリアルタイムでカウントダウンし、上限に近づくと数字が赤く揺れます(231の鉛筆→チェックアイコン切替とは異なる実演で、ここでは文字数カウンターが要です)。隠しミラーの<span>が入力中のテキストの実際の幅を測り、その値に入力欄の幅を毎回合わせていて、バッジは残り8文字以下になったときだけ揺れます。

インライン編集+文字数カウントダウン
新しいタブで開く

07 · インライン編集+文字数カウントダウン — 計測 累積面積 7.756% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

measureWidth() 미러 span 폭 측정input width 실시간 재계산잔여 글자수 badge shake
    function paint() {
      var remain = MAX_LEN - input.value.length;
      badge.textContent = String(remain);
      input.style.width = measureWidth(input.value) + 'px';
      var warn = remain <= WARN_AT;
      badge.classList.toggle('warn', warn);
      input.classList.toggle('warn', warn);
      if (warn && !reduce) {
        badge.classList.remove('shake');
        void badge.offsetWidth;
        badge.classList.add('shake');
      }
    }

08完了タイムライン巻き戻し

完了したtodoが積み上がるほど縦のタイムラインが下へ伸び、巻き戻しボタンを押すと点が直前の順の逆順で一つずつ消えます(231の上から順番に現れる演出とは異なる実演で、ここでは逆順の巻き戻しが要です)。巻き戻しは各点の遅延時間を逆順インデックスから計算するため、最後に完了した項目が最初に消え、最も古い項目が最後に消え、操作はネイティブの<button type="button">へのクリック一つだけで始まります。

完了タイムライン巻き戻し
新しいタブで開く

08 · 完了タイムライン巻き戻し — 計測 累積面積 9.168% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

STAGGER_MS 순차 transitionDelayrewind() 역순 제거 — reverseIndex 지연aria-live 기록/되감기 알림
  // 計算層: 巻き戻しは直近の項目から消すが、各項目の遅延時間は「末尾からの逆順インデックス」で
  // 計算する — 内側(古い項目)ほど後から消える。
  function rewind() {
    var total = shown.length;
    if (total === 0) return;
    var toRemove = shown.slice().reverse();
    toRemove.forEach(function (li, i) {
      var reverseIndex = total - 1 - i; // 末尾から数えるほど大きくなる値
      var delay = reduce ? 0 : reverseIndex * STAGGER_MS * 0.4;
      window.setTimeout(function () {
        li.classList.remove('in');
        li.classList.add('out');
        window.setTimeout(function () {
          if (li.parentNode) li.parentNode.removeChild(li);
          paintLine();
        }, reduce ? 0 : 220);
      }, delay);
    });
    shown = [];
    liveNote.textContent = total + '件の記録を巻き戻しました。';
  }

09チェックリスト進捗率 円形ゲージ

チェックボックスを一つ押すたびに上部の円形ゲージがその比率ぶん即座に満ち、すべてチェックされるとゲージの縁を一周きらめきが走ります(231の取り消し線アニメーションとは異なる実演で、ここでは全体進捗ゲージが要です)。ゲージの塗りはconic-gradientが読む--fill-degというカスタムプロパティから来ていて、一度きりのきらめきは比率が実際に100%へ達したときにしか再生されず、繰り返しはしません。

チェックリスト進捗率 円形ゲージ
新しいタブで開く

09 · チェックリスト進捗率 円形ゲージ — 計測 累積面積 25.237% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

paintGauge() 체크비율→각도 conic-gradientGAUGE_RING_WIDTH 링 두께100% shine 스윕 1회(반복 없음)
  // 計算層: チェック済み件数/全件数の比率を角度(0〜360deg)に変換し、conic-gradientで描画する。
  function paintGauge() {
    var total = rows.length;
    var done = rows.filter(function (r) { return r.dataset.done === '1'; }).length;
    var ratio = done / total;
    var deg = ratio * 360;
    gauge.style.setProperty('--fill-deg', deg + 'deg');
    gaugePct.textContent = Math.round(ratio * 100) + '%';
    if (ratio >= 1 && !wasFull) {
      wasFull = true;
      if (!reduce) {
        shine.classList.remove('play');
        void shine.offsetWidth;
        shine.classList.add('play');
      }
    } else if (ratio < 1) {
      wasFull = false;
    }
  }

計算された値がパターンを動かす場所

上の9つはどれも、実際に仕事をしている一つの変数にぶら下がっています。その変数を追いかけると、なぜそのパターンがその見た目になるのかがそのまま分かります。

01 — 縮小率

バッジの縮小率は1 - (1 - done/total) * BADGE_SHRINK_MAXから来ていて、始めたばかりのリストほど縮小は小さく、終わりに近いリストほど大きく縮みます。

02 — スクロールから導かれるグループ

currentGroupAndRemain()が各グループのoffsetTopをscroller.scrollTopと比較してラベルと残数を決めるため、ヘッダーは直前のスクロールフレームの分だけ新しい情報を表示します。

03 — 座標の差分

flipFrom()がカードと詳細パネルのgetBoundingClientRect()の差をtranslate()/scale()のペアに変換するため、ズームは常にそのカードが実際に置かれている位置から始まり、決まった角からは始まりません。

04 — バッチの指数

nextBatchSize()がLOAD_MORE_MULTIPLIERをクリック回数のべき乗にするため、4回目のタップは1回目よりも明らかに大きいバッチを読み込みます。

05 — 保持時間の比率

paintRing()が円周の長さにelapsed / LONGPRESS_HOLD_MSを毎フレーム掛けるため、リングの満ち具合は指が実際に何秒押されているかをそのまま読み取った値になります。

06 — ポインターと行の比較

gapIndexFor()がポインターのY座標を各行の実測済みのgetBoundingClientRect()の中点と比較するため、挿入線は固定のグリッド位置ではなく最も近い隙間を追いかけます。

07 — ミラーされた幅

隠しミラー要素が入力中のテキストが実際に取る幅を測り、キー入力のたびに入力欄の幅をその値に合わせます。

08 — 逆順インデックス

rewind()がリスト内の各点の位置を逆順インデックスに変換し、元の追加順ではなくそのインデックスが削除までの遅延時間を決めます。

09 — 比率から角度へ

paintGauge()がdone / totalをそのまま0〜360degの角度に変換してリングのconic-gradientに渡すため、ゲージの弧は段階に丸められることなく実際の比率にぴったり合います。

どこで壊れるか

02の可スクロール領域にはtabindexがなく、独自の矢印キースクロールも定義されていません。keydownリスナーは人間が触れたことを記録するだけなので、内部にフォーカス可能な子要素がない状態でTabキーがこの領域を通り過ぎたキーボード利用者は、実際にスクロール操作をする以外に期限グループを移動する手段がありません。これはデモの手抜きではなく、そのまま出荷する前に直すべき本物の欠落です。

reduced-motionが残すもの・消すもの

  • 01 行の展開 — クリック自体の挙動は変わらず、バッジ・キャレット・パネルの遷移だけが消えるので、サブタスクは伸び縮みせず瞬時に開閉します。
  • 02 グループヘッダー — もともと遷移がない項目で、ラベルと残数は最初から瞬時に切り替わっており、reduced motionはデモ自身の自動スクロールループを止めるだけです。
  • 03 カードズーム — transitionend待ちがそのまま省略され、詳細画面は最終位置のまま瞬時に現れたり消えたりします。
  • 04 もっと見る — 新しい行は段階的な入場ディレイと遷移を失い、バッチ全体が一度にバウンドなしで現れます。
  • 05 長押しリング — リングとアクションパネルの両方が遷移を失いますが、リングは保持時間をそのまま読み取り続けるだけで、フレーム間のイージングがなくなります。
  • 06 挿入線 — 遷移だけが取り除かれるため、点線は滑らかに動かず次の隙間へ一瞬で移動します。
  • 07 文字数カウントダウン — 入力欄の幅は変わらず瞬時に更新されますが、残り文字数が上限に近づいても警告バッジは揺れなくなります。
  • 08 タイムライン巻き戻し — 点は同じ逆順のまま消えますが、一つずつではなく瞬時にまとめて消えます。
  • 09 ゲージ — ゲージは正しいパーセンテージへ瞬時に切り替わりますが、完了時のきらめきは一切再生されません。

上の9つのソースはすべて一つの共通パスワードjq5k5x9yの後ろにまとめられているので、一度アーカイブを解凍すれば、この記事のどのパターンもディスク上の対応するHTML・SCSS・JSファイルとしてそのままブラウザで開けます。

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

バリエーション 値 感触
チェックリストゲージリングを太くする 09 GAUGE_RING_WIDTH 6px → 12px GAUGE_RING_WIDTHを12pxに広げると進捗の変化がより目立ちます
長押し確認リングの時間を延ばす 05 LONGPRESS_HOLD_MS 500 → 900 LONGPRESS_HOLD_MSを900に伸ばすと誤って取り消される頻度は減りますが、反応は遅く感じます
もっと見るのバッチ倍率を下げる 04 LOAD_MORE_MULTIPLIER 2.0 → 1.5 LOAD_MORE_MULTIPLIERを1.5に下げると項目が緩やかに増え、スクロール長が急激に伸びません

01のaria-expanded/aria-controlsのペアは行のボタン自身が開閉状態を知らせ、09のチェックボックスはrole="checkbox"とaria-checkedを持ち、03の詳細パネルは閉じている間aria-hiddenを切り替えてスクリーンリーダーから読み飛ばされるようにしています。04・06・08はそれぞれ独自のaria-live="polite"領域を持ち、バッチの読み込み・並べ替え・巻き戻しをフォーカスを奪わずに読み上げます。04のもっと見るボタンと08の巻き戻しボタンはどちらもネイティブの<button type="button">なので、EnterキーとSpaceキーは追加のkeydown処理なしですでに機能します。

FAQ

これらの9つのtodoリスト アプリのパターンにフレームワークやビルド手順は必要ですか?

いいえ。どのパターンもビルド手順もフレームワークも必要としない単体のHTML・CSS・JSファイルなので、index.htmlを直接開くか、マークアップをそのままチェックリスト デザインへ組み込んでも同じように動作します。

prefers-reduced-motionが実際に効いているかは、このタスク管理 uiでどう確認できますか?

OSのアクセシビリティ設定で「視差効果を減らす」をオンにしてから、上の9つのデモのどれかを再読み込みしてください。どれも読み込み時にwindow.matchMedia('(prefers-reduced-motion: reduce)')を確認し、自分の遷移やアニメーションを取り除くので、そのパターンのCSSやJSと直接見比べて確認できます。

08の巻き戻しは、なぜ完了順ではなく逆順で項目を消すのですか?

rewind()は各点の遅延時間をあえてtotal - 1 - iから計算しているため、最後に完了した項目が最初に消え、最も古い項目が最後に消えます。この逆順こそが巻き戻しという機能の本来の目的であり、コードの偶然ではありません。

参考: MDN — conic-gradient()、MDN — prefers-reduced-motion、MDN — aria-live、MDN — CSS transitions、MDN — Element.scrollHeight。

関連記事: アコーディオンリスト UI 9選 — チェックリストまで · ドラッグアンドドロップ 並び替えリスト — 挿入先インデックス計算を重ねた9種 · テーブル UI デザイン 9選 — 列幅ドラッグと範囲選択まで

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

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