真实波纹效果必须用JS计算点击坐标并配合::after伪元素实现,因active:/hover:变体无法获取坐标且动画周期过短;需按钮设relative、伪元素absolute定位并动态注入CSS变量,同时处理Safari渲染bug和移动端touch事件。

不能只靠 active: 或 hover: 变体实现真实波纹——它们无法绑定点击坐标,也无法维持足够长的动画生命周期。
为什么直接写 active:scale-105 不行
Tailwind 默认不启用 active 变体,即使你手动开启,:active 状态也只持续几毫秒,远短于波纹所需的 200–600ms 动画时长。结果就是:点下去没反应,或松手后动画突然中断。更关键的是,它完全无法知道“用户点在哪儿”,所有缩放都从元素中心开始,不是从点击位置扩散。
必须用 JS 计算坐标 + ::after 伪元素
核心是让按钮承载一个绝对定位的圆形遮罩,并由 JS 注入点击坐标。操作要点:
- 按钮加
relative类,否则::after会相对 body 定位 -
::after必须设content: ""、absolute、border-radius: 50%、pointer-events: none、opacity: 0、transform: scale(0) - JS 里用
getBoundingClientRect()算偏移:const x = e.clientX - rect.left,再约束范围:Math.max(0, Math.min(x, rect.width - size)) - 把坐标写进 CSS 变量:
button.style.setProperty('--x', <code>${x}px),CSS 中用left: var(--x)读取 - 过渡必须包含
transform和opacity:transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.4s
Safari 和移动端的硬坑
Safari(尤其 iOS 15 以下)对 transform: scale() + top/left 组合有渲染 bug,波纹可能卡顿或不触发。绕过方式很具体:
立即学习“前端免费学习笔记(深入)”;
- 伪元素必须加
will-change: transform(别写在常态上,点击时动态加) - 圆心定位统一用
transform: translate(<code>${x}px,${y}px),别混用top/left - 移动端必须监听
touchstart,用e.touches[0].clientX取坐标,click在 iOS 上有 300ms 延迟且坐标不准 - 快速连点时,每次点击前要清掉上一个波纹状态,否则多个
::after会重叠错位
真正难的从来不是写动画,而是坐标计算边界处理、Safari 的合成层控制、以及连点时的动画状态清理——漏掉任意一个,真机上就大概率失效。


















