Skip to content

浏览器有很多进程,其中有网络进程,而网络进程又包含网络线程。网络线程完成网络请求任务之后,拿到了一个html文件,但是它没有解析的能力,于是将html文件包装成一个任务,并将其传递给渲染进程的主线程的消息队列。在事件循环机制的作用下,渲染线程取出消息队列中的渲染任务,开启渲染流程。(渲染器进程的核心任务就是把html、js、css、img等资源渲染成用户可交互的web页面。)

渲染就是将HTML代码转换为像素信息的过程。

渲染流程

在这里插入图片描述

解析HTML parse

渲染器进程的主线程对html进行解析,构造DOM数据结构。(DOM文档对象模型是浏览器对页面在其内部表示形式,是Web程序员可以通过JavaScript与之交互的数据结构和API。在 JS 代码中,通过document对象可以访问和修改DOM树。)在 DOM树构造过程中会创建Document对象,然后以Document为根节点的DOM树不断进行修改,向其中添加各种元素。

在这里插入图片描述

CSS也会被解析成CSSOM(CSS Object Model),也是树形结构,根节点(StyleSheetList)是网页中所有的样式表,二级子节点可能包含内部样式表、外部样式表、内联样式表和浏览器默认样式表(取决于代码中是否有这些内容),如果有两个,则会出现两个外部样式表节点。

除了浏览器默认样式表外,内部样式表、外部样式表、内联样式表都可以通过 JS 访问到。
内部样式表和外部样式表:使用document.styleSheets可以访问到一个数组,元素是样式表对象。
使用document.styleSheets[0].addRule(“div”, “border: 1px solid red important”)可以让页面上的所有div标签的边框变成红色,这种做法与传统的“获取所有div标签,再设置其style”的做法不同。 内联样式表:使用dom.style访问

在这里插入图片描述

为了提高解析效率,浏览器在开始解析之前,会启动一个预解析的线程,率先下载HTML中的外部CSS文件和外部的JS文件。 如果渲染主线程解析到link位置,此时外部的CSS文件还没有下载解析好,主线程不会等待CSS文件的下载解析,而会继续解析后续的HTML。这是因为下载和解析CSS的工作是在预解析线程中进行的。这就是CSS不会阻塞HTML解析的根本原因。

在这里插入图片描述

图片和css这些资源需要通过网络下载或者从缓存中直接加载。这些资源不会阻塞html的解析,因为他们不会影响DOM的生成。

渲染主线程遇到 JS 的script标签时必须暂停一切行为,等待下载 JS 文件,并且解析执行 JS 代码,然后才能继续解析 HTML。这是因为JS代码的执行过程可能会修改当前的DOM树,所以DOM 树的生成必须暂停。预解析线程可以分担一点下载 JS 的任务。

在这里插入图片描述

HTML解析完成后,会得到DOM树和CSSOM树,浏览器的默认样式、内部样式、外部样式、行内样式均会包含在CSSOM树中。

样式计算style

根据得到的 DOM 树和 CSSOM 树,通过遍历 DOM 树,依次为树中的每个节点计算它的所有 CSS 属性。 CSS属性值的计算过程,分为如下4个步骤:(详细过程见浏览器渲染原理) 确定声明值; 层叠冲突; 使用继承; 使用默认值

在这里插入图片描述

在这一过程中,很多预设值会变成绝对值,比如red会变成rgb(255,0,0);相对单位会变成绝对单位,比如em会变成px。这一步完成后,会得到一颗带有样式的DOM树。

布局layout

根据 DOM 树里每个节点的样式,计算出每个节点的尺寸和位置。 主线程通过遍历DOM和计算好的样式来生成layout tree,layout tree上的每个节点都记录x,y坐标和边框尺寸。这里需要注意的一点是DOM Tree和layout tree并不是一一对应的,设置了display:none的节点不会出现在layout tree上,而在before伪类中添加了content值的元素,content的内容会出现在layout tree,不会出现在DOM树里。这是因为DOM 是通过html解析获得,并不关心样式。而layout tree是根据dom tree和计算好的样式来生成,layout tree是和最后展示在屏幕上的的节点是对应的。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

布局过程存在两个规则(w3c规定): 1.内容必须在行盒中 2.行盒和块盒不能相邻 如果在块盒中直接写入内容,则会在中间生成一个匿名行盒;如果块盒和行盒相邻,则为行盒外部生成一个匿名块盒。(参考下图)

在这里插入图片描述

html标签只表明语义,不区分行盒或块盒,css决定元素是行盒还是块盒。

通常理解的<p>, <div>是块盒,是因为浏览器默认样式给它们设置了display: block;。

<head><meta>等标签都是隐藏的,是因为浏览器默认样式表给它们设置了display: none;。

分层layer

在生成layout tree之后不能直接绘制,渲染进程会将一些复杂的3D动画、滚动条、z-index层级高的生成图层,并生成一颗layer tree交给GPU加速渲染。 现在的页面大多都十分复杂,并且交互效果很多。如果不分层,用户的一个简单交互将导致整个页面的重新渲染,效率低下。分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提升效率。

在这里插入图片描述

拥有层叠上下文对象的元素会被提升为单独的一层。

文档中层叠上下文满足以下任意一个条件的元素形成:

  1. 根元素(html)
  2. z-index值不为auto的绝对/相对定位元素
  3. fixed/sticky定位
  4. z-index值不为auto的flex子项
  5. opacity属性值小于1的元素
  6. transform、filter、perspective、clip-path、mask、mask-image、mask-border属性值不为none的元素
  7. isolation属性被设置为isolate的元素
  8. -webkit-overflow-scrolling属性值为touch的元素
  9. 在will-change中指定任意CSS属性
  10. contain属性为layout、paint或者综合值(如:strict、content)

在层叠上下文中,子元素同样按照这个规则进行层叠。重要的是,子级层叠上下文的z-idnex值只在父级中有意义,子级层叠上下文被自动视为父级层叠上下文的一个独立单元。

需要剪裁(clip)的地方会被创建图层

当子元素需要展示的区域大于父元素的大小时,就会需要剪裁。出现剪裁情况的时候,渲染引擎会为文字部分单独擦护功能键一个层,如果出现滚动条,滚动条也会被提升为单独的层。

通常来说不会分太多层,因为分层虽然可以提高渲染效率,但是占用很大内存空间。

总结:元素有了层叠上下文属性或者需要被剪裁,满足任意一点,就会提升为单独一层。

will-change:通常大多数元素例如<div>不会单独分为一层,但是如果它的内容经常需要更新、需要重新渲染,可以添加一个属性:will-change。 如果这个元素的transform属性需要经常发生变化,那么可以声明will-change: transform;,告知浏览器其需要经常更新,但是最后是否决定分层依然是浏览器的具体实现决定的。

绘制paint

主线程会为每个层单独产生绘制指令,用于描述这一层的内容该如何画出来。(确定绘制顺序)

分块tiling

分块会将每⼀层分为多个⼩的区域。

当layer tree生成完毕和绘制顺序确定后,渲染主线程将每个图层的绘制信息提交给合成器线程。合成器线程将每个图层栅格化。合成器线程首先将每个图层切分为多个图块,将其划分为更多的小区域。然后将每个图块发送给栅格线程。

在这里插入图片描述

在这里插入图片描述

这一步的目的是,优先画出视口内以及接近视口的内容。想象一个很长的、需要滚动很久才能到底的页面。页面很大,但是接近视口的内容优先级最高,因为我们希望用户能尽早的看到页面的内容。于是分块,接近视口的块优先级高,优先显示出来。 分块的工作是交给多个线程同时进行的。

在这里插入图片描述

其中的合成线程和渲染主线程都位于渲染进程里。

栅格化raster

栅格化是将每个块变成位图,优先处理靠近视⼝的块。位图可以简单理解成用二维数组存储的像素信息。

合成器线程将每个图块发送给栅格线程。栅格线程栅格化每个图块并将它们存储在GPU内存中。对图块进行栅格化后,合成器线程可以给不同的栅格线程分别设置优先级,比如栅格化可视区域图块的栅格线程优先处理。

在这里插入图片描述

在这里插入图片描述

画draw

合成器线程计算出每个位图在屏幕上的位置,交给硬件GPU进行最终呈现。
当图块栅格化完成后,合成器线程将收集称为“draw quads”的图块信息,这些信息里记录了包含诸如图块在内存中的位置和在页面的哪个位置绘制图块的信息。
根据这些数据合成器线程生成了一个合成器帧frame。然后这个合成器帧compositor frame通过IPC传送给浏览器进程,接着浏览器进程将合成器帧传到GPU,然后GPU渲染展示到屏幕上。当页面发生变化,比如你滚动了当前页面,则会生成一个新的compositor frame,新的frame再传给GPU。再次渲染到屏幕上。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

简单概括渲染流程

  1. 主线程将html解析构造DOM树和CSSOM树;
  2. 然后根据DOM树和CSSOM树计算每个DOM节点的最终样式;
  3. 根据 DOM 树里每个节点的样式,计算出每个节点的尺寸和位置,生成layout tree;
  4. 页面所有的元素按照某种规则进行分图层,生成layer tree;
  5. 然后为每个层单独产生绘制指令,用于描述这一层的内容该如何画出来;
  6. 主线程将layer tree和绘制顺序信息一起传给合成器线程,合成器线程将每个图层切分为多个图块;
  7. 然后将每个图块发送给栅格线程进行栅格化,将每个块变成位图,并将它们存储在GPU内存中。
  8. 栅格化完成后,合成器线程会获得栅格线程传过来的"draw quads"图块信息,根据这些信息,合成器线程合成了一个frame,然后将该合成frame通过IPC传回给浏览器进程,浏览器进程在传到GPU进行渲染,最后页面就展示到屏幕上了。

在这里插入图片描述

完整的渲染流程

一个完整的渲染步骤大致可总结为如下:

  1. 渲染进程将HTML内容转换为能够读懂的DOM树结构。
  2. 渲染引擎将CSS样式表转化为浏览器可以理解的 styleSheets ,计算出DOM节点的样式。
  3. 创建布局树,并计算元素的布局信息。
  4. 对布局树进行分层,并生成分层树。
  5. 为每个图层生成绘制列表,并将其提交到合成线程。
  6. 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
  7. 合成线程发送绘制图块命令DrawQuad给浏览器进程。
  8. 浏览器进程根据DrawQuad消息生成页面,并显示到显示器上。

普通图层和复合图层

上面的介绍中,提到了 composite 概念。 可以简单的这样理解,浏览器渲染的图层一般包含两大类:渲染图层(普通图层)以及复合图层。 渲染图层:又称默认复合层,是页面普通的文档流。我们虽然可以通过绝对定位,相对定位,浮动定位脱离文档流,但它仍然属于默认复合层,共用同一个绘图上下文对象(GraphicsContext)。 复合图层:它会单独分配资源(当然也会脱离普通文档流,这样一来,不管这个复合图层中怎么变化,也不会影响默认复合层里的回流重绘) 某些特殊的渲染层会被提升为复合成层(Compositing Layers),复合图层拥有单独的GraphicsLayer,而其他不是复合图层的渲染层,则和其第一个拥有 GraphicsLayer 父层共用一个。 每个 GraphicsLayer 都有一个 GraphicsContext,GraphicsContext 会负责输出该层的位图,位图是存储在共享内存中,作为纹理上传到 GPU 中,最后由 GPU 将多个位图进行合成,然后 draw 到屏幕上,此时,我们的页面也就展现到了屏幕上。

原文链接:https://blog.csdn.net/qq_45803168/article/details/137642530