無料公開中公開翌日4時まで

HTMLを描画するHTML-in-Canvas API

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

(640号)

目次
シェア

はじめに

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

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

新提案された、Canvas内にHTMLを描画するデモのキャプチャ
スクリーンショット:新たに提案されたHTML-in-Canvasを用いて、HTML要素をCanvas内に描画したデモの表示例。図では、open -a "Google Chrome" コマンドに --enable-blink-features=CanvasDrawElement オプションを付加して、http://localhost:8000/ 上で実行している。ブラウザ上には、斜めに回転されたHTMLの内容(テキスト、画像、SVG、絵文字など)がCanvas内に描画されており、青枠でCanvasの領域が示されている。大きな画像

現状のCanvasの制限

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

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

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

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

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

Canvas2Dでのテキスト描画デモ

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

Canvas2Dでのテキスト描画デモのキャプチャ
スクリーンショット:Canvas 2D APIを用いてテキストを描画したデモの表示例。ブラウザには localhost:8000/1.html を開いた状態が示されており、青枠で囲まれたCanvas領域内に、赤色で「すごく長いテキスト、折り返しはされないし、事前に命令したスタイルで一括描画され…」という文章が横一列で描画されている。テキストは折り返されず、Canvasの仕様によりスタイル情報とともに静的に描画されている。大きな画像

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への描画は高性能で、上記のようなユースケースに対応できますが、filterやbackdrop-filter、clip-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>要素を使ってHTML/CSSをCanvasに描画するデモ

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

<foreignObject>要素を使ってHTML/CSSをCanvasに描画するデモのキャプチャ
スクリーンショット:HTML/CSSのレイアウトを<foreignObject>要素によりCanvasへ転写したデモの表示例。上部には、折り返し可能な長文テキスト、Google Fontsによる文字、絵文字、SVG、Flexboxによる整列ボックスが表示されている。下部の青枠内は、Canvasに描画された結果を示しており、レイアウト全体が斜めに回転した状態で再現されている。文字や絵文字は正しく描画されているが、画像部分(特にウェブフォントや <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座標、width、heightです。

なお、drawElement()に渡す要素は、canvas要素に内包しておく必要があります。また、canvas要素には、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()で、HTML/CSSをCanvasに描画する(フラグが必要)

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

drawElement()で、HTML/CSSをCanvasに描画するデモのキャプチャ
スクリーンショット:drawElement()を用いて、HTML/CSSのレイアウトをそのままCanvas内に描画したデモの表示例。描画結果では、長いテキストが自動で折り返され、Google Fontsが適用されたフォントで表示されているほか、絵文字、SVG、画像も正しく描画されている。Flexboxで整列された要素(「flex」「レイアウトの」「テスト」など)も忠実に再現され、全体が斜めに回転した状態で青枠内のCanvasに描画されている。上部には --enable-blink-features=CanvasDrawElement フラグ付きでChromeを起動するためのコマンドが表示されている。大きな画像

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 );

WebGLでのHTML/CSS描画デモ(動画)

WebGLでのHTML/CSS描画デモ(フラグが必要)

過去に検討された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()

この記事の内容についての意見・感想を送る
CodeGridのDiscordで知識を深めよう。参加する。

人気のシリーズ