Better

Ethan的博客,欢迎访问交流

前端快照方案

之前做活动时,就有需要将页面保存为图片来分享的功能,就有尝试使用 html2canvas 库,但发现效果不是很好,存在黑白和样式不一致的问题。

基本原理

前端侧对于快照的处理过程,实质上是将 DOM 节点包含的视图信息转换为图片信息的过程。这个过程完全可以借助 canvas 的原生 API 实现。

绘制阶段:选择希望绘制的 DOM 节点,根据 nodeType 调用 canvas 对象的对应 API,将目标 DOM 节点绘制到 canvas 画布(例如对于的绘制使用 drawImage 方法)。

导出阶段:通过 canvas 的 toDataURL 或 getImageData 等对外接口,最终实现画布内容的导出。

基础方案

canvas 的 drawImage 方法只接受 CanvasImageSource,而 CanvasImageSource 并不包括文本节点、普通的 div 等,将非 <img> 的元素绘制到 canvas 需要特定处理。

当有多个 DOM 元素需要绘制时,层级优先级处理较为复杂。

需要关注 float、z-index、position 等布局定位的处理。

样式合成绘制计算较为繁琐。

因此,基于对综合业务场景的考虑,我们采用社区中认可度较高的方案:html2canvas 和 canvas2image 作为实现快照功能的基础库。

存在的问题

社区中常见的问题有

  • 为什么有些内容显示不完整、残缺、白屏或黑屏?
  • 明明原页面清晰可辨,为什么生成的图片模糊如毛玻璃?
  • 将页面转换为图片的过程十分缓慢,影响后续相关操作,有什么好办法么?

内容完整性

不完整的 checklist

  • 跨域问题:存在跨域图片污染 canvas 画布。
  • 资源加载:生成快照时,相关资源还未加载完毕。
  • 滚动问题:页面中滚动元素存在偏移量,导致生成的快照顶部出现空白。

对于跨域图片

  • useCORS 配置
  • CORS 配置
  • 服务端转发

资源加载

  • Promise.all 预加载资源
  • 图片资源转 Blob:保证图片的地址来自本地,避免在快照转化时加载失败的情况。

滚动问题

  • 典型特征:生成快照的顶部存在空白区域。
  • 在调用 convertToImage 之前,先记录此时的 scrollTop,然后调用 window.scroll(0, 0) 将页面移动至顶部。待快照生成后,再调用 window.scroll(0, scrollTop) 恢复原有纵向偏移量。

清晰度优化

介绍 5 种行之有效的清晰度优化方法

  • 使用 px 单位:为了给到 html2canvas 明确的整数计算值,避免因小数舍入而导致的拉伸模糊,建议将布局中使用中使用 %、vw、vh 或 rem 等单位的元素样式,统一改为使用 px。
  • 优先使用 img 标签展示图片
  • 配置高倍的 canvas 画布:在具体操作中,创建由 devicePixelRatio 放大的图像,然后使用 css 将其缩小相同的倍数,有效地提高绘制到 canvas 中的图像清晰度表现。
  • 关闭抗锯齿:imageSmoothingEnabled 是 Canvas 2D API 用来设置图片是否平滑的属性,true表示图片平滑(默认值),false表示关闭 canvas 抗锯齿:canvas 的抗锯齿是开启的,可以通过关闭抗锯齿来实现一定程度上的图像锐化,提高线条边缘的清晰度。
  • 锐化特定元素:受到 canvas 画布放缩的启发,我们对特定的 DOM 元素也可以采用类似的优化操作,即设置待优化元素宽高设置为 2 倍或devicePixelRatio倍,然后通过 css 缩放的方式控制其展示大小不变。

转换效率

传入节点的视图信息越精简,生成快照处理的计算量就越小

  • 减少 DOM 规模,降低 html2canvas 递归遍历的计算量。
  • 压缩图片素材本身的体积,使用 tinypng 或 ImageOptim 等工具压缩素材。
  • 如果使用了自定义字体,请使用 fontmin 工具对文字进行按需裁剪,避免动辄数兆的无效资源引入。
  • 传入合适的 scale 值以缩放 canvas 画布(5.2.3节)。通常情况下 2~3 倍就已经满足一般的场景,不必要传入过大的放大倍数。
  • 在 5.1.2 节中提到的图片资源转 blob,可将图片资源本地化,避免了生成快照时 html2canvas 的二次图片加载处理,同时所生成的资源链接具备 URL 长度较短等优势。

导出优化

  • convertToPNG
  • convertToJPEG
  • convertToGIF
  • convertToBMP

通常对于没有透明度展示要求的图片素材,可以使用jpeg格式的导出。在我们的相关实践中,jpeg相比于png甚至能够节约 80% 以上的文件体积。

来源



留言