TDMO

Canvas UI エフェクト全部テスト

Canvas UI の主要エフェクトを「全部試す」建前のテストベンチ。ただし本物のライブラリ(npx/ビルド前提)は使わず、同じ狙いを依存ゼロ・ビルド不要の自前実装で再現しています。各タイルにマウス(スマホは指)を重ねると反応します。ShatterRipple はクリック。

この説明はHTML。動くのは各タイルの <canvas> だけで、本文はクローラーから読めます。prefers-reduced-motion で静止、画面外のタイルは自動停止します。 · ← 実験場へ

Vanilla JSでの実装方法

このページは、各エフェクトを個別のライブラリとして読み込んでいるわけではありません。HTMLの data-fx とJavaScriptのオブジェクトを対応させ、すべてのcanvasで入力処理・リサイズ・描画ループを共有しています。基本構造は次のとおりです。

1. HTMLでエフェクト名を指定する

<canvas data-fx="ripple"></canvas>

data-fx の値が、JavaScript側の EFFECTS.ripple に対応します。新しいエフェクトを増やすときも、canvasを追加して同名の処理を定義するだけです。

2. エフェクトを initframe に分ける

var EFFECTS = {
  ripple: {
    init: function (tile) {
      tile.s = { ripples: [] };
    },
    onDown: function (tile) {
      tile.s.ripples.push({
        x: tile.mx, y: tile.my, radius: 2, alpha: 1
      });
    },
    frame: function (tile, dt) {
      // 状態をdt秒ぶん進め、tile.ctxへ描画する
    }
  }
};

init は粒子などの初期状態を作り、frame は毎フレームの更新と描画を担当します。クリックが必要なものだけ onDown を持ちます。状態は各canvasの tile.s に保存するため、同じ仕組みを複数のcanvasで独立して動かせます。

3. CSSサイズと描画解像度を分ける

var rect = canvas.getBoundingClientRect();
var dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width  = Math.round(rect.width  * dpr);
canvas.height = Math.round(rect.height * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

canvasの見た目の大きさはCSSに任せ、内部のピクセル数だけ端末の画素密度に合わせます。描画座標はCSSピクセルのまま扱えるので、マウス座標との計算も単純です。負荷が上がりすぎないよう、この実装ではDPRを最大2に制限しています。

4. Pointer Eventsでマウスとタッチを共通化する

canvas.addEventListener('pointermove', function (event) {
  var rect = canvas.getBoundingClientRect();
  tile.mx = event.clientX - rect.left;
  tile.my = event.clientY - rect.top;
}, { passive: true });

pointermovepointerdown を使えば、マウス・ペン・タッチをほぼ同じコードで扱えます。座標はcanvas左上を原点に変換して、各エフェクトへ渡します。

5. 1本の描画ループですべてを更新する

function loop(now) {
  tiles.forEach(function (tile) {
    if (!tile.active) return;
    var dt = tile.previous
      ? Math.min(0.05, (now - tile.previous) / 1000)
      : 0.016;
    tile.previous = now;
    tile.fx.frame(tile, dt);
  });
  requestAnimationFrame(loop);
}

requestAnimationFrame はcanvasごとに作らず、ページ全体で1本だけ動かします。dt は前フレームからの経過秒で、端末の描画速度が違っても移動速度をそろえるために使います。タブ復帰直後などの大きな値は0.05秒で打ち切り、粒子が突然飛ぶのを防ぎます。

6. 見えているcanvasだけを動かす

IntersectionObserver で画面内にあるタイルだけ active にしています。また、OSの「視差効果を減らす」が有効なら prefers-reduced-motion を検出してアニメーションを開始せず、静止した1フレームだけを描画します。見た目だけでなく、CPU・バッテリー消費とアクセシビリティも実装の一部です。

各エフェクトはCanvas 2D APIの組み合わせ

炎・雲・液体は円形グラデーションと加算合成、Glassはぼかしとクリッピング、Shatterは小さな矩形の速度計算、Particle Revealはオフスクリーンcanvasから文字の画素を採取して再配置しています。VHSはRGBの位置ずれと走査線、Gridは透視図法風の直線、Rippleは半径と透明度を時間で変える円です。つまり特殊なAPIではなく、arcfillRectcreateRadialGradientglobalCompositeOperation など、標準のCanvas 2D APIを小さく組み合わせて再現しています。