使用 html2canvas 实现截图功能

孙博 技术分享
html2canvas vue 截图

春节即将来临,为了确保节假日期间系统的稳定性,我们一如既往地将系统中的业务指标汇总到一个专门的值班系统页面中,并通过 ECharts 图表展示。这些指标包括接口调用次数、成功率、响应时间等重要数据。由于我们的部门负责多个系统,且这些系统之间往往没有直接关联,不同同事在值班时可能对其他系统的指标不太熟悉。因此,各系统负责人分别制定了各自的指标阈值,以便值班人员只需检查指标是否超限,即可判断系统的健康状况。

然而,当系统出现问题时,如何有效地将信息同步给其他同事却一直缺乏统一的标准。有些人喜欢用截图,有些则习惯复制系统信息。而由于监控看板页面可能很长,在不同分辨率的显示器上可能无法完全显示在一屏内。为了更快速、统一地将信息传达给其他同事,我决定为看板页面添加一个截图按钮。点击按钮时,页面会自动将整个看板页面截图并保存到剪贴板中。值班人员只需按 Ctrl + V 即可将截图粘贴到微信中分享。

于是,我开始寻找合适的工具,最终发现了 html2canvas 这个组件。它是一个功能强大的 JavaScript 库,可以直接在浏览器端将 DOM 元素渲染成 canvas 图像,而无需服务端支持。html2canvas 还支持大多数 CSS 样式,因此能够很好地保留页面的视觉效果。


我的监控看板是 vue3 + element-plus 的项目,所以 html2canvas 也是通过 npm 安装的。

npm install html2canvas

首先我们在页面中增加一个按钮,用于触发截图功能。

<div class="assistant-buttons">
  <el-button type="text" @click="takeScreenshot">
    <i class="fas fa-camera"></i>
    截图
  </el-button>
</div>

然后在 script 中定义 takeScreenshot 方法,用于触发截图。

const takeScreenshot = async () => {
  screening.value = true;
  const element: HTMLElement | null = document.querySelector(".luckystarry-ui-container");
  if (!element) return;

  try {
    // 保存原始滚动位置
    const originalScrollPos = window.scrollY;

    // 临时修改容器样式以捕获完整内容
    const originalHeight = element.style.height;
    element.style.height = "auto";
    element.classList.add("screening");

    const canvas = await html2canvas(element, {
      logging: false,
      useCORS: true,
      scale: window.devicePixelRatio,
      windowHeight: element.scrollHeight,
      height: element.scrollHeight,
      scrollY: -window.scrollY,
      onclone: (clonedDoc) => {
        const clonedElement: HTMLElement | null = clonedDoc.querySelector(".luckystarry-ui-container");
        if (clonedElement) {
          clonedElement.style.height = "auto";
        }
      },
    });

    // 恢复原始样式
    element.style.height = originalHeight;
    element.classList.remove("screening");
    window.scrollTo(0, originalScrollPos);

    // 屏蔽下载图片文件的功能
    // // 转换为图片并下载
    // const link = document.createElement('a')
    // link.download = `监控平台截图_${new Date().toLocaleString().replace(/[/:]/g, '-')}.png`
    // link.href = canvas.toDataURL('image/png')
    // link.click()

    // 复制到剪贴板
    try {
      canvas.toBlob(async (blob) => {
        if (blob) {
          await navigator.clipboard.write([
            new ClipboardItem({
              [blob.type]: blob,
            }),
          ]);
          ElMessage.success("截图已复制到剪贴板");
        }
      }, "image/png");
    } catch (clipboardError) {
      console.error("复制到剪贴板失败:", clipboardError);
      ElMessage.warning("复制到剪贴板失败");
    }
  } catch (error) {
    console.error("截图失败:", error);
  } finally {
    screening.value = false;
  }
};

上述代码注释部分展示了如何将 canvas 转换为图片并下载。不过,对于我们日常使用 PC 端值班的情况而言,直接复制到剪贴板可能会更加方便。

熟悉 vue3 的小伙伴应该能够轻松理解这些代码,因此在此不再赘述。有需要的朋友可以参考我的代码,快速实现类似功能。

另外,我的看板界面采用了传统的布局方式:包含一个顶部导航栏和一个左侧纵向菜单栏,页面右侧的大块区域则用来展示实际内容(即 vue-router 承载的页面区域)。为了使菜单栏固定在页面上,我将内容区域设置了固定高度。当页面内容较多时,滚动条只会出现在内容区域内。这种设计符合传统 PC 端的交互习惯,但在截图时可能会因为内容区域高度受限而无法截取到完整的页面内容。

为了解决这个问题,我在调用截图时,覆盖了内容区域的高度设置,使用 height: auto !important; 使其高度自适应,并将整个页面的高度设置为 min-height: 100vh;。这样可以确保在截图时,所有容器类的元素都没有高度约束,从而完整地显示所有内容。

局部样式参考代码如下:

.luckystarry-ui-container {
  position: relative;
  height: 100vh;
  &.screening {
    height: auto !important;
    min-height: 100vh;
    .main-content {
      height: auto !important;
      min-height: 100vh;
    }
  }

  .main-content {
    padding-top: 0;
    padding-bottom: 0;
    height: calc(100vh - 150px);
  }
}

功能虽小,也依照惯例记录下来以备将来参考,同时也希望能帮助到有相同需求的小伙伴。