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

HTML CSS JS ゲーム 9種 — 電卓・タイマー・パズルのコピペ

html css js ゲームとは、ブラウザで動く小さな遊びと実務の道具を素の HTML・CSS・JS だけで組むことです。ライブラリなし、ファイル1つで動く9つをクイズ・電卓・タイマー・抽選の順に用意し、どれも下のグリッドでいますぐ動かせます。

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

この9つの並びは人気の順ではなく、画面が目の前の人に課す仕事が重くなる順です。4つのうち1つを選ばせる(01)が最も軽く、続いて数字を打ち込んで値を作り(02)、時間の見張りを画面に任せ(03)、カード6枚の席を揺らし(04)、スライダーからデザインの値を引き出し(05)、0.01秒まで測って記録を残し(06)、文ひとつに○か×かを問い(07)、最後の2つは結果を人の代わりに決めます(08・09)。スライダーが値とコードを同時に吐く05はcss ジェネレーター 9種と同じ流れの先にあり、円盤を角度で削る03は円形プログレスバー9選の手法をタイマー側に向け直したものです。9つともフォルダ1つに html・css・js だけが入り、ビルドツールを要求しません。

01ロゴ当てパズル

見本カードの幾何マークと同じ図形を選択肢4つから選ぶクイズ盤で、イベントページのアイスブレイクや学習クイズの導入画面に置きます。

ロゴ当てパズル
新しいタブで開く

01 · ロゴ当てパズル — 計測 累積面積 17.351% · 動いたフレーム 19/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

clip-pathconic-gradientaria-live

6つのマークは画像ではなくCSSの図形

.lp__mark[data-shape="hex"] { clip-path: polygon(50% 0, 94% 25%, 94% 75%, 50% 100%, 6% 75%, 6% 25%); }
.lp__mark[data-shape="diamond"] { clip-path: polygon(50% 2%, 98% 50%, 50% 98%, 2% 50%); }
.lp__mark[data-shape="tri"] { clip-path: polygon(50% 4%, 96% 92%, 4% 92%); }
.lp__mark[data-shape="chevron"] { clip-path: polygon(16% 0, 100% 50%, 16% 100%, 0 100%, 84% 50%, 0 0); }
.lp__mark[data-shape="wedge"] {
  background-color: transparent; border-radius: 50%;
  background-image: conic-gradient(from 205deg, currentColor 0deg 125deg, transparent 125deg 360deg);
}

六角・菱形・三角・シェブロンは clip-path の座標で、扇形は conic-gradient 1行で描くので、この項目が読み込む画像は0枚で、グリッドのプレビューでは比較できる23コマのうち19コマで動きが出ました。

外れたときは正解のマスを代わりに灯す

  function pick(i) {
    if (lock || !doneEl.hidden) { return; }
    lock = true;
    root.classList.remove('is-demo');
    var answer = QUESTIONS[qIndex].answer;
    var ok = tiles[i].firstElementChild.getAttribute('data-shape') === answer;
    tiles[i].classList.add(ok ? 'is-correct' : 'is-wrong');
    if (ok) { score++; scoreEl.textContent = String(score); }
    else {
      for (var k = 0; k < tiles.length; k++) {
        if (tiles[k].firstElementChild.getAttribute('data-shape') === answer) { tiles[k].classList.add('is-correct'); }
      }
    }
    window.setTimeout(next, NEXT_MS);
  }

選んだマスには当たりなら is-correct、外れなら is-wrong を付け、外れたときだけ正解の図形を持つマスを探して一緒に灯し、0.8秒後に次の問題へ進みます。

02キーパッド電卓

四則演算ができてキーボードの数字・演算子・Enter・Backspace がそのまま効くキーパッド電卓で、見積もり計算の補助や注文数量の精算画面に貼る基本形です。

キーパッド電卓
新しいタブで開く

02 · キーパッド電卓 — 計測 累積面積 3.732% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

grid-template-areasfont-variant-numericuser-select

20個のキーの席は名札1枚

.kc__pad {
  position: relative; flex: 1 1 auto; min-height: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(5, 1fr);
  grid-template-areas:
    "clear back pct div"
    "n7 n8 n9 mul"
    "n4 n5 n6 sub"
    "n1 n2 n3 add"
    "n0 n0 dot eq";
  gap: $sp-2;
  --kc-space: #{$sp-2};
}

4×5のグリッドにまず名札を描いておき、各キーが grid-area で自分の席を受け取るので、0キーだけ2マスを使う配置が座標の計算なしにでき上がります。

演算子を続けて押しても残るのは最後だけ

  function operator(o) {
    var v = Number(shown);
    if (op !== null && !fresh) {
      var r = calc(acc, v, op);
      if (r === null) { fail(); return; }
      acc = r; show(fmt(r));
    } else { acc = v; }
    op = o; fresh = true;
  }

待機中の演算子があり、その後に新しい数字が届いていれば先に計算して累積値へ積み、そうでなければ値だけを載せ替えるので、+ の後に × を押しても後に押した方だけが残ります。

03ポモドーロリングタイマー

25分の集中と5分の休憩を円盤ひとつで巡るタイマーで、個人作業の時計や講義の進行盤に置ける、javascript タイマー 作り方 の解説によくある表示だけのカウントダウンより一歩先まで行く完成形です。

ポモドーロリングタイマー
新しいタブで開く

03 · ポモドーロリングタイマー — 計測 累積面積 7.381% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

conic-gradientanimation-play-statearia-valuenow

角度1つを削って円盤を空にする

.pt__disc {
  position: absolute; inset: 0; border-radius: 50%;
  --pt-angle: 360deg;
  --pt-fill: #{$color};
  background-image: conic-gradient(var(--pt-fill) 0deg var(--pt-angle), rgba($subject-ink, .18) var(--pt-angle) 360deg);
  animation-name: ptDrain;
  animation-duration: 25s;                // JS がフェーズの長さ ÷ SPEED で書き直す
  animation-timing-function: linear;      // 本物のタイマーは等速でなければならない
  animation-iteration-count: 1;
  animation-fill-mode: both;
  animation-play-state: paused;
}
.pt.is-running .pt__disc { animation-play-state: running; }
.pt.is-break .pt__disc { --pt-fill: #{$subject-blue-deep}; }
@keyframes ptDrain {
  from { --pt-angle: 360deg; }
  to { --pt-angle: 0deg; }
}

--pt-angle を @property で <angle> として登録してあるためキーフレームの間が補間され、停止と再開は animation-play-state の1行が担って JS は時刻を再計算せず、デモは SPEED が60で1秒あたり60秒分進むため25分の区間が25秒で終わるので、実際に使うときはこの値を1に戻してください。

リロードを生き残る残り時間

  try {
    var saved = JSON.parse(window.localStorage.getItem(KEY) || 'null');
    if (saved && typeof saved.left === 'number') {
      root.classList.remove('is-demo');
      phase = saved.phase === 'break' ? 'break' : 'focus';
      left = Math.min(Math.max(saved.left, 0), total());
      syncDisc();
      run(!!saved.running);
    }
  } catch (e) { /* 壊れた値は無視して最初の状態へ戻す */ }

残り秒数と今のフェーズを Web Storage API で保存して窓を開き直したときに蘇らせ、保存された値が壊れていれば静かに最初の状態へ戻します。

04カードシャッフル・整列

数字カード6枚がシャッフルでその場で席を入れ替え、整列で元の順に戻るレールで、抽選順の決定やチーム分けのシャッフルに使います。

カードシャッフル・整列
新しいタブで開く

04 · カードシャッフル・整列 — 計測 累積面積 38.397% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

transitiontranslatepointer-events

カードのDOMの順序は最後まで変わらない

.cs__card {
  position: absolute; left: 0; bottom: 0;
  width: calc((100% - #{$sp-2} * 5) / 6); aspect-ratio: 3 / 4;
  --step: calc(100% + #{$sp-2});
  --slot: 0;
  --slotAlt: 0;
  --lift: 0%;
  translate: calc(var(--step) * var(--slot)) 0;
  transition: translate $dur-enter $easing;
  display: grid; place-items: center;
  border-radius: $r-control;
  background-color: $color; color: $subject-cream;   // 濃紺の面 + クリームの文字 5.98:1
  box-shadow: $shadow-press;
  font-weight: 800; font-size: clamp(14px, 4vw, 22px);
  user-select: none;
}

席はスロット番号 --slot ひとつで決まり、1マスの幅はカード自身の幅100%に間隔を足した値なので、番号が変わると transition がその間を運びます。

動いているあいだはボタンをロックする

  function shuffle() {
    if (moving) { return; }
    root.classList.remove('is-demo');
    var before = slots.join('');
    for (var i = slots.length - 1; i > 0; i--) {            // Fisher-Yates
      var j = Math.floor(Math.random() * (i + 1));
      var t = slots[i]; slots[i] = slots[j]; slots[j] = t;
    }
    if (slots.join('') === before) { var h = slots[0]; slots[0] = slots[5]; slots[5] = h; }
    paint(); lock();
    stateEl.textContent = SHUFFLED;
  }

スロットを Fisher-Yates で混ぜ、結果が前の順序と同じなら両端をもう一度入れ替えて必ず変わるようにし、動いている0.6秒のあいだは .is-moving がボタンの pointer-events を切るので連打で席が絡まりません。

05グラス効果ジェネレーター

ぼかし・不透明度・彩度・境界のスライダーを動かすとプレビューカードと下のコード行が同時に変わる道具で、コーディングなしにグラスカードの値を決めるときに取り出します。

グラス効果ジェネレーター
新しいタブで開く

05 · グラス効果ジェネレーター — 計測 累積面積 17.794% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

backdrop-filterhslclamp

値を作る場所は関数1つだけ

  function code() {
    return [
      'backdrop-filter: blur(' + ranges.blur.value + 'px) saturate(' + ranges.saturate.value + '%);',
      'background: hsl(0 0% 100% / ' + alpha() + ');',
      'border: ' + ranges.border.value + 'px solid hsl(0 0% 100% / .5);'
    ];
  }
  function render() {
    var rows = code();
    glass.style.backdropFilter = 'blur(' + ranges.blur.value + 'px) saturate(' + ranges.saturate.value + '%)';
    glass.style.backgroundColor = 'hsl(0 0% 100% / ' + alpha() + ')';
    glass.style.borderWidth = ranges.border.value + 'px';
    outs.blur.textContent = ranges.blur.value + 'px';
    outs.opacity.textContent = ranges.opacity.value + '%';
    outs.saturate.textContent = ranges.saturate.value + '%';
    outs.border.textContent = ranges.border.value + 'px';
    for (var i = 0; i < lines.length; i++) { lines[i].textContent = rows[i]; }
  }

code() が返す3行がカードのインラインスタイルとコード表示の両方へ同じ値で書き込まれるので、コピーして持ち帰る値と目で見るカードが食い違う場所がありません。

backdrop-filter は背後がくっきりしてこそ見える

.gg__scene { position: relative; overflow: hidden; border-radius: $r-card; background-color: rgba($subject-cream, .06); min-height: 120px; }
// ブロブをぼかしすぎると、その上のカードの backdrop-filter の仕事が画面から消える — ブロブは控えめにぼかす
.gg__blob { position: absolute; width: 64%; aspect-ratio: 1; border-radius: 50%; filter: blur(9px); }
.gg__blob--a { left: -12%; top: -10%; background-color: $subject-pink; }
.gg__blob--b { right: -14%; top: 18%; background-color: $subject-mint; }
.gg__blob--c { left: 18%; bottom: -18%; background-color: $subject-sky; }
.gg__glass {
  position: absolute; inset: 22% 12%;
  border-radius: $r-card;
  border-style: solid; border-width: 1px; border-color: hsl(0 0% 100% / .5);
  background-color: hsl(0 0% 100% / .18);
  backdrop-filter: blur(12px) saturate(160%);
  -webkit-backdrop-filter: blur(12px) saturate(160%);
}

カードの背後にある色のブロブを9pxしかぼかしていない理由はここにあり、背景を先に潰してしまうと backdrop-filter がした仕事が画面から消えてしまいます。

06ラップストップウォッチ

0.01秒単位で進むストップウォッチで、スペースで開始・停止、L キーでラップを刻み、運動記録やセミナー発表の計測に置きます。

ラップストップウォッチ
新しいタブで開く

06 · ラップストップウォッチ — 計測 累積面積 20.363% · 動いたフレーム 21/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

font-variant-numericrequestAnimationFramearia-keyshortcuts

桁が変わっても揺れない表示桁

.sw__time {
  margin: 0;
  font-weight: 800; font-size: clamp(28px, 9vw, 44px); line-height: 1;
  font-variant-numeric: tabular-nums;
  color: $subject-ink;
  letter-spacing: -.02em;
}
.sw__sep { color: rgba($subject-ink, .72); }
.sw__cs { position: relative; display: inline-block; }

font-variant-numeric: tabular-nums が数字の幅をひとつに揃えるので、09 から 10 へ桁が繰り上がる瞬間も秒の位が左右にずれません。

時計は1本、ラップは1行だけ

  function tick() {
    if (!running) { return; }
    elapsed = base + (Date.now() - startAt);
    paint();
    window.requestAnimationFrame(tick);
  }
  function toggle() {
    root.classList.remove('is-demo');
    running = !running;
    if (running) { base = elapsed; startAt = Date.now(); window.requestAnimationFrame(tick); } else { save(); }
    paint();
  }
  function lap() {
    root.classList.remove('is-demo');
    count++;
    laps.unshift({ n: count, at: fmt(elapsed) });
    laps = laps.slice(0, MAX);
    listEl.insertBefore(row(laps[0], true), listEl.firstChild);
    while (listEl.children.length > MAX) { listEl.removeChild(listEl.lastChild); }
    save();
  }

requestAnimationFrame が画面のリフレッシュに合わせて経過時刻を書き直し、ラップはリスト全体を描き直さず先頭に1行だけ差し込むので、新しく届いた行だけが滑り込みます。

07○×クイズパネル

文に対して O/X ボタンかキーボードの O・X キーで答えると当たりは青、外れは赤に灯り、外れたときだけ正解ボタンに枠のヒントが付くパネルで、ライブセッションのクイズや講義スライドに差し込みます。

○×クイズパネル
新しいタブで開く

07 · ○×クイズパネル — 計測 累積面積 14.505% · 動いたフレーム 15/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

aria-pressedtransitioncounter-increment

外れたときだけ正解側にヒントの枠

  function pick(v) {
    if (lock) { return; }
    root.classList.remove('is-demo');
    if (step >= QUESTIONS.length) { reset(); return; }
    lock = true;
    var ok = QUESTIONS[step].a === v;
    var picked = v === 'O' ? oBtn : xBtn;
    var right = QUESTIONS[step].a === 'O' ? oBtn : xBtn;
    picked.setAttribute('aria-pressed', 'true');
    picked.classList.add(ok ? 'is-hit' : 'is-miss');
    if (!ok) { right.classList.add('is-hint'); }
    if (ok) { score++; scoreEl.textContent = String(score); }
    dots[step].classList.add(ok ? 'is-hit' : 'is-miss');
    msgEl.textContent = ok ? HIT : MISS;

押したボタンには aria-pressed を立てて判定の色を付け、外れたときだけ正解ボタンに内側の枠を重ねる項目で、色はカットで切り替わるため、動きが出たのは比較できる23コマのうち15コマでした。

進行ドットの番号はCSSが数える

.ox__dots {
  display: flex; justify-content: center; gap: $sp-2;
  margin: 0; padding: 0; list-style: none;
  counter-reset: oxStep;
}
.ox__dot {
  counter-increment: oxStep;
  display: grid; place-items: center;
  width: clamp(16px, 5vw, 22px); aspect-ratio: 1;
  border-radius: 50%;
  background-color: $subject-cream; color: $subject-ink;
  font-weight: 700; font-size: clamp(8px, 2.2vw, 10px); line-height: 1;
}
.ox__dot::before { content: counter(oxStep); }
.ox__dot.is-now { outline: 2px solid $subject-ink; outline-offset: 2px; }
.ox__dot.is-hit { background-color: $color; color: $subject-cream; }
.ox__dot.is-miss { background-color: $error; color: $subject-cream; }

counter-reset で始めたカウンタをドットごとに1つずつ上げて1から5まで番号を付けるので、JS は色だけを変えて数字には触れません。

08ルーレット抽選

6マスのルーレットが長い余韻を残して減速し、矢印が指したマスを当たりとする盤で、イベントの景品抽選や発表順の決定に回します。

ルーレット抽選
新しいタブで開く

08 · ルーレット抽選 — 計測 累積面積 21.038% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

conic-gradienttransformtransition-timing-function

円盤1枚は conic-gradient 1つ

.rl__disc {
  // ラベル置きの箱は円盤と同じ大きさをマスの角度で回したものなので、回転後の境界箱は円盤より大きくなる —
  // そのはみ出しが body.scrollHeight に伝わり 320px の検査が壊れた(実測 207px)。円の内側に切り抜いて防ぐ
  position: absolute; inset: 0; border-radius: 50%; overflow: hidden;
  box-shadow: 0 0 0 4px $subject-ink;
  // 6マス + マス境界 1deg のインク線。ハードストップなので色は混ざらない
  background-image: conic-gradient(
    $subject-ink 0deg 1deg,     $subject-blue-deep 1deg 60deg,
    $subject-ink 60deg 61deg,   $stage-yellow 61deg 120deg,
    $subject-ink 120deg 121deg, $stage-orange 121deg 180deg,
    $subject-ink 180deg 181deg, $subject-lilac 181deg 240deg,
    $subject-ink 240deg 241deg, $subject-cream 241deg 300deg,
    $subject-ink 300deg 301deg, $subject-ink 301deg 360deg
  );
  transform: rotate(var(--spin));
  transition-property: transform;
  transition-duration: 2400ms;
  transition-timing-function: $spin-ease;
}

マスの色と1度のインクの境界線を conic-gradient のハードストップとして一緒に描き、回転の長い余韻は transition-timing-function に収めた専用の曲線が生みます。

止まるマスを先に決めて角度を作る

  function spin() {
    if (busy) { return; }
    root.classList.remove('is-demo');
    busy = true;
    btn.disabled = true;
    out.textContent = SPINNING;
    var turns = 5 + Math.floor(Math.random() * 4);                       // 5〜8 回転
    var idx = Math.floor(Math.random() * PRIZES.length);                 // 止まるマス
    var target = (360 - (idx * SLICE + SLICE / 2) + 360) % 360;          // そのマスの中央が真上に来る角度
    angle += turns * 360 + ((target - (angle % 360)) + 360) % 360;
    root.style.setProperty('--spin', angle + 'deg');
  }

まずマスを引いて、その中央が12時に来る角度を計算し、その上に5〜8回転を重ねるので、結果は回す前に決まっていて画面はその値に従うだけです。

09ランダムスロット抽選

名前の列が速く流れて、止まったときの中央のマスの名前を当選と読む抽選盤で、コメントイベント当選者の抽選や発表者の決定に使います。

ランダムスロット抽選
新しいタブで開く

09 · ランダムスロット抽選 — 計測 累積面積 2.549% · 動いたフレーム 9/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

animation-timing-functionstepsaria-live

窓の高さはマスの高さの整数倍

.sl__window {
  position: relative; flex: 0 0 auto;
  width: clamp(104px, 34vw, 160px); height: calc(var(--row) * 3);   // 窓の高さはマスの高さの整数倍
  overflow: hidden; border-radius: $r-card;
  background-color: $subject-cream;
}
// 実際の抽選(減速中)にはマスが窓の境界にまたがる — 上下をクリームに滲ませ、文字が切れたまま終わらないように
.sl__window::before,
.sl__window::after {
  content: ""; position: absolute; left: 0; right: 0; z-index: 1;
  height: 14%; pointer-events: none;
}
.sl__window::before { top: 0; background-image: linear-gradient($subject-cream, rgba($subject-cream, 0)); }
.sl__window::after { bottom: 0; background-image: linear-gradient(rgba($subject-cream, 0), $subject-cream); }

マスの高さ --row ひとつが窓の高さも強調の枠も移動距離も決めて、上下のグラデーションが境界にまたがった名前を覆い、切れた名前を見せません。

止まった位置は剰余で巻き戻す

  track.addEventListener('transitionend', function (e) {
    if (e.propertyName !== 'transform') { return; }
    offset = offset % list.length;
    winner = list[(offset + 1) % list.length];
    track.style.transitionProperty = 'none';
    put(offset);
    void track.offsetHeight;                    // リフローを一度強制して、巻き戻りがアニメーションされないように
    track.style.transitionProperty = 'transform';
    banner.textContent = WIN.replace('{n}', winner);
    banner.classList.add('is-win');
    busy = false;
    goBtn.disabled = false;
  });

停止後に位置を候補数で割った剰余で巻き戻し、その間にリフローを一度強制するので、トラックは終端へ押しやられないし、巻き戻りも目に見えません。

html css ゲーム 作成はどのファイルから始めるべきか?

受け取ったファイルは開けばそのまま動きますが、自分のページに載せるときに手を入れる場所は3つに決まっています。1つ目はデータで、01の問題5組と07の文5行と09の名前8人はファイル先頭の定数にそのまま入っているので配列を差し替えるだけで済み、出題は配列の順番をそのままたどるため01の「新しい問題」ボタンも同じ5問を最初から繰り返します。2つ目は保存キーで、03・06・09はそれぞれ pomodoro080・p080-06-stopwatch・p080-09 へ書き込むため、同じページに2つ以上を置くときはキー名を分けないと互いの記録を上書きし合います。3つ目が is-demo クラスで、これはグリッドで見せるために被せたプレビューのループであり、人が最初に触れた瞬間に JS が外し、実際の画面へ埋め込むときは最初から書かなくて構いません。html css 練習 ゲーム として教材を探している方にも、9つとも1ファイルで完結するので写経の題材としてちょうどいい大きさです。

どこで壊れるか — 落とし穴

9つはグリッドの中では CSS の時計で回り、人が触れば JS の経路で回りますが、この記事で実際に立ちはだかったのはそのどちらでもなく、回転と整数でした。以下はどれも測定中に画面で壊れ、上のコードへ修正を書き戻したものです。

回した文字は逆さに立ち、箱は円盤より大きくなる

08は最初、ラベルをマスの角度でそのまま回していました。上側のマスは読めたものの、下側のマスの文字が逆さに立って一覧のサムネイルで読めなくなったため、ラベルの置き場所の箱と文字の箱を分け、文字側に rotate(calc(var(--a) * -1 - var(--spin))) を掛けてどの角度でも水平に保つように直しました。同じ項目では、回転したラベルの箱の境界が円盤より大きくなり、320px の幅で body.scrollHeight が207pxはみ出しました。要素の矩形検査では掴めなかったこのはみ出しを、円盤の overflow: hidden で円の内側に切り抜いて封じてあります。矢印を円盤の外の舞台背景に立てて先端だけが縁に触れるようにしてあるのも同じ測定の結果で、円盤の上に矢印を置くと、止まったマスがインクや濃紺のときに矢印がその色に沈んで見えなくなるからです。

整数に合わないリールは名前を切る

09のリールは窓の高さがマスの高さで割り切れず、止まったフレームごとに名前が上下に半分ずつ切れて写っていました。送り方をマス数で区切る steps(var(--rows), end) に替えるとすべてのフレームでマスが窓にぴったり収まり、9つを収めた圧縮ファイルを開くパスワードは eu89k2wk です。この項目の累積面積 2.55% が9つのうちで最も小さいのも、リールがカットで刻んで進むからです。

同じ行で席を入れ替えるとカードが重なる

04はカードが移動中に互いに重なり、代表画像では1枚がすっかり隠れていました。列全体がひとつずつ席を進める巡りに変え、最後の1枚だけが列の上に浮いて先頭席へ渡るようにすると、どのフレームでも6枚が全部見えます。レール自身をカードの1.85倍の高さにして渡る1枚が舞台の内側で動けるようにし、渡るカードは最後に着地して、前のカードが空けた後の席にだけ降り立ちます。6枚が束で動く項目なので、累積面積は 38.40% で9つのうち最大です。

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

値を3つ変えるだけで、9つの性格が一度に変わります。

名前 変える値 感じ
duration $dur-loop 2s → 1s シャッフル・ルーレット・スロットが2倍速くなる
easing $ease-out → $ease-pop カードとチップがもう一回跳ねる
color $subject-blue → $stage-yellow 青い主役が黄色に変わり、明るい舞台になる

操作はマウスよりキーボードを先に用意してあります。01は1〜4キー、02は数字と演算子に Enter・Backspace・Escape、04は S と R、06はスペースと L・R、07は O と X キーがボタンと同じ仕事をし、どのキーが効くかは aria-keyshortcuts としてボタン自体に書かれています。キーキャップの表示行とヘルプシートを画面に並べる方法はショートカットキー 表示 UI 9選にまとめました。スコアと結果の文は aria-live と role="status" で読み上げられ、03の円盤は role="progressbar" の aria-valuenow に残りの割合を数値で出しています。prefers-reduced-motion: reduce では9つすべてでプレビューのループが止まり、カード・リール・ルーレットの円盤の滑りが即座に終わって結果の画面だけが残り、03の円盤は削られること自体がなくなって残り時間が数字だけで減っていきます。文字のコントラストは9つの画面の52ヶ所を実ピクセルで測り、AA 未達は0ヶ所でした。

FAQ

電卓 作り方 javascript の記事と02は何が違いますか?

多くの入門の解説は四則演算の式を組み立てる所で終わります。02はその先にキーパッドの配置、桁が変わっても揺れない固定幅の数字、キーボードの配線までが同じファイルに入っているので、貼り付けたら電卓として完成です。

WordPress やはてなブログの記事にそのまま貼っても動きますか?

項目ごとの html・css・js をそのまま貼れば多くの場合動きますが、投稿エディタが <style> や <script> を削るサービスがあるので、iframe で包む方が安全です。クラス名は項目ごとに2文字の接頭辞が付いていて、ブログのテーマの CSS と衝突しません。9つは 320px 幅でもすべての項目を測り直してはみ出し0件を確認してあるので、スマートフォンの幅に貼っても段組は崩れません。

保存された時間や候補リストはどうやって消せますか?

03・06・09は値をブラウザの localStorage に置くので、対応するキーを削除するとその項目が最初の状態へ戻ります。09の画面の中の「候補を戻す」ボタンが同じ仕事をし、06の「リセット」ボタンは時計とラップを一緒に空にします。09で当選者を外すと候補は減ったまま保存され、抽選を重ねるたびに候補の母数が減っていきます。

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

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