canvasにHTMLの内容を描画したい
かっこいいサイト見てたら、canvasにHTMLの内容を描画して背景なんかに引いてぐにゃぐにゃさせているようなのがあった。僕はそういうぐにゃぐにゃするようなのすごく好きなので、一体これどうやってるんだろうな、みたいな気持ちになって見てた。
たまたま、そのことをふと思い出して調べてみたら普通にMdnに載ってたので、早速試してみた。 文献これです。
記述されている通りにやると確かに動くけど、もっとがっつりやりたいよね、という気持ちになる。
早い話、document.getElementById
かなんかで要素を取得して、svgにぶち込んで、丸ごと描画させるみたいなことをやりたい気持ちになってくる。
そこで上記の文献を読むと、
SVG は valid な XML でなければならないので、埋め込む HTML も well-formed なものでなければいけません。
なるほど。参考コードが載ってるのでそれに則って一気にやってみる。
// canvasの取得とサイズ指定 const c = document.getElementById('c'); const ctx = c.getContext('2d'); const h = window.innerHeight; const w = window.innerWidth; c.width = w; c.height = h; // canvasに表示したい要素の取得 const html = document.getElementById('js-html'); /** * well-formedなやつ */ // html作成 const doc = document.implementation.createHTMLDocument(''); // そのbodyの中に取得してきたDOMを書き込む doc.write(html); // svg化するのに使うぞ doc.documentElement.setAttribute("xmlns", doc.documentElement.namespaceURI); const serializeHtml = (new XMLSerializer).serializeToString(doc);
あれ、ほんでこれどうすればいいんだ?みたいな気持ちになる。
serializeHtml
を見ると当たり前だけど、シリアライズされて文字列になったHTMLが入っている。
これはつまりこれをsvgにはめ込んでやればオッケーってことなのか?
const data = ` <svg xmlns='http://www.w3.org/2000/svg' viewBox='0, 0, ${w}, ${h}' width='${w}' height='${h}'> <foreignObject width='100%' height='100%'> ${serializeHtml} </foreignObject> </svg> `; // svgファイルに変換 const svg = new Blob([data], {type: "image/svg+xml;charset=utf-8"}); // svgを生成した後、それをimgで読みたいため、urlを生成する const DOMURL = self.URL || self.webkitURL || self; const url = DOMURL.createObjectURL(svg); const img = new Image(); img.src = url; // GO!! img.onload = () => { ctx.drawImage(img, 0, 0); // urlを破棄する DOMURL.revokeObjectURL(url); }
やってみると多分でかいcanvasがあるだけで何も表示されないと思う。
そこでChromeのdeveloperツール開いて、ネットワークパネルを見ると破滅した画像のリソースが見つかった。それを開くと
This page contains the following errors:
error on line 4 at column 11: internal error: detected an error in element content
Below is a rendering of the page up to the first error.
はぁ。
確認すると4行目には<!DOCTYPE html>
がいる。つまりこいつがダメってことか。
だけどdocument.implementation.createHTMLDocument
で生成すればそれは必ず入るので、この方法ではこの場合ダメなんじゃなかろうか。
そういうわけで。
普通に取得してきたDOMをシリアライズして<div xmlns="http://www.w3.org/1999/xhtml"></div>
で囲ってぶち込みましょう。
問題は解決です。
// canvasに表示したい要素の取得 const html = document.getElementById('js-html'); const serializeHtml = (new XMLSerializer).serializeToString(html); const data = ` <svg xmlns='http://www.w3.org/2000/svg' viewBox='0, 0, ${w}, ${h}' width='${w}' height='${h}'> <foreignObject width='100%' height='100%'> ${serializeHtml} </foreignObject> </svg> `;
はい。
スタイルどうするねん問題
<div xmlns="http://www.w3.org/1999/xhtml"></div>
のなかに<style></stye>
で書いてやれば普通にあたる。
だけど愚直に記述するのも厳しいので、cssファイルを取得してきて、打ち込むことにしましょう。
僕はaxiosを使います。以下コード全文
const c = document.getElementById('c'); const ctx = c.getContext('2d'); const h = window.innerHeight; const w = window.innerWidth; c.width = w; c.height = h; // html 取得 & SVG作成 const html = document.getElementById('js-html'); // Get well-formed markup const serializeHtml = (new XMLSerializer).serializeToString(html); axios.get('./style.css') .then(res => { const style = res.data; const data = ` <svg xmlns='http://www.w3.org/2000/svg' viewBox='0, 0, ${w}, ${h}' width='${w}' height='${h}'> <foreignObject width='100%' height='100%'> <div xmlns="http://www.w3.org/1999/xhtml"> <style> ${style} </style> ${serializeHtml} </div> </foreignObject> </svg> `; const svg = new Blob([data], {type: "image/svg+xml;charset=utf-8"}); // svgを生成した後、それをimgで読みたいため、urlを生成する const DOMURL = self.URL || self.webkitURL || self; const url = DOMURL.createObjectURL(svg); const img = new Image(); img.src = url; // GO!! img.onload = () => { ctx.drawImage(img, 0, 0); // urlを破棄する DOMURL.revokeObjectURL(url); } }) .catch(err => { console.log(err); });
結局
画像なんかはまたあれだし、便利にやってくれるhtml2canvasと言うライブラリがあるので、それを使いましょう。