后端搞不了,前端来搞?

107 阅读3分钟

前言

最近,我们团队接到一个看似简单但背后充满挑战的需求:客户希望将HTML内容导出为PDF文件。原本这个任务应该由后端完成,但由于采集到的HTML内容可能存在标签不完整、嵌套混乱等问题,后端在处理时遇到了不少困难。于是,这个任务就落到了前端的肩上。

今天,我想和大家分享一下我们是如何通过前端技术,成功实现这个功能的。

图片

问题背景:HTML内容的复杂性

在我们的项目中,后端从第三方抓取了大量HTML内容,这些内容可能包含未闭合的标签、不规范的嵌套结构,甚至是一些跨域图片。例如:

<h1>前端人</h1>
<p>学好前端,走遍天下都不怕</p>
<div>前端强,前端狂,交互特效我称王!</div
<p>JS 写得好,需求改不了!</p>

浏览器能很好地处理这些问题,但后端在尝试将这些HTML内容转换为PDF时,却频繁报错。后端尝试了多种第三方库,但都无法完美解决问题。

前端兜底:寻找解决方案

既然后端无法处理,那就轮到前端来兜底了。我们首先想到的是利用浏览器的打印功能,但这种方式需要用户手动操作,显然不符合客户的需求。

于是,我们开始寻找更直接的解决方案。经过一番调研,我们选择了html2pdf.js这个库,它可以将HTML内容直接转换为PDF文件,且支持自定义样式和布局。

实现步骤:HTML到PDF的转换

1. 安装依赖

首先,我们需要安装html2pdf.js

npm install html2pdf.js
2. 基础实现

我们从一个简单的例子开始:

import html2pdf from 'html2pdf.js';

const element = `
  <h1>前端人</h1>
  <p>学好前端,走遍天下都不怕</p>
  <p>JS 写得好,需求改不了!</p>
`;

function generatePDF() {
  const opt = {
    margin10,
    filename: '前端大法好.pdf',
    image: { type: 'jpeg', quality: 0.98 },
    html2canvas: { scale: 2 },
    jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
  };

  html2pdf().from(element).set(opt).save();
}

这段代码会将HTML内容转换为PDF文件并保存到本地。

3. 解决图片加载问题

在实际场景中,HTML内容可能包含图片,而这些图片通常是通过异步加载的。如果直接导出,图片可能会显示为空白块。

为了解决这个问题,我们将图片转换为Base64格式,确保图片能够同步加载:

async function convertImagesToBase64(htmlString) {
  const tempDiv = document.createElement('div');
  tempDiv.innerHTML = htmlString;

  const images = tempDiv.querySelectorAll('img');
  for (const img of images) {
    try {
      const base64 = await getBase64FromUrl(img.src);
      img.src = base64;
    } catch (error) {
      console.error(`无法转换图片 ${img.src}:`, error);
    }
  }

  return tempDiv.innerHTML;
}

function getBase64FromUrl(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = 'Anonymous';

    img.onload = () => {
      const canvas = document.createElement('canvas');
      canvas.width = img.width;
      canvas.height = img.height;
      const ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0);
      resolve(canvas.toDataURL('image/png'));
    };

    img.onerror = () => reject(new Error('图片加载失败'));
    img.src = url;
  });
}

在导出PDF之前,我们先调用convertImagesToBase64方法,将HTML中的图片转换为Base64格式:

function generatePDF() {
  const htmlContent = `
    <style>
      img {
        max-width: 100%;
        max-height: 100%;
        vertical-align: middle;
        height: auto !important;
        width: auto !important;
        margin: 10px 0;
      }
    </style>
    <div>
      <img src='http://t13.baidu.com/it/u=2041049195,1001882902&fm=224&app=112&f=JPEG?w=500&h=500' style="width: 300px;" />
      <p>职业:前端</p>
      <p>技能:唱、跳、rap</p>
    </div>
  `;

  convertImagesToBase64(htmlContent)
    .then(convertedHtml => {
      const opt = {
        margin10,
        filename'前端大法好.pdf',
        image: { type'jpeg'quality0.98 },
        html2canvas: { scale: 2 },
        jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
      };

      html2pdf().from(convertedHtml).set(opt).save();
    })
    .catch(error => {
      console.error('转换过程中出错:', error);
    });
}

通过这种方式,我们成功解决了图片加载问题,确保导出的PDF文件中图片能够正常显示。

总结

这次需求虽然看似简单,但背后涉及了HTML内容的复杂性、图片加载问题以及前后端协作的挑战。通过使用html2pdf.js,我们成功实现了HTML到PDF的转换,并解决了图片异步加载的问题。

前后端的协作是项目成功的关键。虽然这次是前端兜底,但我们也希望后端能够提供更多支持,共同解决问题。