涟漪动画核心是用::after伪元素在点击处动态生成圆形波纹,通过position:absolute定位、transform:scale()缩放、overflow:hidden裁剪实现;需JS计算相对坐标并传入CSS变量,配合transform-origin精准控制起始点,使用touchstart优化移动端响应。

涟漪动画的核心原理是用 ::after 伪元素模拟圆形波纹
点击瞬间在按钮内部动态生成一个圆形 ::after 元素,从点击坐标中心出发,同时缩放(transform: scale())并淡出(opacity),配合 overflow: hidden 裁剪超出按钮区域的部分。关键不是“画圆”,而是“定位+缩放+裁剪”三者配合。
常见错误是直接给按钮加 border-radius: 50% 后再套涟漪——这会让波纹被裁成椭圆或不完整;必须确保按钮本身有 position: relative,且伪元素使用 position: absolute 定位到点击点。
- 按钮需设
overflow: hidden,否则波纹会溢出 - 伪元素初始
width和height设为0,靠transform: scale(0)配合transform-origin控制起始点 - 必须用 JavaScript 捕获点击坐标,动态设置
left和top,不能写死
JavaScript 必须实时计算点击位置并设置 left/top
CSS 无法获取鼠标坐标,所以得用 JS 把点击位置转为相对于按钮左上角的偏移值,再赋给伪元素的 left 和 top。注意不是 clientX/clientY,而是减去按钮的 getBoundingClientRect() 值。
容易忽略的是:如果按钮有内边距(padding)或边框(border),坐标要再减去这些值,否则波纹中心会偏移。更稳妥的做法是直接用 event.offsetX 和 event.offsetY(但注意 IE 不支持,现代项目可忽略)。
立即学习“前端免费学习笔记(深入)”;
- 推荐写法:
const rect = btn.getBoundingClientRect(); const x = e.clientX - rect.left - btn.clientLeft; - 设置样式时,用
btn.style.setProperty('--x', x + 'px')将坐标传入 CSS 变量,比操作style.left更干净 - 避免重复添加动画:每次点击前先移除旧的伪元素类(如
ripple-active),再添加,防止动画叠加卡顿
transform-origin 必须设为 center 或具体坐标
如果不设 transform-origin,默认是 50% 50%,看起来没问题,但一旦按钮宽高不等或设置了 border-radius,波纹就可能拉伸变形。最可靠的方式是显式设为 var(--x) var(--y),和 JS 传入的坐标一致。
示例 CSS 片段:
button::after {
content: '';
position: absolute;
width: 0;
height: 0;
background: rgba(0,0,0,0.2);
border-radius: 50%;
transform: scale(0);
transform-origin: var(--x) var(--y);
transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.6s;
}注意 cubic-bezier(0.4, 0, 0.2, 1) 是 Material Design 推荐的缓动,比 ease-out 更自然;opacity 也要参与过渡,否则收尾生硬。
移动端点击延迟和多点触控需额外处理
在 iOS Safari 或部分安卓浏览器中,click 事件有约 300ms 延迟,导致涟漪响应滞后。应改用 touchstart,但要注意它不冒泡、且可能触发多次(比如手指滑动时)。
另一个坑是多点触控:用户两指同时点下,会触发多个 touchstart,每个都生成一个波纹。解决方案是只取第一个触点(e.touches[0]),并禁用 touch-action: manipulation 防止系统默认手势干扰。
- 监听
touchstart时,用e.touches[0].clientX替代e.clientX - 给按钮加
style="touch-action: manipulation"可加速响应,但要测试是否影响滚动 - PC 和移动端逻辑最好合并:用
e.type === 'touchstart' ? e.touches[0] : e统一取坐标
真实项目里最容易被忽略的是波纹尺寸上限——如果按钮很小(比如 24px 图标按钮),按默认逻辑生成的波纹直径可能远超按钮宽高,导致动画时间过长或视觉失衡。建议根据按钮尺寸动态算最大半径,比如 Math.max(btn.clientWidth, btn.clientHeight) * 1.5,再限制 max-width 和 max-height。


















