直接用 :active 无法实现真实波纹,因其仅表示布尔状态且不携带点击坐标,导致波纹总从中心触发、ios safari 默认禁用、移动端动画易中断;须通过 getboundingclientrect() 计算相对坐标,用 css 变量 --x/--y 配合伪元素 transform: translate(-50%,-50%) 锚定圆心,并加 position: relative、border-radius: 50%、pointer-events: none,再以 js 控制临时类与 animationend 清理。

为什么直接用 :active 无法实现真实波纹
因为 :active 只是一个布尔状态,不携带坐标信息。你写 button:active { background: radial-gradient(circle at 50% 50%, ...); },所有点击都从按钮正中心开始——用户点左下角,波纹却从正中炸开,交互失真。更麻烦的是,iOS Safari 默认禁用 :active,必须加 * { cursor: pointer; } 或 touch-action: manipulation 才勉强触发,且移动端手指抬起快,动画常被截断。
必须用 getBoundingClientRect() 算坐标,不能用 e.offsetX
e.offsetX 在 IE 完全不支持,在有 transform、scale 或 iframe 嵌套的容器里返回值不可靠。真实项目中 90% 的波纹偏移错误都源于没做坐标归一化。
- 调用
button.getBoundingClientRect()获取按钮左上角相对于视口的位置 - 计算相对偏移:
const x = e.clientX - rect.left,const y = e.clientY - rect.top - 写入带单位的 CSS 变量:
button.style.setProperty('--x', x + 'px'),单位px必须显式带上,否则var(--x)在left中无法解析 - 每次点击前重置:
button.style.setProperty('--x', '0px'),否则连续点击时新波纹从旧位置起始
伪元素怎么读取 --x/--y 并精准锚定
伪元素不能用 attr(data-ripple-x)——Safari 不支持,且不支持单位运算;唯一可靠方式是 CSS 变量 + transform: translate(-50%, -50%) 把圆心拉到点击点。
- 按钮必须加
relative类(即position: relative),否则::after会相对于body定位 -
::after设position: absolute; top: 0; left: 0;,再写left: var(--x); top: var(--y); transform: translate(-50%, -50%) scale(0); - 漏掉
border-radius: 50%就是方块扩散,不是波纹 - 务必加
pointer-events: none,否则波纹层会拦截后续点击
动画触发和清理的关键时机
仅靠 :active 触发不可靠:移动端可能不触发,鼠标抬起过快会导致动画中断,也无法控制多点点击。
- JS 中添加临时类如
is-rippling,CSS 写.btn.is-rippling::after { transform: scale(1); opacity: 0; } - 监听
animationend事件移除类,而不是用setTimeout——浏览器动画帧可能延迟,setTimeout容易误判 - 快速连点需节流:
if (button.rippling) return; button.rippling = true;,动画结束再置false - 伪元素插入后,第一帧渲染需要等待浏览器合成层就绪,这点常被忽略
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











