如何使用JavaScript在加载PDF的iframe中高亮匹配文本
核心限制
你现在直接用iframe嵌入PDF的写法,原生JavaScript根本实现不了自定义文本高亮,两个绕不开的死穴:
- 跨域拦截:只要加载的PDF和当前页面不同源,浏览器同源策略会直接锁死iframe内部DOM的访问权限,任何操作内部元素的代码都会抛出权限错误
- 无可用接口:各浏览器自带的默认PDF阅读器,没有对外开放文本检索、高亮的JS调用能力,既没法主动触发它的高亮功能,也无法自定义高亮样式
可落地实现方案(全浏览器兼容、逻辑完全可控)
不要依赖浏览器自带的PDF阅读器,直接用PDF渲染库在自有页面内完成PDF渲染和高亮逻辑,所有代码都运行在自己站点下,不存在跨域问题,样式、交互都可以自定义。
实现逻辑:
- 移除原有直接加载PDF的iframe,替换为普通div作为PDF渲染容器
- 将PDF.js相关依赖下载到本地项目引入,加载目标PDF后逐页渲染PDF内容画布和对应的文本层(文本层是实现文本检索、高亮的核心基础,不能省略)
- PDF全部渲染完成后,遍历所有页面的文本层节点,匹配传入的目标检索文本
- 给匹配到的文本片段包裹带高亮样式的标签,完成标注
可直接复用的实现代码:
<!DOCTYPE html> <html> <head> <title>PDF文本高亮示例</title> <style> /* 自定义高亮样式 */ .pdf-highlight { background: #ffeb3b; padding: 2px 0; border-radius: 2px; } #pdf-container { width: 100%; height: 800px; overflow: auto; background: #f0f0f0; } .pdf-page { position: relative; margin: 10px auto; background: #fff; box-shadow: 0 2px 8px rgba(0,0,0,0.15); } /* PDF文本层必备样式,保证文本位置和PDF内容对齐 */ .textLayer { position: absolute; top: 0; left: 0; right: 0; bottom: 0; overflow: hidden; opacity: 0.2; line-height: 1; } .textLayer > span { color: transparent; position: absolute; white-space: pre; cursor: text; transform-origin: 0% 0%; } </style> </head> <body> <div id="pdf-container"></div> <script> // 注意:提前将pdf.min.js、pdf.worker.min.js两个依赖文件下载到本地项目存放 // 替换下方路径为你项目中依赖的实际存放路径,不要引用外部在线资源 const script = document.createElement('script'); script.src = '/static/vendor/pdf.min.js'; script.onload = initPdf; document.head.appendChild(script); async function initPdf() { const pdfjsLib = window['pdfjs-dist/build/pdf']; // worker路径同样替换为本地项目路径 pdfjsLib.GlobalWorkerOptions.workerSrc = '/static/vendor/pdf.worker.min.js'; // 配置项:替换为实际PDF地址、页面传入的待高亮目标文本 const PDF_URL = 'http://www.africau.edu/images/default/sample.pdf'; const TARGET_TEXT = 'sample PDF'; // 加载PDF文档 const pdf = await pdfjsLib.getDocument(PDF_URL).promise; const container = document.getElementById('pdf-container'); // 逐页渲染PDF内容和文本层 for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) { const page = await pdf.getPage(pageNum); const scale = 1.5; const viewport = page.getViewport({scale: scale}); // 创建单页容器 const pageDiv = document.createElement('div'); pageDiv.className = 'pdf-page'; pageDiv.style.width = `${viewport.width}px`; pageDiv.style.height = `${viewport.height}px`; // 渲染PDF内容画布 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = viewport.width; canvas.height = viewport.height; pageDiv.appendChild(canvas); await page.render({canvasContext: ctx, viewport: viewport}).promise; // 渲染文本层 const textLayerDiv = document.createElement('div'); textLayerDiv.className = 'textLayer'; pageDiv.appendChild(textLayerDiv); const textContent = await page.getTextContent(); pdfjsLib.renderTextLayer({ textContentSource: textContent, container: textLayerDiv, viewport: viewport, textDivs: [] }); container.appendChild(pageDiv); } // 执行文本匹配高亮 highlightTargetText(TARGET_TEXT); } function highlightTargetText(target) { if (!target.trim()) return; const textLayerList = document.querySelectorAll('.textLayer'); // 转义正则特殊字符,避免匹配报错 const reg = new RegExp(target.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); textLayerList.forEach(layer => { // 遍历层内所有文本节点 const walker = document.createTreeWalker(layer, NodeFilter.SHOW_TEXT); const textNodes = []; let node; while (node = walker.nextNode()) { textNodes.push(node); } textNodes.forEach(textNode => { const content = textNode.textContent; reg.lastIndex = 0; if (!reg.test(content)) return; // 拆分匹配节点,包裹高亮标签 const frag = document.createDocumentFragment(); let lastIndex = 0; let match; while ((match = reg.exec(content)) !== null) { const matchIndex = match.index; if (matchIndex > lastIndex) { frag.appendChild(document.createTextNode(content.slice(lastIndex, matchIndex))); } const highlightSpan = document.createElement('span'); highlightSpan.className = 'pdf-highlight'; highlightSpan.textContent = match[0]; frag.appendChild(highlightSpan); lastIndex = matchIndex + match[0].length; } if (lastIndex < content.length) { frag.appendChild(document.createTextNode(content.slice(lastIndex))); } textNode.parentNode.replaceChild(frag, textNode); }) }) } </script> </body> </html>
临时测试用方案(不推荐生产环境使用)
如果只是本地临时测试、不需要兼容多浏览器、也不需要自定义高亮样式,可以直接在iframe的PDF地址后拼接search参数,Chrome、Edge等Chromium内核浏览器会自动触发自带的搜索高亮:
<iframe id="myIFrame" width="100%" height="500px" src="http://www.africau.edu/images/default/sample.pdf#page=1&search=替换为要检索的文本" ></iframe>
这个方案缺陷非常明显:
- Safari、Firefox多数版本不支持该参数,添加后无任何效果
- 高亮样式为浏览器默认黄色,完全无法自定义
- 跨域场景下部分浏览器会直接拦截该参数,导致高亮失效
- 无法通过JS获取匹配结果,也无法监听高亮触发状态
内容的提问来源于stack exchange,提问作者trainingConsultancy USA
相关产品推荐
相关产品推荐

