美文网首页
像素的一生

像素的一生

作者: 奔跑的兔子_ | 来源:发表于2019-01-22 19:23 被阅读0次

    本文我们主要讲的是从web content到屏幕显示像素的前因后果。

    网页内部或web应用程序前端的所有代码,我们统称为content。content首先包括HTML、css、js,还包括其他的东西,比如视频图片,音频、webassembly、webGL、Canvas、PDF,本文主要关注HTML。

    一个真正的网页可以有数千行的HTML CSS和JS,通过网络以纯文本的形式提供,这一过程并没有继承其他平台编译或者打包的概念。意味着web应用程序的源代码实际上是渲染流的输入。

    john说过,渲染是发生在沙箱进程中的,这是Chrome安全模块的关键。Webcontents封装了创建和管理的渲染进程,我们要讨论的渲染器进程中所有的实际渲染,都发生在这一个加载的网页中。Blink是我们渲染引擎的名字,实际上Blink是Content层下面的渲染器过程中代码的子集,Blink内部的代码和项目其他部分的代码有的地方有点混淆,是因为Blink曾经是苹果Webkit引擎的一个分支,但基本上你可以把Blink认为是我们队web平台语义的实现,另一方面我们必须使用低层操作系统提供的图形库,将像素显示到屏幕上,现在大部分平台上有一个叫OpenGL的API标准,Windows系统中有时还需要额外转换成DirectX,未来我们还会支持更加新的API,比如Vulkan,这些库提供了底层的图形学基本图形,比如纹理和着色器,可以让你做很多事情,比如把这些坐标上的多边形绘制到虚拟像素的缓冲区中,他们显然不知道什么Web、HTML、CSS。

    现在我们知道了学习这些东西的动机和目标,渲染的总体可以表现为将WebContent也就是HTML、CSS、JS,转换为正确的openGL调用,并在屏幕上显示相应像素,我们的第二个目标,我们希望生成正确的中间数据结构之后,能有效地做渲染更新,并响应来自脚本和系统的其他部分的查询,我们要介绍的是一个流程管道或者说生命周期,当然是从Content开始到呈现像素结束,但它被分成了几个阶段,会把Content变成某些东西,然后又变成别的东西,诸如此类,因为渲染机制太复杂而没有办法单纯表述为一个操作,虽然那些中间的数据结构,能让我们在以后有效地更新渲染。但是并不是每一个更新都需要运行全过程的。等我们讲完流程管道的第一层含义,我再回来说渲染更新的概念,并介绍一些能够帮助我们优化它的新知识点。

    首先我们看第一个阶段,在Blink代码的渲染进程中,从网络获取的第一个资源通常是HTML,页面还会去加载其他类型的资源CSS JS 图片等等,但在最初的响应中,HTML已经直接或间接地引入了这些内容,所以我们渲染的起点是HTML解析器,它接收标签与文本组成的流,HTML标签给文档赋予了语义上的层级结构。

    渲染的第一步就是解析标签,生成能反映该结构的对象模型,我们称之为文档对象模型,简称DOM,DOM属于计算机术语中的树,它是上下颠倒的。在我们引擎中会遇到很多的树,这是第一棵,之所以都是树,是因为他们都直接或间接基于DOM,而DOM又基于HTML结构。DOM树的节点成为DOM节点,DOM有两个作用,一是作为页面的内部表示,二是将查询或修改渲染的API暴露给JS。所以V8引擎用了一个绑定系统,将真实DOM树做一层简单包装后暴露为DOM的WebAPI,现在DOM树就构建完成了,接下来看看CSS样式。

    image.png

    我们知道CSS是有选择器和属性的,CSS选择器的选择范围是其属性声明所作用于的DOM节点的子集。 现在样式属性不仅越来越多,怎样确定样式规则实际运用于哪些元素也越来越复杂,有的被选元素可以同时被多条规则影响,各规则对特定样式属性的声明甚至会互相冲突,所以样式引擎的任务是对所有的这些进行整理,当遇到一个样式表的时候,我们将css文本解析成样式规则的对象模型具有丰富的选择器和属性值映射的表述能力,样式规则被以各种方式索引以便进行有效查找,值得注意的另一点是实现各个样式属性的C++类比如这个BorderLeftColor类,是在构建时由Python脚本自动生成的。

    第二个我们必须弄清楚的地方是,样式是怎样作用于dom元素的,也就是样式分析和样式重算,在这个阶段,我们从文档的有效样式表中,获取所有已解析的样式规则,包括浏览器提供的一组默认样式,然后计算出每个Dom元素样式属性的最终值,储存在ComputedStyle(计算样式)对象模型中,它就是一个样式属性与值映射的超大的map,ComputedStyle对象会挂载元素,算出它是红的、斜体、有2英寸外边距之类,这就是样式引擎的输出。

    现在我们已经构建好了dom,也计算完了所有的样式,下一步是确定所有元素的视觉几何结构,对块级元素,我们要计算出一个矩形的坐标,这个矩形与元素内容所占据的几何区域相对应。

    在最简单的例子中,布局只是按照DOM中的顺序,一个接一个垂直地放置区块,区块随着页面流动出现,所以称之为区块流,即使是这种简单情况,也有相当大的复杂性,因为每个区块的位置由前一区块的高度决定,而想要获取区块的高度,就必须找到文本的换行标记,需要通过计算样式中的字体来测量运行时文本渲染尺寸,这些都属于布局的一部分,布局通常为单个图形对象计算多种边界矩形,假设一个元素DOM的子元素或内容,比该元素所声明的边框盒大,这种情况我们称之为溢出,布局必须跟踪边框盒矩形与布局溢出矩形的情况,有趣的是元素溢出后可以设置成可滚动的,这时还要计算滚动边l界,并为滚动条留出空间,所以也是布局上的另一个副作用,最常见的可以滚动的dom节点,当然就是文档本身,他是dom树的根,但也可以通过css,把任意的dom节点设置为可滚动。


    --

    还有一些情况需要更复杂的布局,比如表格元素或特定元素的样式,有时要将内容分成多列,有时内容要在左浮动布局对象周围环绕显示,有时东亚的文本要显示成竖排的,不过请注意在各种情况下 dom结构和诸如float:left这样的计算样式值,是怎样作为布局算法的输入的,所以流程管道中的各个阶段,都会将上一阶段的输出作为输入并产生影响后续阶段的输出。所有的布局信息保存在另一个与dom关联的树结构中,我们称之为布局树,树中实现了布局算法,所以这有很多布局类 LayoutBox LayoutInline LayoutTable 使用哪个取决于元素需要用到什么布局算法,它们全都继承自layoutObject这个公共基类,所以布局树中的每个节点都是一个LayoutObjective对象。


    image.png

    DOM节点与布局对象基本上是一一对应的,但是有一些例外,假设你给一个节点设置了display:none,他并不会创建布局对象,有时候还存在没有节点的布局对象,在某些怪异的情况下,一个节点甚至可能有不止一个布局对象,所以当我们谈论dom节点和布局对象时,有时会假设他们有直接对应关系,但要注意并不是总是如此。在布局阶段会遍历整个布局树,实际上在样式重新计算完毕之后,还有另一个阶段,即构造布局树。在初次构造布局树 还没有被填入任何几何位置数据,我们的布局更新方法会遍历布局树,填充所有几何位置数据,并处理实际的副作用,比如计算溢出,查找换行标记和设置滚动容器等。

    当前体系结构的一个问题是,布局对象包含布局阶段的输入和输出,却没有清晰地把他们区分开,比如说layoutObject对象在创建之后,实际上需要获取其元素的计算样式的所有权,因此,新布局系统layoutNG Ng表示下一代 试图更清晰地分离输入和输出,让设计新的布局算法更简单,现在还在开发中。

    现在我们已经得到了布局对象的几何位置信息,可以开始绘制了,paint这一过程,把所有的绘制操作,记录到待显示项目的列表中,绘制操作差不多是像在指定坐标内画一个红色的矩形这样的动作,每个布局对象都对应有多个与其视觉外观相对关的待显示项,例如背景、前景、轮廓等不同部分,注意这只是在记录绘制操作,但目前还没真正的执行,后续还能重放这些操作,后面会说明这样做的用处。

    若想让元素在重叠时正确堆叠,就必须按正确的顺序绘制元素,绘制是按照层叠顺序进行的,与dom顺序略有不同,受控于样式属性中的z-index。z-index大的后绘制,yellow在green的前面,但却在green后才开始绘制。甚至可能出现一个元素的一部分在另一个元素前面,另一部分在它后面,那是因为绘制会分多个阶段运行,每个绘制阶段都在路径下自行遍历子树,也就是我们说的层叠上下文。


    image.png

    相关文章

      网友评论

          本文标题:像素的一生

          本文链接:https://www.haomeiwen.com/subject/gzsbrqtx.html