拖拽式图片对比需两个绝对定位的叠加于相对定位容器内,中间覆盖高z-index可拖动分隔线,通过clip-path或宽度控制裁剪,配合requestanimationframe节流及touch事件兼容实现平滑响应。

拖拽式图片对比需要什么 DOM 结构
核心是两个 <img> 元素叠加,再用一个可拖动的分隔线(<div>)控制显示比例。不能只放两张图并排——那样根本没法“拖动对比”。分隔线必须覆盖在重叠图层上,且 <code>z-index 高于图片,同时监听 mousedown + mousemove 实现拖拽逻辑。
常见错误:把分隔线写在两个 <img> 外部、或用 flex 并排布局——这会导致拖拽时只移动分隔线,不改变图片可见区域。
- 容器设为
position: relative - 两张图都设
position: absolute; top: 0; left: 0; width: 100%; height: 100% - 旧图加
clip-path: inset(0 0 0 var(--right))(或用overflow: hidden+ 宽度控制) - 新图加
clip-path: inset(0 var(--left) 0 0)(或同理裁剪) - 分隔线设
position: absolute; left: 50%; top: 0; height: 100%; width: 2px; background: #fff; cursor: col-resize
如何让拖拽响应平滑不卡顿
直接在 mousemove 里反复设置 element.style.left 或修改 clip-path 会触发大量重排重绘,尤其在高分辨率图上明显掉帧。关键不是“能不能动”,而是“动起来是否跟手”。
必须用 requestAnimationFrame 节流,且把计算逻辑和样式更新分离。不要在事件回调里做像素边界判断,提前算好容器宽度、限制拖动范围(比如 min=0, max=containerWidth),避免拖出边界后松手回弹错乱。
- 监听
mousedown时记录初始鼠标位置和当前分隔线left值 - 在
mousemove里只更新临时偏移量,不改 DOM - 用
requestAnimationFrame批量更新left和clip-path,并做边界 clamp - 避免对
<img>设置transform: scale()—— 这会让clip-path计算失准
移动端 touch 事件怎么兼容
桌面端靠 mouse 事件够用,但 iOS/Android 必须处理 touchstart/touchmove/touchend,且不能简单地把 event.clientX 换成 event.touches[0].clientX 就完事——Safari 对 touchmove 的默认行为(如页面滚动)会干扰拖拽。
最简方案是:在 touchstart 里加 event.preventDefault(),但只对分隔线元素生效,别影响外层滚动。同时注意,移动端没有 hover 状态,分隔线默认宽度 2px 太细,建议 touch 设备下自动加宽到 8px,并加 user-select: none 防止误选文本。
- 给分隔线绑定
touchstart、touchmove、touchend三事件 -
touchmove回调里取event.touches[0],不是event.changedTouches[0] - 在
touchstart中调用event.preventDefault(),阻止默认滚动 - 用
CSS @media (pointer: coarse)单独设分隔线尺寸和背景色
图片加载失败或尺寸未定就拖拽会怎样
如果 <img> 还没加载完、naturalWidth 是 0,或者容器宽高靠内容撑开(比如没设 height),拖拽时会出现分隔线跳变、裁剪区域错位、甚至 JS 报 NaN 错误。这不是交互 bug,是时机问题。
可靠做法是等两张图都触发 load 事件,且容器已渲染出尺寸(可用 getBoundingClientRect() 校验),再启用拖拽逻辑。千万别用 setTimeout 猜时间——网络慢时照样崩。
- 用
Promise.all([img1.decode(), img2.decode()])确保解码完成(防 Safari 加载后不触发 load) - 检查
img.naturalWidth > 0 && container.offsetWidth > 0再初始化拖拽 - 给
<img>加crossorigin="anonymous"(若跨域),否则decode()会 reject - 容器高度建议用
aspect-ratio或明确height,避免依赖图片加载后重排
getBoundingClientRect() 动态读取,而不是靠 CSS 百分比硬算。











