REACT-VFX

自己紹介

WebGLでVJやってます

こんなん作りました

https://amagi.dev/react-vfx/

経緯

  • ぼく「ポートフォリオ作りたいな〜」
  • ぼく「参考にAWWWARDSでもみるか」
    • 僕はChromeにPanda入れてます

この画像エフェクトかっこいいな〜

AWWWARDS example 1

かっけ〜!

AWWWARDS example 1

僕のVEDAJSで作れるかな?

  • WebGL(GLSL)でアニメーションを描画するライブラリ
  • 要素の数だけcanvasを生成する必要があり、非効率
  • WebGL contextの数に上限アリ(ソース)

Spector.jsで見てみる

  • BabylonJSチームによるWebGLデバッグツール
  • どういう順番で描画命令とかテクスチャを見れる
Spectorの画面

Devtools見た結果

  • ページ全体にcanvas一枚
  • 画像ごとに描画されてる
  • Three.jsの場合、画像毎にシーン作って
    viewport設定して描画すれば良さそう

参考: Three.jsのexample

できました

REACT-VFX 完成の様子

react-vfxの仕組み

登場人物

  • VFXPlayer: シーン管理、描画
  • VFXProvider: canvas生成、Player初期化
  • VFXElement: img等のラッパー

処理の流れ

    1. VFXProviderがcanvasを生成
    1. Elementsのマウント時に登録
    1. メインループ
    • 要素の位置を更新
    • 画面内の要素を描画

1. VFXProvider

  • 画面全体を覆うcanvasを生成
  • VFXPlayerを初期化
    • Three.js周りを管理するクラス
  • ContextにVFXPlayerを渡す

hooks便利〜

const VFXContext = createContext(null);

const VFXProvider = props => {
  const [player, setPlayer] = useState(null);

  useEffect(() => {
    // 中略

    const p = new VFXPlayer(canvas)
    setPlayer(p);

    // 中略
  }, []);

  return (
    <VFXContext.Provider value={player}>
      {props.children}
    </VFXContext.Provider>
  );
};

VFXElements

  • マウント時にVFXPlayerに登録
  • 要素に応じてTextureを作成
    • img, video: そのままThree.jsに渡す
      • WebGLはvideo要素から直接テクスチャ作れて最高!
    • span, div: 気合で画像に変換(後述)

hooks便利〜

const VFXImg = props => {
  const { shader } = props;
  const player = useContext(VFXContext);
  const ref = useRef(null);

  // 画像ロード後に呼ぶ
  const init = useCallback(() => {
    // VFXPlayerに登録
    player?.addElement(ref.current, { shader });

    return () => {
        // VFXPlayerから削除
        player?.removeElement(ref.current);
    };
  }, [shader, player]);

  return <img ref={ref} {...props} onLoad={init} />;
};

3.メインループ

  • 各要素の要素の位置を毎フレーム取得
  • 画面内にあれば、その位置にテクスチャを描画
  • まあまあ重いけど動くからヨシ!

IntersectionObserverは?

  • 試したけど微妙だった
  • コールバックの実行がたまに遅れる(?)
  • iOSで触ってるときに遅れてきたりする(?)
  • 遅延ロード等に使われるAPIだから仕方ない……

テキストを画像に変換

  • 当初はhtml2canvasを使用
  • 遅い上に無駄なリクエストが走りまくるので断念
    • キャプチャするたびにページ全体をクローンする
    • 正確にスタイルを再現するには
      ページ全体をクローンする必要があるため
  • 今回は、最低限プレーンテキストが画像化できれば良い
  • SVGのforeignObjectを使う
    • SVGにDOMを埋め込める奴

dom2canvasの流れ

  • 画像化したい要素をクローン
    • 親要素もクローン (vertical-align等の再現に必要)
  • outerHTMLからSVG文字列を作成
  • canvasに描画
  • THREE.Textureにcanvasを渡す

dom2canvasの困りごと

  • 微妙にズレたりズレなかったりする
    • html2canvasでもズレてたのでムズそう
  • 子要素があるとおかしくなる
  • 改行するとおかしくなる
  • クロスオリジンなリソースを読み込めない

なんかいい方法あったら教えてください

おまけ: 背景

REACT-VFX 背景アニメーション

react-three-fiberを使用

  • 宣言的にシーンを記述できる
  • React + Three.js系ライブラリの中で
    最も筋が良さそう(個人の感想)
<Canvas>
  <mesh>
    <boxGeometry args={[1, 1, 1]}/>
    <meshStandardMaterial/>
  </mesh>
</Canvas>

アニメーション用の値

  • 普通にnumberをpropsで渡すと重いので🆖
  • react-springのuseSpringを使う
    • アニメーションで使いたい値のラッパー
// scrollのラッパーを作る
const [{ scroll }, set] = useSpring(() => ({ scroll: 0 }));
const onScroll = useCallback(e => {
  set({ scroll: window.scrollY });
}, [set]);

// スクロール位置を 0 ~ 1 に変換
const top = scroll.interpolate(x => {
  return x / (document.body.scrollHeight - window.innerHeight);
});

return <>
  <Triangle top={top} />
  <Particles top={top} />
</>;

REACT-VFX本体にも使う?

  • canvasを直接触りたかったのでやめた
    • react-three-fiberはcanvasやrenderer周りを
      抽象化してくれている
    • ふつーのWebGLやるのにはメチャ便利

まとめ

  • まだまだ荒削りですがPR歓迎です!!!!
  • シェーダー楽しいよ
  • みんなもおもしろWebサイト作ろうぜ

https://amagi.dev/react-vfx/