Skip to content

重绘与回流

  • 对于一些经典的案例进行分析

  • 重绘与回流的案例实战

课程目标

  • 理解浏览器重绘与回流的机制

  • css 性能让 javascript 变慢 ?

回流

  • 当 render tree 中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)
  • 当页面布局和几何属性改变时就需要回流

重绘

  • 当 render tree 中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如 background-color。则就叫称为重绘

  • 回流必将引起重绘,而重绘不一定会引起回流


触发页面重布局的属性

  • 盒子模型相关属性会触发重布局

  • 定位属性及浮动也会触发重布局

  • 改变节点内部文字结构也会触发重布局

  • width

  • height

  • padding

  • margin

  • display

  • border-width

  • border

  • min-height

  • top

  • bottom

  • left

  • right

  • position

  • float

  • clear

  • text-align

  • overflow-y

  • font-weight

  • overflow

  • font-family

  • line-height

  • vertival-align

  • white-space

  • font-size


只触发重绘的属性

  • color
  • border-style
  • border-radius
  • visibility
  • text-decoration
  • background
  • background-image
  • background-position
  • background-repeat
  • background-size
  • outline-color
  • outline
  • outline-style
  • outline-width
  • box-shadow

新建 DOM 的过程

    1. 获取 DOM 后分割为多个图层
    1. 对每个图层的节点计算样式结果(Recalculate style--样式重计算)
    1. 为每个节点生成图形和位置(Layout--回流和重布局)
    1. 将每个节点绘制填充到图层位图中(Paint Setup 和 Paint--重绘)
    1. 图层作为纹理上传至 GPU
    1. 符合多个图层到页面上生成最终屏幕图像(Composite Layers--图层重组)

Chrome 创建图层的条件

  • 3D或透视变换(perspective transform)CSS属性
  • 使用加速视频解码的 <video> 节点
  • 拥有3D(WebGL)上下文或加速的2D上下文的 <canvas> 节点
  • 混合插件(如Flash)
  • 对自己的 opacity 做 CSS 动画或使用一个动画 webkit 变换的元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的层里)
  • 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

实战优化点

    1. 用 translate 替代 top 改变
    1. 用 opacity 替代 visibility
    1. 不要一条一条地修改 DOM 的样式,预先定义好 class,然后修改 DOM 的 className
    1. 把 DOM 离线后修改,比如:先把 DOM 给 display:none (有一次 Reflow),然后你修改100次,然后再把它显示出来
    1. 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量
    1. 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局,使用div布局
    1. 动画实现的速度的选择
    1. 对于动画新建图层
    1. 启用 GPU 硬件加速

案例分析

课程实战

  • 减少重绘回流

案例 1:用 translate 替代 top 改变

html
<!-- 1-translate.html -->
<button onclick="move()">移动</button>
<div id="box" style="width:100px;height:100px;background:red;transition:0.3s;"></div>
<script>
function move() {
  document.getElementById('box').style.transform = 'translateX(200px)';
}
</script>

案例 2:用 opacity 替代 visibility

html
<!-- 2-opacity.html -->
<button onclick="toggle()">切换</button>
<div id="box" style="width:100px;height:100px;background:blue;transition:opacity 0.3s;"></div>
<script>
let show = true;
function toggle() {
  show = !show;
  document.getElementById('box').style.opacity = show ? '1' : '0';
}
</script>

案例 3:通过 className 批量修改样式

html
<!-- 3-classname.html -->
<style>.active { background: green; color: white; }</style>
<div id="box" style="padding:20px;">点击切换</div>
<button onclick="document.getElementById('box').className='active'">切换类</button>

案例 4:离线修改(display:none 后再操作)

html
<!-- 4-offline.html -->
<div id="box" style="width:100px;height:100px;background:orange;"></div>
<button onclick="batch()">批量修改</button>
<script>
function batch() {
  let box = document.getElementById('box');
  box.style.display = 'none';    // 只回流一次
  box.style.width = '200px';
  box.style.height = '200px';
  box.style.background = 'purple';
  box.style.display = 'block';   // 再回流一次
}
</script>

案例 5:循环中缓存属性值

html
<!-- 5-cache.html -->
<div id="container"></div>
<script>
// 错误做法(不要这样):for (let i=0; i<100; i++) { container.style.width = container.offsetWidth + 1 + 'px'; }
// 正确做法:缓存
let container = document.getElementById('container');
let width = container.offsetWidth;
for (let i = 0; i < 100; i++) {
  width += 1;
}
container.style.width = width + 'px';
</script>

案例 6:使用 div 布局代替 table

html
<!-- 6-div-layout.html -->
<style>
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.item { background: #ccc; padding: 10px; text-align: center; }
</style>
<div class="grid">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
  <div class="item">4</div>
</div>

案例 7:动画速度的选择(使用 requestAnimationFrame)

html
<!-- 7-animation-speed.html -->
<div id="box" style="width:50px;height:50px;background:teal;position:absolute;"></div>
<script>
let pos = 0;
function animate() {
  pos += 2;
  document.getElementById('box').style.left = pos + 'px';
  if (pos < 300) requestAnimationFrame(animate);
}
animate();
</script>

案例 8:为动画新建图层(will-change)

html
<!-- 8-new-layer.html -->
<style>
.animated {
  will-change: transform;   /* 提示浏览器创建独立图层 */
  animation: move 2s infinite;
}
@keyframes move {
  0% { transform: translateX(0); }
  100% { transform: translateX(300px); }
}
</style>
<div class="animated" style="width:100px;height:100px;background:hotpink;"></div>

案例 9:启用 GPU 硬件加速(translateZ(0))

html
<!-- 9-gpu-accelerate.html -->
<style>
.gpu {
  transform: translateZ(0);  /* 触发 GPU 加速 */
  animation: spin 2s linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}
</style>
<div class="gpu" style="width:100px;height:100px;background:gold;"></div>

每个文件都是独立、可直接运行的,演示了对应优化点的核心思路,没有任何多余内容。复制保存即可使用。