You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让PDF.js加载的PDF中链接可点击?(单HTML文件场景)

解决PDF.js单HTML展示PDF时内部链接无法点击的问题

问题背景

基于PDF.js官方示例修改了单HTML文件实现PDF展示,但PDF内部的链接无法点击,且无法下载仓库源码进行开发。仅需实现PDF展示功能,需解决链接点击问题。

解决方案

canvas渲染PDF仅生成静态图像,不会自动保留交互功能。需手动提取PDF页面中的链接注释,在canvas上方构建可点击的交互层:

  • 给每个canvas添加相对定位的容器,用于承载可点击链接元素
  • 页面渲染完成后,获取当前页面的所有注释,筛选出链接类型的注释
  • 将PDF注释的原始坐标通过viewport转换为canvas上的实际显示位置
  • 在容器内创建透明的可点击div,绑定点击事件跳转至目标链接

修改后的完整代码

<script src="//mozilla.github.io/pdf.js/build/pdf.mjs" type="module"></script>

<script type="module">
    var url = 'https://lukasjarmara.github.io/hosting/What%20Next%20Catalog.pdf';
    var { pdfjsLib } = globalThis;
    pdfjsLib.GlobalWorkerOptions.workerSrc = '//mozilla.github.io/pdf.js/build/pdf.worker.mjs';

    var pdfDoc = null,
        pageNum = 1,
        pageRendering = false,
        pageNumPending = null,
        scale = 1.5,
        canvas1 = document.getElementById('the-canvas-1'),
        ctx1 = canvas1.getContext('2d'),
        canvas2 = document.getElementById('the-canvas-2'),
        ctx2 = canvas2.getContext('2d');

    function renderPage(num, canvas, ctx, containerId) {
        pageRendering = true;
        pdfDoc.getPage(num).then(function(page) {
            var viewport = page.getViewport({scale: scale});
            canvas.height = viewport.height;
            canvas.width = viewport.width;

            var renderContext = {
                canvasContext: ctx,
                viewport: viewport,
                renderInteractive: true
            };
            var renderTask = page.render(renderContext);

            renderTask.promise.then(function() {
                pageRendering = false;
                // 处理页面链接
                page.getAnnotations().then(function(annotations) {
                    const container = document.getElementById(containerId);
                    // 清空旧链接元素
                    while (container.firstChild && container.firstChild !== canvas) {
                        container.removeChild(container.firstChild);
                    }

                    annotations.forEach(function(annot) {
                        if (annot.subtype !== 'Link' || !annot.url) return;

                        // 转换PDF坐标到canvas显示坐标
                        const rect = annot.rect;
                        const x = rect[0];
                        const y = viewport.height - rect[3];
                        const width = rect[2] - rect[0];
                        const height = rect[3] - rect[1];

                        // 创建可点击元素
                        const linkElement = document.createElement('div');
                        linkElement.style.position = 'absolute';
                        linkElement.style.left = `${x / viewport.scale}px`;
                        linkElement.style.top = `${y / viewport.scale}px`;
                        linkElement.style.width = `${width / viewport.scale}px`;
                        linkElement.style.height = `${height / viewport.scale}px`;
                        linkElement.style.cursor = 'pointer';
                        linkElement.style.backgroundColor = 'rgba(0, 128, 255, 0.1)'; // 可选hover提示

                        linkElement.addEventListener('click', function() {
                            window.open(annot.url, '_blank');
                        });

                        container.appendChild(linkElement);
                    });
                });

                if (pageNumPending !== null) {
                    renderPage(pageNumPending, canvas, ctx, containerId);
                    pageNumPending = null;
                }
            });
        });
    }

    function queueRenderPage(num) {
        if (pageRendering) {
            pageNumPending = num;
        } else {
            renderPage(num, canvas1, ctx1, 'canvas-container-1');
            if (num < pdfDoc.numPages) {
                renderPage(num + 1, canvas2, ctx2, 'canvas-container-2');
            }
            updatePageNum();
        }
    }

    function updatePageNum() {
        document.getElementById('page_num').textContent = pageNum + ' - ' + (pageNum + 1);
    }

    function onPrevPage() {
        if (pageNum <= 1) return;
        pageNum -= 2;
        queueRenderPage(pageNum);
    }
    document.getElementById('prev').addEventListener('click', onPrevPage);

    function onNextPage() {
        if (pageNum >= pdfDoc.numPages - 1) return;
        pageNum += 2;
        queueRenderPage(pageNum);
    }
    document.getElementById('next').addEventListener('click', onNextPage);

    pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {
        pdfDoc = pdfDoc_;
        document.getElementById('page_count').textContent = pdfDoc.numPages;
        queueRenderPage(pageNum);
    });
</script>

<div>
    <button id="prev">Previous</button>
    <button id="next">Next</button>
    &nbsp; &nbsp;
    <span>Page: <span id="page_num"></span> / <span id="page_count"></span></span>
</div>

<div style="display: flex; justify-content: space-between;">
    <div id="canvas-container-1" style="width: 50%; position: relative;">
        <canvas id="the-canvas-1" style="width: 100%;"></canvas>
    </div>
    <div id="canvas-container-2" style="width: 50%; position: relative;">
        <canvas id="the-canvas-2" style="width: 100%;"></canvas>
    </div>
</div>

内容的提问来源于stack exchange,提问作者Lukas007

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 18:14:51