概念定义
布局抖动(强制同步布局)发生在读取布局属性(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); }
}