网站后台被百度蜘蛛抓取,建设银行官网登录,深圳建设项目环保网站办事指南,做电子商务网站 费用浏览器渲染更新过程 文章目录 浏览器渲染更新过程帧维度解释帧渲染过程一些名词解释Renderer进程GPU进程rendering(渲染) vs painting(绘制)⭐位图纹理Rasterize(光栅化) 1. 浏览器的某一帧开始#xff1a;vsync2. Input event handlers3. requestAnimationFrame4. 强制重排(可…浏览器渲染更新过程 文章目录 浏览器渲染更新过程帧维度解释帧渲染过程一些名词解释Renderer进程GPU进程rendering(渲染) vs painting(绘制)⭐位图纹理Rasterize(光栅化) 1. 浏览器的某一帧开始vsync2. Input event handlers3. requestAnimationFrame4. 强制重排(可能存在)5. parse HTML(构建DOM树)6. 计算样式6.1 把CSS转换为浏览器能够理解的结构6.2 转换样式表中的属性值使其标准化6.3 计算出DOM树中每个节点的具体样式 7. 构建Render Tree(渲染树)的流程8. Layout(重排reflow)构建布局树9. 分层、合成层(或者叫update layer tree)9.1 分层9.2 update layer tree9.3 补充解释Render Object、Render Layer、Graphics Layer(又称Compositing Layer)和Graphics ContextRender ObjectRender LayerGraphics Layer(又称Compositing Layer)和Graphics Context 使用 合成层提升 减少重绘重排使用transform和opacity书写动画will-changeCanvas paint(图层绘制)重绘分成图块 栅格化raster操作draw引用 之前阅读过李兵老师的《浏览器工作原理与实践》但还是对其中有些概念模糊于是趁着国庆对浏览器渲染更新原理进行梳理。本篇只是对一些优秀资料的总结及自己的理解如果时间充裕建议阅读本文最后的引用。 先放图 帧维度解释帧渲染过程
在一个流畅的页面变化效果中动画或滚动渲染帧指的是浏览器从js执行到paint的一次绘制过程帧与帧之间快速地切换由于人眼的残像错觉就形成了动画的效果。那么这个“快速”要达到多少才合适呢 我们都知道下层建筑决定了上层建筑。受限于目前大多数屏幕的刷新频率——60次/s浏览器的渲染更新的页面的标准帧率也为60次/s–60FPS(frames/per second)。
高于这个数字在一次屏幕刷新的时间间隔16.7ms(1/60)内就算浏览器渲染了多次页面屏幕也只刷新一次这就造成了性能的浪费。低于这个数字帧率下降人眼就可能捕捉到两帧之间变化的滞涩与突兀表现在屏幕上就是页面的抖动大家通常称之为卡顿
来个比喻。快递每天整理包裹并一天一送。如果某天包裹太多整理花费了太多时间来不及当日帧送到收件人处那就延期了丢帧。 标准渲染帧 在一个标准帧渲染时间16.7ms之内浏览器需要完成Main线程的操作并commit给Compositor进程 丢帧 主线程里操作太多耗时长commit的时间被推迟浏览器来不及将页面draw到屏幕这就丢失了一帧 一些名词解释 Renderer进程
Main线程浏览器渲染的主要执行步骤包含从JS执行到Composite合成的一系列操作。负责解析html css 和主线程中的js我们平时熟悉的那些东西诸如Calculate Style,Update Layer Tree,Layout,Paint,Composite Layers等等都是在这个线程中进行的。 总之就是将我们的代码解析成各种数据直到能被合成器线程接收去做处理。Compositor(合成)线程 接收一个vsync信号表示这一帧开始接收用户的一些交互操作(比如滚动) 然后commit给Main线程唤起Main线程进行操作接收Main线程的操作结果将图层划分为图块tile并交给栅格化线程拿到栅格化线程的执行结果它的结果就是一些位图commit给真正把页面draw到屏幕上的GPU进程 Compositor Tile Work(s)线程Compositor调起Compositor Tile Work(s)来辅助处理页面。Rasterize意为光栅化。这里的 Tile 其实就是位图的意思(下文会详细说明)合成线程会将图层划分为图块tile生成位图的操作是由栅格化来执行的。栅格化线程不止一个可能有多个栅格化线程。 GPU进程
整个浏览器共用一个。主要是负责把Renderer进程中绘制好的tile位图作为纹理上传至GPU并调用GPU的相关方法把纹理draw到屏幕上。GPU进程里只有一个线程GPU Thread。 这里其实只需要知道GPU进程把 render进程的结果 draw 到 页面上。 rendering(渲染) vs painting(绘制)⭐
这里的 painting 也可以理解成上面的 draw火焰图中也会出现这两个关键词。 我们可以想象成 除了浏览器之外还有一个后台工人浏览器使用双缓冲始终有两张图
rendering 渲染后台工人画的过程这里就是 浏览器的render进程painting 绘制当后台工人画好后往浏览器页面上放的过程GPU进程负责将画好的东西paint(draw)到浏览器上 后台工人先render一张render完毕后把浏览器的那张图替换下来叫paint(draw)然后后台工人又开始在替换下来的那张图上进行render 浏览器每一帧会替换一次保证动画是连续的很像动画那样一帧一帧 位图 就是数据结构里常说的位图。你想在绘制出一个图片你应该怎么做显然首先是把这个图片表示为一种计算机能理解的数据结构用一个二维数组数组的每个元素记录这个图片中的每一个像素的具体颜色。所以浏览器可以用位图来记录他想在某个区域绘制的内容绘制的过程也就是往数组中具体的下标里填写像素而已。 纹理
纹理其实就是GPU中的位图存储在GPU video RAM中。前面说的位图里的元素存什么你自己定义好就行是用3字节存256位rgb还是1个bit存黑白你自己定义即可但是纹理是GPU专用的GPU和CPU是分离的需要有固定格式便于兼容与处理。所以一方面纹理的格式比较固定如R5G6B5、A4R4G4B4等像素格式 另外一方面GPU 对纹理的大小有限制比如长/宽必须是2的幂次方最大不能超过2048或者4096等。
总结render进程中的叫位图GPU进程中的叫纹理生成位图(纹理)的这个过程叫栅格化ok过… Rasterize(光栅化) 在纹理里填充像素不是那么简单的自己去遍历位图里的每个元素然后填写这个像素的颜色的。就像前面两幅图。光栅化的本质是坐标变换、几何离散化然后再填充。 同时光栅化从早期的 Full-screen Rasterization基本都进化到了现在的Tile-Based Rasterization 也就是不是对整个图像做光栅化而是把图像分块(tile亦有翻译为瓦片、贴片、瓷片…)后再对每个tile单独光栅化。光栅化好了将像素填充进纹理再将纹理上传至GPU。 原因一方面如上文所说纹理大小有限制即使你整屏光栅化也是要填进小块小块的纹理中不如事先根据纹理大小分块光栅化后再填充进纹理里。另一方面是为了减少内存占用(整屏光栅化意味着需要准备更大的buffer空间)和降低总体延迟分块栅格化意味着可以多线程并行处理。 看到下图中蓝色的那些青色的矩形了吗他们就是tiles。 可以想见浏览器的一次绘制过程就是先把想绘制的内容如文字、背景、边框等通过分块Rasterize绘制到很多纹理里再把纹理上传到gpu的存储空间里gpu把纹理绘制到屏幕上。 上面balabala说了一大堆看得懂就看看不懂就直接看总结… 所以什么是光栅化光栅化本质也是生成位图(纹理)不过会先分块然后对每一块进行生成位图这个分块的过程是由合成线程实现的生成位图的过程是栅格化线程实现的。为什么要先分块再栅格化而不直接对整块屏幕做栅格化为了减少内存占用和多线程处理(那这就意味着栅格化线程不止一个可能有多个栅格化线程)。
名词解释完了开始详细介绍浏览器渲染的每一步。再次摆出整个渲染流程图。 或者另外一张类似的流程图 1. 浏览器的某一帧开始vsync
Compositor(合成)线程接收一个vsync信号表示这一帧开始 2. Input event handlers
Compositor线程接收用户的交互输入比如touchmove、scroll、click等。然后commit给Main线程这里有两点规则需要注意
并不是所有event都会commit给Main线程部分操作比如单纯的滚动事件打字等输入不需要执行JS也没有需要重绘的场景Compositor线程就自己处理了无需请求Main线程同样的事件类型不论一帧内被Compositor线程接收多少次实际上commit给Main线程的只会是一次意味着也只会被执行一次。HTML5标准里scroll事件是每帧触发一次所以自带了相对于动画的节流效果scroll、resize、touchmove、mousemove等事件由于Compositor Thread的机制原因都会每一帧只执行一次 3. requestAnimationFrame
window.requestAnimationFrame() 这个方法既然已经说明了它是一个方法那它一定是在 JavaScript 中执行的。 4. 强制重排(可能存在)
Avoid large, complex layouts and layout thrashing 下面对这个引用文章进行解释 这里本来已经走到了我们熟知的浏览器渲染过程 js修改dom结构或样式 - 计算style - layout(重排) - paint(重绘) - composite(合成) 首先运行 JavaScript然后运行样式计算最后运行布局。然而可以使用 JavaScript 强制浏览器提前执行布局。这称为强制同步布局。 接下来解释 强制重排也叫强制同步布局。 首先要记住的是当 JavaScript 运行时前一帧中的所有旧布局值都是已知的可供您查询。因此例如如果您想在帧的开头写出元素我们称之为“盒子”的高度您可以编写如下代码
// Schedule our function to run at the start of the frame:
requestAnimationFrame(logBoxHeight);function logBoxHeight () {// Gets the height of the box in pixels and logs it out:console.log(box.offsetHeight);
}如果您在询问框的高度_之前_更改了框的样式则会出现问题
function logBoxHeight () {box.classList.add(super-big);// Gets the height of the box in pixels and logs it out:console.log(box.offsetHeight);
}现在为了回答高度问题浏览器必须_首先_应用样式更改因为添加了super-big类_然后_运行布局。只有这样它才能返回正确的高度。这是不必要且可能昂贵的工作。这就是强制重排。 强制重排意思是可能会在JS里强制重排当访问scrollWidth系列、clientHeight系列、offsetTop系列、ComputedStyle等属性时会触发这个效果导致Style和Layout前移到JS代码执行过程中 浏览器有自己的优化机制包括之前提到的每帧只响应同类别的事件一次再比如这里的会把一帧里的多次重排、重绘汇总成一次进行处理。 flush队列是浏览器进行重排、重绘等操作的队列所有会引起重排重绘的操作都包含在内比如dom修改、样式修改等。如果每次js操作都去执行一次重排重绘那么浏览器一定会卡卡卡卡卡所以浏览器通常是在一定的时间间隔一帧内批量处理队列里的操作。但是对于有些操作比如获取元素相对父级元素左边界的偏移值Element.offsetLeft但在此之前我们进行了样式或者dom修改这个操作还攒在flush队列里没有执行那么浏览器为了让我们获取正确的offsetLeft虽然之前的操作可能不会影响offsetLeft的值就会立即执行队列里的操作。 所以我们知道了就是这个特殊操作会影响浏览器正常的执行和渲染假设我们频繁执行这样的特殊操作就会打断浏览器原来的节奏增大开销。 而这个特殊操作具体指的就是
elem.offsetLeft, elem.offsetTop, elem.offsetWidth, elem.offsetHeight, elem.offsetParentelem.clientLeft, elem.clientTop, elem.clientWidth, elem.clientHeightelem.getClientRects(), elem.getBoundingClientRect()elem.scrollWidth, elem.scrollHeightelem.scrollLeft, elem.scrollTop…
更多会触发强制重排的属性See moreWhat forces layout / reflow 5. parse HTML(构建DOM树)
如果有DOM变动那么会有解析DOM的这一过程。 6. 计算样式
样式计算的目的是为了计算出DOM节点中每个元素的具体样式这个阶段大体可分为三步来完成
6.1 把CSS转换为浏览器能够理解的结构
那CSS样式的来源主要有哪些呢你可以先参考下图 从图中可以看出CSS样式来源主要有三种
通过link引用的外部CSS文件style 标记内的 CSS元素的style属性内嵌的CSS和HTML文件一样浏览器也是无法直接理解这些纯文本的CSS样式所以当渲染引擎接收到CSS文本时会执行一个转换操作将CSS文本转换为浏览器可以理解的结构——styleSheets。为了加深理解你可以在Chrome控制台中查看其结构只需要在控制台中输入document.styleSheets然后就看到如下图所示的结构 从图中可以看出这个样式表包含了很多种样式已经把那三种来源的样式都包含进去了。当然样式表的具体结构不是我们今天讨论的重点你只需要知道渲染引擎会把获取到的CSS文本全部转换为styleSheets结构中的数据并且该结构同时具备了查询和修改功能这会为后面的样式操作提供基础
6.2 转换样式表中的属性值使其标准化
现在我们已经把现有的CSS文本转化为浏览器可以理解的结构了那么接下来就要对其进行属性值的标准化操作。 要理解什么是属性值标准化你可以看下面这样一段CSS文本
body { font-size: 2em }
p {color:blue;}
span {display: none}
div {font-weight: bold}
div p {color:green;}
div {color:red; }可以看到上面的CSS文本中有很多属性值如2em、blue、bold这些类型数值不容易被渲染引擎理解所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值这个过程就是属性值标准化。 那标准化后的属性值是什么样子的 从图中可以看到2em被解析成了32pxred被解析成了rgb(255,0,0)bold被解析成了700……
6.3 计算出DOM树中每个节点的具体样式
现在样式的属性已被标准化了接下来就需要计算DOM树中每个节点的样式属性了如何计算呢 这就涉及到CSS的继承规则和层叠规则了。 首先是CSS继承。CSS继承就是每个DOM节点都包含有父节点的样式。这么说可能有点抽象我们可以结合具体例子看下面这样一张样式表是如何应用到DOM节点上的
body { font-size: 20px }
p {color:blue;}
span {display: none}
div {font-weight: bold;color:red}
div p {color:green;}这张样式表最终应用到DOM节点的效果如下图所示 从图中可以看出所有子节点都继承了父节点样式。比如body节点的font-size属性是20那body节点下面的所有节点的font-size都等于20。 为了加深你对CSS继承的理解你可以打开Chrome的“开发者工具”选择第一个“element”标签再选择“style”子标签你会看到如下界面 这个界面展示的信息很丰富大致可描述为如下
首先可以选择要查看的元素的样式位于图中的区域2中在图中的第1个区域中点击对应的元素元素就可以了下面的区域查看该元素的样式了。比如这里我们选择的元素是标签位于html.body.div.这个路径下面其次可以从样式来源位于图中的区域3中中查看样式的具体来源信息看看是来源于样式文件还是来源于UserAgent样式表。这里需要特别提下UserAgent样式它是浏览器提供的一组默认样式如果你不提供任何样式默认使用的就是UserAgent样式。最后可以通过区域2和区域3来查看样式继承的具体过程。
以上就是CSS继承的一些特性样式计算过程中会根据DOM节点的继承关系来合理计算节点样式。 样式计算过程中的第二个规则是样式层叠。层叠是CSS的一个基本特征它是一个定义了如何合并来自多个源的属性值的算法。它在CSS处于核心地位CSS的全称“层叠样式表”正是强调了这一点。关于层叠的具体规则这里就不做过多介绍了网上资料也非常多你可以自行搜索学习 总之样式计算阶段的目的是为了计算出DOM节点中每个元素的具体样式在计算过程中需要遵守CSS的继承和层叠两个规则。这个阶段最终输出的内容是每个DOM节点的样式并被保存在ComputedStyle的结构内。 7. 构建Render Tree(渲染树)的流程
从DOM树的根开始遍历每个可见节点。 一些节点不可见例如脚本标签meta标签等由于它们未反映在输出中因此将其省略。一些节点通过CSS隐藏在渲染树中也被省略。注意visibility: hidden有所不同于display: none。 对于每个可见节点找到合适的CSSOM规则并应用它们。输出每个可见节点具有的内容及其样式。 最终产出一个Render Tree其中包含屏幕上所有可见内容的内容和样式信息。 浏览器已经计算了哪些节点应该可见以及它们的样式但是还没有计算它们在设备视口中的确切位置和大小这是layout阶段该做的事也称为“重排” Render Tree中储存节点渲染信息的对象叫做Render Object这个概念需要留意下面会用到
8. Layout(重排reflow)构建布局树
我们已经知道了DOM节点的大小但是还不知道它在页面上的具体位置这一步就是构建布局树也叫重排。 主线程遍历Render Tree并创建布局树该树具有诸如xy坐标和边界框大小之类的信息。布局树的结构可能与DOM树类似但它仅包含与页面上可见内容有关的信息。如果应用display: none则该元素不属于布局树但是具有visibility: hidden的元素在布局树中。同样如果应用了具有类似的伪类p::before{content:“Hi!”}则即使它不在DOM中它也将包含在布局树中。 但是现在有个问题我们还不知道以什么顺序绘制它们即不知道谁应该覆盖谁。 其实很多资料中都会把上面构建渲染树的步骤放到构建布局树的步骤中 9. 分层、合成层(或者叫update layer tree)
如果我们是首次渲染那就是分层如果是更新操作叫update layer tree。
9.1 分层
现在我们有了布局树而且每个元素的具体位置信息都计算出来了那么接下来是不是就要开始着手绘制页面了 答案依然是否定的。 因为页面中有很多复杂的效果如一些复杂的3D变换、页面滚动或者使用z-indexing做z轴排序等为了更加方便地实现这些效果渲染引擎还需要为特定的节点生成专用的图层并生成一棵对应的图层树LayerTree。如果你熟悉PS相信你会很容易理解图层的概念正是这些图层叠加在一起构成了最终的页面图像。 要想直观地理解什么是图层你可以打开Chrome的“开发者工具”选择“Layers”标签就可以可视化页面的分层情况如下图所示 从上图可以看出渲染引擎给页面分了很多图层这些图层按照一定顺序叠加在一起就形成了最终的页面你可以参考下图 现在你知道了浏览器的页面实际上被分成了很多图层这些图层叠加后合成了最终的页面。下面我们再来看看这些图层和布局树节点之间的关系如文中图所示 通常情况下并不是布局树的每个节点都包含一个图层如果一个节点没有对应的层那么这个节点就从属于父节点的图层。如上图中的span标签没有专属图层那么它们就从属于它们的父节点图层。但不管怎样最终每一个节点都会直接或者间接地从属于一个层。 那么需要满足什么条件渲染引擎才会为特定的节点创建新的层呢通常满足下面两点中任意一点的元素就可以被提升为单独的一个图层。 第一点拥有层叠上下文属性的元素会被提升为单独的一层。 页面是个二维平面但是层叠上下文能够让HTML元素具有三维概念这些HTML元素按照自身属性的优先级分布在垂直于这个二维平面的z轴上。你可以结合下图来直观感受下 从图中可以看出明确定位属性的元素、定义透明属性的元素、使用CSS滤镜的元素等都拥有层叠上下文属性。 第二点需要剪裁clip的地方也会被创建为图层。 不过首先你需要了解什么是剪裁结合下面的HTML代码
stylediv {width: 200;height: 200;overflow:auto;background: gray;}
/style
bodydiv p所以元素有了层叠上下文的属性或者需要被剪裁那么就会被提升成为单独一层你可以参看下图/pp从上图我们可以看到document层上有A和B层而B层之上又有两个图层。这些图层组织在一起也是一颗树状结构。/pp图层树是基于布局树来创建的为了找出哪些元素需要在哪些层中渲染引擎会遍历布局树来创建层树Update LayerTree。/p /div
/body在这里我们把div的大小限定为200 * 200像素而div里面的文字内容比较多文字所显示的区域肯定会超出200 * 200的面积这时候就产生了剪裁渲染引擎会把裁剪文字内容的一部分用于显示在div区域下图是运行时的执行结果 出现这种裁剪情况的时候渲染引擎会为文字部分单独创建一个层如果出现滚动条滚动条也会被提升为单独的层。你可以参考下图 所以说元素有了层叠上下文的属性或者需要被剪裁满足这任意一点就会被提升成为单独一层。 9.2 update layer tree
这一步实际是更新Render Layer的层叠排序关系。 9.3 补充解释Render Object、Render Layer、Graphics Layer(又称Compositing Layer)和Graphics Context Render Object
首先我们有DOM树但是DOM树里面的DOM是供给JS/HTML/CSS用的并不能直接拿过来在页面或者位图里绘制。因此浏览器内部实现了Render Object 每个Render Object和DOM节点一一对应。Render Object上实现了将其对应的DOM节点绘制进位图的方法负责绘制这个DOM节点的可见内容如背景、边框、文字内容等等。同时Render Object也是存放在一个树形结构中的。 既然实现了绘制每个DOM节点的方法那是不是可以开辟一段位图空间然后DFS遍历这个新的Render Object树然后执行每个Render Object的绘制方法就可以将DOM绘制进位图了就像“盖章”一样把每个Render Object的内容一个个的盖到纸上类比于此时的位图是不是就完成了绘制。 不浏览器还有个层叠上下文的东西。这使得文档流中位置靠前位置的元素有可能覆盖靠后的元素。上述DFS过程只能无脑让文档流靠后的元素覆盖前面元素。 因此有了Render Layer。
Render Layer
当然Render Layer的出现并不是简单因为层叠上下文等比如opacity小于1、比如存在mask等等需要先绘制好内容再对绘制出来的内容做一些统一处理的css效果。 总之就是有层叠、半透明等等情况的元素就会从Render Object提升为Render Layer。不提升为Render Layer的Render Object从属于其父级元素中最近的那个Render Layer。当然根元素HTML自己要提升为Render Layer。 因此现在Render Object树就变成了Render Layer树每个Render Layer又包含了属于自己layer的Render Object。 现在浏览器渲染引擎遍历 Layer 树访问每一个 RenderLayer然后递归遍历negZOrderList里的layer、自己的RenderObject、再递归遍历posZOrderList里的layer。就可以将一颗 Layer树绘制出来。 Layer 树决定了网页绘制的层次顺序而从属于 RenderLayer 的 RenderObject 决定了这个 Layer 的内容所有的 RenderLayer 和 RenderObject 一起就决定了网页在屏幕上最终呈现出来的内容。 层叠上下文、半透明、mask等等问题通过Render Layer解决了。那么现在: 开辟一个位图空间-不断的绘制Render Layer、覆盖掉较低的Layer-拿给GPU显示出来 是不是就完全ok了 不。还有GraphicsLayers和Graphics Context
Graphics Layer(又称Compositing Layer)和Graphics Context
合成层的东西。 上面的过程可以搞定绘制过程。但是浏览器里面经常有动画、video、canvas、3d的css等东西。这意味着页面在有这些元素时页面显示会经常变动也就意味着位图会经常变动。每秒60帧的动效里每次变动都重绘整个位图是很恐怖的性能开销。 因此浏览器为了优化这一过程。引出了Graphics Layers和Graphics Context前者就是我们常说的合成层(Compositing Layer) 某些具有CSS3的3D transform的元素、在opacity、transform属性上具有动画的元素、硬件加速的canvas和video等等这些元素在上一步会提升为Render Layer而现在他们会提升为合成层Graphics Layer。每个Render Layer都属于他祖先中最近的那个Graphics Layer。当然根元素HTML自己要提升为Graphics Layer。 Render Layer提升为Graphics Layer的情况
3D 或透视变换(perspective、transform) CSS 属性使用加速视频解码的 元素拥有 3D (WebGL) 上下文或加速的 2D 上下文的 元素混合插件(如 Flash)对 opacity、transform、fliter、backdropfilter 应用了 animation 或者 transition需要是 active 的 animation 或者 transition当 animation 或者 transition 效果未开始或结束后提升合成层也会失效will-change 设置为 opacity、transform、top、left、bottom、right其中 top、left 等需要设置明确的定位属性如 relative 等拥有加速 CSS 过滤器的元素元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
3D transform、will-change设置为 opacity、transform等 以及 包含opacity、transform的CSS过渡和动画 这3个经常遇到的提升合成层的情况请重点记住。 所以在元素存在transform、opacity等属性的css animation或者css transition时动画处理会很高效这些属性在动画中不需要重绘只需要重新合成即可。 在前端页面尤其是在动画过程中由于 Overlap 重叠导致的合成层提升很容易发生。如果每次都将重叠的顶部 RenderLayer 提升为合成层那将消耗大量的 CPU 和内存Webkit 需要给每个合成层分配一个后端存储。为了避免 “层爆炸” 的发生浏览器会进行层压缩Layer Squashing如果多个 RenderLayer 和同一个合成层重叠时这些 RenderLayer 会被压缩至同一个合成层中也就是位于同一个合成层。但是对于某些特殊情况浏览器并不能进行层压缩就会造成创建大量的合成层。 RenderObject、 RenderLayer、 GraphicsLayer 是 Webkit 中渲染的基础其中 RenderLayer 决定了渲染的层级顺序RenderObject 中存储了每个节点渲染所需要的信息GraphicsLayer 则使用 GPU 的能力来加速页面的渲染。 使用 合成层提升 减少重绘重排
提升为合成层干什么呢普通的渲染层普通地渲染用普通的顺序普通地合成不好吗非要搞啥特殊待遇 浏览器就说了我这也是为了大家共同进步提升速度看那些搞特殊待遇的都是一些拖我们队伍后腿的(性能开销大)分开处理才能保证整个队伍稳定快速的进步 特殊待遇合成层的位图会交由 GPU 合成比 CPU 处理要快。当需要 repaint 时只需要 repaint 本身不会影响到其他的层。
对布局属性进行动画浏览器需要为每一帧进行重绘并上传到 GPU 中对合成属性进行动画浏览器会为元素创建一个独立的复合层当元素内容没有发生改变该层就不会被重绘浏览器会通过重新复合来创建动画帧
通过生成独立的Compositing Layer让此层内的重绘重排不引起整个页面的重绘重排 在介绍渲染树的时候提到满足某些条件的 RenderObjectLayer 会被提升为合成层合成层的绘制是在 GPU 中进行的比 CPU 的性能更好如果该合成层需要 Paint不会影响其他的合成层一些合成层的动画不会触发 Layout 和 Paint。 下面介绍几种在开发中常用的合成层提升的方式
使用transform和opacity书写动画
上文提出如果一个元素使用了 CSS 透明效果的动画或者 CSS 变换的动画那么它会被提升为合成层。并且这些动画变换实际上是应用在合成层本身上。这些动画的执行过程不需要主线程的参与在纹理合成前使用 3D API 对合成层进行变形即可。
#cube {transform: translateX(0);transition: transform 3s linear;
}#cube.move {transform: translateX(100px);
}bodydiv idbutton点击移动/divdiv idcube/divscriptconst btn document.getElementById(button);btn.addEventListener(click, () {const cube document.getElementById(cube);cube.classList move;});/script
/body对于上面的动画只有在动画开始后才会进行合成层的提升动画结束后合成层提升也会消失。这也就避免了浏览器创建大量的合成层造成的 CPU 性能损耗。 will-change
这个属性告诉了浏览器接下来会对某些元素进行一些特殊变换。当 will-change 设置为 opacity、transform、top、left、bottom、right其中 top、left、bottom、right 等需要设置明确的定位属性如 relative 等浏览器会将此元素进行合成层提升。在书写过程中需要避免以下的写法
*{ will-change: transform, opacity; }这样所有的元素都会被提升为单独的合成层造成大量的内存占用。所以需要只针对动画元素设定 will-change且动画完成之后需要手动将此属性移除。
Canvas
使用具有加速的 2D Context 或者 3D Contex 的 Canvas 来完成动画。由于具有独立的合成层Canvas 的改变不会影响其他合成层的绘制这种情况对于大型复杂动画比如 HTML5 游戏更为适用。此外也可以设置多个 Canvas 元素通过合理的Canvas 分层来减少绘制开销。 paint(图层绘制)重绘
重绘是以合成层为单位的。 在完成图层树的构建之后渲染引擎会对图层树中的每个图层进行绘制那么接下来我们看看渲染引擎是怎么实现图层绘制的 试想一下如果给你一张纸让你先把纸的背景涂成蓝色然后在中间位置画一个红色的圆最后再在圆上画个绿色三角形。你会怎么操作呢 通常你会把你的绘制操作分解为三步
制蓝色背景在中间绘制一个红色的圆再在圆上绘制绿色三角形
渲染引擎实现图层的绘制与之类似会把一个图层的绘制拆分成很多小的绘制指令然后再把这些指令按照顺序组成一个待绘制列表如下图所示 从图中可以看出绘制列表中的指令其实非常简单就是让其执行一个简单的绘制操作比如绘制粉色矩形或者黑色的线等。而绘制一个元素通常需要好几条绘制指令因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在图层绘制阶段输出的内容就是这些待绘制列表。
其实Paint有两步第一步是记录要执行哪些绘画调用第二步才是执行这些绘画调用。第一步只是把所需要进行的操作记录序列化进一个叫做SkPicture的数据结构里就是上面所说的待绘制列表。 接下来的第二步里会将待绘制列表中的操作replay出来这里才是将这些操作真正执行光栅化和填充进位图。主线程中和我们在Timeline中看到的这个Paint其实是Paint的第一步操作。第二步是后续的Rasterize步骤见后文其实在Rasterize之前会先分成图块关于这两个概念的解释在最开始有提到。 主线程生成待绘制列表交给合成线程 合成线程分成图块交给栅格化线程 栅格化线程栅格化(生成位图) 接着就是将栅格化的结果交给 GPU进程进行draw到浏览器上 这里其实有争议栅格化的结果是直接由栅格化线程交给GPU还是栅格化线程先将结果交给合成线程合成线程再把结果交给GPU进程。 分成图块 栅格化raster操作
绘制列表只是用来记录绘制顺序和绘制指令的列表而实际上绘制操作是由渲染引擎中的合成线程来完成的。你可以结合下图来看下渲染主线程和合成线程之间的关系 如上图所示当图层的绘制列表准备好之后主线程会把该绘制列表提交commit给合成线程那么接下来合成线程是怎么工作的呢 那我们得先来看看什么是视口你可以参看下图 通常一个页面可能很大但是用户只能看到其中的一部分我们把用户可以看到的这个部分叫做视口viewport。 在有些情况下有的图层可以很大比如有的页面你使用滚动条要滚动好久才能滚动到底部但是通过视口用户只能看到页面的很小一部分所以在这种情况下要绘制出所有图层内容的话就会产生太大的开销而且也没有必要。 基于这个原因合成线程会将图层划分为图块tile这些图块的大小通常是256x256或者512x512如下图所示 然后合成线程会按照视口附近的图块来优先生成位图实际生成位图的操作是由栅格化来执行的。所谓栅格化是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池所有的图块栅格化都是在线程池内执行的运行方式如下图所示 通常栅格化过程都会使用GPU来加速生成使用GPU生成位图的过程叫快速栅格化或者GPU栅格化生成的位图被保存在GPU内存中。 相信你还记得GPU操作是运行在GPU进程中如果栅格化操作使用了GPU那么最终生成位图的操作是在GPU中完成的这就涉及到了跨进程操作。具体形式你可以参考下图 从图中可以看出渲染进程把生成图块的指令发送给GPU然后在GPU中执行生成图块的位图并保存在GPU的内存中。 draw
GPU进程把结果draw到浏览器上
引用
https://developer.chrome.com/blog/inside-browser-part3/ 浏览器渲染详细过程重绘、重排和 composite 只是冰山一角 - 掘金 https://segmentfault.com/a/1190000041295744 https://gist.github.com/paulirish/5d52fb081b3570c81e3a 渲染流程下HTML、CSS和JavaScript是如何变成页面的 | 浏览器工作原理与实践 如何不择手段提升scroll事件的性能 https://github.com/aooy/blog/issues/5