纯CSS无法实现真水波纹,必须用JS获取点击坐标并动态创建ripple元素:先通过getBoundingClientRect()换算局部坐标,再用absolute定位+transform-origin:center+scale/opacity动画模拟扩散,最后手动remove避免DOM泄露。

用 :active 伪类加 transform 和 opacity 做不了真水波纹
纯 CSS 水波纹(ripple)不是靠 :active 简单缩放能模拟的——它必须从点击位置中心发散,且动画结束前不能被重复触发。浏览器原生不提供“点击坐标转 CSS 动画起点”的能力,所以必须用 JS 获取 clientX/clientY,再动态生成一个 span 元素作为波纹载体。
JS 获取点击位置 + 动态插入 ripple 元素的关键步骤
核心是把点击事件坐标映射到按钮/容器的局部坐标系,再用绝对定位+缩放动画实现扩散。容易漏掉的点比想象中多:
- 要用
getBoundingClientRect()把clientX/clientY转成相对于目标元素左上角的偏移值 - ripple 元素必须设
position: absolute,且父容器需有position: relative或position: absolute - 缩放起点要设为
transform-origin: center center,否则会偏移 - 动画结束后必须手动
remove()ripple 元素,否则 DOM 泄露
示例关键逻辑:
button.addEventListener('click', e => {
const rect = button.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const ripple = document.createElement('span');
ripple.style.left = `${x}px`;
ripple.style.top = `${y}px`;
ripple.classList.add('ripple');
button.appendChild(ripple);
// 动画结束后移除
setTimeout(() => ripple.remove(), 600);
});
@keyframes ripple 的缩放和透明度组合要点
只用 scale() 会显得僵硬,必须叠加 opacity 渐变才能模拟真实水波消散感。常见错误是 opacity 从 1 开始——实际应该从 0.4 左右起步,避免突兀闪现:
立即学习“前端免费学习笔记(深入)”;
- 起始帧:
transform: scale(0); opacity: 0.4; - 中间帧(约 60%):
transform: scale(2.5); opacity: 0.2;(扩散最大但已半透明) - 结束帧:
transform: scale(3); opacity: 0;(完全透明,避免残留) - 动画时长建议 400–600ms,太短像抖动,太长拖沓
CSS 示例:
@keyframes ripple {
0% { transform: scale(0); opacity: 0.4; }
60% { transform: scale(2.5); opacity: 0.2; }
100% { transform: scale(3); opacity: 0; }
}移动端 click 延迟和 touchstart 的兼容处理
在 iOS Safari 和部分安卓浏览器里,click 事件有约 300ms 延迟,导致水波纹响应滞后。必须监听 touchstart 替代 click,但要注意:
- PC 浏览器没
touchstart,得 fallback 到click -
touchstart的e.touches[0]才是有效触点,不是e.changedTouches[0] - 要阻止默认行为
e.preventDefault(),否则可能触发双击缩放或滚动 - 同一元素上同时绑定
click和touchstart会导致移动端触发两次,得加防重逻辑(比如用isTouching标志位)
最简兼容写法:
const isTouch = 'ontouchstart' in window; const eventType = isTouch ? 'touchstart' : 'click'; element.addEventListener(eventType, handleRipple);
水波纹看似简单,真正卡住人的地方永远是坐标换算精度、移动端事件差异、以及动画结束后忘记清理 DOM —— 这三处一错,效果就断在半路。










