LoginSignup
3
1

Canvasでグリッチアニメーション

Last updated at Posted at 2024-06-27

viviONグループでは、DLsiteやcomipoなど、二次元コンテンツを世の中に届けるためのサービスを運営しています。
ともに働く仲間を募集していますので、興味のある方はこちらまで。

:loudspeaker: はじめに

Canvasでもグリッチアニメーションがしたい!ということで、この記事ではCanvas上でのグリッチアニメーションの実装を解説します。

グリッチアニメーションもといグリッチエフェクトとは、意図的にノイズや歪みを加え、エラーやバグのような視覚効果を再現したものです。元々はCG分野で使用されていましたが、現在ではWebデザインにも利用されています。

Web上でグリッチエフェクトを表示する方法にはCSSやSVGを使用した例がたくさんありますが、Canvasを使うことでさらに細かい制御や複雑なアニメーションを実装できます。Canvasはピクセル単位で操作が可能で、自由度が高く、シンプルなAPIにより実装に集中できます。これによりグラフィック操作や描画アルゴリズムについてよく学ぶことができます。

実際にCanvas上で実装すると、以下のようになります。

See the Pen Untitled by tenori (@tenorichan) on CodePen.

:deciduous_tree: 環境準備

今回はviteで作成した適当な環境にて実装します。

main.jsの内容をほぼそのままcodepenで実装してますので、環境構築は不要です。

npm create vite glitch-canvas -- --template vanilla

:runner: チャンネルシフトの実装

任意のRGB値を横方向や縦方向にずらすことで、色収差やフリンジのような効果を与えます。

以下のコードは、指定された範囲のピクセルデータを取得する例です。

const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const imageData = ctx.getImageData(sx, sy, sw, sh);

getImageDataメソッドは、ImageDataオブジェクトを返します。ImageDatadataプロパティは、RGBAの順で0から255の間の整数を含む1次元配列で、各ピクセルの色データを保持しています。
この配列を操作することで、チャンネルシフト効果を実現します。以下のコードはキャンバス上のR(赤)チャンネル要素を右方向にシフトさせる例です。

See the Pen Untitled by tenori (@tenorichan) on CodePen.

:underage: Rチャンネルのみシフトする

for (let y = 0; y < height; y++) {
  for (let x = 0; x < width; x++) {
    const i = (y * width + x) * 4;
    const dstX = x + shiftX;

    if (dstX >= 0 && dstX < width) {
      const dstI = (y * width + dstX) * 4;
      imageData.data[dstI + channel] = tmp[i + channel];
    }
  }
}

この実装は、forの二重ループでキャンバスの各ピクセルを処理し、Rチャンネル要素のみを横方向にシフトします。iは元のピクセルのインデックス、dstXはシフト後のX座標、dstIは新しいインデックスを、channelは対象のRGBAチャンネルを示します。

0: Red, 1: Green, 2: Blue, 3: Alpha

:scissors: 行で分割してずらす

行で分割してずらす事で、ブロックノイズのような効果を与えることができます。

以下は適当な高さの適当な幅で要素をずらしたコードの実装例です。

See the Pen Untitled by tenori (@tenorichan) on CodePen.

:vhs: RGBチャンネル全ての要素をシフトする

for (let y = 0; y < height; y++) {
  for (let x = 0; x < width; x++) {
    const i = (y * width + x) * 4;
    const dstX = x + shiftX;

    if (dstX >= 0 && dstX < width) {
      const dstI = (y * width + dstX) * 4;
      imageData.data[dstI + 0] = tmp[i + 0];
      imageData.data[dstI + 1] = tmp[i + 1];
      imageData.data[dstI + 2] = tmp[i + 2];
    }
  }
}

今回のコードは前項と似てますが、RGBチャンネル全ての要素をシフトさせてます。

:pencil2: measureTextメソッド

テキストの描画サイズに合わせて分割を行うことを考慮し、measureTextメソッドを用いてベースとなる座標とテキストサイズの計算を行ってます。

measureText は測定したテキストの情報(幅など)を持つTextMetricsオブジェクトを返します。TextMetricsから参照可能な諸々の数値はtextAligntextBaselineの設定に影響されることがあるので注意が必要です。

以下のコードではテキストの高さを得るためにactualBoundingBoxDescentから値を得てますが、textBaselineから底までのサイズを返すので2倍にしています。

const textHeight = ctx.measureText("Glitch").actualBoundingBoxDescent * 2;
const baseY = Math.floor(height / 2 - textHeight / 2);

:watch: アニメーションの追加

グリッチエフェクトにアニメーションを追加することで、より動的で視覚的な効果を発揮します。これまでの実装を踏まえて、ランダムな動きをするグリッチアニメーションを実装してみましょう。

以下のコードでは前項までの実装を合わせて、毎ループランダムな計算結果を表示させています。

See the Pen Untitled by tenori (@tenorichan) on CodePen.

かなりガチャガチャしていていい感じです :hugging:

分割数や幅にこだわるともっとガチャガチャして良いかと思います。

:clapper: ImageDataについて

特段解説する内容でもないですが、各所のputImageDataにてオリジナルのImageDataが失われてしまいます。上書きされる前にオリジナルを変数に保持しておきましょう

:airplane: 応用

今回はテキストを用いて解説しましたが、画像データも利用することができます。

See the Pen Untitled by tenori (@tenorichan) on CodePen.

:speech_balloon: 余談

今どきのWebGLライブラリにはグリッチエフェクト以外にも、標準でもっとハイクオリティのエフェクトがたくさん付いてきます。お得だね :robot:

:heart: 参考

一緒に二次元業界を盛り上げていきませんか?

株式会社viviONでは、フロントエンドエンジニアを募集しています。

また、フロントエンドエンジニアに限らず、バックエンド・SRE・スマホアプリなど様々なエンジニア職を募集していますので、ぜひ採用情報をご覧ください。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1