原生无水波纹需手动实现:用getboundingclientrect计算点击坐标设css变量,::after结合transform实现居中缩放动画;必加position: relative、overflow: hidden、outline: none;disabled状态需前置校验并跳过创建。

原生 <button></button> 没有水波纹特效,这是浏览器默认行为,不是你漏写了什么——想实现 Material 风格的点击波纹,必须自己加 CSS + JS。
为什么纯 CSS 的 ::after 波纹总从中心炸开
因为 CSS 无法读取鼠标点击坐标,::after 是静态伪元素,left: 50% 和 top: 50% 是写死的。哪怕你监听了 click,也无法在伪元素上动态改 left/top 值——除非用 CSS 自定义属性中转。
- 正确做法:在
mousedown或touchstart里算出相对坐标,设为style.setProperty('--x', x + 'px')和--y,CSS 里用left: var(--x); top: var(--y) - 别用
e.offsetX/e.offsetY:按钮带padding时偏差明显 - 必须用
getBoundingClientRect()算基准,再减e.clientX - rect.left,它不受滚动、缩放、transform 影响 - 移动端要 fallback 到
e.touches?.[0],且调e.preventDefault()防止误触发页面滚动
button 必须加的三个基础样式才能让波纹“锁住”
漏掉任意一条,波纹就会飞出按钮、偏移、或根本看不见。
-
position: relative:让::after有定位参考系 -
overflow: hidden:剪裁超出边框的波纹圆,否则会溢出 -
outline: none(配合::-moz-focus-inner { border: 0 }):避免聚焦虚线框干扰视觉
另外,清默认样式不能只靠 padding: 0; border: none,还得加 font: inherit; line-height: normal; appearance: none,否则在 Safari 或 Firefox 下可能错位或保留原生渲染逻辑。
波纹动画用 transition 还是 @keyframes
transition 更可控,尤其要响应不同尺寸按钮时;@keyframes 写死时长和缩放倍数,一换按钮大小就得调参数。
- 推荐用
transition: transform 350ms ease-out, opacity 350ms ease-out,起始设transform: translate(-50%, -50%) scale(0),激活时改scale(3)+opacity: 0 - 结尾必须加
animation-fill-mode: forwards或transitionend监听清理,否则波纹 DOM 或 inline style 不清除,连点后位置错乱、残留残影 - 别用
background-size动画:触发重绘,卡顿,且圆心无法精确定位 - 直径建议取
Math.max(btn.clientWidth, btn.clientHeight) * 2,确保覆盖全按钮,不依赖固定倍数
disabled 状态下波纹还在动?那是没同步判断
很多人加了波纹逻辑,却忘了 button[disabled] 仍能触发 mousedown(尤其键盘空格键),导致禁用态出现波纹。
- 初始化时就要检查
btn.hasAttribute('disabled') || btn.matches(':disabled') - 事件回调第一行就写
if (btn.disabled) return,而不是等动画开始后再隐藏 - 别只靠
opacity: 0隐藏波纹层——得彻底跳过创建伪元素或插入 DOM 的逻辑 - 如果按钮在
btn-group里,确认父容器也设了position: relative,否则::after会以 body 为参考定位
最易被忽略的是:动画结束清理不能写死 setTimeout(..., 350),得监听 transitionend,否则你改了 CSS 的 transition-duration,JS 就失步了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











