关于前端:从浏览器渲染原理谈动画性能优化

33次阅读

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

本文作者:Bermudarat

前言

在越来越多的业务中,前端页面除了展现数据和提供用户操作的 UI,也须要带给用户更丰盛的交互体验。动画作为承载,曾经成为日常前端开发,尤其是 C 端开发的必选项。设施硬件性能的晋升、浏览器内核的降级也给在页面端实现晦涩动画提供了可能。目前,惯例设施的刷新频率通常是 60HZ,也就是说,如果要让用户感触不到显著卡顿,浏览器的渲染流水线须要每秒输入 60 张图片(60 FPS)。
接下来,文章会从根底的渲染树登程,介绍浏览器渲染流水线,以及罕用的优化动画性能的办法。

渲染根底

渲染树

只管不同的渲染引擎渲染流程不同,然而都须要解析 HTML 和 CSS 用于生成渲染树。前端开发接触最多的渲染引擎是 WebKit(以及在其根底上派生的 Blink),接下来本文会以 Webkit 为根底介绍渲染树。

图片来自 GPU Accelerated Compositing in Chrome

上图中,除了咱们相熟的 DOM 树外,还有 RenderObject 树,RenderLayer 树,GraphicsLayer 树,它们独特形成了 “ 渲染森林 ”。

RenderObject

RenderObject 保留了绘制 DOM 节点所须要的各种信息,与 DOM 树对应,RenderObject 也形成了一颗树。然而RenderObject 的树与 DOM 节点并不是一一对应关系。《Webkit 技术底细》指出,如果满足下列条件,则会创立一个 RenderObject

  • DOM 树中的 document 节点;
  • DOM 树中的可见节点(webkit 不会为非可视节点创立 RenderObject 节点);
  • 为了解决须要,Webkit 建设匿名的 RenderObject 节点,如示意块元素的 RenderBlockRenderObject 的子类)节点。

将 DOM 节点绘制在页面上,除了要晓得渲染节点的信息外,还须要各渲染节点的层级。浏览器提供了 RenderLayer 来定义渲染层级。

RenderLayer

RenderLayer 是浏览器基于 RenderObject 创立的。RenderLayer 最后是用来生成层叠上下文 (stacking context),以保障页面元素依照正确的层级展现。同样的,RenderObjectRenderLayer 也不是一一对应的,RenderObject 如果满足以下条件,则会创立对应的 RenderLayer(GPU Accelerated Compositing in Chrome):

  • 文档的根节点;
  • 具备明确 CSS 定位信息的节点(如 relativeabsolute 或者 transform
  • 通明节点;
  • overflowmask 或者 reflection 属性的节点;
  • filter 属性的节点;
  • 有 3D Context 或者减速的 2D Context 的 Canvas 节点;
  • 对应 Video 元素的节点。

咱们能够将每一个 RenderLayer 设想成一个图层。渲染就是在每个 RenderLayer 图层上,将 RenderObject 绘制进去。这个过程能够应用 CPU 绘制,这就是软件绘图。然而软件绘图是无奈解决 3D 的绘图上下文,每一层的 RenderObject 中都不能蕴含应用 3D 绘图的节点,例如有 3D Contex 的 Canvas 节点,也不能反对 CSS 3D 变动属性。此外,页面动画中,每次元素尺寸或者地位变动,都要从新去结构 RenderLayer 树,触发 Layout 及其后续的渲染流水线。这样会导致页面帧率的降落,造成视觉上的卡顿。所以古代浏览器引入了由 GPU 实现的硬件加速绘图。

在取得了每一层的信息后,须要将其合并到同一个图像上,这个过程就是合成(Compositing),应用了合成技术的称之为合成化渲染。

在软件渲染中,实际上是不须要合成的,因为软件渲染是依照从前到后的程序在同一个内存空间实现每一层的绘制。在古代浏览器尤其是挪动端设施中,应用 GPU 实现的硬件加速绘图更为常见。由 GPU 实现的硬件加速绘图须要合成,而合成都是应用 GPU 实现的,这整个过程称之为硬件加速的合成化渲染。
古代浏览器中,并不是所有的绘图都须要应用 GPU 来实现,《Webkit 技术底细》中指出:

对于常见的 2D 绘图操作,应用 GPU 来绘图不肯定比应用 CPU 绘图在性能上有劣势,例如绘制文字、点、线等,起因是 CPU 的应用缓存机制无效缩小了反复绘制的开销而不须要 GPU 并行性。

GraphicsLayer

为了节俭 GPU 的内存资源,Webkit 并不会为每个 RenderLayer 调配一个对应的后端存储。而是依照肯定的规定,将一些 RenderLayer 组合在一起,造成一个有后端存储的新层,用于之后的合成,称之为合成层。合成层中,存储空间应用 GraphicsLayer 示意。对于一个 RenderLayer 对象,如果没有独自晋升为合成层,则应用其父对象的合成层。如果一个 RenderLayer 具备以下几个特色之一(GPU Accelerated Compositing in Chrome),则其具备本人的合成层:

  • 有 3D 或者透视变换的 CSS 属性;
  • 蕴含应用硬件加速的视频加码技术的 Video 元素;
  • 有 3D Contex 或者减速的 2D Context 的 Canvas 元素;(注:一般的 2D Context 不会晋升为合成层);
  • opacitytransform 扭转的动画;
  • 应用了硬件加速的 CSS filter 技术;
  • 后辈蕴含一个合成层;
  • Overlap 重叠:有一个 Z 坐标比本人小的兄弟节点,且该节点是一个合成层。

对于 Overlap 重叠造成的合成层晋升,Compositing in Blink / WebCore: From WebCore::RenderLayer to cc:Layer 给出了三幅图片:

图 1 中,顶部的绿色矩形和底部的蓝色矩形是兄弟节点,蓝色矩形因为某种原因被晋升为合成层。如果绿色矩形不进行合成层晋升的话,它将和父节点共用一个合成层。这就导致在渲染时,绿色矩形位于蓝色矩形的底部,呈现渲染出错(图 2)。所以如果产生重叠,绿色矩形也须要被晋升为合成层。

对于合成层的晋升条件,无线性能优化:Composite 中有更具体的介绍。联合 RenderLayerGraphicsLayer 的创立条件,能够看出动画(尺寸、地位、款式等扭转)元素更容易创立 RenderLayer,进而晋升为合成层(这里要留神,并不是所有的 CSS 动画元素都会被晋升为合成层,这个会在后续的渲染流水线中介绍)。这种设计使浏览器能够更好应用 GPU 的能力,给用户带来晦涩的动画体验。

应用 Chrome 的 DevTools 能够不便地查看页面的合成层:
抉择“More tools -> Layers”

上图中,不仅能够看到云音乐首页的合成层,也能够具体看到每个合成层创立的起因。例如,页面底部的播放栏被晋升为合成层的起因是“Overlaps other composited content”,这对应“Overlap 重叠:有一个 Z 坐标比本人小的兄弟节点,且该节点是一个合成层”。

在前端页面,尤其是在动画过程中,因为 Overlap 重叠导致的合成层晋升很容易产生。如果每次都将重叠的顶部 RenderLayer 晋升为合成层,那将耗费大量的 CPU 和内存(Webkit 须要给每个合成层调配一个后端存储)。为了防止“层爆炸”的产生,浏览器会进行层压缩(Layer Squashing):如果多个 RenderLayer 和同一个合成层重叠时,这些 RenderLayer 会被压缩至同一个合成层中,也就是位于同一个合成层。然而对于某些非凡状况,浏览器并不能进行层压缩,就会造成创立大量的合成层。无线性能优化:Composite 中介绍了会导致无奈进行合成层压缩的几种状况。篇幅起因,就不在此文中进行介绍。

RenderObjectLayerRenderLayerGraphicsLayer 是 Webkit 中渲染的根底,其中 RenderLayer 决定了渲染的层级程序,RenderObject 中存储了每个节点渲染所须要的信息,GraphicsLayer 则应用 GPU 的能力来减速页面的渲染。

渲染流水线

在浏览器创立了渲染树,会如何将这些信息出现在页面上,这就要提到渲染流水线。
对于上面的代码:

<body>
    <div id="button"> 点击减少 </div>
    <script>
        const btn = document.getElementById('button');
        btn.addEventListener('click', () => {const div = document.createElement("div");
            document.body.appendChild(div);
        });
    </script>
 </body>

在 DevTools 中的 Performance 标签能够记录并查看页面的渲染过程(所示图片宽度限度,没有截取合成线程获取事件输出局部)。

这个过程,与 Aerotwist – The Anatomy of a Frame 给出的渲染流水线的示意图简直是统一的。

渲染流水线的示意图中有两个过程:渲染过程(Renderer Process)和 GPU 过程(GPU Process)。
每个页面 Tab 都有独自的渲染过程,它包含以下的线程(池):

  • 合成线程(Compositor Thread): 负责接管浏览器的垂直同步信号(Vsync,批示前一帧的完结和后一帧的开始),也负责接管滚动、点击等用户输出。在应用 GPU 合成状况下,产生绘图指令。
  • 主线程(Main Tread): 浏览器的执行线程,咱们常见的 Javascript 计算,Layout,Paint 都在主线程中执行。
  • 光栅化线程池(Raster/Tile worker):可能有多个光栅化线程,用于将图块(tile)光栅化。(如果主线程只将页面内容转化为绘制指令列表,在在此执行绘制指令获取像素的色彩值)。

GPU 过程(GPU Process)不是在 GPU 中执行的,而是负责将渲染过程中绘制好的 tile 位图作为纹理上传至 GPU,最终绘制至屏幕上。

上面具体介绍下整个渲染流程:

1. 帧开始(Frame Start)

浏览器发送垂直同步信号(Vsync),表明新一帧的开始。

2. 解决输出事件(Input event handlers)

合成线程将输出事件传递给主线程,主线程解决各事件的回调(包含执行一些 Javascript 脚本)。在这里,所有的输出事件(例如 touchmovescrollclick)在每一帧只会被触发一次。

3. requestAnimiationFrame

如果注册了 requestAnimiationFrame(rAF)函数,rAF 函数将在这里执行。

4. HTML 解析(Parse HTML)

如果之前的操作造成了 DOM 节点的变更(例如 appendChild),则须要执行 HTML 解析。

5. 款式计算(Recalc Styles)

如果在之前的步骤中批改了 CSS 款式,浏览器须要从新计算批改的 DOM 节点以及子节点款式。

6. 布局(Layout)

计算每一个可见元素的尺寸、地位等几何信息。通常须要对整个 document 执行 Layout,局部 CSS 属性的批改不会触发 Layout(参考 CSS triggers)。防止大型、简单的布局和布局抖动 指出,对浏览器几何元计算,在 Chrome、Opera、Safari 和 Internet Explorer 中称为布局(Layout)。在 Firefox 中称为主动重排(Reflow),但实际上其过程是一样的。

7. 更新渲染树(Update Layer Tree)

接下来就须要更新渲染树。DOM 节点和 CSS 款式的扭转都会导致渲染树的扭转。

8. 绘制(Paint)

实际上的绘制有两步,这里指的是第一步:生成绘制指令。浏览器生成的绘制指令与 Canvas 提供的绘制 API 很类似。DevTools 中能够进行查看:

这些绘制指令造成了一个绘制列表,在 Paint 阶段输入的内容就是这些绘制列表(SkPicture)。

The SkPicture is a serializable data structure that can capture and then later replay commands, similar to a display list.

9. 合成(Composite)

在 DevTools 中这一步被称为 Composite Layers,主线程中的合成并不是真正的合成。主线程中保护了一份渲染树的拷贝(LayerTreeHost),在合成线程中也须要保护一份渲染树的拷贝(LayerTreeHostImpl)。有了这份拷贝,合成线程能够不用与主线程交互来进行合成操作。因而,当主线程在进行 Javascript 计算时,合成线程依然能够失常工作而不被打断。

在渲染树扭转后,须要进行着两个拷贝的同步,主线程将扭转后的渲染树和绘制列表发送给合成线程,同时阻塞主线程保障这个同步能失常进行,这就是 Composite Layers。这是渲染流水线中主线程的最初一步,换而言之,这一步只是生成了用于合成的数据,并不是真正的合成过程。

10. 光栅化(Raster Scheduled and Rasterize)

合成线程在收到主线程提交的信息(渲染树、绘制指令列表等),就将这些信息进行位图填充,转化为像素值,也就是光栅化。Webkit 提供了一个线程池来进行光栅化,线程池中线程数和平台和设施性能无关。因为合成层每一层大小是整个页面大小,所以在光栅化之前,须要先对页面进行宰割,将图层转化为图块(tile)。这些图块的大小通常是 256*256 或者 512*512。在 DevTools 的“More tools -> Rendering”中,抉择“Layer borders”能够查看。

上图展现了一个页面被划分的图块,橙色是合成层的边框,青色是分块信息。光栅化是针对于每一个图块进行的,不同图块有不同的光栅化优先级,通常位于浏览器视口(viewpoint)左近的图块会首先被光栅化(更具体的能够参考 [Tile Prioritization Design
](https://docs.google.com/docum…)。古代浏览器里,光栅化并不是在合成线程里进行的,渲染过程保护了一个光栅化的线程池,也就是图中的(Compositor Tile Workers),线程池中线程数取决于零碎和设施兼容性。

光栅化能够分为软件光栅化(Software Rasterization)和硬件光栅化(Hardware Rasterization),区别在于位图的生成是在 CPU 中进行,之后再上传至 GPU 合成,还是间接在 GPU 中进行绘图和图像素填充。硬件光栅化的过程如下图所示:

图片来自 Raster threads creating the bitmap of tiles and sending to GPU

咱们能够在 chrome://gpu/ 中查看 Chrome 的硬件光栅化是否开启。

11. 帧完结(Frame End)

图块的光栅化实现后,合成线程会收集被称为 draw quads 的图块信息用于创立合成帧(compositor frame)。合成帧被发送给 GPU 过程,这一帧完结。

Draw quads: Contains information such as the tile’s location in memory and where in the page to draw the tile taking in consideration of the page compositing.
Compositor frame: A collection of draw quads that represents a frame of a page.

12. 图像显示

GPU 过程负责与 GPU 通信,并实现最初图像的绘制。GPU 过程接管到合成帧,如果应用了硬件光栅化,光栅化的纹理曾经存储在 GPU 中。浏览器中提供了用于绘图的 3D API(如 Webkit 的 GraphicsContext3D 类)将各纹理合并绘制到同一个位图中。

前文中提过,对于设置了透明度等动画的元素,会独自晋升为合成层。而这些变动,理论是设置在合成层上的,在纹理合并前,浏览器通过 3D 变形作用到合成层上,即能够实现特定的成果。所以咱们才说应用 transfrom 和透明度属性的动画,能够进步渲染效率。因为这些动画在执行过程中,不会扭转布局构造和纹理,也就是不会触发后续的 Layout 和 Paint。

动画性能优化

下面介绍了浏览器的渲染流水线,然而并不是每次一次渲染都会触发整个流水线。其中的某些步骤也不肯定只会被触发一次。上面依照渲染流水线的程序,来介绍进步渲染效率的几种形式:

正当解决页面滚动

在浏览器的渲染流水线里,合成线程是用户输出事件的入口,当用户的输出事件产生时,合成线程须要确定是否须要由主线程参加后续渲染。比方当用户滚动页面,所有图层曾经被光栅化了,合成线程能够间接进行合成帧的生成,并不需要主线程的参加。如果用户在一些元素上绑定了事件处理,那么合成线程会标记这些区域为非疾速滚动区域(non-fast scrollable region)。当用户在非疾速滚动滚动区域产生输出事件时,合成线程会将此事件传递给主线程进行 Javascript 计算和后续解决。
在前端开发中,常常应用事件委托这种形式将一些元素的事件委托到其父元素或者更外层的元素上(例如 document),通过事件冒泡登程其外层元素的绑定事件,在外层元素上执行函数。事件委托能够缩小因为多个子元素绑定同样事件处理函数导致的内存耗费,也能反对动静绑定,在前端开发中利用很广。

如果应用事件委托的模式,在 document 上绑定事件处理,那么整个页面都会被标记为非疾速滚动区域。这就象征合成线程须要将每次用户输出事件都发送给主线程,期待主线程执行 Javascript 解决这些事件,之后再进行页面的合成和显示。在这种状况下,晦涩的页面滚动是很难实现的。

为了优化下面的问题,浏览器的 addEventListener 的第三个参数提供了{passive: true}(默认为 false),这个选项通知合成线程仍然须要将用户事件传递给主线程去解决,然而合成线程也会持续合成新的帧,不会被主线程的执行阻塞,此时事件处理函数中的 preventDefault 函数是有效的。

document.body.addEventListener('touchstart', event => {event.preventDefault(); // 并不会阻止默认的行为
 }, {passive: true});

图片来自 Inside look at modern web browser (part 4)

此外,在例如懒加载等业务场景中,常常须要监听页面滚动去判断相干元素是否处于视口中。常见的办法是应用 Element.getBoundingClientReact() 获取相干元素的边界信息,进而计算是否位于视口中。主线程中,每一帧都调用 Element.getBoundingClientReact() 会造成性能问题(例如不当应用导致页面强制重排)。Intersection Observer API 提供了一种异步检测指标元素与先人元素或视口相交状况变动的办法。这个 API 反对注册回调函数,当被监督的元素合其余元素的相交状况发生变化时触发回调。这样,就将相交的判断交给浏览器自行治理优化,进而进步滚动性能。

Javascript 优化

缩小主线程中 Javascript 的执行工夫

针对于帧率为 60FPS 的设施,每个帧须要在 16.66 毫秒内执行结束,如果无奈实现此需要,则会导致内容在屏幕上抖动,也就是卡顿,影响用户体验。在主线程中,须要对用户的输出进行计算,为了保障用户的体验,须要在主线程中防止长时间的计算,避免阻塞后续的流程。
优化 JavaScript 执行一文中,提出了以下几点来优化 Javascript:

  1. 对于动画成果的实现,防止应用 setTimeout 或 setInterval,请应用 requestAnimationFrame。
  2. 将长时间运行的 JavaScript 从主线程移到 Web Worker。
  3. 应用微工作来执行对多个帧的 DOM 更改。
  4. 应用 Chrome DevTools 的 Timeline 和 JavaScript 分析器来评估 JavaScript 的影响。

应用 setTimeout/setTimeInterval 来执行动画时,因为不确定回调会产生在渲染流水线中的哪个阶段,如果正好在开端,可能会导致丢帧。而渲染流水线中,rAF 会在 Javascript 之后、Layout 之前执行,不会产生上述的问题。而将纯计算的工作转移到 Web Worker,能够缩小主线程中 Javascript 的执行工夫。对于必须在主线程中执行的大型计算工作,能够思考将其宰割为微工作,并在每帧的 rAF 或者 RequestIdleCallback 中解决(参考 React Fiber 的实现)。

缩小不合理 Javascript 代码导致的强制重排(Force Layout)

渲染流水线中,Javascript/rAF 的操作可能会扭转渲染树,进而触发后续的 Layout。如果在 Javascript/rAF 中拜访了比方 el.style.backgroundImageel.style.offsetWidth 等布局属性或者计算属性,可能会触发强制重排(Force Layout),导致后续的 Recalc styles 或者 Layout 提至此步骤之前执行,影响渲染效率。

requestAnimationFrame(logBoxHeight);
function logBoxHeight() {box.classList.add('super-big');
  // 为了获取到 box 的 offsetHeight 值,浏览器须要在先利用 super-big 的款式扭转,而后进行布局(Layout)console.log(box.offsetHeight);
}

正当的做法是

function logBoxHeight() {console.log(box.offsetHeight);
  box.classList.add('super-big');
}

缩小 Layout 和 Paint

也就是陈词滥调的缩小重排和重绘。针对渲染流水线的 Layout、Paint 和合成这三个阶段,Layout 和 Paint 绝对比拟耗时。然而并不是所有的帧变动都须要通过残缺的渲染流水线:对于 DOM 节点的批改导致其尺寸和地位产生扭转时,会触发 Layout;而如果扭转并不影响它在文档流中的地位,浏览器不须要从新计算布局,只须要生成绘制列表,进行 Paint。Paint 是以合成层为单位的,一旦更改了某个会触发 Paint 的元素款式,该元素所在的合成层都会从新 Paint。因而,所以对于某些动画元素,能够将其晋升为独自的合成层,缩小 Paint 的范畴。

合成层晋升

在介绍渲染树的时候提到满足某些条件的 RenderObjectLayer 会被晋升为合成层,合成层的绘制是在 GPU 中进行的,比 CPU 的性能更好;如果该合成层须要 Paint,不会影响其余的合成层;一些合成层的动画,不会触发 Layout 和 Paint。上面介绍几种在开发中罕用的合成层晋升的形式:

应用 transformopacity书写动画

上文提出,如果一个元素应用了 CSS 通明成果的动画或者 CSS 变换的动画,那么它会被晋升为合成层。并且这些动画变换实际上是利用在合成层自身上。这些动画的执行过程不须要主线程的参加,在纹理合成前,应用 3D API 对合成层进行变形即可。

  #cube {transform: translateX(0);
      transition: transform 3s linear;
  }

  #cube.move {transform: translateX(100px);
  }
<body>
    <div id="button"> 点击挪动 </div>
    <div id="cube"></div>
    <script>
        const btn = document.getElementById('button');
        btn.addEventListener('click', () => {const cube = document.getElementById('cube');
            cube.classList = 'move';
        });
    </script>
 </body>

对于下面的动画,只有在动画开始后,才会进行合成层的晋升,动画完结后合成层晋升也会隐没。这也就防止了浏览器创立大量的合成层造成的 CPU 性能损耗。

will-change

这个属性通知了浏览器,接下来会对某些元素进行一些非凡变换。当 will-change 设置为 opacitytransformtopleftbottomright(其中 topleftbottomright 等须要设置明确的定位属性,如 relative 等),浏览器会将此元素进行合成层晋升。在书写过程中,须要防止以下的写法:

*{will-change: transform, opacity;}

这样,所有的元素都会被晋升为独自的合成层,造成大量的内存占用。所以须要只针对动画元素设定 will-change,且动画实现之后,须要手动将此属性移除。

Canvas

应用具备减速的 2D Context 或者 3D Contex 的 Canvas 来实现动画。因为具备独立的合成层,Canvas 的扭转不会影响其余合成层的绘制,这种状况对于大型简单动画(比方 HTML5 游戏)更为实用。此外,也能够设置多个 Canvas 元素,通过正当的 Canvas 分层来缩小绘制开销。

CSS 容器模块

CSS 容器模块(CSS Containment Module)最近刚公布了 Level 3 版本。次要指标通过将特定的 DOM 元素和整个文档的 DOM 树隔离开来,使其元素的更改不会影响文档的其余局部,进而进步页面的渲染性能。CSS 容器模块次要提供了两个属性来反对这样的优化。

contain

contain 属性容许开发者指定特定的 DOM 元素独立于 DOM 树以外。针对这些 DOM 元素,浏览器能够独自计算他们的布局、款式、大小等。所以当定义了 contain 属性的 DOM 元素产生扭转后,不会造成整体渲染树的扭转,导致整个页面的 Layout 和 Paint。
contain 有以下的取值:

layout

contain 值为 layout 的元素的布局将与页面整体布局独立,元素的扭转不会导致页面的 Layout。

paint

contain值为 paint 的 DOM 节点,表明其子元素不会超出其边界进行展现。因而如果一个 DOM 节点是离屏或者不可见的,它的子元素能够被确保是不可见的。它还有以下作用:

  • 对于 position 值为 fixed 或者 absolute 的子节点,contain 值为 paint 的 DOM 节点成为了一个蕴含块(containing block)。
  • contain 值为 paint 的 DOM 节点会创立一个层叠上下文。
  • contain 值为 paint 的 DOM 节点会创立一个格式化上下文(BFC)。

size

contain值为 size 的 DOM 节点,它的 size 不会受其子节点的影响。

style

contain值为 style 的 DOM 节点,表明其 CSS 属性不会影响其子节点以外的其余元素。

inline-size

inline-size 是 Level 3 最新减少的值。contain 值为 inline-size 的 DOM 节点,它的 principal box 的内联轴的 intrinsic-size 不受内容影响。

strict

等同于 contain: size layout paint

content

等同于 contain: layout paint

在具备大量 DOM 节点的简单页面中,对没有在独自的合成层中的 DOM 元素进行批改会造成整个页面的 Layout 和 Paint,此时,对这些元素设置 contain 属性(比方 contain:strict)能够显著进步页面性能。

An introduction to CSS Containment 中给出了一个长列表例子,将长列表中的第一个 itemcontain 属性设置为 strict,并扭转这个 item 的内容,在此前后手动触发页面的强制重排。绝对于没有设置为 strict,Javascript 的执行工夫从 4.37ms 升高到 0.43ms,渲染性能有了很大的晋升。contain 的浏览器反对状况如下所示:

content-visibility

contain 属性须要咱们在开发的时候就确定 DOM 元素是否须要进行渲染上的优化,并设定适合的值。content-visibility 则提供了另外一种形式,将它设定为 auto,则浏览器能够主动进行优化。上文中提到,合成线程会对每个页面大小的图层转化为图块(tile),而后针对于图块,依照肯定的优先级进行光栅化,浏览器会渲染所有可能被用户查看的元素。content-visibility 的值设置为 auto 的元素,在离屏状况下,浏览器会计算它的大小,用来正确展现滚动条等页面构造,然而浏览器不必对其子元素生成渲染树,也就是说它的子元素不会被渲染。当页面滚动使其呈现在视口中时,浏览器才开始对其子元素进行渲染。
然而这样也会导致一个问题:content-visibility 的值设置为 auto 的元素,离屏状态下,浏览器不会对其子元素进行 Layout,因而也无奈确定其子元素的尺寸,这时如果没有显式指定尺寸,它的尺寸会是 0,这样就会导致整个页面高度和滚动条的显示出错。为了解决这个问题,CSS 提供了另外一个属性 contain-intrinsic-size来设置 content-visibility 的值为 auto时的元素的占位大小。这样,即便其没有显式设置尺寸,也能保障在页面 Layout 时元素依然占据空间。

.ele {
    content-visibility: auto;
    contain-intrinsic-size: 100px;
}

content-visibility: the new CSS property that boosts your rendering performance 给出了一个旅行博客的例子,通过正当设置 content-visibility,页面的首次加载性能有了 7 倍的晋升。content-visibility 的浏览器反对状况如下所示:

总结

对于浏览器渲染机制,曾经有大量的文章介绍。然而局部文章,尤其是波及到浏览器内核的局部比拟艰涩。本文从浏览器底层渲染登程,具体介绍了渲染树和渲染流水线。之后依照渲染流水线的程序,介绍了进步动画性能的形式:正当解决页面滚动、Javascript 优化、缩小 Layout 和 Paint。心愿对大家了解浏览器的渲染机制和日常的动画开发有所帮忙。

参考文章

  1. Webkit 技术底细 —— 朱永盛
  2. GPU Accelerated Compositing in Chrome
  3. Compositing in Blink / WebCore: From WebCore::RenderLayer to cc:Layer
  4. 无线性能优化:Composite
  5. The Anatomy of a Frame
  6. 防止大型、简单的布局和布局抖动
  7. Software vs. GPU Rasterization in Chromium
  8. 优化 JavaScript 执行
  9. 浏览器渲染流水线解析与网页动画性能优化
  10. Tile Prioritization Design
  11. CSS Containment Module Level 3
  12. Let’s Take a Deep Dive Into the CSS Contain Property
  13. CSS triggers
  14. 浏览器渲染具体过程:重绘、重排和 composite 只是冰山一角
  15. 仅应用 CSS 进步页面渲染速度

本文公布自 网易云音乐大前端团队,文章未经受权禁止任何模式的转载。咱们长年招收前端、iOS、Android,如果你筹备换工作,又恰好喜爱云音乐,那就退出咱们 grp.music-fe (at) corp.netease.com!

正文完
 0