涟漪效果的核心原理是用JS获取点击坐标并动态设置CSS变量,使::after伪元素圆心精准落在点击处,再通过transform: scale()动画实现扩散。纯CSS无法读取坐标,故必须结合JS计算偏移量、监听click事件、设置--x/--y变量,并配合relative定位、border-radius:50%及cubic-bezier过渡。
本质是用 ::after 伪元素在点击位置生成一个圆形,初始 scale(0),再通过 transform: scale() 动画撑开,配合 opacity 淡出。关键不是“画圆”,而是让圆心精准落在鼠标点击坐标 —— 这必须靠 JS 获取 clientX/clientY 并设置为伪元素的 left/top 偏移。
因为纯 CSS 无法读取点击坐标。常见误区是写死 left: 50%; top: 50%,结果涟漪永远从盒子中心炸开,和点击位置无关。真实交互中用户可能点在右下角,涟漪也得从那里开始扩散。
必须用 JS 计算偏移量,并动态写入内联样式或自定义属性:
click 事件,获取 e.clientX - rect.left 和 e.clientY - rect.top
style.setProperty('--x', x + 'px') 和 --y
left: var(--x); top: var(--y) 定位伪元素圆心::after 伪元素画出圆形涟漪伪元素本身不接受 JS 操作,所以定位和动画必须靠 CSS 变量 + transform 配合。注意几个硬性约束:
立即学习“前端免费学习笔记(深入)”;
position: relative 必须加在目标盒子上,否则 ::after 的 absolute 会相对 body 定位::after 要设 content: ''、border-radius: 50%、background: rgba(0,0,0,0.1)
transform: scale(0) → scale(3),别用 width/height,否则触发重排,卡顿transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94),比线性更自然示例关键 CSS:
.ripple { position: relative; overflow: hidden;}.ripple::after { content: ''; position: absolute; width: 10px; height: 10px; background: rgba(0,0,0,0.2); border-radius: 50%; transform: translate(-50%, -50%) scale(0); transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); left: var(--x, 50%); top: var(--y, 50%);}
连续点击会叠加多个 ::after 动画实例?不会 —— 伪元素只有一个,但多次设置 --x/--y 会让它跳到新位置并重新播放 scale 动画。问题在于:如果用户快速连点,前一个动画还没结束,新动画会打断它,导致缩放不完整。
解决办法是加节流或等动画结束再允许下一次触发:
getComputedStyle(el).getPropertyValue('transform') 判断是否还在动(不推荐,性能差)setTimeout 记录状态,比如设置 isRippling = true,0.45s 后置为 false
el.style.removeProperty('--x'); el.style.removeProperty('--y'),再设新值,强制重绘真正容易被忽略的是:如果盒子有 border 或 padding,rect.left/top 计算要减去它们,否则偏移错位。用 getBoundingClientRect() 是最稳的。