原生 button 无法直接实现涟漪效果,因其不支持点击位置计算与动态圆形扩散,需 js 获取坐标并插入带动画的绝对定位 span 元素;关键需设按钮 position: relative、涟漪 transform: scale() + opacity 动画、加 translatez(0) 避免抖动;移动端须用 touchstart 替代 click 并兼容多端。

为什么原生 button 无法直接实现涟漪效果
因为 HTML 按钮本身不支持「点击位置计算 + 动态圆形扩散」这种视觉反馈逻辑,CSS 的 :active 只能做全局状态切换,没法知道你点在左上角还是正中心。必须靠 JavaScript 拿到 clientX/clientY,再结合 getBoundingClientRect() 算出相对坐标,最后动态插入一个 span 圆形元素并加 CSS 动画。
怎么用纯 CSS + JS 实现可复用的涟漪函数
核心是监听 click 事件,在目标元素内部创建带定位的 span.ripple,并用 transform: scale() 配合 opacity 动画模拟水波扩散。关键细节:
- 必须给按钮设
position: relative,否则涟漪元素会脱离定位上下文 - 涟漪
span要设position: absolute、border-radius: 50%、pointer-events: none - 直径要足够大(比如
max(200px, 2 * Math.hypot(w/2, h/2))),确保角落点击也能盖住全区域 - 动画时长建议
600ms,太短没感觉,太长像卡顿 - 用
requestAnimationFrame批量插入比直接appendChild更顺滑(尤其连点时)
示例片段:
function createRipple(e) {
const btn = e.currentTarget;
const rect = btn.getBoundingClientRect();
const size = Math.max(rect.width, rect.height) * 1.5;
const ripple = document.createElement('span');
ripple.className = 'ripple';
ripple.style.left = `${e.clientX - rect.left - size/2}px`;
ripple.style.top = `${e.clientY - rect.top - size/2}px`;
ripple.style.width = ripple.style.height = `${size}px`;
btn.appendChild(ripple);
// 动画结束后移除,避免 DOM 泄漏
setTimeout(() => ripple.remove(), 600);
}
document.querySelectorAll('button.ripple-effect').forEach(b => b.addEventListener('click', createRipple));
为什么涟漪消失后按钮文字偶尔会“抖一下”
这是 DOM 插入/删除导致的重排(reflow)。常见原因有两个:
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
-
span.ripple没设transform: translateZ(0)或will-change: transform,触发了非合成层渲染 - 按钮用了
font-weight: bold或letter-spacing,而涟漪元素插入时浏览器重绘字体度量,造成微小位移 - 更隐蔽的是:父容器有
overflow: hidden但涟漪初始opacity: 0,某些浏览器在 opacity 变化瞬间会重新裁剪,引发抖动
解决办法:给按钮加 transform: translateZ(0) 强制 GPU 加速;涟漪元素用 transform: scale(0) 初始态,动画改为 transform: scale(1) + opacity: 0 → 0.3,避开重排敏感属性。
移动端点击延迟和多点触控怎么处理
默认 click 在 iOS/Android 上有约 300ms 延迟,且不响应 touchstart 多指操作。必须改用 touchstart + preventDefault(),但要注意:
- 只对
button和带role="button"的元素禁用默认行为,否则影响表单提交或链接跳转 - 用
e.touches[0]取第一个触点,忽略多指——涟漪只响应主点击,多指不叠加 - PC 端仍需保留
click监听,避免鼠标用户失效;可用'ontouchstart' in window做简单判断 - 别忘了在
touchend后加setTimeout(() => ripple.remove(), 600),因为 touch 事件没有冒泡阶段的自动清理时机
实际项目中,建议封装成一个 ripple(el, options) 函数,把 click/touchstart 统一桥接,再透传事件对象给内部坐标计算逻辑。
真正难的不是画个圆圈,而是让每个按钮在各种尺寸、缩放、高 DPI 屏幕、不同输入设备下都算得准位置、消得干净、不抢焦点、不闪动。这些细节堆起来,才叫“完整版”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










