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

煙 エフェクト 9パターン — ノイズ計算で作るキャンバス演出

この煙 エフェクトは9パターンあります。この煙にはパーティクル物理エンジンがない。それでも06番はポインターが近づくと渦を巻いて避け、07番はボタンを押すと塊が弾けるように広がり、09番はスクロール値だけで場面全体の密度が変わる。

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

煙 エフェクト 9パターン — ひとつのキャンバス計算が煙の動き方をどう変えるか

以下の9パターンはすべて同じキャンバスループを共有しています。パーティクルを発生させ、寿命の割合で年齢を進め、放射状グラデーションで描く——CSSの @keyframes で要素をトゥイーンさせているわけではありません。01番は最も素直な読み方で、ただ立ち上って滲むだけの煙柱です。06番になると同じループがポインター駆動のベクトル場に沿って曲がり、09番はまったく同じ計算をスクロール連動のシーン全体に使っていて、フレームごとにグラデーションを塗り直す発想はレインボーグラデーション 9パターン — キャンバスで作るプラズマ背景とも共通しています。以下の並びは、静的な煙柱から風・密度・距離・色・ポインター操作・押下トリガー・奥行き層・スクロール駆動シーンへと、ループに読ませる入力をひとつずつ増やしていく順になっています。

01上に立ち上る基本の煙柱

煙のパーティクルが下から立ち上るにつれ、徐々に横に広がりぼやけが強くなります——PLUME_DRIFT が横揺れの幅を決め、PLUME_LIFE_MS が寿命の上限を決め、その寿命が尽きる前に SMOKE_RISE_SPEED_PX が粒子を上端の外まで運びます。暗い背景さえあれば発光がそのまま読めるので、控えめな背景演出としてそのまま使えます。

上へ立ち上る基本の煙の柱
新しいタブで開く

01 · 上へ立ち上る基本の煙の柱 — 計測 累積面積 12.548% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

SMOKE_RISE_SPEED_PXPLUME_DRIFTPLUME_LIFE_MS
  function drawSmoke(nowMs) {
    var sx = cw / CALC_W, sy = ch / CALC_H;
    ctx.clearRect(0, 0, cw, ch);
    ctx.globalCompositeOperation = 'lighter';
    for (var i = 0; i < particles.length; i += 1) {
      var p = particles[i];
      var f = Math.max(0, Math.min(1, (nowMs - p.born) / PLUME_LIFE_MS));
      var turb = (valueNoise2D(p.wSeed * 0.03, nowMs * 0.0018) - 0.5) * 2;
      var x = p.bx + turb * PLUME_DRIFT * f * p.sign;
      var r = 7 + f * 32;
      var alpha = Math.sin(f * Math.PI) * 0.55;
      if (alpha <= 0.01) continue;
      var dx = x * sx, dy = p.y * sy, dr = r * ((sx + sy) / 2);
      var g = ctx.createRadialGradient(dx, dy, 0, dx, dy, dr);
      g.addColorStop(0, 'rgba(255,247,230,' + alpha + ')');
      g.addColorStop(1, 'rgba(255,247,230,0)');
      ctx.fillStyle = g;
      ctx.beginPath();
      ctx.arc(dx, dy, dr, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.globalCompositeOperation = 'source-over';
  }

01番は面積12.5%・強度25.8・動いたフレーム23/23でした。

02風の流れ場に沿って横に巻く煙

流れ場の強さは、時間とともに強まったり弱まったりするゲイン値と掛け合わされていて、その値が大きいほど煙柱が上りながら大きく横に曲がります——WIND_FLOW_STRENGTH が曲がりの上限を決め、gustEnvelope(nowMs) は訪問者が動かすスライダーではなく時間ベースのゲインです。flowAngle() は各粒子自身の位置を読むので、発生源に近い粒子ほど曲がりが小さく、すでに高く上った粒子ほど曲がりが大きくなります。

風の流れ場に沿って横に曲がる煙
新しいタブで開く

02 · 風の流れ場に沿って横に曲がる煙 — 計測 累積面積 11.102% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

WIND_FLOW_STRENGTHflowAnglegustEnvelope
  function stepSim(dtMs, nowMs) {
    var dtS = dtMs / 1000;
    spawnAcc += dtMs * PLUME_SPAWN_PER_S / 1000;
    while (spawnAcc >= 1) { spawn(nowMs); spawnAcc -= 1; }
    var gust = gustEnvelope(nowMs);
    var alive = [];
    for (var i = 0; i < particles.length; i += 1) {
      var p = particles[i];
      var f = Math.max(0, Math.min(1, (nowMs - p.born) / PLUME_LIFE_MS));
      var ang = flowAngle(p.x, p.y, nowMs);
      p.x += Math.sin(ang) * WIND_FLOW_STRENGTH * gust * f * dtS;
      p.y -= SMOKE_RISE_SPEED_PX * dtS;
      if (nowMs - p.born < PLUME_LIFE_MS) alive.push(p);
    }
    particles = alive;
  }

02番は面積11.1%・強度16.7・動いたフレーム23/23でした。

03発生量の値で濃さが変わる煙

発生量の値を上げると重なり合う粒子が増えて、煙がより濃く不透明に見えます——SMOKE_EMISSION_RATE が smkRate の1秒あたりの発生数を決め、DEMO_SWEEP_MS がその値を自動で上下に揺らすので、何も操作しなくても違いがそのまま見えます。警告や注意状態の背景として使うと、濃い煙ほど緊迫感が伝わります。

発生量の値で濃さが変わる煙
新しいタブで開く

03 · 発生量の値で濃さが変わる煙 — 計測 累積面積 17.719% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

SMOKE_EMISSION_RATEsmkRateDEMO_SWEEP_MS
  function stepSim(dtMs, nowMs) {
    var dtS = dtMs / 1000;
    spawnAcc += dtMs * emissionRate / 1000;
    while (spawnAcc >= 1) { spawn(nowMs); spawnAcc -= 1; }
    var alive = [];
    for (var i = 0; i < particles.length; i += 1) {
      var p = particles[i];
      p.y -= SMOKE_RISE_SPEED_PX * dtS;
      if (nowMs - p.born < PLUME_LIFE_MS) alive.push(p);
    }
    particles = alive;
    if (particles.length > 320) particles = particles.slice(particles.length - 320);
  }

03番は面積17.7%・強度16.2・動いたフレーム23/23でした。

04発生源から離れるほど溶けて消えていく煙

発生源からの距離に応じて不透明度が下がっていき、ある距離を超えると粒子は完全に消えます——DISSOLVE_DISTANCE がその境界で、SMOKE_DISSOLVE_MS と DRIFT_SPEED_PX_S から一度だけ計算され、時間の長さを移動距離に変換しています。読み込み完了後の後片付け演出として使えば、手動でフェードアウトさせなくても自然に消えます。

発生源から離れるほど薄れて消える煙
新しいタブで開く

04 · 発生源から離れるほど薄れて消える煙 — 計測 累積面積 15.542% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

SMOKE_DISSOLVE_MSDISSOLVE_DISTANCEDRIFT_SPEED_PX_S
  function spawn() {
    var ang = -Math.PI / 2 + (pRand() - 0.5) * Math.PI * 0.9;
    particles.push({
      x: SOURCE_X, y: SOURCE_Y,
      vx: Math.cos(ang) * DRIFT_SPEED_PX_S,
      vy: Math.sin(ang) * DRIFT_SPEED_PX_S,
      wSeed: pRand() * 1000
    });
  }

  function stepSim(dtMs) {
    var dtS = dtMs / 1000;
    spawnAcc += dtMs * SPAWN_PER_S / 1000;
    while (spawnAcc >= 1) { spawn(); spawnAcc -= 1; }
    var alive = [];
    for (var i = 0; i < particles.length; i += 1) {
      var p = particles[i];
      p.x += p.vx * dtS;
      p.y += p.vy * dtS;
      var dist = Math.hypot(p.x - SOURCE_X, p.y - SOURCE_Y);
      if (dist < DISSOLVE_DISTANCE) alive.push(p);
    }
    particles = alive;
  }

04番は面積15.5%・強度18.5・動いたフレーム23/23でした。

05時間とともに色が移り変わる煙

立ち上ってからの時間が長いほど、粒子の色が2色の間を少しずつ移り変わります——SMOKE_TINT_CYCLE_MS が SMOKE_TINT_FROM から SMOKE_TINT_TO まで一周する時間を決めます。ブランドカラーのヒーロー背景として使う場合、移り変わりが十分ゆっくりなので警告のようには見えず、雰囲気演出として読めます。

時間とともに色が変わる煙
新しいタブで開く

05 · 時間とともに色が変わる煙 — 計測 累積面積 16.073% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

SMOKE_TINT_CYCLE_MSSMOKE_TINT_FROMSMOKE_TINT_TO
    var sx = cw / CALC_W, sy = ch / CALC_H;
    var colorT = 0.5 + 0.5 * Math.sin(nowMs / SMOKE_TINT_CYCLE_MS * Math.PI * 2);
    var cr = Math.round(lerp(SMOKE_TINT_FROM[0], SMOKE_TINT_TO[0], colorT));
    var cg = Math.round(lerp(SMOKE_TINT_FROM[1], SMOKE_TINT_TO[1], colorT));
    var cb = Math.round(lerp(SMOKE_TINT_FROM[2], SMOKE_TINT_TO[2], colorT));
    ctx.clearRect(0, 0, cw, ch);
    ctx.globalCompositeOperation = 'lighter';
    for (var i = 0; i < particles.length; i += 1) {
      var p = particles[i];
      var f = Math.max(0, Math.min(1, (nowMs - p.born) / PLUME_LIFE_MS));
      var turb = (valueNoise2D(p.wSeed * 0.03, nowMs * 0.0018) - 0.5) * 2;
      var x = p.bx + turb * 30 * f * p.sign;
      var r = 7 + f * 32;
      var alpha = Math.sin(f * Math.PI) * 0.6;
      if (alpha <= 0.01) continue;
      var dx = x * sx, dy = p.y * sy, dr = r * ((sx + sy) / 2);
      var g = ctx.createRadialGradient(dx, dy, 0, dx, dy, dr);
      g.addColorStop(0, 'rgba(' + cr + ',' + cg + ',' + cb + ',' + alpha + ')');
      g.addColorStop(1, 'rgba(' + cr + ',' + cg + ',' + cb + ',0)');
      ctx.fillStyle = g;
      ctx.beginPath();
      ctx.arc(dx, dy, dr, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.globalCompositeOperation = 'source-over';

05番は面積16.1%・強度30.6・動いたフレーム23/23でした。

06ポインターを避けて渦を巻く煙

ポインターが煙柱に近づくほど、近くの粒子が渦を巻いて押しのけられます——SMOKE_SWIRL_RADIUS がその押しの届く範囲を決め、SMOKE_SWIRL_STRENGTH が押しの強さを決め、誰もホバーしていないときは virtualPointerOrbit がデモ用の仮想ポインターを自分で動かします。矢印キーはその仮想ポインターを上下左右4方向に動かせるので、マウスがなくても渦が見られます。

ポインターを避けて渦を巻く煙
新しいタブで開く

06 · ポインターを避けて渦を巻く煙 — 計測 累積面積 21.019% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

SMOKE_SWIRL_RADIUSSMOKE_SWIRL_STRENGTHvirtualPointerOrbit
  function stepSim(dtMs, nowMs) {
    var dtS = dtMs / 1000;
    spawnAcc += dtMs * PLUME_SPAWN_PER_S / 1000;
    while (spawnAcc >= 1) { spawn(nowMs); spawnAcc -= 1; }
    var alive = [];
    for (var i = 0; i < particles.length; i += 1) {
      var p = particles[i];
      var turb = (valueNoise2D(p.wSeed * 0.03, nowMs * 0.0018) - 0.5) * 2;
      p.x += turb * 8 * dtS;
      p.y -= SMOKE_RISE_SPEED_PX * dtS;
      var rx = p.x - pointerX, ry = p.y - pointerY;
      var dist = Math.hypot(rx, ry);
      if (dist < SMOKE_SWIRL_RADIUS && dist > 0.5) {
        var push = (1 - dist / SMOKE_SWIRL_RADIUS) * SMOKE_SWIRL_STRENGTH;
        var tx = -ry / dist, ty = rx / dist;
        p.x += tx * push * dtS;
        p.y += ty * push * dtS;
      }
      if (nowMs - p.born < PLUME_LIFE_MS) alive.push(p);
    }
    particles = alive;
  }

06番は面積21.0%・強度29.6・動いたフレーム23/23でした。

07ボタンを押すと弾けるように広がる煙

ボタンを押した瞬間、ひとつの粒子塊が発生してあらゆる方向に弾け飛び、その後すぐに消えていきます——SMOKE_BURST_COUNT が塊の大きさを決め、SMOKE_BURST_LIFE_MS が消える速さを決め、smkBtn はクリックでもEnterでもSpaceでも同じように反応します。送信完了ボタンのフィードバックとして使うと、一度弾けたらすぐに消えるので邪魔になりません。

ボタンを押すと弾けるように広がる煙
新しいタブで開く

07 · ボタンを押すと弾けるように広がる煙 — 計測 累積面積 25.765% · 動いたフレーム 18/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

SMOKE_BURST_COUNTSMOKE_BURST_LIFE_MSsmkBtn
  function burst(bornMs) {
    for (var i = 0; i < SMOKE_BURST_COUNT; i += 1) {
      var ang = pRand() * Math.PI * 2;
      var speed = SMOKE_BURST_SPEED_PX_S * (0.4 + pRand() * 0.9);
      particles.push({
        x: ORIGIN_X, y: ORIGIN_Y,
        vx: Math.cos(ang) * speed,
        vy: Math.sin(ang) * speed,
        born: bornMs
      });
    }
  }

  function stepSim(dtMs, nowMs) {
    var dtS = dtMs / 1000;
    var alive = [];
    for (var i = 0; i < particles.length; i += 1) {
      var p = particles[i];
      p.x += p.vx * dtS;
      p.y += p.vy * dtS;
      p.vy -= 8 * dtS;
      if (nowMs - p.born < SMOKE_BURST_LIFE_MS) alive.push(p);
    }
    particles = alive;
  }

07番は面積25.8%・強度72.7・動いたフレーム18/23でした——他より低いのは意図どおりで、弾けた塊は固定23フレームの計測時間より先に消え切るため、後半の数フレームは動かす粒子自体がもう残っていないだけです。

08層ごとに速さが違う重なった煙

奥の層ほど粒子の動きが遅くぼやけて見え、手前の層との速度差がそのまま奥行きに読めます——LAYERS が層ごとの粒子プールを別々に持ち、radiusMul・alphaMul が共通の描画ループに触れずに層ごとの大きさと不透明度だけを調整します。ポートフォリオのヒーロー演出として使うと、静止画の状態でも奥行きが伝わります。

レイヤーごとに速度が違う何層もの煙
新しいタブで開く

08 · レイヤーごとに速度が違う何層もの煙 — 計測 累積面積 23.225% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

LAYERSradiusMulalphaMul
  function drawSmoke(nowMs) {
    var sx = cw / CALC_W, sy = ch / CALC_H;
    ctx.clearRect(0, 0, cw, ch);
    for (var li = 0; li < LAYERS.length; li += 1) {
      var L = LAYERS[li];
      for (var i = 0; i < L.particles.length; i += 1) {
        var p = L.particles[i];
        var f = Math.max(0, Math.min(1, (nowMs - p.born) / PLUME_LIFE_MS));
        var turb = (valueNoise2D(p.wSeed * 0.03, nowMs * 0.0016) - 0.5) * 2;
        var x = p.bx + turb * 14 * f;
        var r = (6 + f * 16) * L.radiusMul;
        var alpha = Math.sin(f * Math.PI) * L.alphaMul;
        if (alpha <= 0.01) continue;
        var dx = x * sx, dy = p.y * sy, dr = r * ((sx + sy) / 2);
        var g = ctx.createRadialGradient(dx, dy, 0, dx, dy, dr);
        g.addColorStop(0, 'rgba(30,28,34,' + alpha + ')');
        g.addColorStop(1, 'rgba(30,28,34,0)');
        ctx.fillStyle = g;
        ctx.beginPath();
        ctx.arc(dx, dy, dr, 0, Math.PI * 2);
        ctx.fill();
      }
    }
  }

08番は面積23.2%・強度18.8・動いたフレーム23/23でした。

09スクロール値に反応するヒーロー煙シーン

スクロール値が大きくなるにつれ、煙の発生量と広がりの半径が同時に増えていき、シーン全体の密度が濃くなっていきます——scrollProgress が両方の数値を同時に動かし、実際のスクロール入力がないときは triangleWave() が SCROLL_AUTO_PERIOD_MS ごとに自動で値を行き来させます。ArrowUpとArrowDown、PageUpとPageDownは同じ値を手動で動かせます。サイトのトップにあるヒーローセクションとして使うと、スクロールが深くなるほどシーンの密度が増していきます。

スクロール値に反応するヒーロー煙シーン
新しいタブで開く

09 · スクロール値に反応するヒーロー煙シーン — 計測 累積面積 11.384% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

scrollProgresstriangleWaveSCROLL_AUTO_PERIOD_MS
    var emissionRate = BASE_EMISSION_PER_S + scrollProgress * EMISSION_RANGE_PER_S;
    var swirlRadius = SMOKE_SWIRL_RADIUS_BASE + scrollProgress * SMOKE_SWIRL_RADIUS_RANGE;
    spawnAcc += dtMs * emissionRate / 1000;
    var cap = 260;
    while (spawnAcc >= 1) {
      if (particles.length < cap) spawn(nowMs);
      spawnAcc -= 1;
    }
    var alive = [];
    for (var i = 0; i < particles.length; i += 1) {
      var p = particles[i];
      var turb = (valueNoise2D(p.wSeed * 0.03, nowMs * 0.0017) - 0.5) * 2;
      p.x += turb * 10 * dtS;
      p.y -= SMOKE_RISE_SPEED_PX * dtS;
      var rx = p.x - pointerX, ry = p.y - pointerY;
      var dist = Math.hypot(rx, ry);
      if (dist < swirlRadius && dist > 0.5) {
        var push = (1 - dist / swirlRadius) * SMOKE_SWIRL_STRENGTH;
        var tx = -ry / dist, ty = rx / dist;
        p.x += tx * push * dtS;
        p.y += ty * push * dtS;
      }
      if (nowMs - p.born < PLUME_LIFE_MS) alive.push(p);
    }
    particles = alive;

09番は面積11.4%・強度18.0・動いたフレーム23/23でした。

煙 エフェクト 9パターンを面積と強度で測定した結果

累積面積は固定カメラのフレームのうち実際に塗り直された割合を平均したもので、強度は塗り直された部分の平均ピクセル差です。07番の弾けは触れたピクセルあたりの強度が最も高い一方で最も寿命が短く、06番のポインター渦は雰囲気演出の中で最も広い面積を占め、09番のヒーローシーンはスクロール値でしか密度が増えないぶん面積は控えめです。煙 エフェクト 動画のような落ち着いた見た目にするなら、面積20%未満・強度30未満に収まるあたりが目安で、この9パターンの大半がそこに収まっています。

| パターン | 累積面積 | 平均強度 | 動いたフレーム |

|---|---|---|---|

| 01 立ち上る煙柱 | 12.5% | 25.8 | 23/23 |

| 02 風の流れ場ドリフト | 11.1% | 16.7 | 23/23 |

| 03 発生量の密度変化 | 17.7% | 16.2 | 23/23 |

| 04 距離フェード溶解 | 15.5% | 18.5 | 23/23 |

| 05 色調シフト | 16.1% | 30.6 | 23/23 |

| 06 ポインター渦回避 | 21.0% | 29.6 | 23/23 |

| 07 押下バースト | 25.8% | 72.7 | 18/23 |

| 08 層状奥行きパララックス | 23.2% | 18.8 | 23/23 |

| 09 ヒーロースクロールシーン | 11.4% | 18.0 | 23/23 |

値が煙を変える場所 — 発生量・上昇速度・溶解時間

以下の3つの値は見た目以上に働いています——SMOKE_EMISSION_RATE は03番が1画面に詰め込む粒子数を決め、SMOKE_RISE_SPEED_PX は01番の煙柱がどれだけ速く上って画面から消えるかを決め、SMOKE_DISSOLVE_MS は04番の粒子がどこまで漂ってから消えるかを決めます。どれも描かれる形そのものは変えず、同じ放射状グラデーションのループがどれだけ濃く・速く・遠くまで読めるかを変えるだけですが、そこがまさに煙 エフェクト 素材として作り込まれたものと、同じ噴煙をただループさせただけのものを分ける部分です。html5 canvas アニメーション9種 — 水滴とブラックホールも似た種類のピクセル単位の計算を、漂う粒子ではなく歪んだ画像の上で行っています。

どこで壊れるか — reduced motion 設定・低スペック端末でのフレーム落ち・背景なしでの使用

prefers-reduced-motion はキャンバス自体を止めるのではなく、読み込み時に全項目から自動再生の .is-demo クラスを外すだけなので、06番は実際にポインターが動けば渦を巻き続け、07番は実際にクリックすれば弾け続けます——止まるのはスクリプトの自動再生ループだけです。すべての項目が粒子ひとつひとつに createRadialGradient() を呼んで描き直しており、そこに globalCompositeOperation: 'lighter' が重なるため、何百もの粒子が重なり合う状況で低スペック端末のフレーム落ちが真っ先に表れます。これらの粒子は暗さを前提に読ませる設計で、9つのどれかを白一色のページに置くと加算発光がほとんど見えなくなります——暗いピクセルを明るくする仕組みであって、明るいページの上に不透明な形として乗る仕組みではないからです。この9つを支える index.html・style.scss の組は59z8ez3nで、この9パターンすべてを支えるキャンバスループがどれだけ小さいかを確かめる一番早い方法です。

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

| 変形 | 値の変化 | 印象 |

|---|---|---|

| 煙の発生量の値を調整する | 03 SMOKE_EMISSION_RATE 4 → 18 | 値を下げると粒子数が少なく保たれて煙は薄くまばらに見え、上げると粒子が密に重なって濃く不透明に見えます。 |

| 煙の上昇速度の値を調整する | 01 SMOKE_RISE_SPEED_PX 12 → 40 | 値を下げるとゆっくり立ち上って長く画面に留まり、上げると勢いよく上って早く画面から消えます。 |

| 煙の溶解時間の値を調整する | 04 SMOKE_DISSOLVE_MS 900 → 2600 | 値を下げると発生源のすぐ近くで消え、上げると遠くまで漂ってからようやく消えます。 |

9つすべてが自分のスクリプトの冒頭で一度だけ prefers-reduced-motion を確認し、個々のプロパティを止めるのではなく自動再生クラスを外すことで応えるので、モーションを減らしたい訪問者にも空白のキャンバスではなく静止した形がきちんと見えます。矢印キーは06番の仮想ポインターを上下左右4方向に動かし、EnterまたはSpaceはクリックと同じように07番の弾けを起こし、09番はArrowUp・ArrowDownに加えてPageUp・PageDownにも反応してスクロールバーなしでスクロール値を動かせます。CanvasRenderingContext2D、requestAnimationFrame()、prefers-reduced-motion はMDNに文書化されています。

煙エフェクトはパーティクルライブラリなしで作れるか?

この9つはどれもライブラリを読み込んでいません。それぞれが単純なオブジェクトの配列を自前で持ち、Math.max(0, Math.min(1, (nowMs - p.born) / PLUME_LIFE_MS)) で年齢を進め、寿命が尽きたものを取り除いているだけです——パーティクルライブラリが自前のAPIで包んでいるのと同じ仕組みを、そのまま外に出しているにすぎません。流体シミュレーション9種 — canvas の煙・インク格子計算 (WebGLなし)も同じくライブラリなしのアプローチを、個々の粒子ではなくピクセルグリッド全体で行っています。個々の粒子9つでは足りなくなったときの次の一歩がこちらです。

この9つを試す前によくある質問に答えます

ビルド手順なしで、この9つのどれかをhtml css smoke effectとしてページにそのまま置けますか?

置けます。どの項目も <canvas>・スクリプト・スタイルシートを内包した単体の index.html で、ブラウザで開けばそのまま動きます。バンドラーもフレームワークも要りません。

なぜ07番だけ動いたフレームが他より少ないのですか?

弾けた塊は固定23フレームの計測時間の途中で完全に消え切るため、後半の数フレームには本当に動かす粒子が残っていません。これは計測漏れではなく、設計どおりバーストが働いた結果です。

これらのような煙 エフェクト 動画の背景にWebGLは必要ですか?

必要ありません。9つとも発光には globalCompositeOperation: 'lighter' を使った普通の2D CanvasRenderingContext2D API だけで動いていて、WebGLコンテキストもシェーダーもどこにもありません。

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

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