纯css无法实现符合material design规范的按钮涟漪动效,因其无法获取点击坐标、:active伪类生命周期过短且无法驱动三段式径向动画,必须结合js计算坐标并操控::after伪元素。

纯 CSS 无法实现符合 Material Design 规范的按钮涟漪动效——它必须响应点击坐标,而 :active 伪类只能触发全局状态,无法读取 clientX/clientY,也不能驱动三段式(出现→扩散→淡出)径向动画。
为什么 :active + transition 不行
常见错误是给按钮加 transition: all 0.4s,再写 button:active::after { transform: scale(3); opacity: 0; }。这会导致:
- 涟漪圆心永远固定在左上角或中心,不随点击位置变化
- 动画只在按下瞬间触发,:active 生命周期太短(毫秒级),撑不起完整扩散过程
- 所有点击都复用同一套动画,违背 Material 的“点击即反馈”直觉
- 无法约束涟漪不溢出按钮边界(尤其带 border-radius 时)
必须用 ::after 伪元素 + JS 计算坐标
核心链路是监听事件 → 获取按钮相对坐标 → 动态设置 ::after 的 left/top 或 transform: translate() → 启动 @keyframes 动画。关键细节:
- 按钮必须设 position: relative,否则 ::after 会以最近定位祖先为参考系
- ::after 需设 position: absolute; z-index: -1; pointer-events: none;,否则遮挡文字或拦截后续点击
- 圆心坐标要约束在按钮内:left: Math.max(0, Math.min(x - radius, width - diameter))
- 初始状态用 transform: scale(0),比 width: 0; height: 0 更高性能且兼容硬件加速
- 动画结束必须加 animation-fill-mode: forwards,否则波纹缩回原形
iOS Safari 和安卓 WebView 的卡顿修复项
Safari(尤其 iOS 15 及更早)对 transform: scale() + 伪元素组合有重绘 bug,表现是涟漪一闪而过、放大中途卡死。实测有效的绕过写法:
- 给 ::after 加 will-change: transform(仅动画期间动态添加,结束后移除)
- 放弃 left/top 定位,统一改用 transform: translate(x, y) scale(s)
- 按钮父容器(即按钮自身)必须加 overflow: hidden,否则 Safari 会裁切动画轨迹
- 动画中只改 transform 和 opacity,避免同时修改 background 等触发 layout 的属性
- 移动端优先用 touchstart(click 有 300ms 延迟),但需同时监听 mousedown 保证桌面端兼容
多点击与性能控制的关键点
用户快速连点时容易堆叠多个涟漪,导致内存泄漏或动画错乱:
- 每次点击前先检查 if (btn.rippleActive) return;,做简单节流
- 推荐用 button.insertAdjacentElement('afterbegin', ripple) 插入涟漪元素,确保它始终在按钮内容最底层,避免被图标/文字遮挡
- 涟漪 DOM 元素应在动画结束后立即 remove(),不要复用;伪元素方案则靠 CSS 自动回收
- rippleSize 建议取按钮宽高较大值 × 1.2,保证全覆盖又不过度膨胀
真正合规的涟漪效果,不是“有没有动画”,而是“坐标是否精准、边界是否可控、多点是否隔离”。这些细节在真机上一试便知,别被“纯 CSS 实现”的标题误导了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











