原生radio无法用CSS伪类实现波纹,须用label包裹并JS动态创建绝对定位span波纹元素,基于offsetLeft/Top计算坐标,仅状态变更时触发,动画仅限transform和opacity,结束后用requestAnimationFrame及时清理。

radio点击时没有默认波纹,:focus-visible也不触发动画
浏览器原生<input type="radio">不支持:active伪类做波纹,更不会响应:focus-visible来启动CSS过渡。你写的transition: all 0.3s对它完全无效——因为它的视觉状态切换(选中/未选中)由checked属性驱动,而非:hover或:active这类瞬态伪类。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 必须用
label包裹input,把点击行为“转移”到可样式化的label上 - 把
input设为position: absolute; opacity: 0; pointer-events: none,让它不可见且不拦截事件 - 在
label内额外加一个<span class="ripple">作为波纹容器,用JS监听click生成绝对定位的<span>元素 - 不要试图用
@keyframes配合animation硬套——波纹起始点必须是鼠标坐标,CSS动画无法动态计算
用getBoundingClientRect()算不准波纹中心?
直接用event.clientX / clientY减去label.getBoundingClientRect().left/top看似合理,但一旦label有transform、border、padding或父级overflow: hidden,坐标就会偏移。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 波纹
span的left和top必须基于event.clientX - label.offsetLeft和event.clientY - label.offsetTop(注意:不是getBoundingClientRect) -
label必须设position: relative,否则offsetLeft/Top返回0 - 如果
label是flex或grid子项,先调用label.getClientRects()[0]取第一个矩形再算偏移,比getBoundingClientRect更稳 - 波纹
span创建后立即设style.cssText = "position: absolute; border-radius: 50%; pointer-events: none;",避免重排
波纹缩放+透明度过渡卡顿?
用transform: scale(0)→scale(4)配合opacity: 0→1看起来顺,但若同时动画width/height或没启用硬件加速,低端设备会掉帧。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 只动画
transform和opacity,其他一律禁用 - 给波纹
span加style.willChange = "transform, opacity"(仅首次创建时设一次) - 过渡时间控制在
250ms以内,超过300ms人眼明显感知延迟 - 动画结束必须手动
remove()波纹元素,否则DOM节点持续堆积(尤其高频点击场景) - 用
requestAnimationFrame包裹remove(),避免同步删除引发布局抖动
radio组里多个选项同时触发波纹?
点击已选中的radio不会触发change事件,但click事件照常发生——结果就是重复点击同一选项,波纹不断叠加,DOM里塞满无用span。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 监听
label的click,但只在input.checked === false时才生成波纹(即仅未选中状态才响应) - 或者更稳妥:用
input.addEventListener('change', ...),并在回调里生成波纹——这样确保只在真实状态变更时触发 - 如果用
change事件,注意它不冒泡,必须绑定到input本身,不能委托到label或父容器 - 别忘了给
input加name属性,否则浏览器不识别为同一组,change逻辑会错乱
波纹的核心不是“好看”,是准确锚定点击位置并及时清理;坐标算错一像素,波纹就飘在按钮外,而漏删一个span,十次点击后页面就多出十个无用节点。


















