• css 优化


    // 注: 以下内容大量借阅自<<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

  • 相关阅读:
    apk反编译
    Eclipse Android项目中如如第三方library文件
    layout_gravity属性和gravity属性区别(转载)
    android weight 属性正解(转载)
    010_01Servlet Request&Response
    009_02sendRedirect() forward() include()
    009_01Servlet基础简介
    008_02HTTP基础知识
    008_01WEB基础知识
    【实用】让代码变的更加简洁
  • 原文地址:https://www.cnblogs.com/liuyingde/p/10765420.html
Copyright © 2020-2023  润新知