如何判断请求是否来自移动浏览器 适配移动端PDF嵌入预览
移动端PDF内嵌预览场景的可靠判断方案
核心思路
放弃屏幕尺寸、触摸支持、全量UA匹配这类高误判、高维护成本的判断逻辑,以实际PDF渲染能力特征检测为核心判断依据,辅以极轻量的已知不支持环境UA规则做前置快速判断,检测结果缓存复用,兼顾准确率和性能。
具体实现
1. 核心特征检测逻辑
通过插入不可见的测试iframe,加载极小的空白base64格式PDF样本,根据iframe实际加载结果判断当前环境是否支持内嵌PDF渲染,不需要依赖任何外部资源:
// 内存缓存检测结果 let pdfSupportCache = null; async function checkPdfEmbedSupport() { if (pdfSupportCache !== null) return pdfSupportCache; // 同会话内存储缓存,不需要重复检测 const cacheKey = '__pdf_embed_support__'; const storedCache = sessionStorage.getItem(cacheKey); if (storedCache !== null) { pdfSupportCache = storedCache === '1'; return pdfSupportCache; } // 139字节的最小空白PDF样本,无额外网络请求 const TEST_PDF = 'data:application/pdf;base64,JVBERi0xLjAKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFI+PgplbmRvYmoKMiAwIG9iago8PCAvVHlwZSAvUGFnZXMgL0tpZHMgWzMgMCBSXSAvQ291bnQgMSA+PgplbmRvYmoKMyAwIG9iago8PCAvVHlwZSAvUGFnZSAvUGFyZW50IDIgMCBSIC9NZWRpYUJveCBbMCAwIDMgM10gL0NvbnRlbnRzIDQgMCBSIC9SZXNvdXJjZXMgPDwgL0ZvbnQgPDwgL0YxIDUgMCBSID4+ID4+ID4+CmVuZG9iago0IDAgb2JqCjw8IC9MZW5ndGggMjIgPj4Kc3RyZWFtCkJUCi9GMSAxMiBUZgo2Ni42IDY2LjYgVGQKRVQKZW5kc3RyZWFtCmVuZG9iago1IDAgb2JqCjw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSA+PgplbmRvYmoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAwNjAgMDAwMDAgbiAKMDAwMDAwMDEyMCAwMDAwMCBuIAowMDAwMDAwMjI3IDAwMDAwIG4gCjAwMDAwMDAzMjYgMDAwMDAgbiAKdHJhaWxlcgo8PCAvU2l6ZSA2IC9Sb290IDEgMCBSID4+CnN0YXJ0eHJlZgozNzkKJSVFT0Y='; return new Promise(resolve => { const testFrame = document.createElement('iframe'); // 测试iframe完全不可见,不影响页面布局和交互 testFrame.style.cssText = 'position:fixed;width:1px;height:1px;top:-9999px;left:-9999px;opacity:0;pointer-events:none;border:none;'; let timeoutTimer; const clearTest = (result) => { clearTimeout(timeoutTimer); testFrame.remove(); pdfSupportCache = result; sessionStorage.setItem(cacheKey, result ? '1' : '0'); resolve(result); }; // 300ms超时判定:超过时间未完成正常渲染即判定为不支持 timeoutTimer = setTimeout(() => clearTest(false), 300); testFrame.onload = () => { try { const frameDoc = testFrame.contentDocument; // 支持判定规则:能正常访问iframe内部文档,且存在PDF渲染特征 const support = !!(frameDoc && ( frameDoc.contentType === 'application/pdf' || frameDoc.querySelector('embed[type="application/pdf"]') || frameDoc.querySelector('#pdfViewer') )); clearTest(support); } catch (err) { // 跨域异常、无法访问内部文档,一般是触发了下载/外部打开行为,判定为不支持 clearTest(false); } }; testFrame.onerror = () => clearTest(false); testFrame.src = TEST_PDF; document.body.appendChild(testFrame); }); }
2. 轻量前置兜底判断
只匹配明确验证过不支持内嵌PDF的环境特征,不做全量移动设备UA枚举,就算后续UA规则过期,核心特征检测也能兜住结果,不会出现误判:
function isKnownUnsupportedEnv() { const ua = navigator.userAgent.toLowerCase(); // 仅覆盖已验证的不支持场景,不需要长期维护全量UA库 return /android.*(chrome|edge|firefox)/.test(ua) || (/iphone|ipad/.test(ua) && !/safari/.test(ua)); }
3. 预览逻辑调用流程
- 触发PDF预览时,先调用
isKnownUnsupportedEnv(),如果命中直接展示带下载链接的备选提示 - 未命中兜底规则时,调用
checkPdfEmbedSupport()获取实际支持情况 - 支持则渲染内嵌PDF预览组件,不支持则展示备选提示
方案优势
- 完全不依赖屏幕尺寸、触摸支持等和PDF渲染能力无关的判断维度,诊所场景下的小尺寸老旧桌面设备只要支持内嵌PDF就会正常展示预览,不会误触发备选视图
- 维护成本极低:不需要长期跟进UA变更、浏览器版本更新,特征检测直接验证浏览器实际能力,适配所有现有和未来的浏览器版本
- 性能开销可忽略:测试样本体积极小,检测过程用户完全无感知,同会话内结果缓存不重复检测
- 准确率高:覆盖所有移动端不支持内嵌的场景,包括触发下载、跳转外部阅读器、嵌入空白框等异常情况
内容的提问来源于stack exchange,提问作者Steve -Cutter- Blades
相关产品推荐
相关产品推荐

