比較検証 JavaScript Rendering

キャラクターを動かす5方式を比較
— SVG・Canvas・DOM・WebGL・LVGL

「元絵に似ている」と「自由に動かせる」は同じではありません。
実際に作った5種類を、同じ判断軸で比べました。

← Articles 一覧へ戻る

比較の目的

サマターゲという同じキャラクターを、Webサイト、配信アバター、モバイルアプリ、デスクトップロボットへ展開しています。画面サイズも、入力も、必要な表情も違うため、一つの描画方式を全環境へ使い回すことはできませんでした。

そこで、次の5方式を実装し、見た目の忠実度、動かす単位、転送量、解像度への追従、隠れた部分の扱いを比較しました。

  1. 輪郭を1,130個のSVGパーツへ変換する
  2. 全RGB画素を埋め込み、WebGLで一度だけサンプリングする
  3. Canvas 2Dで顔を毎フレーム描き直す
  4. HTML要素とCSS図形で顔を組み立てる
  5. Stack-chanの320×240画面へLVGL部品として描く
ここで示すファイル容量は、2026年7月30日に公開時の主要ファイルを測定した値です。フレーム時間の統一ベンチマークはまだないため、「必ず高速」といった性能順位は付けていません。

比較表

方式 元絵の忠実度 動かす単位 向いていた用途 難しかった点
SVGパーツ 輪郭と色の近似 面・線・部位グループ 拡縮、部分変形、構造の観察 部位分類と細線の保持
RGB+WebGL 静止時は1px完全一致 テクスチャ上のリグ 元絵を保ったまま大きく動かす 転送量と隠れ画素の補完
Canvas 2D 特徴を図形で再構成 目・瞳・眉・口などの数値 顔追跡、配信、リアルタイム表情 毎フレームの状態整合
DOM+CSS 記号的な顔 HTML要素とCSS変数 公式サイトの軽い追視演出 複雑な変形と要素数
LVGL 小画面向けに単純化 組み込みGUIの部品 Stack-chanの瞬き・視線・感情 既存アニメーションとの競合

SVG方式:構造が見える

SVG方式は、元画像を面330個と線800本へ分けます。輪郭や色は近似ですが、各パーツへ haiririseyeLine などのラベルを付けられます。同じラベルをグループ化すれば、目だけを閉じたり、アホ毛だけを回したりできます。

公開時の主要ファイルは約1.09MB、gzip相当で約356KBでした。ベクターなので拡大しても輪郭が崩れず、ブラウザのDOMから個々の部品を確認できます。

一方、色だけで部位を推定すると白目と白い髪を区別できません。線を塗り面として扱うと棘が出るため、中心線と線幅へ変換する処理も必要でした。詳しい生成工程はSVG化の記事にまとめています。

RGB+WebGL方式:静止時の完全一致を守る

比較用の別実装では、1254×1254、合計1,572,516画素のRGB値を pixel-data.js へ埋め込みました。ページを開くと canvas.putImageData() で原寸へ戻し、全画素を元データと比較します。1画素でも違えば検証を失敗させます。

アニメーション時は画像を部位ごとに切って重ねるのではなく、元RGBをGPUテクスチャとして一度読み込み、耳・髪・顔・身体のリグを変形します。各出力画素が元テクスチャを一度だけ参照するため、切り抜き境界の二重表示を避けられます。WebGLが使えない環境では同じ規則のCanvasへ切り替えます。

代わりに、主要ファイルは約7.55MB、gzip相当で約1.75MBでした。SVG方式のgzip相当と比べて約4.9倍です。また、頭を傾けて初めて見える髪の裏側など、元の1枚絵に存在しない画素は完全複製できません。1,131個の輪郭をリグへ再分類し、隠れ領域を別状態で管理しています。

Canvas方式:表情を数値として扱う

配信用Webアバターでは、元画像の再現よりも、カメラから得た表情をすぐ反映することを優先しました。Canvas 2Dへ縦長の目、瞳、眉、口を毎フレーム描き、yawpitchblinkLeftmouthOpen など11個の正規化値で制御します。

描画対象が少ないので、顔の向きと視線を別々に動かしやすく、表情の調整も数値で追えます。反面、線や重なりをすべてコードで管理するため、装飾の多い全身イラストには向きません。実装は顔画像を送らないWebアバターの記事で説明しています。

DOM+CSS方式:サイトの一部分として動かす

Screen Timeアプリの公式サイトでは、顔をHTML要素とCSSの角丸図形で作りました。JavaScriptがポインターと両目の距離を計算し、移動可能範囲へ収めた目標値へ requestAnimationFrame で補間します。

この方式は、テキストやボタンと同じレイアウトの中へ置きやすく、CSSだけで色やサイズを変更できます。ページが非表示になったらフレーム更新を止め、prefers-reduced-motion が有効なら追視を始めません。

ただし、髪の束や服までDOM要素にすると管理対象が急増します。公式サイトの「顔がこちらを見る」程度の演出に範囲を限定すると扱いやすい方式でした。

LVGL方式:小さな画面と既存の動きを優先する

Stack-chanでは、ESP32-S3上のLVGLで縦長の目、独立した瞳、眉、口をGUI部品として作りました。視線は目全体ではなく瞳だけを移動させます。これはWeb版の見た目をそのまま縮小するのではなく、320×240画面で表情が読めるように単純化したものです。

難しかったのは描画そのものより、公式ファームの瞬き、呼吸、頭をなでた反応、IMUによる傾きと共存させることでした。感情を変更した直後、瞬き処理が古い目の状態へ戻してしまうため、瞬き側の基準値を再同期しています。

変更を顔のskinへ寄せ、マイク、スピーカー、タッチ、カメラ、サーボの経路を残したことで、上流機能を使いながらキャラクターだけを差し替えられました。

選び方

  • 原画を分析し、部位構造を見たい:SVGパーツ
  • 静止画の完全一致を検証しつつ大きく変形したい:RGB+WebGL
  • 顔追跡の値をリアルタイムに反映したい:Canvas 2D
  • WebページのUIと一緒に軽く動かしたい:DOM+CSS
  • 小型端末の既存ライフサイクルへ組み込みたい:LVGL

比較して分かったのは、最も忠実な方式が常に最も動かしやすいわけではないことです。元絵に存在しない隠れ領域まで動かすなら、どこかで人間の分類や補完が必要です。用途ごとに「絶対に守るもの」を先に決める方が、描画技術を選びやすくなりました。