最稳方案是直接用 + showmodal(),ios/安卓主流浏览器均支持,无需手动处理全屏兼容、esc退出、状态同步;旧版safari/edge需降级为requestfullscreen()+遮罩层。

直接用 <dialog></dialog> + showModal() 是最稳的现代方案,iOS 和安卓主流浏览器都支持,不用手动处理全屏 API 兼容性、ESC 退出、状态同步这些坑。
但如果你的项目要兼容 Safari 15.4 之前或旧版 Edge,就得降级到 requestFullscreen() + 遮罩层。下面分场景讲清楚怎么做、为什么这么选、以及最容易栽跟头的地方。
用 <dialog></dialog> 实现原生模态预览(推荐优先尝试)
Chrome 93+、Firefox 102+、Edge 93+ 和 Safari 15.4+ 原生支持 <dialog></dialog>,它自带 ESC 关闭、焦点管理、背景点击穿透拦截,比手写遮罩干净得多。
- 图片触发元素加
onclick="document.getElementById('imgDialog').showModal()",别用onmousedown或异步回调,否则 Safari 会拒绝 -
<dialog></dialog>内部只放一个<img>,src必须是完整 URL(相对路径在模态框里容易 404),可用new URL(img.src, location.href).href补全 - CSS 上必须设
max-width: 90vw; max-height: 90vh;,不然大图溢出;加object-fit: contain;保比例,别用width: 100%; - 关闭按钮写
<button onclick="this.closest('dialog').close()">×</button>,不要调document.exitFullscreen()——<dialog></dialog>不走全屏 API
对 requestFullscreen() 的兼容性兜底(旧浏览器必需)
当目标用户包含 iOS 15.3 及更早版本、IE 或某些定制 WebView 时,<dialog></dialog> 会静默失效,必须切回全屏 API。但注意:Safari 对非 <video></video> 元素调 requestFullscreen() 一直不支持,所以得用“伪全屏”——即固定定位 + 100vw/vh 遮罩。
- 别对
<img>直接调requestFullscreen(),部分 Chrome/Firefox 会失败;包裹一层<div class="fs-container"><img></div>再调用 - 调用前必须检查:
if (el.requestFullscreen) el.requestFullscreen(); else if (el.webkitRequestFullscreen) el.webkitRequestFullscreen(); - 监听
document.addEventListener('fullscreenchange', () => { if (!document.fullscreenElement) { /* 清理状态 */ } }),不能只靠关闭按钮重置 UI - 退出后记得
el.blur(); document.querySelector('img').focus();,否则键盘 Tab 会卡住
移动端双击缩放和 touch 事件冲突怎么破
iOS Safari 默认双击放大图片,安卓部分浏览器 click 有 300ms 延迟,这两个问题会让弹窗体验断层。不是加个 user-scalable=no 就完事,得针对性处理。
- 触发图片同时绑定
click和touchstart,并在 handler 里调event.preventDefault() - 预览图片元素加
style="pointer-events: none;",彻底禁用系统级手势 - 遮罩层加
style="touch-action: none;",防止手指滑动误触发页面滚动 - 如果项目已用 viewport 禁用了缩放,这条可跳过;否则必须加,否则用户双击就跳出预览
表格中多张图片怎么统一绑定预览逻辑
表格里每行都有缩略图,不可能给每个 <img> 单独写 onclick。用事件委托最省事,但也容易漏掉 data 属性或路径补全。
- 统一用
data-fullsrc存原图地址,缩略图 src 可以是 CDN 缩略版,避免带宽浪费 - 表格外层加
id="photoTable",然后用document.getElementById('photoTable').addEventListener('click', e => { if (e.target.matches('img')) { /* 处理 */ } }) - 取地址时优先读
e.target.dataset.fullsrc || e.target.src,别硬编码拼路径 - 动态插入预览结构时,确保每次只存在一个
<div class="overlay">,重复插入不移除会导致内存泄漏和点击失效<p>真正难的不是“怎么显示”,而是“怎么让所有设备上点一下就出来、按 ESC 就关、再点原图还能聚焦回去”。<code><dialog></dialog>能帮你扛下 80% 的状态管理,剩下的 20%,比如路径补全、touch 兼容、焦点恢复,才是实操里最常被跳过的细节。











