前端性能优化之首屏加载【懒加载,Tree shaking,骨架屏优化白屏时长,长列表虚拟滚动,压缩图片】

52 篇文章 14 订阅
3 篇文章 0 订阅

目录

路由懒加载

SPA :一个路由对应一个页面

把所有页面打包成一个文件

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

webpackChunkName:分离到单独的 chunk

组件懒加载

webpackChunkName: "dialogInfo"

适用场景

体积大

非首屏

复用性高

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

原理:ES6模块静态分析

只适用于函数式编程

骨架屏优化白屏时长

虚拟列表:大量数据

长列表虚拟滚动:只渲染可视区,非可见区域的不渲染

虚拟滚动插件

Web Worker 优化长任务

适用:当任务的运算时长 - 通信时长 > 50ms

requestAnimationFrame 制作动画:刷新频率与显示器的频率保持一致

优先级

setTimeout/setInterval 属于 JS引擎

requestAnimationFrame 属于 GUI引擎

JS引擎与GUI引擎是互斥的: GUI 引擎在渲染时会阻塞 JS 引擎的计算

时间

requestAnimationFrame 刷新频率是固定且准确的

setTimeout/setInterval 是宏任务

性能

setTimeout/setInterval 定时器仍会在后台执行动画任务,

requestAnimationFrame 当页面处于未激活的状态下,屏幕刷新任务会被系统暂停

图片

动态裁剪:在图片url地址上动态添加参数

懒加载

src 属性发送请求并下载图片

data-xxx 先暂存 src 的值

vue-lazyload 插件

兼容版:window监听scroll事件+节流

B版API:IntersectionObserver :回调

图片转 base64 编码字符串

并写入 HTML 或者 CSS 中,减少 http 请求

适用于:小图片、图片大小会膨胀为原文件的 4/3

url-loader 将图片转 base64:

懒/动态加载:按需/运动加载

路由懒加载

SPA :一个路由对应一个页面

把所有页面打包成一个文件

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

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

调用 import() 之处,被作为分离的模块起点,意思是,被请求的模块和它引用的所有子模块,会分离到一个单独的 chunk 中
——摘自《webpack——模块方法》的import()小节

webpackChunkName:分离到单独的 chunk

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

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

// 通过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
    },
    …………
 ]

组件懒加载

webpackChunkName: "dialogInfo"

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

<script>
const dialogInfo = () => import(/* webpackChunkName: "dialogInfo" */ '@/components/dialogInfo');
export default {
  name: 'homeView',
  components: {
    dialogInfo
  }
}
</script>

适用场景

体积大

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

非首屏

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

复用性高

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

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

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

// 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));
}

原理:ES6模块静态分析

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

只适用于函数式编程

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

// 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)

骨架屏优化白屏时长

虚拟列表:大量数据

长列表虚拟滚动:只渲染可视区,非可见区域的不渲染

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

虚拟滚动插件

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

Web Worker 优化长任务

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

查看页面的长任务:

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

适用:当任务的运算时长 - 通信时长 > 50ms

Time 是这个资源的通信时长(也叫加载时长)

requestAnimationFrame 制作动画:刷新频率与显示器的频率保持一致

可以解决用 setTimeout/setInterval 制作动画卡顿的情况

优先级

setTimeout/setInterval 属于 JS引擎

requestAnimationFrame 属于 GUI引擎

JS引擎与GUI引擎是互斥的: GUI 引擎在渲染时会阻塞 JS 引擎的计算

时间

requestAnimationFrame 刷新频率是固定且准确的

setTimeout/setInterval 是宏任务

根据事件轮询机制,其他任务会阻塞或延迟js任务的执行,会出现定时器不准的情况

性能

当页面被隐藏或最小化时,

setTimeout/setInterval 定时器仍会在后台执行动画任务,

requestAnimationFrame 当页面处于未激活的状态下,屏幕刷新任务会被系统暂停

图片

动态裁剪:在图片url地址上动态添加参数

比如阿里云七牛云,都提供了图片的动态裁剪功

懒加载

(Load On Demand)延迟加载、按需加载

可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,减少了服务器的负载。懒加载适用于图片较多,页面列表较长(长列表)的场景中。

src 属性发送请求并下载图片

data-xxx 先暂存 src 的值

由于浏览器会自动对页面中的 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>

兼容版:window监听scroll事件+节流

  1. 图片的 src 属性设为默认图片
  2. 图片的真实路径则设置在data-src属性中,
  3. 绑定 window 的 scroll 事件,对其进行事件监听。
  4. //节流
    window.addEventListener('scroll', throttle(lazyload, 200))
  5. 在scroll事件的回调中,判断我们的懒加载的图片是否进入可视区域,
  6. 如果图片在可视区内将图片的 src 属性设置为data-src的值
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Lazyload</title>
    <style>
      .image-item {
	    display: block;
	    margin-bottom: 50px;
	    height: 200px;//一定记得设置图片高度
	}
    </style>
</head>
<body>
    <img src="./img/default.png" data-src="./img/1.jpg" />
    ...
    <img src="./img/default.png" data-src="./img/10.jpg" />

<script>
function lazyload() {
  let viewHeight = document.body.clientHeight //获取可视区高度
//用属性选择器返回属性名为data-src的img元素列表
  let imgs = document.querySelectorAll('img[data-src]')
  imgs.forEach((item, index) => {
    if (item.dataset.src === '') return

    // 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
    let rect = item.getBoundingClientRect()
    if (rect.bottom >= 0 && rect.top < viewHeight) {
      item.src = item.dataset.src
      item.removeAttribute('data-src')//移除属性,下次不再遍历
    }
  })
}

lazyload()//刚开始还没滚动屏幕时,要先触发一次函数,初始化首页的页面图片
document.addEventListener("scroll",lazyload)
</script>
</body>
</html>

B版API:IntersectionObserver :回调

IntersectionObserver "交叉观察器"。可见(visible)本质是,目标元素与视口产生一个交叉区

callback 一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)

//callback 是可见性变化时的回调函数,option 是配置对象(该参数可选)
var io = new IntersectionObserver(callback, option)

// 开始观察
io.observe(document.getElementById('example'))

// 停止观察
io.unobserve(element)

// 关闭观察器
io.disconnect()

callback 函数的参数(entries)是一个数组,每个成员都是一个 IntersectionObserverEntry 对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries 数组就会有两个成员。

  • time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
  • target:被观察的目标元素,是一个 DOM 节点对象
  • isIntersecting: 目标是否可见
  • rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回 null
  • boundingClientRect:目标元素的矩形区域的信息
  • intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
  • intersectionRatio:目标元素的可见比例,即 intersectionRectboundingClientRect 的比例,完全可见时为 1,完全不可见时小于等于 0
const config = {
  rootMargin: '0px',
  threshold: 0,
}
let observer = new IntersectionObserver((entries, self) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      let img = entry.target
      let src = img.dataset.src
      if (src) {
        img.src = src
        img.removeAttribute('data-src')
      }
      // 解除观察
      self.unobserve(entry.target)
    }
  })
}, config)

imgs.forEach((image) => {
  observer.observe(image)
})

前端性能优化之图片懒加载 - 掘金

图片转 base64 编码字符串

并写入 HTML 或者 CSS 中,减少 http 请求

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

适用于:小图片、图片大小会膨胀为原文件的 4/3

转 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
            }
        }]
     }]
  }
};

参考链接:前端性能优化——首页资源压缩63%、白屏时间缩短86% - 掘金

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值