☰
pdf.js 1.9.426 老旧系统 PDF 预览部署与踩坑指南
2026/11/5 2:30:45 网站建设 项目流程

简介:这份 pdf.js 1.9.426 压缩包面向需要在网页中嵌入 PDF 阅读能力的开发者,尤其适合不想依赖浏览器插件、希望用纯 JavaScript 实现预览的项目。它提供缩放、打印、文本查找等常用阅读功能,兼容 Firefox、Chrome 及 IE9 以上主流浏览器,基于 H5 技术,因此 IE8 及更低版本无法使用;包体偏大也会在一定程度上影响首次加载速度,选型时需权衡。压缩包共 372 个文件,约 5.54MB,主体是 168 个 bcmap 字符映射文件和 105 个 properties 语言与配置资源,另含 png/svg 界面图形、核心 js 脚本、css/html 页面入口及示例 PDF、license 授权说明,目录组织较完整。已有 993 人学习下载。对读者而言,可直接用于搭建本地 PDF 在线预览页,研究字符映射与多语言显示、界面资源组织及打印查找等模块的实现方式,也便于排查浏览器兼容与加载性能问题,适合前端开发、门户系统集成和 PDF 组件二次开发参考。

1. pdf.js 1.9.426 这个压缩包里到底是什么东西

前阵子有人拿着一份只在内网跑的老后台来找我,说 pdf.js 1.9.426 这个包是他们现在唯一还能用的 PDF 预览方案,换成新版页面直接白屏,控制台一片红。这类需求我一年能碰上几回:系统还挂在旧浏览器上,构建链是十几年前那套,升级成本比修 bug 高得多。pdf.js 1.9.426 就是这样一个「停在某一代」的 PDF 渲染库整包,解压出来不是 npm 依赖,而是一份能直接丢进静态目录、用 viewer.html 打开就能看 PDF 的完整工程,连阅读器界面都给你做好了。它解决的是「浏览器里不想装插件、又要把 PDF 渲染出来」这件事:纯前端渲染、支持文字层选中、支持中文、支持翻页与缩放,代价是要自己管文件、自己配跨域。适合两类人:维护老系统的后端或全栈,以及需要把 PDF 预览嵌进已有页面的前端。如果你手上是新项目,别从这一版起手;如果是接手别人的老摊子,这一包就是你最省事的后悔药。

2. 解包与上线裁剪:从 rar 里挑出能直接跑的最小文件集

2.1 解开之后先认清四个目录

压缩包本身没什么花头,unrar或者图形化解压工具都能拆。真正让人发懵的是拆完以后那一屏目录:明明只想放个 PDF 预览,怎么源码、测试用例、构建脚本全都躺在里面。先别动文件,把结构看清楚再动手。

# 解到固定位置,路径别带中文和空格,后面配 workerSrc 会省很多事 mkdir -p /opt/static/pdfjs && cd /opt/static/pdfjs unrar x ~/Downloads/pdfjs1.9.426.rar . # 没有 unrar 就 7z x 或图形工具解开 # 只看一级目录,先不改任何东西 ls -la

这几行命令没什么技术含量,重点在ls的输出:这一包是「完整工程」形态,不是构建产物压缩包。四个目录分工很明确——build/是打包后的运行时代码,pdf.js和pdf.worker.js两个文件;web/是自带的阅读器,viewer.html、viewer.css、viewer.js、locale/、cmaps/、images/都在里面;src/是没打包的源码,出问题时对照着看比读压缩后的代码舒服;test/是单元测试和一堆参考 PDF,占空间但偶尔能救命——比如你怀疑某个 PDF 渲染不对,去test/pdfs/里找同类文件对比一下,能快速判断是库的问题还是文件的问题。

目录/文件作用上线是否必须
build/pdf.js主库,暴露全局PDFJS必须
build/pdf.worker.js解析 worker,跑在独立线程必须,且路径要能直连访问
web/viewer.html阅读器页面入口用自带阅读器就必须
web/viewer.css阅读器样式,含.textLayer规则必须
web/viewer.js阅读器逻辑,AMD 模块必须
web/cmaps/中日韩字体的预定义编码表有中文 PDF 就必须
web/locale/界面语言包,含zh-CN需要中文界面就必须
web/images/、web/debugger.js图标、调试钩子建议保留,删了可能 404
src/、test/源码与测试上线不需要

这张表我一般直接贴在部署文档里,因为最容易出事的就是「看着像没用」的那几个:cmaps/目录几百个.bcmap文件,很多人以为可以删,删完中文 PDF 全是方块;debugger.js名字带 debug,看着像开发残留,但viewer.js里可能引用了它,删掉就是一条 404。

2.2 裁剪:能删的删干净,不能删的一个别碰

确定只做线上部署、不做二次开发,那src/和test/完全可以不传。这一步的意义不是省那几十兆,而是减少暴露面——test/里一堆样例 PDF,挂到公网上没意义,还可能被人爬。

# 在解包目录里执行,删之前先确认当前路径 pwd # 只做部署:删掉源码与测试,保留 build 与 web rm -rf src test examples gulpfile.js package.json # 删完核对一遍,这四个东西缺一个都会翻车 ls build/pdf.js build/pdf.worker.js web/viewer.html web/cmaps/ | head -20

删除动作本身很简单,关键是删完的核对清单。build/pdf.worker.js必须和pdf.js放在同一个目录下,因为默认的 worker 地址是按pdf.js的相对位置推算的;web/cmaps/必须整目录保留,不能只挑几个.bcmap;web/viewer.html和它同级的viewer.css、viewer.js是配套的,少一个页面就白屏。我习惯删完立刻起服务打开一次,确认能渲染再继续改配置,别攒一堆改动一起验证——那样出了错根本不知道是哪一步引入的。

2.3 起个静态服务,先把 viewer 跑通

不要用file://直接双击viewer.html,这条路基本走不通:worker 加载受同源限制,PDF 文件又走 XHR 读取,两个都会被拦。本地验证最省事的是起一个静态 HTTP 服务。

# 在 /opt/static 下起服务,端口随便挑一个没占用的 cd /opt/static python3 -m http.server 8000 # 另一个终端验证文件真的能取到,注意看 HTTP 状态码是不是 200 curl -I http://127.0.0.1:8000/pdfjs/web/viewer.html curl -I "http://127.0.0.1:8000/files/report.pdf"

python3 -m http.server只适合本机验证,它默认不支持 Range 分段请求,也不处理 CORS,所以别指望用它测出真实环境的行为。两个curl -I是必须的:第一个确认viewer.html能取到,第二个确认待预览的 PDF 路径可达。很多人后面排查半天「为什么一直转圈」,最后发现是file参数指向的路径压根 404。真正上线建议用 nginx 或已有网关托管静态目录,build/和web/放在同一个站点下,PDF 文件放在同源路径下,跨域问题从根上就不存在了。

跑通之后浏览器打开http://127.0.0.1:8000/pdfjs/web/viewer.html?file=/files/report.pdf,能看到页面、能翻页、能选中文字,说明这一包本身是完整的,接下来的工作才是接入和调参。

3. 三种接入方式:从最省事到最可控

3.1 viewer.html?file= 直连:省事,但栽在编码上的人最多

最省事的用法就是把file参数拼在地址后面,让自带的阅读器去加载。这条路适合「我就想要一个 PDF 预览页」的场景,工具栏、缩略图、搜索、下载按钮全都是现成的,一行代码不用写。问题出在参数编码:file的值会被 URL 解码一次,如果你的文件名带中文、空格、&、#,不处理就会截断或者乱码。

// 生成 viewer 直连地址:编码只做一次,多编一次反而会 404 function buildViewerUrl(base, pdfPath) { // encodeURIComponent 会把 / 也编掉,所以先按段编码再拼回去 var encoded = pdfPath.split('/').map(encodeURIComponent).join('/'); return base + '?file=' + encoded + '#page=1&zoom=page-width'; } var url = buildViewerUrl('/pdfjs/web/viewer.html', '/files/2024年度报告 v2.pdf'); // 结果里的中文和空格都被转义,井号后面的 hash 参数不会被当成 file 的一部分 console.log(url);

这段代码的关键点是「按段编码再拼」。直接对整个路径做encodeURIComponent会把/也变成%2F,服务端拒绝解析;反过来完全不编码,文件名里有个空格就断在那里。#page=1&zoom=page-width是 viewer 支持的地址参数,控制初始页码和缩放策略——page-width表示按容器宽度铺满,比默认的auto在窄屏上稳。另外要注意,file用的相对路径是相对viewer.html所在目录算的,不是相对你的业务页面,这一点坑过不少人:业务页在/app/下,file=report.pdf会去找/pdfjs/web/report.pdf。

3.2 iframe 嵌入:把工具栏藏起来,把控制权拿回来

真要嵌进已有页面,iframe 是最快的方式。同源的情况下,你甚至可以直接摸到 iframe 里的全局对象去做联动;跨域就只剩改 hash 这一条路。

<!-- 嵌入阅读器,用 hash 控制初始状态;toolbar 等界面元素在 viewer 里另有开关 --> <iframe id="pdf-frame" src="/pdfjs/web/viewer.html?file=%2Ffiles%2Freport.pdf#page=1&zoom=page-fit" style="width:100%;height:720px;border:0" title="PDF 预览"> </iframe>
var frame = document.getElementById('pdf-frame'); // 同源场景:直接操作 iframe 内的阅读器实例,翻页、缩放都能控 function gotoPage(pageNo) { try { var app = frame.contentWindow.PDFViewerApplication; // 1.9 的 viewer.js 会挂这个全局 if (app && app.page !== pageNo) { app.page = pageNo; // 赋值即触发重绘 } } catch (e) { // 跨域时访问 contentWindow 会抛异常,退化成改 hash frame.src = frame.src.replace(/#.*$/, '') + '#page=' + pageNo; } }

这里有个必须讲清楚的边界:contentWindow.PDFViewerApplication只在同源时可用,跨域访问会直接抛 SecurityError,所以try/catch不是防御性编程,是必需的。跨域退化方案是改src里的 hash,1.9 的阅读器监听 hash 变化,改完不会整页重载,但会重置一部分状态,体验上差一截。另一个实操细节是隐藏工具栏:自带阅读器的界面元素集中在viewer.html顶层结构里,与其在外部用 CSS 硬压,不如直接改那份 HTML,把不需要的按钮节点删掉或者加hidden,比在 iframe 外面套一层遮罩干净得多。

3.3 用 PDFJS.getDocument 自己画:可控性最高,也最需要管好生命周期

如果自带阅读器的界面跟你的产品风格完全对不上,或者你只需要「渲染一页缩略图」,那就绕开 viewer,直接用库本身。1.9 这一代的 API 是全局PDFJS,不是后来那个pdfjsLib,网上抄来的 2.x 示例直接扔进来会报pdfjsLib is not defined。

// 1) worker 路径必须显式指定,且是浏览器能直连的地址 PDFJS.workerSrc = '/static/pdfjs/build/pdf.worker.js'; // 2) 中文 PDF 依赖外置 cmaps,注意结尾的斜杠不能省 PDFJS.cMapUrl = '/static/pdfjs/web/cmaps/'; PDFJS.cMapPacked = true; var loadingTask = PDFJS.getDocument({ url: '/files/report.pdf', // 后端不支持 Range 时,一次性拉全量,避免卡在分段请求上反复重试 disableAutoFetch: true, disableStream: true }); loadingTask.promise.then(function (pdf) { return pdf.getPage(1).then(function (page) { var viewport = page.getViewport(1.5); // 1.5 倍缩放,1.0 是 PDF 原始点尺寸 var canvas = document.getElementById('pdf-canvas'); var ctx = canvas.getContext('2d'); canvas.width = viewport.width; canvas.height = viewport.height; var renderTask = page.render({canvasContext: ctx, viewport: viewport}); return renderTask.promise; // 渲染完成才返回,别用 setTimeout 猜 }); }).catch(function (err) { console.error('PDF 加载失败', err && err.name, err && err.message); });

参数逐个数:workerSrc必须是能返回 200 的 JS 地址,同源最稳;cMapUrl和cMapPacked是给中文 PDF 用的,后面单独说;disableAutoFetch和disableStream是关掉分段加载的开关,服务端 Range 支持有问题时打开它们能立刻止血,代价是大文件要一次拉完。getViewport(scale)里的 scale 是纯数字倍率,不是百分比,想要「适配容器宽度」得自己算:containerWidth / viewport.width。

最容易翻车的地方在生命周期。loadingTask和pdf对象都会持有 worker 侧的解析结果,连续预览十几个文件不释放,内存曲线会一路往上爬,最后标签页直接崩。所以只要是自渲染,我一般把打开和关闭成对写在一个小封装里,用完立刻pdf.destroy(),renderTask.cancel()在切页时也要调——不然用户快速翻页,几个渲染任务叠在同一个 canvas 上,画面会花。

4. 中文显示、清晰度与文字层:三个最容易被当成玄学的问题

4.1 中文变方块:根因是预定义 CMap 没加载

中文 PDF 里很常见的一种写法是字体只嵌了一部分,剩下的靠「预定义 CMap」描述编码到字形的映射,比如UniGB-UCS2-H。这类文件在渲染器眼里缺一张对照表,没有表就只能画方块。表本身就在web/cmaps/里,格式是.bcmap,需要你告诉库去哪儿找。

// 自己调 API 时必须手动配;用自带 viewer.html 的话它默认已经指向 ../web/cmaps/ PDFJS.cMapUrl = '/static/pdfjs/web/cmaps/'; // 结尾斜杠必须有,库是按 url + 文件名 拼的 PDFJS.cMapPacked = true; // .bcmap 是压缩过的,必须置 true // 迁到 2.x 之后这两个参数要挪进 getDocument,写法不一样,别混抄

cMapUrl结尾的斜杠是最低级的坑,拼出来变成/web/cmapswhat.bcmap直接 404,但页面上只表现成中文方块,没有任何报错。cMapPacked要和实际文件格式对上:这一包web/cmaps/下是.bcmap,就得是true;如果你从别处拷了一份解压后的表,那要改成false。判断是不是 CMap 问题有个笨办法但很准——把同一个 PDF 用系统里的阅读器打开正常、浏览器里方块,基本可以锁定是 CMap 或字体没配,而不是文件损坏。

还有一类是「字体完全没嵌入」的 PDF,只声明了标准字体名。这一代的处理方式和 2.x 不一样,2.x 才把标准字体目录单独做成standardFontDataUrl参数,1.9 没有这个口子。所以遇到那种纯标准字体的老 PDF,字形回退效果一般,能看但不精致。要较真就得自己补字体,性价比不高,我通常建议业务方直接让生成方把字体嵌进去。

4.2 高分屏发虚:canvas 像素和 CSS 尺寸要分开算

很多人第一次自渲染都会遇到「文字糊」的问题,第一反应是调大 scale,结果 canvas 越调越大,页面直接撑爆。根子在 canvas 有两套尺寸:width/height属性是真实像素,style.width/height是显示尺寸,高分屏下前者应该是后者的devicePixelRatio倍。

var dpr = window.devicePixelRatio || 1; var baseViewport = page.getViewport(1); // scale=1 的原始尺寸,单位是 PDF 点 var scale = (containerWidth / baseViewport.width) * dpr; // 先适配宽度,再乘像素比 var viewport = page.getViewport(scale); canvas.width = Math.floor(viewport.width); // 真实像素,决定清晰度 canvas.height = Math.floor(viewport.height); canvas.style.width = Math.floor(viewport.width / dpr) + 'px'; // 显示尺寸,决定排版占位 canvas.style.height = Math.floor(viewport.height / dpr) + 'px'; var ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); // 切页必须先清,否则旧内容叠上去 await page.render({canvasContext: ctx, viewport: viewport}).promise;

关键在于两步分开:先按容器宽度算出「铺满需要多大」,再乘 dpr 得到真实像素,最后把 CSS 尺寸除回去。只做第一步,高分屏上是 1 个 canvas 像素铺 2 个物理像素,必然糊;只做第二步不除回去,画面会变成两倍大。clearRect那行也别省,canvas 不会自动清空,连续翻页会看到上一页的残影叠在下面,第一次遇到会以为是库的 bug。

4.3 文字层:能选中、能复制、能搜索的那一层

canvas 画出来的东西本质是图片,选不中。要支持选中复制,得在 canvas 上叠一层透明的 DOM,把每个文字片段按坐标摆上去。这一层自己写很痛苦,好在这一代提供了现成的renderTextLayer。

var textContent = await page.getTextContent(); var textLayer = document.getElementById('text-layer'); textLayer.innerHTML = ''; // 复用容器前先清空 textLayer.style.width = viewport.width / dpr + 'px'; textLayer.style.height = viewport.height / dpr + 'px'; var textDivs = []; var task = PDFJS.renderTextLayer({ textContent: textContent, // 上面取到的文字内容,含每段的 transform 矩阵 container: textLayer, // 挂载容器,必须 position:absolute 盖在 canvas 上 viewport: viewport, // 必须和 canvas 用同一个 viewport,否则整层错位 textDivs: textDivs // 输出数组,便于后续做高亮、定位 }); await task.promise; // 有的小版本返回对象直接带 promise,取不到就 console.log 看一眼结构

三个参数是强绑定的:viewport必须和 canvas 那次一模一样,差一点整层文字就会偏;container的样式必须抄web/viewer.css里.textLayer那一段,包括绝对定位、color: transparent、line-height: 1、transform-origin: 0 0,自己发明样式基本对不齐;textDivs传进去的数组渲染完会被填满,做全文高亮时按它遍历就能定位到具体片段,不用再查一遍坐标。实践里最容易出问题的是缩放:如果容器尺寸变了但没重新渲染文字层,文字会跟 canvas 错开几个像素,看着像「重影」,其实是两层的坐标基准不同步。

5. 避坑与排查:这五件事我几乎每次都碰上

5.1 跨域与 Range:转圈不出图,八成卡在这

现象:页面一直显示加载中,控制台只有一句含糊的加载失败;或者进度条走到一半不动,Network 面板里只有一个没结束的请求。

原因:viewer 默认用 XHR 分段拉取 PDF,靠Range头和服务端返回206 Partial Content实现。服务端不支持 Range、或者反向代理把Range头吞了,请求就会退化成整包下载甚至挂住。跨域场景还要额外给Access-Control-Allow-Origin,并且Content-Range这类响应头默认不暴露给前端。

解决:优先同源部署,把 PDF 放在和web/同一个站点下,这一步能消掉八成问题。做不到同源,就让服务端老实支持 Range;实在改不动后端,就在getDocument里打开disableAutoFetch和disableStream,让它一次性拿全量。动态输出 PDF 的接口要显式转发 Range 并返回 206:

location /files/ { # 后端动态吐 PDF:Range 必须转发,否则前端拿不到 206,只能整包等 proxy_pass http://127.0.0.1:8080; proxy_set_header Range $http_range; proxy_set_header If-Range $http_if_range; proxy_hide_header Accept-Ranges; add_header Accept-Ranges bytes; # 跨域场景必须放开这两个,否则前端读不到 Content-Range add_header Access-Control-Allow-Origin "*"; add_header Access-Control-Expose-Headers "Content-Range, Accept-Ranges"; }

静态目录由 nginx 直接托管时自带 Range 支持,上面这段只针对反代到应用服务器的情形。proxy_hide_header那行是为了避免上游和本地各发一次Accept-Ranges造成重复头,浏览器对重复头的处理不完全一致,稳妥起见收掉一个。

5.2 worker 起不来:主线程扛解析,大文件必卡死

现象:控制台出现「设置伪 worker」之类的警告,小文件能看,几十兆的 PDF 一打开整个页面卡住,滚动都卡。

原因:workerSrc没配、配错、指向 404,或者 worker 文件和pdf.js版本不匹配。库发现 worker 不可用会退化到主线程解析,功能上「还能用」,性能上直接崩。另一个隐蔽原因是协议:file://下 worker 加载会被拦,所以本地双击 HTML 永远走伪 worker。

解决:显式写死PDFJS.workerSrc的绝对路径,curl -I确认返回 200 且Content-Type是 JS;build/pdf.js和build/pdf.worker.js必须来自同一次构建,别从另一个版本拷一个过来凑;本地验证一律走 HTTP 服务,别用文件协议。

5.3 版本混用:全局对象对不上,抄来的示例全报错

现象:pdfjsLib is not defined、PDFJS.getDocument is not a function、或者 viewer 页面加载完但一片空白,控制台提示某个模块 undefined。

原因:1.9 这一代全局对象是PDFJS,2.x 才改成pdfjsLib;另外build/和web/如果来自不同版本,viewer.js里引用的内部结构对不上,报错位置会很莫名其妙。

解决:整包用,不要东拼西凑。build/和web/必须同源同版本;从网上抄示例先看它用的是哪个全局对象,1.9 的示例里PDFJS.workerSrc、PDFJS.getDocument是标准写法。升级时先只换build/下的两个文件试跑,别一次把web/也换掉,出问题好定位。

5.4 内存只涨不降:连续预览十几个文件后标签页崩掉

现象:单次预览没问题,用户来回切十几个 PDF 之后页面越来越卡,最后崩溃或者渲染出白屏。

原因:loadingTask、pdf、renderTask都持有 worker 侧的资源,不显式释放就一直挂着;切页时旧renderTask没取消,多个渲染任务叠在同一个 canvas 上。

解决:打开和关闭成对写,切页前先renderTask.cancel(),关页面时pdf.destroy(),loadingTask.destroy()一并调。封装成open / renderPage / close三个函数,业务侧只能通过封装访问,就不会漏。

5.5 大文件首屏太慢:等全部解析完才出第一页

现象:几十兆的 PDF,点了之后十几秒都是白的,之后突然整页出来。

原因:关掉分段加载后要等整个文件下载完才能解析;或者首屏渲染用了过大的 scale 加上高分屏倍率,单页渲染耗时被放大几倍。

解决:先按低倍率(比如 1.0)渲染一版占位,再异步按目标倍率重渲染;页面多的话只渲染当前页和相邻页,其余页用空白占位。这套「先粗后细」的策略在移动端尤其明显,能显著改善第一眼的等待感。

6. 加一层适配再谈迁移:把 1.9 的调用点收成三个函数

如果你只是临时用一下,直接写PDFJS.xxx没什么问题。但只要这个项目还要维护一两年,我建议第一天就加一层薄封装,把版本差异关在一个文件里。理由很实在:1.9 和 2.x 的差别集中在全局对象和参数位置上,业务代码里散落十几处PDFJS.调用,将来迁移就是一个下午的机械劳动加一堆回归测试;收敛到一层,改的是一个文件。

用途1.9.426 的写法2.x 的写法
全局对象PDFJSpdfjsLib
worker 地址PDFJS.workerSrcpdfjsLib.GlobalWorkerOptions.workerSrc
CMap 配置PDFJS.cMapUrl/PDFJS.cMapPacked全局设置作为getDocument({cMapUrl, cMapPacked})的入参
标准字体目录这一代没有独立参数getDocument({standardFontDataUrl})
加载PDFJS.getDocument(src).promise同样返回 task,取.promise
文字层PDFJS.renderTextLayer({textContent, ...})函数保留,但入参名改过几轮,注意textContent与textContentSource
// pdf-adapter.js:业务只认这一层,换版本时只改这个文件 (function (global) { var PDFJS = global.PDFJS; // 迁到 2.x 时换成 global.pdfjsLib PDFJS.workerSrc = '/static/pdfjs/build/pdf.worker.js'; PDFJS.cMapUrl = '/static/pdfjs/web/cmaps/'; PDFJS.cMapPacked = true; function open(url, password) { return PDFJS.getDocument({ url: url, password: password || '', disableAutoFetch: true, // 后端 Range 不靠谱时先兜住 disableStream: true }).promise; } function renderPage(pdf, pageNo, canvas, scale) { return pdf.getPage(pageNo).then(function (page) { var viewport = page.getViewport(scale || 1.5); canvas.width = Math.floor(viewport.width); canvas.height = Math.floor(viewport.height); return page.render({canvasContext: canvas.getContext('2d'), viewport: viewport}).promise; }); } function close(pdf) { return pdf && pdf.destroy(); // 不调这句,连续预览十几个文件内存就下不来 } global.PdfAdapter = {open: open, renderPage: renderPage, close: close}; })(window);

迁移前的验证别偷懒,我一般固定跑六类文件:纯中文正文(验证 cmaps)、扫描件大图(验证内存与首屏)、带密码的加密件(验证password回调与失败提示)、带/Rotate的横版页(验证 viewport 旋转)、五百页以上带大纲的(验证侧边栏与分页性能)、带表单域的(验证注释层有没有丢)。这六类里任意一类在迁移后表现不同,都说明适配层没兜住,回头改那一个文件就行,不用满项目找调用点。

血泪经验是:别在迁移的同时顺手改业务逻辑。有一次我把版本升级和渲染倍率优化塞进同一个提交,结果页面上出现的重影到底是版本差异还是倍率算错,排查了两个小时。从那以后我每次动渲染相关的配置,都强制先把这六类文件跑一遍、留存截图,再改代码。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询