原生 html 无波纹效果是设计使然,material 波纹需从点击坐标出发,依赖 getboundingclientrect() 计算相对位置,结合 mousedown/touchstart 动态创建 dom 并清理,同步 disabled 状态与动画结束事件。

原生 HTML <button></button> 没有波纹效果,这不是你写错了,而是它本来就不该有——Material Design 的 ripple 是交互逻辑,不是样式规则。
为什么 transition + ::after 无法真正还原 Material 波纹
纯 CSS 方案(比如用 ::after 径向渐变 + scale())只能固定从中心扩散,但 Material 规范要求波纹必须从**点击坐标点**出发。鼠标点在按钮右下角,波纹就得从那里炸开;移动端 touch 点在哪,中心就得算哪。
- 用
left: 50%; top: 50%写死居中,点击偏移时视觉失真 -
mousedown事件在 iOS Safari 有约 300ms 延迟,直接绑定会卡顿或失效 - 伪元素无法动态设置
left/top,必须靠 JS 插入真实<span></span>并计算偏移 - 快速连点时,多个波纹 DOM 不清理,内存泄漏+动画打架
getBoundingClientRect() 是计算点击坐标的唯一可靠方式
不能用 offsetLeft/offsetTop,它们受滚动、transform、父级定位影响;也不能只依赖 clientX/clientY 减去 scrollLeft/scrollTop——页面缩放或 fixed 元素会让结果错位。
- 正确做法:在
mousedown或touchstart回调里调用btn.getBoundingClientRect() - 再用
e.clientX - rect.left和e.clientY - rect.top算出相对坐标 - 移动端要 fallback 到
e.touches?.[0],且需preventDefault()防止误触发 scroll - 直径建议取
Math.max(btn.clientWidth, btn.clientHeight) * 2,确保覆盖全按钮
如何与 Bootstrap 5 的 disabled 状态真正同步
很多人加了波纹 JS,却忘了 disabled 按钮仍可能响应 mousedown(尤其键盘空格触发),导致波纹在禁用态“偷偷出现”。
- 初始化时必须检查
btn.hasAttribute('disabled')或btn.matches(':disabled') - 事件监听器里第一行就加
if (btn.disabled || btn.classList.contains('disabled')) return - 不要只靠 CSS 隐藏波纹层(
opacity: 0),得彻底跳过插入和动画逻辑 - 若按钮是
btn-group里的子项,确认其父容器有position: relative,否则波纹absolute定位会脱标
最易被忽略的点:波纹动画结束必须监听 transitionend 清理 DOM,而不是写死 setTimeout(..., 600)——用户改了 CSS 的 transition-duration,你的延时就废了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











