回流( reflow ) 与 重绘( repaint )

了解页面渲染过程:

渲染过程如下:

  1. 解析 HTML 生成 DOM Tree;解析 CSS 生成 Style Rules
  2. 两者合并生成 Render Tree
  3. 根据几何信息(位置、大小等),生成页面布局 (Layout);
  4. 根据特征信息(颜色、透明度等),绘制页面 (Painting);
  5. 显示 (Display) ,将像素发送给GPU,展示在页面上。

 注意,渲染(render)时,会遍历 DOM 节点,会忽略非可见节点(如 script 标签meta 标签等 及 "display:none" 的元素)。

还有,"visibility:hidden" 与 "display:none" 不同:

  • "visibility:hidden" 将元素设置为不可见,但是同样在布局上占有一定空间,仍在 render tree 上;
  • "display:none" 的元素是将节点从 render tree 中移除,所以不是布局中的一部分

回流/重绘 的定义:

  • 回流(reflow): 发生在上图的 Layout 位置,当修改属性引发页面布局变化,则触发回流
  • 重绘(repaint): 发生在上图的 Painting 位置,当修改属性不引发页面布局变化,则只触发重绘

所以,可能只发生重绘;但 回流 必定包含 重绘 过程。


引发回流(reflow)的常见情况:

  • 页面首次渲染(初始化)
  • DOM 树变化(如:增/删/移动节点)
  • Render 树变化(如:padding改变)
  • 浏览器窗口大小发生变化
  • 获取元素的某些属性

注意,获取元素某些属性也会引发回流,因为浏览器为了获取正确值而提前触发回流。这些属性包括:

  • offsetLeft/Left/Width/Height、
  • scrollTop/Left/Width/Height、
  • clientTop/Left/Width/Height、
  • getComputedStyle() 或者 IE的currentStyle

优化(减少 回流/重绘 的次数):

1. 尽量一次性修改节点样式,避免逐个

let el = document.getElementById('test');
// el.style.padding = '5px';
// el.style.borderLeft = '1px';
// el.style.borderRight = '2px';
// 使用 cssText 避免多次修改
el.style.cssText += 'border-left:1px; border-right:2px; padding:5px'

2. 批量修改 DOM 时,使用 DocumentFragment 缓存起来,再一次性插入到指定节点

const data = ['one', 'two', 'three', 'four', ...]
function appendDataToElement(appendToElement, data) {
  let 
    li, 
    i, 
    // 存放 li
    fragment = document.createDocumentFragment();
  for (i = 0; i < data.length; i++) {
    li = document.createElement('li');
    li.textContent = data[i];
    fragment.appendChild(li);
  }
  // 一次性插入所有元素
  appendToElement.appendChild(fragment)
}
const ul = document.getElementById('list');
appendDataToElement(ul, data);

3. 将需要多次修改的元素设置 "display:none"操作完再显示(上面提及"display:none"不在render树内,不触发回流重绘)

// 但不建议使用本方法,插入元素时间过长会影响体验
function appendDataToElement(appendToElement, data) {
  let li, i;
  for (i = 0; i < data.length; i++) {
    li = document.createElement('li');
    li.textContent = 'text';
    appendToElement.appendChild(li);
  }
}
const ul = document.getElementById('list');
// 先隐藏该元素
ul.style.display = 'none';
appendDataToElement(ul, data);
// 插入完成后再显示
ul.style.display = 'block';

4. 避免多次读取某些属性

5. 将复杂节点元素脱离文档流 (如 "position:absolute" ),降低回流成本


多说几句:

  • CSS 放在头部,原因是渲染(render)需要 DOM(HTML) 和 CSSOM(CSS) ,放在头部有助于缩短首次渲染时间
  • JS 放在尾部,原因是JS会阻塞浏览器解析,当发现一个外链脚本需要下载完后在继续解析后续的 HTML
  • DOMContentLoaded 事件触发时,仅当DOM加载完成,不包括样式表,图片...
  • load 事件触发时,页面上所有的DOM,样式表,脚本,图片都已加载完成

其他博主的文章:

DOM 操作成本到底高在哪儿?         你真的了解回流和重绘吗? (有部分错误)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值