// 注: 以下内容大量借阅自<<Webkit技术内幕>>--朱永盛(14年出版的) , 很多内容可能早已更新 , 因此个人并不能确定论述是否正确。部分摘录内容有删减 , 目录为了编辑方便作了些改动。读者可自行下载原文档阅读。

1. Webkit 的网页渲染过程

1.1.1 加载和渲染:

  浏览器的主要作用就是将用户输入的 URL 转变成可视化的图像。按照某些文档的分析, 这其中包含两个过程。其一是网页加载过程,就是从"URL"到构建DOM树, 其二是网页渲染过程,从DOM树到生成可视化图像。这两个过程也会交叉。

  网页渲染还有一个特性, 那就是网页通常比我们的屏幕可视面积要大(在移动设备上尤其明显), 而当前可见的区域, 我们称为视图(viewport)。

  因为网页比可视区域大, 所以浏览器在渲染网页的时候, 一般会加入滚动条以帮助翻滚网页。

1.1.2 Webkit 的渲染过程

  数据和模块

  数据包括网页内容 , DOM, 内部表示和图像

  模块包括 HTML解释器, CSS解释器, JavaScript引擎以及布局和绘图模块。

  

  根据数据的流向, 可以将渲染过程分成三个阶段, 第一个阶段是从网页的URL到构建完 DOM树, 第二个阶段是从 DOM树到构建完 Webkit的绘图上下文, 第三个阶段是从绘图上下文到生成最终的图像。

  第一个阶段

  从网页 URL 到构建完 DOM树这个过程如下:

    1. 当用户输入网页 URL的时候, Webkit调用其资源加载器加载该 URL对应的网页

    2. 加载器依赖网络模块建立连接, 发送请求并接收答复

    3. Webkit 接收到各种网页或者资源的数据, 其中某些资源可能是同步或异步获取的

    4. 网页被交给 HTML解释器转变成一系列的词语(Token)

    5. 解释器根据词语构建节点(Node), 形成 DOM树。

    6. 如果节点是 JavaScript 代码的话, 调用 JavaScript 引擎解释并执行

    7. JavaScript 代码可能会修改 DOM树的结构

    8. 如果节点需要依赖其他资源, 例如图片, CSS, 视频等, 调用资源加载器来加载它们, 但是它们是异步的, 不会阻碍当前 DOM树的继续创建;如果是 JavaScript资源 URL(没有标记异步方式), 则需要停止当前 DOM树的创建,

       直到 JavaScript的资源加载并被 JavaScript 引擎执行后才继续 DOM树的创建。

    在上述过程中, 网页在加载和渲染过程中会发出 "DOMContent" 事件和 DOM的 "onload" 事件, 分别在 DOM树构建完之后, 以及 DOM树构建完并且网页所依赖的资源都加载完之后发生。

  

  第二个阶段

  从 DOM树到构建完 Webkit的绘图上下文的具体过程如下:

    1. CSS 文件被 CSS解释器解释成内部表示结构。

    2. CSS 解释器工作完以后, 在 DOM 树上附加解释后的样式信息, 这就是 RenderObject 树

    3. RenderObject 节点在创建的同时, Webkit 会根据网页的层次结构创建 RenderLayer 树, 同时创建一个虚拟的绘图上下文。这中间还有复杂的内部过程。

    RenderObject 树的建立并不表示 DOM树会被销毁,事实上, DOM树, RenderObject树, RenderLayout树, 绘图上下文这四个内部表示结构一直存在, 直到网页被销毁。

  

  第三个阶段

  从绘图上下文到生成最终的图像。

    这一过程主要依赖 2D和 3D 图形库

    1. 绘图上下文是一个与平台无关的抽象类, 它将每个绘图操作桥接到不同的具体实现类, 也就是绘图具体实现类

    2. 绘图实现类也可能有简单的实现, 也可能有复杂的实现。在 Chormium中,它的实现相当复杂, 需要 Chromium 的合成器来完成复杂的多进程和 GPU加速机制

    3. 绘图实现类将 2D图形库或者 3D 图形库绘制的结果保存下来, 交给浏览器来同浏览器界面一起显示。

    现代浏览器为了绘图上的高效性和安全性, 可能会在这一过程中引入复杂的机制, 而且, 绘图也从之前单纯的软件渲染, 到现在的 GPU硬件渲染, 混合渲染模型等方式.

  先说明几个重要概念

    框模型: 框模型是布局计算的基础, 渲染引擎可以根据模型来理解该如何排版元素以及元素之间的位置关系; 一个框模型大致包含了四个部分, 从外到内分别是外边距(margin), 边框(border), 内边距(padding) 和内容(content)。

    RenderObject 树: 顾名思义, "渲染节点树", 什么是渲染节点, p, div, body 这些都可看作是渲染节点, 称之为"可视化节点", 而诸如 meta节点, 是看不到的, 可称之为"非可视节点"。

             对于这些"可视节点", 因为 Webkit 需要将它们的内容绘制到最终的网页结果中, 所以Webkit 会为它们建立相应的 RenderObject 对象。一个 RenderObject 对象保存了为绘制 DOM节点所需要的各种信息。

             例如: 样式布局信息(位置-position, 大小-框模型, 颜色等),经过 Webkit的处理之后, RenderObject 对象知道如何绘制自己。

             这些 RenderObject 对象同 DOM 的节点对象类似, 它们也构成一棵树, 称之为 RenderObject 树。RenderObject 是基于 DOM树建立起来的一颗新树。是为了布局计算和渲染等机制而构建的一种内部表示。

             哪些情况下为一个 DOM节点创建一个新的 RenderObject 对象呢?

              1. DOM 树的 doucment 节点

              2. DOM 树中的可视节点,例如 html, body, div 等。而 Webkit 不会为非可视化节点创建 RenderObject 节点,例如 meta标签, 注释节点。

              3. 某些情况下 Webkit 需要建立匿名的 RenderObject 节点, 该节点不对应于 DOM树中的任何节点, 而是 Webkit 处理上的需要.

              影子节点. 例如 Video, canvas这些节点(可能还有 JavaScript 创建出来的影子节点, 典型的 <OBJECT></OBJECT>),  虽然 JavaScript 代码没法访问影子 DOM, 但是 Webkit需要创建并渲染 RenderObject。

    层次和 RenderLayout 对象

            网页是可以分层的, 一是为了方便网页开发者开发网页并设置网页的层次, 二是为了 Webkit 处理上的便利。也就是说为了简化渲染的逻辑

            Webkit 会为网页的层次创建相应的 RenderLayer 对象。当某些类型 RenderObject 的节点或者具有某些 css 样式的 RenderObject 节点出现的时候, Webkit就会为这些节点创建 RenderLayer 对象。

            一般来说, 某个 RenderObject 节点的后代都属于该节点。除非 Webkit 根据规则为某个后代 RenderObject 节点创建了一个新的 RenderLayer 对象。

            RenderLayer 树是基于 RenderObject 树建立起来的一颗新树。

            RenderLayer 节点和 RenderObject 不是一一对应关系,而是一对多的关系。哪些情况下的 RenderObject 节点需要建立新的 RenderLayer 节点呢?

              1. DOM 树的 Document 节点对应的 RenderView 节点。

              2. DOM 树中的 Document 的子女节点, 也就是 HTML 节点对应的 RenderBlock 节点。(个人注解: document.documentElement.childNodes, 但必须是可视节点, 也即 body 节点).

              3. 显示的指定 css 位置的 RenderObject 节点。(个人注解: 也即设置了 position 属性的 dom 节点, fixed, absolute, relative, 设置了 z-index 应该也会额外创建一个 RenderLayer 节点)

              4. 有透明效果的 RenderObject 节点。(个人注解: 设置了 opacity 的方便理解, 但如果是设置了 background: rgba(0,0,0,0.2) 这种应该是无须创建 RenderLayer 节点的)

              5. 节点有溢出 (overflow), alpha 或者反射效果的 RenderObject 节点。

              6. 使用 Canvas 2D 和 3D (WebGL) 技术的 RenderObject 节点。

              7. Video 节点对应的 RenderObject 节点。

            除了根节点也就是 RenderLayer 节点,一个 RenderLayer 节点的父亲就是该 RenderLayer 节点对应的 RenderObject 节点的祖先链中最近的祖先,并且祖先所在的 RenderLayer 节点同该节点的 RenderLayer 节点不同。

            基于这一原理, 这些 RenderLayer 节点也即构成了一颗 RenderLayer 树。

    绘图上下文

            RenderObject 对象用什么来绘制内容呢?在 Webkit 中, 绘图操作被定义了一个抽象层, 这就是绘图上下文。

            绘图操作可以分为 2D 绘图上下文: 绘制 2D 图行的上下文:提供基本绘图单元的绘制接口以及设置绘图的样式,绘图接口包括画点, 画线,画图片, 画多边形, 画文字等;绘图样式包括颜色,线宽, 字号大小,渐变等

                     3D 绘图上下文: 绘制 3D 图行的上下文:  支持 css3D , WebGL 等

            渲染方式, 软件绘图, GPU硬件加速绘图, 合成。理想情况下, 每个层都有个绘制的存储区域, 这个存储区域用来保存绘图的结果。

            (理想情况下, 每一个 RenderLayer 对象都有一个后端存储与其对应。然后将哪些 RenderLayer 对象组合在一起, 形成一个新的合成层,再由合成器将多个合成层合成起来, 形成最终可视化结果, 实际就是一张图片)

    

    

书上介绍了很多知识 , 个人技术有限尚无法理解,转述到此为止了。

理解的大意呢大致是

先创建 DOM 树结构,

再通过 css 解析器 过滤掉没有用的 DOM节点得到要显示的 DOM节点(RenderObject),根据层叠样式表,计算当前节点的布局(块模型), 其他样式。设置 RenderObject对象类型, 样式等基本布局信息。

分析 RenderObject 树根据特定规则创建 RenderLayer 层,以分层渲染

如过需要硬件加速, 对 RenderLayer 层进行组合创建新的合成层 , 然后递归渲染新的合成层。

最后由渲染引擎 将所有绘制完的合成层合成起来, 输出到显示设备。

根据上述渲染的步骤, 做优化时可以考虑以下几点:

1. 精简你的 html 代码 , 每一个渲染节点都意味着性能损失,有用但不显示的 html 节点可以应用注释, 或包裹在一个 script? type="xxx" 标签中,减少 renderObject 数量;

2. 如果浏览器的默认样式能够符合要求 , 就不用额外添加样式;如果不是要用到的 css, 无须引入到页面中, 简化你的 css 代码;并在编写css时,选择器具体化, 减少样式的层叠, 以减轻布局计算量;

3. css 文件放置位置的讨论: 有说把样式放在页面头部, 更有甚的直接把样式全部内联, 出于性能内联更佳, 出于方便维护放头部好些, 个人见解: 可参考现流行的组件化设计, 为每一个“组件”, 头部插入一个 <style>, 并应用 css_module,

既保证了维护性, 也拥有尽快的性能, 而且应用 css_module 后, 选择器的具体化更是避免了样式的层叠, 简化了样式的计算, 目前而言是一套非常好的方案。

4. 渲染层的使用。创建层需要额外的开销, 所以不是必要情况, 减少层的创建。如避免 overflow: hidden 属性的滥用, position 的滥用,css3 动画, opacity 透明效果,z-index 等。清除浮动推荐使用 clear: left | right | both 属性.

5. 对于必要的动画, 推荐使用 css3 的动画去实现. 因为 css3 的动画在渲染方式上会启用硬件加速, 流畅度会好很多。比如一个左右运动,可以使用 transform: translateX 来实现, 避免采用 style.left, style.top 这些操作。

首次渲染之外, 还有个重要的是重绘和重排。

  我们都知道直接操作 dom 很慢, 对 dom 的操作会触发重排或重绘或二者都有。所以我们要尽可能的减少重绘重排。而引起重绘重排的操作这里不再赘述, 网上有很多这类型文章。推荐:

  https://blog.csdn.net/lhjuejiang/article/details/79793331

每当重新绘制新的一帧的时候, 一般需要三个阶段: 计算布局, 绘图, 合成. 这三个阶段中, 计算布局和绘图比较费时间, 而合成需要的时间相对要少一些。可以着重从前两个阶段中去优化;

1. 合适的使用分层,减少需要重新计算的布局和绘图; 如对于频繁修改的区域, 使用 z-index或 定位属性让其创建一个新的 RenderLayer, 每次对该区域的修改只会对当前层造成影响。

2. 使用 css 3D 变形技术: 因为应用了 css 3D变形技术既会创建一个新的层 , 而且还会应用硬件加速。既不会影响到其他层的布局, 也无须对当前层进行重新计算布局, 重新绘制,只是使用合成功能。(2D 图行也有硬件加速机制)

错误之处欢迎指正 , 非常感谢朱永盛前辈的分享 , 开源精神!

参考文档: https://www.cnblogs.com/luluping/archive/2013/04/05/3000460.html

      https://www.oschina.net/translate/chrome-accelerated-rendering?cmp&p=2

css 优化的更多相关文章

  1. 常用CSS优化总结——网络性能与语法性能建议

    在前端面试中最常见的问题就是页面优化和缓存(貌似也是页面优化),被问了几次后心虚的不行,平然平时多少会用到一些,但突然问我,很难把自己知道的都说出来.页面优化明显不是一两句能够说完的,这两天总结了一下 ...

  2. (转)常用CSS优化总结——网络性能与语法性能建议

    原文地址:http://www.cnblogs.com/dolphinX/p/3508657.html 在前端面试中最常见的问题就是页面优化和缓存(貌似也是页面优化),被问了几次后心虚的不行,平然平时 ...

  3. 关于js优化和css优化

    css优化: 1.css代码的压缩. 2.css文件的合并. 3.不滥用float,因为float在渲染时计算量比较大,所以尽量减少使用float. 4.避免在html标签中写style属性. js优 ...

  4. 教你如何写出高效整洁的 css 代码——css优化(转载)

    css 写起来并不难,但在大型项目中,就变得难以管理,特别是不同的人在 css 书写风格上稍有不同,团队上就更加难以沟通,为此总结了一些如何实现高效整洁的 css 代码原则. css 优化的原则 1. ...

  5. css优化,提高性能

    CSS 优化主要是四个方面: 加载性能比如不要用 @import 等等,@import会影响css文件的加载速度,考虑加载性能时,主要是从减少文件体积.减少阻塞加载.提高并发方面入手,任何 hint ...

  6. CSS优化压缩

    顾名思义缩写有简写意思,那就总结一下CSS缩写知识点.为什么要让CSS属性缩写?1.简化代码.一些CSS属性简写可以减少CSS代码从而减少CSS文件的占用字节.加快网页下载速度和网页加载速度.2.优化 ...

  7. css优化篇

    平时总说如何如何优化,今天就详细的写一下css如何优化,嘿嘿. 首先,CSS的优化工作主要从两个方面着手 网络性能:把CSS写到字节数最少,加快下载速度,自然可以让页面渲染的更快一些 语法性能:同样都 ...

  8. PHP中,JS和CSS优化工具Minify的使用方法

    为减少HTTP请求,我们往往需要合并和压缩多个JS和CSS文件,下面记录下网上关于实现这个功能的PHP源码以及开源项目Minify的使用方法 一.实现合并和压缩多个JS和CSS文件的代码请参考 1.一 ...

  9. js css优化-- 合并和压缩

    在项目框架中,首先要引用很多css和js文件,80%的用户响应时间都是浪费在前端.而这些时间主要又是因为下载图片.样式表.JavaScript脚本.flash等文件造成的.减少这些资源文件的Reque ...

  10. WEB前端性能优化之二——css优化

    1.把样式表置于顶部 现把样式表放到文档的< head />内部似乎会加快页面的下载速度.这是因为把样式表放到< head />内会使页面有步骤的加载显示.HTML规范清 楚指 ...

随机推荐

  1. v-echart 按需加载

    import 'v-charts/lib/style.css';import VeBar from 'v-charts/lib/bar' // 条形图import VeRadar from 'v-ch ...

  2. ES6 —— 数组总结

    1. map:映射   一个对一个 arr.map(function(item) { ... })    可以配合箭头函数:arr.map(item => ... ) let arr1 = [1 ...

  3. There is no getter for property named 'user' in 'class com.jyr.wh.domain.User' 异常

    今天在使用mybatis时,出现了一个问题:There is no getter for property named 'user' in 'class com.jyr.wh.domain.User, ...

  4. nodejs连接阿里云物联网套件(mqtt)

    文档地址:https://help.aliyun.com/document_detail/73742.html   连接方法:先HTTPS认证再连接 /* *阿里云物联网套件nodejs连接示例 *C ...

  5. eclipse启动tomcat错误解决

    clipse启动tomcat报出下面的错误提示: 控制台: 九月 06, 2018 9:01:31 下午 org.apache.tomcat.util.digester.SetPropertiesRu ...

  6. PX转REM简易计算器(适用于fittext.js插件计算)

    当你使用fittext.js插件时,通过量取的像素单位PX计算出REM单位值,是一件比较麻烦而繁琐的,为了提高工作效率,自己闲暇写了个小DEMO,现在给大家分享出来. 先看dom: <heade ...

  7. 记一次网络原因导致的mysql连接中断问题(druid)

    date: 2018-04-19 21:00 tag: java,mysql,exception,mat,调试,jvm 工具: gceasy.io, MAT 线上系统出现一个诡异的bug,通过heap ...

  8. AssetBundle-----资源包划分

    资源包划分 经常更新的资源放在一个单独的包里面,跟不经常更新的包分离;需要同时加载的资源放在一个包里面;其他包共享的资源放在一个单独的包里面;需要同时加载的小资源打包成一个包;对于一个同一个资源有两个 ...

  9. 在Javascript中闭包(Closure)

    在Javascript中闭包(Closure) 什么是闭包 “官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. ...

  10. C# Cookie方法

    //写入 protected void Button1_Click(object sender, EventArgs e) { HttpCookie cookie=new HttpCookie(&qu ...