再看重排(或回流:reflow)和重绘(:repaint)

149 阅读4分钟

注:部分内容来自gogle,如有雷同纯属借鉴

首先我们复习一下浏览器是如何呈现页面的

vbtr2gfaitr0am5nl4io.png

  1. 当我们输入一个地址的时候,浏览器会从服务器上下载网页源码
  2. 浏览器解析HTML代码并转换成Tokens<, TagName, Attribute, AttributeValue, >
  3. Token 将转换为节点并构建 DOM 树
  4. CSSOM 树将根据 CSS 规则生成
  5. DOM 和 CSSOM 树将合并到 RenderTree
  6. RenderTree 的构造如下:
  • 从 dom 树的根开始计算哪些元素是可见的以及它们的计算样式
  • RenderTree 将忽略不可见的元素,如(meta、script、link)和 display:none
  • 它将可见节点与适当的 CSSOM 规则匹配并应用它们
  1. Reflow回流:计算每个可见节点的位置和大小

  2. repaint重绘:现在,浏览器将在屏幕上绘制渲染树

重绘

当元素的外观发生更改时会发生重绘,这些更改会更改可见性,但不会影响布局

例如:Visibility, background color, outline

重排/回流

重排意味着重新计算文档中元素的位置和几何形状。 对元素进行更改时会发生重排,这会影响部分或整个页面的布局。 元素的 Reflow 会导致 DOM 中所有子元素和祖先元素的后续回流

回流和重绘都是昂贵的操作

根据 Opera,大多数回流本质上会导致页面重新渲染:

重排在性能方面非常昂贵,并且是导致 DOM 脚本缓慢的主要原因之一,尤其是在具有低性能的设备上 处理能力,例如电话。 在很多情况下,它们相当于重新布局整个页面。

发生重排/重绘的情况:

  • 添加、删除、更新 DOM 节点时会发生回流
  • 使用 display: none 隐藏 DOM 元素将导致重排和重绘
  • 移动,动画一个 DOM 节点将触发重排和重绘
  • 调整窗口大小将触发重排
  • 更改字体样式会改变元素的几何形状。 这意味着它可能会影响页面上其他元素的位置或大小,这两者都需要浏览器执行重排。 一旦这些布局操作完成,任何损坏的像素都需要重新绘制
  • 添加或删除样式表将导致重排/重绘
  • 脚本操作 DOM 是一项昂贵的操作,因为每次文档或文档的部分修改时它们都会重新计算。 正如我们从触发回流的所有许多事情中看到的那样,它每秒可能发生数千次
var bodyStyle = document.body.style; // cache

bodyStyle.padding = "20px"; // reflow, repaint
bodyStyle.border = "10px solid red"; // reflow, repaint

bodyStyle.color = "blue"; // repaint only, no dimensions changed
bstyle.backgroundColor = "#cc0000"; // repaint

bodyStyle.fontSize = "2em"; // reflow, repaint

// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('Hello!'));

尽量减少重绘和回流

减少重排/重绘对用户体验的负面影响的策略是简单地减少重排和重绘以及减少对样式信息的请求,以便浏览器可以优化重排。 该怎么做?

  • 不要一一改变个人风格。 最好的理智和可维护性是更改类名,而不是样式。 如果样式是动态的,请编辑 css Text 属性
// bad
var left = 10,
    top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";

// better 
el.className += " theclassname";

// or when top and left are calculated dynamically...

// better
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
  • 批量 DOM 更改
  1. 使用 documentFragment 保存临时更改
  2. 克隆、更新、替换节点
  3. 隐藏带有显示的元素:无(1 次重排,1 次重绘),添加 100 处更改,恢复显示(总共 2 次重排,2 次重绘)
  • 不要重复要求计算样式,将它们缓存到变量中
  1. Multiple reads/writes (like for the height property of an element)
  2. Writes, then reads, from the DOM, multiple times causing document reflows
  3. 读取(缓存),写入(无效布局),读取(触发布局)。
  4. To fix:先阅读所有内容,然后编写所有内容

Chrome Tool Performance

Chrome 提供了一个很棒的工具,可以帮助我们弄清楚我们的代码发生了什么,我们有多少回流(布局)和重绘,以及关于内存、事件等的更多细节。

Bad code

var box1Height = document.getElementById('box1').clientHeight;
document.getElementById('box1').style.height = box1Height + 10 + 'px';

var box2Height = document.getElementById('box2').clientHeight;
document.getElementById('box2').style.height = box2Height + 10 + 'px';

var box3Height = document.getElementById('box3').clientHeight;
document.getElementById('box3').style.height = box3Height + 10 + 'px';

var box4Height = document.getElementById('box4').clientHeight;
document.getElementById('box4').style.height = box4Height + 10 + 'px';

var box5Height = document.getElementById('box5').clientHeight;
document.getElementById('box5').style.height = box5Height + 10 + 'px';

var box6Height = document.getElementById('box6').clientHeight;
document.getElementById('box6').style.height = box6Height + 10 + 'px';

1f5eb1e3a5b56588d7523aaae11f2b5.png Optimized Code

var box1Height = document.getElementById('box1').clientHeight;
var box2Height = document.getElementById('box2').clientHeight;
var box3Height = document.getElementById('box3').clientHeight;
var box4Height = document.getElementById('box4').clientHeight;
var box5Height = document.getElementById('box5').clientHeight;
var box6Height = document.getElementById('box6').clientHeight;

document.getElementById('box1').style.height = box1Height + 10 + 'px';
document.getElementById('box2').style.height = box2Height + 10 + 'px';
document.getElementById('box3').style.height = box3Height + 10 + 'px';
document.getElementById('box4').style.height = box4Height + 10 + 'px';
document.getElementById('box5').style.height = box5Height + 10 + 'px';
document.getElementById('box6').style.height = box6Height + 10 + 'px';

9fceead180d2c0e9dbee8b706e17e58.png