家资是何物,积帙列梁梠。这篇文章主要讲述像素的一生相关的知识,希望能为你提供帮助。
提到浏览器不得不说Chrome,Chrome是Google发行的商业产品,而Chromium是一个开源版本的Chrome,两者很像但是不完全一样。
写这篇文章是想追忆像素的由来,我们且从chrome入手,窥探其内核是如何将web内容转换为像素。
渲染事实上这个转换的过程就是渲染,网页的渲染可以表示为??Content?
?经过??rendering?
?最后呈现的过程,即*Code ->
可交互的页面*
简单的说浏览器作为应用,底层分别有content,Blink,V8,Skia等等,一层一层像套娃一样一层引用一层。对比普通应用的项目来说就是不断用第三方库和组件来拼凑应用,Chrome也不例外
何为content可以看到content就是WebContents对象,C++代码的一个类。其代表的区域其实是标签页页打开的部分(即下图红色部分)。而浏览器主进程还包含有地址栏、导航按钮、菜单、扩展,安全提示的小弹窗等等。
在Chrome中其安全模型实现的关键:渲染发生在沙盒进程中。这么做的好处在于当渲染进程??render process?
?挂了不会引起整个浏览器停止服务
渲染进程??render process?
?包含Blink渲染排版引擎和??Chromium compositor?
?(图中绿色的CC简写)
作为content来说,其基本构建块是文本、图像、标记(围绕文本)、样式(定义标记的呈现方式)和脚本(可以动态修改上述所有内容)。
当然了,其他类型的内容以特殊的方式呈现比如???video?
??, ??canvas?
??, ??WebAssembly?
??, ??WebGL?
??, ??WebVR?
??, ??PDF?
?, ...,这里不做讨论。
在古早时期,当时的网页只是通过网络以纯文本形式提供的数千行 html、CSS 和 javaScript。
那个时候没有编译或打包的概念,然而这种简单性是网络早期成功的关键。
综上,content就是网页代码最后运行的结果,浏览器开发者工具可以看到最后是一个经过处理后的HTML的结构。而这个HTML在渲染流水线里是一个输入:)
像素的一生像素??pixels?
?到底是怎么出现的呢?为何一串简简单单普普通通的代码可以获的满屏的色彩,彷佛一念花开,一念世界。
写过C/C++代码的同学知道,我们必须使用操作系统提供的底层API去画图,通过操作系统底层去调用驱动程序,令驱动程序驱动硬件将图形库的像素放到屏幕上。
今天大多数平台上都提供了???OpenGL?
??的标准化API。在Windows上有一个额外的??DirectX?
?转换。这些库提供诸如“纹理”和“着色器”之类的低级图形基元,并允许执行类似“在这些坐标处绘制一个三角形到虚拟像素缓冲区”之类的底层操作。未来计划用Vulkan替代Skia来做底层图形化调用。
因此渲染流水线的整个过程就是将输入的HTML、CSS、JS转化为OpenGL调用,最后在屏幕上呈现像素
像素的意义简单来说,像素就是为了可以更加舒服的表达自身的意义,在此认为像素意义在于两种渲染:
随着时间推移,每个渲染阶段的结果会为了提高渲染效率而被缓存下来。此外还有JS API会查询一些渲染数据如某个DOM节点的信息
渲染阶段我们不妨将把渲染管道分成多个阶段,每个阶段都是像素生命周期的一个环节,从图中可以看出原来的??content?
?内容会被各个阶段??stage?
?处理为中间数据,最后才呈现为画面呈现出来。
parsing
HTML 标签在文档上强加了一个语义上有意义的层次结构。 例如,一个??<
div>
?
?可能包含两个段落,每个段落都有文本。 所以第一步是解析这些标签来构建一个反映这种结构的对象模型。
DOM
我们常说DOM树的原因,通过一层层铺垫的结构,形似一棵树
其是???JavaScript?
??操作网页的接口,全称为文档对象模型??Document Object Model?
?。我们主要关注三个概念:文档、元素、节点
因此常用的操作DOM的五种方法:
DOM(Document Object Model)本质上是一棵树,树有父子,邻居的关系,而且这棵树是暴露API给JS调用,JS可以查询和修改这棵树。JS引擎V8通过??bindings?
?的系统将DOM包装为DOM API供给Web开发者调用
在生产、学习的过程中,我们不可避免的需要在同一份文档中夹带多份DOM树,树多了就成了森林,对于森林的处理则是采用影子树???shadow tree?
?的形式对当前的DOM树进行套娃。
还记得我们在使用入vue中经常会采用的一种特性,??v-slot?
?,其本质上就是应用了影子树,
如下图的示例,自定义元素custom element有shadow tree。ShadowRoot的子元素其实被嵌入到slot元素里了
本质上最后是在遍历树后合成视图,也就是两棵树合并为一棵树
style
构建 DOM 树后,下一步是处理 CSS 样式。CSS 选择器选择其属性声明应应用于的 DOM 元素子集。
![image](E91BB520A37C4614AA7C8C8BB491BF78)
通过style这个属性,我们可以对像素进行各种个性化处理,如旋转跳跃、浮动变色、黯淡闪现等等,当然了这些属性也不能太浪,有可能会出现一些使用上的冲突,因此现在前端工程中定义了一种新的专门的编程语言,可以为CSS增加一些编程的特性,编译后成正常的CSS文件。具有无需考虑浏览器的兼容问题,让CSS更加简洁,适应性更强,可读性更佳,更易于代码的维护等诸多好处。
目前常见的css的预处理器
实现原理
CSS 解析器根据每个活动样式表构建样式规则模型。
样式规则以各种方式索引以进行有效查找。
如上图所示属性类在构建时由python脚本自动生成,以声明方式定义了所有样式属性,如右上侧??css_properties.json?
?经过py脚本转化为??.cc?
?文件
样式表可能位于项目工程中???<
style>
?
?元素、单独加载的资源 (styles.css) 或由浏览器提供。
样式解析(或重新计算)从活动样式表中获取所有已解析的样式规则,并计算每个 DOM 元素的每个样式属性的最终值。 这些存储在一个名为???ComputedStyle?
? 的对象中,本质上它只是从样式属性到值的映射。
我们可以在开发者工具中发现所有 DOM 元素的???ComputedStyle?
??。它也暴露在??Javascript?
??中。 这些都是基于??Blink?
??的??ComputedStyle?
??对象,注意到有时候一些属性增加了布局??layout?
??数据。我们还可以通过??getComputedStyle?
??的??JSAPI?
?去获取。
layout
在构建了 DOM 并计算了所有style之后,下一步是确定所有元素的视觉几何形状。
对于这个块级元素,我们正在计算一个矩形的坐标,该矩形对应于该元素占据的内容区域的几何区域,如计算??x?
?,??y?
?,??width?
?,??height?
?这些数据
在最简单的情况下,布局按 DOM 顺序一个接一个地放置块,垂直下降。 我们称之为“块流”。
文字和内联元素如???<
span>
?
?则是左右浮动的,而且内联元素会被行尾打断(自动换行)。当然也有从右到左的语言,比如阿拉伯语和希伯来语
当然了布局也包括字体的排列,因为布局需要考虑文本在那里进行换行, ???Layout?
??
?使用名为 ??HarfBuzz?
??
?的开源文本库来计算每个字形的大小和位置,这决定了文本的总宽度。字体成型必须考虑到排版特征,如字距调整 ??letter-spacing?
??
?和连字。
布局可以计算单个元素的多种边界矩形。例如,当存在溢出时, ???Layout?
??
?将同时计算边界框和布局溢出。如果节点的溢出是可滚动的, ??Layout?
?
还会计算滚动边界并为滚动条预留空间。最常见的可滚动DOM节点是文档本身
表格元素或样式需要更复杂的布局,这些元素或样式指定诸如将内容分成多列、位于一侧且内容在其周围流动的浮动对象、或文本垂直而不是水平排列的东亚语言。
请注意 DOM 结构和 ???ComputedStyle?
?
?值(如“float: left”)如何作为布局算法的输入。
此外渲染流水线的每个阶段都会使用到前面阶段的结果
![image](54929B36E302485FBC111030C450CC73)
通过遍历DOM树创建渲染树???LayoutTree?
??,节点一一对应。布局树中的节点实现布局算法。根据所需的布局行为,??LayoutObject?
??有不同的子类。比如??LayoutBlockFlow?
??就是块级??Flow?
?的文档节点。样式更新阶段也构建布局树。
在样式解析最后结束时需要构建布局树??LayoutTree?
?,布局阶段遍历布局树,对布局树每个节点??LayoutObject?
?执行布局,计算几何数据、换行符,滚动条等。
??DOM?
?节点跟??Layout?
?节点不一定是一一对应
一般情况下一个DOM节点会有一个LayoutObject,但是有时候 ???LayoutObject?
??
?是没有DOM节点与之对应的。
比如上图,span标签外部没有section标签嵌套,但是LayoutTree会自动创建 ???LayoutBlock?
??
?的匿名节点与之对应,再比如样式有 ??display:none?
??
?的样式,那么也不会创建对应的 ??LayoutTree?
??
。
最后,如果是 ???shadowTree?
??
?的话,其 ??LayoutObject?
??
?节点可能会在不同的 ??TreeScope?
?
里
layout引擎的未来??LayoutNG?
?代表下一代的布局引擎,2020年布局引擎还在过渡阶段,所以有中间形态,如上图包含了??LayoutObject?
?和??LayoutNGMixin?
?混合节点。未来所有节点都会变成??LayoutNG?
?的??layout object?
?
NG节点的更新主要是因为之前的节点包含了输入、输出还有布局算法的信息,也就是说单个节点可以看到整棵树的状态(节点有可能需要获取父节点的宽高数据,但是父节点正在递归子节点布局中,实际上还没确定最后的布局)。
而新的NG节点对输入和输出做了明显的区分,而且输出是???immutable?
?的,可以缓存结果
布局结果指向描述物理几何的片段树,如图一个???NGLayoutResult?
??对应几??个NGPhysicalFragment?
??,对应右上角的几个矩形图形,如果??NGLayoutResult?
?没变化则对应整块都不会变化。
实例大家且看这段代码会渲染出什么效果
<
div style="max-width: 100px">
<
div style="float: left;
padding: 1ex">
F<
/div>
<
br>
The <
b>
quick brown<
/b>
fox
<
div style="margin: -60px 0 0 80px">
jumps<
/div>
<
/div>
如图所示
其对应的DOM树如下图所示
那如果用???LayoutTree?
??来表示呢?其实Layout树和DOM树很像,节点几乎是一一对应的,但是注意这里??anonymous?
?匿名节点被创建出来,它只有一个块级子元素。一个布局节点只能拥有块级元素或者内联元素其中之一
图中的子元素前面两个其实共享了匿名??LayoutNGBlockFlow?
?,也就是说有共同的父节点
paint
绘制??paint?
?阶段创建绘制指令列表??paint ops list?
?
绘制指令??paint op?
?可以理解为在某些坐标用什么颜色画一个矩形类似的意思,
每个布局对象??LayoutObejct?
?可以有多个显示项目,对应于其视觉外观的不同部分,如背景、前景、轮廓等
正确的绘制顺序非常重要,这样当元素重叠时,它们才能正确堆叠。顺序可以由样式控制,而不是完全依靠DOM的先后顺序
每个绘制阶段???paint phase?
??都需单独遍历堆叠上下文??staking context?
?。
一个元素甚至可能部分位于另一个元素的前面,部分位于另一个元素的后面。这是因为绘制在多个阶段中运行,每个绘制阶段都对自己的子树进行遍历。
例子且看这段代码渲染出来的效果
<
style>
#p
position: absolute;
padding: 2px;
width: 50px;
height: 20px;
left: 25px;
top: 25px;
border: 4px solid purple;
background-color: lightgrey;
<
/style>
<
div id=p>
pixels <
/div>
我们不妨分析一下这个指令的解析过程,一个样式和DOM节点渲染出来的结果,包含了四个绘制指令paint ops:
?document?
?背景色绘制
文本绘制操作包含文本块的绘制,其中包含每个字的字符和偏移量以及字体。如图这些数据都是HarfBuzz计算后得到的???raster?
?
中文说的栅格化或者光栅化,本文取PS图层右键的栅格化为译文。熟悉PS的会知道矢量图形栅格化后放大图形会"糊"是不做栅格化处理直接放大矢量图形则不会。原因就是栅格化后只记录了单像素点的??rgba?
?值,放大后本来一个点数据要填满N个点,图像就"糊"
raster
??raster?
??
?将绘制指令转化为位图,可以把显示列表里的绘制操作执行的过程,成为任务,也称栅格化。比如PS里的合并图层任务,主要区别就是本来矢量的图任务后会变成位图 ??bitmap?
??
?,后面再缩放就会模糊。
生成的位图 ???bitmap?
??
?中的每个单元格都包含对单个像素的颜色和透明度进行编码的位。这里用十六进制 ??FFFFFFFF?
??
?表示一个点的 ??rgba?
??
值
其还对嵌入在页面中的图像资源进行解码。 绘制操作引用压缩数据(JPEG、PNG 等),然后 raster 调用适当的解码器对其进行解压缩。
GPU加速【像素的一生】GPU还可以运行生成位图的命令(“加速栅格化”)。请注意,此时这些像素还没有出现在屏幕上
???raster?
?
?产生的位图数据存储在GPU内存中,通常是OpenGL纹理对象引用的GPU内存。
过去通常是存在内存里再传给GPU,但是现代GPU可以直接运行着色器shader并在GPU上生成像素,这种情况称为“加速栅格化”。但是两个结果都是一致的,最终内存(主存或者GPU内存)里拥有位图 ???bitmap?
?
?
raster通过Skia发出GL调用
推荐阅读
- 实战篇(MySQL高可用--MGR实战)
- [ 数据结构 -- 手撕排序算法第一篇 ] 插入排序
- [OpenCV实战]13OpenCV中使用Mask R-CNN进行对象检测和实例分割
- PassJava 开源项目(二十)之 详解 Elasticsearch 高级检索玩法
- 树莓派组建 k8s 集群(centos版)
- Stream流的前世今生
- zabbix-微信报警
- ubuntu 20.04安装搜狗输入法
- 优维低代码(从构件开始,解析EasyMABuilder)