PixiJSで画像にグリッチエフェクトをかけるやり方を解説

今回はPixiJSを使って画像にグリッチエフェクトを付与する方法をざっくり書いていこうと思います。
他にも方法はあると思いますが何かの参考になれば。

必要なもの

今回のやり方で必要なのはPixiJS本体PixiJS Filtersの二つです。
グリッチエフェクトはPixiJS Filtersにある既存のものを使います。

import * as PIXI from 'pixi.js';
import * as filters from 'pixi-filters';

HTMLとCSS

今回のエフェクトではimgタグに付けたclassをターゲットとして付与します。HTMLとCSSの記述自体はシンプルな者です。
ただし対象のDOMがabsoluteやfixedなどでレイアウトされてる場合はうまくいかない事があると思うので注意してください。

<div class="vfxwrap"><img class="vfx" src="photo.jpg"></div>
.vfxwrap{
  position: relative;
}
.vfx{
  visibility: hidden;
}
.glitch_canvas{
  position: absolute;
  display: block;
  font-size: 0;
  pointer-events:none;
}

画像の位置をデザインする際は.vfxwrapに対してstyleを当てて行く事になります。
.vfxに対してhiddenをかけているのは画像の見た目自体はcanvasに描画しているのでimgタグ自体は見えなくしておくためです。
.glitch_canvasへのstyleはPixiJSで追加するcanvasサイズの拡張に対応するためのものになります。

JavaScriptの記述

必要な事としては、
・DOMから画像情報を取得
・PixiJSでcanvasに画像を描画、それを元のDOMの位置に追加
・それらのcanvasにグリッチフィルターを付与
基本的な考え方はこんな感じになります。細かいところはだいたいコメントに書いておきました。

// 画像のサイズを取得してcanvasを生成
let vfxdom = document.querySelectorAll('.vfx');
let vfxlist = new Array;
let applist = new Array;
let imgloadcount = 0; // 画像読み込み用のカウント
const padding = 80; // グリッチでDOMからはみ出す値

// 画像が読み込まれたらの処理
for (let i = 0; i < vfxdom.length; i++) {
    let img = new Image();
    img.onload = function() {
        imgloadcount++;
        if (imgloadcount === vfxdom.length) {
          setCanvasDom();
        }
    }
    img.src = vfxdom[i].src;
}

  function setCanvasDom(){
    for (let i = 0; i < vfxdom.length; i++) {
      const clientRect = vfxdom[i].getBoundingClientRect();
      const w = clientRect.width;
      const h = clientRect.height;

      // PIXI appを定義
      let app = new PIXI.Application({
              width: w + padding * 2,
              height: h + padding * 2,
              antialias: true,
              transparent: true
          });

      // canvasのDOMを配置するための枠を作成
      const canvasWrap = document.createElement("div");
      canvasWrap.setAttribute('class','glitch_canvas');
      canvasWrap.setAttribute('style','margin-left:'+padding*-1+'px;margin-top:'+padding*-1+'px;');
      canvasWrap.appendChild(app.view); // 作成したcanvasにpixi appを追加
      // そのcanvasを対象のDOMの前に配置
      vfxdom[i].parentNode.insertBefore(canvasWrap, vfxdom[i]); 

      // canvasに転写する画像情報をセット
      const srcdata = vfxdom[i].src;
      vfxlist.push(PIXI.Sprite.from(srcdata));
      vfxlist[i].x = padding;
      vfxlist[i].y = padding;
      vfxlist[i].width = w;
      vfxlist[i].height = h;

      // 図形を描画する為のContainerを定義。画像Spriteをcontainerに追加。
      const container = new PIXI.Container();
      container.addChild(vfxlist[i]);

      // Containerをappに追加
      app.stage.addChild(container);
      applist.push(app);
    }

    canvasRenderer();
  }


  function canvasRenderer(){

    for (let i = 0; i < applist.length; i++) {
      let counter = 0;

      // PIXI appのループ用
      applist[i].ticker.add(function(time) {
        applist[i].ticker.update(time);
        counter++;
        if (0 < counter && counter < 12) {
          const filter_glitch = new filters.GlitchFilter({
            slices:5,
            offset:80,
            direction:10*time,
            fillMode:0,
            average:false,
            seed:0,
            red:[0,0],
            blue:[0,0],
            green:[0,0],
          });
          filter_glitch.padding = 100;
          vfxlist[i].filters = [filter_glitch];
        }else if(counter === 13){
          const _filter_glitch = new filters.GlitchFilter({
            slices:0,
            offset:0,
            direction:0,
            fillMode:1,
            average:false,
            seed:0,
            red:[0,0],
            blue:[0,0],
            green:[0,0],
          });
          vfxlist[i].filters = [_filter_glitch];
        }else if(200 < counter){
          counter = 0;
        }
      });

    } // endfor
  }

  // リサイズの場合の処理
  window.addEventListener("resize", function(event) {
    for (let i = 0; i < applist.length; i++) {
      // 現在のDOMを取得
      const clientRect = vfxdom[i].getBoundingClientRect();
      const w = clientRect.width;
      const h = clientRect.height;
      // PIXI Spriteのサイズを変更
      vfxlist[i].width = w;
      vfxlist[i].height = h;
      // canvasのサイズを変更
      applist[i].renderer.resize(w + padding * 2, h + padding * 2);
    }
  });

一見長いですがやってる事は割とシンプルなのでそこまで難しくはないはずです。今回はグリッチフィルターを設定していますが、その他にもいろいろなフィルターがあるのでそれらを試してみるのも楽しいと思いますよ。

※WPプラグインのバグでJavaScript内の半角「&」が正しく出ず文字コード「&amp;」になってしまうので、実際に動かすときはそこだけ半角&に直してください。

HTML5 Canvas