pdf.js同一Canvas渲染多PDF滚动显示旧页面问题求助
问题描述
我使用Flask、HTML和JS构建了一个UI,该UI包含一个元素列表,每个元素都有独立按钮,可在同一个canvas中渲染不同的PDF。问题在于,当我打开第2个或第3个PDF并使用鼠标滚动页面时,会显示第一个PDF的页面。我已经尝试了StackOverflow上两个相关问题的所有建议,但问题仍未解决。
相关代码
HTML代码
<div class="col-lg-6" id="col-preview"> {% if data_to_render %} <h5>Preview</h5> {% endif %} <canvas id="pdfCanvas"></canvas> </div>
JS脚本
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; document.addEventListener('DOMContentLoaded', function () { var buttons = document.querySelectorAll('.btn-secondary'); buttons.forEach(function (button) { button.addEventListener('click', function () { var pdfUrl = button.getAttribute('data-pdf-url'); var chunk = button.getAttribute('data-chunk'); renderPdf(pdfUrl, 'pdfCanvas', chunk); }); }); }); function renderPdf(url, containerId, chunk) { var canvas = document.getElementById(containerId); var pdfDoc = null; var currentPage = 1; var heightRatio = window.innerHeight/953; var widthRatio = window.innerWidth/1920; var scale = Math.min(heightRatio, widthRatio); function getCanvasTopLeftCoordinates(canvas) { if (canvas) { const rect = canvas.getBoundingClientRect(); const left = rect.left; const top = rect.top; return { left, top }; } else { return null; // 未找到canvas } } // 小屏幕下显示有问题(待修复) function renderHighlightChunk(page, chunk, maxY) { const canvasCoordinates = getCanvasTopLeftCoordinates(canvas); var lowerCaseChunk = chunk.toLowerCase(); page.getTextContent().then(function (textContent) { const textItems = textContent.items; textItems.forEach(function (textItem) { const text = textItem.str; var lowerCaseText = text.toLowerCase(); if (lowerCaseChunk.includes(lowerCaseText)) { posX = (textItem.transform[4] + canvasCoordinates.left) * scale; posY = (maxY - textItem.transform[5] - textItem.transform[0] + canvasCoordinates.top + window.scrollY) * scale; const highlight = document.createElement('div'); highlight.className = 'highlight'; highlight.style.position = 'absolute'; highlight.style.left = posX + 'px'; highlight.style.top = posY + 'px'; highlight.style.width = textItem.width * scale + 'px'; highlight.style.height = textItem.height * scale + 'px'; highlight.style.backgroundColor = 'yellow'; highlight.style.opacity = '0.5'; document.body.appendChild(highlight); } }); }); }; function clearHighlights() { // 找到所有高亮div并移除 const highlights = document.querySelectorAll('.highlight'); highlights.forEach(function (highlight) { highlight.parentNode.removeChild(highlight); }); } var renderingInProgress = false; function renderPage(pageNumber) { if (renderingInProgress) { return; } renderingInProgress = true; clearHighlights(); pdfDoc.getPage(pageNumber).then(function (page) { var viewport = page.getViewport({ scale: scale }); canvas.height = viewport.height; canvas.width = viewport.width; var context = canvas.getContext('2d'); var renderContext = { canvasContext: context, viewport: viewport }; page.render(renderContext).promise.then(function () { renderHighlightChunk(page, chunk, canvas.height); renderingInProgress = false; }); }).catch(function (reason) { console.error('页面渲染错误', reason); renderingInProgress = false; }); } function clearCanvas() { var context = canvas.getContext('2d'); context.clearRect(0, 0, canvas.width, canvas.height); } pdfjsLib.getDocument(url).promise.then(function (pdf) { pdfDoc = pdf; clearCanvas(); renderPage(currentPage); var isMouseOverCanvas = false; canvas.addEventListener('mouseenter', function () { isMouseOverCanvas = true; }); canvas.addEventListener('mouseleave', function () { isMouseOverCanvas = false; }); canvas.addEventListener('wheel', function (e) { if (!isMouseOverCanvas) { return; } e.preventDefault(); if (e.deltaY < 0 && currentPage > 1) { currentPage--; renderPage(currentPage); } else if (e.deltaY > 0 && currentPage < pdfDoc.numPages) { currentPage++; renderPage(currentPage); } }); }).catch(function (reason) { console.error('PDF加载错误', reason); }); }
问题根源与解决建议
问题根源
每次点击按钮调用renderPdf时,都会给canvas绑定新的wheel、mouseenter、mouseleave事件监听,但旧的监听并未移除。同时,之前的PDF加载和渲染任务也没有中断。当滚动鼠标时,多个事件处理函数会同时触发,而这些旧的函数引用的是第一次调用时闭包里的pdfDoc和currentPage变量,导致显示第一个PDF的页面。
修复方案
1. 移除旧的事件监听并中断旧的PDF任务
在renderPdf函数开头,先清理之前的事件监听和未完成的PDF加载任务:
function renderPdf(url, containerId, chunk) { var canvas = document.getElementById(containerId); // 移除之前绑定的事件监听 if (canvas._wheelHandler) { canvas.removeEventListener('wheel', canvas._wheelHandler); } if (canvas._mouseEnterHandler) { canvas.removeEventListener('mouseenter', canvas._mouseEnterHandler); } if (canvas._mouseLeaveHandler) { canvas.removeEventListener('mouseleave', canvas._mouseLeaveHandler); } // 中断之前未完成的PDF加载任务 if (canvas._pdfLoadingTask) { canvas._pdfLoadingTask.cancel(); } // 原有的变量定义... var pdfDoc = null; var currentPage = 1; // ...后续代码 }
2. 保存事件处理函数引用,方便后续移除
将事件处理函数定义为变量,绑定后保存到canvas元素上,以便下次调用时能找到并移除:
pdfjsLib.getDocument(url).promise.then(function (pdf) { pdfDoc = pdf; clearCanvas(); renderPage(currentPage); var isMouseOverCanvas = false; // 定义命名的事件处理函数 const mouseEnterHandler = function () { isMouseOverCanvas = true; }; const mouseLeaveHandler = function () { isMouseOverCanvas = false; }; const wheelHandler = function (e) { if (!isMouseOverCanvas) { return; } e.preventDefault(); if (e.deltaY < 0 && currentPage > 1) { currentPage--; renderPage(currentPage); } else if (e.deltaY > 0 && currentPage < pdfDoc.numPages) { currentPage++; renderPage(currentPage); } }; // 绑定事件 canvas.addEventListener('mouseenter', mouseEnterHandler); canvas.addEventListener('mouseleave', mouseLeaveHandler); canvas.addEventListener('wheel', wheelHandler); // 保存函数引用到canvas canvas._mouseEnterHandler = mouseEnterHandler; canvas._mouseLeaveHandler = mouseLeaveHandler; canvas._wheelHandler = wheelHandler; }).catch(function (reason) { console.error('PDF加载错误', reason); });
3. 保存PDF加载任务引用
在调用pdfjsLib.getDocument时,将任务对象保存到canvas上,方便后续中断:
// 替换原有的pdfjsLib.getDocument调用 canvas._pdfLoadingTask = pdfjsLib.getDocument(url); canvas._pdfLoadingTask.promise.then(function (pdf) { // 原有的后续逻辑... }).catch(function (reason) { console.error('PDF加载错误', reason); });
4. 额外优化:确保渲染状态重置
在renderPdf开头重置渲染状态,避免旧状态影响新的PDF渲染:
function renderPdf(url, containerId, chunk) { var canvas = document.getElementById(containerId); // ...前面的清理代码 // 重置渲染状态 var renderingInProgress = false; // ...后续代码 }
内容的提问来源于stack exchange,提问作者Marco Abbatangelo
相关产品推荐
相关产品推荐

