PDF作为最常见的电子文档格式如何实现电子签名和盖章功能,成为了每个开发者和企业必须面对的问题。在这篇文章中将深入探讨如何在React应用中集成PDF预览功能
要实现对pdf进行预览操作首先我们要实现对pdf的预览操作,实现预览操作的方式有很多如使用pdfjs-dist、react-pdf、pdf-viewer等预览工具,它们之间的区别如下所示:
1)pdfjs-dist:基于Mozilla的pdf.js项目的js库,提供了丰富的API且允许开发者对pdf文件进行各种操作,如缩放、旋转、搜索等,功能强大兼容性好,支持PC端和移动端浏览器。
2)react-pdf:专门为react框架设计的pdf渲染库,它基pdfjs-dist但提供了更加简洁易用的react组件接口,通过react-pdf可以轻松地在React应用中实现PDF预览功能。
3)pdf-viewer:基于vue的pdf渲染组件,与react-pdf类似也提供了简洁易用的API,方便开发者在vue应用中实现pdf预览功能,优点是易于集成和使用同时支持vue的响应式布局和组件化开发。
pdfjs-dist、react-pdf和pdf-viewer都是实现前端PDF预览功能的优秀工具,它们各有优缺点,选择哪个取决于你的具体需求和技术栈,因为此次采用的react框架,所以这里我们就使用react-pdf进行预览操作,预览的样式布局我们就参考e签宝:地址 的样式布局来进行实现吧,如下所示:
终端执行如下命令按照pdf预览插件,关于这个插件详细具体的使用,可以参考:npm 或 官网 进行查看,这里不再赘述:
npm i react-pdf
pdf预览:安装完成之后,我们在网上随便下载一个pdf资料来进行演示,这里我们要使用react-pdf进行预览就要引入配置pdf的worker文件,然后通过Document设置文档容器,Page设置pdf每一页的内容,如下代码所示:
import { useState, useRef } from 'react';
import { pdfjs, Document, Page } from 'react-pdf';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css'
import 'react-pdf/dist/esm/Page/TextLayer.css';
import "./index.less"
// 配置 PDF.js 的 worker 文件
pdfjs.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
const PDFPreview = () => {
const [numPages, setNumPages] = useState<number>(0);
const [pdfWidth, setPdfWidth] = useState<number>(600); // 默认宽度为 600px
const pdfWidthRef = useRef(pdfWidth); // 用于存储和防止频繁更新 pdfWidth 的 ref
const pageRefs = useRef<Array<HTMLDivElement | null>>([]);
return (
<div className='pdf-container'>
<div className='pdf-center'>
<div className='pdf-center-bottom' style={{ overflowY: 'auto' }}>
<Document
file={new URL('/public/test.pdf', import.meta.url).toString()}
onLoadSuccess={({ numPages }) => setNumPages(numPages)}
onLoadError={(error) => console.log('Error loading PDF:', error)}
>
{numPages && Array.from(new Array(numPages), (el, index) => (
<div
key={`page-container_${index + 1}`}
className='pdf-content-item'
ref={(ref: any) => pageRefs.current[index] = ref}
>
<Page
width={pdfWidthRef.current} // 使用 ref 存储的宽度
key={`page_${index + 1}`}
pageNumber={index + 1}
onLoadSuccess={({ width }) => setPdfWidth(width)}
/>
</div>
))}
</Document>
</div>
</div>
</div>
);
};
export default PDFPreview;
效果如下所示:
布局调整:接下来我们仿照e签宝的样式来对pdf预览进行调整布局,这里我们用到了antd和其对应的icon内容,可以提前安装一下对应的依赖,最终实现的效果如下所示:
<div className='pdf-container'>
<div className='pdf-left'>
<div className='pdf-left-top'>操作内容</div>
</div>
<div className='pdf-center'>
<div className='pdf-center-top'>
<div className='slider'>
<Slider value={percentage} min={50} max={300} step={10}
tooltip={{ formatter: null }} style={{ width: '150px' }}
onChange={handlePercentageChange}
/>
<div className='slider-value'>{percentage}%</div>
</div>
<div className='pagination'>
<Pagination simple total={numPages} pageSize={1} onChange={handlePageChange} />
</div>
<IsFullScreen />
</div>
<div className='pdf-center-bottom' ref={scrollContainerRef} style={{ overflowY: 'auto' }}>
<Document
loading={<IsLoading />}
file={new URL('/public/test.pdf', import.meta.url).toString()}
onLoadSuccess={({ numPages }) => setNumPages(numPages)}
onLoadError={(error) => console.log('Error loading PDF:', error)}
>
{numPages && Array.from(new Array(numPages), (el, index) => (
<div
key={`page-container_${index + 1}`}
className='pdf-content-item'
ref={(ref: any) => pageRefs.current[index] = ref}
>
<Page
width={pdfWidthRef.current} // 使用 ref 存储的宽度
key={`page_${index + 1}`}
pageNumber={index + 1}
onLoadSuccess={({ width }) => setPdfWidth(width)}
/>
</div>
))}
</Document>
</div>
<div className='operate'>
<Button icon={<VerticalAlignTopOutlined />} onClick={scrollToTop}></Button>
<Button icon={<VerticalAlignBottomOutlined />} onClick={scrollToBottom}></Button>
</div>
</div>
<div className='pdf-right'>
<div className='pdf-right-top'>位置信息</div>
</div>
</div>
基础操作:接下来开始实现预览pdf顶部的一些按钮操作,实现放大缩小、切换分页、回到顶部底部操作,核心逻辑就是改变pdf预览的宽度以及切换滚动距离,很容易就实现了,代码如下所示:
// 防抖处理滑块值变化
const handlePercentageChange = useCallback(
debounce((value: number) => {
setPercentage(value);
const newWidth = 600 * (value / 100);
setPdfWidth(newWidth);
pdfWidthRef.current = newWidth;
}, 100), []
);
const handlePageChange = (pageNumber: number) => {
const targetPageRef = pageRefs.current[pageNumber - 1];
if (targetPageRef && scrollContainerRef.current) {
const offsetTop = targetPageRef.offsetTop;
scrollContainerRef.current.scrollTop = offsetTop -80;
}
};
// 平滑滚动到顶部
const scrollToTop = () => {
if (scrollContainerRef.current) {
smoothScrollTo(0);
}
};
// 平滑滚动到底部
const scrollToBottom = () => {
if (scrollContainerRef.current) {
const maxScrollHeight = scrollContainerRef.current.scrollHeight;
smoothScrollTo(maxScrollHeight);
}
};
// 平滑滚动到指定位置
const smoothScrollTo = (targetPosition: number) => {
if (scrollContainerRef.current) {
const currentPosition = scrollContainerRef.current.scrollTop;
const distance = targetPosition - currentPosition;
const duration = 500; // 滚动持续时间(毫秒)
let startTime: number;
const scrollStep = (timestamp: number) => {
if (!startTime) startTime = timestamp;
const progress = timestamp - startTime;
const increment = distance * (progress / duration);
scrollContainerRef.current.scrollTop = currentPosition + increment;
if (progress < duration) {
requestAnimationFrame(scrollStep);
} else {
scrollContainerRef.current.scrollTop = targetPosition;
}
};
requestAnimationFrame(scrollStep);
}
};
全屏操作:对于全屏操作这里我封装了个组件,然后直接引入该组件进行使用就行了,如下所示:
import { useState } from "react";
import { ArrowsAltOutlined, ShrinkOutlined } from "@ant-design/icons";
const IsFullScreen = () => {
const [isFullScreen, setIsFullScreen] = useState(false);
// 进入全屏模式的函数
const enterFullScreen = () => {
const elem: any = document.documentElement; // 获取整个文档元素
if (elem.requestFullscreen) {
elem.requestFullscreen(); // 标准全屏
} else if (elem.mozRequestFullScreen) {
elem.mozRequestFullScreen(); // Firefox
} else if (elem.webkitRequestFullscreen) {
elem.webkitRequestFullscreen(); // Chrome, Safari 和 Opera
} else if (elem.msRequestFullscreen) {
elem.msRequestFullscreen(); // Internet Explorer / Edge
}
setIsFullScreen(true); // 更新状态,进入全屏
};
// 退出全屏模式的函数
const exitFullScreen = () => {
const elem: any = document; // 获取整个文档元素
if (elem.exitFullscreen) {
elem.exitFullscreen(); // 退出全屏
} else if (elem.mozCancelFullScreen) {
elem.mozCancelFullScreen(); // Firefox
} else if (elem.webkitExitFullscreen) {
elem.webkitExitFullscreen(); // Chrome, Safari 和 Opera
} else if (elem.msExitFullscreen) {
elem.msExitFullscreen(); // Internet Explorer / Edge
}
setIsFullScreen(false); // 更新状态,退出全屏
};
return (
<div style={{ cursor: 'pointer' }} onClick={isFullScreen ? exitFullScreen : enterFullScreen}>
{isFullScreen ? <ShrinkOutlined /> : <ArrowsAltOutlined />}
</div>
);
};
export default IsFullScreen;
至此对于pdf的预览以及一些基础操作完成了,感兴趣的朋友可以继续开发其他功能!