前言
目前功能包括:
- 切换到首页。
- 切换到尾页。
- 上一页。
- 下一页。
- 添加标签。
- 标签管理
- 页面旋转
- 页面随意拖动
- 双击后还原位置
其实按照自己的预期来说,有很多功能还没有开发完,配色也没有全都搞完,先发出来吧,后期有需要继续添加功能。
功能预览
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代码
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="https://cdnjs.cloudflare.com/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()">×</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代码
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 = 'https://raw.githubusercontent.com/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');
}