浏览器渲染原理的个人整理

  近年来,随着各种virtual dom的技术大行其道,越来越多新入行的同学对于dom的概念,乃至浏览器渲染的知识的了解和熟悉程度都越来越低了,当然,毕竟浏览器渲染牵涉到内容广大而深远,不同浏览器的策略又不完全相同,但是近来团队里的童鞋频繁遇到了动效实现的具体方式上选择的困扰,于是笔者从前到后梳理了一下有关的知识点,只是没想到不理不知道,一理吓一跳,整个过程感觉自己也有了很多新的收获,真可谓温故而知新。

  

  首先是大家都熟悉的一张传统的浏览器渲染流程图,图来源为耗子叔的blog,笔者最开始入门的时候也是看的这张图,原po中还有一个渲染过程的视频,非常直观,简单的说:

1、浏览器从上往下(HTML 文档的顺序)逐行解析整个网页:(当然,这里还有同/异步下载远程资源的过程,不过这里不是本文的重点,就略过了)

  1)对于类html文件,会转换生成为Dom Tree(DOM的概念);

  2)对于css等样式文件,解析css并转换为CSSOM;

  3)对于script文件,按照其行为遵循以上操作;

2、紧接着根据dom tree和拿到的css匹配构造并生成render tree;

3、进行layout(因为实际过程中会产生多次layout,而国内外对这个过程的命名和不同厂商对这个过程的命名都有不同,常用的英文名还有:reflow layouting relayout,中文名也有“布局”、“回流”、“重排”等多种称呼,本文中笔者统一使用chrome中使用的方式)

4、进行paint(同样,该过程也会发生多次,也有不同的称呼:如repaint redraw,中文一般称作“重绘”)

5、进行composite layers  

  对于第5条,也许你并未在上图中看到,让我们随意打开一个网站,打开调试工具具体的看看一个网页加载过程:

  

  在chrome中,可以看到,

  在paint后面往往很偷偷摸摸的跟着一个小条,名为“Composite layers”,这也算是现代浏览器对渲染的进一步优化吧,所以现代浏览器的渲染可以简化为以下的几个步骤:

  不过上图并没有记录dom tree和render tree的生成过程,相当于整个construct过程都省略了,而是着重强调了开发者可控制的剩余几个过程,接下来我们就来谈一下在这几个过程中,浏览器都做了什么:

1、JavaScript 和 style:这两个阶段就相当于我们的代码动态的修改了dom元素和css rules,然后需要重新进行一次浏览器渲染操作;

2、layout:顾名思义就是进行布局操作,确定每个的HTMLElement的几何位置关系,确定自身的大小等(可以理解为几乎所有的几何位置关系都会在此过程中进行,有关页面结果的所有操作都在此过程中进行);

3、paint: 同样顾名思义为渲染操作,即为每个确定位置的HTMLElement填充颜色、图像、文字、边框、阴影等;

4、composite:合成图层(这个阶段又通常被称作“复合层” 、“合成层”、 “组合层”等);

  看到这里可能又会奇怪了,不是只有布局,渲染么?为什么还需要合成呢?其实早在layout阶段,当dom直接能够确定了位置关系之后,一种新的关系就生成了:遮挡关系。因为dom并不是都像现在的flex布局那样所有项目之间都是不会存在遮挡关系的,既然产生了遮挡关系,所以分层就应运而生了,随之而产生的还有我们熟悉的层叠上下文(TSC),定义并规范了这种情形,在只要满足它的条件,浏览器就会产生一个新的图层用于存放TSC,然后等到了最后的composite阶段,进行层压缩(Layer Squashing)避免过多的层产生过多的开销,影响页面性能。

 好了,说到页面性能我们终于到了我们的正题了。虽然第一个阶段是我们修改页面时的必要开销,但是剩下的三个阶段,都相当于是浏览器的应激反应,实际上并不是我们所期望的,接下来让我们来再仔细的看看这三个阶段产生什么具体的影响:

1、layout:

  如前所述,在布局阶段,所有dom元素的位置、大小等几何关系都会确定,而反过来说,所有有关几何关系的操作都会不可避免的触发这个阶段,而这个阶段对于浏览器渲染而言,不得不说是一个非常昂贵的开销,且撇开它本身的开销不谈,由于页面的layout,不可避免的还会触发paint和composite,所以这个阶段带来的将是最昂贵的性能开销。

  那么什么样的操作会触发layout呢?  

  简单的来讲有以下几个原因:

    1) dom元素的几何属性变化,以及dom的显示和隐藏(单指display,不包括visibility)

    2) DOM tree的结构变化

    3) 获取某些属性

    4) window的resize或者scroll

    5) font-size的改变

  而从另外个角度,从触发源来说,又有两种情形会触发layout:

    1) 一种是写重排,即每次尝试给这些值赋值会引起layout:width height left top margin padding

    2) 另一种是读重排,即每次尝试读取这些值的时候就会引起layout:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、 clientTop、clientLeft、clientWidth、clientHeight、(getComputedStyle() or currentStyle in IE),尤其值得一提的是,浏览器在实际处理layout的时候会做一些优化,来节省性能开销,它会选择将一些操作放入队列中然后批量layout,但是当遇到读重排的情况的时候,这个逻辑则不会触发。

  第一次发现还有读重排的情况的时候笔者也是震惊的,但是笔者再三核对之后,发现的确是这么一回事,而且还发现了一份完整的列表,不仅仅是包含对某些属性的访问,调用某些方法也会引起layout:

  Element: clientHeight, clientLeft, clientTop, clientWidth, focus(), getBoundingClientRect(), getClientRects(), innerText, offsetHeight, offsetLeft, offsetParent, offsetTop, offsetWidth, outerText, scrollByLines(), scrollByPages(), scrollHeight, scrollIntoView(), scrollIntoViewIfNeeded(), scrollLeft, scrollTop, scrollWidth

  Frame, Image: height, width

  Range: getBoundingClientRect(), getClientRects()

  SVGLocatable: computeCTM(), getBBox()

  SVGTextContent: getCharNumAtPosition(), getComputedTextLength(), getEndPositionOfChar(), getExtentOfChar(), getNumberOfChars(), getRotationOfChar(), getStartPositionOfChar(), getSubStringLength(), selectSubString()

  SVGUse: instanceRoot

  window: getComputedStyle(), scrollBy(), scrollTo(), scrollX, scrollY, webkitConvertPointFromNodeToPage(), webkitConvertPointFromPageToNode()

2、paint

  讲完了最重的layout我们来看看下一个paint,它是为填充layer的颜色、图像、文字、边框、阴影等而生的,即所有相关的改变都会触发该阶段,通常的如:opacity visibility outline transform等,值得一提的是,我们在通常修改dom的位置的时候之所以推荐使用transform而不使用left/top的原因就在于此,因为对left和top的修改是必定会触发layout的,而对transform的修改则只会触发paint,性能更好一些。

  不过谈到paint就不得不提一下层叠上下文(the stacking context)了,上文简单了提及了一下TSC产生的背景,这里列一下它产生的条件:

  1)根元素
  2)有明确的定位属性(relative、fixed、sticky、absolute,其中相对和绝对定位的zIndex不能为auto)
  3)opacity 小于 1
  4)具备filter、mask、mix-blend-mode(不为normal)、reflection、column-count(不为auto)、column-width(不为auto)、-webkit-overlfow-scrolling(值为touch)、isolation(值为isolate)属性的元素
  5)具备transform属性(不为none)
  6)backface-visibility hidden的元素
  7) 有对于opacity、transform、fliter、backdrop-filter应用animation或者transition的元素
  8) z-index不为auto的flex项目
  9) isolation 属性被设置为 "isolate"的元素

更详细的也可以参考MDN的层叠上下文说明 

 

  当然,paint阶段和layout阶段也有类似的规律,触发它虽然不会触发layout,但是仍然会不可避免的触发composite,所以性能开销依然不低,而且在实际工作中我们的页面中的动效实际是不可避免的,我们依赖paint阶段来实现也是一个通用方法。

  有关layout与paint,优化这两个阶段的基本原则就是:

    “减少不必要的dom操作”

  1)尽量使用class和cssText来进行必要的style操作以减少layout

  2)将复杂的修改离线化(display:none)

  3) 不要频繁的使用computedStyles,将需要的值读一次缓存起来处理(offsetTop等)

3、composite

  前文我们已经提过,在layout和paint阶段之后,几乎一定会经过composite阶段将之前的layer合并,而为了避免过多的合成层影响渲染效率,浏览器一般会进行层压缩(Layer Squashing)的操作来节省资源,而压缩操作会遇到无法压缩层(squashing Clipping Container Mismatch)的情况,进而可能导致层爆炸(Layer Explosion),这是我们需要特别注意的。

  虽然有“层爆炸”这只黑天鹅需要我们处理,但是多数时候,只要我们能够控制合成层的产生,不要让它过量出现,这个问题就通常可以避免,那么合成层到底是怎么出现的呢?总的来说它还是和之前的遮挡关系以及我们常常使用的硬件加速有关:

  1)硬件加速的iframe、flash等

  2)video及覆盖在video标签上的控制栏

  3)3D canvas

  4)3d transform

  5)backface-visibility为hidden的元素(这是css 3d的一部分,是指3d渲染元素的背部是否可见)http://h5.51ping.com/app/app-threejs-demo/cards.html

  6)对 opacity、transform、fliter、backdrop-filter 应用了 animation 或者 transition(需要是 active 的 animation 或者 transition,当 animation 或者 transition 效果未开始或结束后,合成层也会失效)

  7)will-change 设置为 opacity、transform、top、left、bottom、right(其中 top、left 等需要设置明确的定位属性,如 relative 等)

  8)元素有一个z-index较低且包含一个合成层的兄弟元素(即该元素在合成层上渲染)

  当然以上这些还不是合成层出现的全部条件,有时候因为其他情况也会间接的导致合成层的出现:

  1)后代有transform、opacity(<1)、mask、filter、reflection的属性

  2)后代overflow不为visible,(特别的,如果本身定位明确,则需要满足zIndex不为auto的条件)

  3)有合成层后代,且自身fixed定位或者具备perserves-3d属性或者具备perspective属性

  4)以上都是由于后代为合成层,导致自身提升为合成层的情况,特别的,自身为合成层,兄弟节点的后代overflow不为visible,且具备定位的时候,会将兄弟节点提升为合成层

  听到这里是不是已经晕得不行了?笔者当初看到这里的时候一度想起了以前在学校里背法典的情形。。

  让我们换个角度来看待合成层,首先,打开一个页面:

  

  然后切换到layers选项卡(笔者使用的chrome版本号是 58.0.3029.110,低于此版本的同学请在timeline->layers里查看,如果还没有最好就升下级吧),通过menu栏的操作按钮改变了页面的角度之后,我们就能比较直观的看到一个页面的合成层了(因为合成层是浏览器最终展示给用户的分成,所以能够有如此比较方便的观察手段,比之前的paint阶段的的layer则没这么方便了,不过那个阶段的分层规则也相对简单),左边是合成层对应dom,右边则是具体排列,你还可以勾选顶部的paints,那么它将会绘制出layer的内容,接着我们选中左边的一个具体的层,然后我们可以看到如下信息:

  

  从上图可以看出,我们可以知道一个具体的合成层的原因、使用的内存、还有宽高等信息,关键就在于这个内存使用,虽然我们之前就知道通过开启硬件加速(其实就是生成合成层的一种方式,来提升体验),但是由于合成层带来了额外的内存开销,过渡使用又会再一次引起性能问题。(写到这里笔者脑海里突然响起了“均衡守护万物之基”的声音。。。)

  看到这里你会不会觉得很奇怪,当初我们为了提升页面性能而打开的硬件加速,到了现在却变成了拖慢页面性能的潜在原因,说起来也讽刺,但是其实性能优化就是这样,很难会有一个放之四海而皆准而法则,往往需要我们在实际工作中不断改进和测试我们的页面,才是最正确的途径。

  不过话说回来,composite阶段的优化和前两个阶段的优化确实有着不一样的困难,加之纷繁复杂的规则,我们在实际应对时往往会比较没有头绪,其实,握住硬件加速、遮挡是大部分成因,内存是重要缺陷的脉络去整理自己的html,也就不那么复杂了。

  说完了合成层如何“被”产生我们再来说说如果手动产生合成层:

  1、will-change: transform/opacity;

  2、transform: tanslateZ(0)/translate3d(0,0,0);

  虽然它有内存占用的缺陷,但是为了避免有些动效频繁地引起页面的paint和layout,有节制的使用合成层也能够提高我们的页面性能,这也是硬件加速盛行的原因,其他熟悉硬件加速的大家看到以上两个途径第一反应想必也是“这不是开启硬件加速吗?”其实,我们回顾下硬件加速的机制:

  “通过开启硬件加速,将本来由CPU的计算量一部分分配给GPU,提高页面性能”

  其实,合成层也是在基于这个机制在工作,诉求都是为了提高页面性能,只是由于本身浏览器并不会特别“智能”的响应我们的所有诉求,而且也由于其内部机制的限制,才会出现如“层爆炸”的黑天鹅。

  限于篇幅限制,笔者便不再赘述有关“层爆炸”的相关知识点了,有兴趣可以参看淘宝前端团队的无线性能优化:Composite,里面对上文提到的很多规则都提供了完整demo。

  另外如果你还需要更为深入的明确哪些dom操作会触发哪些过程,则可以参考csstriggers,里面有比较完整的说明。

更多参考文献:

浏览器的工作原理:http://taligarsiel.com/Projects/howbrowserswork1.htm

Composite in Chrome: http://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome

will-change: https://dev.opera.com/articles/css-will-change-property/

Layer Introduction: https://www.html5rocks.com/zh/tutorials/speed/layers/

How to trigger layout: http://gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-webkit.html

What`s mean of repaint: http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/

时间: 2024-10-23 22:47:04

浏览器渲染原理的个人整理的相关文章

浏览器渲染原理--reflow

Web页面运行在各种各样的浏览器当中,浏览器载入.渲染页面的速度直接影响着用户体验简单地说,页面渲染就是浏览器将html代码根据CSS定义的规则显示在浏览器窗口中的这个过程.先来大致了解一下浏览器都是怎么干活的:1. 用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件:2. 浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文件:3. 浏览器又发出CSS文件的请求,服务器返回这个CSS文件:4.

浏览器渲染原理解析

作者:贝程学院 浏览器内核分为两部分:渲染引擎(Layout Engine 或者 Rendering Engine)和 JS 引擎.早期渲染引擎和 JS 引擎并没有明显区分,随着 JS 引擎越来越独立,内核逐渐变成了渲染引擎的代名词.渲染引擎包括: HTML 解释器 CSS 解释器 布局 网络 存储 图形 音视频 图片解码器 等等 渲染引擎简介 浏览器——Firefox.Chrome和Safari是基于两种渲染引擎构建的,Firefox使用Geoko——Mozilla自主研发的渲染引擎,Safa

浏览器渲染原理及流程

我们可能都知道浏览器含有一个渲染引擎,用来渲染窗口所展示的内容.默认情况下,渲染引擎可以显示html.xml文档及图片,它也可以借助插件(一种浏览器扩展)显示其他类型数据,例如使用PDF阅读器插件,用于显示PDF格式.但是其具体的渲染原理和流程估计也有很多人都不知道或者不清楚吧.这些天研究了一下浏览器的渲染原理,有了些心得,在这里跟大家分享一下,这里只讨论渲染引擎最主要的用途--显示应用了CSS之后的html及图片. 渲染引擎简介 本文所讨论的浏览器--Firefox.Chrome和Safari

浏览器渲染原理

浏览器输入url到页面呈现发生了什么? 1:输入url 浏览器会开启一个线程来处理这个请求,对URL分析判断如果是http协议就按照Web方式来处理,浏览器会从历史记录,书签等地方,找到已经输入的字符串可能对应的 url,然后给出智能提示,让你可以补全url地址.对于 google的chrome 的浏览器,他甚至会直接从缓存中把网页展示出来. 2:域名解析 通过DNS解析获取网址的IP地址, IP 地址:IP 协议为互联网上的每一个网络和每一台主机分配的一个逻辑地址.IP 地址如同门牌号码,通过

【浏览器渲染原理】渲染树构建之渲染树和DOM树的关系(转载 学习中。。。)

在DOM树构建的同时,浏览器会构建渲染树(render tree).渲染树的节点(渲染器),在Gecko中称为frame,而在webkit中称为renderer.渲染器是在文档解析和创建DOM节点后创建的,会计算DOM节点的样式信息. 在webkit中,renderer是由DOM节点调用attach()方法创建的.attach()方法计算了DOM节点的样式信息.attach()是自上而下的递归操作.也就是说,父节点总是比子节点先创建自己的renderer.销毁的时候,则是自下而上的递归操作,也就

不同内核的浏览器,以及渲染原理

一. "Rendering Engine"渲染引擎,也可以叫做浏览器内核(这个部分要扩展一下,原本的浏览器内核分为渲染引擎和Js引擎,后来Js引擎越来越独立,内核就倾向于只指渲染引擎了),是浏览器最核心的部分,浏览器内核的不同对于网页的语法解释会有不同,所以渲染出来的效果也不同,简单说就是不同的内核决定了浏览器如何显示页面,也是因为这个原因导致了浏览器兼容性问题的出现. 下面是5种浏览器分别采用的不同引擎: - IE内核 Trident- 谷歌内核 WebKit / Blink(由we

浏览器渲染

浏览器渲染原理简介 2014年8月30日 0:40 看到这个标题大家一定会想到这篇神文<How Browsers Work>,这篇文章把浏览器的很多细节讲得很细,而且也被翻译成了中文.为什么我还想写一篇呢?因为两个原因, 这篇文章太长了,阅读成本太大,不能一口气读完. 花了大力气读了这篇文章后可以了解很多,但似乎对工作没什么帮助. 所以,我准备写下这篇文章来解决上述两个问题.希望你能在上班途中,或是坐马桶时就能读完,并能从中学会一些能用在工作上的东西. 浏览器工作大流程 废话少说,先来看个图:

不同内核浏览器的差异以及浏览器渲染(转)

一.简单介绍一下什么是浏览器内核. 浏览器最重要或者说核心的部分是“Rendering Engine”,可大概译为“解释引擎”,不过我们一般习惯将之称为“浏览器内核”.负责对网页语法的解释(如HTML.JavaScript)并渲染(显示)网页. 所以,通常所谓的浏览器内核也就是浏览器所采用的渲染引擎,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息.不同的浏览器内核对网页编写语法的解释也有不同,因此同一网页在不同的内核的浏览器里的渲染(显示)效果也可能不同,这也是网页编写者需要在不同内核

【转】不同内核浏览器的差异以及浏览器渲染简介

一.简单介绍一下什么是浏览器内核. 浏览器最重要或者说核心的部分是“Rendering Engine”,可大概译为“解释引擎”,不过我们一般习惯将之称为“浏览器内核”.负责对网页语法的解释(如HTML.JavaScript)并渲染(显示)网页. 所以,通常所谓的浏览器内核也就是浏览器所采用的渲染引擎,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息. 不同的浏览器内核对网页编写语法的解释也有不同,因此同一网页在不同的内核的浏览器里的渲染(显示)效果也可能不同,这也是网页编写者需要在不同内