关于javascript:动画开发-浏览器渲染相关

46次阅读

共计 10562 个字符,预计需要花费 27 分钟才能阅读完成。

页面渲染流程

咱们看几张的经典流程图

浏览器根底构造

咱们始终都在说 Javascript 是单线程,但浏览器是多线程的,在内核管制下互相配合以放弃同步,次要的常驻线程有:

GUI 渲染线程

负责渲染界面,解析 HTML,CSS,构建 DOM 和 Render 树布局绘制等。如果过程中遇到 Javascript 引擎执行会被挂起线程,GUI 更新保留在一个队列中期待 Javascript 引擎闲暇才执行;

Javascript 引擎线程

负责解析运行 Javascript;执行工夫过程会导致页面渲染加载阻塞;

事件触发线程

浏览器用以管制事件循环。当 Javascript 引擎执行过程中触发的事件(如点击,申请等)会将对应工作增加到事件线程中,而当对应的事件合乎触发条件被触发时会把对应工作增加到解决队列的尾部等到 Javascript 引擎闲暇时解决;

定时器触发线程

因为 Javascript 引擎是单线程容易阻塞,所以须要有独自线程为 setTimeout 和 setInterval 计时并触发,同样是合乎触发条件(记时结束)被触发时会把对应工作增加到解决队列的尾部等到 Javascript 引擎闲暇时解决;W3C 标准规定工夫距离低于 4ms 被算为 4ms。

异步 http 申请线程

XMLHttpRequest 在连贯后浏览器新开线程去申请,检测到状态变动如果有设置回调函数会产生状态变更事件,而后把对应工作增加到解决队列的尾部等到 Javascript 引擎闲暇时解决;

Composite 线程

通过 GPU 执行,真正将页面绘制并输入到屏幕上

页面渲染

渲染流程次要步骤:

解析 HTML 生成 DOM 树

整个过程是一个深度遍历的操作, 它会从以后节点从上往下一一构建完之后才会去构建同层级节点及其子节点, 最初的构造我网上轻易找了一张图, 大略如下.

然而这个过程是 能够被 CSS 或者 Javascript 加载而阻塞, 这是浏览器的一个解决机制, 因为这两者都可能会扭转 DOM 树结构的变动, 所以会等它们执行完之后再持续解析

解析款式生成 CSSOM 树

这里是跟 DOM 树同时生成的, 这里蕴含了 link, style 和内联 多份不同的款式整合解析成一份最终的规定, 整个构造也是相似 DOM 树的属性构造, 最初的构造我也是网上轻易找了一张图, 大略如下.

款式的整合计算是个很简单的过程, 存在默认款式, 权重, 继承, 伪元素, 反复定义, 单位换算等多种不同水平的难题

权重规定大略如下:

  • 元素选择符: 1
  • class 选择符: 10
  • id 选择符: 100
  • 内联款式: 1000
  • !important 优先级最高
  1. 如果优先级雷同, 则抉择最初呈现的款式;
  2. 继承失去的款式的优先级最低;
  3. 嵌套选择器优先级是相加, 例如: #A .B = 100 + 10 = 110;

实际上浏览器 CSS 选择器的 解析规定是从右往左的, 每一步都能过滤掉些不合乎规定的分支状况, 直到找到根元素或满足条件的匹配规定的选择器就完结这个分支的遍历. 所以尽量避免深层嵌套 CSS, 因为寻找选择器和计算最终款式都会受影响的.

两者合并构建 Render 树

DOM 树和 CSSOM 树是依据代码解析而成的, 而代码最终给到浏览器渲染的必定是视觉上的体现构造, 因为它们都带有字体, 色彩, 尺寸, 地位甚至是否存在的款式管制, 最初的构造我也是网上轻易找了一张图, 大略如下.

总的来说, 会遍历整个 DOM 树节点, 疏忽不可见节点(例如 meta,header 以及指定了属性 display: none 等节点), 得出各个节点的 CSS 定义以及他们的从属关系,从而去计算出每个节点在屏幕中的地位

依据 Render 树开始布局绘制

页面布局以可见区域为画布, 从左到右从上往下从根节点开始布局. 遍历 Render 树调用渲染器的 API 绘制节点内容填充屏幕进行像素级信息计算与绘制

实际上这一步绘制是在多个层上进行绘制

Composite

实际上在 paint 之后,display之前还有一个环节叫 渲染层合并

对页面中 DOM 元素的绘制是在多个层上进行的。在每个层上实现绘制过程之后,浏览器会将所有层依照正当的程序合并成一个图层,而后显示在屏幕上。对于有地位重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并程序出错,将会导致元素显示异样。(上面会详解)

回流与重绘

这个别是解析过程或者两头款式构造被扭转须要从新进行计算布局才会产生,

回流:当浏览器发现变动影响了布局须要从新计算

重绘:只影响以后元素的视觉效果而不会影响其余元素

回流是影响页面性能的重要因素之一, 尽量避免

渲染实现

这不是一个循序渐进的过程, 浏览器为了尽快渲染界面展现给用户看, 从网络申请获取到文档内容的同时就开始部分渲染, 而不会等所有 HTML 解析完才会创立渲染. 而且这是根本的流程, 实际上当初支流浏览器会有些区别, 也会优化整个渲染流程, 所以这个过程实际上并没有咱们设想中的这么慢

渲染层合成

咱们就以 Chrome 为例

在 Chrome 中其实有几种不同的层类型:

  • RenderLayers 渲染层,这是负责对应 DOM 子树.
  • GraphicsLayers 图形层,这是负责对应 RenderLayers 子树。

RenderLayers 渲染层

DOM 树每个节点都有一个对应的 渲染对象 (RenderObject), 当它们处于一个雷同的 坐标空间 (z 轴) 就会独特造成一个渲染层., 从下面晓得以坐标空间辨别不同的渲染层, 所以

  • 渲染层能够有多个
  • DOM 元素通过扭转层叠上下文即可创立新的渲染层
  • 坐标空间决定层叠上下文, 但不是惟一因素, 上面条件可得

而可能满足条件的状况有:

NormalPaintLayer

  • 文档根元素(<html>);
  • position 值为 absolute(相对定位)或 relative(绝对定位)且 z-index 值不为 auto 的元素;
  • position 值为 fixed(固定定位)或 sticky(粘滞定位)的元素(沾滞定位适配所有挪动设施上的浏览器,但老的桌面浏览器不反对);
  • flex (flexbox) 容器的子元素,且 z-index 值不为 auto;
  • grid (grid) 容器的子元素,且 z-index 值不为 auto;
  • opacity 属性值小于 1 的元素(参见 the specification for opacity);
  • mix-blend-mode 属性值不为 normal 的元素;
  • 以下任意属性值不为 none 的元素:

    • transform
    • filter
    • perspective
    • clip-path
    • mask / mask-image / mask-border
  • isolation 属性值为 isolate 的元素;
  • -webkit-overflow-scrolling 属性值为 touch 的元素;
  • will-change 值设定了任一属性而该属性在 non-initial 值时会创立层叠上下文的元素(参考这篇文章);
  • contain 属性值为 layout、paint 或蕴含它们其中之一的合成值(比方 contain: strict、contain: content)的元素。

摘抄自层叠上下文

OverflowClipPaintLayer

  • overflow 不为 visible;

NoPaintLayer

  • 不须要 paint 的 PaintLayer,比方一个没有视觉属性(背景、色彩、暗影等)的空 div

DOM 节点和渲染对象是一一对应的, 满足下面条件的渲染对象领有独立的渲染层, 不满足的将与其第一个领有渲染层的父元素共用同一个. 简略来说,RenderObject 决定渲染内容, 而 RenderLayers 决定绘制程序.

GraphicsLayers 图形层

每个 GraphicsLayer对应着一个渲染层是负责最终出现的内容图形层, 它领有一个 图形上下文 (GraphicsContext) 负责输入该层的位图. 贮存在共享内存的位图将作为纹理上传到GPU, 最初由 GPU 将多个位图进行合成绘制. 每个 GraphicsLayer 能够独立的进行渲染而不会相互影响,那么将一些频繁重绘的元素放到独自的 GraphicsLayer 中就不会对整个页面造成影响

所以 GraphicsLayer 是一个重要的渲染载体和工具,但 它并不间接解决渲染层,而是解决合成层

CompositingLayer 合成层

某些非凡的渲染层会被晋升至 合成层(Compositing Layers),晋升的前提是必须为 SelfPaintingLayer(能够认为就是下面提到的 NormalPaintLayer). 合成层领有独自的 GraphicsLayer,而其余不是合成层的渲染层,则和其第一个领有 GraphicsLayer 父层共用一个。

当满足以下条件的渲染层会被浏览器主动晋升为合成层

间接起因(direct reason)

  • 硬件加速的 iframe 元素(比方 iframe 嵌入的页面中有合成层)
  • video 元素
  • 笼罩在 video 元素上的视频管制栏
  • 3D 或者 硬件加速的 2D Canvas 元素(一般 2D Canvas 不会晋升为合成层)
  • 硬件加速的插件,比方 flash 等等
  • 在 DPI 较高的屏幕上,fix 定位的元素会主动地被晋升到合成层中。但在 DPI 较低的设施上却并非如此,因为这个渲染层的晋升会使得字体渲染形式由子像素变为灰阶
  • 有 3D transform
  • backface-visibility 为 hidden
  • 对 opacity、transform、fliter、backdropfilter 利用了 animation 或者 transition(须要是 active 的 animation 或者 transition,当 animation 或者 transition 成果未开始或完结后,晋升合成层也会生效)
  • will-change 设置为 opacity、transform、top、left、bottom、right(其中 top、left 等须要设置明确的定位属性,如 relative 等)

后辈元素起因

  • 有合成层后辈同时自身有 transform、opactiy(小于 1)、mask、fliter、reflection 属性
  • 有合成层后辈同时自身 overflow 不为 visible(如果自身是因为明确的定位因素产生的 SelfPaintingLayer,则须要 z-index 不为 auto)
  • 有合成层后辈同时自身 fixed 定位
  • 有 3D transfrom 的合成层后辈同时自身有 preserves-3d 属性
  • 有 3D transfrom 的合成层后辈同时自身有 perspective 属性

overlap 重叠起因

  • 重叠或者说局部重叠在一个合成层之上。(上面隐式合成会讲到)

    • 最常见和容易了解的就是元素的 border box(content + padding + border)和合成层的有重叠
  • filter 成果同合成层重叠
  • transform 变换后同合成层重叠
  • overflow scroll 状况下同合成层重叠。即如果一个 overflow scroll(不论overflow:auto 还是 overflow:scrill,只有是能 scroll 即可)的元素同一个合成层重叠,则其可视子元素也同该合成层重叠
  • 假如重叠在一个合成层之上(assumedOverlap)

    比方一个元素的 CSS 动画成果,动画运行期间,元素是有可能和其余元素有重叠的。针对于这种状况,于是就有了 assumedOverlap 的合成层产生起因, 即便动画元素视觉上并没有和其兄弟元素重叠,但因为 assumedOverlap 的起因,其兄弟元素仍然晋升为了合成层。

    须要留神的是该起因下,有一个很非凡的状况:

    如果合成层有内联的 transform 属性,会导致其兄弟渲染层 assume overlap,从而晋升为合成层。

摘抄自无线性能优化:Composite

合成层领有独自的 GraphicsLayers, 而其余渲染层则和第一个领有 GraphicsLayers 的父层共用一个

隐式合成

局部渲染层在一些特定场景下,会被默认晋升为合成层, 举例来说

  1. 两个相对定位有不同 z -index 属性的元素重叠在一起, 能够从控制台看出两个元素跟父元素共用同一个 GraphicsLayers

  1. 这时候如果底层元素加上 transform: translateZ(0) 晋升至合成层之后, 按理说应该会层级更高, 然而实际上两个元素都被晋升到合成层, 这是因为浏览器为了纠正层叠程序保障视觉效果统一, 隐性强行晋升了本来高于底层元素层级的元素至合成层

层爆炸和层压缩

层爆炸

一些合成层的条件非常荫蔽, 导致产生了不在预期范畴内的合成层, 达到肯定数量之后会重大耗费性能占用 GPU 和大量的内存资源引起页面卡顿, 形成层爆炸. 咱们看上面代码

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title></title>
    <style>
        @keyframes slide {
            from {transform: none;}

            to {transform: translateX(100px);
            }
        }

        .animating {
            width: 300px;
            height: 30px;
            background-color: orange;
            color: #fff;
            animation: slide 5s alternate linear infinite;
        }

        #ul {
            margin-top: -20px;
            padding: 5px;
            border: 1px solid #000;
        }

        li {
            width: 600px;
            height: 30px;
            margin-bottom: 5px;
            background-color: blue;
            color: #fff;
            position: relative;
            /* 会导致无奈压缩:squashingClippingContainerMismatch */
            overflow: hidden;
        }

        p {
            position: absolute;
            top: 2px;
            left: 2px;
            font-size: 16px;
            line-height: 16px;
            padding: 2px;
            margin: 0;
            background-color: green;
        }
    </style>
</head>

<body>
    <div class="animating">composited animating</div>
    <ul id="ul"></ul>
</body>
<script>
    window.onload = function() {var $ul = document.getElementById('ul');
        for (var i = 0; i < 10; i++) {var ulObj = document.createElement("li");
            var pObj = document.createElement("p");
            pObj.innerText = i
            // ulObj.appendChild(pObj);
            $ul.appendChild(ulObj);
        }
    }
</script>

</html>

试验可得 10 个左右曾经开始卡顿,100 就根本卡死了. 再看控制台当初层的状况

造成层爆炸的起因因为满足了以下状况:

  1. animating 元素利用了 transform 动画, 被晋升到合成层
  2. 动画运行期间,元素是有可能和其余元素有重叠的(assumedOverlap), 动画元素视觉上并没有和其兄弟元素重叠,但因为 assumedOverlap 的起因,其兄弟元素仍然晋升为了合成层。所以 li 元素都被晋升到合成层, 如果单单这种状况还是能层压缩的
  3. li 设置了overflow: hidden, 当渲染层同合成层有不同的裁剪容器(clipping container)时,该渲染层无奈压缩(squashingClippingContainerMismatch)

最佳计划是突破 overlap 的条件,也就是说让其余元素不要和合成层元素重叠。

  1. 让其余元素不和合成层重叠

    .animating {
      ...
      position: relative;
      z-index: 1;
    }

  1. 肯定须要笼罩动画之上的话, 能够调整款式代替裁剪款式, 去掉 overflow 属性

层压缩

浏览器会对多个渲染层同一个合成层重叠时进行优化, 这些渲染层会被压缩到一个 GraphicsLayer 中,以避免因为重叠起因导致可能呈现的“层爆炸”, 持续用下面的例子持续加元素

能够发现在晋升到合成层元素之上的多个不同元素会独特晋升至同一个合成层, 但也有很多无奈压缩的状况. 但也不是必然, 也有很多无奈压缩的状况

  • 无奈进行会突破渲染程序的压缩(squashingWouldBreakPaintOrder)

    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            #container {
                position: relative;
                width: 420px;
                height: 80px;
                border: 1px solid black;
            }
    
            #composited {
                width: 100%;
                height: 100%;
                transform: translateZ(0);
            }
    
            #ancestor {-webkit-mask-image: -webkit-linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));
            }
    
            #overlap-child {
                position: absolute;
                left: 0;
                top: 10px;
                bottom: 0px;
                width: 100%;
                height: 60px;
                background-color: orange;
            }
        </style>
    </head>
    
    <body>
        <div id="container">
            <div id="composited">Text behind the orange box.</div>
            <div id="ancestor">
                <div id="overlap-child"></div>
            </div>
        </div>
    </body>
    
    </html>

在本例中,#overlap-child 同合成层重叠,如果进行压缩,会导致渲染程序的扭转,其父元素 #ancestor 的 mask 属性将生效,因而相似这种状况下,是无奈进行层压缩的。目前常见的产生这种起因的状况有两种,一种是上述的先人元素应用 mask 属性的状况,另一种是先人元素应用 filter 属性的状况

  • video 元素的渲染层无奈被压缩同时也无奈将别的渲染层压缩到 video 所在的合成层上(squashingVideoIsDisallowed)
  • iframe、plugin 的渲染层无奈被压缩同时也无奈将别的渲染层压缩到其所在的合成层上(squashingLayoutPartIsDisallowed)
  • 无奈压缩有 reflection 属性的渲染层(squashingReflectionDisallowed)
  • 无奈压缩有 blend mode 属性的渲染层(squashingBlendingDisallowed)
  • 当渲染层同合成层有不同的裁剪容器(clipping container)时,该渲染层无奈压缩(squashingClippingContainerMismatch)

    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            .clipping-container {
                overflow: hidden;
                height: 10px;
                background-color: blue;
            }
    
            .composited {transform: translateZ(0);
                height: 10px;
                background-color: red;
            }
    
            .target {
                position: absolute;
                top: 0px;
                height: 100px;
                width: 100px;
                background-color: green;
                color: #fff;
            }
        </style>
    </head>
    
    <body>
        <div class="clipping-container">
            <div class="composited"></div>
        </div>
        <div class="target"> 不会被压缩到 composited div 上 </div>
    </body>
    
    </html>

本例中 .target 同 合成层 .composited 重叠,然而因为 .composited 在一个 overflow: hidden 的容器中,导致 .target 和合成层有不同的裁剪容器,从而 .target 无奈被压缩。

  • 绝对于合成层滚动的渲染层无奈被压缩(scrollsWithRespectToSquashingLayer)

    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="utf-8">
        <title></title>
        <style>
            body {
                height: 1500px;
                overflow-x: hidden;
            }
    
            .composited {
                width: 50px;
                height: 50px;
                background-color: red;
                position: absolute;
                left: 50px;
                top: 400px;
                transform: translateZ(0);
            }
    
            .overlap {
                width: 200px;
                height: 200px;
                background-color: green;
                position: fixed;
                left: 0px;
                top: 0px;
            }
        </style>
    </head>
    
    <body>
        <div class="composited"></div>
        <div class="overlap"></div>
    </body>
    
    </html>

红色的 .composited 晋升为了合成层,, 当滑动页面,.overlap 重叠到 .composited 上时,.overlap 会因重叠起因晋升为合成层,同时,因为绝对于合成层滚动,因而无奈被压缩(原文一开始说只有 composited 是合成, 重叠之后 overlap 才会晋升, 然而我试验是一开始就都晋升了)

  • 当渲染层同合成层有不同的具备 opacity 的先人层(一个设置了 opacity 且小于 1,一个没有设置 opacity,也算是不同)时,该渲染层无奈压缩(squashingOpacityAncestorMismatch,同 squashingClippingContainerMismatch)
  • 当渲染层同合成层有不同的具备 transform 的先人层时,该渲染层无奈压缩(squashingTransformAncestorMismatch,同上)
  • 当渲染层同合成层有不同的具备 filter 的先人层时,该渲染层无奈压缩(squashingFilterAncestorMismatch,同上)
  • 当笼罩的合成层正在运行动画时,该渲染层无奈压缩(squashingLayerIsAnimating),当动画未开始或者运行结束当前,该渲染层才能够被压缩(这一块我没试验过, 间接用原文图片)

梳理流程

再回顾一下流程图

  1. 文档被解析成对应的 DOM 树结构和 CSSOM 树结构
  2. 再被解析成 Render 树, 每个节点被解析成对应的 RenderObject, 外面蕴含了对于浏览器怎么渲染节点的信息, 如果某些节点不被渲染则不会生成对应的RenderObject, 而RenderObject 的作用就是向 GraphicsContext 收回绘制的调用来进行元素的绘制
  3. Render 树再被解析成 RenderLayers 树, RenderObject 依据层叠程序解析成不同的 RenderLayers 渲染层,
  4. RenderLayers 树解析成 GraphicsLayers 树, 每个 GraphicsLayer 都有本人的 GraphicsContext 能够进行独立渲染
  5. 某些非凡的 RenderLayers 会也被晋升成 CompositingLayers,领有独自的 GraphicsLayer
  6. 开始将一层一层绘制 Layer Tree,一个 Layer 的绘制会被拆分成许多个简略的 绘制指令 ,而后按程序将这些指令组成一个 绘制列表。绘制列表生成之后,渲染过程的主线程(GUI 渲染线程)会给 Composite 线程发送音讯,把绘制列表给 Composite 线程开始绘制页面。
  7. 拆分图块, 拆分图块的目标是为了优先绘制首屏范畴笼罩的内容,而不须要等一个 Layer 绘制实现才显示。同时,Chrome Webkit 思考到首屏的内容仍然可能非常复杂导致绘制的工夫较长,为了改善用户体验,绘制图块时会先显示低分辨率的图片,当图块绘制实现时,再把低分辨率的图片绘制成残缺的。(这就是下面说过的浏览器优化渲染局部)

  1. 栅格化, 栅格化会将图块(tile)转换成位图(bitmap),如果开启 GPU 硬件加速,则转换过程在 GPU 过程中实现,生成的位图会缓存在 GPU 的内存 中;如果没有开启 GPU 硬件加速,则由渲染过程实现,生成的位图缓存在 共享内存 中。

  1. 合成和显示, 在上一步的栅格化实现之后,Composite 线程会发送音讯告诉浏览器主过程从内存中获取位图数据,将位图绘制到屏幕上。

绘制局部摘抄自理解浏览器渲染的过程和原理

CPU 与 GPU 渲染区别

CPU

中央处理器(CPU,central processing unit)作为计算机系统的运算和管制外围,是信息处理、程序运行的最终执行单元。

在计算机体系结构中,CPU 是对计算机的所有硬件资源(如存储器、输入输出单元)进行管制调配、执行通用运算的外围硬件单元。CPU 是计算机的运算和管制外围。计算机系统中所有软件层的操作,最终都将通过指令集映射为 CPU 的操作。

GPU

图形处理器(英语:Graphics Processing Unit,缩写:GPU),又称显示外围、视觉处理器、显示芯片,是一种专门在个人电脑、工作站、游戏机和一些挪动设施(如平板电脑、智能手机等)上做图像和图形相干运算工作的微处理器。

GPU 使显卡缩小了对 CPU 的依赖,并进行局部本来 CPU 的工作,尤其是在 3D 图形处理时 GPU 所采纳的核心技术有硬件 T &L(几何转换和光照处理)、立方环境材质贴图和顶点混合、纹理压缩和凹凸映射贴图、双重纹理四像素 256 位渲染引擎等,而硬件 T &L 技术能够说是 GPU 的标记。

它不单单存储图形,而且能实现大部分图形性能,这样就大大加重了 CPU 的累赘,进步了显示能力和显示速度。

从上可得, 当 CPU 解决好位图信息交给 GPU 渲染可能极大解放 CPU 累赘, 也能疾速渲染图层. 当然适度应用会导致重大的性能降落, 内存占用过高

正文完
 0