document.querySelector 与 classList.toggle 是实现页面动态效果最轻量可靠的基础组合,应通过 css 类而非 style.display 控制显隐,用 requestAnimationFrame 替代 setTimeout 实现平滑动画,scroll/resize 需节流,且需根据环境判断动效必要性与降级策略。

document.querySelector 和 classlist.toggle 是实现大多数页面动态效果最轻量、最可靠的基础组合。
点击切换元素显隐,别用 display: none 硬切
直接操作 style.display 会覆盖 CSS 中定义的其他显示逻辑(比如响应式断点里的 display: flex),也难维护。更稳妥的方式是通过 CSS 类控制:
button.addEventListener('click', () => { const panel = document.querySelector('#sidebar'); panel.classList.toggle('is-hidden'); });
对应 CSS 写成:
.is-hidden { display: none !important; }
- 用
!important是为了确保能压过内联样式或其他高优先级规则 - 避免在 js 里写
panel.style.display = 'none'—— 后续想加过渡动画或适配暗色模式时会卡住 - 如果需要保留占位空间,改用
visibility: hidden+ 单独类名,而非display
requestAnimationFrame 替代 setTimeout 做逐帧动画
滚动视差、数字滚动计数、进度条填充等需要平滑变化的效果,用 setTimeout 容易掉帧或卡顿。浏览器对 requestAnimationFrame 有优化调度:
立即学习“Java免费学习笔记(深入)”;
function animateCounter(el, target, current = 0) { if (current >= target) { el.textContent = target; return; } const step = Math.ceil((target - current) / 20); el.textContent = Math.min(current + step, target); requestAnimationFrame(() => animateCounter(el, target, number(el.textContent))); }
- 不要用
for循环 +setTimeout模拟动画 —— 事件队列堆积会导致延迟不可控 -
requestAnimationFrame自动匹配屏幕刷新率(通常是 60fps),且标签页非激活时会暂停,省资源 - 数值类动画务必用
Number()转类型,避免字符串拼接(如'10' + 1 === '101')
监听 scroll 或 resize 时必须节流
这些事件在用户操作中高频触发(每秒几十次),不加限制直接执行 dom 操作会严重拖慢页面:
let isThrottled = false; window.addEventListener('scroll', () => { if (isThrottled) return; isThrottled = true; doStickyHeaderLogic(); setTimeout(() => isThrottled = false, 16); // ≈ 60fps 一帧 });
- 用
setTimeout+ 标志位比 Lodash 的throttle更轻量,适合简单场景 - 别在
scroll回调里调用getBoundingClientRect()或offsetTop—— 触发强制同步布局(Layout Thrashing) - 现代方案可考虑
IntersectionObserver替代 scroll 监听做懒加载或吸顶,它本身已内置性能优化
真正难的不是写出动效,而是判断该不该动、何时停、怎么退化。比如悬停菜单在触摸设备上没 hover,就得 fallback 到点击展开;又比如动画在低性能设备上应自动降级为 class 切换而非逐帧计算。这些细节不在语法里,而在运行时环境的判断中。