整理的前端截图的方案

海龙2023-03-15编程JS/TS

HTML2canvas

该脚本通过读取 DOM 以及应用于元素的不同样式,将当前页面呈现为 canvas 图像。

它不需要来自服务器的任何渲染,因为整个图像是在客户端上创建的。但是,由于它太依赖于浏览器,因此该库不适合在 nodejs 中使用。它也不会神奇地规避任何浏览器内容策略限制,因此呈现跨域内容将需要代理来将内容提供给相同的源。

该脚本仍然处理非常实验状态,因此不建议在生产环境中使用它,也不建议使用它来构建应用程序,因为仍然会有重大更改。

该库应该可以在以下浏览器上正常工作

  • Firefox 3.5+
  • Google Chrome
  • Opera 12+
  • IE9+
  • Edge
  • Safari 6+

由于需要手动构建每一个 CSS 属性以支持,因此还有许多尚不支持的属性。

使用方法

// 安装
npm install html2canvas
// 引入
import html2canvas from 'html2canvas'
// 使用
html2canvas(document.body).then(function(canvas) {
  document.body.appnedChild(canvas)
})

中文文档

https://allenchinese.github.io/html2canvas-docs-zh-cn/open in new window

缺陷

会有 dom 样式还原和 img 的同源策略的问题

dom-to-image 与 dom-to-image-more

dom-to-image-more 是 dom-to-image 的升级版

dom-to-image-more 是一个库,它可以将任意 DOM 节点(包括同源和 blob iframe)转换为用 JavaScript 编写的矢量(SVG)或光栅(PNG 或 JPEG)图像。

使用方法

// 安装
npm install dom-to-image-more
// 引入
/* in ES 6 */
import domtoimage from "dom-to-image-more";
/* in ES 5 */
var domtoimage = require("dom-to-image-more");
// 使用
var node = document.getElementById("my-node");
domtoimage
  .toPng(node)
  .then(function (dataUrl) {
    var img = new Image();
    img.src = dataUrl;
    document.body.appendChild(img);
  })
  .catch(function (error) {
    console.error("oops, something went wrong!", error);
  });

文档

https://www.npmjs.com/package/dom-to-image-moreopen in new window

缺陷

也有样式还原不完全或者不正确问题

使用 navigator.mediaDevices.getDisplayMedia() API

这个 MediaDevicesopen in new window 接口的 getDisplayMedia() 方法提示用户去选择和授权捕获展示的内容或部分内容(如一个窗口)在一个MediaStreamopen in new window 里。然后,这个媒体流可以通过使用 MediaStream Recording APIopen in new window 被记录或者作为WebRTCopen in new window 会话的一部分被传输。

这个 api 返回的屏幕视频流可以被用于 video 标签来进行播放,然后 video 又可以通过 canvas 的 context 的 drawImage 方法绘制一帧作为 canvas 的页面。随后使用 canvas.toDataURL 绘制成 base64 的图像数据。

这个数据可以通过种种方式进行使用。

使用方法

// 容器
let imgFile, imgUrl;

// 点击进行截图
function leaderHandler() {
  // 创建标签
  const video = document.createElement("video");
  // 自动播放
  video.setAttribute("autoplay", "autoplay");
  // 静音
  video.setAttribute("muted", "muted");

  startCapture(function (screen) {
    video.srcObject = screen;
  });
  video.addEventListener("loadeddata", function () {
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    // 设置一个宽高
    canvas.width = 1280;
    canvas.height = 773;
    if (!ctx) return;
    setTimeout(() => {
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
      let base64 = canvas.toDataURL("image/png");
      // 转为File对象
      imgFile = base64ImgtoFile(base64);
      // HACK 创建的地址 可以通过revokeObjectURL()清除以释放内存 是个优化点
      imgUrl =
        window.webkitURL.createObjectURL(img) ||
        window.URL.createObjectURL(img);
      // 停止以释放内存和停止分享流
      let tracks = video.srcObject.getTracks();
      tracks.forEach((track) => track.stop());
      video.srcObject = null;
    }, 250);
  });
}

// 开启视频流截屏
async function startCapture(success) {
  var constraints = {
    audio: false,
    video: true,
    width: 1280,
    height: 773,
  };
  if (navigator.mediaDevices?.getDisplayMedia) {
    navigator.mediaDevices.getDisplayMedia(constraints).then(success);
  } else {
    navigator.getDisplayMedia(constraints).then(success);
  }
}
// base64 转file对象
function base64ImgtoFile(data, filename = "file") {
  const arr = data.split(",");
  const mime = arr[0].match(/:(.*?);/)[1];
  const suffix = mime.split("/")[1];
  const bstr = atob(arr[1]);
  let n = bstr.length;
  const u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], `${filename}.${suffix}`, {
    type: mime,
  });
}

缺陷

需要用户同意权限

有模糊的问题,不能做到完全清晰,但是也有对应缓解方案。

移动端兼容问题

缓解不太清晰的方案

canvas 标签,将 width 和 height 标签属性扩大一倍,然后通过 css 将样式上的 width 和 height 规定成正确的样式。

Puppeteer

这是一个使用 node 的后端解决方案,让 node 去加载页面,像爬虫一样。然后再使用浏览器的 devtool 进行截屏。

缺陷

需要 node 做中间层,而且是 node 去重新加载页面,不会保留用户的操作

最后更新日期 6/24/2025, 10:29:18 AM