首页科学探索前端性能优化——首页资源压缩63%、白屏时间缩短86%
45116

前端性能优化——首页资源压缩63%、白屏时间缩短86%

我要新鲜事2023-05-13 16:45:470

提升首屏的加载速度,是前端性能优化中最重要的环节,这里笔者梳理出一些 常规且有效 的首屏优化建议

目标: 通过对比优化前后的性能变化,来验证方案的有效性,了解并掌握其原理

SPA 项目,一个路由对应一个页面,如果不做处理,项目打包后,会把所有页面打包成一个文件,当用户打开首页时,会一次性加载所有的资源,造成首页加载很慢,降低用户体验

列一个实际项目的打包详情:

app.js 初始体积: 1175 KBapp.css 初始体积: 274 KB

将路由全部改成懒加载

// 通过webpackChunkName设置分割后代码块的名字

const Home = () => import(/* webpackChunkName: "home" */ "@/views/home/index.vue");

const MetricGroup = () => import(/* webpackChunkName: "metricGroup" */ "@/views/metricGroup/index.vue");

…………

const routes = [

{

path: "/",

name: "home",

component: Home

},

{

path: "/metricGroup",

name: "metricGroup",

component: MetricGroup

},

…………

]

复制代码

重新打包后,首页资源拆分为 app.js 和 home.js,以及对应的 css 文件

app.js:244 KB、 home.js: 35KBapp.css:67 KB、home.css: 15KB

通过路由懒加载,该项目的首页资源压缩约 52%

懒加载前提的实现:ES6的动态地加载模块——import()

调用 import() 之处,被作为分离的模块起点,意思是,被请求的模块和它引用的所有子模块,会分离到一个单独的 chunk 中

——摘自《webpack——模块方法》的import()小节

要实现懒加载,就得先将进行懒加载的子模块分离出来,打包成一个单独的文件

webpackChunkName 作用是 webpack 在打包的时候,对异步引入的库代码(lodash)进行代码分割时,设置代码块的名字。webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中

除了路由的懒加载外,组件的懒加载在很多场景下也有重要的作用

举个:

home 页面 和 about 页面,都引入了 dialogInfo 弹框组件,该弹框不是一进入页面就加载,而是需要用户手动触发后才展示出来

home 页面示例:

<template>

<div class="homeView">

<p>home 页面</p>

<el-button @click="dialogVisible = !dialogVisible">打开弹框</el-button>

<dialogInfo v-if="dialogVisible" />

</div>

</template>

<script>

import dialogInfo from '@/components/dialogInfo';

export default {

name: 'homeView',

components: {

dialogInfo

}

}

</script>

复制代码

项目打包后,发现 home.js 和 about.js 均包括了该弹框组件的代码(在 dist 文件中搜索dialogInfo弹框组件)

当用户打开 home 页时,会一次性加载该页面所有的资源,我们期望的是用户触发按钮后,再加载该弹框组件的资源

这种场景下,就很适合用懒加载的方式引入

弹框组件懒加载:

<script>

const dialogInfo = () => import(/* webpackChunkName: "dialogInfo" */ '@/components/dialogInfo');

export default {

name: 'homeView',

components: {

dialogInfo

}

}

</script>

复制代码

重新打包后,home.js 和 about.js 中没有了弹框组件的代码,该组件被独立打包成 dialogInfo.js,当用户点击按钮时,才会去加载 dialogInfo.js 和 dialogInfo.css

最终,使用组件路由懒后,该项目的首页资源进一步减少约 11%

有时资源拆分的过细也不好,可能会造成浏览器 http 请求的增多

总结出三种适合组件懒加载的场景:

1)该页面的 JS 文件体积大,导致页面打开慢,可以通过组件懒加载进行资源拆分,利用浏览器并行下载资源,提升下载速度(比如首页)

2)该组件不是一进入页面就展示,需要一定条件下才触发(比如弹框组件)

3)该组件复用性高,很多页面都有引入,利用组件懒加载抽离出该组件,一方面可以很好利用缓存,同时也可以减少页面的 JS 文件大小(比如表格组件、图形组件等)

Tree shaking 的作用:消除无用的 JS 代码,减少代码体积

举个:

// util.js

export function targetType(target) {

return Object.prototype.toString.call(target).slice(8, -1).toLowerCase();

}

export function deepClone(target) {

return JSON.parse(JSON.stringify(target));

}

复制代码

项目中只使用了 targetType 方法,但未使用 deepClone 方法,项目打包后,deepClone 方法不会被打包到项目里

tree-shaking 原理:

依赖于ES6的模块特性,ES6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,这就是 tree-shaking 的基础

静态分析就是不需要执行代码,就可以从字面量上对代码进行分析。ES6之前的模块化,比如 CommonJS 是动态加载,只有执行后才知道引用的什么模块,就不能通过静态分析去做优化,正是基于这个基础上,才使得 tree-shaking 成为可能

并不是说所有无用的代码都可以被消除,还是上面的代码,换个写法 tree-shaking 就失效了

// util.js

export default {

targetType(target) {

return Object.prototype.toString.call(target).slice(8, -1).toLowerCase();

},

deepClone(target) {

return JSON.parse(JSON.stringify(target));

}

};

// 引入并使用

import util from '../util';

util.targetType(null)

复制代码

同样的,项目中只使用了 targetType 方法,未使用 deepClone 方法,项目打包后,deepClone 方法还是被打包到项目里

在 dist 文件中搜索 deepClone 方法:

究其原因,export default 导出的是一个对象,无法通过静态分析判断出一个对象的哪些变量未被使用,所以 tree-shaking 只对使用 export 导出的变量生效

这也是函数式编程越来越火的原因,因为可以很好利用 tree-shaking 精简项目的体积,也是 vue3 全面拥抱了函数式编程的原因之一

使用骨架屏,可以缩短白屏时间,提升用户体验。国内大多数的主流网站都使用了骨架屏,特别是手机端的项目

SPA 单页应用,无论 vue 还是 react,最初的 html 都是空白的,需要通过加载 JS 将内容挂载到根节点上,这套机制的副作用:会造成长时间的白屏

常见的骨架屏插件就是基于这种原理,在项目打包时将骨架屏的内容直接放到 html 文件的根节点中

使用骨架屏插件,打包后的 html 文件(根节点内部为骨架屏):

同一项目,对比使用骨架屏前后的 FP 白屏时间:

无骨架屏:白屏时间 1063ms

有骨架屏:白屏时间 144ms

骨架屏确实是优化白屏的不二选择,白屏时间缩短了 86%

这里以 vue-skeleton-webpack-plugin 插件为例,该插件的亮点是可以给不同的页面设置不同的骨架屏,这点确实很酷

1)安装

npm i vue-skeleton-webpack-plugin

复制代码

2)vue.config.js 配置

// 骨架屏

const SkeletonWebpackPlugin = require("vue-skeleton-webpack-plugin");

module.exports = {

configureWebpack: {

plugins: [

new SkeletonWebpackPlugin({

// 实例化插件对象

webpackConfig: {

entry: {

app: path.join(__dirname, './src/skeleton.js') // 引入骨架屏入口文件

}

},

minimize: true, // SPA 下是否需要压缩注入 HTML 的 JS 代码

quiet: true, // 在服务端渲染时是否需要输出信息到控制台

router: {

mode: 'hash', // 路由模式

routes: [

// 不同页面可以配置不同骨架屏

// 对应路径所需要的骨架屏组件id,id的定义在入口文件内

{ path: /^\/home(?:\/)?/i, skeletonId: 'homeSkeleton' },

{ path: /^\/detail(?:\/)?/i, skeletonId: 'detailSkeleton' }

]

}

})

]

}

}

复制代码

3)新建 skeleton.js 入口文件

// skeleton.js

import Vue from "vue";

// 引入对应的骨架屏页面

import homeSkeleton from "./views/homeSkeleton";

import detailSkeleton from "./views/detailSkeleton";

export default new Vue({

components: {

homeSkeleton,

detailSkeleton,

},

template: `

<div>

<homeSkeleton id="homeSkeleton" style="display:none;" />

<detailSkeleton id="detailSkeleton" style="display:none;" />

</div>

`,

});

复制代码

首页中不乏有需要渲染长列表的场景,当渲染条数过多时,所需要的渲染时间会很长,滚动时还会造成页面卡顿,整体体验非常不好

虚拟滚动——指的是只渲染可视区域的列表项,非可见区域的不渲染,在滚动时动态更新可视区域,该方案在优化大量数据渲染时效果是很明显的

虚拟滚动图例:

虚拟滚动基本原理:

计算出 totalHeight 列表总高度,并在触发时滚动事件时根据 scrollTop 值不断更新 startIndex 以及 endIndex ,以此从列表数据 listData 中截取对应元素

虚拟滚动性能对比:

在不使用虚拟滚动的情况下,渲染10万个文本节点:使用虚拟滚动的情况后:

使用虚拟滚动使性能提升了 78%

虚拟滚动的插件有很多,比如 vue-virtual-scroller、vue-virtual-scroll-list、react-tiny-virtual-list、react-virtualized 等

这里简单介绍 vue-virtual-scroller 的使用

// 安装插件

npm install vue-virtual-scroller

// main.js

import VueVirtualScroller from 'vue-virtual-scroller'

import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'

Vue.use(VueVirtualScroller)

// 使用

<template>

<RecycleScroller

class="scroller"

:items="list"

:item-size="32"

key-field="id"

v-slot="{ item }">

<div class="user"> {{ item.name }} </div>

</RecycleScroller>

</template>

复制代码

该插件主要有 RecycleScroller.vue、DynamicScroller.vue 这两个组件,其中 RecycleScroller 需要 item 的高度为静态的,也就是列表每个 item 的高度都是一致的,而 DynamicScroller 可以兼容 item 的高度为动态的情况

由于浏览器 GUI 渲染线程与 JS 引擎线程是互斥的关系,当页面中有很多长任务时,会造成页面 UI 阻塞,出现界面卡顿、掉帧等情况

查看页面的长任务:

打开控制台,选择 Performance 工具,点击 Start 按钮,展开 Main 选项,会发现有很多红色的三角,这些就属于长任务(长任务:执行时间超过50ms的任务)

测试实验:

如果直接把下面这段代码直接丢到主线程中,计算过程中页面一直处于卡死状态,无法操作

let sum = 0;

for (let i = 0; i < 200000; i ) {

for (let i = 0; i < 10000; i ) {

sum = Math.random()

}

}

复制代码

使用 Web Worker 执行上述代码时,计算过程中页面正常可操作、无卡顿

// worker.js

onmessage = function (e) {

// onmessage获取传入的初始值

let sum = e.data;

for (let i = 0; i < 200000; i ) {

for (let i = 0; i < 10000; i ) {

sum = Math.random()

}

}

// 将计算的结果传递出去

postMessage(sum);

}

复制代码

Web Worker 具体的使用与案例,详情见 一文彻底了解Web Worker,十万、百万条数据都是弟弟

并不是执行时间超过 50ms 的任务,就可以使用 Web Worker,还要先考虑通信时长的问题

假如一个运算执行时长为 100ms,但是通信时长为 300ms, 用了 Web Worker可能会更慢

比如新建一个 web worker, 浏览器会加载对应的 worker.js 资源,下图中的 Time 是这个资源的通信时长(也叫加载时长)

当任务的运算时长 - 通信时长 > 50ms,推荐使用Web Worker

requestAnimationFrame 是浏览器专门为动画提供的 API,它的刷新频率与显示器的频率保持一致,使用该 api 可以解决用 setTimeout/setInterval 制作动画卡顿的情况

下面的案例演示了两者制作进度条的对比(运行按钮可点击)

可以看到使用定时器制作的动画,卡顿还是比较明显的

setTimeout/setInterval、requestAnimationFrame 三者的区别:

1)引擎层面

setTimeout/setInterval 属于 JS引擎,requestAnimationFrame 属于 GUI引擎

JS引擎与GUI引擎是互斥的,也就是说 GUI 引擎在渲染时会阻塞 JS 引擎的计算

2)时间是否准确

requestAnimationFrame 刷新频率是固定且准确的,但 setTimeout/setInterval 是宏任务,根据事件轮询机制,其他任务会阻塞或延迟js任务的执行,会出现定时器不准的情况

3)性能层面

当页面被隐藏或最小化时,setTimeout/setInterval 定时器仍会在后台执行动画任务,而使用 requestAnimationFrame 当页面处于未激活的状态下,屏幕刷新任务会被系统暂停

<script src="index.js"></script>

复制代码

这种情况下 JS 会阻塞 dom 渲染,浏览器必须等待 index.js 加载和执行完成后才能去做其它事情

<script async src="index.js"></script>

复制代码

async 模式下,它的加载是异步的,JS 不会阻塞 DOM 的渲染,async 加载是无顺序的,当它加载结束,JS 会立即执行

使用场景:若该 JS 资源与 DOM 元素没有依赖关系,也不会产生其他资源所需要的数据时,可以使用async 模式,比如埋点统计

<script defer src="index.js"></script>

复制代码

defer 模式下,JS 的加载也是异步的,defer 资源会在 DOMContentLoaded 执行之前,并且 defer 是有顺序的加载

如果有多个设置了 defer 的 script 标签存在,则会按照引入的前后顺序执行,即便是后面的 script 资源先返回

所以 defer 可以用来控制 JS 文件的执行顺序,比如 element-ui.js 和 vue.js,因为 element-ui.js 依赖于 vue,所以必须先引入 vue.js,再引入 element-ui.js

<script defer src="vue.js"></script>

<script defer src="element-ui.js"></script>

复制代码

defer 使用场景:一般情况下都可以使用 defer,特别是需要控制资源加载顺序时

<script type="module">import { a } from './a.js'</script>

复制代码

在主流的现代浏览器中,script 标签的属性可以加上 type="module",浏览器会对其内部的 import 引用发起 HTTP 请求,获取模块内容。这时 script 的行为会像是 defer 一样,在后台下载,并且等待 DOM 解析

Vite 就是利用浏览器支持原生的 es module 模块,开发时跳过打包的过程,提升编译效率

<link rel="preload" as="script" href="index.js">

复制代码

link 标签的 preload 属性:用于提前加载一些需要的依赖,这些资源会优先加载(如下图红框)

vue2 项目打包生成的 index.html 文件,会自动给首页所需要的资源,全部添加 preload,实现关键资源的提前加载

preload 特点:

1)preload 加载的资源是在浏览器渲染机制之前进行处理的,并且不会阻塞 onload 事件;

2)preload 加载的 JS 脚本其加载和执行的过程是分离的,即 preload 会预加载相应的脚本代码,待到需要时自行调用;

<link rel="prefetch" as="script" href="index.js">

复制代码

prefetch 是利用浏览器的空闲时间,加载页面将来可能用到的资源的一种机制;通常可以用于加载其他页面(非首页)所需要的资源,以便加快后续页面的打开速度

prefetch 特点:

1)pretch 加载的资源可以获取非当前页面所需要的资源,并且将其放入缓存至少5分钟(无论资源是否可以缓存)

2)当页面跳转时,未完成的 prefetch 请求不会被中断

async、defer 是 script 标签的专属属性,对于网页中的其他资源,可以通过 link 的 preload、prefetch 属性来预加载

如今现代框架已经将 preload、prefetch 添加到打包流程中了,通过灵活的配置,去使用这些预加载功能,同时我们也可以审时度势地向 script 标签添加 async、defer 属性去处理资源,这样可以显著提升性能

平常大部分性能优化工作都集中在 JS 方面,但图片也是页面上非常重要的部分

特别是对于移动端来说,完全没有必要去加载原图,浪费带宽。如何去压缩图片,让图片更快的展示出来,有很多优化工作可以做

淘宝首页的图片资源都很小:

很多云服务,比如阿里云或七牛云,都提供了图片的动态裁剪功能,效果很棒,确实是钱没有白花

只需在图片的url地址上动态添加参数,就可以得到你所需要的尺寸大小,比如:http://7xkv1q.com1.z0.glb.clouddn.com/grape.jpg?imageView2/1/w/200/h/200

图片瘦身前后对比:

原图:1.8M

裁剪后:12.8KB

经过动态裁剪后的图片,加载速度会有非常明显的提升

对于一些图片量比较大的首页,用户打开页面后,只需要呈现出在屏幕可视区域内的图片,当用户滑动页面时,再去加载出现在屏幕内的图片,以优化图片的加载效果

图片懒加载实现原理:

由于浏览器会自动对页面中的 img 标签的 src 属性发送请求并下载图片,可以通过 html5 自定义属性 data-xxx 先暂存 src 的值,然后在图片出现在屏幕可视区域的时候,再将 data-xxx 的值重新赋值到 img 的 src 属性即可

<img src="" alt="" data-src="./images/1.jpg">

<img src="" alt="" data-src="./images/2.jpg">

复制代码

这里以 vue-lazyload 插件为例

// 安装

npm install vue-lazyload

// main.js 注册

import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload)

// 配置项

Vue.use(VueLazyload, {

preLoad: 1.3,

error: 'dist/error.png', // 图片加载失败时的占位图

loading: 'dist/loading.gif', // 图片加载中时的占位图

attempt: 1

})

// 通过 v-lazy 指令使用

<ul>

<li v-for="img in list">

<img v-lazy="img.src" :key="img.src" >

</li>

</ul>

复制代码

字体图标是页面使用小图标的不二选择,最常用的就是 iconfont

字体图标的优点:

1)轻量级:一个图标字体要比一系列的图像要小。一旦字体加载了,图标就会马上渲染出来,减少了 http 请求

2)灵活性:可以随意的改变颜色、产生阴影、透明效果、旋转等

3)兼容性:几乎支持所有的浏览器,请放心使用

将小图片转换为 base64 编码字符串,并写入 HTML 或者 CSS 中,减少 http 请求

转 base64 格式的优缺点:

1)它处理的往往是非常小的图片,因为 Base64 编码后,图片大小会膨胀为原文件的 4/3,如果对大图也使用 Base64 编码,后者的体积会明显增加,即便减少了 http 请求,也无法弥补这庞大的体积带来的性能开销,得不偿失

2)在传输非常小的图片的时候,Base64 带来的文件体积膨胀、以及浏览器解析 Base64 的时间开销,与它节省掉的 http 请求开销相比,可以忽略不计,这时候才能真正体现出它在性能方面的优势

项目可以使用 url-loader 将图片转 base64:

// 安装

npm install url-loader --save-dev

// 配置

module.exports = {

module: {

rules: [{

test: /.(png|jpg|gif)$/i,

use: [{

loader: 'url-loader',

options: {

// 小于 10kb 的图片转化为 base64

limit: 1024 * 10

}

}]

}]

}

};

复制代码

“春江水暖鸭先知,产品好坏客户知”,作为前端开发,我们更注重客户体验,产品的好坏决定着客户的体验,那么一款产品的好坏有很多因素,其中性能是决定因素,那么怎么优化才能让产品的性能达到优良,让客户体验良好,今天我就带大家去了解学习前端性能优化

优化的目的在于让页面加载的更快,对用户操作响应更及时,为用户带来更好的用户体验,对于开发者来说优化能够减少页面请求数,能够节省资源。

前端优化的方法有很多种,可以将其分为两大类,第一类是页面级别的优化如http请求数,内联脚本的位置优化等,第二类为代码级别的优化,例Javascript中的DOM 操作优化、CSS选择符优化、图片优化以及 HTML结构优化等等。

那么我们需要优化哪些点呢?

加载资源优化渲染优化浏览器缓存策略图片优化节流与防抖

说起加载,当我们输入URL时,我们要知道这中间发生了什么?

首先做 DNS 查询,如果这一步做了智能 DNS 解析的话,会提供访问速度最快的 IP 地址回来接下来是 TCP 握手,应用层会下发数据给传输层,这里 TCP 协议会指明两端的端口号,然后下发给网络层。网络层中的 IP 协议会确定 IP 地址,并且指示了数据传输中如何跳转路由器。然后包会再被封装到数据链路层的数据帧结构中,最后就是物理层面的传输了TCP 握手结束后会进行 TLS 握手,然后就开始正式的传输数据数据在进入服务端之前,可能还会先经过负责负载均衡的服务器,它的作用就是将请求合理的分发到多台服务器上,这时假设服务端会响应一个 HTML 文件首先浏览器会判断状态码是什么,如果是 200 那就继续解析,如果 400 或 500 的话就会报错,如果 300 的话会进行重定向,这里会有个重定向计数器,避免过多次的重定向,超过次数也会报错浏览器开始解析文件,如果是 gzip 格式的话会先解压一下,然后通过文件的编码格式知道该如何去解码文件文件解码成功后会正式开始渲染流程,先会根据 HTML 构建 DOM 树,有 CSS 的话会去构建 CSSOM 树。如果遇到 script 标签的话,会判断是否存在 async 或者 defer ,前者会并行进行下载并执行 JS,后者会先下载文件,然后等待 HTML 解析完成后顺序执行,如果以上都没有,就会阻塞住渲染流程直到 JS 执行完毕。遇到文件下载的会去下载文件,这里如果使用 HTTP 2.0 协议的话会极大的提高多图的下载效率。初始的 HTML 被完全加载和解析后会触发 DOMContentLoaded 事件CSSOM 树和 DOM 树构建完成后会开始生成 Render 树,这一步就是确定页面元素的布局、样式等等诸多方面的东西在生成 Render 树的过程中,浏览器就开始调用 GPU 绘制,合成图层,将内容显示在屏幕上了

我们从输入 URL 到显示页面这个过程中,涉及到网络层面的,有三个主要过程:

DNS 解析TCP 连接HTTP 请求/响应

这里我们就不用去管DNS解析和TCP链接了,毕竟不是我们的事,也干不来,但是HTTP请求和响应是我们优化的重点。

HTTP优化可分为两个方面:

尽量减少请求次数尽量减少单次请求所花费的时间

减少请求数:

合理的设置http缓存,恰当的缓存设置可以大大减少http请求。要尽可能地让资源能够在缓存中待得更久。从设计实现层面简化页面,保持页面简洁、减少资源的使用时是最直接的。资源合并与压缩,尽可能的将外部的脚本、样式进行合并,多个合为一个。CSS Sprites,通过合并 CSS图片,这是减少请求数的一个好办法

内联脚本的位置:

浏览器是并发请求的,很多时候我们会加入很多的外链脚本,而外链脚本在加载时却常常阻塞其他资源,例如在脚本加载完成之前,它后面的图片、样式以及其他脚本都处于阻塞状态,直到脚本加载完成后才会开始加载。如果将脚本放在比较靠前的位置,则会影响整个页面的加载速度从而影响用户体验。所以说尽可能的将脚本往后挪,减少对并发下载的影响。

客户端的渲染

前端去取后端的数据生成DOM树,加载过来后,自己在浏览器由上而下跑执行JS,随后就会生成相应的DOM。

优点:

客户端的渲染使得前后端分离,开发效率高用户体验更好,我们将网站做成SPA(单页面应用)或者部分内容做成SPA,当用户点击时,不会形成频繁的跳转

缺点:

前端响应速度慢,特别是首屏,这样用户是受不了的不利于SEO优化,因为爬虫不认识SPA,所以它只是记录了一个页面

服务端的渲染

DOM树在服务端生成,然后返回给前端,页面上展现的内容,我们在HTML源文件也能找到。

优点:

服务端渲染尽量不占用前端的资源,前端这块耗时少,速度快利于SEO优化,因为在后端有完整的html页面,所以爬虫更容易爬取信息

缺点:

不利于前后端分离,开发的效率降低了对html的解析,对前端来说加快了速度,但是加大了服务器的压力

类似企业级网站,主要功能是页面展示,它没有复杂的交互,并且需要良好的SEO,那我们应该使用服务端渲染。

现在很多网站使用服务端渲染和客户端渲染结合的方式:首屏使用服务端渲染,其他页面使用客户端渲染。这样可以保证首屏的加载速度,也完成了前后端分离。

区分:源码里如果能找到前端页面中的内容文字,那就是在服务端构建的DOM,就是服务端渲染,反之是客户端渲染。

浏览器渲染

浏览器渲染机制一般分为:

分析HTML并构建DOM树分析CSS构建CSSOM树将DOM和CSSOM合并成一个渲染树根据渲染树布局,计算每个节点的位置调用GPU绘制,合成图层,显示页面

在渲染DOM的时候,浏览器所做的事情:

获取DOM后分割为多个图层对每个图层的节点计算样式结果(recalculate style -- 样式重计算)为每个节点生成图形和位置(layout -- 回流和重布局)将每个节点绘制填充到图层位图中(paint setup 和 paint -- 重绘)图层作为纹理上传至GPU复合多个图层到页面上生成最终屏幕图像(composite layers -- 图层重组)

新建独立图层会减少重回回流带来的影响,但是在图层重组的时候会消耗大量的性能,所以要权衡利弊,有所选择。

渲染流程的CSS优化

CSS的渲染是从右到左进行匹配的,我们应该注意:

避免大量使用通配符,可选择需要用到的元素关注可以通过继承实现的属性,避免重复匹配,重复定义少用标签选择器,例如.header ul li aid和class选择器不应该被多余的选择器拖后腿,例如.header#title减少嵌套,后代选择器的开销最高,不要一大串,要将选择器的深度降到最低,尽可能使用类来关联每一个标签元素。

CSS阻塞

我们将css放在head标签里和尽快启用CDN实现静态资源加载速度的优化,因为只要CSSOM不OK,那么渲染就不会完成。

JS阻塞

JS引擎是独立于渲染引擎存在的,就是说插在页面那,就在那执行,浏览器遇到script标签时,它就会停止交于JS引擎渲染,等它渲染完,浏览器又交于渲染引擎继续CSSOM和DOM的构建。

DOM渲染优化

也就是说重绘回流问题

回流:前面我们通过构造渲染树,我们将可见DOM节点以及它对应的样式结合起来,可是我们还需要计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流。重绘:最终,我们通过构造渲染树和回流阶段,我们知道了哪些节点是可见的,以及可见节点的样式和具体的几何信息(位置、大小),那么我们就可以将渲染树的每个节点都转换为屏幕上的实际像素,这个阶段就叫做重绘节点。

当页面布局和几何信息发生变化的时候,就需要回流。比如以下情况:

添加或删除可见的DOM元素元素的位置发生变化元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。页面一开始渲染的时候(这肯定避免不了)浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)

注意:回流一定会触发重绘,而重绘不一定会回流,回流比重绘做的事情要多,带来的开销也大,在开发中,要从代码层面出发,尽可能把回流和重绘的次数最小化。

如何最小化重绘和重排

用 translate 替代 top用 opacity 替代 visibility不要一条一条的修改 DOM 的样式,预先定义好 class,然后修改 DOM 的 className把 DOM 离线后修改,比如:先把 DOM 给 display: none(有一次 reflow),然后修改100次,然后再显示出来不要把 DOM 节点的属性值放在一个循环里当成循环里的变量不要使用 table 布局,可能很小的一个改动就会造成整个 table 的重新布局动画实现的速度的选择对于动画新建图层启用 GPU 硬件加速

本文主要介绍的是 代码层面 的性能优化,经过上面的一系列优化,首页打开速度有了明显的提升,虽然都是一些常规方案,但其中可以深挖的知识点并不少

0000
评论列表
共(0)条