纯css的:active无法实现真实波纹效果,因其不记录点击坐标、动画周期仅几毫秒且松手即停;必须用js计算坐标,配合::after伪元素动态定位、缩放与淡出,并结合tailwind过渡类协同控制。

纯 CSS 的 :active 无法做出真实波纹效果——它不记录点击坐标、动画太短、松手即停。必须用 JavaScript 计算坐标 + ::after 伪元素 + Tailwind 的过渡类协同控制。
为什么不能只靠 active: 变体
Tailwind 默认禁用 active: 变体,即使手动开启,:active 状态也只维持几毫秒,远不够波纹所需的 300–600ms 动画周期。更关键的是::active 没有坐标信息,所有缩放/位移都从中心开始,不是“点哪从哪起”。
-
active:scale-95或active:shadow-sm只能做瞬时反馈,不是波纹 - 波纹需要两个动态值:点击位置
x/y和扩散半径(通常取按钮宽高较大者的 2 倍) - 若强行用
active:模拟,会看到动画突然中断、或在松手后“跳回”,视觉断裂
用 ::after + JS 实现可定位波纹的核心步骤
核心是让按钮成为相对定位容器,再由 JS 注入一个绝对定位的圆形遮罩,并用 CSS 变量传入坐标。
- 按钮必须加
relative和overflow-hidden,否则::after会溢出或错位 -
::after要设content: ""、absolute、border-radius: 50%、pointer-events: none、opacity: 0、transform: scale(0) - JS 中用
e.clientX - rect.left算出相对于按钮左上角的x,再约束在[0, rect.width]范围内(避免小数或越界) - 把坐标写进 CSS 变量:
button.style.setProperty('--x', `${x}px`),CSS 中用left: calc(var(--x) - 50%)定位圆心(配合transform: translate(-50%, -50%)) - 过渡必须同时包含
transform和opacity:transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.4s
Safari 和移动端必须绕开的三个坑
iOS Safari(尤其 15 以下)对 top/left + transform: scale() 组合有合成层 bug,波纹常卡顿或不触发;移动端 touch 事件坐标也不等于 clientX/Y。
- 伪元素在触发波纹时才加
will-change: transform,别写在常态 CSS 里(否则强制 GPU 合成反而拖慢) - 圆心定位统一用
transform: translate(${x}px, ${y}px),彻底弃用top/left - 移动端监听
touchstart,取e.touches[0].clientX和e.touches[0].clientY,别依赖click事件 - 快速连点时,每次创建新波纹前先
ripple.remove()上一个,否则多个::after叠加导致错位或内存泄漏
阴影与波纹节奏不同步怎么办
如果按钮本身用了 shadow-md hover:shadow-lg active:shadow-sm,而波纹动画持续 400ms,但 active:shadow-sm 只存在约 100ms,就会看到阴影先缩再扩,和波纹脱节。
- 删掉所有
active:shadow-*类,改用统一的transition-shadow控制 - 把阴影变化逻辑收进 focus 状态:
focus:shadow-lg focus:ring-2 focus:ring-blue-300 focus:ring-opacity-50 - 确保
transition-shadow时长和波纹的transition一致(如都是0.4s),缓动函数也保持相同
真正难的不是写出那个 scale(0) → scale(1) 动画,而是坐标边界处理、Safari 的 will-change 时机、连点时的 DOM 清理——漏掉任意一个,真机上就大概率失效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











