Vue首屏性能优化组件知识点总结

Vue首屏性能优化组件

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

描述

先考虑首屏场景,当做一个主要为展示用的首屏时,通常会加载较多的资源例如图片等,如果我们不想在用户打开时就加载所有资源,而是希望用户滚动到相关位置时再加载组件,此时就可以选择IntersectionObserver这个接口,当然也可以使用onscroll事件去做一个监听,只不过这样性能可能比较差一些。还有一些组件,我们希望他必须要加载,但是又不希望他在初始化页面时同步加载,这样我们可以使用异步的方式比如Promise和setTimeout等,但是如果想再降低这个组件加载的优先级,我们就可以考虑requestIdleCallback这个接口,相关代码在https://github.com/WindrunnerMax/webpack-simple-environment的vue--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时,会执行指定的回调函数。
  • 参数option,IntersectionObserver构造函数的第二个参数是一个配置对象,其可以设置以下属性:
  • 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,表示top、right、bottom和left四个方向的值。
  • 属性IntersectionObserver.root只读,所监听对象的具体祖先元素element,如果未传入值或值为null,则默认使用顶级文档的视窗。
  • 属性IntersectionObserver.rootMargin只读,计算交叉时添加到根root边界盒bounding box的矩形偏移量,可以有效的缩小或扩大根的判定范围从而满足计算需要,此属性返回的值可能与调用构造函数时指定的值不同,因此可能需要更改该值,以匹配内部要求,所有的偏移量均可用像素pixel、px或百分比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:目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为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,防止内存泄漏。使用requestIdleCallback就比较简单了,只需要将回调函数执行即可,同样也类似于Promise.resolve().then这种异步处理的情况。

这里是简单的实现逻辑,通常observer的使用方案是先使用一个div等先进行占位,然后在observer监控其占位的容器,当容器在视区时加载相关的组件,相关的代码在https://github.com/WindrunnerMax/webpack-simple-environment的vue--first-screen-optimization分支,请尽量使用yarn进行安装,可以使用yarn.lock文件锁住版本,避免依赖问题。使用npm run dev运行之后可以在Console中看到这四个懒加载组件created创建的顺序,其中A的observer懒加载是需要等其加载页面渲染完成之后,判断在可视区,才进行加载,首屏使能够直接看到的,而D的懒加载则是需要将滚动条滑动到D的外部容器出现在视图之后才会出现,也就是说只要不滚动到底部是不会加载D组件的,另外还可以通过component-params和component-events将attrs和listeners传递到懒加载的组件,类似于$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>
Copy
<!-- 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-ignore
            if (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首屏性能优化组件知识点总结的文章就介绍到这了,更多相关Vue首屏性能优化组件内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue项目开启Gzip压缩和性能优化操作

    vue 项目开启gzip自拍压缩和部署 nginx 开启gzip优化性能 第一步:在vue项目中安装依赖并将productionGzip改为true,开启Gzip压缩: npm install --save-dev compression-webpack-plugin 第二步:运行 npm run build打包项目,这时可能会报错,提示ValidationError: Compression Plugin Invalid Options. 根据官网提示,需要将CompressionWebpac

  • Vue2.x 项目性能优化之代码优化的实现

    众所周知,Vue项目采用了数据双向绑定和虚拟DOM基础,在数据驱动代替DOM频繁渲染已经算是非常高效了,对开发者而言已经非常优化了,那为什么还会有Vue性能优化这一说呢? 因为目前Vue 2.x使用了webpack等第三方打包构建工具,并且支持其他第三方的插件,我们在项目中使用这些工具时可能不同的操作在运行或打包效率上会有不同的效果,下面就来详细说明优化的方向. 1 v-if 和 v-show 的使用 v-if 为false的时候不会渲染DOM到视图,为true的时候才会渲染到视图: v-sho

  • 加速vue组件渲染之性能优化

    背景 平时在用vue开发后台管理系统的时候,应该会用到大量的table这种组件,正常这种组件我们会在项目里做二次封装,然后针对表头title做参数化配置,如下: export default { data(){ return { tableTitle:[ { label:'省份', prop:'prop' }, { label:'城市', prop:'prop' }, { label:'汇总', prop:'prop', colconfig:[ { label:'下级', prop:'prop'

  • Vue性能优化的方法

    今天来谈一谈Vue中一些性能优化的问题,仅仅是个人使用中的一些小心得,来,今天我一句废话不多说,直接上内容好吧 1.v-if和v-show的使用, 我们都知道这两个都可以控制显隐,那我们用哪个呢,个人觉得要从两个方面入手来确定使用哪个, 1.权限的问题,只要涉及到权限相关的展示用v-if比较好 2.切换地频率,如果频繁的切换我们用v-show,不频繁的切换用v-if 其实两者各有优缺,就看你是怎么选择了,用v-if能减少页面中的DOM总数,加快渲染的速度,而且我们要清楚一个事情 v-if是'真正

  • Vue.js九个性能优化技巧(值得收藏)

    目录 Functional components Child component splitting Local variables Reuse DOM with v-show KeepAlive Deferred features Time slicing Non-reactive data Virtual scrolling 总结 参考资料 这篇文章主要参考了 Vue.js 核心成员Guillaume Chau在 19 年美国的 Vue conf 分享的主题:9 Performance se

  • Vue项目首屏性能优化组件实战指南

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

  • Vue首屏性能优化组件知识点总结

    Vue首屏性能优化组件 简单实现一个Vue首屏性能优化组件,现代化浏览器提供了很多新接口,在不考虑IE兼容性的情况下,这些接口可以很大程度上减少编写代码的工作量以及做一些性能优化方面的事情,当然为了考虑IE我们也可以在封装组件的时候为其兜底,本文的首屏性能优化组件主要是使用IntersectionObserver以及requestIdleCallback两个接口. 描述 先考虑首屏场景,当做一个主要为展示用的首屏时,通常会加载较多的资源例如图片等,如果我们不想在用户打开时就加载所有资源,而是希望

  • 浅谈vue首屏加载优化

    本文介绍了浅谈vue首屏加载优化,分享给大家,具体如下: 库使用情况 vue vue-router axios muse-ui material-icons vue-baidu-map 未优化前 首先我们在正常情况下build 优化 1. 按需加载 当前流行的UI框架如iview,muse-ui,Element UI都支持按需加载,只需稍微改动一下代码. 修改前: import MuseUI from 'muse-ui' import 'muse-ui/dist/muse-ui.css' imp

  • Vue中的性能优化方案

    目录 减少响应式使用 1. 使用 computed 缓存计算结果 2. 本地化响应式变量 3. 函数式组件(Vue2) 减少 DOM 渲染压力 1. DOM 频繁切换展示的情况使用 v-show 2. keep-alive 缓存组件状态 3. 路由懒加载 4. 图片懒加载 5. 组件销毁时要清除定时器.EventListener 6. 列表使用唯一 key 减少打包体积 1. 开启 gzip 压缩 2. 按需引入第三方组件 最近使用 Vue 开发的过程中使用到一些对于性能有所提升的编码方式,所以

  • Vue.js 应用性能优化分析+解决方案

    目录 一.介绍 二.为什么我们需要 Vue JS 性能优化? 三.Vue 性能不佳背后的主要原因 1. 生成报告 2. 运行命令和npm 运行生成 四.如何优化 Vue js 应用程序的性能? 1. 在 Vue js 中懒加载 2. 基于路线的代码拆分 3.Vue js预加载组件 4. 优化第三方库 5. 使用浏览器缓存 6. 优化和压缩图像 前言: 假设我们在开发整个 Vue 应用程序方面非常努力.js.但没有优先考虑其性能:现在,我们的应用程序需要一段时间来加载.导航.提交或执行任何用户操作

  • vue虚拟滚动性能优化方式详解

    目录 引言 虚拟滚动(Virtual Scrolling) 理解虚拟滚动 虚拟 滚动 实现虚拟滚动 核心步骤 效果预览 最后 引言 一个简单的情景模拟(千万别被带入): A: 假设现在有 10 万条数据,你作为前端该怎么优化这种大数据的列表? B: 针对大数据列表一般不会依次性加载,会采用上拉加载.分页加载等方式实现优化. A: 那假如加载到最后一条数据的时候,页面上只是列表部分的数据就至少对应 10 万个 dom 节点,你觉得一个页面渲染至少 10 万个 dom 节点的性能如何? A: 如果这

  • Vue服务端渲染实践之Web应用首屏耗时最优化方案

    随着各大前端框架的诞生和演变,SPA开始流行,单页面应用的优势在于可以不重新加载整个页面的情况下,通过ajax和服务器通信,实现整个Web应用拒不更新,带来了极致的用户体验.然而,对于需要SEO.追求极致的首屏性能的应用,前端渲染的SPA是糟糕的.好在Vue 2.0后是支持服务端渲染的,零零散散花费了两三周事件,通过改造现有项目,基本完成了在现有项目中实践了Vue服务端渲染. 关于Vue服务端渲染的原理.搭建,官方文档已经讲的比较详细了,因此,本文不是抄袭文档,而是文档的补充.特别是对于如何与现

  • 分享12个Vue开发中的性能优化小技巧(实用!)

    目录 前言 1.长列表性能优化 1.不做响应式 2.虚拟滚动 2.v-for遍历避免同时使用v-if 3.列表使用唯一key 4.使用v-show复用DOM 5.无状态的组件用函数式组件 6.子组件分割 7.变量本地化 8.第三方插件按需引入 9.路由懒加载 10.keep-alive缓存页面 11.事件的销毁 12.图片懒加载 总结 前言 性能优化,是每一个开发者都会遇到的问题,特别是现在越来越重视体验,以及竞争越来越激烈的环境下,对于我们开发者来说,只完成迭代,把功能做好是远远不够的,最重要

  • JS前端首屏优化技巧

    目录 引言 打包分析 路由懒加载 vue.config.js 修改 productionSourceMap 为 false 首屏请求优化 图片压缩 配置使用 CDN 方式引入资源库 公共代码抽离 开启 gzip 压缩 其他优化 引言 前段时间领导突然找到我,说新项目的访问怎么变得这么慢,我立马访问对应页面,果然,首页加载8.9s,这近10s的等待时间,对于用户来说,无疑是痛苦的.于是乎领导就把这个首页加载优化的任务交给我了. 在经过我的一顿折腾后,首屏加载时间基本都在2s左右,这样的首屏加载时间

随机推荐