HTMLのDOMをそのまま画像化したいと思ったことはありませんか?
ネット上で色々な情報がありますが、いわゆる「そのままの状態でスクショしたい」に答えるのは意外と難しいです。
主な選択肢と特徴
- html2canvas
- ユーザーにブラウザ上でダウンロードしてもらいたい場合におすすめです。ios, safariでも使えます
- dom-to-image
- ユーザーが特定のブラウザのみでダウンロードできればいい時におすすめです。safariでは正常に動かないことがあります
- スクレイピングによる方法
- ユーザーがダウンロードしなくても良く自分で特定のサイトからダウンロードする場合におすすめです
- ブラウザの開発者ツール
- 要素単位でのダウンロードが実装なしでできます
- ブラウザの拡張機能
- 拡張機能によりますが、ページ全体を綺麗にダウンロードするのには向いています
何にどの選択肢を使ったか
オリジナルのカードを作れるシステムで、ユーザーが入力したテキストや画像を元にカード画像を作る時に利用しました。
ios, safari では html2canvas, それ以外では dom-to-image を採用しました。
以下が主な理由です。
- dom-to-image では ios, safari で正常に動かない
- html2canvas では 縦書き(
writing-mode: vertical-rl
)が使えない
それぞれの詳細
html2canvas
html2canvas は、その名の通り、HTMLの要素をHTML canvas要素に変換するライブラリです。
Usage(GitHubより)
html2canvas(document.body).then(function(canvas) {
document.body.appendChild(canvas);
});
append せずに HTMLCanvasElement.toDataURLを使って
HTMLCanvasElement.toDataURL("image/png")
のようにすればpngなどの画像でダウンロードできます。
特徴
- HTMLやCSSでの描画を
CanvasRenderingContext2D
で自力で1から再現したライブラリです- GitHubのコードを見ると、domをコピーして子要素をスタックして、下の方に描画される要素から1つ1つcanvasに描画しているのがわかります
- 環境の影響をあまり受けず、safariやios端末でもほぼ同じ動作をします
-
writing-mode
など、再現されていないプロパティがあります(今後の拡大に期待)- サポートされているプロパティはdocs/features.mdを参照
- 同一ドメインの要素しか描画できません(外部の画像などは描画できないので注意)
dom-to-image
dom-to-image は、domを指定した拡張子の画像に変換するライブラリです。
Usage(GitHubより)
var node = document.getElementById('my-node');
domtoimage.toPng(node)
.then(function (dataUrl) {
var img = new Image();
img.src = dataUrl;
document.body.appendChild(img);
})
.catch(function (error) {
console.error('oops, something went wrong!', error);
});
特徴
- DOMを
XMLSerializer().serializeToString()
(MDNでの説明)でSVGにして、そこからCanvas
などを経て様々な画像に変換するライブラリです- 以下の「実装の主要部分」を参照
-
XMLSerializer
に依存し、ios, safariではtext-shadow
がうまく再現できなかったり、画像がうまく描画されない時があったりと不安定でした - 同一ドメインの要素しか描画できません(外部の画像などは描画できないので注意)
function makeSvgDataUri(node, width, height) {
return Promise.resolve(node)
.then(function (node) {
node.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
return new XMLSerializer().serializeToString(node);
})
.then(util.escapeXhtml)
.then(function (xhtml) {
return '<foreignObject x="0" y="0" width="100%" height="100%">' + xhtml + '</foreignObject>';
})
.then(function (foreignObject) {
return '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="' + height + '">' +
foreignObject + '</svg>';
})
.then(function (svg) {
return 'data:image/svg+xml;charset=utf-8,' + svg;
});
}
スクレイピングによる方法
Puppeter や Selenium などのスクレイピングツールでは、スクリーンショットを撮ることができます。
例えば Puppeter では以下のメソッドが使えるので、
このように実装できます。
const puppeteer = require('puppeteer');
const main = async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://qiita.com/zuisho-1848');
await page.screenshot({ path: 'example.png' });
await browser.close();
}
main();
あまり使っていないので解説は他の記事に任せます。
例えば、以下の記事など参考になるかもしれません。
定期的にサイトを自動で訪れてスクショを撮りたいなど、人間が見ずにスクショを撮るのに便利だと思います。
(もしかしたら、Google の PageSpeed Insights で表示されるスクショはこれで撮ってるのかもしれないと思っています)
ブラウザの開発者ツール
開発者ツールの Element
から画像化したいノードをサブクリックし、 Caputure node screenshot
を押すとpngで保存できます。
safariなどでも撮影できるようです。
以下の記事を引用しておきます。
レスポンシブの画面にして、右上の下矢印マークからも撮れます。(Caputure full size screenshot
だとページ全体を撮れます)
特徴
- 外部のライブラリに頼らなくて良いです
- 非エンジニアでも実装せずにパッと使えます
- 再現度はあまり試せていないのでなんとも言えません
- スマホからはできないので、一般のユーザーにやってもらうのは難しいです
ブラウザの拡張機能
ページ全体の画像を綺麗に撮りたい時に役立つ拡張機能があります。
使ったことがあるのは、以下のChromeの拡張機能です。
スクショというよりも、ページ全体を印刷しなければいけない時に、cssなどを綺麗に反映したい時に使いました(通常のページの印刷だとレスポンシブなどがひどいことになるので。。)
他にもあると思うので、探してみてください。
まとめ
HTML DOM の画像化は意外とシンプルにはいかないようで、ライブラリも結構強引にやっているものばかりです。
いつか、ブラウザのAPIなどで対応するといいなと思っています。
( HTMLElement.toImage()
みたいなメソッドがあればいいのに。)
ブラウザ開発者からしたら難しいよって話なんですかね?
「レンダリングする代わりにその結果を画像化する」みたいな。
専門外なのでこの程度にしておきます。
ちょっと宣伝
Hi!story【ハイスト】チーム(株式会社Highsto)ではメンバーを募集しています。
もし興味があったら覗いてみてください!
Xのフォローもよろしくです!