VFX-JS v1.0 をリリースしました - HTML-in-Canvas対応、Three.js依存削除、エフェクトのモジュール化

先日、 VFX-JS Ver.1.0 をリリースしました。

github.com

VFX-JS は、Web 上の画像・動画・テキストに対して WebGLで 視覚的なエフェクトを加えるライブラリです。他のWebGLライブラリと比べて、canvasのセットアップが自動化されていることや、画像・動画の HTML 要素をそのまま入力として渡せるのが特徴です。

例えば、このようなエフェクトをWeb上で実現できます:

このライブラリは前身の REACT-VFX から6年ほどチマチマ開発を続けていますが、最近大きめの機能追加と、APIの大幅なリニューアルができたので、改めてバージョン 1.0 としてリリースすることにしました。

この記事では VFX-JS の概要と、今回実装した新機能について紹介します。 あわせて、リリースに関して考えたことについても書いていきます。

VFX-JS とは

VFX-JS はその名の通り、Web 上の画像や動画に視覚的な効果(ビジュアルエフェクト)を付与できるライブラリです。 例えば、CSSではできない細かい色の調整や、色収差エフェクト、グリッチ、グロー、ハーフトーンといった、Webで実現しづらいエフェクトなどを手軽に使用できます。 他のライブラリと比べてとにかくセットアップが簡単で、HTML要素に一発ででエフェクトをかけられるのが特徴です。

const vfx = new VFX();
vfx.add(img, { effect: [new BloomEffect()] });

WebGL を使ったWeb開発では、全てのオブジェクトを WebGLの3D空間上でレイアウトする必要があります。 一般的なWeb 開発のような CSS での位置/サイズ指定ができないため、通常のテキスト要素やWeb独自の要素と組み合わせるのが難しい、という問題があります。

VFX-JS では、 HTML 上の要素の配置をもとに、 WebGL のキャンバス上へオブジェクトを自動で配置し、ページのスクロールや要素の移動にも自動で同期します。これにより、通常の Web 開発フローのまま WebGL エフェクトを書けるのが特徴です。

VFX-JSのレイアウト

また、画像や動画だけでなくテキスト要素にもエフェクトをかけられるのが強みの一つです。これはもともと、SVG の foreignObject を使って HTML を Canvas に変換するというハックによって実現していました。しかし今回、HTML-in-Canvas という API がブラウザに実装されたことで、より高度な表現が可能になりました (後述)。

新機能

HTML in Canvas サポート

今回、Chrome が「HTML in Canvas」をサポートしたことで、HTML 要素の描画結果をそのまま WebGL のテクスチャとして読み込めるようになりました。 VFX-JS でもこれをサポートしています。従来のtextキャプチャ機能と区別するため、新たに API を用意しました。

const el = document.querySelector("#div");
vfx.addHTML(el, { effect: [...] });

VFX-JS はもともと div のキャプチャ自体には対応していたが、これまでは SVG の foreignObject 機能を使って HTML を無理やり Canvas に変換する(HTML → SVG → Canvas → texture)という方法を取っていました。この方法にはいくつか制限があり、例えば CSS のスタイルを完全に再現できなかったり、Web フォントのサポートが現実的に不可能だったりしました。

しかし、HTML-in-CanvasAPI では ブラウザが実際にレンダリングした HTML の出力をそのまま Canvas として読み込めるため、これまでできなかった Web フォントもバッチリサポートできるようになりました。実際に HTML Canvas を使ったデモをいくつか用意してあります。

HTML-in-Canvas API は Firefox でもすでに実装されてるけど、まだいくつか制約があります(動画が再生できない等)。 CSS animationは動作するので、アニメーションが一律でブロックされているわけではなさそうだけど、この辺りはブラウザの対応次第という状況です。

とはいえ、確実に表現の幅は広がっており、実際、WebGL 界隈の人たちはこぞって HTML-in-Canvas で面白いデモを作って公開しています。 このAPIがモダンブラウザで安定して使えるようになれば WebGL の状況は大きく変わると思うので、VFX-JS でも積極的にサポートしていきたいと思っています。

Effect API

今回、Effect というinterfaceを定義し、ユーザーが自分でエフェクトを実装して配布や再利用できるAPIを公開しました。

以前の VFX-JS では、ユーザーがシェーダー (GLSLのコード) とパラメータを指定するという形になっていましたが、それには様々な制限がありました。 例えば、パラメータのデフォルト値を定義したり、マウスクリックに応じて内部状態を変化させる、といったケースです。

これまでは、ユーザー側の JS で状態を管理してもらうという設計になっていて、僕が CodePen に投稿していたデモもそのような形を取っていました。 ただ、これはさすがに扱いづらく、もう少し親切なAPIを提供したいと考えていました。

codepen.io

そこで今回、エフェクトを Effect インターフェースとして定義しました。 エフェクトが内部状態を自分で更新したり、テクスチャ等のリソースも持てるようになり、パーティクルや流体シミュレーションのような複雑なエフェクトも閉じたオブジェクトとして扱えるようになりました。

Particle
Stable Fluid

また、これらのエフェクトは重ね掛けできるので、例えば Scanline と Bloom を組み合わせて CRT ディスプレイを再現したり、グラデーションとハーフトーンを組み合わせてポップな表現ができたりします。

このエフェクトはユーザーが自分で再利用したり、npm で他の人に配布したりすることもできます。 VFX-JS 本体にも新たにエフェクトパッケージを用意し、別の npm パッケージとしてプリセットを配布するようにしました(後述)。

@vfx-js/effect パッケージの追加

従来のシェーダープリセットや、最近開発したエフェクトをまとめて @vfx-js/effect として公開しました。

実装済みのエフェクト一覧:

  • AsciiEffect (アスキーアート化)
  • BadJpegEffect (JPEG圧縮劣化シミュ―レート)
  • BloomEffect
  • ChromaticEffect
  • ColoredEdgesEffect
  • DatamoshEffect (データモッシュ)
  • DitherEffect
  • DuotoneEffect
  • FluidEffect
  • GlitchEffect
  • GradientMapEffect
  • HalftoneEffect
  • HueShiftEffect
  • JPEGGlitchEffect (JPEGグリッチ)
  • MatrixEffect (Matrix風の文字降下)
  • ParticleEffect
  • ParticleExplodeEffect
  • PatternRefractionEffect
  • PixelStretchEffect
  • PixelateEffect
  • PixelSortEffect
  • RainbowEffect
  • RgbGlitchEffect
  • RgbShiftEffect
  • ScanlineEffect
  • SinewaveEffect
  • SliceShiftEffect
  • TilePixelateEffect
  • TritoneEffect
  • VignetteEffect
  • VoronoiEffect
  • WarpEffect

これらのエフェクトはStorybookで試せるようにしています: https://amagi.dev/vfx-js/storybook/?path=/story/effect--bloom

使用する際は、 @vfx-js/core と合わせてimportしてください:

import { VFX } from "@vfx-js/core";
import { BloomEffect } from "@vfx-js/effects";

const vfx = new VFX();
vfx.add(img, { effect: [new BloomEffect()] });

Three.js 依存の削除

VFX-JSはThree.jsを使って実装していたけど、今回 WebGL を直で叩くようにして、 Three.js 依存を削除しました。

もともと Three.js を利用していたのは、開発がラクというのもあるけど、より多くの人がコードを読んで理解しやすいだろうという理由でした。 しかし、実際の所PRがたくさん来るわけでもなく、むしろ Three.js 依存によって容量が大きくなっていたので、利点を上回っていました。

というわけで、今回は Claude Code の力を借りて、生の WebGL API への移行を行いました。 VFX-JSは Three.js の機能のうち極々一部しか使っていなかったので、移行はほぼ一瞬で完了し、ビルドサイズはほぼ5分の1になりました。もっと早くやればよかった……。

この際、WebGL ではなく WebGPU に移行してしまうことも考えたけど、既存エフェクトとの互換性の問題があるため見送りました。 シェーダーをビルド時に自動変換する等もやろうと思えば可能ですが、WebGL と WebGPU では Y 軸の向きが逆になるなど互換性の問題があるため、良いアイデアが思い浮かぶまでは保留にしています。

Example ギャラリーの作成

VFX-JS で何かデモを作ったときは CodePen で公開していたのですが、自作ライブラリの機能紹介ばかりを CodePen に投稿するのは行儀が悪いだろうという点と、動画や画像を使ったデモは CodePen だと作りづらいという点があり、新たに Example ギャラリーページを作成することにしました。

VFX-JS Examples

ギャラリーのデザインは Claude Design でいくつかアイデアを出してもらい、それをベースに自分で手直ししたものです。 これで自由にデモを作れるようになったのですが、CodePen で公開していたときより明らかに反応が鈍いので、CodePen とギャラリーページの棲み分けについては、もう少し考え中です……😇

感想

VFX-JS は、2020年に僕がリリースした REACT-VFX というライブラリがベースになっている。 当時は React の Provider API でページ全体を1枚のcanvasで覆い、アプリ内の画像や動画の位置をトラッキングするという設計だった。 その後、React前提をやめてVFX-JSという名前でリリースし直した。生のHTML要素を直接引数で渡して、エフェクトの実装を気軽に始められるようにした。

そう、ライブラリの主眼としては「誰でも再利用できるエフェクト」というより、「エフェクトを気軽に開発するためのライブラリ」という位置づけだった。

WebGLの文脈では、誰でも再利用できるエフェクトというのは実はあまり求められていない。というのも、WebGLでのWeb開発というのはつまり「他にはないWebサイト」をいかに作るかということであって、他と同じでいいならわざわざWebGLを使う必要もないからだ。 VFX-JSは、最初から後者がメインのつもりだったんだが、GLSLを書くハードルが高すぎたのと、「誰でも再利用」というクリシェに引っ張られて、単なるpreset集になってしまっていた感じがする。プリセットについても、ある程度複雑なエフェクトになると以前の仕組みでは配布が難しく、本当にシンプルな物しか同梱できなかった。

「自分でエフェクトを開発するためのライブラリ」として、より凝った処理をするための Effect API やマルチパス対応は、それこそ2020年からやりたいな〜と思ってはいたものの、時間も実装力も足りずに実現できずにいた。

今年の春に HTML-in-Canvas が実装され始めたのと、Vercel のエンジニアがVFX-JSとほぼ同じ事をやってバズっていたのを見て、久しぶりに開発の機運が高まった。やりたいことは固まっていたので、 Claude Code をバシバシしばくことで、やっとこさ v1.0 にこぎつけた、というお話。

シェーダーの開発は以前としてハードルが高いとは思うんだけど、それこそAIの力を借りれば、WebGLの知識がない人でもある程度サクっとエフェクトが作れると思う。今回あらたにプリセットに追加したeffectでも、寝る前にClaudeに指示を投げて、出来上がってきたものを調整して作ったものが多数ある。AIはまだWebGLの描画結果を正しく解釈/評価できるほどには賢くなっていないが、期待する仕様、挙動、バグについて伝えれば、シェーダーは勝手に書いてくれる。

先日のFigma ConfigではFigma Shadersが発表されたが、これもユーザーにシェーダーを書かせるのではなく、ユーザーのプロンプトに対してAIがシェーダーを勝手に実装するという流れになっている。 シェーダーに限らず、UIコンポーネントでもなんでも、全体的なデザインの舵取りは人間がしつつ、個々のコンポーネントはAIがたたき台を作って調整する、という流れになっていくんだと思う。それでもマニエリスムというか、手でコードをこねくり回しているときにだけ訪れる閃きというのは存在するので、やはり我々はエディタを捨て去ることはできないのだが……。

今度、Claude Codeでエフェクトを実装するチュートリアルでも作ってみようかと思っている。

ターミナルで動くMarkdownスライド作成ツール Ratride を作った

Ratrideという、ターミナルで動くMarkdownスライド作成ツールを作った。

特長:

  • アニメーション対応
  • Webでも動く
  • 見出しをAAに変換できる

github.com

経緯

先日の #onishi50 でLTするために開発。 LTの内容が「ターミナルで動くDAW」についてだったので、LTもiTerm2上で行いたかった。

ターミナルで動くスライド発表ツールというのは特に珍しいものではない。古くはEmacsのorgモードで発表している人も沢山いたし、Go製のslidesやRustで書かれたpresentermなど、多機能なツールが沢山ある。

今回LT資料を作るにあたってpresentermも試したが、自分のスタイルには合わなかった。 LT用スライドを作るくらいならClaude Codeに投げたら一瞬で出来るだろうと思い、自作することにした。

開発言語はRust。 Ratatui というTUIフレームワークを使っている。 名前は Rust + スライド で Ratride とした。

使い方

cargoでインストール:

cargo install ratride
ratride slide.md

npmからインストールして、JSライブラリとしても使える。

npm install ratride
import { run } from 'ratride';

const md = await fetch('./slide.md').then(r => r.text());
run(md);

機能

アニメーション対応

スライドのトランジション アニメーションに対応している。もちろんターミナルで動く。 Ratatui には tachyonFx というアニメーションフレームワークがあるので、これを使用した。

https://github.com/ratatui/tachyonfx

Webでも動く

ratride slide.md --export OUT_DIR とすることで、そのままデプロイ可能なHTML入りディレクトリを出力できる。 Ratrideのwebサイトもこれで出力している。

Ratride - TUI slide presenter with FX

また、 ratride slide.md --serve とすると、 localhost:3000 にlive-reload serverが立つ。 スライド作成に便利。

Ratatui製アプリをWebで動かす Ratzilla というライブラリもあるが、今回は使っていない。 スライド中のリンクをクリックできるようにしたかったが、Ratzilla では難しいので、Claude CodeにCanvas実装をイチから作らせた。 Ratzilla に比べると色々不足しているんだろうけど、自分用なので気にしない。

見出しをアスキーアートに変換

スライドの先頭で <!-- figlet --> というoptionを追加することで、見出しをアスキーアートに変換できる。

例:

オプションは figlet となっているが、文字色やグラデーションやwasm対応のため、Rustで figrat という figlet フォークを作成し、内部で使用している。

GitHub - fand/figrat · GitHub

TUIツールの隆興について

最近は Ratatui や Go における Bubbletea を使った、リッチな TUI を持つツールが大量にリリースされている。 これは Claude Code などのCLIツールに見られるターミナル回帰の流れもあるが、コーディングエージェントにとってGUI出力よりもターミナル出力のほうがデバッグしやすい、という事実が何より影響していると思う。

去年、しばらく業務でテキストレンダリング関連のコードを書いていたんだけど、Claude Codeは画像内のテキストのレイアウトや描画バグを全く理解できなかった。それに比べ、TUIの出力はLLMが評価しやすいので、GUIツールを作成するよりも圧倒的にサイクルを回しやすい、というのがTUIツールの流行に寄与しているのではないか。