最简纯CSS水波纹方案是用伪元素::after配合radial-gradient与transform scale动画,定位需relative父容器,pointer-events: none防遮挡;精准定位需JS注入--x/--y变量;圆角容器宜用clip-path裁切;移动端须加touch-action: manipulation并移除:active干扰。

用 radial-gradient + animation 实现点击水波纹最简方案
纯 CSS 就能做,不需要 JS 监听点击或插入元素。核心是利用 radial-gradient 画出圆形渐变,再用 @keyframes 控制透明度和尺寸变化。
常见错误是直接给按钮加 background: radial-gradient(...) 然后动画——这会覆盖原有背景色,且无法定位到点击点。正确做法是用伪元素 ::after 叠在元素上,并通过 transform: scale() 模拟扩散。
- 必须设
position: relative在触发元素上,否则伪元素无法精确定位 - 伪元素初始
opacity: 0,动画末尾设为0并配合transform: scale(3)让它“撑开消失” - 动画时长建议
300ms–400ms,太快像闪一下,太慢失去反馈感 - 别忘了加
pointer-events: none到伪元素,否则第二次点击会因遮挡失败
button {
position: relative;
overflow: hidden;
}
button::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255,255,255,0.3);
border-radius: 100%;
transform: translate(-50%, -50%) scale(0);
animation: ripple 350ms linear;
pointer-events: none;
}
@keyframes ripple {
to {
transform: translate(-50%, -50%) scale(3);
opacity: 0;
}
}让水波纹从点击位置出发而不是居中
纯 CSS 无法读取鼠标坐标,所以“精准定位”必须靠 JS 注入 left/top 值。但不用写完整事件系统——只需在点击时动态设置两个 CSS 自定义属性,CSS 动画读取它们即可。
关键点在于:JS 只负责记坐标,不控制动画;CSS 仍主导渲染,性能有保障。
立即学习“前端免费学习笔记(深入)”;
- 监听
click事件,用e.clientX - e.target.getBoundingClientRect().left算出相对 X - 用
element.style.setProperty('--x', x + 'px')注入变量 - CSS 中伪元素改用
left: var(--x); top: var(--y);,不再固定50% - 记得每次点击前重置变量,否则旧值残留会导致水波纹错位
clip-path 方案适合圆角容器或不规则形状
当按钮带 border-radius 或是图片、卡片等非矩形区域时,radial-gradient 扩散会溢出边缘,显得生硬。clip-path 能强制裁切动画区域,更贴合视觉边界。
缺点是 Safari 旧版本支持差(clip-path: circle() 需要 -webkit- 前缀),且不能和 transform 同时用于同一元素做复杂动画。
- 对圆角按钮,直接用
clip-path: inset(0 round 8px)配合scale更自然 - 若用
circle(),半径得随动画增大,需配合@keyframes动态改clip-path值 - 注意
clip-path会裁掉阴影、边框外扩部分,调试时先关掉其他效果聚焦裁切逻辑
移动端点击延迟导致水波纹滞后?禁用 :active 伪类干扰
iOS Safari 默认有 300ms 点击延迟,且会优先触发 :active 样式(比如背景变暗),和水波纹动画抢资源,造成卡顿或不触发。
- 加
touch-action: manipulation到按钮,告诉浏览器这是手势操作,可跳过延迟 - 移除所有
button:active { ... }样式,改用 JS 添加临时 class 控制按下态 - 确保伪元素
::after的z-index高于任何:active改动的背景层 - 真机测试必做:模拟器里表现正常,但 iPhone 上可能因渲染管线不同而掉帧
实际最难调的不是动画本身,而是坐标计算精度和多层叠加时的 z-index 层级冲突。尤其在 flex/grid 容器里嵌套按钮,getBoundingClientRect() 返回的坐标容易受滚动、缩放影响,建议加一层 container.getBoundingClientRect() 做归一化。



















