如何将 DOM 生成为图片?速度打败 html2canvas、dom-to-image

·  阅读 6855
如何将 DOM 生成为图片?速度打败 html2canvas、dom-to-image

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 17 天,点击查看活动详情

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}`;
复制代码

问题:图片内容无样式

5LJlAe.gif

svg 以字符串的形式通过 img src data 加载,不与当前页面共享样式。

市面上的截图插件 html2canvasdom-to-image 都是通过内联样式的方式解决此问题。 深度遍历每个源 DOM 元素,每个 DOM 元素通过 window.getComputedStyle 方法当前元素的所有样式属性和值。 找到相应的克隆 DOM 元素,通过 getPropertyValue 方法和 setProperty 方法重新赋值。

image.png

image.png

“DOM 操作很慢”,在此也有所体现。 ​

简单算一笔账,每个 DOM 都有 300+ 个样式属性,假设每个 DOM 都有 5 个有效样式,100 个 DOM 就需要复制样式 500 次。假设每次复制耗时 0.5 毫秒,那就需要耗时 250 毫秒。 ​

在条件允许的情况下,可以提前准备好样式内容,嵌入 svg,移除这一环节,提升截图效率。

const svg = `
  <svg
    xmlns='http://www.w3.org/2000/svg'
    width='${document.body.clientWidth}'
    height='${document.body.clientHeight}'
  >
+		<style>
+			${svgStyle}
+		</style>
    <foreignObject
      x='0'
      y='0'
      width='100%'
      height='100%'
    >
      ${html}
    </foreignObject>
  </svg>
`;
复制代码

image.png

移除样式同步环节之后,速度有质的提升。

问题:图片内容字体丢失

5LYNG9.gif

跟图片内容样式丢失原因一样,字体也需要嵌入 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}");
	}
`;
复制代码

5LYsaD.gif

其它方式

puppeteer

参考


全文完,如果觉得这篇文章对你有用,欢迎 点赞 👍、评论 ✍️、收藏 👀

分类:
前端
收藏成功!
已添加到「」, 点击更改