DOM生成图片原理
工作原理
使用svg
的一个特性,允许在<foreignobject>
标签中包含任意的html
内容。(主要是 XMLSerializer | MDN这个api
将dom
转为svg
) 所以,为了渲染那个dom
节点,你需要采取以下步骤:
- 递归
clone
原始的dom
节点 - 获取 节点以及子节点 上的
computed style
,并将这些样式添加进新建的style标签中(不要忘记了clone 伪元素的样式) - 嵌入网页字体
- 找到所有的
@font-face
- 解析URL资源,并下载对应的资源
- base64编码和内联资源 作为
data:
URLS引用 - 把上面处理完的
css rules
全部都放进<style>
中,并把标签加入到clone的节点中去
- 内嵌图片(都转成dataUrl)
- 内联图片src 的url 进
<img>元素
- 背景图片 使用 background css 属性,类似fonts的使用方式
- 序列化 clone 的 dom 节点 为
svg
- 将xml包装到
<foreignobject>
标签中,放入svg
中,然后将其作为data: url
- 将png内容或原始数据作为
uint8array
获取,使用svg作为源创建一个img
标签,并将其渲染到新创建的canvas
上,然后把canvas
转为base64
- 完成
1. 复制 DOM 并序列化
const cloneNode = document.body.cloneNode(true);
const xmlSerializer = new XMLSerializer();
const html = xmlSerializer.serializeToString(cloneNode);
2. 嵌入 svg foreignObject
const svg = `
<svg
xmlns='http://www.w3.org/2000/svg'
width='${document.body.clientWidth}'
height='${document.body.clientHeight}'
>
<foreignObject
x='0'
y='0'
width='100%'
height='100%'
>
${html}
</foreignObject>
</svg>
`;
3.通过 canvas 生成图片
const canvas = document.createElement('canvas');
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0);
canvas.toBlob((blob) => {
const blobURL = URL.createObjectURL(blob);
window.open(blobURL);
URL.revokeObjectURL(blobURL);
});
};
img.src = `data:image/svg+xml;charset=utf-8,${svg}`;
问题:图片内容无样式
svg 以字符串的形式通过 img src data 加载,不与当前页面共享样式。
市面上的截图插件 html2canvas、dom-to-image 都是通过内联样式的方式解决此问题。 深度遍历每个源 DOM 元素,每个 DOM 元素通过 window.getComputedStyle
方法当前元素的所有样式属性和值。 找到相应的克隆 DOM 元素,通过 getPropertyValue
方法和 setProperty
方法重新赋值。
问题:图片内容字体丢失
跟图片内容样式丢失原因一样,字体也需要嵌入 svg。 因为不与当前页面共享资源,字体资源引用不能使用 URL 形式,需要转换成 base64 格式。
const response = await fetch(url, { headers: { responseType: 'blob' } });
const blob = await response.blob();
const fileReader = new FileReader();
fileReader.onload = () => {
const b64 = fileReader.result;
};
fileReader.readAsDataURL(blob);
svgStyle 添加字体声明。
svgStyle += `
@font-face {
font-family: "${name}";
src: local("${name}"), url("${b64}");
}
`;
参考链接
- CSSStyleDeclaration.setProperty() - Web API 接口 | MDN
- dom-to-image
- XML DOM - XMLSerializer 对象