用浏览器把 HTML 详情页导出成长图 / PDF 工具(附核心思路)

在这里插入图片描述

用浏览器把 HTML 详情页导出成长图 / PDF(附核心思路)

写商品详情、图文教程时,经常会遇到这种情况:

  • 内容是 HTML 写的,预览很舒服
  • 店铺、小程序、网盘说明却只要 长图PDF
  • 截屏拼接费劲,打印另存又容易跑版

其实在浏览器里就能做:把 HTML 渲染出来 → 调好宽度 → 截成一张长图,或再塞进 PDF。下面只讲实现思路和关键代码,方便自己改、自己集成。


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1. 整体流程

载入 HTML 片段
    ↓
放进固定宽度的预览容器(如 750 / 1200)
    ↓
等图片加载完成
    ↓
DOM → Canvas(截图)
    ↓
Canvas → PNG 下载
  或 Canvas → jsPDF → PDF 下载

宽度建议先定死再截:

场景常用宽度
手机详情长图750
部分电商稿790
电脑宽图1200

预览用多少宽,导出就按这个宽截,比「先截再缩放」稳得多。


2. 预览容器(先所见,再所得)

<div id="stage" style="width:750px;background:#fff;margin:0 auto;">
  <!-- 这里塞你的 HTML 详情片段 -->
</div>

如果原文根节点写了 max-width:750px,在导出容器里可以强制撑满:

#stage .export-root {
  max-width: none !important;
  width: 100% !important;
  margin-left: 0 !important;
  margin-right: 0 !important;
  box-sizing: border-box;
}

宽度切换只改容器,不要改用户 HTML 里每一处样式:

function setPreviewWidth(px) {
  document.getElementById('stage').style.width = px + 'px';
}

3. 导出前先等图片

外链图没加载完就截,容易出现空白块:

async function waitForImages(root) {
  const imgs = [...root.querySelectorAll('img')];
  await Promise.all(
    imgs.map((img) => {
      if (img.complete && img.naturalWidth) return;
      return new Promise((resolve) => {
        img.addEventListener('load', resolve, { once: true });
        img.addEventListener('error', resolve, { once: true });
      });
    })
  );
}

外链图若站点不允许跨域,Canvas 会「污染」或缺图。实务上常见处理:

  • 图床开启 CORS;或
  • 导出前改成本地可访问路径;或
  • 用能把资源嵌进结果的截图库(仍依赖对方是否放行)

4. DOM 转长图(两种常见做法)

方案 A:html2canvas(兼容广)

async function exportPng(stage, scale = 2) {
  await waitForImages(stage);

  const canvas = await html2canvas(stage, {
    scale,              // 2 = 高清,但个别排版可能和预览略有差异
    useCORS: true,
    backgroundColor: '#ffffff',
    width: stage.offsetWidth,
    height: stage.scrollHeight,
  });

  canvas.toBlob((blob) => {
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'export.png';
    a.click();
  });
}

注意:scale > 1 时,库会按放大后的逻辑重算文字,行内带背景的代码块最容易和浏览器预览对不齐。不对齐时优先试 scale: 1

方案 B:基于 SVG foreignObject 的截图(更贴预览)

很多「精确模式」工具会走类似 modern-screenshot / html-to-image 这条路:先克隆 DOM,再画进 Canvas,文字换行通常更接近真实页面。

// 示意:具体 API 以你引入的库为准
const canvas = await modernScreenshot.domToCanvas(stage, {
  scale: 2,
  backgroundColor: '#ffffff',
});

工程上可以:默认精确模式,失败再回退 html2canvas


5. 长图再转 PDF

有了 Canvas 之后,用 jsPDF 很直接:

async function exportPdf(canvas, mode = 'long') {
  const { jsPDF } = window.jspdf;
  const img = canvas.toDataURL('image/jpeg', 0.92);
  const pxToMm = 0.264583; // 按 96dpi 近似

  if (mode === 'long') {
    // 单页长 PDF:页面尺寸跟图走
    const w = canvas.width * pxToMm;
    const h = canvas.height * pxToMm;
    const pdf = new jsPDF({
      orientation: w > h ? 'l' : 'p',
      unit: 'mm',
      format: [w, h],
    });
    pdf.addImage(img, 'JPEG', 0, 0, w, h);
    pdf.save('export.pdf');
    return;
  }

  // A4 分页:一张长图切多页
  const pdf = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' });
  const pageW = pdf.internal.pageSize.getWidth();
  const pageH = pdf.internal.pageSize.getHeight();
  const imgW = pageW;
  const imgH = (canvas.height * imgW) / canvas.width;

  let heightLeft = imgH;
  let position = 0;
  pdf.addImage(img, 'JPEG', 0, position, imgW, imgH);
  heightLeft -= pageH;

  while (heightLeft > 0.5) {
    position -= pageH;
    pdf.addPage();
    pdf.addImage(img, 'JPEG', 0, position, imgW, imgH);
    heightLeft -= pageH;
  }
  pdf.save('export.pdf');
}

浏览器对 Canvas 高度有上限(常见大约一万多像素量级)。内容特别长时,需要降清晰度、拆段截取,或换服务端渲染(Puppeteer 一类),这是前端方案的硬边界。


6. 减少「预览正常、导出错位」

实操里最有用的几条:

  1. 预览宽度 = 导出逻辑宽度(例如直接用 750,而不是 375 再 ×2 指望布局一样)
  2. 出问题先把清晰度改成 1x
  3. 行内 code / 带背景的 span 可加:
code, kbd, samp {
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

短路径文字必要时 display: inline-block; white-space: nowrap;,避免截图引擎把灰底拆成两截。


7. 最小可运行骨架(拼起来)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>HTML → 长图 Demo</title>
  <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
</head>
<body>
  <button id="btn">导出 PNG</button>
  <div id="stage" style="width:750px;margin:20px auto;background:#fff;padding:16px;">
    <h1>示例标题</h1>
    <p>把你的详情 HTML 贴到这里。</p>
  </div>

  <script>
    document.getElementById('btn').onclick = async () => {
      const stage = document.getElementById('stage');
      const canvas = await html2canvas(stage, {
        scale: 2,
        useCORS: true,
        backgroundColor: '#ffffff',
      });
      const a = document.createElement('a');
      a.download = 'demo.png';
      a.href = canvas.toDataURL('image/png');
      a.click();
    };
  </script>
</body>
</html>

本地保存成 .html,用 Chrome / Edge 打开即可试。正式使用再补:宽度预设、PDF、精确截图引擎、跨域与错位处理。


8. 小结

  • 核心不是「神奇算法」,而是 固定宽度预览 + DOM 截图 +(可选)写入 PDF
  • 难度主要在:图片跨域、超长画布、高清缩放导致的文字错位
  • 自己写个 Demo 很快;要做成每天能用的成品,还得把预览、预设、导出选项和避坑打磨一遍

如果你只是想直接用现成的本地小工具(调宽度预览、导出 PNG/PDF),可以在这边看看整理好的版本:
https://zhixuanxingqiu.com

(浏览器打开即用,按需自取;本文只讲原理,方便学习与二次改造。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

命运之光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值