纯CSS无法实现点击位置触发的真实波纹效果,因:active无坐标信息;真实方案需JS计算clientX/clientY并动态设置--x/--y变量,配合transition和transitionend清理。

纯 CSS 做不了“点击哪、波纹从哪起”的真实波纹效果——:active 没有坐标信息,@keyframes 也不能读取 clientX/clientY。所谓“纯 CSS 波纹”,实际只是固定从按钮中心炸开的简化版,适合快速预览或降级兜底。
为什么直接写 ::after + :active 看不到波纹?
最常见卡点就三个:
-
button没设position: relative,导致::after相对于body定位,波纹全飘到页面左上角 - 忘了加
overflow: hidden,圆形伪元素放大后直接溢出按钮边界,尤其在圆角按钮上特别明显 -
::after缺少transform: translate(-50%, -50%),即使写了top: 50%; left: 50%,圆心也不在正中——它会以左上角为锚点缩放
用 transition 而不是 @keyframes 更稳妥
动画控制权交给 JS 切换 class,比每次触发新 animation 更易清理、中断和复用。基础结构如下:
.ripple-btn {
position: relative;
overflow: hidden;
outline: none;
}
.ripple-btn::after {
content: "";
position: absolute;
border-radius: 50%;
background: rgba(0,0,0,0.1);
transform: translate(-50%, -50%) scale(0);
transition: transform 0.4s cubic-bezier(0.2, 0, 0.1, 1), opacity 0.4s;
pointer-events: none;
z-index: -1;
}
.ripple-btn.is-rippling::after {
transform: translate(-50%, -50%) scale(3.5);
opacity: 0;
}关键点:
立即学习“前端免费学习笔记(深入)”;
- 初始
scale(0)+opacity: 1(Safari 首次点击需不为 0 才触发) - 结束态用
scale(3.5)足够覆盖多数按钮尺寸,太大反而边缘模糊 - 必须同时过渡
transform和opacity,否则动画结束时会突然消失
JS 动态定位波纹圆心的最小必要代码
要让波纹真从点击点出发,必须用 JS 计算坐标并注入。别用 e.offsetX/e.offsetY——它们在带 padding 的按钮上不准:
button.addEventListener('click', (e) => {
const rect = button.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
<p>button.style.setProperty('--x', <code>${x}px</code>);
button.style.setProperty('--y', <code>${y}px</code>);
button.classList.add('is-rippling');</p><p>// 动画结束移除 class,避免连续点击堆叠
button.addEventListener('transitionend', () => {
button.classList.remove('is-rippling');
}, { once: true });
});CSS 里对应写:
.ripple-btn::after {
left: var(--x);
top: var(--y);
}注意:
- 移动端要监听
touchstart并e.preventDefault(),否则可能触发双击缩放 - iOS Safari 对混合定位(
left+transform)合成不稳定,圆心定位优先用transform: translate(x, y),而非left/top - 动画结束必须靠
transitionend清理,不能用setTimeout,真机上容易残留
真正麻烦的不是写动画,而是坐标计算和清理时机——尤其是快速连点时,旧波纹还没收完,新波纹又生成,DOM 和渲染层都容易堆积。别省略 { once: true } 和 class 移除逻辑。


















