直接用transition无法实现真实波纹效果,因其仅支持属性级渐变,无法动态生成基于点击坐标的径向渐变或伪元素定位动画;必须结合JS计算坐标并注入CSS变量,再由::after读取变量驱动radial-gradient与scale过渡。

为什么直接用 transition 无法实现真实波纹效果
波纹(ripple)不是简单的背景色渐变,而是从点击坐标中心向外扩散的圆形遮罩。Tailwind 默认的 transition 类(如 transition-colors)只作用于属性级变化,无法动态生成基于鼠标位置的 radial-gradient 或伪元素动画。硬套 hover:scale-105 或 focus:ring 看起来像“反馈”,但不符合 Material Design 中“响应式触点”的交互逻辑——用户点哪,涟漪就从哪开始。
用 ::after 伪元素 + CSS 变量控制波纹定位与尺寸
核心思路:监听 click 事件,计算点击坐标,写入自定义 CSS 变量(如 --x、--y、--s),再让按钮的 ::after 伪元素读取这些变量做径向渐变和缩放过渡。
实操要点:
- 按钮需设
position: relative(Tailwind 中用relative类),否则::after无法精确定位 - 伪元素初始状态要透明、不可见:
content: ""、opacity: 0、transform: scale(0) - 使用
radial-gradient(circle, rgba(255,255,255,0.5) 0%, transparent 70%)模拟波纹,注意渐变中心用at var(--x) var(--y)绑定变量 - 过渡必须包含
transform和opacity:transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.4s
示例关键 CSS(可放入 @layer utilities):
.ripple {
position: relative;
overflow: hidden;
}
.ripple::after {
content: "";
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: radial-gradient(circle, rgba(255,255,255,0.5) 0%, transparent 70%);
background-position: var(--x) var(--y);
background-size: var(--s) var(--s);
opacity: 0;
transform: scale(0);
transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.4s;
pointer-events: none;
}
.ripple.active::after {
transform: scale(1);
opacity: 1;
}
JavaScript 动态注入点击坐标到 CSS 变量
不能靠纯 CSS 实现坐标捕获,必须 JS 计算。重点不是“加事件”,而是如何把 clientX/clientY 映射为相对于按钮左上角的百分比值(这样 background-position 才能对齐)。
常见错误现象:--x 直接设像素值,导致波纹总在页面左上角炸开;或未减去 getBoundingClientRect() 的 left/top,造成偏移错位。
正确做法:
- 监听
click,而非mousedown(避免长按误触发) - 用
event.clientX - rect.left得到相对 X,再除以rect.width转为百分比(如50%) - 同理算 Y,再拼成
calc(var(--x) * 1px) calc(var(--y) * 1px)不可靠,应直接存百分比字符串 - 设置变量后立即加
.active类,下一帧再移除(避免被浏览器合并渲染)
简短示例(绑定到所有带 ripple 类的按钮):
document.querySelectorAll('.ripple').forEach(btn => {
btn.addEventListener('click', e => {
const rect = btn.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width * 100) + '%';
const y = ((e.clientY - rect.top) / rect.height * 100) + '%';
const size = Math.max(rect.width, rect.height) * 2;
<pre class="brush:php;toolbar:false;">btn.style.setProperty('--x', x);
btn.style.setProperty('--y', y);
btn.style.setProperty('--s', `${size}px`);
btn.classList.add('active');
setTimeout(() => btn.classList.remove('active'), 400);}); });
兼容性与性能注意事项
Tailwind 默认不处理 CSS 变量的前缀,var(--x) 在旧版 Safari(radial-gradient 在 IE 完全不支持。如果项目需兼容,得降级为 JS 创建 <span></span> 波纹节点,但会破坏 Tailwind 的原子类心智模型。
性能上容易被忽略的点:
- 每个波纹都要重排(
getBoundingClientRect()触发 layout),高频点击时有卡顿风险;可加节流(!btn.classList.contains('active')判断) - 伪元素的
background-size若设过大(如屏幕宽高),会触发 GPU 内存暴涨,建议限制--s最大值(如min(--s, 400px)) - 暗色模式下白波纹不显眼,应改用
rgba(255,255,255,0.2)或提取主题色变量
真正难的是让波纹“看起来自然”:扩散速度、衰减透明度、边缘模糊感,这些细节 Tailwind 不提供抽象,得自己调 cubic-bezier 和渐变 stop 位置。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











