Files
2026-07-13 21:36:17 +08:00

108 lines
3.2 KiB
Markdown

---
title: Avoid Layout Thrashing
impact: MEDIUM
impactDescription: 防止强制同步布局,减少性能瓶颈
tags: javascript, dom, css, performance, reflow, layout-thrashing
---
## 避免布局抖动
避免将样式写入与布局读取交错进行。当你在样式更改之间读取布局属性(如 `offsetWidth``getBoundingClientRect()``getComputedStyle()`)时,浏览器会被迫触发同步重排。
**这样是可以的(浏览器会批量处理样式更改):**
```typescript
function updateElementStyles(element: HTMLElement) {
// 每一行都会使样式失效,但浏览器会批量重新计算
element.style.width = '100px'
element.style.height = '200px'
element.style.backgroundColor = 'blue'
element.style.border = '1px solid black'
}
```
**错误做法(读写交错会强制重排):**
```typescript
function layoutThrashing(element: HTMLElement) {
element.style.width = '100px'
const width = element.offsetWidth // 强制重排
element.style.height = '200px'
const height = element.offsetHeight // 再次强制重排
}
```
**正确做法(批量写入,然后一次性读取):**
```typescript
function updateElementStyles(element: HTMLElement) {
// 将所有写入操作批量处理
element.style.width = '100px'
element.style.height = '200px'
element.style.backgroundColor = 'blue'
element.style.border = '1px solid black'
// 所有写入完成后读取(仅一次重排)
const { width, height } = element.getBoundingClientRect()
}
```
**正确做法(批量读取,然后再写入):**
```typescript
function avoidThrashing(element: HTMLElement) {
// 读取阶段——先执行所有布局查询
const rect1 = element.getBoundingClientRect()
const offsetWidth = element.offsetWidth
const offsetHeight = element.offsetHeight
// 写入阶段——之后再执行所有样式更改
element.style.width = '100px'
element.style.height = '200px'
}
```
**更好的做法:使用 CSS 类**
```css
.highlighted-box {
width: 100px;
height: 200px;
background-color: blue;
border: 1px solid black;
}
```
```typescript
function updateElementStyles(element: HTMLElement) {
element.classList.add('highlighted-box')
const { width, height } = element.getBoundingClientRect()
}
```
**React 示例:**
```tsx
// 错误做法:将样式更改与布局查询交错进行
function Box({ isHighlighted }: { isHighlighted: boolean }) {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
if (ref.current && isHighlighted) {
ref.current.style.width = '100px'
const width = ref.current.offsetWidth // 强制布局
ref.current.style.height = '200px'
}
}, [isHighlighted])
return <div ref={ref}>Content</div>
}
// 正确做法:切换类名
function Box({ isHighlighted }: { isHighlighted: boolean }) {
return (
<div className={isHighlighted ? 'highlighted-box' : ''}>
Content
</div>
)
}
```
尽可能优先使用 CSS 类而非内联样式。CSS 文件会被浏览器缓存,类能提供更好的关注点分离,且更易于维护。
更多关于强制布局操作的信息,请参见[此 Gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) 和 [CSS Triggers](https://csstriggers.com/)。