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

コマ 回転 9パターン — 角速度と歳差で描く回転シーン

回る、傾く、ふらつく、倒れる — コマの一生は四つの動詞で終わり、03番はその二つ目を数字で計算する。コマ 回転の九つの場面を、このページで動かしながら確かめます。

九つのコマの場面をこのページで回して確かめる

下のグリッドの九つのキャンバスは、スクロールして見えた時点ですでに動いています。どれも角速度・傾き・軸の向きを持つ同じ小さなコマから始まり、クリックを待つ場面はなく、ポインターが回転を生む06番だけが例外です。

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

コマ 回転 9パターン — 角速度ひとつが傾きと揺れを一緒に決める

この九つは、角速度という一つの数が16msのステップごとに少しずつ落ち、傾き・軸の円・床の上の移動・転倒がすべてその数を読む、というつくりのコマです。コマ 回転運動を触って確かめられる形にしたもので、GIFのように同じループを繰り返すのではなく、毎フレーム計算し直すので、各項目の冒頭の定数を変えれば次のフレームから見え方が変わります。

九つはコマの一生の順に並べてあります。減衰、傾く軸、広がる揺れ、床をさまよう動き、二つのコマの衝突、ポインターで回すコマ、にじむ縞、転倒、そして全部を混ぜたヒーローアリーナです。どの項目も makeTop() のオブジェクトと drawTop() の描画関数を共有しているので、以下で読むコードはその項目が足した部分だけです。

01摩擦で角速度が落ちていく基本のコマ

decayAngularSpeed() が16msのステップごとに ANGULAR_DECAY(0.55)から作った指数の係数を角速度に掛け、縞の角度は残った速度のぶんだけ進むので、最初は速く回って徐々に落ち着きます。角速度が4を下回ると OMEGA_START(38)に戻って波紋が広がるため、回転シーンの最初の画面に向いています。

摩擦で角速度が落ちていく基本のコマ
新しいタブで開く

01 · 摩擦で角速度が落ちていく基本のコマ — 計測 累積面積 22.972% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ANGULAR_DECAYdecayAngularSpeeddrawOmegaGauge
  var ANGULAR_DECAY = 0.55;
  var OMEGA_START = 38;
  var spinTop = makeTop(160, 158, 0, 58, OMEGA_START);

  function decayAngularSpeed(t) { t.omega *= Math.exp(-ANGULAR_DECAY * dtS); }

  function stepWorld() {
    decayAngularSpeed(spinTop);
    spinTop.spin += spinTop.omega * dtS;
    var slow = 1 - clamp(spinTop.omega / OMEGA_START, 0, 1);
    spinTop.tilt = 0.06 + 0.5 * slow * slow;
    spinTop.az += (2 + slow * 5) * dtS;
    if (spinTop.omega < 4) {
      spinTop.omega = OMEGA_START;
      addPulse(spinTop.x, spinTop.y + 4, '255,214,230');
    }
    stepPulses();
  }

項目01はフレームの23.0%を平均強度76.2で塗り替え、23/23フレームで動きが測定されました。

02傾いた軸が円を描く歳差のコマ

PRECESSION_GAIN(0.6)は軸がどれだけ傾くかだけを決め、TILT_PER_GAIN(0.7)にその係数を掛けたものが傾きになり、軸が回る速さは precessAxis() の中で PRECESSION_K(200)を角速度で割って決まります。軸の先端を白い点と110点の軌跡で追うので、傾いた軸が回り続ける理由を見せる教育シーンに向いています。

傾いた軸が円を描く歳差のコマ
新しいタブで開く

02 · 傾いた軸が円を描く歳差のコマ — 計測 累積面積 23.096% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

PRECESSION_GAINprecessAxisTILT_PER_GAIN
  function precessAxis(t) {
    var rate = PRECESSION_K * Math.sin(t.tilt) / Math.max(t.omega, 8);
    t.az += rate * dtS;
  }

  function stepWorld() {
    spinTop.omega *= Math.exp(-AXIS_DECAY * dtS);
    spinTop.spin += spinTop.omega * dtS;
    var slow = 1 - clamp(spinTop.omega / OMEGA_FULL, 0, 1);
    spinTop.tilt = TILT_PER_GAIN * PRECESSION_GAIN * (1 + 0.5 * slow);
    precessAxis(spinTop);
    pushTrail(tipTrail, spinTop.tipX, spinTop.tipY, 110);
    if (spinTop.omega < 12) {
      spinTop.omega = OMEGA_FULL;
      addPulse(spinTop.x, spinTop.y + 4, '201,184,255');
    }
    stepPulses();
  }

項目02はフレームの23.1%を平均強度64.6で塗り替え、23/23フレームで動きが測定されました。

03遅くなるほど傾きの振れ幅が増えるコマ

tiltAmplitude() は TILT_AMP_MAX(0.88)に、失われた速度の割合の1.6乗を掛けた振れ幅を返し、遅くなるほど速まる正弦の位相が、その範囲で傾きを左右に振ります。キャンバス上の半透明の扇が揺れ幅の拡大を見せるので、止まる直前の緊張感を出したい場面に使えます。

遅くなるほど傾きの振れ幅が増えるコマ
新しいタブで開く

03 · 遅くなるほど傾きの振れ幅が増えるコマ — 計測 累積面積 20.204% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

TILT_AMP_MAXtiltAmplitudedrawAmplitudeFan
  function tiltAmplitude(omega) {
    var slow = 1 - clamp(omega / OMEGA_TOP, 0, 1);
    return TILT_AMP_MAX * Math.pow(slow, 1.6);
  }

  function stepWorld() {
    spinTop.omega *= Math.exp(-OMEGA_FADE * dtS);
    spinTop.spin += spinTop.omega * dtS;
    currentAmp = tiltAmplitude(spinTop.omega);
    var slow = 1 - clamp(spinTop.omega / OMEGA_TOP, 0, 1);
    wobblePhase += (5 + 9 * slow) * dtS;
    spinTop.tilt = 0.05 + currentAmp * (0.55 + 0.45 * Math.sin(wobblePhase));
    spinTop.az += (3 + slow * 5) * dtS;
    if (spinTop.omega < 7) {
      spinTop.omega = OMEGA_TOP;
      addPulse(spinTop.x, spinTop.y + 4, '159,216,255');
    }
    stepPulses();
  }

項目03はフレームの20.2%を平均強度68.2で塗り替え、23/23フレームで動きが測定されました。

04床に摩擦の軌跡を残してさまようコマ

毎ステップ、回り続ける進行方向へ DRIFT_ACCEL(70)の力を足し、速度に FLOOR_FRICTION(0.992)を掛けてから接点を動かします。32msごとに位置を floorTrace(最大420点)へ記録して床に淡い線を残すので、動きが跡として読み取れる床のシーンになります。

床に摩擦の軌跡を残してさまようコマ
新しいタブで開く

04 · 床に摩擦の軌跡を残してさまようコマ — 計測 累積面積 25.688% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

FLOOR_FRICTIONDRIFT_ACCELfloorTrace
  function stepWorld() {
    spinTop.omega *= Math.exp(-SPIN_FADE * dtS);
    spinTop.spin += spinTop.omega * dtS;
    heading += HEADING_RATE * dtS;
    var drive = DRIFT_ACCEL * (spinTop.omega / OMEGA_BASE) * dtS;
    spinTop.vx += Math.cos(heading) * drive;
    spinTop.vy += Math.sin(heading) * drive * 0.7;
    spinTop.vx *= FLOOR_FRICTION;
    spinTop.vy *= FLOOR_FRICTION;
    spinTop.x += spinTop.vx * dtS;
    spinTop.y += spinTop.vy * dtS;
    wallBounce(spinTop, 0.6, 6);
    var slow = 1 - clamp(spinTop.omega / OMEGA_BASE, 0, 1);
    spinTop.tilt = 0.1 + 0.4 * slow * slow + 0.1 * Math.min(1, Math.hypot(spinTop.vx, spinTop.vy) / 120);
    spinTop.az = heading;
    if (elapsed % 32 === 0) pushTrail(floorTrace, spinTop.x, spinTop.y + 3, 420);
    if (spinTop.omega < 9) {
      spinTop.omega = OMEGA_BASE;
      spinTop.vx += 50; spinTop.vy -= 30;
      addPulse(spinTop.x, spinTop.y + 4, '168,230,207');
    }
    stepPulses();
  }

項目04はフレームの25.7%を平均強度60.8で塗り替え、23/23フレームで動きが測定されました。

05二つのコマがぶつかって弾き合う衝突

pairCollide() は縦の距離を1.6倍して遠近を反映し、その距離が半径の和の0.92倍より小さくなったときに衝突とみなして二つを押し離し、COLLISION_RESTITUTION(0.9)を掛けた衝撃を接触の法線方向に与えます。さらに角速度の差の30%(SPIN_MIX)を交換して合計は変わらず、遅い方が速くなり速い方は遅くなるので、二つの力がぶつかる対決シーンに向いています。

二つのコマがぶつかって弾き合う衝突
新しいタブで開く

05 · 二つのコマがぶつかって弾き合う衝突 — 計測 累積面積 40.629% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

COLLISION_RESTITUTIONresolveTopPairexchangeSpin
  function pairCollide(a, b, rest, spinMix, kickAmt) {
    var dx = b.x - a.x, dy = (b.y - a.y) * 1.6;
    var dist = Math.hypot(dx, dy);
    var minDist = (a.R + b.R) * 0.92;
    if (dist <= 0 || dist >= minDist) return false;
    var nx = dx / dist, ny = dy / dist;
    var overlap = (minDist - dist) * 0.5;
    a.x -= nx * overlap; a.y -= ny * overlap / 1.6;
    b.x += nx * overlap; b.y += ny * overlap / 1.6;
    var vn = (b.vx - a.vx) * nx + (b.vy - a.vy) * ny;
    if (vn >= 0) return false;
    var j = -(1 + rest) * vn / 2;
    a.vx -= j * nx; a.vy -= j * ny;
    b.vx += j * nx; b.vy += j * ny;
    var tr = (b.omega - a.omega) * spinMix;
    a.omega += tr; b.omega -= tr;
    a.vx += -ny * tr * 1.4; a.vy += nx * tr * 1.4;
    b.vx -= -ny * tr * 1.4; b.vy -= nx * tr * 1.4;
    a.kick = kickAmt; b.kick = kickAmt;
    addPulse((a.x + b.x) / 2, (a.y + b.y) / 2 + 6, '255,255,255');
    return true;
  }

項目05はフレームの40.6%を平均強度72.1で塗り替え、23/23フレームで動きが測定されました。

06ポインターでなでると回転が付くコマ

impartSpin() はコマの中心からポインターまでのベクトルと、ポインターが今動いたベクトルの外積を回転力にし、SPIN_PER_PX(0.0045)を掛けて角速度に足し、OMEGA_MAX(44)の範囲に収めます。中心の上をなでるか下をなでるかで回る向きが逆になり、速くなでるほど強く回り、触れるまでは台本どおりの四回のスワイプが再生されます。

ポインターでなでると回転が付くコマ
新しいタブで開く

06 · ポインターでなでると回転が付くコマ — 計測 累積面積 15.637% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

SPIN_PER_PXimpartSpinvirtualSwipe
  function impartSpin(px, py, dx, dy) {
    var cx = spinTop.x, cy = spinTop.y - 34;
    var rx = px - cx, ry = (py - cy) * 1.4;
    if (Math.hypot(rx, ry) > spinTop.R * 1.7) return;
    var torque = rx * dy - ry * dx;
    spinTop.omega = clamp(spinTop.omega + torque * SPIN_PER_PX, -OMEGA_MAX, OMEGA_MAX);
    spinTop.kick = Math.min(0.6, spinTop.kick + Math.abs(torque) * 0.004);
  }

項目06はフレームの15.6%を平均強度73.1で塗り替え、23/23フレームで動きが測定されました。

07速いと縞がにじんで見える残像のコマ

縞が通過する頻度(角速度 × STRIPE_COUNT(6)÷ 2π)を STRIPE_FUSE_HZ(14)と比べ、超えると縞の上に中間色の円盤を重ね、さらに過去の三つの角度の縞を薄く重ねて残像にします。角速度は46まで上がってから指数で落ち、6まで下がるとまた上がるので、縞が色の帯ににじんではまたはっきりする様子が毎ループ見られます。

速いと縞がにじんで見える残像のコマ
新しいタブで開く

07 · 速いと縞がにじんで見える残像のコマ — 計測 累積面積 20.401% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

STRIPE_COUNTSTRIPE_FUSE_HZrevUp
    var sgn = t.omega < 0 ? -1 : 1;
    var w = Math.abs(t.omega);
    wedges(R, STRIPE_COUNT, t.spin, 1, t.pal.a);
    if (w > 8) {
      for (var k = 1; k <= 3; k += 1) wedges(R, STRIPE_COUNT, t.spin - sgn * k * w * 0.012, 0.2 / k, t.pal.a);
    }
    var f = w * STRIPE_COUNT / 6.2832;
    var blur = clamp((f - STRIPE_FUSE_HZ) / 26, 0, 0.8);
    if (blur > 0) {
      ctx.globalAlpha = blur;
      ctx.beginPath();
      ctx.arc(0, 0, R, 0, 6.2832);
      ctx.fillStyle = t.pal.mid;
      ctx.fill();
      ctx.globalAlpha = 1;
    }

項目07はフレームの20.4%を平均強度60.4で塗り替え、23/23フレームで動きが測定されました。

08傾きが限界を超えると倒れる転倒判定

judgeTumble() は失われた速度の割合から傾きを決め、角速度が FALL_OMEGA(9)未満で、傾きが TILT_LIMIT(0.62)を超えたときだけ真を返します。すると状態が回転から転倒に変わり、重力が傾きの速さに足され、勢いが十分なら28%の速さで跳ね返り、1100ms横たわってから再び回り始めて、点線が限界の角度を示します。

傾きが限界を超えると倒れる転倒判定
新しいタブで開く

08 · 傾きが限界を超えると倒れる転倒判定 — 計測 累積面積 22.438% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

TILT_LIMITFALL_OMEGAjudgeTumble
  function stepTop(t) {
    if (t.state === 'spin') {
      t.omega *= Math.exp(-OMEGA_FADE * dtS);
      t.spin += t.omega * dtS;
      t.az += (3 + (1 - t.omega / OMEGA_START) * 4) * dtS;
      if (judgeTumble(t)) { t.state = 'fall'; t.tv = 0.5; t.az = 0.35 + (t.x < 160 ? 0 : 2.4); }
    } else if (t.state === 'fall') {
      t.spin += t.omega * dtS;
      t.tv += 18 * Math.sin(t.tilt) * dtS;
      t.tilt += t.tv * dtS;
      if (t.tilt >= 1.5) {
        t.tilt = 1.5;
        if (t.tv > 0.9) { t.tv = -t.tv * 0.28; addPulse(t.x, t.y + 4, '255,255,255'); }
        else { t.tv = 0; t.state = 'down'; t.rest = 0; }
      }
    } else {
      t.omega *= 0.97;
      t.spin += t.omega * dtS;
      t.rest += FIXED_DT;
      if (t.rest > 1100) {
        t.state = 'spin'; t.omega = OMEGA_START; t.tilt = 0.08;
        addPulse(t.x, t.y + 4, '168,230,207');
      }
    }
  }

項目08はフレームの22.4%を平均強度68.4で塗り替え、23/23フレームで動きが測定されました。

09角速度・歳差・衝突を一枚にまとめたヒーローアリーナ

heroStep() は半径24のコマ三つを一つのループで回し、角速度の減衰(HERO_DECAY 0.22)・歳差(HERO_PRECESSION_GAIN 0.9)・中心への引力・床の摩擦(HERO_FRICTION 0.995)と、全ての組への pairCollide() を同時に計算します。ぶつかり合って遅くなり、角速度が9を下回ったコマは全速に戻されるのでヒーロー背景として続きますが、勝ち負けのコードはなく、決着のルールは自分で足す必要があります。

角速度・歳差・衝突を一枚にまとめたヒーローアリーナ
新しいタブで開く

09 · 角速度・歳差・衝突を一枚にまとめたヒーローアリーナ — 計測 累積面積 28.945% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

HERO_PRECESSION_GAINHERO_RESTITUTIONheroStep
    for (i = 0; i < heroTops.length; i += 1) {
      t = heroTops[i];
      t.omega *= Math.exp(-HERO_DECAY * dtS);
      t.spin += t.omega * dtS;
      var slow = 1 - clamp(Math.abs(t.omega) / HERO_OMEGA_START, 0, 1);
      t.kick = Math.max(0, t.kick - dtS * 0.9);
      t.tilt = HERO_PRECESSION_GAIN * 0.3 * (1 + slow) + t.kick * 0.5;
      t.az += 150 * Math.sin(t.tilt) / Math.max(Math.abs(t.omega), 8) * dtS;
      var cxs = 160 - t.x, cys = 120 - t.y, d = Math.hypot(cxs, cys) || 1;
      t.vx += (cxs / d * 14 + Math.cos(t.az) * HERO_DRIFT * 0.4) * dtS;
      t.vy += (cys / d * 14 + Math.sin(t.az) * HERO_DRIFT * 0.3) * dtS;
      t.vx *= HERO_FRICTION; t.vy *= HERO_FRICTION;
      t.x += t.vx * dtS; t.y += t.vy * dtS;
      wallBounce(t, 0.85, 6);
      if (elapsed % 48 === 0) pushTrail(heroTrails[i], t.x, t.y + 3, 70);
      if (Math.abs(t.omega) < 9) {
        t.omega = (i === 1 ? -1 : 1) * HERO_OMEGA_START;
        addPulse(t.x, t.y + 4, '255,241,201');
      }
    }

項目09はフレームの28.9%を平均強度64.0で塗り替え、23/23フレームで動きが測定されました。

技法別に見るコマ回転のコード

九つの項目を並べると、再描画面積が最も大きいのは05(40.6%)で、二つのコマがキャンバスを横切り、衝突のたびに波紋が加わります。最も小さいのは06(15.6%)で、コマは一か所に留まりポインターのリングだけが動きます。項目04は例外で、1フレームの最大は3.9%しかありませんが、床の軌跡が積み重なってループ全体では25.7%になります。平均強度は07の60.4から01の76.2までで、最も低いのは縞が帯に融合する07です。

項目 技法 再描画面積 平均強度 動いたフレーム
01 角速度の減衰 23.0% 76.2 23/23 フレーム
02 歳差の軸の円 23.1% 64.6 23/23 フレーム
03 増えていく傾きの振れ幅 20.2% 68.2 23/23 フレーム
04 床の摩擦の軌跡 25.7% 60.8 23/23 フレーム
05 二つのコマの衝突 40.6% 72.1 23/23 フレーム
06 ポインターの回転力 15.6% 73.1 23/23 フレーム
07 残像の縞 20.4% 60.4 23/23 フレーム
08 転倒の判定 22.4% 68.4 23/23 フレーム
09 ヒーローアリーナ 28.9% 64.0 23/23 フレーム

値が場面を変える場所 — 角速度・摩擦・歳差係数

場面の見え方はほとんど三組の定数で決まります。減衰の割合(ANGULAR_DECAY・AXIS_DECAY・OMEGA_FADE・HERO_DECAY)は回転がどれだけ続くかを決め、摩擦と移動の定数(FLOOR_FRICTION・DRIFT_ACCEL・HERO_FRICTION)はコマがどこまでさまようかを決め、係数と振れ幅の定数(PRECESSION_GAIN・TILT_AMP_MAX・TILT_LIMIT)はどこまで傾くかを決めます。どれもループの構造には触れず、ステップ関数がすでに読んでいる数を拡大縮小するだけで、これは 跳ねるボール アニメーション 9選 — 衝突と重力の物理計算 の反発係数の扱いと同じ考え方です。コマ 回転速度の変わり方を一つずつ見たいときは、すぐ下の変形の表が一行につき値を一つだけ変えています。

ただし、コマ 回転 模様や動きを剛体の力学で厳密に導く式はコードにありません。どの場面も手で調整した近似で、角速度は割合で落ち、傾きは残った速度の関数で、力を積分する計算は一つもありません。だからスマートフォンでも動き、定数を変えても式が壊れません。回る盤に同じ指数の減衰を使う例は web ルーレット — 弾いて回し摩擦で止まる回転盤キャンバス9種 にもあります。

どこで壊れるか — 止まる直前・狭い画面・衝突が重なるとき

止まる直前はコマのアニメーションが最も壊れやすい場所で、このコードは二か所で守っています。precessAxis() は Math.max(t.omega, 8) で割るので、角速度がゼロに近づいても軸の速さが発散せず、ループする場面は項目01の4や項目09の9のような下限を割ると速度を戻します。項目08の転倒判定にも細かい点があり、傾きの式 0.08 + 0.95 × slow^2.2 では傾きの限界0.62を超えるのは角速度がおよそ6.8を下回ったときなので、コマが倒れる時点では FALL_OMEGA の9の条件はすでに満たされています。狭い画面では fit() がキャンバスを枠の幅に合わせ、変わるのは描画の倍率 sc だけで、物理は幅320・高さ200の固定の空間で動き続けるので、場面の中身は変わらずコマが小さく見えます。三つ目の弱点は衝突で、項目09のループは組を一組ずつ順に処理するため、最初の組に押されたコマが自分の番の前に次の組と重なることがあり、pairCollide() はすでに離れつつある組には衝撃を与えません。九つの index.html はパスワード tqsqe3wp の zip にまとめてあるので、定数を大きくして自分の場面が壊れるところまで試せます。

変形3つとアクセシビリティ

変形 値 感じ方
歳差係数を調整する 02 PRECESSION_GAIN 0.6 → 1.4 全速のときの傾きは係数の0.7倍なので0.42から0.98に増え、軸の先端が床の上で大きな円を描いて胴が大きくふらつき、値を小さくするとコマはほぼまっすぐ立って回ります。
床の摩擦率を調整する 04 FLOOR_FRICTION 0.992 → 0.97 値を下げるのは摩擦を強めることで、移動の力が保てる速さがおよそ4分の1に落ちてコマはあまり動き回らず、値を上げると長く滑って床に長い軌跡を残します。
残像の縞の本数を調整する 07 STRIPE_COUNT 6 → 12 本数を増やすと縞が細かくなり、にじみ始める角速度がおよそ14.7からおよそ7.3に下がり、減らすと太い縞が遅くなるまではっきり残ります。

九つの Canvas API の場面はすべて、requestAnimationFrame のループを始める前に matchMedia で prefers-reduced-motion を読みます。動きを減らす設定のときは九つとも固定ステップの短い予熱(項目01〜09で順に30・40・40・30・30・20・40・20・60)を回し、落ち着いた一枚を描いてループを始めません。tabindex="0" を持ち入力を受けるのは項目06だけで、ポインターのドラッグ、左右の矢印キー、スペースキーがアニメーションの停止中も回転を変え、台本のスワイプは無効になります。

コードはコマの傾きを何で計算しているのか?

どの場面でも傾きは失われた角速度の関数で、slow = 1 - omega / start と書けます。項目01は 0.06 + 0.5 × slow²、項目03は振れ幅に正弦を掛け、項目08は slow の2.2乗を使い、項目02は係数から傾きを決めます。drawTop() はその傾きと軸の向き az の正弦・余弦から軸の上端を置き、胴は傾きが大きいほど縦に潰れます。遠近の床のグリッド、コマの下の光だまり、半透明の回転の残像の円盤は表示層で、速度・傾き・位置・衝突の衝撃が計算の本体です。計算して描くという同じ考え方は html5 canvas アニメーション9種 — 水滴とブラックホール と 物理シミュレーション 作り方 9選 — バネから慣性スクラブまで にも通じます。

コマの場面を使う前によくある質問

コマ 回転 時間を長くするには、どの値を変えればいいですか?

項目01なら ANGULAR_DECAY を小さくします。割合が小さいほど角速度が落ちる速さが緩やかになり、OMEGA_START に戻るまでの時間が延びます。摩擦の FLOOR_FRICTION は逆に、1に近いほど長く滑ります。

項目06は紐を引くコマのように動きますか?

いいえ、紐はコードにありません。代わりに、なでた動きとコマの中心からのベクトルの外積が回転力になり、スペースキーを押し続けるとコマがすでに回っている向きに速度が上がり続けます。

この九つのうち一つだけを自分のページに入れられますか?

はい。各場面は、自分のキャンバス・定数・ステップ関数を持つ一つの index.html に収まっているので、一つのコマだけをヒーロー部分に取り出しても、残り八つのコードを持ち込む必要はありません。

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

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