浏览器中的event loop和页面渲染简单了解。
2019-10-28 13:54 罗小二 阅读(685) 评论(0) 编辑 收藏 举报- 渲染流程
- event loop简单了解
- event loop和渲染
- 总结
1. 渲染流程
1.1资源下载
忽略掉了从导航栏输入URL到资源从网络下载到本地的整个过程下图步骤(详细了解点击)
进入浏览器展示页面部分,先了解下进程线程
1.2进程和线程
浏览器展示页面信息,浏览器内进程线程之间的分工合作(如下图有个大概印象)。
进程(process):
狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
CPU的主要功能是解释计算机指令以及处理计算机软件中的数据,进程是CPU资源分配的最小单位。
线程(thread):
是操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程是 CPU调度的最小单位,一个进程可以包括多个线程,这些线程共享这个进程的资源。
1.3 chrome浏览器进程
- 控制浏览器界面显示,包括用户交互、前进、后退等操作
- 处理网络堆栈以从Internet接收数据
- 将渲染的内容绘制到用户界面上
- 控制其它子进程创建和销毁
渲染进程: 负责页面的渲染、脚本执行、事件处理。不同浏览器有不同的渲染引擎, Trident(IE渲染引擎) 、Gecko(Firefox渲染引擎)、Webkit(Safari渲染引擎)、Blink(Google 从 WebKit fork 出的引擎,Edge 也使用了)
GPU进程:用于3D绘制,与其他进程隔离地处理GPU任务。由于GPU处理来自多个应用程序的请求并将它们绘制在同一表面上,因此将其分为不同的过程。
Plugin:控制网站使用的所有插件。
......
浏览器展示页面部分,最需要了解的是渲染进程
1.4渲染进程(主要点)
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> body { font-size: 16px } p { font-weight: bold } span { color: red } p span { display: none } img { float: right } </style> </head> <body> <p>Hello <span>web performance</span> students!</p> <div><img src="awesome-photo.jpg"></div> </body> </html>
1.4.1 Parsing→Style calculation
如下图:Parsing→Style calculation 主要是创建对象模型,样式计算- HTML → Document Object Model (DOM)
- CSS → CSS Object Model (CSSOM)(附带样式计算,涉及层叠规则和层叠样式表)
(4) 一个思考题:p{ } 和 .box p {} 哪个性能更好?
提示:CSS 选择符从右往左匹配查找,从右到左匹配CSS选择器应该避免节点层级过多
//p{ } 和 .box p {} 哪个性能更好? <div class="box"> <p> 哈哈哈 </p> </div> <style> p { color: red; } .box p { color: red; } </style> //p{ },浏览器只需要找到页面中所有的p 标签然后设置颜色 //.box p {},浏览器首先需要找到所有的 p 标签,然后找到 p 标签上.box ,给符合这种条件的 p 标签设置颜色,这样的递归过程很复杂。 //所以 p{ } 的性本更好 // 我们应该避免写过于具体的 CSS 选择器,降低选择器的复杂性,尽可能少的添加无意义标签,保证层级扁平。 // CSS 中 BEM 的命名方式了解一下
补充一下:对于现代浏览器来说,通常类匹配是最快的匹配选择器
1.4.2 Layout tree(Render tree)
Document Object Model (DOM) 和 CSS Object Model (CSSOM) 是相互独立的数据结构。将DOM和CSSOM组合成了Render tree(Chrome 新的叫法 Layout tree)。
输入DOM和CSSOM 输出Layout tree(Render tree)
从上图可以看出Render tree(Layout tree)仅包含了屏幕中可见的节点
Render tree(Layout tree)的构建(引用)
To construct the render tree, the browser roughly does the following:
Starting at the root of the DOM tree, traverse each visible node.
- Some nodes are not visible (for example, script tags, meta tags, and so on), and are omitted since they are not reflected in the rendered output.
- Some nodes are hidden via CSS and are also omitted from the render tree; for example, the span node---in the example above---is missing from the render tree because we have an explicit rule that sets the "display: none" property on it.
For each visible node, find the appropriate matching CSSOM rules and apply them.
Emit visible nodes with content and their computed styles.
- 从DOM树的根开始,遍历每个可见节点。
- 一些节点不可见(例如,脚本标记,元标记等),由于它们未反映在渲染的输出中,因此将其省略。
- 一些节点通过CSS隐藏,并且在渲染树中也被省略; 例如,在上例中,span节点(在上面的示例中)丢失了,因为我们在其上设置了“ display:none”属性(p span { display: none })
- 对于每个可见节点,找到合适的匹配CSSOM规则并应用它们。
- 发出具有内容及其计算样式的可见节点。
DOM和CSSOM树合并在一起形成Render tree( Layout tree)。Render tree( Layout tree)包含屏幕上可见内容有关的信息。但它没有位置和形状大小。计算这些位置和形状大小被称为布局。
1.4.3 Layout
Render tree( Layout tree)仅包含渲染页面所需的节点,父子容器层级关系,不包括位置坐标节点形状大小。
Layout 主要是计算节点的位置形状和大小。输入Layout tree(Render tree)输出盒子模型
HTML使用基于流的布局模型,大多数时候都可以单次计算几何形状。“在流程中”之后的元素通常不会影响在“流程中”之前的元素的几何形状,因此布局可以在文档中从左到右,从上到下进行。
坐标系相对于根框架,使用左上角为 (0,0)为基础坐标(layout流程具体参考)
The layout usually has the following pattern:
- Parent renderer determines its own width.
- Parent goes over children and:
- Place the child renderer (sets its x and y).
- Calls child layout if needed–they are dirty or we are in a global layout, or for some other reason–which calculates the child's height.
- Parent uses children's accumulative heights and the heights of margins and padding to set its own height–this will be used by the parent renderer's parent.
- Sets its dirty bit to false.
- 父渲染器确定其自身的宽度。
- 父渲染器检查子节点,并:
- 放置子渲染器(设置子渲染器x和y位置坐标)。
-
如果需要,调用子布局(子布局需要重新布局或着我们处于全局布局中,或出于其他原因-要计算子级的高度)
- 父级使用子级的累计高度以及边距和填充的高度来设置自己的高度(这高度值将由父级渲染器的父级使用)。
- 将dirty 值设置为false。(作为需要重绘的标志)
1.4.4 Paint
Paint 主要是遍历Layout tree以创建绘制记录。
也可以打开Chrome的“开发者工具”,选择“Layers”标签查看绘顺记录。
1.4.5 合成(Compositing)
知道了文档的结构,每个元素的样式几何形状和绘制顺序,如何将这些信息转换为屏幕上的像素页面展示的?
将这些信息转换为屏幕上的像素的过程称为栅格化。
但现在的页面并不是直接栅格化,因为页面交互和动画的复杂性,现代的浏览器运行着一种更复杂的过程,为了找出哪些元素需要位于哪个图层中,主线程遍历了布局树以创建Layer Tree图层树(在DevTools性能面板中,此部分称为“更新图层树(Update Layer Tree)”,更新层树是确保z-index注意诸如此类的过程的一部分)。
可以看到页面的各个部分被分为了若干层,以ul.nav-list.left 部分为例,箭头所指此部分被分层的原因,ul.nav-list.left 部分被分层的原因是容纳可以滚动的内容。
当ul.nav-list.left 部分向左右滑动时变换只有本层不会影响到其他元素的绘制合成,本层已经被光栅化,新的帧渲染就非常快了(存储本层信息需要消耗一定内存)。合成(Compositing)是靠消耗额外的内存和管理资源换取性能优化的。
1.4.6小结
以WebKit流程为例主理解的步骤有:Parsing→Style calculation→Layout→Paint 后续的Compositing等
- Parsing→Style calculation 主要是创建对象模型,样式计算
- 输入HTML文档输出Document Object Model
- 输入CSS样式表输出CSS Object Model
- 输入DOM和CSSOM 输出Layout tree(Render tree)。Render tree( Layout tree)包含屏幕上可见内容有关的信息
- Layout
- 输入Layout tree(Render tree)输出盒子模型。布局阶段得到的是元素的具体位置以及box盒模型的具体数值(width,height,margin,padding,border,left,top,…)
- Paint
- 输入Layout tree(Render tree),输出Layout tree绘制记录。主要是遍历Layout tree以创建绘制记录。
- Compositing
- 主线程遍历了布局树以创建图层树,主线程一旦创建了layer tree 并确定了绘制顺序,主线程便将该信息提交给合成线程
- 合成器线程将每个图层栅格化。光栅化后完成后,合成器线程创建合成框架,将框架发送到浏览器进程,浏览器进程将内容显示在屏幕上
2. event loop简单了解
渲染器进程要处理 DOM、计算样式、处理布局渲染,同时协调事件,用户交互,脚本,联网个过程使用到了event loop(事件循环)。我们需要简单了解event loop。
- FPS
- 堆,调用栈等概念
- 执行顺序
2.1 FPS
2.1.1 帧,FPS(每秒传输帧数)
帧:就是影像动画中最小单位的单幅影像画面,相当于电影胶片上的每一格镜头
FPS是图像领域中的定义,是指画面每秒传输帧数,通俗来讲就是指动画或视频的画面数。
2.1.2 页面如何看起来顺畅了?
2.1.3怎么查看FPS?
2.1.4页面渲染中我们可控的关键流程
2.2 堆(Heap),调用栈(call stack)等概念
堆(Heap): 对象是在堆中分配的,堆只是一个名称,表示一个大的(主要是非结构化的)内存区域。2.3 event loop 执行顺序
2.3.1简述规范
简述:
- 从 task 队列( 一个或多个) 中选出最旧的一个 task, 执行它。
- 执行 microtask 队列中的所有 microtask, 直到队列为空。如果 microtask 中又添加了新的 microtask, 直接放进本队列末尾。
- 执行 UI render 操作,当前是否有渲染机会且需要渲染,如需要渲染执行各种渲染所需工作,包括执行 animation frame callbacks,最后渲染 UI,如不需要直接跳过。
浏览上下文呈现机会是根据硬件限制(例如显示刷新率)和其他因素(例如页面性能或页面是否在后台)确定的。 渲染机会通常会定期出现。
2.3.2 结合图片
- 若不为空,执行macrotask中的一个任务,检查microtask队列是否为空
- 若为空,检查microtask队列是否为空
- 若不为空,执行 microtask 队列中的所有 microtask, 直到队列为空。如果 microtask 中又添加了新的 microtask, 直接放进本队列末尾。等队列为空,检查是否需要渲染。
- 若为空,检查是否需要渲染
- 需要渲染更新视图,检查macrotask队列是否为空
- 不需要渲染,检查macrotask队列是否为空
2.3.3伪代码简单实现
while (eventLoop.waitForTask()) { const taskQueue = eventLoop.selectTaskQueue() if (taskQueue && taskQueue.hasTask()) { let currentTask = taskQueue.oldTask() execute(currentTask) } // 一个eventloop 只有一个 microtaskQueue const microtaskQueue = eventLoop.microTaskQueue while (microtaskQueue.hasMicrotask()) { microtaskQueue.processMicrotask() } if (shouldRender()) { // requestAnimationFrame() 回调执行时机 runAnimationFrames() render() } }
2.3.4小结
3 event loop和渲染
这个小段讨论了我觉得可以加深理解和有意思的问题
3.1强制同步布局(forced synchronous layout)
以此段代码为例
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> body { font-size: 16px } p { font-weight: bold } div { width: 100px; height: 100px; border: 1px solid #ccc; } </style> </head> <body> <p>Hello </p> <div id="box"></div> <button id="button">do it</button> <script> var button = document.getElementById('button'); var box = document.getElementById('box'); button.addEventListener('click', () => { for (var i = 0; i < 10; i++) { box.style.width = box.offsetWidth + 'px'; } }) </script> </body> </html>
代码反复读取修改了box的宽度。因为修改了box的宽度,又读取了box的宽度,浏览器需要应用这个宽度,然后运行布局,方便读取此时box精确的宽度。
可以看见反复进行了Style calculation,和Layout ,此时的流程如图。当前task 执行完后,等到渲染是界面才会进行像素管道整个流程。
3.2 渲染时机
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> body { font-size: 50px } p { font-weight: bold } div { width: 400px; height: 400px; border: 10px solid #ccc; } </style> </head> <body> <div id='con'>this is con</div> <script> var con = document.getElementById('con'); con.onclick = function() { setTimeout(function() { con.textContent = 0; }, 0) setTimeout(function() { con.textContent = 1; }, 0) setTimeout(function() { con.textContent = 2; }, 0) setTimeout(function() { con.textContent = 3; }, 0) setTimeout(function() { con.textContent = 4; }, 0) setTimeout(function() { con.textContent = 5; }, 0) setTimeout(function() { con.textContent = 6; }, 0) }; </script> </body> </html
chrome-devtools performance工具点击记录,点击方框,得到分析截图如下(运行得到结果不一,可以多运行几次观察思考,)
以图中显示结果0,4,6 的结果图进行分析,并不是每一轮事件循环都会有渲染机会,图中第二个UI render 前执行了 4个task(setTimeout 的 Function Call)。当前UI render 渲染的变化会在下一帧显示。所以0过后一帧显示的图像是4。观察图像出现时间大概间隔16.6ms,当JavaScript 的task及它相关的所有microtask,执行时间较短时,浏览器会匹配设备的刷新率,并为每个屏幕刷新放置1张新图片或一帧,以实现平滑动画。
4. 总结
文章整理三个问题:event loop处理模型,浏览器渲染流程,event loop和渲染。简述了宏任务,微任务,UI渲染时机,requestAnimationFram执行,记录了部分实践和自己的理解,理解不准确之处,还请教正。欢迎一起讨论学习。
视频:
参考文章: