CSS类切换比JS操作style更快,因浏览器对其渲染优化成熟,可避免重排和部分重绘;而频繁修改style属性易触发同步布局计算导致卡顿。

为什么直接用 CSS 类切换比 JS 操作 style 属性更快
浏览器对 class 切换的渲染优化更成熟,尤其是配合 will-change、transform 或 opacity 等触发合成层的属性时,能避免重排(reflow)和部分重绘(repaint)。而频繁写 element.style.xxx = '...' 容易触发同步布局计算,尤其在循环或滚动中会明显卡顿。
- 优先用
element.classList.add('active')替代element.style.display = 'block' - 需要动态颜色或尺寸时,改用 CSS 自定义属性(
--bg-color),再由 JS 修改element.style.setProperty('--bg-color', '#ff0'),保持样式逻辑仍在 CSS 引擎内 - 避免在
requestAnimationFrame外反复读取offsetHeight、getComputedStyle—— 这会强制同步回流
Vue/React 中 class 绑定的性能陷阱
框架的响应式 class 绑定(如 Vue 的 :class、React 的 className={cls})本身开销小,但若绑定值是实时计算函数或内联对象,每次渲染都新建对象或执行函数,会干扰虚拟 DOM diff 和缓存判断。
- Vue 中避免写
:class="{ active: isAct() }",改用计算属性computed: { cls() { return { active: this.active } } } - React 中不要写
className={{ active: isActive } ? 'active' : ''},改用模板字符串或clsx库:className={clsx('btn', { active: isActive })} - 大量列表项使用条件 class 时,确保 key 稳定——重复 key 或用 index 当 key 会导致无谓的节点复用失败,间接拖慢样式更新
如何让 CSS 动画不被 JS 阻塞
CSS 动画(@keyframes + animation)默认运行在合成线程,不依赖主线程 JS 执行。但一旦用 JS 主动控制(如 el.animate()、el.style.animationName 或监听 animationend 后立刻操作 DOM),就可能因 JS 长任务打断动画帧。
- 启动动画只做最小动作:添加 class 触发动画,不要在
animationstart里调用fetch或复杂计算 - 避免在
animationend中修改影响布局的属性(如height、margin),改用transform或opacity配合will-change: transform - 用
prefers-reduced-motion媒体查询降级动画:@media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; } }
JS 框架里哪些 CSS 操作会意外触发 layout thrashing
所谓 layout thrashing,就是 JS 在一次任务中反复“读-写-读-写”布局相关属性,迫使浏览器多次同步计算样式和布局。常见于轮播图、虚拟滚动、下拉菜单展开等场景。
立即学习“前端免费学习笔记(深入)”;
- 典型错误模式:
el.offsetHeight; el.classList.add('open'); el.offsetHeight;—— 中间那句 class 切换虽快,但两次offsetHeight就已触发两次回流 - 修复方式:把所有读操作集中到前面,所有写操作集中到后面;或用
getBoundingClientRect()批量读,再统一写 - 框架内更隐蔽:比如在 Vue
mounted钩子中立即读取el.scrollHeight,然后调用this.$nextTick再设高度——这中间已有一次渲染,但若组件有异步数据,实际 DOM 可能还没完全就绪
真正卡顿往往不出现在动画帧数上,而藏在那些“只读一次”的 offsetTop 调用里,或是 classList.toggle 后紧跟着的 clientWidth 查询。这些细节不报错,但叠加起来就吃掉 60fps 的余量。











