Vue首屏性能优化组件

简单实现一个Vue首屏性能优化组件,现代化浏览器提供了很多新接口,在不考虑IE兼容性的情况下,这些接口可以很大程度上减少编写代码的工作量以及做一些性能优化方面的事情,当然为了考虑IE我们也可以在封装组件的时候为其兜底,本文的首屏性能优化组件主要是使用IntersectionObserver以及requestIdleCallback两个接口。

描述

先考虑首屏场景,当做一个主要为展示用的首屏时,通常会加载较多的资源例如图片等,如果我们不想在用户打开时就加载所有资源,而是希望用户滚动到相关位置时再加载组件,此时就可以选择IntersectionObserver这个接口,当然也可以使用onscroll事件去做一个监听,只不过这样性能可能比较差一些。还有一些组件,我们希望他必须要加载,但是又不希望他在初始化页面时同步加载,这样我们可以使用异步的方式比如PromisesetTimeout等,但是如果想再降低这个组件加载的优先级,我们就可以考虑requestIdleCallback这个接口,相关代码在https://github.com/WindrunnerMax/webpack-simple-environmentvue--first-screen-optimization分支。

IntersectionObserver

IntersectionObserver接口,从属于Intersection Observer API,提供了一种异步观察目标元素与其祖先元素或顶级文档视窗viewport交叉状态的方法,祖先元素与视窗viewport被称为根root,也就是说IntersectionObserver API,可以自动观察元素是否可见,由于可见visible的本质是,目标元素与视口产生一个交叉区,所以这个API叫做交叉观察器,兼容性https://caniuse.com/?search=IntersectionObserver

const io = new IntersectionObserver(callback, option);// 开始观察
io.observe(document.getElementById("example"));
// 停止观察
io.unobserve(element);
// 关闭观察器
io.disconnect();
  • 参数callback,创建一个新的IntersectionObserver对象后,当其监听到目标元素的可见部分穿过了一个或多个阈thresholds时,会执行指定的回调函数。
  • 参数optionIntersectionObserver构造函数的第二个参数是一个配置对象,其可以设置以下属性:
    • threshold属性决定了什么时候触发回调函数,它是一个数组,每个成员都是一个门槛值,默认为[0],即交叉比例intersectionRatio达到0时触发回调函数,用户可以自定义这个数组,比如[0, 0.25, 0.5, 0.75, 1]就表示当目标元素0%25%50%75%100%可见时,会触发回调函数。
    • root属性指定了目标元素所在的容器节点即根元素,目标元素不仅会随着窗口滚动,还会在容器里面滚动,比如在iframe窗口里滚动,这样就需要设置root属性,注意,容器元素必须是目标元素的祖先节点。
    • rootMargin属性定义根元素的margin,用来扩展或缩小rootBounds这个矩形的大小,从而影响intersectionRect交叉区域的大小,它使用CSS的定义方法,比如10px 20px 30px 40px,表示toprightbottomleft四个方向的值。
  • 属性IntersectionObserver.root只读,所监听对象的具体祖先元素element,如果未传入值或值为null,则默认使用顶级文档的视窗。
  • 属性IntersectionObserver.rootMargin只读,计算交叉时添加到根root边界盒bounding box的矩形偏移量,可以有效的缩小或扩大根的判定范围从而满足计算需要,此属性返回的值可能与调用构造函数时指定的值不同,因此可能需要更改该值,以匹配内部要求,所有的偏移量均可用像素pixelpx或百分比percentage%来表达,默认值为0px 0px 0px 0px
  • 属性IntersectionObserver.thresholds只读,一个包含阈值的列表,按升序排列,列表中的每个阈值都是监听对象的交叉区域与边界区域的比率,当监听对象的任何阈值被越过时,都会生成一个通知Notification,如果构造器未传入值,则默认值为0
  • 方法IntersectionObserver.disconnect(),使IntersectionObserver对象停止监听工作。
  • 方法IntersectionObserver.observe(),使IntersectionObserver开始监听一个目标元素。
  • 方法IntersectionObserver.takeRecords(),返回所有观察目标的IntersectionObserverEntry对象数组。
  • 方法IntersectionObserver.unobserve(),使IntersectionObserver停止监听特定目标元素。

此外当执行callback函数时,会传递一个IntersectionObserverEntry对象参数,其提供的信息如下。

  • time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒。
  • target:被观察的目标元素,是一个DOM节点对象。
  • rootBounds:根元素的矩形区域的信息,是getBoundingClientRect方法的返回值,如果没有根元素即直接相对于视口滚动,则返回null
  • boundingClientRect:目标元素的矩形区域的信息。
  • intersectionRect:目标元素与视口或根元素的交叉区域的信息。
  • intersectionRatio:目标元素的可见比例,即intersectionRectboundingClientRect的比例,完全可见时为1,完全不可见时小于等于0
{time: 3893.92,rootBounds: ClientRect {bottom: 920,height: 1024,left: 0,right: 1024,top: 0,width: 920},boundingClientRect: ClientRect {// ...},intersectionRect: ClientRect {// ...},intersectionRatio: 0.54,target: element
}

requestIdleCallback

requestIdleCallback方法能够接受一个函数,这个函数将在浏览器空闲时期被调用,这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应,函数一般会按先进先调用的顺序执行,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序,兼容性https://caniuse.com/?search=requestIdleCallback

const handle = window.requestIdleCallback(callback[, options]);
  • requestIdleCallback方法返回一个ID,可以把它传入window.cancelIdleCallback()方法来结束回调。
  • 参数callback,一个在事件循环空闲时即将被调用的函数的引用,函数会接收到一个名为IdleDeadline的参数,这个参数可以获取当前空闲时间以及回调是否在超时时间前已经执行的状态。
  • 参数options可选,包括可选的配置参数,具有如下属性:
    • timeout: 如果指定了timeout,并且有一个正值,而回调在timeout毫秒过后还没有被调用,那么回调任务将放入事件循环中排队,即使这样做有可能对性能产生负面影响。

实现

实际上编写组件主要是搞清楚如何使用这两个主要的API就好,首先关注IntersectionObserver,因为考虑需要使用动态组件<component />,那么我们向其传值的时候就需要使用异步加载组件() => import("component")的形式。监听的时候,可以考虑加载完成之后即销毁监听器,或者离开视觉区域后就将其销毁等,这方面主要是策略问题。在页面销毁的时候就必须将Intersection Observer进行disconnect,防止内存泄漏。另外我们为了使用IntersectionObserver则必须需要一个可以观察的目标,如果什么不都渲染,我们就无从观察,所以我们需要引入一个骨架屏,我们可以为真实的组件做一个在尺寸上非常接近真实组件的组件,在这里为了演示只是简单的渲染了<section />作为骨架屏。使用requestIdleCallback就比较简单了,只需要将回调函数执行即可,同样也类似于Promise.resolve().then这种异步处理的情况。
这里是简单的实现逻辑,通常observer的使用方案是先使用一个div等先进行占位,然后在observer监控其占位的容器,当容器在视区时加载相关的组件,相关的代码在https://github.com/WindrunnerMax/webpack-simple-environmentvue--first-screen-optimization分支,请尽量使用yarn进行安装,可以使用yarn.lock文件锁住版本,避免依赖问题。使用npm run dev运行之后可以在Console中看到这四个懒加载组件created创建的顺序,其中Aobserver懒加载是需要等其加载页面渲染完成之后,判断在可视区,才进行加载,首屏使能够直接看到的,而D的懒加载则是需要将滚动条滑动到D的外部容器出现在视图之后才会出现,也就是说只要不滚动到底部是不会加载D组件的,另外还可以通过component-paramscomponent-eventsattrslisteners传递到懒加载的组件,类似于$attrs$listeners,至此懒加载组件已简单实现。

<!-- App.vue -->
<template><div><section>1</section><section><div>2</div><lazy-load:lazy-component="Example"type="observer":component-params="{ content: 'Example A' }":component-events="{'test-event': testEvent,}"></lazy-load></section><section><div>3</div><lazy-load:lazy-component="Example"type="idle":component-params="{ content: 'Example B' }":component-events="{'test-event': testEvent,}"></lazy-load></section><section><div>4</div><lazy-load:lazy-component="Example"type="lazy":component-params="{ content: 'Example C' }":component-events="{'test-event': testEvent,}"></lazy-load></section><section><div>5</div><lazy-load:lazy-component="Example"type="observer":component-params="{ content: 'Example D' }":component-events="{'test-event': testEvent,}"></lazy-load></section></div>
</template><script lang="ts">import { Component, Vue } from "vue-property-decorator";
import LazyLoad from "./components/lazy-load/lazy-load.vue";
@Component({components: { LazyLoad },
})
export default class App extends Vue {protected Example = () => import("./components/example/example.vue");protected testEvent(content: string) {console.log(content);}
}</script><style lang="scss">@import "./common/styles.scss";
body {padding: 0;margin: 0;
}
section {margin: 20px 0;color: #fff;height: 500px;background: $color-blue;
}</style>
<!-- lazy-load.vue -->
<template><div><component:is="renderComponent"v-bind="componentParams"v-on="componentEvents"></component></div>
</template><script lang="ts">import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class LazyLoad extends Vue {@Prop({ type: Function, required: true })lazyComponent!: () => Vue;@Prop({ type: String, required: true })type!: "observer" | "idle" | "lazy";@Prop({ type: Object, default: () => ({}) })componentParams!: Record<string, unknown>;@Prop({ type: Object, default: () => ({}) })componentEvents!: Record<string, unknown>;protected observer: IntersectionObserver | null = null;protected renderComponent: (() => Vue) | null = null;protected mounted() {this.init();}private init() {if (this.type === "observer") {// 存在`window.IntersectionObserver`if (window.IntersectionObserver) {this.observer = new IntersectionObserver(entries => {entries.forEach(item => {// `intersectionRatio`为目标元素的可见比例,大于`0`代表可见// 在这里也有实现策略问题 例如加载后不解除`observe`而在不可见时销毁等if (item.intersectionRatio > 0) {this.loadComponent();// 加载完成后将其解除`observe`this.observer?.unobserve(item.target);}});});this.observer.observe(this.$el.parentElement || this.$el);} else {// 直接加载this.loadComponent();}} else if (this.type === "idle") {// 存在`requestIdleCallback`// eslint-disable-next-line @typescript-eslint/ban-ts-comment// @ts-ignoreif (window.requestIdleCallback) {requestIdleCallback(this.loadComponent, { timeout: 3 });} else {// 直接加载this.loadComponent();}} else if (this.type === "lazy") {// 存在`Promise`if (window.Promise) {Promise.resolve().then(this.loadComponent);} else {// 降级使用`setTimeout`setTimeout(this.loadComponent);}} else {throw new Error(`type: "observer" | "idle" | "lazy"`);}}private loadComponent() {this.renderComponent = this.lazyComponent;this.$emit("loaded");}protected destroyed() {this.observer && this.observer.disconnect();}
}</script>

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html
https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback

Vue首屏性能优化组件相关推荐

  1. vue首屏性能优化,解决页面加载时间过长(白屏)方法

    vuecli做了个spa项目,大概有几十个个路由 直接 npm run build打包出来,有一个 1M的巨大 js文件,导致首页白屏时间过长 分析工具 vuecli 2.x自带了分析工具,只要运行 ...

  2. vue2首屏性能优化(splitChunks/externals/gzip/路由懒加载)

    首屏加载慢原因: Vue只有第一次会加载页面, 以后的每次页面切换,只需要进行组件替换.因为Vue 是SPA,所以首页第一次加载时会把所有的组件以及组件相关的资源全都加载了,造成网站首页打开速度变慢的 ...

  3. Vue首屏加载白屏问题及解决方案

    Vue首屏加载白屏问题及解决方案 首先说一下首页加载为什么会白屏? 先说下 SPA 单页面的加载过程 首先就是 html ,也就是 FP 阶段 FP(全称"First Paint" ...

  4. Vue.js 应用性能优化,给你专业的分析+解决方案

    目录 介绍 为什么我们需要 Vue JS 性能优化? Vue 性能不佳背后的主要原因 如何检查您的 VueJS 应用的大小? 如何优化 Vue js 应用程序的性能? 1. 在 Vue js 中懒加载 ...

  5. 10个技巧!实现Vue.js极致性能优化(建议收藏)

    导语 | Vue是一套用于构建用户界面的渐进式的JavaScript框架.它具有体积小,更高的运行效率,双向数据绑定,生态丰富.学习成本低等优点,所以Vue也被广泛用在移动端跨平台框架上.接下来,我将 ...

  6. 移动H5首屏秒开优化方案

    随着移动设备性能不断增强,web 页面的性能体验逐渐变得可以接受,又因为 web 开发模式的诸多好处(跨平台,动态更新,减体积,无限扩展),APP 客户端里出现越来越多内嵌 web 页面(为了配上当前 ...

  7. vue首屏加载速度慢_Vue首屏加载速度优化如何提升80%?本文详解

    在Vue项目中,引入到工程中的所有js.css文件,编译时都会被打包进vendor.js,浏览器在加载该文件之后才能开始显示首屏.若是引入的库众多,那么vendor.js文件体积将会相当的大,影响首屏 ...

  8. VUE 首屏加载时间优化

    在Vue项目中,引入到工程中的所有js.css文件,编译时都会被打包进vendor.js,浏览器在加载该文件之后才能开始显示首屏.若是引入的库众多,那么vendor.js文件体积将会相当的大,影响首屏 ...

  9. Vue首屏加载速度优化,提升80%以上

    在Vue项目中,引入到工程中的所有js.css文件,编译时都会被打包进vendor.js,浏览器在加载该文件之后才能开始显示首屏.若是引入的库众多,那么vendor.js文件体积将会相当的大,影响首屏 ...

最新文章

  1. mySQL双机冗余_MySQL双机热备实现原理
  2. 2.8 使用开源的实现方案-深度学习第四课《卷积神经网络》-Stanford吴恩达教授
  3. 程序员面试题精选100题(32)-不能被继承的类[C/C++/C#]
  4. C# 编程实现非自相交多边形质心
  5. HDU - 5572 An Easy Physics Problem(几何-碰撞问题)
  6. 第十五章 Python和Web
  7. python求线段长度_python微元法计算函数曲线长度的方法
  8. 数据概览_2015年概览:开源年鉴
  9. python sum_Python sum()
  10. Matlab中使用varargin来实现参数可变的函数
  11. 复习,网课,视频回放,太慢怎么办,试试倍速播放吧 (无需下载)
  12. pano2vr怎么制作漫游_如何制作全景图?Pano2VR制作FLASH全景图教程
  13. mysql索引失效的几种情况
  14. 如何通过压缩视频软件,减少大小且画质无损技巧
  15. 神经网络论文研究-图像处理方向4-geoglenet
  16. 在Excel工作表数据输入表单中编辑记录
  17. c++socket双人聊天
  18. HTML标签大全,存起来总有一天用的上
  19. 【软考中级】多媒体应用设计师复习笔记第九章
  20. moviepy音视频开发:音频剪辑基类AudioClip

热门文章

  1. redis有序集合sorted set详解
  2. Onvif之设备发现-基于gsoap2.8.27
  3. MYSQL  GROUP BY 对多个字段进行分组
  4. ElasticSearch的安装过程
  5. security中常用的加密算法
  6. java垃圾回收理解与算法
  7. Pycharm在Ubuntu14.04中的基本使用指南
  8. 【Keras】从两个实际任务掌握图像分类
  9. 解决 QQ2006 键盘加密造成的系统当机故障
  10. CentOS7下LVS+Keepalived实现高性能高可用负载均衡