4.网页渲染优化

渲染优化

不管我们是写的Css样式代码还是JavaScript脚本代码,最后都是靠浏览器渲染出来的,要想浏览器渲染的更快更稳点,针对渲染优化是必不可少的。

浏览器如何渲染页面

浏览器将域名通过网络通信从服务器拿到html文件后,将会执行以下操作:

构建DOM树及CSSOM树

根据html文件构建DOM树和CSSOM树。构建DOM树期间,如果遇到JS,阻塞DOM树及CSSOM树的构建,优先加载JS文件,加载完毕,再继续构建DOM树及CSSOM树。

构建DOM树

HTML 文档中的所有内容皆是节点,各节点之间拥有层级关系,如父子关系、兄弟关系等,彼此相连,构成DOM树。最常见的几种节点有:文档节点元素节点文本节点属性节点注释节点

DOM节点树中节点与HTML文档中内容一一对应,DOM树构建过程:读取html文档将字节转换成字符,确定tokens(标签)再将tokens转换成节点以节点构建 DOM 树。如下图所示:
在这里插入图片描述

构建CSSOM树

CSS文档中,所有元素皆是节点,与HTML文件中的标签节点一一对应。CSS中各节点之间同样拥有层级关系,如父子关系、兄弟关系等,彼此相连,构成CSSOM树。
在构建DOM树的过程中,在 HTML 文档的 head 标签中遇到 link 标签,该标签引用了一个外部CSS样式表。由于预见到需要利用该CSS资源来渲染页面,浏览器会立即发出对该CSS资源的请求,并进行CSSDOM树的构建。
CSSOM树构建过程与DOM树构建流程一致:读取CSS文档,将字节转换成字符,确定tokens(标签),再将tokens转换成节点,以节点构建 CSSOM 树。如下图所示:
在这里插入图片描述

构建渲染树

渲染树(Render Tree)由DOM树、CSSOM树合并而成,但并不是必须等DOM树及CSSOM树加载完成后才开始合并构建渲染树。三者的构建并无先后条件,亦非完全独立,而是会有交叉,并行构建。因此会形成一边加载,一边解析,一边渲染的工作现象。
构建渲染树,根据渲染树计算每个可见元素的布局(Layout),并输出到绘制流程(Paint),将像素发送给GPU,渲染展示到屏幕上。

关键渲染路径 (Critical Rendering Path)

现代浏览器的渲染流程大致如下:
JavaScript->Style->Layout->Paint->Composite

  1. JavaScript : 一切能改变元素相关属性发生变化的操作,例如脚本修改元素的宽高,或者是animation
  2. Style:计算样式
  3. Layout:根据生成的渲染树,计算他们在设备视口(viewport)的确切位置和大小。
  4. Paint:根据渲染树和Layout阶段得到的几何信息,绘制额外的属性(比如color,box-shadow等等),得到节点绝对像素。
  5. Composite:复合图层(类似于PS的图层一样,合成输出)。

现在可以得知现代浏览器到底是如何渲染HTML文件和中间经历了哪些过程。知道了这些我们就可以针对性的作出渲染优化。

重排和重绘

什么是重排和重绘,在面试的时候经常会被面试官所问到,简单地介绍以下:

重排(reflow):也有称回流,当渲染树节点发生改变,影响了节点的几何属性(如宽、高、内边距、外边距、或是float、position、display:none;等等),导致节点位置发生变化,此时触发浏览器重排(reflow),需要重新生成渲染树。譬如JS为某个p标签节点添加新的样式:“display:none;”。导致该p标签被隐藏起来,该p标签之后的所有节点位置都会发生改变。此时浏览器需要重新生成渲染树,重新布局,即重排(reflow)。

重绘(repaint):屏幕的一部分要重新绘制。渲染树节点发生改变,但不影响该节点在页面当中的空间位置及大小。譬如某个div标签节点的背景颜色、字体颜色等等发生改变,但是该div标签节点的宽、高、内外边距并不发生变化,此时触发浏览器重绘(repaint)。

可以得出重排对应的渲染阶段就是Layout,而重绘对应的渲染阶段就是Paint。两者的关系:重排必将引起重绘,而重绘不一定会引起重排。

看一个简单的例子:

.root{
    width: 100px;
    height: 200px;
    background-color: red;
    position: relative;
    left: 0;
    top: 0;
  }
  
  .bgY{
    background-color: yellow;
  }
  
  .t200{
    top: 200px;
  }
  .y200{
    transform: translateY(200px);
  }
import { useState } from 'react';
import './Home.css';

export default () => {

    const [bg, setBg] = useState(false);
    const [t, setT] = useState(false);
    const [y, setY] = useState(false);

    let classS = '';
    if (bg) {
        classS = `${classS} bgY`;
    }
    if (t) {
        classS = `${classS} t200`;
    }
    if (y) {
        classS = `${classS} y200`;
    }
    return (
        <>
            <div className={`root ${classS}`}>
            </div>
            <button onClick={() => setBg(true)}>改变背景颜色</button>
            <button onClick={() => setT(true)}>改变top</button>
            <button onClick={() => setY(true)}>改变Transform</button>
        </>
    )
}

初始加载效果:
在这里插入图片描述
通过Performance查看加载的时候渲染经历了哪些阶段
在这里插入图片描述
点击改变背景颜色看看经历了那几个阶段:
在这里插入图片描述
可以看出来没有经历Layout阶段,也就是没有引起重排。

点击改变宽度按钮看看经历了哪几个阶段:
在这里插入图片描述
看看点击改变Transform:
在这里插入图片描述
很明显没有Layout阶段,即没有进行重排,这里实现了跟改变元素的top值,以改变元素的位置的一样操作,但是使用transfrom没有进行重排,加快了渲染。

优化重排

现在知道了重排会经历浏览器渲染的多个阶段,所以日常开发中要尽可能避免或者减少重排操作。首先就就必须要知道哪些操作会引起重排。

  1. 添加/删除元素
  2. 操作相关styles:边距、填充、边框、宽度和高度等
  3. 读取DOM元素的相关位置信息:offsetTop,scrollTop,clientWidth等
  4. 移动元素位置:display、float、position、overflow等
  5. 修改浏览器大小,字体大小:resize事件发生时等
  6. 内容改变:比如文本改变或者图片大小改变而引起的计算值宽度和高度改变等。

一个节点的重排很有可能导致子节点,甚至父节点以及兄弟节点的重绘。在一些高性能的电脑上也许还没什么,但是如果重绘制发生在手机上,那么这个过程是延慢加载和耗电的。重排带来的问题之一就是布局抖动(layout thrashing)

解决方案:

  1. 直接改变className,如果动态改变样式,则使用cssText(考虑没有优化的浏览器);
  2. 让要操作的元素进行”离线处理”,处理完后一起更新;
    a) 使用DocumentFragment进行缓存操作,引发一次回流和重绘;
    b) 使用display:none技术,只引发两次回流和重绘;
    c) 使用cloneNode(true or false) 和 replaceChild 技术,引发一次回流和重绘;
  3. 不要经常访问会引起浏览器flush队列的属性(offsetTop,scrollTop,clientWidth等),如果你确实要访问,利用缓存。
  4. 让元素脱离动画流,减少回流的渲染树的规模;
  5. 改变元素的位置信息和尺寸等属性,尽可能使用transform来完成。
  6. 对于需要读取3中介绍的属性来完成相关动画的操作,请保持DOM元素操作的读写分离。推荐使用fastdom

优化重绘

对于重绘的避免和减少,实际上就是针对重排的优化。设想一下,鼠标放上元素,元素显示高亮这种操作,是肯定会重绘的,基本上除了重排引起的重绘外,都是不可避免的。一句话,搞定重排的问题就好了。

复合线程(conpositor thread)和图层(layers)

在渲染的时候,会有一个复合操作:
在这里插入图片描述
这个CompositeLayers操作就是用复合线程来操作的,复合线程所做的事情:将页面拆分图层绘制在进行复合。
所以对DOM元素的相关操作最好弄在不同的图层上进行操作,这样就可以避免不必要的重排和重绘,优化渲染。影响复合的样式操作如下:

  1. position的方位值操作由transform:translate操作来代替
  2. 元素的尺寸操作由transform:scale操作来代替
  3. 旋转操作由transform:rotate操作来代替
  4. 使用opactiy代替display,visibility操作

通过Chrome Devtools我们可以查看网站的图层分布情况:
在这里插入图片描述
通过css设置will-change属性可以新增图层:
在这里插入图片描述
新增图层会优化渲染,但是请不要过度使用,毕竟新建图层也是有性能消耗的。

高频事件处理函数(防抖)

注意这里只是防抖,不要去想节流的操作。对于scroll,touchMove,mouseMove等高频事件,在日常开发中是经常使用到的。通常对于这些操作我们都需要进行防抖,网上关于防抖的介绍一大推,这里不做过多介绍,只介绍几个场景,帮助我们合理使用哪一种防抖操作。

  1. setTimeout:对于执行时间的精确度不太高的场景(页面切换会继续执行)
  2. requestAnimationFrame:浏览器每一帧执行一次(页面切换会停止执行-会在layout和paint之前执行,考虑兼容性的话,需要使用setTimeout进行polyfill,)
  3. requestidlecallback (浏览器每一帧任务执行完成以后,还有空闲时间-IDLE,就执行,兼容性不太行,可以参考React源码中使用的scheduler模块进行polyfill)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值