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

css グリッチ エフェクト 9種の切り替え演出

css グリッチ エフェクトは、こう動きます。画面が急に途切れて次の内容に切り替わると、故障のように見える — オフセット値でRGBチャンネルをずらして戻す転換、ノイズ強度の値で一瞬だけホワイトノイズを上げて下げる転換、解読進行率の値でランダムな文字を実際のテキストに変える転換のように、値ひとつが制御されたグリッチを作

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

9つという数はきりのいい数字を選んだわけではありません。1枚の画像の読み込み(01)から、画面全体を動かすスライス(02・03)、明るさ順で並べ替えるリビール(04)、短いコンテンツの点滅(05)、色だけがずれる2つの演出(06・07)、画面を裂く演出(08)、最後に文字だけの切り替え(09)へと順に進み、素のJavaScriptの数値ひとつがキャンバスの1フレームをどう動かせるかを一つずつ見せていきます。

デモ

上の3×3グリッドは、それぞれが独立したrequestAnimationFrameループで動く9つのキャンバス要素です。これはどこかのペンを指す埋め込みではありません — どのタイルも、このページ自身のキャンバスであり、下の各パターンに載っているコードがそのまま動いています。タイルをクリックするか、Tabで選んでEnterかSpaceを押すか、ウィンドウをリサイズすると、そのタイルのRENDER(p)関数がそのフレームのピクセルバッファを処理します。CSSのtransitionendイベントを待つ処理はどこにもありません。そもそもCSSトランジションがこの裏で動いていないからです。

なぜCSSのトランジションではなく、キャンバスで全ピクセルを計算するのか — css グリッチエフェクトの仕組み

CSSのトランジションは、色・位置・不透明度といったプロパティ自体の値をある数値から別の数値へ補間するだけです。1つのピクセルのバイト値を読み取り、代わりにどのピクセルをコピーするか選んだり、1行のピクセルを明るさ順に並べ替えたりすることはできません。getImageDataがそのフレームの生のバイト配列をキャンバスから受け取り、putImageDataが新しい配列を書き戻すので、以下の9つのパターンはどれも、毎フレーム再計算される単純な数値pひとつから、そのピクセル単位の判断を導けます。9つのどれもMath.randomは使いません。決定的な乱数生成器(makeLcg)が、ブロックのずれ・行ごとのジッター・ノイズの種といった固定レイアウトを一度だけ作るので、同じパターンは毎回同じ形で再生され、実行のたびに崩れることはありません。この記事の裏にある9つのファイルのどこにも、ライブラリもWebGLコンテキストもCDNスクリプトもありません。

グリッチトランジション9種の内訳 — 計算が動く場所

以下の測定値は推定ではなく、9つのデモファイルから直接取ったものです。累積面積は転換全体でキャンバスがどれだけ変化したか、平均強度は変化した箇所の1ピクセルあたりの平均バイト差、動いたフレームは23回のフレーム間差分のうち実際にピクセルが動いた回数を示します。

# パターン 累積面積 平均強度 動いたフレーム
01 チャンネルのずれ値で分かれてから重なる画像の切り替え 48.7% 45.8 23 / 23
02 横方向のずれ値が元に戻る画面の切り替え 28.9% 105.9 23 / 23
03 ブロック座標の値が散らばってから揃う画面の切り替え 32.6% 134.4 23 / 23
04 明るさ整列の進行率値が元に戻す画像の切り替え 28.2% 149.6 23 / 23
05 ノイズ強度の値が収まるコンテンツの切り替え 48.8% 29.7 22 / 23
06 色収差の振幅値が減衰する画面の切り替え 38.4% 74.9 23 / 23
07 トラッキング揺れ値が安定する画面の切り替え 44.6% 212.2 23 / 23
08 裂け目のオフセット値がかみ合う画面の切り替え 37.6% 195.6 21 / 23
09 解読進行率の値が実際の文字に変えるテキストの切り替え 19.3% 50.6 22 / 23

パターン別コード

以下の各パターンは、ソースフレームを読み取り、出力するバイトを計算し、それを書き戻すという同じ3段階を踏んでおり、2段階目の計算方法だけが異なります。掲載している範囲はデモのソースからそのまま切り出したもので、書き換えていません。

01チャンネルのずれ値で分かれてから重なる画像の切り替え

画像が現れた瞬間にチャンネルのずれ値が赤と青を左右に押し広げ、緑はその場に留まります。ROW_JITTERが行ごとにそのずれ量を揺らすので、単なる平行なずれには見えません。さらにBRIGHT_PULSEとGHOST_MIXが、チャンネルが分かれていく間に短い明るさのパルスと重なり合う残像を加えます。タイルをクリックするか、Tabで選んでEnterを押すと、読み込みの瞬間をもう一度再生できます。

チャンネルのずれ値で分かれてから重なる画像の切り替え
新しいタブで開く

01 · チャンネルのずれ値で分かれてから重なる画像の切り替え — 計測 累積面積 48.733% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

RGB_OFFSET_MAXgetImageDataputImageData
    var pulse = 1 + BRIGHT_PULSE * Math.sin(Math.PI * p);
    for (var y = 0; y < ih; y++) {
      var rowOffset = Math.round(offset * rowJitter[y]);
      var gy = clampI(y - voffset, 0, ih - 1);
      for (var x = 0; x < iw; x++) {
        var rx = clampI(x - rowOffset, 0, iw - 1);
        var bx = clampI(x + rowOffset, 0, iw - 1);
        var rx2 = clampI(x - Math.round(rowOffset * 1.6), 0, iw - 1);
        var bx2 = clampI(x + Math.round(rowOffset * 1.6), 0, iw - 1);
        var k = (y * iw + x) * 4;
        var kr = (y * iw + rx) * 4;
        var kr2 = (y * iw + rx2) * 4;
        var kg = (gy * iw + x) * 4;
        var kb = (y * iw + bx) * 4;
        var kb2 = (y * iw + bx2) * 4;
        o[k] = clampI(Math.round((data[kr] * (1 - GHOST_MIX) + data[kr2] * GHOST_MIX) * pulse), 0, 255);
        o[k + 1] = clampI(Math.round(data[kg + 1] * pulse), 0, 255);
        o[k + 2] = clampI(Math.round((data[kb + 2] * (1 - GHOST_MIX) + data[kb2 + 2] * GHOST_MIX) * pulse), 0, 255);
        o[k + 3] = 255;
      }
    }

02横方向のずれ値が元に戻る画面の切り替え

PRECOMPUTE()はシード付き乱数を使って各行のrowOffsetsを一度だけ計算し、RENDER(p)はその行ごとのずれ値に現在の進行度を掛けてから、ずれた元の行からピクセルをコピーします。ずれ値は一度だけ計算してpで倍率をかけるだけなので、同じギザギザの形が再生のたびに同じ場所へ戻ります。

横方向のずれ値が元に戻る画面の切り替え
新しいタブで開く

02 · 横方向のずれ値が元に戻る画面の切り替え — 計測 累積面積 28.934% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

rowOffsetsstripeWputImageData
  function PRECOMPUTE() {
    iw = cv.width; ih = cv.height;
    data = bctx.getImageData(0, 0, iw, ih).data;
    outImg = ctx.createImageData(iw, ih);
    var rng = makeLcg(SEED + 7);
    rowOffsets = new Float32Array(ih);
    for (var y = 0; y < ih; y++) rowOffsets[y] = (rng() * 2 - 1) * ROW_OFFSET_MAX * dpr;
  }

  function clampI(v, lo, hi) { return v < lo ? lo : (v > hi ? hi : v); }

  function RENDER(p) {
    var o = outImg.data;
    for (var y = 0; y < ih; y++) {
      var shift = Math.round(rowOffsets[y] * p);
      var rowBase = y * iw;
      for (var x = 0; x < iw; x++) {
        var sx = clampI(x - shift, 0, iw - 1);
        var k = (rowBase + x) * 4;
        var sk = (rowBase + sx) * 4;
        o[k] = data[sk]; o[k + 1] = data[sk + 1]; o[k + 2] = data[sk + 2]; o[k + 3] = 255;
      }
    }
    ctx.putImageData(outImg, 0, 0);
  }

03ブロック座標の値が散らばってから揃う画面の切り替え

キャンバスはBLOCK_ROWS×BLOCK_COLSの正方形に分割され、各ブロックは自分専用のblockDx・blockDy散らばり量を一度だけ持ちます。RENDER(p)はpを0へ向かって戻していくので、各ブロックのピクセルは縮んでいくずれ量から元の座標に着地します。ウィンドウをリサイズすると、同じブロック分割が新しいサイズで組み直されます。

ブロック座標の値が散らばってから揃う画面の切り替え
新しいタブで開く

03 · ブロック座標の値が散らばってから揃う画面の切り替え — 計測 累積面積 32.552% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

blockDxblockDyputImageData
  function RENDER(p) {
    var o = outImg.data;
    for (var y = 0; y < ih; y++) {
      var by = Math.min(BLOCK_ROWS - 1, Math.floor(y / blockH));
      for (var x = 0; x < iw; x++) {
        var bx = Math.min(BLOCK_COLS - 1, Math.floor(x / blockW));
        var bi = by * BLOCK_COLS + bx;
        var sx = clampI(Math.round(x - blockDx[bi] * p), 0, iw - 1);
        var sy = clampI(Math.round(y - blockDy[bi] * p), 0, ih - 1);
        var k = (y * iw + x) * 4;
        var sk = (sy * iw + sx) * 4;
        o[k] = data[sk]; o[k + 1] = data[sk + 1]; o[k + 2] = data[sk + 2]; o[k + 3] = 255;
      }
    }
    ctx.putImageData(outImg, 0, 0);
  }

04明るさ整列の進行率値が元に戻す画像の切り替え

PRECOMPUTE()は各行のすべてのピクセルの明るさを読み取り、その値でその行のx座標をsortedIdxへ並べ替えるので、一番暗いピクセルと一番明るいピクセルが行の両端に集まります。進行率値が下がるにつれて、その並べ替えた順序と元の順序を混ぜていくのが、画像が焦点を結ぶ前に縞模様が伸びて見える理由です。

明るさ整列の進行率値が元に戻す画像の切り替え
新しいタブで開く

04 · 明るさ整列の進行率値が元に戻す画像の切り替え — 計測 累積面積 28.2% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

sortedIdxbrightnessclampI
  function PRECOMPUTE() {
    iw = cv.width; ih = cv.height;
    data = bctx.getImageData(0, 0, iw, ih).data;
    outImg = ctx.createImageData(iw, ih);
    sortedIdx = [];
    for (var y = 0; y < ih; y++) {
      var row = new Int32Array(iw);
      var order = [];
      for (var x = 0; x < iw; x++) order.push(x);
      var base = y * iw;
      order.sort(function (a, b) { return brightness((base + a) * 4) - brightness((base + b) * 4); });
      for (var j = 0; j < iw; j++) row[j] = order[j];
      sortedIdx.push(row);
    }
  }

05ノイズ強度の値が収まるコンテンツの切り替え

すべてのピクセルは、hash01が作る自分専用のノイズ色を持ちます。これはMath.randomではなく、ピクセルの座標と現在のフレーム数から決まる決定的なハッシュです。そのノイズ色は、1へ跳ね上がってから収まっていく値pの分だけ本物のピクセルと混ぜられます。NOISE_BURST_DURATIONを長くすると、pが1付近に留まるフレーム数が増えるだけで、下のコンテンツがはっきり見えるまでの時間が伸びます。

ノイズ強度の値が収まるコンテンツの切り替え
新しいタブで開く

05 · ノイズ強度の値が収まるコンテンツの切り替え — 計測 累積面積 48.761% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

NOISE_BURST_DURATIONframeCounterhash01
    for (var y = 0; y < ih; y++) {
      var ySalt = Math.imul(y, 668265263) >>> 0;
      var base = y * iw;
      for (var x = 0; x < iw; x++) {
        var seed = (Math.imul(x, 374761393) ^ ySalt ^ fSalt) >>> 0;
        var h0 = hash01(seed);
        var h1 = hash01(seed ^ 0x9e3779b9);
        var h2 = hash01(seed ^ 0x85ebca6b);
        var h3 = hash01(seed ^ 0xc2b2ae35);
        var g = 198 + h0 * 57;
        var nr = clamp255(g + (h1 - 0.5) * 60);
        var ng = clamp255(g + (h2 - 0.5) * 60);
        var nb = clamp255(g + (h3 - 0.5) * 60);
        var k = (base + x) * 4;
        o[k] = data[k] * (1 - p) + nr * p;
        o[k + 1] = data[k + 1] * (1 - p) + ng * p;
        o[k + 2] = data[k + 2] * (1 - p) + nb * p;
        o[k + 3] = 255;
      }
    }

06色収差の振幅値が減衰する画面の切り替え

dxR・dxB・dyGはCHROMATIC_DRIFT_AMPLITUDEから計算されるチャンネルごとのピクセルずれ量を保持し、RENDER(p)は赤・緑・青をそれぞれ別の座標から読み取ってから同じ出力ピクセルに書き込みます。これがそもそもチャンネルを分けている理由です。easeInOutの曲線がその振幅を瞬時にではなく徐々に0へ減衰させるので、合流が急停止ではなく落ち着いていく動きに見えます。

色収差の振幅値が減衰する画面の切り替え
新しいタブで開く

06 · 色収差の振幅値が減衰する画面の切り替え — 計測 累積面積 38.372% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

CHROMATIC_DRIFT_AMPLITUDEdxReaseInOut
    for (var y = 0; y < ih; y++) {
      var base = y * iw;
      var gy = clampI(y + dyG, 0, ih - 1);
      var gBase = gy * iw;
      for (var x = 0; x < iw; x++) {
        var rx = clampI(x - dxR, 0, iw - 1);
        var gx = clampI(x, 0, iw - 1);
        var bx = clampI(x - dxB, 0, iw - 1);
        var k = (base + x) * 4;
        var kr = (base + rx) * 4;
        var kg = (gBase + gx) * 4;
        var kb = (base + bx) * 4;
        o[k] = data[kr];
        o[k + 1] = data[kg + 1];
        o[k + 2] = data[kb + 2];
        o[k + 3] = 255;
      }
    }

07トラッキング揺れ値が安定する画面の切り替え

揺れ値がサイン波でrollYを上下に動かすので、RENDER(p)は各行を縦方向にラップした位置から読み取ります(wrap()がその行番号をキャンバスの外へ出さずに保ちます)。さらに、05と同じhash01の仕組みから作った小さめの行ごとのjitterXを重ねます。TAPE_WOBBLE_AMPLITUDEは、値が収まって画面が安定するまでの間、そのサイン波が1行をどこまで押せるかを決めます。

トラッキング揺れ値が安定する画面の切り替え
新しいタブで開く

07 · トラッキング揺れ値が安定する画面の切り替え — 計測 累積面積 44.612% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

TAPE_WOBBLE_AMPLITUDErollYwrap
  function RENDER(p) {
    phaseRoll += 0.33;
    frameCounter = (frameCounter + 1) >>> 0;
    var rollY = Math.round(TAPE_WOBBLE_AMPLITUDE * dpr * p * Math.sin(phaseRoll));
    var fSalt = Math.imul(frameCounter, 2246822519) >>> 0;
    var o = outImg.data;
    for (var y = 0; y < ih; y++) {
      var srcY = wrap(y - rollY, ih);
      var jh = hash01((Math.imul(srcY, 668265263) ^ fSalt) >>> 0);
      var jitterX = Math.round(TAPE_JITTER_AMPLITUDE * dpr * p * (jh * 2 - 1));
      var base = y * iw;
      var srcBase = srcY * iw;
      for (var x = 0; x < iw; x++) {
        var sx = clampI(x - jitterX, 0, iw - 1);
        var k = (base + x) * 4;
        var sk = (srcBase + sx) * 4;
        o[k] = data[sk]; o[k + 1] = data[sk + 1]; o[k + 2] = data[sk + 2]; o[k + 3] = 255;
      }
    }
    ctx.putImageData(outImg, 0, 0);
  }

08裂け目のオフセット値がかみ合う画面の切り替え

tearYがキャンバスを上半分と下半分に一度だけ分け、1つの裂け目オフセット値がその2つを逆方向にずらします。RENDER(p)はそのオフセットにpを掛けるので、pが0へ近づくにつれて2つの断片がすべての行で揃うまで戻り、画面が1枚につながって見えます。エラー画面やフルスクリーンメニューにも、同じ分割を急な切り替えとして使えます。

裂け目のオフセット値がかみ合う画面の切り替え
新しいタブで開く

08 · 裂け目のオフセット値がかみ合う画面の切り替え — 計測 累積面積 37.565% · 動いたフレーム 21/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

TEAR_OFFSET_MAXtearYclampI
  function RENDER(p) {
    var off = Math.round(TEAR_OFFSET_MAX * dpr * p);
    var o = outImg.data;
    for (var y = 0; y < ih; y++) {
      var dx = y < tearY ? off : -off;
      var base = y * iw;
      for (var x = 0; x < iw; x++) {
        var sx = clampI(x - dx, 0, iw - 1);
        var k = (base + x) * 4;
        var sk = (base + sx) * 4;
        o[k] = data[sk]; o[k + 1] = data[sk + 1]; o[k + 2] = data[sk + 2]; o[k + 3] = 255;
      }
    }
    ctx.putImageData(outImg, 0, 0);
  }

09解読進行率の値が実際の文字に変えるテキストの切り替え

getImageDataを使わず、RENDER(p)はctx.fillTextで直接文字を描きます。文字ごとのインデックスに対して解読進行率値をDECODE_SPEEDと比較し、その文字の番が来るまではPOOLからランダムな文字を選んでハッシュ由来の小さなジッターを加えGHOST_COLORで描き、解決した瞬間にTEXT_COLORの本物の文字へ切り替わります。DECODE_SPEEDを下げると、1文字が確定するまで乱れた状態でいるフレーム数が増え、左からの確定が目に見えて遅くなります。

解読進行率の値が実際の文字に変えるテキストの切り替え
新しいタブで開く

09 · 解読進行率の値が実際の文字に変えるテキストの切り替え — 計測 累積面積 19.26% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

DECODE_SPEEDcharXctx.fillText
    for (var i = 0; i < n; i++) {
      var resolved = decodeFrac * DECODE_SPEED * n >= (i + 1);
      var ch = chars[i];
      var drawCh = ch;
      var jy = 0;
      if (!resolved && ch !== ' ') {
        var seed = (Math.imul(i + 1, 2654435761) ^ Math.imul(frameCounter, 2246822519) ^ SEED) >>> 0;
        var hIdx = Math.floor(hash01(seed) * POOL.length);
        drawCh = POOL[hIdx];
        jy = Math.round((hash01(seed ^ 0x9e3779b9) - 0.5) * 5 * p);
        ctx.fillStyle = GHOST_COLOR;
        boundaryX = charX[i];
      } else {
        ctx.fillStyle = TEXT_COLOR;
      }
      ctx.fillText(drawCh, charX[i], baselineY + jy);
    }

RGB_OFFSET_MAX・NOISE_BURST_DURATION・DECODE_SPEEDを変えると何が変わるか

以下の3つの定数は、ファイルの他の部分に一切触れずに数値ひとつでどこまで印象を変えられるかを確かめるために、それぞれ一度だけ動かしたものです。

変形 変えた値 感じ方
RGBチャンネルのずれ最大値を調整する 01 RGB_OFFSET_MAX 24px → 60px 最大値を24pxから60pxに上げると、読み込み時にR・Bチャンネルがより大きく広がってから0に戻る幅が増え、ずれがより荒く感じられる。
ノイズバーストの持続時間値を調整する 05 NOISE_BURST_DURATION 560ms → 900ms 持続時間を560msから900msに伸ばすと、ノイズが収まるまでの時間が長くなり、ホワイトアウトがより長く残る印象になる。
文字の解読速度値を調整する 09 DECODE_SPEED 1.7 → 1.0 解読速度を1.7から1.0に下げると、各文字が実際の文字に確定するまでのフレーム数が増え、左からの確定が目に見えて遅くなる。

商品画像・通知バナー・ログインメッセージは実際どこで使うのか

パターン01のチャンネル分割は、商品詳細画像やポートフォリオのサムネイルのように、実際の画像が読み込まれる瞬間のために作られています。切り替え先の別画像が要らず、読み込んだビットマップだけで完結するからです。クリックの代わりにホバーをトリガーにすれば、同じRENDER(p)をポインターが乗った瞬間に走らせるcss グリッチ アニメーションとしても書き出せます。

パターン05のノイズバーストは、タブの切り替えや通知バナーの出現のような、短くて負荷の軽いコンテンツ変化に向いています。1秒もの静止ノイズはトランジションではなくエラーに見えてしまうので、NOISE_BURST_DURATIONの既定値も1秒未満に抑えられています。

パターン09は文字のために作られた演出です。ヒーローの見出しの登場や、スピナーを置き換えるログイン成功メッセージなどに使えます。DECODE_SPEEDもPOOLも特定の文字列に依存していないので、画面が1文字ずつ見せたい短い一行であれば、同じ解読処理をそのまま使い回せます。

グリッチトランジションが壊れるのはどこか — モーション・コントラスト・キーボード操作

9つはすべて、ページが読み込まれた瞬間に一度だけprefers-reduced-motionを読み取り、毎フレーム読み取ることはありません。そのメディアクエリがreduceを返した場合は、パターンに応じてRENDER(0.5)かRENDER(1)の静止フレームを1枚描くだけでループを止めます。設定を無視してタイマーで動き続けるパターンはひとつもありません。実際に壊れやすいのは、07の揺れと03のブロック散らばりです。どちらもキャンバスの広い範囲を動かすため、非力な端末ではフレーム処理が遅れて揺れが単なるカクつきに見えることがあります。画面が横に広い場合は、BLOCK_ROWSとBLOCK_COLSを控えめにしておくとよいでしょう。クリックできるタイルにはすべてrole="button"・tabindex="0"・aria-labelが付いており、クリック・Enterキー・Spaceキーのどれにも同じハンドラーが応答するため、マウスなしでも9つすべてを再生できます。パターンごとの9つのソースファイルは、このデモと同じフォルダの中で8mgtprm2の向こうに置かれています。値を大きくする前に一度開いて中身を見ておくと、既定値では問題なかった裂けや散らばりが、大きな値ではキャンバスの外へはみ出すことがあると分かります。

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

上の表にある3つの変形はどれも数値定数をひとつ変えているだけなので、画面に元からなかった動きを新しく足すことはありません。RGB_OFFSET_MAXを24pxから60pxに上げても、DECODE_SPEEDを1.7から1.0に下げても、変わるのはパターンがどれだけ・どれくらいの時間動くかであって、reduced-motion下で動くかどうかではありません。reduced-motion側の分岐は、どの定数であっても同じRENDER(p)関数を固定されたpで一度だけ呼んでいるからです。アクセシビリティはオンオフする別の変形ではなく、どの変形もすでに呼んでいる同じ関数を、ループではなく一度だけ評価しているだけのことです。

同じようなグリッチトランジションの実例はどこで見つかる?

ジェネレーターはすぐに動くキーフレームアニメーションを渡してくれるので、たたき台としては便利です。ただ、このページのどのパターンも、CSSプロパティを補間するのではなく毎アニメーションフレームでJavaScriptの値を読み取っており、これは04の明るさ整列や09の1文字ずつの解読が成立する唯一の方法です。CSSキーフレームを土台にしたジェネレーターには、並べ替えるピクセルバッファも、解読する文字の配列もありません。ジェネレーターがよく省いてしまうのは、ここまで説明したreduced-motionの分岐とキーボード操作への対応で、どちらも読み込み時に値を一度だけ読むことに依存しており、トランジションを出力するだけでは足りません。

FAQ

css グリッチ 画像だけでなく、見出しの文字にも使えますか?

はい。09のパターンはまさにそのために作られています。charXとPOOLをデモの文字列ではなく見出しの文字列に差し替えれば、RENDER(p)自体には触れずに同じ解読進行率値が新しい文字を乱してから解決します。

05のノイズと06の色ずれは、何が違うのですか?

05はフェードしていく値でランダムなノイズ色をすべてのピクセルに混ぜますが、06は赤・緑・青をそれぞれ別の座標から読み取るだけです。05は元の画像にない色を足し、06は元々ある色を並べ替えているだけという違いです。

9つのパターンにライブラリやCDNのスクリプトは必要ですか?

いいえ。9つのパターンはすべて、同じファイル内に書かれたgetImageData・putImageData・シード付き擬似乱数生成器だけで動いており、WebGLコンテキストも外部のscriptタグも9つのデモのどこにもありません。

Sources: MDN — getImageData · MDN — putImageData · MDN — requestAnimationFrame · MDN — prefers-reduced-motion · MDN — mix-blend-mode

関連記事: 画像 切り替え エフェクト 9種 — キャンバスが計算するリビール · CSS 画面切り替え アニメーション9選 · パーリンノイズ地形9選 — ライブラリなし、計算だけで — 1つ目はこの記事と同じgetImageData・putImageDataの骨組みを使いながら、画面を切り替えるのではなく1枚の静止画像を現します。2つ目はキャンバスのピクセル処理を使わずCSSだけで組んだ画面切り替えを扱い、3つ目はパターン05のノイズバーストよりずっと長い時間をかけて動く、同じ値ノイズの計算を深く掘り下げています。

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

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