JavaScriptでページ内の一部を画像に変換する(スクショする)方法がないか検討していた際に見かけた、html2canvasというライブラリを試してみます。
サンプルコード
html2canvasはHTMLを画像ではなくcanvasに変換するライブラリなので、まずはcanvasへの変換を試してみます。
デモ用のHTMLを用意します。
<header class="header">header</header>
<div class="contents convert-html">
<h1>コンテンツ内容</h1>
<figure>
<img src="../img.jpg" alt="サンプル画像">
<figcaption>サンプル画像の説明</figcaption>
</figure>
<table>
<thead>
<tr>
<th>見出し1</th>
〜 略 〜
<td>項目C-3</td>
</tr>
</tbody>
</table>
<button class="convert-btn">変換</button>
</div>
<footer class="footer">footer</footer>
今回のデモのうちヘッダーとフッター部分は変換対象外で、convert-html のclassのついたコンテンツ要素を変換する想定です。
html2canvasのライブラリをページ内で読み込み、変換ボタンをクリックした際に変換処理を実行するようにします。
// 画像化する要素
const convertHtml = document.querySelector('.convert-html');
// 変換ボタン
const convertBtn = document.querySelector('.convert-btn');
convertBtn.addEventListener('click', function() {
// 変換対象要素をcanvasに変換
html2canvas(convertHtml).then(canvas => {
// body内末尾にcanvasを追加
document.body.appendChild(canvas);
});
});
変換ボタンをクリックすると、フッターの下に変換されたcanvasが追加されました。
canvasに変換するデモページ
次にcanvasを画像に変換する処理を追加してみます。
// 画像化する要素
const convertHtml = document.querySelector('.convert-html');
// 変換ボタン
const convertBtn = document.querySelector('.convert-btn');
convertBtn.addEventListener('click', function() {
// 変換対象要素をcanvasに変換
html2canvas(convertHtml).then(canvas => {
// canvasを画像に変換
const data = canvas.toDataURL();
// body内末尾に画像を追加
const img = document.createElement('img');
img.src = data;
document.body.appendChild(img);
});
});
10行目がcanvasの内容を画像に変換する処理になりますが、この変換処理は以前に記事を投稿していますので、詳しくはそちらをご確認ください。
画像に変換するデモページ
変換対象要素内にある特定の要素を描画から除外したい場合、その要素に data-html2canvas-ignore 属性を付与することで除外されます。
例として、変換ボタンを除外するようにしてみます。
<header class="header">header</header>
<div class="contents convert-html">
<h1>コンテンツ内容</h1>
<figure>
<img src="../img.jpg" alt="サンプル画像">
<figcaption>サンプル画像の説明</figcaption>
</figure>
<table>
<thead>
<tr>
<th>見出し1</th>
〜 略 〜
<td>項目C-3</td>
</tr>
</tbody>
</table>
<button class="convert-btn" data-html2canvas-ignore>変換</button>
</div>
<footer class="footer">footer</footer>
これでcanvas変換時に該当のボタンは除外されるようになりました。
特定の要素を変換対象から除外するデモページ
これで元々やりたかった実装はできましたが、html2canvasのライブラリを使用する上でいくつか注意する点があるのでその点も試してみます。
詳しくは公式ドキュメントのAboutに記載があります。
まず1つ目は、ページ内で使用する画像は同じドメインの画像である必要がある点です。
例として、前述のデモ内の画像を別ドメインの画像に変更して、canvasに変換するようにしてみます。
// 画像化する要素
const convertHtml = document.querySelector('.convert-html');
// 変換ボタン
const convertBtn = document.querySelector('.convert-btn');
convertBtn.addEventListener('click', function() {
// 変換対象要素をcanvasに変換
html2canvas(convertHtml).then(canvas => {
// body内末尾にcanvasを追加
document.body.appendChild(canvas);
});
});
この場合、画像部分がうまく表示されませんでした。
別ドメインの画像を変換対象に含んだ場合のデモページ
これはライブラリというよりはcanvas自体の仕様で、別ドメインの画像をcanvas上に描画して汚染されないようにしているためです。
ライブラリのオプションでallowTaintという項目が用意されていて、この値をtrue(デフォルトはfalse)にすると、canvasに描画されるようになります。
// 画像化する要素
const convertHtml = document.querySelector('.convert-html');
// 変換ボタン
const convertBtn = document.querySelector('.convert-btn');
convertBtn.addEventListener('click', function() {
// 変換対象要素をcanvasに変換
html2canvas(convertHtml, {
allowTaint: true
}).then(canvas => {
// body内末尾にcanvasを追加
document.body.appendChild(canvas);
});
});
ただcanvasが汚染されてしまうため、canvasから画像に変換しようとするとエラーになります。
// 画像化する要素
const convertHtml = document.querySelector('.convert-html');
// 変換ボタン
const convertBtn = document.querySelector('.convert-btn');
convertBtn.addEventListener('click', function() {
// 変換対象要素をcanvasに変換
html2canvas(convertHtml, {
allowTaint: true
}).then(canvas => {
// canvasを画像に変換
const data = canvas.toDataURL(); // DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
// body内末尾に画像を追加
const img = document.createElement('img');
img.src = data;
document.body.appendChild(img);
});
});
2つ目はライブラリで機能しないCSSプロパティがある点です。
このライブラリの仕組みとしては、実際にブラウザで表示されている内容をスクショしているわけではなく、DOMなどのページ情報を解析した上でcanvasでの構築を行っています。
そのため、ライブラリ側で理解できるCSSプロパティは正しくレンダリングされますが、一部機能しないプロパティもあるようです。
公式のドキュメント内でサポートされているプロパティがまとめられているので、詳しくはそちらをご確認ください。
例として、サポートされていないobject-fit を使ってみます。
img {
width: 300px;
height: 400px;
object-fit: cover;
}
canvasへの変換を試してみると、確かにうまく描画されないようでした。
サポートされていないCSSプロパティを使用するデモページ
コメントが承認されるまで時間がかかります。