业务开发实现二维码、PDF下载记录

一、前言

最近开发过程中遇到了二维码和PDF下载的需求,虽然这些都有常见的开源库实现,但在开发的过程还是出现了一些卡壳问题,今天记录下,方便参考。

二、二维码设置和下载

csharp 复制代码
yarn add qrcode.react

下载二维码的开源库很多,我们选择qrcode.react,这个功能比较满足我的需要,体验可以点击zpao.github.io/qrcode.reac...

要实现的效果如下:

二维码展示在页面中是很简单的,在实际业务中,遇到了如下的问题:

1、调整二维码的尺寸,展示给用户时尺寸不变,下载的时候要生成用户需要的尺寸

2、二维码中间的图片的地址存在跨域问题,那么需要转化成同源的才可以同时下载。

3、下载的PNG或JPEG文件尺寸,在Mac平台和Win平台尺寸不一致

1、解决展示和下载二维码尺寸不一致问题

当你调整二维码大小参数时,二维码会变大,导致展示不友好,我们只需要下载的时候保持用户选择的尺寸就行。

下面的实现是随着size的大小而变化。

js 复制代码
import { QRCodeSVG, QRCodeCanvas } from 'qrcode.react';
  
<QRCodeSVG
  value={"https://picturesofpeoplescanningqrcodes.tumblr.com/"}
  size={size}
  level={"L"}
  includeMargin={false}
  imageSettings={{
    src: "https://static.zpao.com/favicon.png",
    height: 24,
    width: 24,
    excavate: true,
  }}
/>

上面是我们用来展示二维码,下面的我们添加display: none用来下载,对用户不可见。

js 复制代码
<div style={{ display: 'none' }}>
  <div ref={RQCodeRef}>
    {currentValue.imageType === 'svg' ? (
      <QRCodeSVG {...QROptions} />
    ) : (
      <QRCodeCanvas {...QROptions} />
    )}
  </div>
</div>

2、解决远程图片跨域无法下载问题

无法下载是因为图片异步加载,无法读取图片,所以我们可以将图片下载到本地或者引用本地的图片。

js 复制代码
const [imageUrl, setImageUrl] = useState('');
const getImage = () => { 
    fetch(faviconSrc) .then((response) => { 
        if (!response.ok) { 
            throw new Error('网络响应不正常'); 
        } 
        return response.blob(); 
    }).then((imageBlob) => { 
        const imageUrl = URL.createObjectURL(imageBlob);
        setImageUrl(imageUrl); 
    });
}

通过http下载图片资源,通过URL.createObjectURL转化成本地的URL,然后再使用。

3、解决下载的尺寸不一致问题

尺寸不一样是因为不同电脑的屏幕像素比不一样。

测试发现,在window电脑上window.devicePixelRatio为0.75,在mac上为1,为了获取实际像素,需要计算下Math.ceil(canvasImg.width / scaleFactor),然后再将图片绘制在canvas上,就可以转化成图片进行下载了。

下载PNG或JPEG

js 复制代码
const downloadImage = (fileName, imageType) => {
    // png/jpeg
    const canvasImg = RQCodeRef.current; // 获取canvas类型的二维码
    const scaleFactor = window.devicePixelRatio || 1;

    // 获取原始尺寸的 dataUrl
    const dataUrl = canvasImg.toDataURL(`image/${imageType}`);

    // 设置 canvas 的实际像素尺寸
    canvasImg.width = Math.ceil(canvasImg.width / scaleFactor);
    canvasImg.height = Math.ceil(canvasImg.height / scaleFactor);

    const context = canvasImg.getContext('2d');

    // 清空 canvas
    context.clearRect(0, 0, canvasImg.width, canvasImg.height);

    // 将缩小后的 dataUrl 绘制到 canvas
    const img = new Image();
    img.onload = function () {
      context.drawImage(img, 0, 0, canvasImg.width, canvasImg.height);

      // 创建下载链接并触发下载
      const a = document.createElement('a');
      a.href = canvasImg.toDataURL(`image/${imageType}`);
      a.download = `${fileName}.${imageType}`;
      a.click();
    };
    img.src = dataUrl;
  };

下载SVG

js 复制代码
const downloadSVG = (fileName) => {
  const svgElement = RQCodeRef.current;
  const svgXml = new XMLSerializer().serializeToString(svgElement);
  const blob = new Blob([svgXml], { type: 'image/svg+xml' });

  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = `${fileName}.svg`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
}

上面就分别实现了SVG和PNG的下载,并且对用户不可见。

三、PDF下载

PDF的下载调研了html2pdf.jsjspdf,前者简单一点,可以支持自动分页,但下载下来发现页面中的图表错位严重,暂时放弃了,这里通过jspdf看如何实现。

使用了html2canvas库,它可以将 DOM 元素渲染为Canvas。方便生成截图、制作网页快照或将HTML嵌入其他文档。

js 复制代码
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';

const htmlCanvasOptions = {
    allowTaint: true,
    taintTest: false,
    dpi: '192',
    scale: 2
}

const download = () => {
    const element = document.getElementById('slk-export-pdf');
    if (!element) return;
    const canvas = await html2canvas(element, htmlCanvasOptions);
    // 获取canvas的宽度和高度
    const contentWidth = canvas.width;
    const contentHeight = canvas.height;
    // 按A4纸的宽度和高度,将html元素的宽度和a4纸的比,计算出A4值的高度。
    const pageHeight = (contentWidth / a4Width) * a4Height;
    let leftHeight = contentHeight;
    let position = 0;
    // 计算出添加到PDF的高度
    const imgWidth = a4Width;
    const imgHeight = (a4Width / contentWidth) * contentHeight;
    
    // 将Canvas转化成base64编码
    const pageData = canvas.toDataURL('image/jpeg', 1.0);
    const pdf = new jsPDF('', 'pt', 'a4');
    // 如果剩余高度小于页面高度,就不再分页
    if (leftHeight < pageHeight) {
      pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
    } else {
    // 分页逻辑
      while (leftHeight > 0) {
        pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight);
        leftHeight -= pageHeight;
        position -= 841.89;
        if (leftHeight > 0) {
          pdf.addPage();
        }
      }
    }
    pdf.save('test.pdf');
}

我们用html2canvas将html转化成canvas元素,然后写入PDF,但此时要注意页面比例要和pdf保持一致,特别是长页面,需要在特定比例高度保持间隙,否则会被截取部分。

最后我们来看下效果,还是很不错的。

四、总结

现在各种开源工具真的是造福程序员,但选择成本也在上升,我选择的不一定是最好的,各位朋友如果有更好的可以推荐下。这篇文章主要用来记录踩过的一些坑。

相关推荐
莫的感情7 分钟前
下载按钮点击一次却下载两个文件问题
前端
一个很帅的帅哥11 分钟前
JavaScript事件循环
开发语言·前端·javascript
小宁爱Python16 分钟前
Django Web 开发系列(二):视图进阶、快捷函数与请求响应处理
前端·django·sqlite
fox_16 分钟前
深入理解React中的不可变性:原理、价值与实践
前端·react.js
Github项目推荐18 分钟前
你的错误处理一团糟-是时候修复它了-🛠️
前端·后端
Code小翊23 分钟前
C语言bsearch的使用
java·c语言·前端
云枫晖24 分钟前
Webapck系列-初识Webpack
前端·javascript
慧一居士27 分钟前
HTML5 功能介绍,使用场景,对应功能点完整使用示例
前端
海在掘金6112735 分钟前
告别“undefined is not a function”:TS如何让你的函数调用更安心
前端
云中雾丽38 分钟前
Flutter中Stream的各种使用场景和实现方式
前端