likes
comments
collection

css 渲染优化

作者站长头像
站长
· 阅读数 33
在日常的开发中,通过使用css属性,做一些动效、动画时,会发现在页面有卡顿;在Android低端机尤为明显,故需要知道浏览器渲染以及优化手段

浏览器渲染流程

css 渲染优化css 渲染优化

  • 构建 DOM 树:浏览器将 HTML 解析成树形结构的 DOM 树,一般来说,这个过程发生在页面初次加载,或页面 JavaScript 修改了节点结构的时候。
  • 构建渲染树:浏览器将 CSS 解析成树形结构的 CSSOM 树,再和 DOM 树合并成渲染树。
  • 布局(Layout):浏览器根据渲染树所体现的节点、各个节点的CSS定义以及它们的从属关系,计算出每个节点在屏幕中的位置。Web 页面中元素的布局是相对的,在页面元素位置、大小发生变化,往往会导致其他节点联动,需要重新计算布局,这时候的布局过程一般被称为回流/重排(Reflow)。
  • 绘制(Paint):遍历渲染树,调用渲染器的 paint() 方法在屏幕上绘制出节点内容,本质上是一个像素填充的过程。这个过程也出现于回流或一些不影响布局的 CSS 修改引起的屏幕局部重画,这时候它被称为重绘(Repaint)。实际上,绘制过程是在多个层上完成的,这些层我们称为渲染层(RenderLayer)
  • 渲染层合成(Composite):多个绘制后的渲染层按照恰当的重叠顺序进行合并,而后生成位图,最终通过显卡展示到屏幕上。

以上是浏览器渲染的基本步骤,简述:

DOM tree + CSS tree  == Render tree  ==>   Layout tree  ==>  PaintLayer => Composite
                                           PaintLayer => Composite
                                            

只要页面上元素有改变,都会重复渲染以上部分步骤,在一帧内完成(16.77ms);通过设置css 样式 ,做动画,在一帧内无法渲染完,甚至来不及渲染,就会卡顿;其中部分渲染卡顿,跟渲染层合成(Composite)有关系。

Composite

什么是渲染层合成

在 DOM 树中每个节点都会对应一个渲染对象(RenderObject),当它们的渲染对象处于相同的坐标空间(z 轴空间)时,就会形成一个 RenderLayers,也就是渲染层。渲染层将保证页面元素以正确的顺序堆叠,这时候就会出现层合成(composite),从而正确处理透明元素和重叠元素的显示,这步叫做“层叠上下文”这个模型类似于 Photoshop 的图层模型,在 Photoshop 中,每个设计元素都是一个独立的图层,多个图层以恰当的顺序在 z 轴空间上叠加,最终构成一个完整的设计图。对于有位置重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并顺序出错,将会导致元素显示异常。

css 渲染优化

渲染对象(RenderObject)

一个 DOM 节点对应了一个渲染对象,渲染对象依然维持着 DOM 树的树形结构。

渲染层(RenderLayer)

这是浏览器渲染期间构建的第一个层模型,处于相同坐标空间(z轴空间)的渲染对象,都将归并到同一个渲染层中,因此根据层叠上下文,不同坐标空间的的渲染对象将形成多个渲染层,以体现它们的层叠关系。因此满足形成层叠上下文条件的 RenderObject 一定会为其创建新的渲染层,当然还有其他的一些特殊情况,为一些特殊的 RenderObject 创建一个新的渲染层,比如 overflow != visible 的元素。根据创建 RenderLayer 的原因不同,可以将其分为常见的 3 类:

  • NormalPaintLayer
  • 根元素(HTML)
  • 有明确的定位属性(relative、fixed、sticky、absolute)
  • 透明的(opacity 小于 1)
  • 有 CSS 滤镜(fliter)
  • 有 CSS mask 属性
  • 有 CSS mix-blend-mode 属性(不为 normal)
  • 有 CSS transform 属性(不为 none)
  • backface-visibility 属性为 hidden
  • 有 CSS reflection 属性
  • 有 CSS column-count 属性(不为 auto)或者 有 CSS column-width 属性(不为 auto)
  • 当前有对于 opacity、transform、fliter、backdrop-filter 应用动画
  • OverflowClipPaintLayer
  • overflow 不为 visible
  • NoPaintLayer
  • 不需要 paint 的 PaintLayer,比如一个没有视觉属性(背景、颜色、阴影等)的空 div。

满足以上条件的 RenderObject 会拥有独立的渲染层,而其他的 RenderObject 则和其第一个拥有渲染层的父元素共用一个。

图形层(GraphicsLayer)

渲染层,和其第一个拥有 GraphicsLayer 的父层共用一个。每个 GraphicsLayer 都有一个 GraphicsContext,GraphicsContext 会负责输出该层的位图,位图是存储在共享内存中,作为纹理上传到 GPU 中,最后由 GPU 将多个位图进行合成,然后 draw 到屏幕上,此时,我们的页面也就展现到了屏幕上。

合成层(CompositingLayer)

满足某些特殊条件的渲染层,会被浏览器自动提升为合成层。合成层拥有单独的 GraphicsLayer,个渲染层满足哪些特殊条件时,才能被提升为合成层呢?这里列举了一些常见的情况:

  • 3D transforms:translate3d、translateZ 等
  • video、canvas、iframe 等元素
  • 通过 Element.animate() 实现的 opacity 动画转换
  • 通过 СSS 动画实现的 opacity 动画转换
  • position: fixed
  • 具有 will-change 属性
  • 对 opacity、transform、fliter、backdropfilter 应用了 animation 或者 transition

常见实用**transforms:translate3d、translateZ ** 来形成合成层,这样是对原先的布局没有影响

示例

css 渲染优化css 渲染优化

其他合成情况

隐式合成

除此之外,在浏览器的 Composite 阶段,还存在一种隐式合成,部分渲染层在一些特定场景下,会被默认提升为合成层。其实就是元素div之间相互重叠css 渲染优化上图所示:把position-1生成合成层,后面的元素会相互重叠,为了层叠上下文展示正确;导致后面的元素直接隐式合成了;

合层压缩

如上图,目前没有遇到

合成爆炸

通过上图研究,很容易就产生一些不在预期范围内的合成层,当这些不符合预期的合成层达到一定量级时,就会变成层爆炸。需要排查是否有重叠,能否通过z-index来调节层叠顺序

优化

优点

  • 合成层的位图,会交由 GPU 合成,比 CPU 处理要快得多;
  • 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层;
  • 元素提升为合成层后,transform 和 opacity 才不会触发 repaint,如果不是合成层,则其依然会触发 repaint。(只有css动画,才可以,js去控制还是会重排、重绘的)使用 transform 或者 opacity 来实现动画效果

更新元素几何属性(发生重排)css 渲染优化浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的引发重排的情况

  • 页面首次渲染;
  • 浏览器窗口大小发生变化;
  • 元素的内容发生变化;
  • 元素的尺寸或者位置发生变化;
  • 元素的字体大小发生变化;
  • 激活CSS伪类;
  • 查询某些属性或者调用某些方法;

    • offsetTop、offsetLeft、offsetWidth、offsetHeight
    • scrollTop、scrollLeft、scrollWidth、scrollHeight
    • clientTop、clientLeft、clientWidth、clientHeight
    • width、height
    • getComputedStyle()
    • getBoundingClientRect()
    • 添加或者删除可见的DOM元素。

更新元素绘制属性(发生重绘)css 渲染优化如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些引发重绘的情况

  • 重排
  • visibility: visible <=> hidden
  • 颜色改变
  • 其他几何变化...

直接合成阶段css 渲染优化我们使用了CSS的transform来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。缺点

  • 绘制的图层必须传输到 GPU,这些层的数量和大小达到一定量级后,可能会导致传输非常慢,进而导致一些低端和中端设备上出现闪烁;
  • 隐式合成容易产生过量的合成层,每个合成层都占用额外的内存,而内存是移动设备上的宝贵资源,过多使用内存可能会导致浏览器崩溃,让性能优化适得其s

    其他优化

    css

  • 使用 transform 替代 top
  • 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局
  • 避免使用table布局,可能很小的一个小改动会造成整个 table 的重新布局。
  • 尽可能在DOM树的最末端改变class,回流是不可避免的,但可以减少其影响。尽可能在DOM树的最末端改变class,可以限制了回流的范围,使其影响尽可能少的节点。
  • 避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。
  • 将动画效果应用到position属性为absolute或fixed的元素上,避免影响其他元素的布局,这样只是一个重绘,而不是回流,同时,控制动画速度可以选择 requestAnimationFrame,详见探讨 requestAnimationFrame
  • 避免使用CSS表达式,可能会引发回流。
  • 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点,例如will-change、video、iframe等标签,浏览器会自动将该节点变为图层。

    js:

  • 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
  • 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
  • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

    总结

    在做动画时尽可能的使用使用csstransforms属性做动画,,它跳过Layout和Paint阶段,直接进入渲染,所以会提升部分性能,同时看看所属空间位置,是否提升了合成层;还有注意元素是否重叠,防止合成层爆炸;这样至少能保证css 方面,尽可能性能没有问题。剩下的请查看其他优化手段

示例代码

重叠

<style>
 .ball-running,.ball-running1 {
      width: 100px;
      height: 100px;
      animation: run-around 4s linear alternate 100;
      background: red;
      position: absolute;
      border-radius: 50%;
    }
    @keyframes run-around {
      0% {
        top: 0;
        left: 0;
      }
      25% {
        top: 0;
        left: 200px;
      }
      50% {
        top: 200px;
        left: 200px;
      }
      75% {
        top: 200px;
        left: 0;
      }
    }
    .position-1,.position-2,.position-3,.position-4 {
      position: absolute;
      width: 100px;
      height: 100px;
      background-color: red;
      border: #1b1b1b 1px solid;
    }
    .position-1 {
      transform: translateZ(10px);
    }
    .position-2 {
      top: 90px;
      left: 90px;
     // transform: translateZ(10px);
    }
    .position-3 {
      top: 180px;
      left: 180px;
    }
    .position-4 {
      top: 270px;
      left: 270px;
    }
    .parent-1 {
      //width: 100px;
      //height: 200px;
      background: #5b3a13;
      //overflow: auto;
      //transform: translateZ(10px);
    }
    .position-5 {

    }
<style/>    
<div class="parent-1">
      <div class="position-1">
        1
      </div>
      <div class="position-2">
        2
      </div>
      <div class="position-3">
        3
      </div>
      <div class="position-4">
        4
      </div>
    </div>

动画

<style>
.ball-running,.ball-running1 {
      width: 100px;
      height: 100px;
    //  animation: run-around2 4s linear alternate 100;
      background: red;
      position: absolute;
      border-radius: 50%;
    }
    @keyframes run-around {
      0% {
        top: 0;
        left: 0;
      }
      25% {
        top: 0;
        left: 200px;
      }
      50% {
        top: 200px;
        left: 200px;
      }
      75% {
        top: 200px;
        left: 0;
      }
    }





    //.ball-running {
    //  width: 100px;
    //  height: 100px;
    //  animation: run-around 2s linear alternate 100;
    //  background: red;
    //  border-radius: 50%;
    //}
    //@keyframes run-around {
    //  0% {
    //    transform: translate(0, 0);
    //  }
    //  25% {
    //    transform: translate(200px, 0);
    //  }
    //  50% {
    //    transform: translate(200px, 200px);
    //  }
    //  75% {
    //    transform: translate(0, 200px);
    //  }
    //}

</style>
<div class="ball-running"></div>

参考:

transform和left改变位置的性能区别GPU Accelerated Compositing in Chrome浏览器层合成与页面渲染优化浏览器渲染页面过程与页面优化无线性能优化:Composite浏览器渲染过程渲染流程Stick to Compositor-Only Properties and Manage Layer Count