h5单页预览PDF文件模糊问题解决
这是h5单页预览PDF文件的补充
https://blog.csdn.net/motoudi/article/details/147362883?spm=1011.2415.3001.10575&sharefrom=mp_manage_link
目的:
在小程序进行文件预览,使用的是H5单页写的预览页面。
之所以采用这种方式是因为需要在预览页面上放置其他功能按钮。
该单页预览能实现基本的拖拽、缩放功能。
问题:
在预览PDF文件的时候是使用的pdf.min.js和pdf.worker.min.js,将PDF文件使用canvas绘制出来,转为普通图片进行预览。但是绘制出来的图片文字看不清楚、失真。
解决办法:
在绘制的时候按照比例将其放大然后再绘制,这样绘制出一副放大的图片,在显示的时候再将其缩放回原来的比例尺寸。这样肉眼看到的PDF文件中的文字就能清晰。
异步加载PDF文件
pdfjsLib.getDocument(url).promise.then(function(pdfDoc_){})
分页渲染每一页的PDF页面
page.getViewport() 是 PDF.js 库中的一个重要方法,用于获取页面的视口(viewport)信息。
视口(Viewport)的作用:
视口定义了如何将PDF页面映射到canvas上的一个矩形区域,它包含了:
- 尺寸信息:页面的宽度和高度
- 缩放比例:页面的显示缩放级别
- 变换矩阵:坐标系的转换信息
pdfDoc.getPage(num).then(function(page) {// 在获取pdf窗口信息的时候,我们直接去获取放大后的pdf尺寸,按照这个尺寸绘制const viewport = page.getViewport({ scale: scale });
}
绘制:
// 创建容器divconst pageDiv = document.createElement('div');pageDiv.className = 'pdf-page';pageDiv.id = `page-${num}`;// 移除旧的页面元素const oldPage = document.getElementById(`page-${num}`);if (oldPage) oldPage.remove();// 创建canvas元素const canvas = document.createElement('canvas');const context = canvas.getContext('2d');// 设置canvas尺寸canvas.height = viewport.height;canvas.width = viewport.width;// 将canvas添加到页面容器pageDiv.appendChild(canvas);// 将页面容器添加到PDF容器pdfContainer.appendChild(pageDiv);// 渲染PDF页面到canvas上const renderContext = {canvasContext: context,viewport: viewport};const renderTask = page.render(renderContext);
预览页面:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0"><title></title><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;background-color: #f5f5f5;color: #333;height: 100vh;}.modal-content {width: 100%;height: 100%;display: flex;flex-direction: column;}#pdf-container {width: 100%;flex-grow: 1;}#video-container {width: 100%;margin: auto;flex-grow: 1;display: flex;align-items: center;position: relative;}#image-container {width: 100%;min-height: 100vh;flex-grow: 1;display: flex;align-items: center;justify-content: center;}#image-container img {width: 100%;}.loading {width: 100%;height: 100vh;background: rgba(0, 0, 0, 0.5);display: flex;justify-content: center;align-items: center;color: white;}/* 如果你想要一个旋转动画 */.loading::after {content: "";width: 30px;height: 30px;border: 3px solid rgba(255, 255, 255, 0.3);border-radius: 50%;border-top-color: white;animation: spin 1s linear infinite;}@keyframes spin {to { transform: rotate(360deg); }}.error {color: white;text-align: center;margin-top: 50%;padding: 20px;}.video-js .vjs-control-bar{height: 5em !important;padding-bottom: 2em;}.video-js .vjs-big-play-button{top: 50% !important;left: 50% !important;transform: translate(-50%, -50%);}/* 视频暂停时显示大播放按钮 */.video-js.vjs-paused .vjs-big-play-button,.video-js.vjs-ended .vjs-big-play-button {display: flex !important;opacity: 1 !important;visibility: visible !important;}/* PDF 控制栏样式 */.pdf-controls {position: fixed;bottom: 20px;left: 50%;transform: translateX(-50%);background: rgba(0, 0, 0, 0.7);padding: 10px 15px;border-radius: 20px;display: flex;align-items: center;color: white;z-index: 100;}.pdf-controls button {background: none;border: none;color: white;font-size: 16px;padding: 5px 10px;cursor: pointer;}.pdf-controls span {margin: 0 10px;}.pdf-page {margin-bottom: 20px;box-shadow: 0 2px 5px rgba(0,0,0,0.1);width: 100% !important;height: auto !important;}.pdf-page canvas {width: 100% !important;height: auto !important;display: block;}/* 添加点击区域覆盖层 */.video-click-area {position: absolute;top: 0;left: 0;width: 100%;height: calc(100% - 4em);z-index: 10;}</style><!-- Video.js 视频播放器 -->
<!-- <link href="https://vjs.zencdn.net/7.20.3/video-js.css" rel="stylesheet">-->
<!-- <script src="https://vjs.zencdn.net/7.20.3/video.min.js"></script>-->
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.min.js"></script>-->
<!-- <script>-->
<!-- // 设置PDF.js worker路径-->
<!-- pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.10.377/pdf.worker.min.js';-->
<!-- </script>--><!-- Video.js 视频播放器 --><link href="./static/preview/video-js.css" rel="stylesheet"><script src="./static/preview/video.min.js"></script><script src="./static/preview/pdf.min.js"></script><script src="./static/dayjs.js"></script><script src="./static/md5.js"></script><script src="./static/wechat.env.js"></script><script>// 设置PDF.js worker路径pdfjsLib.GlobalWorkerOptions.workerSrc = './static/preview/pdf.worker.min.js';</script>
</head>
<body>
<!-- 预览模态框 -->
<div id="preview-modal" class="modal"><div class="modal-content"><div id="image-container" style="display: none;"><img id="preview-image" src="" class="img-box"></div><div id="video-container" style="display: none;"><video id="preview-video" class="video-js" controls playsinline style="width: 100%;height: 100vh;"></video><div class="video-click-area" id="video-click-area" style="display: none;"></div></div><div id="pdf-container" style="display: none;"></div><div id="loading" class="loading"></div><div id="error" class="error" style="display: none;"></div><!-- PDF 控制栏 --><div id="pdf-controls" class="pdf-controls" style="display: none;"><button id="prev-page">上一页</button><span id="page-num">1 / 1</span><button id="next-page">下一页</button></div></div>
</div><script>// 获取URL参数function getQueryParam(name) {const urlParams = new URLSearchParams(window.location.search);return urlParams.get(name);}// 获取文件类型function getFileType(url) {if (!url) return null;// 提取文件扩展名const extension = url.split('.').pop().toLowerCase().split('?')[0];// 图片类型const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];if (imageTypes.includes(extension)) return 'image';// 视频类型const videoTypes = ['mp4', 'webm', 'ogg', 'mov'];if (videoTypes.includes(extension)) return 'video';// PDF类型if (extension === 'pdf') return 'pdf';return null;}// 预览文件函数function previewFile(url) {const modal = document.getElementById('preview-modal');const loading = document.getElementById('loading');const errorDiv = document.getElementById('error');const imageContainer = document.getElementById('image-container');const videoContainer = document.getElementById('video-container');const pdfContainer = document.getElementById('pdf-container');const videoClickArea = document.getElementById('video-click-area');// 显示加载中和模态框loading.style.display = 'flex';errorDiv.style.display = 'none';imageContainer.style.display = 'none';videoContainer.style.display = 'none';pdfContainer.style.display = 'none';videoClickArea.style.display = 'none';modal.style.display = 'block';console.log('加载开始');// 获取文件类型const fileType = getFileType(url);if (!fileType) {loading.style.display = 'none';errorDiv.style.display = 'block';errorDiv.textContent = '不支持的文件类型或URL格式不正确';return;}// 根据文件类型处理if (fileType === 'image') {const img = document.getElementById('preview-image');img.alt = '图片预览';img.onload = function() {loading.style.display = 'none';imageContainer.style.display = 'flex';};img.onerror = function() {showError('图片加载失败');};img.src = url;}else if (fileType === 'video') {const metaViewport = document.querySelector('meta[name="viewport"]');if (metaViewport) {metaViewport.setAttribute('content','width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no');}videoContainer.style.display = 'flex';videoClickArea.style.display = 'block';// 初始化视频播放器const player = videojs('preview-video', {controls: true,autoplay: true,preload: 'auto',playsinline: true,poster: url + '?vframe/png/offset/1',sources: [{src: url,type: getVideoMimeType(url)}]});loading.style.display = 'none';player.on('error', function() {showError('视频加载失败');});// 添加点击事件处理videoClickArea.addEventListener('click', function() {if (player.paused()) {player.play();} else {player.pause();}});// 监听播放状态变化,确保暂停时显示大播放按钮player.on('pause', function() {const bigPlayButton = player.bigPlayButton;bigPlayButton.show();});player.on('play', function() {const bigPlayButton = player.bigPlayButton;bigPlayButton.hide();});// 存储播放器实例以便关闭时销毁modal.dataset.player = player;}else if (fileType === 'pdf') {pdfContainer.style.display = 'block';const previewContainer = document.getElementById('pdf-container');// 存储PDF相关变量let pdfDoc = null;let currentPage = 1;let pageRendering = false;let pageNumPending = null;const scale = 1.0;// 获取DOM元素const prevPageBtn = document.getElementById('prev-page');const nextPageBtn = document.getElementById('next-page');const pageNumSpan = document.getElementById('page-num');// 渲染PDF页面function renderPage(num) {pageRendering = true;// 使用promise获取页面pdfDoc.getPage(num).then(function(page) {const viewport = page.getViewport({ scale: scale });// 创建容器divconst pageDiv = document.createElement('div');pageDiv.className = 'pdf-page';pageDiv.id = `page-${num}`;// 移除旧的页面元素const oldPage = document.getElementById(`page-${num}`);if (oldPage) oldPage.remove();// 创建canvas元素const canvas = document.createElement('canvas');const context = canvas.getContext('2d');// 设置canvas尺寸canvas.height = viewport.height;canvas.width = viewport.width;// 将canvas添加到页面容器pageDiv.appendChild(canvas);// 将页面容器添加到PDF容器pdfContainer.appendChild(pageDiv);// 渲染PDF页面到canvas上const renderContext = {canvasContext: context,viewport: viewport};const renderTask = page.render(renderContext);// 渲染完成renderTask.promise.then(function() {pageRendering = false;if (pageNumPending !== null) {// 有新页面要渲染renderPage(pageNumPending);pageNumPending = null;}// 更新页面显示pageNumSpan.textContent = `${currentPage} / ${pdfDoc.numPages}`;// 加载完成if (currentPage === 1) {loading.style.display = 'none';}});});}// 跳转到指定页面function gotoPage(num) {if (pageRendering) {pageNumPending = num;} else if (num !== currentPage && num > 0 && num <= pdfDoc.numPages) {currentPage = num;renderPage(currentPage);// 滚动到页面顶部const pageElement = document.getElementById(`page-${currentPage}`);if (pageElement) {pageElement.scrollIntoView();}}}// 上一页prevPageBtn.onclick = function() {if (currentPage <= 1) return;gotoPage(currentPage - 1);};// 下一页nextPageBtn.onclick = function() {if (currentPage >= pdfDoc.numPages) return;gotoPage(currentPage + 1);};// 异步加载PDF文档pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {pdfDoc = pdfDoc_;// 更新页面总数显示pageNumSpan.textContent = `1 / ${pdfDoc.numPages}`;// 初始渲染第一页renderPage(1);// 预加载后续页面for (let i = 2; i <= Math.min(999999999999, pdfDoc.numPages); i++) {renderPage(i);}}).catch(function(error) {console.error('Error loading PDF:', error);showError('PDF加载失败');});}}// 获取视频MIME类型function getVideoMimeType(url) {const extension = url.split('.').pop().toLowerCase().split('?')[0];switch(extension) {case 'mp4': return 'video/mp4';case 'webm': return 'video/webm';case 'ogg': return 'video/ogg';default: return 'video/mp4';}}// 显示错误信息function showError(message) {document.getElementById('loading').style.display = 'none';const errorDiv = document.getElementById('error');errorDiv.style.display = 'block';errorDiv.textContent = message;}// 关闭模态框function closeModal() {const modal = document.getElementById('preview-modal');modal.style.display = 'none';// 如果有视频播放器实例,则销毁它if (modal.dataset.player) {modal.dataset.player.dispose();delete modal.dataset.player;}// 如果有图片查看器实例,则销毁它if (modal.dataset.viewer) {modal.dataset.viewer.destroy();delete modal.dataset.viewer;}}// 初始化请求字段function initParams() {const salt = '81ad0be7fd53914f8cf8193c1886f635'const timestamp = dayjs().unix();const sign = hex_md5(timestamp + salt);const channel = 3;const reqConfig = {timestamp,sign,channel,};return reqConfig;}// post请求方法function postRequest(url, data, additionalHeaders = {}) {const headers = {'Content-Type': 'application/json',...additionalHeaders // 允许传入额外的头部信息};const pageUrl = window.location.originconst reqUrl = pageUrl + urlconst reqData = {...initParams(),...data,}return fetch(reqUrl, {method: 'POST', // 指定请求方法为POSTheaders: headers,body: JSON.stringify(reqData) // 将JavaScript对象转换为JSON字符串}).then(response => {if (!response.ok) { // 检查响应是否成功throw new Error('Network response was not ok ' + response.statusText);}return response.json(); // 假设服务器返回的是JSON格式的数据}).catch(error => {console.error('There has been a problem with your fetch operation:', error);throw error; // 重新抛出错误以便调用者处理});}// 点击模态框背景关闭document.getElementById('preview-modal').addEventListener('click', function(e) {if (e.target === this) {closeModal();}});// 页面加载完成后自动处理document.addEventListener('DOMContentLoaded', function() {document.getElementById('loading').style.display = 'flex';const fileUrl = getQueryParam('url');const fileId = getQueryParam('fileId')if (fileId) {postRequest('/api/net_disk_file/no_login_detail', { id: fileId }).then(async res => {if (res.success) {previewFile(res.data.url);} else {showError('未提供文件URL参数');document.getElementById('preview-modal').style.display = 'block';document.getElementById('loading').style.display = 'none';}}).catch(err => {showError('未提供文件URL参数');document.getElementById('preview-modal').style.display = 'block';document.getElementById('loading').style.display = 'none';})} else {previewFile(fileUrl);}});
</script>
</body>
</html>
