水波纹效果不能只靠 :active 实现,因其仅在点击瞬间触发,无法实现从点击点扩散、渐隐的动画;必须用 javascript 动态创建带定位和 scale 动画的 span 元素,并配合 transform-origin 和 overflow: hidden 等细节处理。

水波纹效果为什么不能只靠 :active 实现
:active 伪类只能在鼠标按下或触摸期间触发,松开就立刻结束,而真实水波纹需要从点击点向外扩散、渐隐的动画过程。纯 :active 没有「起点定位」「尺寸动态计算」「持续时间控制」能力,强行用它做动画只会得到全按钮统一缩放或闪一下的假效果。
常见错误现象::active 中写 transform: scale(0.95) 或 opacity: 0.7 —— 这只是按压反馈,不是水波纹。
必须用 JavaScript 定位点击坐标并插入 span 元素
水波纹本质是动态创建一个圆形 span,用 JS 获取 clientX/clientY 相对于按钮左上角的偏移,再设为 left 和 top,最后加 CSS 动画。
- 按钮需设
position: relative,否则子元素无法精确定位 - 水波纹
span必须用position: absolute+border-radius: 50% - 动画关键帧要包含
scale(0)→scale(3)和opacity: 0.4→opacity: 0 - 每次点击都要新建
span,且动画结束后自动remove(),避免 DOM 泄漏
示例核心逻辑:
button.addEventListener('click', e => {
const ripple = document.createElement('span');
const rect = button.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
const x = e.clientX - rect.left - size / 2;
const y = e.clientY - rect.top - size / 2;
ripple.style.left = `${x}px`;
ripple.style.top = `${y}px`;
ripple.style.width = ripple.style.height = `${size}px`;
button.appendChild(ripple);
setTimeout(() => ripple.remove(), 600);
});
CSS 动画需配合 @keyframes 和 transform-origin
如果只用 left/top 移动,边缘点击时水波会跑出按钮;必须用 transform: scale() 并设 transform-origin 为点击坐标,才能保证从中心均匀扩散。
-
transform-origin: center不行 —— 它固定在元素中心,不是点击点 - 正确做法:JS 设置
ripple.style.transformOrigin = `${x}px ${y}px` - 动画时长建议 400–600ms,太短像弹窗,太长拖沓
- 用
will-change: transform, opacity提升动画层,避免低端机卡顿
对应 CSS 片段:
@keyframes ripple {
0% { transform: scale(0); opacity: 0.4; }
100% { transform: scale(3); opacity: 0; }
}
.ripple {
position: absolute;
border-radius: 50%;
background: rgba(0,0,0,0.15);
transform: scale(0);
animation: ripple 600ms linear;
}
移动端触摸事件需额外处理 touchstart
在 iOS 和部分安卓设备上,click 有约 300ms 延迟,且不保证触发位置准确;必须监听 touchstart 并阻止默认行为(e.preventDefault())才能拿到真实触点。
- 同时监听
click和touchstart,但用touchstart优先 - 触摸事件中取
e.touches[0].clientX而非e.clientX - 注意:Safari 对
transform-origin的百分比支持不稳定,坚持用像素值 - 若按钮含文字换行或内边距不均,
getBoundingClientRect()返回的宽高可能不准,可改用offsetWidth/Height计算直径
容易被忽略的一点:水波纹容器(即按钮)的 overflow: hidden 必须显式设置,否则扩散动画会溢出边界。这个属性常被遗忘,尤其当按钮用了 flex 或 grid 布局时。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











