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

キャンバス 稲妻 エフェクト 作り方9種 — フラクタル中点変位で落ちる雷

稲妻 エフェクト 作り方とは、雷の画像を貼る仕事ではなく、まっすぐな線分を再帰的に割っていく計算のことです。ライブラリ·WebGL なしで線分を再帰的に割る中点変位ひとつで稲妻の枝が自ら分岐します。

デモ

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

線分をひとつ割る規則が九つの雷に分かれる理由

並び順の基準は派手さではなく再帰が広がっていく向きです。01の基本落雷が中点変位ひと層で幹を立てる原形で、02はその分割の過程へ低い確率の分岐を乗せ、03は複数の幹へ指数分布のリズムを重ねます。04が幹を消した後に残る光で画面を運ぶと、05と06は経路を毎フレーム計算し直す持続型(電極間のアーク・カーソル追跡)へ進み、07は5つの着弾点をつなぐ連鎖、08は幹よりも画面全体のフラッシュの減衰、09は幹まで捨てた雲の中の発光で終わります。同じ種の乱数で山を積むノイズ地形の9盤や燃料を燃やす計算の炎と違い、この9盤を貫く数は線分の長さに比例して縮む変位の幅ひとつです。

残る長さに比例する変位が9盤を貫く

01から09まで、幹を割る関数が持っている鍵は「ずれの幅をいま割ろうとしている線分の長さに結び付ける」ことです。再帰が一段進むごとに線分は半分になり、ずれも半分に近づくため、太い屈曲の上に細かいうねりが載る自分と似た構造が勝手に育ちます。順序はこの一本の規則が幹から分岐へ、分岐から光へ、光から畑全体へと移っていく道筋として読めます。

稲妻はなぜフラクタルで描くか — 中点変位と再帰分岐の計算

中点変位は線分が短くなるほど変位も小さくなる

off = (rand() * 2 - 1) * len * ROUGH * 0.5の中で、ずれの幅が残った線分の長さlenに比例していることがこの技法の心臓です。再帰が一段進むたびに線分は半分になるのでずれも半分に近づき、太い屈曲の上に細かいうねりが載る自分と似た構造はここから生まれます。地形の標高を同じ規則で揺らすダイヤモンド・スクエアと同じ系譜なので、この1関数で山脈も稲妻も描けます。ずれを固定ピクセルで与えると、大きい区間は滑らかで小さい区間は砕けた、上と下で尺の合わない幹になります。

分岐確率と世代減衰がフラクタルの木を作る

稲妻の傍枝は幹の複製ではなく幹の縮小版でなければ読めません。02は枝を生やす確率BRANCH_P * (1.15 - gen / (MAX_GEN + 1))を世代が深くなるほど下げ、変位の幅ROUGH * Math.pow(DECAY, gen)と太さ・明るさまで同じDECAYで減衰させて、世代が下がるほど細く暗い枝にします。確率だけがあって減衰がないとすべての枝が元の幹と同じ存在感を持って稲妻が野原の黒い木のようになり、減衰だけがあって確率の条件がないと枝が幹の一定の位置へ機械的に寄ります。

指数分布の待ち時間が空のリズムを決める

実際の落雷は一定の周期で落ちず、短く連発してから長く休みます。このリズムは一様な乱数ではなく指数分布-Math.log(1 - rand()) * meanから出てきます。一様分布は最小と最大の間が均等に広がって空が時計のようになり、指数分布は短い待ちが高頻度で引かれながらまれにとても長い待ちが現れるため、連発と静けさの対比が生まれます。03は嵐の強さstormが高まっている間は平均をMEAN * (1.65 - storm * 1.1)へ短く詰めて、空の状態が自らリズムを変えるようにしています。

lighter合成が光の重なりを計算する

稲妻は線ではなく光なので、描いた線の上へ半透明の色を重ねる通常の合成では発光の存在感が出ません。02・03・05・06・07・08・09が幹や光源をglobalCompositeOperation = 'lighter'で描くのは、ピクセルの値を互いに足すこの合成が重なった光を本物の光のように積み上げるからです。08のフラッシュが空とシルエットを一度に明るくするのも、09のパルスが雲の上へ積もるのも、同じ計算の別の表現です。

項目別コード

各項目とも、動きを決める定数の名前を明示し、動いているデモのソースからそのまま写した関数を示します。要約し直した断片ではなく実コードなので、このページをそのまま自分の画面に持ち込めます。

01基本の落雷 — 線分を半分に割る中点変位ひと層

ヒーロー背景に一瞬落ちる落雷やローディング画面の閃光に使う、始点と終点を結ぶ線分をDEPTH6段階で再帰分割する最小の盤です — 実測では累積面積37.683%・強度平均46.0で、24フレーム中23フレームに動きがありました。

基本の落雷 — 線分を半分に割る中点変位ひと層
新しいタブで開く

01 · 基本の落雷 — 線分を半分に割る中点変位ひと層 — 計測 累積面積 37.683% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

moveTolineTorequestAnimationFrame

法線方向へ押された中点が幹を作る

// ── 中点変位 — 稲妻のすべて ──
function displace(ax, ay, bx, by, depth, out) {
  if (depth === 0) { out.push([bx, by]); return; }
  var mx = (ax + bx) / 2, my = (ay + by) / 2;
  var dx = bx - ax, dy = by - ay;
  var len = Math.hypot(dx, dy) || 1;
  var nx = -dy / len, ny = dx / len;              // 線分に垂直な法線
  var off = (rand() * 2 - 1) * len * ROUGH * 0.5; // 線分の長さに比例する乱数変位
  mx += nx * off; my += ny * off;
  displace(ax, ay, mx, my, depth - 1, out);
  displace(mx, my, bx, by, depth - 1, out);
}

中点を線分の向きではなく法線-dy, dx側へずらすことが荒い折れの出発点で、ずれの幅がlen * ROUGH(0.42) * 0.5と長さに縛られているため再帰が深くなるほど太い屈曲から細かいうねりへ自然に下り、落雷はシード0x316A01のmulberry32の順にANCHORS8か所を巡って落ち、進行度0.3〜0.37の帯で明るさを0.82まで押し戻す二度点火の包絡線が本物の稲妻の点き直しを真似ています。

02再帰分岐 — 深さごとに細く暗くなる枝

ゲームのスキルエフェクトやバナーの華やかな落雷装飾に使う、分かれた幹の上から低い確率で傍枝をさらに再帰で描き足す盤です — 実測では累積面積31.275%・強度平均41.3で、24フレーム中23フレームに動きがありました。

再帰分岐 — 深さごとに細く暗くなる枝
新しいタブで開く

02 · 再帰分岐 — 深さごとに細く暗くなる枝 — 計測 累積面積 31.275% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

lineToglobalCompositeOperationrequestAnimationFrame

世代が下がるほど穏やかになる分岐確率

function grow(ax, ay, bx, by, gen, angle, depth, segments) {
  var pts = [[ax, ay]];
  displace(ax, ay, bx, by, depth, ROUGH * Math.pow(DECAY, gen), pts);
  segments.push({ pts: pts, gen: gen });
  if (gen >= MAX_GEN) return;
  // 幹に沿って枝の候補を広く調べる
  var baseA = Math.atan2(by - ay, bx - ax);
  var k, idx, ba, bl, p;
  for (k = 1; k <= 5; k++) {
    idx = Math.floor(pts.length * k / 6);
    if (idx < 1 || idx >= pts.length) continue;
    if (rand() < BRANCH_P * (1.15 - gen / (MAX_GEN + 1))) {
      p = pts[idx];
      ba = baseA + (rand() < 0.5 ? -1 : 1) * (0.35 + rand() * 0.6);
      bl = Math.hypot(bx - ax, by - ay) * (0.5 + rand() * 0.28);
      grow(p[0], p[1], p[0] + Math.cos(ba) * bl, p[1] + Math.sin(ba) * bl,
           gen + 1, ba, depth - 1, segments);
    }
  }
}

幹を6等分した位置k=1..5ごとにBRANCH_P(0.15)のさいころを振って枝を生やし、世代genが深くなると確率・変位・太さ・明るさがすべてDECAY(0.66)の指数で縮むためMAX_GEN5世代を超えず、枝が元の幹と同じ太さで育つと稲妻が木ではなく雑草に見えるので、その境界をこの減衰の一行が守っています。

03嵐の空 — ランダムな間隔で重なる複数の落雷

ダークモードヒーローの背景に使う、次の落雷までの待ち時間を指数分布で引いて連発と静けさを交互に作る盤です — 実測では累積面積52.833%・強度平均28.6で、24フレーム中20フレームに動きがありました。

嵐の空 — ランダムな間隔で重なる複数の落雷
新しいタブで開く

03 · 嵐の空 — ランダムな間隔で重なる複数の落雷 — 計測 累積面積 52.833% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

lineToglobalCompositeOperationrequestAnimationFrame

連発と静けさを同じ分布から引く待ち時間

function spawn(t, storm) {
  // 距離 — 嵐が迫るときは近い稲妻の比重が増える
  var dist = Math.pow(rand(), 1.6 - storm * 0.8) * FARMAX;
  var ax = ANCHORS[cycle % ANCHORS.length] + (rand() - 0.5) * 0.12;
  cycle++;
  var sx = (ax + (dist * 0.5 - 0.25) * 0.2) * W;   // 遠い稲妻は中央へ寄る
  var ex = sx + (rand() - 0.5) * W * 0.2 * (1 - dist * 0.5);
  var pts = null;
  if (dist < 0.55) {
    pts = [[sx, -6]];
    displace(sx, -6, ex, H * (0.82 - dist * 0.1), DEPTH - (dist > 0.3 ? 1 : 0), pts);
  }
  bolts.push({ pts: pts, born: t, life: 0.45 + rand() * 0.25, dist: dist, ex: ex, flickAt: t, sx: sx });
}

固定間隔で落とすと落雷がメトロノームのように機械的になるのを、待ち時間-Math.log(1 - rand()) * meanが短い待ちと長い待ちを同じ分布から取り出して防ぎ、距離distが0.55を超える稲妻は幹を描かず空の明るさの反応だけを残すので、遠い雷は音もなく雲だけを光らせます。

04残光拡散 — ピクセルバッファで広がる稲妻のハロー

製品紹介ページの強調背景やダークカードのほのかに発光する縁に使う、幹が通った場所の明るさを隣接ピクセルと混ぜて広げる盤です — 実測では累積面積33.303%・強度平均40.8で、24フレーム中23フレームに動きがありました。

残光拡散 — ピクセルバッファで広がる稲妻のハロー
新しいタブで開く

04 · 残光拡散 — ピクセルバッファで広がる稲妻のハロー — 計測 累積面積 33.303% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ImageDataputImageDatarequestAnimationFrame

隣接平均で広がり冷却で冷える二重バッファ

// 拡散 — 各ピクセルを隣接4つの平均側へ SPREAD だけ引き寄せ、COOL で冷やす。2つのバッファを交代で使い偏りを消す
function diffuse() {
  for (var y = 0; y < H; y++) {
    var row = y * W;
    var up = (y > 0 ? y - 1 : y) * W;
    var dn = (y < H - 1 ? y + 1 : y) * W;
    for (var x = 0; x < W; x++) {
      var i = row + x;
      var l = lum[i];
      var av = (lum[row + (x > 0 ? x - 1 : x)] + lum[row + (x < W - 1 ? x + 1 : x)] +
                lum[up + x] + lum[dn + x]) * 0.25;
      lum2[i] = (l + SPREAD * (av - l)) * COOL;
    }
  }
  var tmp = lum; lum = lum2; lum2 = tmp;
}

明るさを色ではなくFloat32Array1枚に保存して毎フレーム隣接4ピクセルの平均側へSPREAD(0.44)だけ引き寄せCOOL(0.986)で冷やし、画面の再構成は明るさへ色を割り当てるLUT(インク・すみれ・ライラック・芯)の参照とputImageData1枚で終わるので軽く、平均GAP_MS430ms間隔で落ちる落雷の幹が消えた後に残るハローは、静止画へ書き出せばそのまま稲妻 エフェクト 素材として使える余白の光になります。

05テスラアーク — 2つの電極間を毎フレーム引き直す持続放電

SFテーマの装飾やボス戦の背景に使う、固定された2つの電極の間の経路を毎フレーム新しい乱数で計算し直して震わせる盤です — 実測では累積面積32.122%・強度平均46.5で、24フレーム中23フレームに動きがありました。

テスラアーク — 2つの電極間を毎フレーム引き直す持続放電
新しいタブで開く

05 · テスラアーク — 2つの電極間を毎フレーム引き直す持続放電 — 計測 累積面積 32.122% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

lineToglobalCompositeOperationrequestAnimationFrame

両端を固定して経路だけ震わせる呼吸

var throb0 = 0.62 + 0.38 * Math.abs(Math.sin(tSec * 2.1));
var roomA = 0.04 + 0.42 * throb0;   // インクの上に敷かれるライラック照明のアルファ
var throb = throb0;
var mag = H * 0.38 * throb;

var main = genBolt(elL.x, elL.y, elR.x, elR.y, mag, JITTER);

// 残光拡散バッファ — このフレームのアークを刻印し、広げてから舞台の上に敷く
glowStampPath(main, 14);
glowDiffuse();
glowCompose();

// 傍枝 — 経路のランダムな地点から上へ短く分かれる。毎フレーム新しい地点・新しい経路
for (var k = 0; k < FORK_N; k++) {
  var t = 0.2 + rand() * 0.6;
  var idx = Math.min(main.length - 2, Math.floor(t * (main.length - 1)));
  var p = main[idx];
  var bl = H * (0.18 + rand() * 0.22);
  var side = rand() < 0.5 ? -1 : 1;
  var br = genBolt(p.x, p.y, p.x + side * W * 0.07, p.y - bl, bl * 0.42, 4);
  glowStampPath(br, 9);   // 傍枝も残光に残す
}

電極の座標elL・elRはフレームの間でまったく動かさずgenBoltの変位幅magだけH * 0.38 * throbの半周期で呼吸させるので位置は固定のまま経路だけが絶えず計算し直され、04の拡散を電極間の舞台へ移植した残光バッファがVOLT240kV・FORK_N3本の傍枝を持つ放電の後ろに光輪を敷きます。

06ポインター追跡落雷 — カーソルへ向け描き直す経路

インタラクティブヒーローのマウストレイルやバッジのhoverに使う、固定の始点からポインター座標までの区間を毎フレーム新しく割り直す盤です — 実測では累積面積31.824%・強度平均71.6で、24フレーム中23フレームに動きがありました。

ポインター追跡落雷 — カーソルへ向け描き直す経路
新しいタブで開く

06 · ポインター追跡落雷 — カーソルへ向け描き直す経路 — 計測 累積面積 31.824% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

pointermovelineTorequestAnimationFrame

目標を追うlerpと毎フレーム引き直す幹

// 目標更新 — ポインターがなければリサージュの8の字による自動デモ経路
if (!hasPointer) {
  var a = tSec * AUTOSPD;
  ptrX = W * 0.5 + Math.sin(a * 1.3 - 1.1) * W * 0.40;
  ptrY = H * 0.55 + Math.sin(a * 2.6) * H * 0.36;
}
// 表示位置 lerp — 目標を滑らかに追う
curX += (ptrX - curX) * 0.22;
curY += (ptrY - curY) * 0.22;

// 幹 — 始点から表示位置まで新しい経路
var main = genBolt(origin.x, origin.y, curX, curY, MAG_PX, 6);

ポインター座標をそのまま使うと稲妻がカーソルに張り付いて震えるだけになるところを、毎フレーム目標との差へ0.22を掛けて近づくlerpが滑らかな追跡へ変えて稲妻がカーソルを追って伸びたり縮んだりする印象を作り、pointermoveを一度も受け取っていない環境ではAUTOSPD0.9倍速の8の字リサージュが演示の目標になって画面の隅まで使い、変位幅はMAG_PX26pxに抑えて幹を細やかに保ちます。

07チェインライトニング — 着弾点から次の着弾点へ跳ぶ連鎖

ゲームUIのスキルプレビューや複数のカードをつなぐ強調に使う、5つの着弾点を近い順につなぎ区間ごとに点灯の遅れを増やして跳んでいく盤です — 実測では累積面積52.858%・強度平均28.5で、24フレーム中23フレームに動きがありました。

チェインライトニング — 着弾点から次の着弾点へ跳ぶ連鎖
新しいタブで開く

07 · チェインライトニング — 着弾点から次の着弾点へ跳ぶ連鎖 — 計測 累積面積 52.858% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

lineToglobalCompositeOperationrequestAnimationFrame

貪欲な最近接の並べ替えと区間ごとの点灯遅延

// 区間 e の明るさ包絡線(envelope) — 点灯遅延 → 保持 → フェード → 休止
var N_EDGES = 4;
var FLASH_IN = 40, HOLD_AFTER = 60, FADE_MS = 330;
function edgeAlpha(e, t) {
  var start = e * CHAIN_GAP_MS;
  var fadeStart = (N_EDGES - 1) * CHAIN_GAP_MS + HOLD_AFTER;
  if (t < start) return 0;
  if (t < start + FLASH_IN) return (t - start) / FLASH_IN;
  if (t < fadeStart) return 1;
  if (t < fadeStart + FADE_MS) return 1 - (t - fadeStart) / FADE_MS;
  return 0;
}

0番から始めて毎回いちばん近い未訪問の着弾点をつなぐ貪欲な並べ替えで順序を固定し、区間の番号eにCHAIN_GAP_MS(55ms)を掛けて開始時刻を後ろへずらすと40msの点灯・保持・330msのフェードの包絡線が順に流れて連鎖として読め、画面は毎フレーム完全に消さずrgba(C_INK, 0.065)の薄い重ね塗りで残すので光の尾がひとつの周期の中で切れません。

08ストライクフラッシュ — 着弾直後、画面全体が指数的に冷める

フルスクリーンのイントロや通知バナーの強い注意喚起に使う、落雷のフレームで空全体を明るくしてから指数的に冷やす盤です — 実測では累積面積52.849%・強度平均30.9で、24フレーム中23フレームに動きがありました。

ストライクフラッシュ — 着弾直後、画面全体が指数的に冷める
新しいタブで開く

08 · ストライクフラッシュ — 着弾直後、画面全体が指数的に冷める — 計測 累積面積 52.849% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

fillRectglobalCompositeOperationrequestAnimationFrame

前の値に減衰率を掛ける指数冷却

// フラッシュ減衰 — 前の値に減衰率を掛ける指数減衰がこのデモの主題
flash *= Math.pow(DECAY, dt * 60);
// 落雷の瞬間(周期の開始0〜120ms)はフラッシュが最大値を保つ
if (t < 120) flash = 1;

// 空 — フラッシュが灯るほど深いスレートがライラックの光へ明るくなる
var skyCol = mix(C_SKY, C_LILAC, flash * 0.5);
ctx.fillStyle = rgba(skyCol, 1);
ctx.fillRect(0, 0, W, H);

flashへ毎フレームMath.pow(0.95, dt * 60)を掛けるだけでカメラフラッシュのような指数冷却が得られ、周期CYCLE_MS1000msの最初の120msは値を1へ押し上げて幹が確実に見える時間を確保し、幹は周期の始まりごとにシードを戻して同じ形が繰り返し落ち、裏山・丘の稜線・小さな塔のシルエットが同じフラッシュで一緒に浮かび上がります。

09雲の中の発光 — 幹なしで霧の中に瞬く雲間放電

暗雲の背景の演出や天気ウィジェットの嵐の状態に使う、幹を描く代わりに雲の中のランダムな地点へ光源を短く何度も灯す盤です — 実測では累積面積45.795%・強度平均62.6で、24フレーム中23フレームに動きがありました。

雲の中の発光 — 幹なしで霧の中に瞬く雲間放電
新しいタブで開く

09 · 雲の中の発光 — 幹なしで霧の中に瞬く雲間放電 — 計測 累積面積 45.795% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

createRadialGradientglobalCompositeOperationrequestAnimationFrame

幹の代わりに光源を予定表へ並べる

prngState = SEED >>> 0;
pulses = [];
for (var i = 0; i < PULSE_COUNT; i++) {
  pulses.push({
    t0: i * (CYCLE_MS / PULSE_COUNT) + rand() * 18,   // 順次発生 — 連発の感覚
    dur: 110 + rand() * 120,                           // 短く灯ってから冷えるパルス長
    x: (0.10 + rand() * 0.80) * W,
    y: (0.16 + rand() * 0.62) * CLOUD_H,
    col: GLOWS[i % GLOWS.length]
  });
}

発光源PULSE_COUNT9つの開始時刻をCYCLE_MS(500ms)を9で割った間隔へ乱数18msを足して順に並べると空が一斉に点くのではなく撫でていくように瞬き、各パルスは速く満ちて指数で冷える包絡線で半径CLOUD_H * GLOW_RADIUS(0.95)の放射グラデーションを描き、lighter合成で雲の静的な層の上に光が積もります。

落雷エフェクトを実際に使う画面

ゲームUIの被弾演出やボタンのクリック反応には、06のポインター追跡落雷が合います。入力座標へ向けて経路が描き直されるからです。着弾直後に画面全体が冷める08のストライクフラッシュは、そのまま画面転換のトランジション効果としても使えます。

背景演出には、複数の落雷が重なる03の嵐の空や、静かに瞬く09の雲の中の発光が合いますが、どちらも画面全体を覆うヒーロー領域だけで使います。

再帰の深さを上げすぎると雷がここで壊れる落とし穴

深さを上げると線分数が指数で増える

再帰の深さを6から8へ上げるのは2倍ではなく4倍です。分割のたびに区間数が2倍になるため6段階64本が8段階256本になり、02のように区間ごとに枝の候補を5か所調べる盤では候補検査の回数がそのまま掛けられてフレームが目に見えて重くなります。配布されている稲妻 エフェクト フリー素材の画像を貼るやり方ならこの費用は払わずに済みますが、毎フレーム経路を引き直す07のような反応は作れません。ソースの注釈に5は太い屈曲・7は細かいジグザグと書いてあるとおり深さは質感の問題でもあるので、値を上げる前に太い折れが先に消えるかどうかから確かめるのが順序です。

法線オフセットがないとジグザグが一方向へ寄る

中点をx軸だけで揺らすと、縦に落ちる幹は左右にだけ分かれ、斜めの区間では折れが潰れます。ずれを線分に垂直な法線(-dy, dx) / lenの向きへ押す理由がここにあって、向きに無関係に常に線分に逆らう力になることで、どの角度の区間でも同じ荒さが出ます。この法線の計算を抜かした盤は上から見るとそれらしく見えても、経路が横に寝た瞬間にフラクタルが壊れたことがすぐ露見します。

毎フレーム全体を消すと残光が生きない

05・06のように経路を毎フレーム計算し直す盤で背景を毎回不透明に塗り直すと、直前のフレームの光が全部消えてアークが明滅する電球のようになります。04と05は明るさを別のバッファへ保存して拡散で冷やし、07はrgba(C_INK, 0.065)の薄い重ね塗りで前の絵をゆっくり消すトレールフェードでこの問題を解きます。稲妻の残像は幹を描き直すことではなく消す速さを遅くすることで得られるので、値を直接揺らしてみたくなったらソース一式が入ったzipを開くことになり、解凍のパスワードはgy22a2jjです。

枝の数ひとつで変わる3つのバリエーションとアクセシビリティ対応

定数ひとつで変わる3つのバリエーション

名前 変える値 手触り
分岐確率 02 再帰分岐 0.15 → 0.4 枝が増えるほど稲妻が木のように密になる
変位の強さ 01 基本落雷 変位幅 20px → 60px 幹がより荒く鋭く折れる
発生頻度 03 嵐の空 平均待ち 900ms → 2400ms 連発していた空が間遠になり緊張が伸びる

どれもソースの先頭にある定数の一行だけを変える軸なので、残りの計算はそのまま生きています。

動きを減らす設定で残る静止フレーム

動きを減らす設定(prefers-reduced-motion: reduce)が検知されたとき、9盤すべてがアニメーションのループを始めず、その盤が最も読める一場面だけを計算して描きます。05と06は1.6秒の時刻のアークと追跡の幹を、07は90msの時点で3区間が全部点いた瞬間を、08は60msの時点でフラッシュがわずかに残った落雷を、09は120msの時点で発光が2・3個点いた雲を示します。01・02・03は落雷が1本入った一枚をstillFrame()で撮り、04は拡散を6ステップ先まで回してから落雷を1回歩かせ、ハローが定位置に収んだ一枚を出します。計算はアニメーションと同じものを一度だけ回すので、静止画の品質は下がりません。

9つの全盤が焦点を受け取る実験盤

9盤ともrole="application"とtabindex="0"を持つ実験盤なので、タブで焦点を移して絵の領域へ入れます。ポインターの入力を受け取るのは06だけで、カーソルを動かすと稲妻がその場所を追って掴む反応が盤の主題なので、動きを減らす設定の利用者でも手が届いた瞬間にその時刻の一枚を計算し直して描きます。残りの8盤は自動で流れる場面なので、ポインターがなくても完結しています。

落雷エフェクトを組み込む前によくある質問

CSSの稲妻とキャンバスの中点変位は何が違いますか?

CSSアニメーションは決めたキーフレームの間をブラウザが補間する方式なので、稲妻の経路そのものを作れず、稲妻の形をした画像やclip-pathを揺らすだけで終わります。キャンバスの中点変位は毎フレーム線分を割り直して経路を計算するので同じ幹が二度と現れず、06のようにポインターへ向けて経路を引き直す相互作用も同じ規則の中で作れます。

この稲妻 エフェクト 動画を録画して素材として使い回せますか?

使えます。この記事の実測値も24フレームのgifへレンダリングした画面をフレームごとに比べて得たもので、画面の録画でgifやmp4を作るか、zipの素の盤を設置してキャンバスをフレームごとに画像へ保存してつなげば動画になります。ただし03のように指数分布の待ち時間が入った盤は、連発と静けさが両方収まるように録画の幅を余裕を持って取ってください。

03の嵐の空だけなぜ20/24フレームなのですか?

遠い稲妻は幹を描かず空の明るさの反応だけを残しますが、その反応が計測の閾値を下回るフレームが4つあったためです。幹が描かれる近い稲妻のフレームは全部動いており、空全体の明るさをわずかに変える遠い稲妻の閃光はピクセルの変化量が小さくて計測の道具に届かなかったもので、画面が死んでいるという意味ではありません。

もっと読む

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

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