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代码

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()">&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代码

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');
	}
相关推荐
天宇&嘘月2 小时前
web第三次作业
前端·javascript·css
牵牛老人2 小时前
Qt中使用QPdfWriter类结合QPainter类绘制并输出PDF文件
数据库·qt·pdf
小王不会写code2 小时前
axios
前端·javascript·axios
luckyext3 小时前
HBuilderX中,VUE生成随机数字,vue调用随机数函数
前端·javascript·vue.js·微信小程序·小程序
小小码农(找工作版)3 小时前
JavaScript 前端面试 4(作用域链、this)
前端·javascript·面试
鱼樱前端5 小时前
深入JavaScript引擎与模块加载机制:从V8原理到模块化实战
前端·javascript
yangjiajia1234565 小时前
vue3 ref和reactive的区别
前端·javascript·vue.js
诚信爱国敬业友善6 小时前
Vue 基础二(进阶使用)
前端·javascript·vue.js
なし.6 小时前
【Web前端开发精品课 HTML CSS JavaScript基础教程】第二十四章课后题答案
前端·javascript·css·html