页面解析与加载、回流与重绘

1、页面解析

  • 解析过程:把HTML结构变成一个有一个的节点,然后组成一个domTree,即DOM树
  • DOM树构建的本质:HTML元素节点的解析
  • DOM树构建原则:深度优先解析原则
  • 包含隐藏节点: display:none; JS动态创建的节点

2、页面加载

(1)cssTree CSS树

  • 页面解析的时候会抽取相应的CSS样式形成 cssTree,即样式结构体
  • 构建cssTree的时候会自动忽略浏览器不能识别的样式

(2)renderTree 渲染树

  • renderTree = domTree + cssTree
  • renderTree构建完毕后 -> 浏览器会根据它绘制页面
renderTree构建过程:
1、渲染树每个节点都有自己的样式
2、渲染树不包含display:none、head之类不需要绘制的节点
3、visibility: hidden 相对应的节点是包含在渲染树上的,因为影响布局 layout
4、渲染树上每一个节点都会被当作一个盒子box;具备内容填充、边距、边框、位置、大小、其他样式

3、回流和重绘

  • 当JS对页面的节点操作时,就会产生回流或者重绘
  • 回流 - 重排 - reflow -> 一定会引起重绘
  • 重绘 - repaint -> 不一定是回流产生的后续反应

(1)回流
在节点的尺寸、布局、display:none block 这一些改变的时候,渲染树中的一部分或者全部需要重新构建,这时就会产生回流, 一个页面至少有1次回流。

(2)重绘
回流时,浏览器会看重新构建受影响部分的渲染树 -> 重绘
回流完成后,浏览器会根据新的渲染树重新绘制回流影响部分节点,这个重新绘制的过程 -> 重绘
除开回流之外,其余改变只会引起重绘

(3)引起回流的因素

  1. DOM节点增加、删除
  2. DOM节点位置变化
  3. 元素的尺寸、边距、填充、边框、宽高
  4. DOM节点display 显示与否
  5. 页面渲染初始化
  6. 浏览器窗口尺寸变化 -> resize
  7. 向浏览器请求某些样式信息:offset、scroll、client、width、height、getComputeStyle()、currentStyle

(4)一个示例

<div class="box">盒子</div>
<script type="text/javascript">
var oBoxStyle = document.getElementsByClassName('box')[0].style;

// 回流 + 重绘
oBoxStyle.width = '200px';
// 回流 + 重绘
oBoxStyle.height = '200px';
// 回流 + 重绘
oBoxStyle.margin = '20px';
// 重绘
oBoxStyle.backgroundColor = 'green';
// 回流 + 重绘
oBoxStyle.border = '5px solid orange';
// 重绘
oBoxStyle.color = '#fff';
// 回流 + 重绘
oBoxStyle.fontSize = '30px';
// 最后增加只产生1次 回流 + 重绘
var h1 = document.createElement('h1');
h1.innerHtml = '标题';
document.body.appendChild(h1);
</script>

(5)浏览器性能优化
A、回流比重绘的代价要更高,回流的花销跟render tree有多少节点需要重新构建有关系,即跟节点的量与规模有关系。
B、较新的浏览器本身能够使用队列批处理来优化,尽可能减少重绘和回流。
C、原理:浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。
D、浏览器解决不了的问题:
当你请求向浏览器请求一些 style信息的时候,就会让浏览器flush队列,比如:

  • offsetTop, offsetLeft, offsetWidth, offsetHeight
  • scrollTop/Left/Width/Height
  • clientTop/Left/Width/Height
  • width,height

请求了getComputedStyle(), 或者 IE的 currentStyle
当你请求上面的一些属性的时候,浏览器为了给你最精确的值,需要flush队列,因为队列中可能会有影响到这些值的操作。即使你获取元素的布局和样式信息跟最近发生或改变的布局信息无关,浏览器都会强行刷新渲染队列。

(6)优化:改成1次重绘和回流

1、先定义一个css,再使用JS添加一次css:this.className += ' active';
2、使用this.style.cssText = '样式字符串';
3、使用文档碎片进行优化:document.createDocumentFragment();
    引发一次回流和重绘;
4、使用display:none技术,只引发两次回流和重绘; 
    过程:(1)先使用display: none;  (2)样式改变;  (3)再使用display:block;
5、缓存优化 offset、scroll、client、width、height、getComputeStyle()、currentStyle
    // 先变量缓存
    var oLeft = div.offsetLeft;
    // 再计算
    div.style.left = oLeft + 10 + 'px';
6、动画、元素 一定绝对定位,不能直接操作 marginLeft
    原因:绝对定位使它脱离文档流,否则会引起父元素及后续元素大量的回流
7、少用 table,table很费性能