一、前言
最近开发过程中遇到了二维码和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.js
、jspdf
,前者简单一点,可以支持自动分页,但下载下来发现页面中的图表错位严重,暂时放弃了,这里通过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保持一致,特别是长页面,需要在特定比例高度保持间隙,否则会被截取部分。
最后我们来看下效果,还是很不错的。
四、总结
现在各种开源工具真的是造福程序员,但选择成本也在上升,我选择的不一定是最好的,各位朋友如果有更好的可以推荐下。这篇文章主要用来记录踩过的一些坑。