← 中文资源

简体中文版本

布局抖动

布局抖动指 JavaScript 读写交替访问几何信息与修改样式,迫使浏览器每帧同步重算布局几十次,造成帧预算超支与卡顿。

阅读英文原文

概念定义

布局抖动(强制同步布局)发生在读取布局属性(offsetHeight、getBoundingClientRect)与样式写入交错执行时。写入之后的每次读取都会在帧之前强制同步计算布局,浏览器无法批量处理,成本成倍放大直至丢帧。

工作机制

样式写入会把文档标记为脏;下一个布局查询会立即冲刷脏状态。修复思路是阶段分离:先把所有读批量完成并缓存值,再统一应用所有写;rAF 回调则让帧调度保持一致。纯动画改用 transform 与 opacity 可把工作交给合成器,完全绕开布局。

使用场景

拖拽交互、类电子表格界面、图表动画是高发区。Long Animation Frames 条目与性能追踪中的 forced-reflow 警告能精确定位肇事代码;滚动监听器里逐事件调用 getBoundingClientRect 会以超过帧率的频率强制布局。

常见误区

读取布局并不免费:存在待处理变更时,写后的第一次读要支付全部布局成本。用缓存几何换速度会引入陈旧值,正确做法是通过 ResizeObserver 在 resize 时重算,而不是反复内联读取。即使简单列表,只要在循环里逐元素读写交替,同样会抖动。

代码示例

public class StringBuilderManipulation {

    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder(64);

        sb.append("Hello");
        sb.append(" ");
        sb.append("World");
        System.out.println("Built: " + sb);

        sb.insert(5, " Java");
        System.out.println("After insert: " + sb);

        sb.delete(5, 10);
        System.out.println("After delete: " + sb);

        sb.replace(0, 5, "Hi");
        System.out.println("After replace: " + sb);

        sb.reverse();
        System.out.println("Reversed: " + sb);

        String csv = String.join(", ",
            "Apple", "Banana", "Cherry", "Date");
        System.out.println("CSV: " + csv);

        String formatted = String.format(
            "Name: %s, Age: %d, Score: %.2f",
            "Alice", 30, 95.5
        );
        System.out.println(formatted);

        String stripped = "  padded  ".strip();
        System.out.println("Stripped: '" + stripped + "'");
    }
}
@keyframes fadeIn {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}

.animate-fade {
  animation: fadeIn 0.3s ease-out forwards;
}

@keyframes pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.05); }
}