Open lulusir opened 6 years ago
渲染引擎通过通过网络请求接收渲染内容
渲染引擎的第一步是解析html文档并将解析的元素转换为dom树中的实际dom节点。
当浏览器解析dom的时候,遇到link标签,引用外部的css样式表,引擎会将css抽象成cssom
HTML中的可视指令与来自cssom树的样式数据结合使用来创建渲染树。 为了构建渲染树,浏览器大致如下:
当渲染器被创建并添加到树中时,它没有位置和大小。计算这些值称为布局。 html使用基于流的布局模型,这意味着大多数时候它可以一次性计算几何。坐标系相对于根渲染器。使用顶部和左侧坐标。 布局是一个递归过程,从根元素开始,也就是html,每个渲染器都会去计算他自己的位置和大小 绘制渲染树 CSSOM 为何具有树结构?为页面上的任何对象计算最后一组样式时,浏览器都会先从适用于该节点的最通用规则开始(例如,如果该节点是 body 元素的子项,则应用所有 body 样式),然后通过应用更具体的规则(即规则“向下级联”)以递归方式优化计算的样式。 在这个阶段,遍历渲染器树,调用渲染器的paint()方法在屏幕上显示内容。
当渲染器被创建并添加到树中时,它没有位置和大小。计算这些值称为布局。
html使用基于流的布局模型,这意味着大多数时候它可以一次性计算几何。坐标系相对于根渲染器。使用顶部和左侧坐标。
布局是一个递归过程,从根元素开始,也就是html,每个渲染器都会去计算他自己的位置和大小
CSSOM 为何具有树结构?为页面上的任何对象计算最后一组样式时,浏览器都会先从适用于该节点的最通用规则开始(例如,如果该节点是 body 元素的子项,则应用所有 body 样式),然后通过应用更具体的规则(即规则“向下级联”)以递归方式优化计算的样式。 在这个阶段,遍历渲染器树,调用渲染器的paint()方法在屏幕上显示内容。
渲染分为全局渲染和增量渲染
当解析器到达script标记时,脚本将被立即解析并执行。 文档的解析将暂停,直到脚本执行完毕。 这意味着该过程是同步的
这也是为什么把script标签放在body结束之前
html5添加了一个选项,将脚本标记为异步,以便它可以被其他线程解析和执行。
减少reflow和repaint
javascript
除非将 JavaScript 显式声明为异步,否则它会阻止构建 DOM。 <script src="app.js" async></script> 避免使用setTimeout setInterval 来更新视图,这会在render之后提交修改需求 在micro-tasks中修改dom。这会在render之前提交修改需求 把script标签放在body结束之前,或者使用异步script(defer, async) 把计算量大的js放在workers执行,例如解析一个大的json文件
除非将 JavaScript 显式声明为异步,否则它会阻止构建 DOM。
<script src="app.js" async></script>
CSS
// 设置单个属性 elt.style.color = "blue";
// 在单个语句中设置多个样式 elt.style.cssText = "color: blue; border: 1px solid black";
// 在单个语句中设置多个样式 elt.setAttribute("style", "color:red; border: 1px solid blue;");
- 通过改变类名来修改样式 **DOM** 1. 使元素脱离文档流 2. 对其应用多重修改 3. 把元素带回文档中 > 这个过程会触发两次回流,第一步和第三步。把会触发多次回流的步骤放在第二步 三种基本方法: - display:none,然后修改样式,然后在恢复 - 使用文档片段(document fragment)在当前dom树之外构建一个子树,再把他拷贝回文档。 ```javascript var fragment = document.createDocumentFragment() ... 在这里进行dom操作,可以减少回流和重绘的次数 document.getElementById('#app').appendChild(fragment)
var old = document.getElementById('#app') var clone = old.cloneNode(true) ... 在这里进行dom操作,可以减少回流和重绘的次数 old.parentNode.replaceChild(clone, old)
缓存布局信息
前面提到在查询布局信息(offsetLeft...)的时候也会引起回流,我们在使用的时候可以把布局信息缓存起来,减少回流次数
这里贴上<<高性能javascript>>中的例子:把myElement元素沿对角线移动,每次移动一个像素,从100100的位置开始,到500500的位置结束。在timeout循环体中你可以使用下面的方法
// 低效的 myElement.style.left = 1 + myElement.offsetLeft + 'px' myElement.style.top = 1 + myElement.offsetTop + 'px' if (myElement.offsetTop >= 500) { stopAnimation(); }
// 优化 // 在循环外层获取初始值 var current = myElement.offsetLeft . . . // 直接使用current变量,不再查询偏移量 current++ myElement.style.left = current + 'px' myElement.style.top = current + 'px' if (current >= 500) { stopAnimation(); }
使元素进行动画效果的时候脱离文档流 在元素发生动画效果的时候,会引起底部元素的回流,这个影响可能很大,也可能很小,取决于元素在文档流的位置
回流、重绘及其优化
渲染过程
渲染引擎通过通过网络请求接收渲染内容
抽象DOM tree
抽象CSSOM tree
构建渲染树
渲染树的布局
渲染分为全局渲染和增量渲染
处理脚本和样式表的顺序
这也是为什么把script标签放在body结束之前
html5添加了一个选项,将脚本标记为异步,以便它可以被其他线程解析和执行。
回流和重绘(reflow和repaint)
何时触发回流和重绘
优化渲染性能,减少回流和重绘
减少reflow和repaint
javascript
CSS
// 在单个语句中设置多个样式 elt.style.cssText = "color: blue; border: 1px solid black";
// 在单个语句中设置多个样式 elt.setAttribute("style", "color:red; border: 1px solid blue;");
缓存布局信息
这里贴上<<高性能javascript>>中的例子:把myElement元素沿对角线移动,每次移动一个像素,从100100的位置开始,到500500的位置结束。在timeout循环体中你可以使用下面的方法
使元素进行动画效果的时候脱离文档流 在元素发生动画效果的时候,会引起底部元素的回流,这个影响可能很大,也可能很小,取决于元素在文档流的位置
下面简要概述了浏览器完成的步骤:
关键渲染路径
渲染时间点
参考高性能javascript
参考How JavaScript works: the rendering engine and tips to optimize its performance