不能只靠 :active + @keyframes 实现点击波纹——:active 无坐标信息、持续时间短、不支持动态圆心定位,且移动端兼容性差;必须用 JS 获取点击坐标创建 ripple 元素,配合 CSS transform 动画实现。

直接用 :active 配合 animation 做点击波纹,基本不可行——动画无法在伪类触发时“从点击位置开始播放”,且 :active 持续时间极短、不支持关键帧重置,强行套用只会导致波纹偏移、闪烁或根本不出现在点击点。
为什么不能只靠 :active + @keyframes
浏览器对 :active 的处理是瞬时的:用户按下鼠标就激活,松开就失效。而波纹需要“以点击坐标为圆心、从 0 扩展到足够大、再透明消失”,这要求三个动态要素:起始位置(x/y)、缩放比例、透明度变化——:active 本身不提供坐标,CSS 也无法在伪类中读取 JS 事件的 clientX/clientY。
-
:active触发时,animation只能走预设路径,无法动态定位圆心 - 即使硬写
animation: ripple 0.6s ease-out;,所有点击都会从元素左上角或中心开始,不是真实点击点 - 移动端
:active兼容性差,iOS Safari 默认禁用,需额外加cursor: pointer或touch-action
必须用 JS 创建波纹元素 + CSS 动画控制
核心思路是:监听 click(或 touchstart),计算点击相对于目标元素的坐标,动态插入一个 span 作为波纹载体,并用内联样式设置 left/top,再通过 class 触发动画。
- 波纹容器需设
position: relative,否则绝对定位的波纹会脱出父级范围 - 波纹元素用
position: absolute; border-radius: 50%;,确保圆形扩展 - 动画结束必须手动移除波纹 DOM,否则内存泄漏;可用
animationend事件监听 - 推荐用
transform: scale()替代width/height动画,性能更好且不触发布局重排
示例关键代码:
立即学习“前端免费学习笔记(深入)”;
button {
position: relative;
overflow: hidden;
}
.ripple {
position: absolute;
border-radius: 50%;
background: rgba(0,0,0,0.2);
transform: scale(0);
animation: ripple 0.6s ease-out;
}
@keyframes ripple {
to { transform: scale(4); opacity: 0; }
}移动端 touchstart 适配要点
移动端点击有 300ms 延迟,且 click 事件坐标可能不准(尤其缩放后),应优先用 touchstart 并取 touches[0]。
- 监听
touchstart时,记得加preventDefault()防止滚动干扰(但仅限按钮类可点击区域) - 计算坐标要用
touch.clientX - element.getBoundingClientRect().left,而非pageX,避免页面滚动影响 - iOS Safari 中,若父容器有
overflow: hidden但未设-webkit-overflow-scrolling: touch,波纹可能被裁切 - 为兼容旧安卓,可同时监听
mousedown和touchstart,但需用evt.cancelable && evt.preventDefault()统一处理
性能与可访问性容易被忽略的地方
波纹动画本身很轻量,但高频点击(如连点按钮)会导致大量 DOM 插入/删除,卡顿明显;同时屏幕阅读器用户无法感知纯视觉反馈。
- 加节流:两次波纹间隔至少 150ms,避免瞬间创建多个
.ripple - 用
will-change: transform提前提示 GPU 加速,但别滥用——只加在.ripple上 - 对键盘操作(
Enter/Space)也应触发波纹,监听keydown并判断evt.key === 'Enter' || evt.key === ' ' - 色盲用户可能看不到低对比度波纹,建议默认用
rgba(0,0,0,0.15)而非更淡的值
真正可靠的波纹,是 JS 控制坐标 + CSS 控制形变和消隐的组合,任何试图绕过 JS 动态定位的纯 CSS 方案,最终都会在多设备、多交互方式下露出破绽。


















