必须用getBoundingClientRect()将视口坐标转为相对坐标,否则伪元素定位偏移;移动端须取touches[0]坐标;每次点击前需清空旧值;Safari需will-change: transform+transform定位避bug;pointer-events: none防遮挡连点。

直接用 ::after 伪元素 + transform: scale() 是目前最轻量、兼容性最好的水波纹实现方式,但必须配合 JS 动态设置圆心坐标——纯 CSS 永远做不到点击哪就从哪炸开。
为什么::after必须配合getBoundingClientRect()算坐标
浏览器无法在 CSS 里读取 click 或 touchstart 的坐标,event.clientX 是相对于视口的,而伪元素的 left/top 是相对于父元素(需 position: relative)的。不转换就会偏移。
- 漏掉
const rect = button.getBoundingClientRect(),所有坐标都错 -
left应设为e.clientX - rect.left + 'px',不是e.offsetX(IE 不支持,嵌套 transform 下失真) - 移动端必须从
e.touches[0].clientX取值,不是e.clientX - 每次点击前要清空旧值:
button.style.setProperty('--x', '0px'),否则新波纹从上一次位置起始
transform-origin: center 和 translate(-50%, -50%) 到底谁该用
两者目的都是让缩放以点击点为中心,但适用场景不同:
- 如果用
left/top定位伪元素,必须设transform-origin: center center,否则scale()会以左上角为基点拉伸 - 如果改用
transform: translate(x, y) scale(s)(推荐,尤其 Safari),就不需要transform-origin,但必须加translate(-50%, -50%)把圆心对齐到坐标点 - Safari 15.4 以下对
top/left + scale组合有渲染 bug,强制要求统一走transform定位
动画卡顿、闪一下、或边缘发虚的三个硬性修复点
这些问题几乎都集中在 Safari 和高 DPR 屏幕上,不是缓动函数能解决的。
立即学习“前端免费学习笔记(深入)”;
- 给伪元素加
will-change: transform,Safari 必须这个才触发硬件加速,否则首帧空白或卡顿 - 避免小数像素缩放:计算
scale目标值时用Math.round(),比如scale(2.345)改成scale(2.3),减少 subpixel 插值模糊 - 父按钮加
overflow: hidden,否则波纹放大后会撑出边界,尤其在圆角按钮上 Safari 会裁切动画轨迹 - 别用
transition: all,只写transition: transform 0.6s cubic-bezier(0.2, 0, 0.1, 1), opacity 0.6s,否则其他属性变化会干扰动画
伪元素样式里pointer-events: none为什么不能省
这是连点失效、第二下点不中的根本原因。伪元素是绝对定位,但默认仍参与事件捕获流,会挡住后续点击。
- 不加
pointer-events: none,快速连点时第二个click可能被上一个还没销毁的伪元素拦截 -
z-index无法解决这个问题——它只控制绘制顺序,不改变事件捕获路径 - 即使伪元素
opacity: 0且scale(0),只要没设pointer-events: none,它就在事件流里“存在” - 这个属性必须写在伪元素规则里,不能靠父级控制
真正难的从来不是写出第一版水波纹,而是处理 Safari 的渲染异常、连点时的动画状态重置、以及高 DPR 下的像素对齐——这些细节不手动兜底,效果在真机上大概率崩。

















