由于业务需求,PM让我实现H5直接预览PDF,好家伙,我以为真的就像他说的一样,殊不知,横竖都是二,到处都是坑,真的是“井田”啊!
常见的PDF预览功能主要三个方向来实现:
- 读PDF文件,进行预览展示(通常是XXX.pdf文件)
- 读PDF文件链接,进行预览展示(https://192.168.0.1/mock/downloadPdf?params=xxx)
- 读Base64编码后的PDF,进行预览展示(比较少见,除非业务场景需要)
react-pdf
直达地址:www.npmjs.com/package/rea…
1、安装
npm install react-pdf 或者 yarn add react-pdf
2、代码实现
import React, { useState } from 'react';
import { Document, Page } from 'react-pdf';
function MyApp() {
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }) {
setNumPages(numPages);
}
return (
<div>
<Document
file="somefile.pdf" //PDF文件在此
onLoadSuccess={onDocumentLoadSuccess}
>
<Page pageNumber={pageNumber} />
</Document>
<p>Page {pageNumber} of {numPages}</p>
</div>
);
}
注意:这里直接读的是PDF文件,如果只有PDF链接地址,可以直接修改file的值,直接设置为链接地址即可,如:file={"http:www.baidu.com"}
3、如何一次性将所有的PDF全部展示出来
对上面的代码稍作修改
import React, { useState } from 'react';
import { Document, Page } from 'react-pdf';
function MyApp() {
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }) {
setNumPages(numPages);
}
function render(nums: number){
return new Array(nums).fill(1).map((e,i:number)=>{
if(i>0){
return <Document
key={i}
file="somefile.pdf"
onLoadSuccess={onDocumentLoadSuccess}
>
<Page pageNumber={i+1} />
</Document>
}
})
}
return (
<div>
<Document
file="somefile.pdf" //PDF文件在此
onLoadSuccess={onDocumentLoadSuccess}
>
<Page pageNumber={pageNumber} />
</Document>
{
numPages > 0 && render(numPages) //这里显示除了第一张PDF,剩下所有的PDF
}
</div>
);
}
到这里,基本上已满足90%的业务场景,你可以下班了。
什么时候不能直接用链接跳转的方式?
某些场景下,请求链接是需要带请求头header的,这是为了告知后端,用户是从某一入口进来的,这种情况下请求头参数就是一个标识作用。随着业务的扩展,我们可能有会开放不同的入口给不同场景下的用户使用,在此条件下,链接跳转的方式就无法满足业务需求。所有绝大多少情况下,考虑到延展性,我们会使用读PDF文件的方式,来实现PDF预览的功能。
又一特殊情况出现了。。。后端无法直接将PDF文件返过来,只能返PDF文件流
PDF文件流长什么样?长的一点都不优雅,这是前端能看得懂的程度吗?
header参数为 content-type: application/pdf
react-pdf-js
那么react-pdf-js就登场了。我们可以通过把一点都不优雅的PDF文件流转化为base64,再读出来进行PDF预览。
1、安装
yarn add @mikecousins/react-pdf 或者 npm install @mikecousins/react-pdf
2、代码实现
import React, { useEffect, useState } from "react";
import Pagination from '@material-ui/lab/Pagination'; //本场景下使用的是Material-ui组件库,一般的用Antd就ok啦
import PDF from 'react-pdf-js';
function MyApp() {
const [page, setPage] = useState(1);
const [totalPage, setTotalPages] = useState(1);
const [currentPage, setCurrentPage] = useState(1);
const [blob, setBlob] = useState<any>(null);
const [pdfblob, setPdfBlob] = useState<any>(null);
useEffect(() => { //重点在此!!!!!如何将PDF文件流转base64
fetchData({
url:'填接口地址噢',
method: 'get',
responseType: 'blob', //必写!
params:{
number: number, // 接口传参
}
}).then((res)=>{
setBlob(res.data)
let reader = new FileReader();
reader.readAsDataURL(res.data); // 转换为base64,可以直接放入a标签href
reader.addEventListener("load", function () {
let base64 = reader.result as string
setPdfBlob(base64.split(',')[1])
});
})
}, []);
//史诗级重点!不进行异常处理会报错!全网找不到的!
let newpdfblob = "data:application/pdf;base64,"+pdfblob
if(!pdfblob) return null
function onDocumentLoadSuccess(totalPage:any) {
setTotalPages(totalPage);
setCurrentPage(1);
}
function onChangePage(page:any) {
setCurrentPage(page);
console.log(page)
}
return (
<div>
<PDF
scale={0.61}
file={newpdfblob} //这里的newpdfblob已经是base64格式了
onDocumentComplete={onDocumentLoadSuccess}
page={currentPage}
/>
<Pagination onChange={()=>onChangePage(page)} count={totalPage} page={currentPage}/>
</div>
);
}
还是要再次强调一下:史诗级重点!不进行异常处理会报错!全网找不到的!
let newpdfblob = "data:application/pdf;base64,"+pdfblob
if(!pdfblob) return null
3、如何一次性将所有的PDF全部展示出来
<PDF
scale={0.61}
file={newpdfblob}
onDocumentComplete={onDocumentLoadSuccess}
page={currentPage}
/>
{
totalPage > 1 && directlyRenderPdf(totalPage)
}
function directlyRenderPdf(nums: number){
const x = [];
for (let i = 2; i <= totalPage; i++)
x.push(<PDF page={i} key={`x${i}`} file={newpdfblob} scale={0.61}/>);
return x;
}
同样的,图片文件流也可以转成base64的格式进行展示:<img src="data:image/jpeg;base64,base64转码后的字符串" ></img>
写在最后
目前,Data URI scheme支持的类型有:
- data:,文本数据
- data:text/plain,文本数据
- data:text/html,HTML代码
- data:text/html;base64,base64编码的HTML代码
- data:text/css,CSS代码
- data:text/css;base64,base64编码的CSS代码
- data:text/javascript,Javascript代码
- data:text/javascript;base64,base64编码的Javascript代码
- data:image/gif;base64,base64编码的gif图片数据
- data:image/png;base64,base64编码的png图片数据
- data:image/jpeg;base64,base64编码的jpeg图片数据
- data:image/x-icon;base64,base64编码的icon图片数据
base64简单地说,它把一些 8-bit 数据翻译成标准 ASCII 字符,网上有很多免费的base64 编码和解码的工具,在PHP中可以用函数base64_encode() 进行编码,如echo base64_encode(file_get_contents(‘wg.png’));
目前,IE8、Firfox、Chrome、Opera浏览器都支持这种小文件嵌入。