真实水波效果需JS捕获点击坐标并动态创建span元素,结合CSS动画实现以点击点为圆心的扩散遮罩;必须用getBoundingClientRect()精确定位、设absolute与border-radius:50%、动画后remove元素;推荐@keyframes替代transition以提升控制力和性能,并统一处理touchstart/mousedown事件防多触发。

用 :active 无法实现真实水波效果
点击瞬间的视觉反馈必须脱离伪类依赖——:active 只能触发样式切换,不能生成动态扩散动画。水波本质是「以点击点为圆心、半径随时间增长」的圆形遮罩,需要 JS 捕获坐标 + CSS 动画配合。
核心实现:JS 创建 span 水波元素并定位
每次点击时,在按钮内部动态插入一个 span,设置其 left 和 top 为鼠标相对按钮左上角的偏移量,再通过 transform: scale() 或 width/height 动画让它从 0 扩散到覆盖整个按钮。
- 必须用
getBoundingClientRect()计算按钮位置,再减去事件clientX/clientY,否则水波中心会偏移 - 水波
span需设position: absolute、border-radius: 50%、pointer-events: none - 动画结束要手动
remove()元素,否则 DOM 节点持续堆积 - 示例关键片段:
const ripple = document.createElement('span');<br>const rect = btn.getBoundingClientRect();<br>ripple.style.left = `${e.clientX - rect.left - radius}px`;<br>ripple.style.top = `${e.clientY - rect.top - radius}px`;<br>btn.appendChild(ripple);
transition vs @keyframes 的选择
用 transition 简单但控制弱:只能定义起止状态,无法精确控制扩散节奏(比如先快后慢);@keyframes 更灵活,适合做缓动或多次缩放(如模拟水波回弹)。
- 若只用
transition,需在 JS 中动态设style.width和style.height,初始为0,然后加 class 触发过渡 - 若用
@keyframes,推荐定义to { transform: scale(3); opacity: 0; },避免重排,性能更好 - 注意:Chrome/Safari 对
transform: scale()的硬件加速支持稳定,但老版 Firefox 在scale(0)到scale(1)之间可能有渲染毛刺
移动端点击位置不准、多次触发问题
触摸事件的 touches[0] 坐标和鼠标事件不完全等价,且 click 在移动端有 300ms 延迟,容易导致水波滞后或重复创建。
立即学习“前端免费学习笔记(深入)”;
- 统一监听
touchstart和mousedown,优先用touchstart的touches[0] - 添加
preventDefault()防止部分安卓浏览器默认高亮干扰 - 用节流(如
setTimeout+ 标志位)阻止快速连点生成多个水波层 - 按钮需有
cursor: pointer和user-select: none,避免 Safari 点击时文字选中打断动画
水波效果真正难的不是动画本身,而是坐标计算精度、多端事件对齐、以及动画结束后及时清理——这三个点漏掉任意一个,都会让效果看起来“卡”“偏”或者“越点越多”。










