主页 > 游戏开发  > 

Javascript网页设计案例:通过PDF.js实现一款PDF阅读器,包括预览、页面旋转、页面切换、放大缩小、

Javascript网页设计案例:通过PDF.js实现一款PDF阅读器,包括预览、页面旋转、页面切换、放大缩小、
前言

目前功能包括:

切换到首页。切换到尾页。上一页。下一页。添加标签。标签管理页面旋转页面随意拖动双击后还原位置

其实按照自己的预期来说,有很多功能还没有开发完,配色也没有全都搞完,先发出来吧,后期有需要继续添加功能。

功能预览 1.加载后的主页面

白天模式 黑夜模式

2.功能区

一、关于这款PDF阅读器的功能说明 1. 基本布局与样式 具有响应式设计,通过meta标签设置视口以适应不同设备宽度。支持主题切换(明暗模式),定义了一系列CSS变量来管理颜色和布局相关属性,方便切换不同主题风格。 2. 工具栏功能 导航功能:包含首页、上一页、下一页、末页的导航按钮,对应firstPage()、prevPage()、nextPage()、lastPage()函数。缩放功能:有缩小、放大按钮,分别对应zoomOut()和zoomIn()函数,还提供了一个范围输入控件#zoomControl用于更精确地控制缩放比例。旋转功能:顺时针旋转和逆时针旋转按钮,对应rotateClockwise()和rotateCounterClockwise()函数。书签功能:可以输入书签名称,点击添加书签按钮(addBookmark()函数)添加书签,还有书签管理按钮(toggleBookmarkPanel()函数)用于管理已添加的书签。主题切换:有一个主题切换按钮(toggleTheme()函数),点击可切换明暗主题。 3. 侧边栏与缩略图 侧边栏(.sidebar)用于显示PDF文档的缩略图,缩略图容器为#thumbnails,每个缩略图项(.thumbnail-item)包含一个canvas元素用于显示缩略图,点击缩略图项可进行相关操作。 4. 主内容区域 主内容区域(.main-content)包含PDF文档的显示区域(#pdf-container),其中有一个canvas元素(#pdf-canvas)用于渲染PDF页面,用户可以通过鼠标抓取操作(cursor: -webkit-grab)进行交互。状态栏(.status-bar)用于显示一些状态信息,如页码等。 5. 模态窗口 书签管理模态窗口(#bookmarks-modal),默认隐藏,用于管理已添加的书签,包含书签列表(#bookmarks-list)和一些操作按钮(.modal-buttons)。 二、Html代码 <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href=" cdnjs.cloudflare /ajax/libs/font-awesome/6.0.0/css/all.min.css"> <title>PDF阅读器</title> <style> :root { --primary-color: #2196F3; --hover-color: #1976D2; --background-color: #1a1a1a; --surface-color: #2d2d2d; --toolbar-bg: #333333; --text-color: #ffffff; --error-color: #ff4444; --border-radius: 8px; --gap: 20px; --toolbar-height: 60px; } body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: var(--background-color); color: var(--text-color); box-sizing: border-box; } .pdf-reader-container { display: flex; gap: var(--gap); max-width: 100%; min-width: 800px; margin: 0 auto; background-color: var(--surface-color); border-radius: var(--border-radius); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); padding: var(--gap) ; /* 0 上下内边距 */ margin-top: var(--toolbar-height); box-sizing: border-box; flex-wrap: nowrap; } .toolbar { position: fixed; top: 0; left: 0; right: 0; height: var(--toolbar-height); display: flex; align-items: center; justify-content: space-between; padding: 0 var(--gap); background-color: var(--toolbar-bg); border-radius: 0 0 var(--border-radius) var(--border-radius); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); z-index: 1000; box-sizing: border-box; } .logo { display: flex; align-items: center; padding: 0 20px; height: 60%; } .logo img { height: 100%; object-fit: contain; transition: transform 0.3s ease; } .toolbar-buttons { display: flex; gap: 10px; } /* 修改按钮样式部分 */ button { padding: 8px 12px; font-size: 16px; cursor: pointer; background-color: var(--primary-color); color: var(--text-color); border: none; border-radius: 50%; aspect-ratio: 1/1; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; position: relative; } button:hover { background-color: var(--hover-color); transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } button::before { font-family: "Font Awesome 5 Free"; font-weight: 900; } /* 为每个按钮指定图标 */ button:nth-child(1)::before { content: "\f100"; } /* 首页 */ button:nth-child(2)::before { content: "\f104"; } /* 上一页 */ button:nth-child(3)::before { content: "\f105"; } /* 下一页 */ button:nth-child(4)::before { content: "\f101"; } /* 末页 */ button:nth-child(5)::before { content: "\f068"; } /* 缩小 */ button:nth-child(7)::before { content: "\f067"; } /* 放大 */ button:nth-child(8)::before { content: "\f2f9"; } /* 顺时针旋转 */ button:nth-child(9)::before { content: "\f2ea"; } /* 逆时针旋转 */ button:nth-child(11)::before { content: "\f02e"; } /* 添加书签 */ button:nth-child(12)::before { content: "\f02e"; } /* 书签管理 */ button span { display: none; } .toolbar-buttons { display: flex; gap: 8px; align-items: center; } #zoomControl { width: 100px; height: 6px; background: var(--surface-color); border-radius: 3px; } #bookmarkLabel { padding: 8px; border-radius: var(--border-radius); border: 1px solid var(--primary-color); background: var(--surface-color); color: var(--text-color); } button::after { content: attr(aria-label); position: absolute; bottom: -30px; left: 50%; transform: translateX(-50%); background: var(--toolbar-bg); color: var(--text-color); padding: 4px 8px; border-radius: 4px; font-size: 12px; white-space: nowrap; opacity: 0; transition: opacity 0.2s; pointer-events: none; } button:hover::after { opacity: 1; } ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background-color: var(--toolbar-bg); border-radius: 4px; } ::-webkit-scrollbar-thumb { background-color: var(--primary-color); border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background-color: var(--hover-color); } /* 侧边栏样式 */ .sidebar { flex: 0 0 250px; background-color: var(--toolbar-bg); border-radius: var(--border-radius); padding: 16px; height: calc(100vh - var(--toolbar-height) - var(--gap)*2 - 40px); overflow-y: auto; box-sizing: border-box; } .thumbnail-container { display: flex; flex-direction: column; gap: 10px; } .thumbnail-item { position: relative; cursor: pointer; border-radius: 4px; overflow: hidden; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } .thumbnail-item canvas { width: 100%; height: auto; } .thumbnail-item.active { border: 2px solid var(--primary-color); } .main-content { flex: 1; background-color: var(--surface-color); border-radius: var(--border-radius); /* padding: 16px; */ height: calc(100vh - var(--toolbar-height) - var(--gap)*2 - 40px); box-sizing: border-box; } #pdf-container { width: 100%; height: 96%; /* height: calc(100vh - var(--toolbar-height) - var(--gap)*2 - 80px - 40px); */ border: 1px solid var(--toolbar-bg); border-radius: var(--border-radius); overflow: auto; background-color: #333333; /* padding: 16px; */ box-sizing: border-box; overflow-x: hidden; } .status-bar { height: 4%; font-size: 14px; color: var(--text-color); /* padding: 5px; */ background-color: var(--toolbar-bg); border-radius: var(--border-radius); margin-top: auto; } .error-message { color: var(--error-color); padding: 10px; margin-top: 10px; background-color: #4d4d4d; border-radius: var(--border-radius); } /* 模态窗口样式 */ .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 1000; transition: opacity 0.3s ease; } .modal-content { background-color: var(--toolbar-bg); margin: 15% auto; padding: 20px; width: 80%; max-width: 500px; position: relative; border-radius: var(--border-radius); } .close { position: absolute; right: 20px; top: 10px; font-size: 28px; cursor: pointer; color: var(--text-color); } .close:hover { color: var(--primary-color); } #bookmarks-list { list-style-type: none; padding: 0; margin: 0; } #bookmarks-list li { padding: 8px; margin: 4px 0; cursor: pointer; border-radius: 4px; } #bookmarks-list li:hover { background-color: var(--primary-color); color: var(--text-color); } .modal-buttons { margin-top: 20px; } .modal-buttons button { margin: 0 5px; } canvas#pdf-canvas { position: relative; cursor: -webkit-grab; } .theme-toggle { position: relative; display: inline-block; } .light-theme { --background-color: #f9fafb; --surface-color: #ffffff; --toolbar-bg: #e5e7eb; --text-color: #374151; --error-color: #ef4444; --primary-color: #3b82f6; --hover-color: #2563eb; } .light-theme .toolbar { background-color: var(--toolbar-bg); } .light-theme .sidebar { background-color: var(--toolbar-bg); } .light-theme .main-content { background-color: var(--surface-color); } .light-theme button { background-color: var(--primary-color); color: var(--text-color); } .light-theme .status-bar { background-color: var(--toolbar-bg); } </style> </head> <body> <div class="toolbar"> <div class="logo"> <img src="logo.png" alt="Logo"> </div> <div class="toolbar-buttons"> <button onclick="firstPage()" aria-label="首页"><span>首页</span></button> <button onclick="prevPage()" aria-label="上一页"><span>上一页</span></button> <button onclick="nextPage()" aria-label="下一页"><span>下一页</span></button> <button onclick="lastPage()" aria-label="末页"><span>末页</span></button> <button onclick="zoomOut()" aria-label="缩小"><span>缩小</span></button> <input type="range" min="0.5" max="2.0" step="0.1" value="1.0" id="zoomControl"> <button onclick="zoomIn()" aria-label="放大"><span>放大</span></button> <button onclick="rotateClockwise()" aria-label="顺时针旋转"><span>顺时针旋转</span></button> <button onclick="rotateCounterClockwise()" aria-label="逆时针旋转"><span>逆时针旋转</span></button> <input type="text" id="bookmarkLabel" placeholder="输入书签名称"> <button onclick="addBookmark()" aria-label="添加书签"><span>添加书签</span></button> <button onclick="toggleBookmarkPanel()" aria-label="书签管理"><span>书签管理</span></button> <button onclick="toggleTheme()" aria-label="切换主题" class="theme-toggle"> <i class="fas fa-moon"></i> </button> </div> </div> <div class="pdf-reader-container"> <div class="sidebar"> <div class="thumbnail-container" id="thumbnails"></div> </div> <div class="main-content"> <div id="pdf-container"><canvas id="pdf-canvas"></canvas></div> <div class="status-bar" id="status-bar"></div> </div> </div> <div id="bookmarks-modal" class="modal" style="display: none;"> <div class="modal-content"> <span class="close" onclick="closeBookmarkPanel()">&times;</span> <h3>书签管理</h3> <ul id="bookmarks-list"></ul> <div class="modal-buttons"> <button onclick="addBookmark()">添加书签</button> <button onclick="clearBookmarks()">清除书签</button> <button onclick="closeBookmarkPanel()">关闭</button> </div> </div> </div> <script src="pdf.min.js"></script> <script src="pdf-reader.js"></script> </body> </html> 三、Javascript代码 let doc = null; let currentPage = 1; let scale = 1.5; let rotation = 0; let bookmarks = []; let isDragging = false; let lastX = 0; let lastY = 0; let offsetX = 0; let offsetY = 0; let thumbnails = []; const thumbnailsContainer = document.getElementById('thumbnails'); const bookmarksList = document.getElementById('bookmarks-list'); const bookmarksPanel = document.getElementById('bookmarks-panel'); const container = document.getElementById('pdf-container'); // 新增变量用于存储当前视口的偏移量 let viewportOffsetX = 0; let viewportOffsetY = 0; // 在window.onload 中添加键盘事件监听 window.addEventListener('keydown', function(e) { if (e.key === 'ArrowLeft') { prevPage(); } else if (e.key === 'ArrowRight') { nextPage(); } }); // 初始化滚动条 const zoomControl = document.getElementById('zoomControl'); zoomControl.value = scale.toString(); // 初始化旋转状态 const savedRotation = localStorage.getItem('pdfRotation'); if (savedRotation !== null) { rotation = parseInt(savedRotation); } // 加载PDF文件 function loadPDF(fileUrl) { pdfjsLib.getDocument(fileUrl).promise.then(function (loadedDoc) { doc = loadedDoc; const numPages = doc.numPages; loadPage(currentPage); createThumbnails(numPages); // 更新状态栏 document.getElementById('status-bar').textContent = `第 ${currentPage} 页 / 共 ${numPages} 页 | 缩放比例: ${scale * 100}% | 旋转: ${rotation}°`; }).catch(function (error) { console.error('Error loading PDF:', error); showError(`加载PDF时出错:${error.message}`); }); } // 创建缩略图 function createThumbnails(numPages) { thumbnailsContainer.innerHTML = ''; for (let i = 1; i <= numPages; i++) { const thumbnailItem = document.createElement('div'); thumbnailItem.className = 'thumbnail-item'; thumbnailItem.dataset.page = i; const canvas = document.createElement('canvas'); thumbnailItem.appendChild(canvas); thumbnailsContainer.appendChild(thumbnailItem); loadThumbnail(i, canvas); thumbnailItem.addEventListener('click', function() { currentPage = i; loadPage(currentPage); updateThumbnailsActiveState(); }); } } // 更新缩略图活动状态 function updateThumbnailsActiveState() { const items = thumbnailsContainer.querySelectorAll('.thumbnail-item'); items.forEach(item => { item.classList.remove('active'); }); const currentItem = thumbnailsContainer.querySelector(`[data-page="${currentPage}"]`); if (currentItem) { currentItem.classList.add('active'); } } // 加载指定页面 // 修改loadPage函数 function loadPage(pageNum) { if (!doc) { showError('未加载PDF文件,请检查文件路径。'); return; } doc.getPage(pageNum).then(function (page) { while (container.firstChild) { container.removeChild(container.firstChild); } const defaultViewport = page.getViewport({ scale: scale }); const containerWidth = container.clientWidth; const containerHeight = container.clientHeight; const newScale = Math.min( containerWidth / defaultViewport.width, containerHeight / defaultViewport.height ); const finalScale = Math.max(0.5, Math.min(2.0, newScale)); const viewport = page.getViewport({ scale: scale, rotation: rotation }); const canvas = document.createElement('canvas'); canvas.id = 'pdf-canvas'; container.appendChild(canvas); adjustCanvasSize(canvas, viewport); const context = canvas.getContext('2d'); if (!context) { showError('无法获取Canvas上下文,请检查浏览器支持。'); return; } page.render({ canvasContext: context, viewport: viewport }).promise.then(() => { console.log('Page rendered successfully'); hideError(); // 初始化偏移量 viewportOffsetX = (container.clientWidth - canvas.width) / 2; viewportOffsetY = 0; updateCanvasPosition(); }).catch(error => { console.error('Error rendering page:', error); showError(`渲染页面时出错:${error.message}`); }); document.getElementById('status-bar').textContent = `第 ${pageNum} 页 / 共 ${doc.numPages} 页 | 缩放比例: ${Number(scale.toFixed(1)) * 100}% | 旋转: ${rotation}°`; }).catch(error => { console.error('Error getting page:', error); showError(`获取页面时出错:${error.message}`); }); } // 调整Canvas尺寸以适应页面内容 function adjustCanvasSize(canvas, viewport) { canvas.width = viewport.width; canvas.height = viewport.height; const containerRect = container.getBoundingClientRect(); const canvasRect = canvas.getBoundingClientRect(); canvas.style.left = `${(containerRect.width - canvasRect.width) / 2}px`; canvas.style.top = `${(containerRect.height - canvasRect.height) / 2}px`; } // 修改导航函数 function firstPage() { currentPage = 1; loadPage(currentPage); } function prevPage() { currentPage = Math.max(1, currentPage - 1); loadPage(currentPage); } function nextPage() { currentPage = Math.min(doc.numPages, currentPage + 1); loadPage(currentPage); } function lastPage() { currentPage = doc.numPages; loadPage(currentPage); } // 缩放功能 function zoomIn() { scale = Math.min(2.0, scale + 0.1); zoomControl.value = scale.toString(); updateZoomButtons(); loadPage(currentPage); } function zoomOut() { scale = Math.max(0.5, scale - 0.1); zoomControl.value = scale.toString(); updateZoomButtons(); loadPage(currentPage); } // 错误提示功能 function showError(message) { const errorDiv = document.createElement('div'); errorDiv.className = 'error-message'; errorDiv.textContent = message; document.body.appendChild(errorDiv); } function hideError() { const errorDiv = document.querySelector('.error-message'); if (errorDiv) { errorDiv.remove(); } } // 旋转功能 function rotateClockwise() { rotation += 90; if (rotation >= 360) rotation = 0; localStorage.setItem('pdfRotation', rotation); loadPage(currentPage); } function rotateCounterClockwise() { rotation -= 90; if (rotation < 0) rotation += 360; localStorage.setItem('pdfRotation', rotation); loadPage(currentPage); } // 初始化PDF阅读器 window.onload = function () { const fileUrl = ' raw.githubusercontent /mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf'; loadPDF(fileUrl); // 恢复旋转状态 if (savedRotation !== null) { rotation = parseInt(savedRotation); loadPage(currentPage); } // 加载保存的书签 loadSavedBookmarks(); // 新增事件监听 const PDF = document.getElementById('pdf-container'); console.log(PDF); PDF.addEventListener('mousedown', startDrag); PDF.addEventListener('mousemove', drag); PDF.addEventListener('mouseup', stopDrag); PDF.addEventListener('wheel', zoomWithWheel); PDF.addEventListener('dblclick', resetPosition); const currentTheme = localStorage.getItem('theme') || 'dark'; if (currentTheme === 'light') { document.body.classList.add('light-theme'); } }; // 新增拖拽功能 function startDrag(e) { if (e.button === 0) { // 左键点击 isDragging = true; lastX = e.clientX; lastY = e.clientY; } } // 双击还原 function resetPosition(e) { canvas = document.getElementById("pdf-canvas"); viewportOffsetX = (container.clientWidth - canvas.width) / 2; viewportOffsetY = 0; updateCanvasPosition(); } function drag(e) { if (isDragging) { const deltaX = e.clientX - lastX; const deltaY = e.clientY - lastY; // 更新偏移量 viewportOffsetX += deltaX; viewportOffsetY += deltaY; // 限制偏移范围 const canvas = document.getElementById('pdf-canvas'); // 更新canvas位置 updateCanvasPosition(); lastX = e.clientX; lastY = e.clientY; } } function stopDrag() { isDragging = false; } // 新增滚轮缩放功能 function zoomWithWheel(e) { e.preventDefault(); const delta = e.deltaY; if (delta > 0) { zoomOut(); } else { zoomIn(); } } // 更新canvas位置 function updateCanvasPosition() { const canvas = document.getElementById('pdf-canvas'); if (canvas) { canvas.style.left = `${viewportOffsetX}px`; canvas.style.top = `${viewportOffsetY}px`; } } /** * 更新缩放按钮的显示 */ function updateZoomButtons() { const buttons = document.querySelectorAll('button'); buttons.forEach(button => { if (button.onclick === zoomIn) { button.textContent = `放大 (${scale.toFixed(1)})`; } else if (button.onclick === zoomOut) { button.textContent = `缩小 (${scale.toFixed(1)})`; } }); } /** * 处理滚动条输入事件 */ zoomControl.addEventListener('input', function (e) { const inputValue = parseFloat(e.target.value); // 反向计算 scale 值 scale = inputValue - 0.1; updateZoomButtons(); // 使用节流处理以优化性能 throttledLoadPage(currentPage); }); /** * 节流函数 * @param {function} func 目标函数 * @param {number} wait 延迟时间(毫秒) * @returns {function} 节流后的函数 */ function throttle(func, wait) { let timeout; return function (...args) { if (!timeout) { func.apply(this, args); timeout = setTimeout(() => { timeout = null; }, wait); } }; } // 对 loadPage 进行节流处理 const throttledLoadPage = throttle(loadPage, 100); // 监听窗口调整大小事件 window.addEventListener('resize', function () { if (doc && currentPage) { loadPage(currentPage); } }); // 切换书签面板显示状态 function toggleBookmarkPanel() { const isHidden = bookmarksPanel.style.display === 'none'; bookmarksPanel.style.display = isHidden ? 'block' : 'none'; if (isHidden) { loadBookmarks(); } } // 关闭书签面板 function closeBookmarkPanel() { bookmarksPanel.style.display = 'none'; } // 添加当前页面为书签 function addBookmark() { const bookmarkLabel = document.getElementById('bookmarkLabel').value.trim(); if (!bookmarkLabel) { showError('请输入书签名称'); return; } const bookmark = { page: currentPage, scale: scale, label: bookmarkLabel }; bookmarks.push(bookmark); saveBookmarks(); loadBookmarks(); document.getElementById('bookmarkLabel').value = ''; // 清空输入框 } // 加载并显示所有书签 function loadBookmarks() { const bookmarksList = document.getElementById('bookmarks-list'); bookmarksList.innerHTML = ''; bookmarks.forEach((bookmark, index) => { const li = document.createElement('li'); li.innerHTML = ` <span>${bookmark.label}</span> <button onclick="jumpToBookmark(${index})">跳转</button> <button onclick="deleteBookmark(${index})">删除</button> `; li.style.backgroundColor = '#4CAF50'; li.style.color = 'white'; li.style.padding = '8px'; li.style.margin = '4px 0'; li.style.borderRadius = '4px'; bookmarksList.appendChild(li); }); } // 删除指定索引的书签 function deleteBookmark(index) { bookmarks.splice(index, 1); saveBookmarks(); loadBookmarks(); } // 清除所有书签 function clearBookmarks() { bookmarks = []; saveBookmarks(); loadBookmarks(); } // 跳转到指定书签 function jumpToBookmark(index) { const bookmark = bookmarks[index]; currentPage = bookmark.page; scale = bookmark.scale; loadPage(currentPage); } // 保存书签到 localStorage function saveBookmarks() { localStorage.setItem('pdfBookmarks', JSON.stringify(bookmarks)); } // 加载保存的书签 function loadSavedBookmarks() { const saved = localStorage.getItem('pdfBookmarks'); if (saved) { bookmarks = JSON.parse(saved); loadBookmarks(); } } function toggleBookmarkPanel() { const modal = document.getElementById('bookmarks-modal'); modal.style.display = 'block'; loadBookmarks(); } function closeBookmarkPanel() { const modal = document.getElementById('bookmarks-modal'); modal.style.display = 'none'; } // 加载缩略图 function loadThumbnail(pageNum, canvas) { doc.getPage(pageNum).then(function (page) { const viewport = page.getViewport({ scale: 0.5 }); canvas.width = viewport.width; canvas.height = viewport.height; const context = canvas.getContext('2d'); if (!context) return; page.render({ canvasContext: context, viewport: viewport }).promise.then(() => { if (pageNum === currentPage) { thumbnailItem.classList.add('active'); } }); }); } function toggleTheme() { document.body.classList.toggle('light-theme'); const root = document.documentElement; root.style.setProperty('--background-color', document.body.classList.contains('light-theme') ? '#f5f5f5' : '#1a1a1a'); root.style.setProperty('--surface-color', document.body.classList.contains('light-theme') ? '#ffffff' : '#2d2d2d'); root.style.setProperty('--toolbar-bg', document.body.classList.contains('light-theme') ? '#f0f0f0' : '#333333'); root.style.setProperty('--text-color', document.body.classList.contains('light-theme') ? '#333333' : '#ffffff'); root.style.setProperty('--error-color', document.body.classList.contains('light-theme') ? '#cc0000' : '#ff4444'); }
标签:

Javascript网页设计案例:通过PDF.js实现一款PDF阅读器,包括预览、页面旋转、页面切换、放大缩小、由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Javascript网页设计案例:通过PDF.js实现一款PDF阅读器,包括预览、页面旋转、页面切换、放大缩小、