Canvasの最新情報2025 HTMLを描画するHTML-in-Canvas API

Canvas2DとWebGLの機能の一部として、HTML/CSSを直接描画するための新しいAPIが提案されています。これらのAPIは、HTML要素をCanvasに直接描画するためのもので、柔軟なレイアウトとテキスト処理が可能になります。

発行

著者 小山田 晃浩 フロントエンド・エンジニア
Canvasの最新情報2025 シリーズの記事一覧

はじめに

2025年春、Chromium系ブラウザに「HTML-in-Canvas」と題した新提案があり、CanvasにHTML/CSSを直接描画するための新しいAPIのテスト実装が始まりました。Google Chrome 138以降でフラグを設定すると、この新しいAPIを試すことができます。

本稿では、HTMLをCanvasに描画することの意義と、提案されているAPIの概要について解説します。下のキャプチャのようなことが可能になるわけですが、詳細はこれから解説していきます。

現状のCanvasの制限

まずは現状のCanvasの制限についておさらいしましょう。HTML5の<canvas>やWebGLは、2D/3D描画の自由度を与えてくれる反面、「テキストや複雑なレイアウトを描き込みたい」という場面では長らく制約がありました。

Canvas2Dには、プリミティブな図形や1行テキストを描画するためのAPIはあります。ただし、描画時にはx座標、y座標の指定が必要で、これはCSSでいうところのposition: absoluteのような絶対位置指定しかできません。Canvas2D内ではHTML/CSSのように気軽なレイアウトができないのです。

テキストについては、1行で描画することはできますが、行内に複数のスタイルを混ぜることもできません。つまり、特定の単語だけ太字にするとか、色を変えるといったことが簡単にできないわけです。また、テキストボックスのような範囲に応じた自動折り返しはできません。

たとえば、次のデモでは、x: 50y: 100の位置にテキストが描画されます。

Canvas2Dでのテキスト描画

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.font = '16px Arial';
ctx.fillStyle = '#ff0000';
ctx.fillText( 'すごく長いテキスト。折り返しはされないし、事前に命令したスタイルで一括描画されるので、テキストスタイルの混在はできません', 50, 100 );

fillText()の第二引数(x位置)、第三引数(y位置)で指定した、Canvas内の左から50px、上から100pxの位置を起点に、テキストが描画されます。

CanvasのAPIの制限を知ると、HTML/CSSのレイアウトがどれだけ強力なのかがわかりますね。

がんばれば、Canvas2Dでテキストの折り返しを実現できるが……

テキスト折り返しのよくある手法としては、描画前にテキストの長さを計測しながら、描画位置を調整するというやり方があります。

しかし、これは、なかなか手間がかかります。whileでテキストボックスに収まるまで文字を削って……を行ごとに繰り返して、なんとか折り返しを実現するわけです。たとえば、描画された場合の文字の大きさを事前算出するには、次のようにします。

Canvas2Dでのテキストの長さ計測

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.font = '16px Arial';
ctx.fillStyle = '#ff0000';
const text = 'すごく長いテキスト。折り返しはされないし、事前に命令したスタイルで一括描画されるので、テキストスタイルの混在はできません';
const metrics = ctx.measureText( text );
console.log( metrics.width );

これまでの、HTML/CSSをエミュレートして描画する方法

Canvasの中にHTML/CSSをそのまま描画できれば、レイアウトの弱さとテキストの扱いが大幅に改善されます。そして、さまざまなユースケースに対応できるようになります。

たとえば、次のような利用が考えられるでしょう。

  • グラフやチャートの説明文や注釈
  • 画像内に自由に文字入れができるユーザー生成コンテンツ
  • JavaScriptだけで画面キャプチャ
  • ゲームのUI
  • WebページをVRの複眼で使うために、左目用/右目用の2つの画面を描画
  • CSS Shaderの再来

こうした需要を満たすため、HTML/CSSをエミュレートしてCanvasに描画するサードパーティライブラリであるhtml2canvasが存在します。

html2canvasによるHTML/CSSのCanvasへの描画は高性能で、上記のようなユースケースに対応できますが、filterbackdrop-filterclip-pathなどの一部のCSSプロパティはサポートされていません。また、レイアウトや塗りはライブラリがJavaScriptで独自に計算しており、これにはある程度の負荷がかかります。実際、描画は非同期で実行され、その結果がPromiseで返されます。

一方、ネイティブの機能だけで、HTML/CSSのCanvas描画するハックもあります。SVGには、<foreignObject>要素があり、この中にはHTMLを展開することができます。SVG自体は画像ですから、Canvasに描画することもできます。つまり、HTMLを<foreignObject>でSVGの一部にしてしまい、そのSVGをCanvasにdrawImage()で描画してしまえばいいわけです。

ただ、この方法にも制限があります。<foreignObject>でHTMLとCSS自体を持ち込めても、それ以外のリソース(画像やWebフォントなど)は、SVGの外部なので、CanvasでdrawImage()を実行した際は、外部リソースが読み込めていない状態での描画になります。

デモでは、上の囲みで表示されている内容を、<foreignObject>に入れてCanvasに転写しています。下側のCanvasでの描画結果を確認してみると、文字の折り返しやFlexboxを用いたHTML/CSSのレイアウトなどはうまく描画できているものの、Google Fontsによるウェブフォントや<img>による画像はうまく描画されていません。

このように、完璧なソリューションは存在していなかったのが現状です。

CanvasにHTML/CSSを描画するdrawElement()

冒頭で述べたように、Canvas2DとWebGLの機能の一部として、HTML/CSSを直接描画するための新しいAPIが提案されています。これらのAPIは、HTML要素をCanvasに直接描画するためのもので、CSSスタイルやレイアウトをそのまま反映できます。

Canvas2DにはdrawElement()というAPIで、たとえば以下のようにしてHTML要素をCanvasに描画することができます。

Canvas2DでのdrawElementを利用したHTML要素描画

const ctx = document.getElementById( "canvas" ).getContext( "2d" );
const el = document.getElementById( "draw-element" );
const { width, height } = el.getBoundingClientRect();
ctx.drawElement( el, 30, 0, width, height );

昔からあるCanvas2DのAPIのdrawImage()を実行するのと同じ要領で、drawElement()というメソッドの第一引数にDOM要素を渡します。第二引数以降は、描画時のx座標、y座標、widthheightです。

なお、drawElement()に渡す要素は、canvas要素に内包しておく必要があります。また、canvas要素には、layoutsubtree="true"属性を必須で明示しておく必要があります。canvas要素の外側の要素は描画対象にはできません。

layoutsubtree属性の意味

通常、canvas要素にDOMを内包した場合、Canvas内のDOMはブラウザにはレンダリングされず、canvas要素のみが表示されます。そのため、Canvas内のDOMはレイアウト情報も持ちません。layoutsubtree="true"を明示することで、「Canvas内の要素だけれど、レイアウト情報は計算してください」とブラウザに指示するわけです。

drawElement()で、HTML/CSSをCanvasに描画するコード例

<canvas
  id="canvas"
  width="512"
  height="256"
  layoutsubtree="true"
>
  <div id="draw-element">
    長いテキストは自動で折り返すでしょうか?Google Fonts は効いていますか?<br />
    emojiも😊❤️👀<br />
    ...省略
  </div>
</canvas>

現段階では、フラグの有効化が必要です。そのためには、ターミナルに以下を入力してGoogle Chrome起動します。

Chromeのフラグの有効化コマンド

open -a "Google Chrome" --args \
  --enable-blink-features=CanvasDrawElement \
  http://localhost:8000/

drawElement()を利用すると、Google Fontsや画像の読み込み、SVG画像、文字の折り返しやFlexboxを用いたHTML/CSSのレイアウトなどが適用された状態でCanvas内に描画できています。

WebGLにHTML/CSSを描画するtexElement2D()

WebGLには、HTML/CSSをそのままテクスチャとして描画するためのtexElement2D()というAPIが提案されています。

texElement2D()は、HTMLをWebGLの中に持ってきてGLSLを適用できるようになり、結果的に、Canvasを介してHTML/CSSの描画結果にGLSLを適用できるようになります。過去に標準化が検討された、CSS Shaderが実現したかった表現と似たことができるようになるわけです(CSS Shaderについては節末のコラムを参照してください)。

WebGLで利用する場合も、テクスチャとして適用する要素はlayoutsubtree="true"を明示したcanvas要素内に内包しておく必要があります。

WebGLでのtexElement2Dを利用したHTML要素描画

const el = document.getElementById( 'draw-element' );
const texture = gl.createTexture();
gl.bindTexture( gl.TEXTURE_2D, texture );
gl.texElement2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, el );

過去に検討されたCSS Shaderの表現

texElement2D()は、CSS Shaderが過去に実現したかった表現と似たことができるようになると述べましたが、これは2012年頃の、CSSにGLSLによるシェーダーを読み込めるようにするという草案のことを指しています。結局、草案のまま正式なCSSの仕様に取り込まれることはありませんでした。

vsは頂点シェーダー、fsはフラグメントシェーダーのソースコードファイルで、それらを読み込んでfilterに適用してしまおうという機能でした。

CSSにGLSLによるシェーダーを読み込む(過去の草案であり、現在のCSSでは不正)

.shaded {
  filter: custom(
    url(distort.vs) url(tint.fs),
    distortAmount 0.5, lightVector 1.0 1.0 0.0,
    disp texture(disp.png)
  );
}

まとめ

現在は、これらのAPIはまだ実験的な段階であり、このまま全ブラウザで使えるようになるかは未定ですが、将来的にはHTML/CSSをCanvasに描画するための標準的な方法となることが期待できます。

アクセシビリティについては、現時点で機能として実装されていないようですが、drawElement()の登場により、canvas要素のアクセシビリティがよくなるかもしれません。 これまでは、canvas内に文字を入れる場合、fillText()の描画命令で意味づけされていない文字列を描画していました。drawElement()があれば、HTMLでマークアップされた文字列をcanvas内に描画できることになります。この内容はフォールバックにも使えることが期待できます。

まだChromium系ブラウザ限定ですが、すでに使える状態ではあるので、デジタルサイネージなど、環境を限定できるプロジェクトでは利用してみる価値はありそうです。

余談:FirefoxのCSSの要素を背景画像に適用する機能

そういえば、ついでの余談ですが、Firefoxは大昔、任意のHTML要素をCSSのbackground-imageに適用できる機能を持っていたな、と思い出しました。この機能は今(2025年7月現在、バージョン140.0.4)でもFirefoxで有効でした。

参考: elements()