Loading

html2canvas 解决截图空白问题

业务场景介绍

 

 

点击浏览器右上角已安装的chrome插件图标,这个时候会出现一个界面,我们称这个界面为popup,界面上有个"从页面获取产品信息"按钮,单机它会对当前标签页面内容进行截图,最后将截图的图片转成base64发送至xx接口

部分核心代码解读:截取当前可视区域的图片,为了能够截图足够多信息,图片向上衍生300px,向下衍生300px,那图片高度= 300 + 可视区域 + 300

const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const threshold = 300;
const scrollY = window.scrollY;
const y = scrollY > threshold ? scrollY - threshold : 0;
const h =
    scrollY > threshold
        ? viewportHeight + threshold * 2
        : viewportHeight + threshold + scrollY;
window
    .html2canvas(document.querySelector('body'), {
        width: viewportWidth,
        height: h,
        x: window.scrollX,
        y: y,
        scrollX: 0,
        scrollY: 0,
        useCORS: true // 如果有跨域图片
    })

抛出问题

问题1. 经过测试,概率上百分之99%的页面都能抓取,部分页面不能抓,抓取效果如下图(页面出现了大片空白白板),如:http://www.sinochemheb.com/s/18959-55112-274763.html

 

解决思路

简单分析一波:调试模式查看页面元素没有什么特殊DOM,也没什么iframe嵌套

猜想1

可能是因为html2canvas插件问题,让gpt给出可平替方案

dom-to-image
这个库也可以将 DOM 元素渲染成图像,支持各种格式(如 PNG、JPEG 等)。它支持的特性包括:背景透明、SVG 图像支持、渐变和阴影等。

官网:https://github.com/tsayen/dom-to-image

canvas2image
canvas2image 是一个将 <canvas> 转换为图像的库。它比较轻量,简单易用,支持将 <canvas> 输出为图片(PNG、JPEG 或 BMP 格式)。

官网:https://github.com/hongru/canvas2image

rasterizeHTML.js
这个库支持将 HTML 页面渲染成图像。它能够解析 HTML 中的样式并将它们转换为一个 Canvas 图像。与 html2canvas 类似,它支持从 DOM 生成图像。

官网:https://github.com/cburgmer/rasterizeHTML.js

puppeteer
puppeteer 是一个更为强大的库,它是一个 Node.js 库,提供了一个高层次的 API 来控制 Chrome 或 Chromium 浏览器。可以用它来截图整个网页或页面的特定部分,并能处理更复杂的渲染问题,支持更复杂的 CSS 和 JavaScript 执行。

官网:https://github.com/puppeteer/puppeteer

html-to-image
这是一个简单的 JavaScript 库,用于将 HTML 元素转换为图像。它基于 canvas,并支持各种格式导出,如 PNG 和 JPEG。与 html2canvas 相比,它的 API 更简洁易用。

尝试性使用:dom-to-image

截图空白区域问题得到了解决,但抛出了更大的问题,百分之90%以上的站点都截图失败

尝试性使用:rasterizeHTML.js

很多UI样式丢失,截出来的图没发看

继续思考

最直接的方法没有效果,那就回归初始状态,研究html2canvas插件原理

为啥选择html2canvas呢?

第一:它截图出来的效果更接近与预期

第二:github的star数更最多,前人栽树后人乘凉的原理,应该没错吧

于是打开 https://github.com/niklasvh/html2canvas,翻了几页查看issues,没有找到自己想要的答案,接着看插件原理

插件底层原理

html2canvas 是一个将 HTML 页面内容转换为画布(Canvas)图像的 JavaScript 库。它通过遍历 HTML 元素,解析元素的样式、字体、颜色等信息,并通过调用 Canvas API 来绘制图像。底层原理包括以下步骤:

  1. 遍历 DOM 树html2canvas 会递归遍历 DOM 元素,分析每个元素的计算样式。
  2. 计算布局:计算元素的尺寸、位置,处理复杂的布局(如定位、浮动等)。
  3. 渲染样式:获取元素的背景色、边框、字体等信息,并转化为画布可识别的格式。
  4. 处理图像和媒体:对于图像、视频等元素,html2canvas 会加载并绘制到画布上,可能会通过跨域处理(CORS)来获取外部资源。
  5. 绘制到 Canvas:最终通过 Canvas API 渲染所有计算后的元素,生成最终图像

看到这底层原理,直呼牛逼,原来是将html转成canvas渲染,最终形成图片,那么就接着再去看下要截图的网页,分析下除了没有特殊的iframe之外还有没有特殊的内容,刷了几次页面之后,突然发现点蛛丝马迹

这网站有个从底部向上上升的一个过度效果,看了下元素

 于是大胆猜测,插件在执行截图时,将html的计算样式1:1的绘制到了canvas上,而开始时,这些带动画效果的属性是不可见的,只有在出现到可视区域才会触发显示,而canvas显然是没有这个逻辑,于是我在截图之前把这些属性都给去掉或者修改元素属性改为可见,ok,真相了,完美截图了,达到了预期效果

最终的解决方案

导致部分网站截图失败原因是因为html2canvas在往canvas绘制内容时,默认隐藏的内容也会被绘制到canvas上,导致截图出来是空白的,解决方案就是在截图之前把这些隐藏的元素给展现出来,动画的过滤效果去掉,元素显示通用代码如下

const node = document.createTreeWalker(
  window.document.body,
  NodeFilter.SHOW_ELEMENT,
  {
    acceptNode: function (node: HTMLElement) {
      // 如果节点是 div 元素,则接受它
      if (node?.tagName === 'DIV') {
        return NodeFilter.FILTER_ACCEPT;
      }
      return NodeFilter.FILTER_SKIP; // 如果不是 div,则跳过
    }
  }
);
let nextNode;
while ((nextNode = node.nextNode() as HTMLDivElement)) {
  const tagName = nextNode?.parentElement?.tagName;
  if (tagName !== 'SCRIPT' && tagName !== 'STYLE' && tagName !== 'CODE') {
    nextNode.style.opacity = '1';
    nextNode.style.visibility = 'visible';
    nextNode.style.animationDuration = '0s';
    nextNode.style.animationDelay = '0s';
  }
}

后续思考

除了使用document.createTreeWalker API 之外,动态往页面中插入css样式强制让div显示去除动画效果应该也是可行的

posted @ 2024-12-02 14:18  冯叶青  阅读(218)  评论(0编辑  收藏  举报