Reactで画像やテキストにWebGLエフェクトをかけるライブラリ作った

f:id:amagitakayosi:20200120205554g:plain

amagiです。先日React用コンポーネントライブラリのREACT-VFXをリリースしました。 REACT-VFXを使うと、画像や動画、テキストにWebGLでエフェクトをかけることが出来ます。

例えば、画像をグリッチしたり:

f:id:amagitakayosi:20200122230646g:plain
VFXImgのサンプル

GIFアニメをエモい感じにしたり……

f:id:amagitakayosi:20200122230824g:plainf:id:amagitakayosi:20200122230837g:plain
VFXImgでGIFアニメを表示したサンプル

プレーンテキストにもエフェクトをかけたり出来ます!!

f:id:amagitakayosi:20200122230935g:plain
VFXSpanのサンプル

この記事では、REACT-VFXを作った経緯や、動作の仕組みを解説します🦄

REACT-VFXを作った経緯

今後の仕事のために自分のポートフォリオを作り直そうと思い、インスピレーションを得るためにAWWWARDSを見ていた所、こんな感じでWebGLエフェクトが多様されてるのを発見した。

f:id:amagitakayosi:20200122233856g:plain
https://www.sachatourtoulou.com/work

f:id:amagitakayosi:20200122234122g:plain
https://collection-wakawaka.world/objects

かっこ良すぎる…………!僕もこういうWebGLエフェクトを使いたい!

ポートフォリオはReactベースで作ろうとしてたんだけど、ReactでWebGLを扱うのは結構難しい。 react-three-fiberはReact向けWebGLライブラリの中で最も筋が良いライブラリだ(個人の感想)。 react-three-fiberを使うと、JSXでThree.jsのシーンやオブジェクト、イベントなどを記述できる。 WebGLなサイトをちょちょっと作るのにすごく便利なんだけど、今回やりたい事に対してはオーバーキルだし、canvasの制御やシーン生成を手でやりたいって時には向いていない。

僕がやりたいのは、単にDOM要素をWebGLの空間に配置して、シェーダーでエフェクトをかけて描画する、って事だけだ。 という訳で、自分でライブラリを作る事にした。

使い方

インストールは npm i で行います。

npm i -S react-vfx

REACT-VFXVFXProviderVFXElements で構成されています。

  • VFXProvider: canvasWebGLコンテキスト、ページ全体の状態を管理
  • VFXElements: DOM要素をラップして、エフェクトのためのシェーダーを管理

まず、Reactアプリケーション 全体を <VFXProvider> で囲みます。

VFXProviderの使い方

次に、VFXElementsを配置していきます。 <img><video> の代わりに<VFXImg><VFXVideo> で置き換えてください。

VFXImgの使い方

これで完了!

エフェクトの種類は shader プロパティで変更できます。 この例では、画像にグリッチ風エフェクトがかかっているはずです。

シェーダーは自分で書くことも出来ます。 shader プロパティにGLSLシェーダーを渡してください。

自作シェーダーを使うサンプル

このコードはこんな感じに描画されます:

自作シェーダーの出力結果

より詳しい機能については、Webサイトの Usage セクションをご覧ください。

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

仕組み

REACT-VFXはWebサイト全体を巨大な <canvas> 要素で覆うことで動作しています。 VFXElementsの位置やサイズをトラッキングし、WebGLのテクスチャとして読み込んで、毎フレーム描画しているだけで、特に変わった事はしていません。

f:id:amagitakayosi:20200122231746p:plain
VFXProviderとVFXElementの仕組み

とはいえ、GIFアニメ対応やテキストの画像変換周りでちょっと苦労したので、その辺を解説します。

GIFアニメ対応

WebGLは画像や動画をテクスチャとしてロードできますが、GIFアニメには対応していません。 (2年前に試した時は、chromeではGIFアニメをロードできてたんだけどな……)

そのため、今回GIFアニメに対応するんは、まずGIFアニメをパースして画像に分割し、順番にテクスチャにロードしていく、ということをする必要がありました。

GIFアニメのパースにはSuperGIFが良く使われるけど動作が重すぎるので、今回は matt-way/gifuct-js を使う事にした。 PromiseベースのAPIだしサクサク動いて便利なんだけど、bower時代の産物なのでforkする必要があった。

gifuct-jsのデモ

gifuct-jsによってフレームごとの画像データが手に入ったので、次はこれをWebGLでロード出来るようにする。 gifuct-jsはフレームごとの長さも返してくれるので、現在時刻と再生時間、フレームの長さを比較して順番に描画する事で、簡易的なGIFプレイヤーを実装できる。

f:id:amagitakayosi:20200123153453p:plain
gifuct-jsによるGIFプレイヤーの実装例

あとはこのcanvasWebGLに渡すだけで、GIFアニメをWebGL上に描画できた。

テキストを画像に変換する

WebGLでテキストを扱いたい場合、事前にテキストを画像に変換しておくか、Three.jsのドキュメントで解説されているようなトリックを使う必要があった。

https://threejs.org/docs/#manual/en/introduction/Creating-text

これらのトリックを使うのも大変だし、デザイン変更の度にPhotoshop等で画像を書き出すのも面倒……という事で、もっと手軽にDOM要素を画像に変換できないか調べた。

まず、html2canvasが使えるか試してみた。

html2canvas - Screenshots with JavaScript

html2canvasはWebページのスクリーンショットをブラウザ上で生成するライブラリだ。一応いい感じにDOM要素の画像を生成できたんだけど、html2canvasは毎回ページ全体をクローンするため、不要なリクエストが走りまくって動作がメチャクチャ重くなってしまい、今回の用途には合わないと判断した。

結局、html2canvasと似た動作をするdom2canvasというスクリプトを自作する事にした。

SVGforeignObjectという要素をつかうと、DOM要素をSVGに埋め込む事ができる。 これを利用すると、次の要領でDOM要素をcanvasに描画できる。

DOM要素 -> HTML文字列 -> SVG文字列 -> Canvas要素に描画 -> WebGL texture

いろいろ制限はあるけど(クロスオリジンなリソースを読み込めない等)、今回はプレーンテクストだけ変換できればいいので、これで充分だ。

こうして、渡された文字列を画像に変換してエフェクトをかけるコンポーネント VFXSpan ができた。 VFXSpanは文字列が更新されたら自動的に再描画するので、例えばtextareaと組み合わせたりもできる。

textareaに連動するVFXSpan

foreignObject周りはこの辺の記事を参考に実装しました🙏

今後の課題

今後は以下の機能を追加したいと考えています。

  • カスタムuniform変数のサポート
  • backbufferサポート
  • エフェクトプリセット追加

REACT-VFXはまだまだ粗もあるけど、ちょっとしたエフェクトをWebサイトに追加したい場合にはかなり便利に使えるはずです。 ぜひ試してみてください!

感想や機能リクエストはGitHubTwitterで気軽にどうぞ!

よろしく〜👻👻👻

Kyoto.js 17を開催しました

2020-01-11 (土)、Kyoto.js 17を開催しました。 少し時間が空いてしまったけど、僕の個人的な振り返りを書いていきます。

f:id:amagitakayosi:20200120011414p:plain

Kyoto.jsとは

Kyoto.jsは京都のWebプログラマーやデザイナーのためのコミュニティです。 年1~3回のペースでゆるりと開催しています。

.js と言っているとおりJavaScriptがメインですが、あんまりJavaScriptに関係ない話でも面白ければOKという体でやっています。 開催は京都ですが、毎回なぜか大阪や神戸、名古屋、たまに東京の人も来てくれています(本当にありがとうございます!)

今回は株式会社はてなの京都オフィスで開催しました。

資料まとめ

kyotojs.connpass.com

Twitterで補足できた分だけまとめました。 他の方も資料を公開されていたら追加するので教えて下さい!

Kyoto.jsは他のWeb系勉強会と比べて、イベント自体にテーマがないので雑多な話がしやすいという特徴があるけど、今回もまあまあ緩い感じでできたかな。 今回はWeb系成分が強めだったけれども。

あと、最後の id:takawo さんのp5.jsライブコーディングがめっちゃ盛り上がって良かった。 やっぱ動くものを見せられると楽しくていいですね。

僕の発表について

今回は、先日公開したReact向けライブラリ REACT-VFX の実装について発表した。

資料はこちら(スマホでは見れません……スマン)↓

amagi.dev

GIFアニメをWebGLでロードできるようにした話とか、発表資料をfusumaで作った話とか、話せる事は沢山あったんだけど、仕事が忙しくて資料を作る時間をあまり割けなかったのでスルーしてしまった。 またブログにでも書こうかな。

個人的振り返り

良かった所

  • 参加率驚異の100%
  • 飛び入りLT3人!
  • まあまあ落ちついて運営できた

connpassで当日までに参加登録されていた方の参加率が100%だった。これは30人規模の勉強会では異常な事である。 Kyoto.jsは開催頻度が少ないし、地方のイベントはそもそもキャンセル率が低いというのもあるけど、コレはほんまにすごい。 やはり参加者の皆様のおかげで「ちょっと行ってみようかな」という人も参加しやすい雰囲気が作れているのだと思います。 皆様ありがとうございます……!!

飛び入りLTも3人もいて凄かった。 ほんとはもっとLT用の時間も長くとりたいんだけど、これ以上長くすると5時間コースになっちゃうんだよな……

問題点

  • 質問できなかった
  • 参加者全員と会話できなかった
  • 運営タスクはメンバーに事前にアサインしておくべきだった

まずは質問。 質問はバンバン飛び出た方が発表する側も喋りやすいし、休憩時間に参加者同士が会話するキッカケにもなったりするので、もし質問がなかったら運営メンバーが「はいはいはーい!」って手を上げた方が良いと思ってる……のだが、今回は初手でミスってからなんか質問できなくなっちゃった……。 Kyoto.jsの主催になってからしばらくは意識的に手を上げたりしてたんだけど、ここ数回は勝手に手が上がってたから、完全に甘えてたな。

次に、参加者全員とお話できなかった!主催なのに!!! うおお何故だ!??今回は結構話したはずなんだけどなあ。。。 発表してくれた方々でもあまりお話できなかったので非常に申し訳なく思っています、すみません……

特に初参加の人は会話に入っていけず疎外感を感じてしまう事があるので、それを避けたいと常々思っている。 懇親会での馴れ合いを好まない人もいるけど、基本的には参加するなら周りの人と会話できると良いなと思っています。 参加者同士での会話って基本的に良いことばかりで、技術的な知見の交換もできるし、仕事につながることもよくある。 もしかしたら最高の友達になれるかもしれない。

京都ではあんまりJSのイベントがないので、Kyoto.jsがハブとしての役割を果たせると理想なんだよな。 次は絶対みんなに挨拶します!!!!

あと、今回は会場にたまたま来てくれたはてな社員に仕事を振ったりしていた……。 幸い「運営手伝います」って言ってくれる人も増えて来たので、次は開催日までにタスクのアサインを行うようにしよう。

まとめ

次はGWか夏ごろかな~。ホンマか?

夏、鴨川にプロジェクター持ち寄って、こぢんまりとしたDJ/VJイベントやりたいんだよな~。
知見求む!!!