You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用html2canvas/dom-to-image导出PDF时无法正确捕获滚动内容

问题:导出网页可见区域为PDF时滚动位置被重置

我尝试用JavaScript导出网页可见部分为PDF,内容包含带滚动视图的表格、图表等元素。先后用html2canvas和dom-to-image捕获内容,但两者都会把滚动位置重置到左上角(0, 0),没法捕获用户当前屏幕的可见内容。

  • 目标导出效果:目标导出效果
  • 实际导出效果:实际导出效果

已尝试方案

  • html2canvas:传入与当前滚动位置匹配的scrollX和scrollY参数,但生成的图片仍从顶部开始捕获,忽略实际滚动位置。
html2canvas(content, {
  scrollX: -content.scrollLeft,
  scrollY: -content.scrollTop,
  width: content.clientWidth,
  height: content.clientHeight
});
  • dom-to-image:使用dom-to-image并传入类似参数,期望更好处理动态内容,但问题依旧。
domtoimage.toPng(content, {
  height: content.clientHeight,
  width: content.clientWidth
});

完整函数代码

export const exportAsPDFSingleReport = async (
  content,
  fileName,
  fileType,
  screenWidth
) => {
  const MAX_IMAGE_WIDTH = 2000;
  const MAX_IMAGE_WIDTH_HALF_SCREEN = 1000;
  const IMAGE_WIDTH_OFFSET = 100;

  const childDiv = content.querySelector("div");
  const reportType = childDiv ? childDiv.dataset.reporttype : null;
  let currentWidth;

  if ((reportType !== reportTypes.PieChart && reportType !== reportTypes.CreatedVsResolved) || screenWidth <= 1149) {
    currentWidth = Math.min(MAX_IMAGE_WIDTH, screenWidth - IMAGE_WIDTH_OFFSET);
  } else {
    currentWidth = Math.min(MAX_IMAGE_WIDTH_HALF_SCREEN, screenWidth / 2 - 5);
  }

  content.setAttribute("style", `width: ${currentWidth}px`);

  try {
    const canvas = await html2canvas(content, {
      scrollX: -content.scrollLeft,
      scrollY: -content.scrollTop,
      width: content.clientWidth,
      height: content.clientHeight,
      windowWidth: content.scrollWidth,
      windowHeight: content.scrollHeight,
      backgroundColor: null,
      useCORS: true
    });
    const imgData = canvas.toDataURL("image/png");

    const img = new Image();
    img.src = imgData;
    img.onload = function () {
      const imageWidth = img.width;
      const imageHeight = img.height;
      const orientation = imageHeight > imageWidth ? "p" : "l";
      const pdf = new jsPDF({
        orientation,
        unit: "px",
        format: [imageWidth, imageHeight]
      });
      pdf.addImage(imgData, "PNG", 0, 0, imageWidth, imageHeight);
      pdf.save(`${fileName}.${fileType}`);
    };
  } catch (error) {
    console.error("Error exporting as PDF:", error);
  } 
};

预期与实际结果

  • 预期结果:捕获用户当前可见的网页部分,保留垂直和水平方向的当前滚动位置。
  • 实际结果:捕获的图片重置到内容区域左上角,忽略用户设置的滚动,无法反映预期导出的可见区域。

解决方案:精准捕获可见区域

问题核心在于html2canvas的scrollX/scrollY参数针对的是窗口的滚动偏移,而非目标元素自身的滚动。以下两种方法可解决该问题:

方法1:完整截图后裁剪可见区域

先捕获元素的完整截图,再根据元素的滚动偏移裁剪出当前可见部分:

export const exportAsPDFSingleReport = async (
  content,
  fileName,
  fileType,
  screenWidth
) => {
  // 原宽度计算逻辑保留
  const MAX_IMAGE_WIDTH = 2000;
  const MAX_IMAGE_WIDTH_HALF_SCREEN = 1000;
  const IMAGE_WIDTH_OFFSET = 100;

  const childDiv = content.querySelector("div");
  const reportType = childDiv ? childDiv.dataset.reporttype : null;
  let currentWidth;

  if ((reportType !== reportTypes.PieChart && reportType !== reportTypes.CreatedVsResolved) || screenWidth <= 1149) {
    currentWidth = Math.min(MAX_IMAGE_WIDTH, screenWidth - IMAGE_WIDTH_OFFSET);
  } else {
    currentWidth = Math.min(MAX_IMAGE_WIDTH_HALF_SCREEN, screenWidth / 2 - 5);
  }

  content.setAttribute("style", `width: ${currentWidth}px`);

  try {
    // 1. 捕获整个元素的完整截图
    const fullCanvas = await html2canvas(content, {
      backgroundColor: null,
      useCORS: true,
      windowWidth: content.scrollWidth,
      windowHeight: content.scrollHeight
    });

    // 2. 创建新Canvas,裁剪出可见区域
    const visibleCanvas = document.createElement('canvas');
    const ctx = visibleCanvas.getContext('2d');
    
    visibleCanvas.width = content.clientWidth;
    visibleCanvas.height = content.clientHeight;

    // 根据元素的滚动偏移,从完整截图中裁剪可见部分
    ctx.drawImage(
      fullCanvas,
      content.scrollLeft, // 裁剪起始X(元素水平滚动距离)
      content.scrollTop,  // 裁剪起始Y(元素垂直滚动距离)
      content.clientWidth, // 裁剪宽度
      content.clientHeight, // 裁剪高度
      0, 0, // 绘制到新Canvas的起始位置
      content.clientWidth, content.clientHeight // 绘制尺寸
    );

    const imgData = visibleCanvas.toDataURL("image/png");

    // 后续PDF生成逻辑不变
    const img = new Image();
    img.src = imgData;
    img.onload = function () {
      const imageWidth = img.width;
      const imageHeight = img.height;
      const orientation = imageHeight > imageWidth ? "p" : "l";
      const pdf = new jsPDF({
        orientation,
        unit: "px",
        format: [imageWidth, imageHeight]
      });
      pdf.addImage(imgData, "PNG", 0, 0, imageWidth, imageHeight);
      pdf.save(`${fileName}.${fileType}`);
    };
  } catch (error) {
    console.error("Error exporting as PDF:", error);
  } 
};

方法2:临时修改元素滚动样式

将元素的滚动偏移转化为transform位移,再捕获截图,完成后恢复原样式:

export const exportAsPDFSingleReport = async (
  content,
  fileName,
  fileType,
  screenWidth
) => {
  // 原宽度计算逻辑保留
  const MAX_IMAGE_WIDTH = 2000;
  const MAX_IMAGE_WIDTH_HALF_SCREEN = 1000;
  const IMAGE_WIDTH_OFFSET = 100;

  const childDiv = content.querySelector("div");
  const reportType = childDiv ? childDiv.dataset.reporttype : null;
  let currentWidth;

  if ((reportType !== reportTypes.PieChart && reportType !== reportTypes.CreatedVsResolved) || screenWidth <= 1149) {
    currentWidth = Math.min(MAX_IMAGE_WIDTH, screenWidth - IMAGE_WIDTH_OFFSET);
  } else {
    currentWidth = Math.min(MAX_IMAGE_WIDTH_HALF_SCREEN, screenWidth / 2 - 5);
  }

  content.setAttribute("style", `width: ${currentWidth}px`);

  try {
    // 临时修改元素样式,将滚动偏移转为位移
    const originalTransform = content.style.transform;
    const originalOverflow = content.style.overflow;
    content.style.transform = `translate(-${content.scrollLeft}px, -${content.scrollTop}px)`;
    content.style.overflow = 'hidden';

    // 捕获当前可见区域的截图
    const canvas = await html2canvas(content, {
      width: content.clientWidth,
      height: content.clientHeight,
      backgroundColor: null,
      useCORS: true
    });

    // 恢复元素原样式
    content.style.transform = originalTransform;
    content.style.overflow = originalOverflow;

    const imgData = canvas.toDataURL("image/png");

    // 后续PDF生成逻辑不变
    const img = new Image();
    img.src = imgData;
    img.onload = function () {
      const imageWidth = img.width;
      const imageHeight = img.height;
      const orientation = imageHeight > imageWidth ? "p" : "l";
      const pdf = new jsPDF({
        orientation,
        unit: "px",
        format: [imageWidth, imageHeight]
      });
      pdf.addImage(imgData, "PNG", 0, 0, imageWidth, imageHeight);
      pdf.save(`${fileName}.${fileType}`);
    };
  } catch (error) {
    console.error("Error exporting as PDF:", error);
  } 
};

内容的提问来源于stack exchange,提问作者Kristina Garanchuk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 14:20:24