水波纹效果必须用::after伪元素配合JS计算点击坐标实现,核心是动态生成圆形遮罩并scale放大+opacity淡出;按钮需设position:relative,::after设absolute、border-radius:50%、transform:scale(0),JS只注入坐标并添加触发类,CSS负责动画,键盘和触摸事件需分别适配。

水波纹效果必须用 ::after 伪元素模拟,不能靠背景色渐变
纯 CSS 实现点击涟漪,核心是动态生成一个圆形遮罩并放大+透明度淡出。直接改 background 或用 transition 切换渐变色做不到“从点击点中心扩散”的效果——因为背景渐变没有锚点控制。必须用绝对定位的 ::after 伪元素,通过 JS 注入点击坐标(left/top),再用 transform: scale() 和 opacity 过渡。
常见错误:给按钮加 overflow: hidden 却忘了设 position: relative,导致 ::after 脱离定位上下文,涟漪飞到页面左上角;或者用 width/height 动画,性能差且无法响应式适配。
- 按钮容器必须设
position: relative -
::after必须设content: ""、position: absolute、border-radius: 50% - 初始
transform: scale(0),激活时设为scale(3)或更大(确保覆盖整个按钮) - 过渡只写
transform和opacity,避免触发布局重排
JS 只负责计算点击坐标并添加 class,不操作样式属性
CSS 负责动画逻辑,JS 只做一件事:监听 click,拿到 clientX/clientY 相对于按钮左上角的偏移,设置为 ::after 的 left 和 top,然后加一个触发类(如 ripple-active)。不要用 JS 改 style.left 或写内联 transform——这会让 CSS 动画失效或冲突。
关键细节:坐标要减去按钮的 getBoundingClientRect() 值,再考虑滚动偏移(window.scrollX/Y),否则在滚动页面中点击位置会偏移。
立即学习“前端免费学习笔记(深入)”;
- 用
element.addEventListener('click', e => {...}),别用onclick - 计算 left =
e.clientX - rect.left - window.scrollX,top 同理 - 设置
element.style.setProperty('--x', left + 'px')和--y,让 CSS 读取 - 立刻添加 class 触发动画,之后用
transitionend移除 class 清理状态
:focus-within 无法替代 click,键盘操作需额外支持
有人试图用 :focus-within 或 :active 模拟涟漪,但 :active 持续时间太短,松开鼠标就消失,无法完成完整 scale+fade 动画;:focus-within 又只适用于聚焦场景,且无法获取点击坐标。所以真实可用的方案必须依赖 JS 的 click 事件。
但键盘用户(Tab + Enter/Space)也不能被忽略。需要监听 keydown,对 Enter 和 Space 键手动触发相同涟漪逻辑,并确保按钮有 tabindex="0"。
- 按钮需设
tabindex="0"才能被键盘聚焦 -
keydown中判断e.key === 'Enter' || e.key === ' '(注意空格键是' ') - 键盘触发时,涟漪中心默认设为按钮水平垂直居中(
left: 50%; top: 50%),不用坐标计算 - 避免
Space键默认滚动页面,加e.preventDefault()
移动端 touchstart 替代 click,但需防重复触发
在 iOS/Android 上,click 有约 300ms 延迟,且可能因双击缩放被拦截。必须改用 touchstart,但要注意:一次触摸可能触发多次 touchstart(比如多指),且 touchstart 的 touches[0] 坐标才是真实触点。
更麻烦的是,某些安卓浏览器会在 touchstart 后立刻触发 click,造成涟漪播两次。解决方案是加节流标记(如 isRippling),并在 touchstart 中立刻设为 true,click 回调开头检查该标记并 return。
- 监听
touchstart,取e.touches[0].clientX等坐标 - 加
preventDefault()防止部分安卓触发默认行为 - 用
setTimeout(() => isRippling = false, 1000)重置标记(略长于动画时长) - CSS 中动画时长设为
0.6s,JS 节流设为700ms更稳妥
实际效果是否自然,取决于 scale 放大倍数、opacity 起止值、timing-function(推荐 cubic-bezier(0.25, 0.46, 0.45, 0.94))和清除时机——很多人忘了在 transitionend 里移除 class,导致后续点击失效。


















