重绘与回流
对于一些经典的案例进行分析
重绘与回流的案例实战
课程目标
理解浏览器重绘与回流的机制
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 的过程
- 获取 DOM 后分割为多个图层
- 对每个图层的节点计算样式结果(Recalculate style--样式重计算)
- 为每个节点生成图形和位置(Layout--回流和重布局)
- 将每个节点绘制填充到图层位图中(Paint Setup 和 Paint--重绘)
- 图层作为纹理上传至 GPU
- 符合多个图层到页面上生成最终屏幕图像(Composite Layers--图层重组)
Chrome 创建图层的条件
- 3D或透视变换(perspective transform)CSS属性
- 使用加速视频解码的
<video>节点 - 拥有3D(WebGL)上下文或加速的2D上下文的
<canvas>节点 - 混合插件(如Flash)
- 对自己的 opacity 做 CSS 动画或使用一个动画 webkit 变换的元素
- 拥有加速 CSS 过滤器的元素
- 元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的层里)
- 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
实战优化点
- 用 translate 替代 top 改变
- 用 opacity 替代 visibility
- 不要一条一条地修改 DOM 的样式,预先定义好 class,然后修改 DOM 的 className
- 把 DOM 离线后修改,比如:先把 DOM 给 display:none (有一次 Reflow),然后你修改100次,然后再把它显示出来
- 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量
- 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局,使用div布局
- 动画实现的速度的选择
- 对于动画新建图层
- 启用 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>每个文件都是独立、可直接运行的,演示了对应优化点的核心思路,没有任何多余内容。复制保存即可使用。