直接用 :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
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 触发不可靠:移动端可能不触发,鼠标抬起过快会导致动画中断,也无法控制多点点击。
立即学习“前端免费学习笔记(深入)”;
is-rippling,CSS 写 .btn.is-rippling::after { transform: scale(1); opacity: 0; }
animationend 事件移除类,而不是用 setTimeout——浏览器动画帧可能延迟,setTimeout 容易误判if (button.rippling) return; button.rippling = true;,动画结束再置 false