纯CSS无法实现精准定位的波纹效果,必须用JS计算点击坐标并存入CSS自定义属性,再由::after读取定位与动画。

纯 CSS 无法实现“点击哪就在哪炸开”的波纹效果——:active 不提供坐标,::after 无法读取 clientX/clientY,所有“纯样式”方案实际都是居中扩散。
为什么不能只靠 :active + @keyframes
:active 是瞬时状态:按下即激活、松开即销毁,浏览器不会等你动画播完。它不携带任何位置信息,CSS 也无法在伪类中调用 JS 变量或函数。强行写 animation: ripple 0.6s,波纹永远从左上角或中心开始,和用户点击点无关。iOS Safari 默认禁用 :active,连基础反馈都不可靠。
必须用 JS 计算坐标 + ::after 承载动画
核心是把点击坐标存进 CSS 自定义属性,再由伪元素读取并定位。这不是“混合方案”,而是唯一能兼顾定位精度与渲染性能的路径。
- 按钮必须设
position: relative,否则::after的absolute会相对于 body 偏移 - 监听
click(桌面)和touchstart(移动端),用getBoundingClientRect()算出相对偏移 - 把
x、y存为style.setProperty('--x', x + 'px')和--y,别用dataset或attr()—— 后者不支持单位,Safari 不兼容 -
::after初始设transform: translate(var(--x), var(--y)) scale(0),动画中改为scale(25)+opacity: 0 - 务必加
animation-fill-mode: forwards,否则动画一结束就跳回scale(0) opacity: 1,闪一下
transform: scale() 比 width/height 关键在哪
改 width 和 height 会触发 layout(重排),尤其在低端安卓机上卡顿明显;transform: scale() 只走合成层,GPU 加速,且能和 opacity 同步动画而不冲突。
- 初始状态必须是
scale(0) opacity: 0.5,不是0—— 否则缩放前会“闪现”一个圆点 - 放大倍数建议按按钮最大边长 × 1.5 计算,比如按钮宽高 48px,
scale(36)才能覆盖全区域 - 不要同时用
top/left和transform定位圆心,Safari 会渲染异常;统一用transform: translate(x, y) scale(s) - 动画曲线用
cubic-bezier(0.2, 0, 0.1, 1),比ease-out更接近水波衰减感
移动端必须处理的三个硬性兼容点
iOS Safari 对伪元素 + transform 组合有重绘 bug,不加特定防护会卡顿、闪烁甚至不触发动画。
- 给
::after加will-change: transform,强制开启硬件加速 - 按钮需加
touch-action: manipulation,避免 300ms 延迟,也防止误判为滑动 - 快速连点时,每次触发前先清掉旧值:
element.style.removeProperty('--x'),否则新动画会被旧坐标干扰
真正难的不是写动画,而是让每个点击坐标都精准落在视觉中心——这要求 JS 算坐标的逻辑严丝合缝,CSS 里所有定位、缩放、透明度的时序和数值必须咬合无误。漏掉 will-change、写错 transform-origin、或忘了 animation-fill-mode,都会导致波纹歪斜、残留、卡顿或根本不出现在点击点。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











