波纹动画需在按钮内动态插入元素并用js计算点击相对坐标:通过getboundingclientrect()获取按钮位置,再用clientx/clienty减去left/top得到偏移;css需设置transform-origin为点击坐标、scale与opacity过渡、最大缩放值为math.max(width,height)*2,并在定时器中清理dom。

波纹动画需要什么 DOM 结构
直接在 <button></button> 上加 ::after 伪元素做波纹是行不通的——伪元素无法响应 pointer-events,且无法动态计算点击坐标。必须手动插入一个 <span></span> 子元素作为波纹容器,并用 JS 记录点击位置。
推荐结构:
<button class="ripple-btn">提交</button>,不嵌套多余标签,JS 动态追加波纹元素,避免影响语义和 SSR 渲染。
如何用 JS 获取点击坐标并定位波纹
关键不是用 clientX/clientY,而是要转成相对于按钮左上角的偏移值,否则波纹会出现在页面任意位置。得用 getBoundingClientRect() 算出按钮位置,再减去。
const rect = btn.getBoundingClientRect();const x = e.clientX - rect.left;const y = e.clientY - rect.top;- 注意:如果按钮有
border或padding,波纹起点仍以内容区左上为原点,无需额外修正
CSS 波纹的关键动画参数
波纹本质是圆形 <span></span> 从 0 缩放至足够覆盖按钮的尺寸,同时透明度从 1 降到 0。不能只靠 transform: scale(),必须配合 opacity 过渡,否则边缘会生硬。
示例关键样式:
.ripple-btn .ripple {<br> position: absolute;<br> border-radius: 50%;<br> background: rgba(0,0,0,0.2);<br> transform: scale(0);<br> pointer-events: none;<br> opacity: 1;<br> transition: transform 0.6s ease-out, opacity 0.6s ease-out;<br>}
-
transform-origin必须设为x y(比如left top不行),用内联 style 动态设置:ripple.style.transformOrigin = `${x}px ${y}px` - 最大缩放值建议用
Math.max(btn.width, btn.height) * 2,确保全覆盖 - 动画时长控制在 0.4–0.7s,太短没感知,太长像卡顿
容易被忽略的清理和兼容问题
波纹元素不销毁会堆积 DOM 节点,尤其频繁点击时。不能依赖 CSS 动画结束事件(transitionend)——它在动画被中断或取消时可能不触发。
- 用
setTimeout在动画时长后主动remove(),比监听事件更可靠 - 移动端需加
touchstart事件,但注意 iOS Safari 的click延迟,优先用touchstart触发 - 禁用状态(
disabled)下要跳过创建波纹,检查btn.disabled或btn.hasAttribute('disabled') - 如果按钮用了
overflow: hidden,波纹会被裁剪,需确保父容器允许溢出或改用clip-path替代
真正麻烦的是多点触控和快速连击——一个按钮同一时刻不该存在多个波纹,每次新点击前先清除已有的 .ripple 元素。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











