HTML-in-Canvas、何ができる? HTMLで組んだ文字を canvas で崩してみた
Chrome / Edge 157 以降で利用できる API を使用
HTML-in-Canvas は、HTML の要素を canvas に描き込むための API だ。2026年11月3日に安定版になった Chrome 157(Edge も 157 から)で、フラグなしで使えるようになった。
Flash では、文字も図形も同じ画面の中で、自由に重ねて動かせた。元 Flash 使いとして、Web でも同じことができるようになるのか気になり、試してみた。
これまで、canvas と HTML はどう組み合わせていたか
これまで、canvas と HTML は別々に描かれていた。2D の canvas にも fillText などで文字を描く機能はあるが、HTML のように段落を組んだり、自動で折り返したりする機能はなく、HTML の要素も描けない。そのため、canvas で作るゲームや 3D、データビジュアライゼーションに文字や UI を載せるときは、次のどれかの方法をとってきた。
- HTML を canvas の上に重ねる。3D の物体にラベルを付けるときなどによく使う方法だ。canvas 側の物体が動くたびに、HTML の位置や大きさ、変形を毎フレーム計算し直して、canvas の座標に合わせる必要がある。canvas の上に重ねた HTML は、同じ canvas 内の図形の間には挟めず、シェーダーで効果をかけることもできない。
- canvas の中に、文字や UI を自前で描く。折り返し、行送り、入力欄、テキストの選択まで、自分で作ることになる。描いた文字はスクリーンリーダーから見えないので、読み上げ用の DOM を別に用意する必要もある。
- html2canvas のようなライブラリで HTML を画像にし、canvas に貼る。ライブラリが DOM と CSS を読み取って描き直すので、対応していない CSS は再現されない。描いた時点の静止画になるため、入力やホバーにも反応しない。
HTML-in-Canvas とは
HTML-in-Canvas を使うと、ブラウザがレイアウトした HTML の要素を、そのまま canvas に描ける。描いた後も要素は DOM に残るので、テキストの選択、翻訳、ページ内検索、スクリーンリーダーでの読み上げも使える。2D では、HTML の要素を drawElementImage() で直接描くと、ブラウザがクリックを受け付ける位置も描画先に合わせる。WebGL や WebGPU では、updateElementGeometry() で位置を伝える。今回のデモのように、いったん描いた文字を画像として切り分けて動かす場合は、帯や破片の位置に合わせて元の文字の選択範囲まで動くわけではない。基本の手順は次のとおりだ。
- canvas に content="drawable" 属性を付ける。canvas の子孫の HTML がレイアウトされるようになる(そのままでは表示されない)。
- 描きたい要素に drawable 属性を付ける。
- canvas の paint イベントの中で ctx.drawElementImage(要素, x, y) を呼び、要素を描く。
- canvas.requestPaint() を呼ぶと、次のフレームで paint イベントが 1 回発生する。描いた要素の見た目が変わったときにも、ブラウザが paint イベントを発生させる。
描く先は 2D に限らず、WebGL と WebGPU にも対応している。要素の見た目をテクスチャとして 3D の中に貼れるので、three.js、PixiJS、Babylon.js などのライブラリも対応を始めている。
対応状況
Chrome では、フラグ(chrome://flags/#canvas-draw-element)で試せる段階と Origin Trial を経て、Chrome 157 で正式に有効になった。Microsoft Edge も 157 で対応している。試験提供の間に API が変わっていて、canvas に付ける属性は layoutsubtree から content="drawable" になり、描く要素それぞれに drawable 属性が必要になった。そのため、古い記事のサンプルや、古い版のライブラリでは動かないことがある。
2026年11月8日時点で、Firefox と Safari での提供は確認できない。Mozilla は標準化への立場をまだ表明していない。公開するページで使うなら、対応状況を調べて表示を切り替え、対応していないブラウザには別の表示を用意する必要がある。
描けるものにも制限がある。canvas に描いた画素は JavaScript で読み出せる。そのため、訪問済みリンクの色や別オリジンの iframe の内容など、読み取りを制限している情報は描画から除かれる。
何ができるか
Chrome の公式ブログや仕様の解説では、次のような使い方が挙がっている。
- HTML のテキストや UI に、WebGL のシェーダーで効果をかける。
- 3D シーンの中に、操作できる HTML の UI を置く(ゲーム内の端末や、ページをめくれる本など)。
- グラフの凡例や、デザインツールのテキストボックスなど、canvas で作ったアプリの中の文字を HTML で組む。
- HTML を含む canvas 全体を画像にしたり(toBlob)、映像として取り出したり(captureStream)する。
デモ:HTMLで組んだ 1 語を、canvas で崩す
デモでは、HTML と CSS で組んだ英単語を素材にして、8 つの動きを作った。語は、その場面の動きそのものを指している。タイルをずらして組み替える SHIFT、帯を引き伸ばす ELASTIC、下の行ほど遅れてついてくる LATE、文字ごとに太さが変わる MOODY、帯ごとにねじる TWIST、モザイクにする LOW RES、蛇腹に折る FOLD、破片に割って組み戻す SHATTER である。
組版は HTML と CSS に任せた。書体は Roboto の可変フォントで、幅の軸を 75% まで詰め、太さは 100 から 900 まで使っている。MOODY の場面では、同じ語を 9 段階の太さで組んでおき、文字ごとにどの太さの版から切り出すかを切り替える。文字の幅は最も太い版に揃えてあるので、太さが変わっても文字の位置は動かない。
canvas 側の仕事は、組んだ文字を切って並べ直すことだけだ。ELASTIC では語を 180 本の横帯に切り、帯ごとに高さを変えて積み直している。四隅の時刻と場面名も HTML の要素で、毎フレーム中身を書き換えながら drawElementImage で描いている。
作ってみてわかったこと
このデモでは、帯ごとに drawElementImage を呼ぶと動きが重くなった。そこで、組んだ文字を最初に一度だけ別の canvas に描いておき、帯の切り出しには drawImage を使った。一度画像に写してから切り出しているので、このデモの画面上の文字は選択できない。
getImageData で画素を読み出す方法も、このデモでは重かった。そこで、文字のインクがある範囲は、画素を調べる代わりに measureText の計測値から求めた。
drawElementImage で描くときは、幅と高さを明示した。省略すると既定の大きさで描かれるが、その大きさが環境によって変わったからだ。
Flash と比べて
似ているのは、文字と描画が同じ空間に入ることだ。Flash では、テキストフィールドも図形も画像も、同じステージの上に並ぶ部品だった。文字を図形の奥に置くことも、回転や拡大をかけることも、ぼかしなどのフィルターをかけることもでき、それでいて文字は選択も入力もできた。HTML-in-Canvas によって、Web でもようやく、HTML の文字や UI を canvas の描画と重ね順まで混ぜて扱えるようになる。ENTER_FRAME で毎フレームの描画を回したように、paint イベントと requestPaint で回す点も近い。
違うのは、文字が HTML のまま描かれることだ。Flash の文字は Flash Player の中だけで扱われた。Flash にも読み上げの仕組みはあったが、対応する環境は限られていた。HTML-in-Canvas で canvas に描いた要素は DOM に残り、選択、翻訳、ページ内検索、読み上げの対象になる。組版にも、CSS の可変フォントや字間の指定をそのまま使える。もう一つの違いは、プラグインではなくブラウザ自体の機能だという点だ。
今回は、HTML と CSS で組んだ文字を canvas に写し、そこから帯や破片を切り出して動かした。Flash の頃によく作られていた文字の動きを、Web の組版を使って作れたのが面白かった。