Vue項目首屏性能優(yōu)化組件實戰(zhàn)指南
Vue首屏性能優(yōu)化組件
簡單實現(xiàn)一個Vue首屏性能優(yōu)化組件,現(xiàn)代化瀏覽器提供了很多新接口,在不考慮IE兼容性的情況下,這些接口可以很大程度上減少編寫代碼的工作量以及做一些性能優(yōu)化方面的事情,當(dāng)然為了考慮IE我們也可以在封裝組件的時候為其兜底,本文的首屏性能優(yōu)化組件主要是使用IntersectionObserver以及requestIdleCallback兩個接口。
描述
先考慮首屏場景,當(dāng)做一個主要為展示用的首屏?xí)r,通常會加載較多的資源例如圖片等,如果我們不想在用戶打開時就加載所有資源,而是希望用戶滾動到相關(guān)位置時再加載組件,此時就可以選擇IntersectionObserver這個接口,當(dāng)然也可以使用onscroll事件去做一個監(jiān)聽,只不過這樣性能可能比較差一些。還有一些組件,我們希望他必須要加載,但是又不希望他在初始化頁面時同步加載,這樣我們可以使用異步的方式比如Promise和setTimeout等,但是如果想再降低這個組件加載的優(yōu)先級,我們就可以考慮requestIdleCallback這個接口,相關(guān)代碼在https://github.com/WindrunnerMax/webpack-simple-environment的vue--first-screen-optimization分支。
IntersectionObserver
IntersectionObserver接口,從屬于Intersection Observer API,提供了一種異步觀察目標(biāo)元素與其祖先元素或頂級文檔視窗viewport交叉狀態(tài)的方法,祖先元素與視窗viewport被稱為根root,也就是說IntersectionObserver API,可以自動觀察元素是否可見,由于可見visible的本質(zhì)是,目標(biāo)元素與視口產(chǎn)生一個交叉區(qū),所以這個API叫做交叉觀察器,兼容性https://caniuse.com/?search=IntersectionObserver。
const io = new IntersectionObserver(callback, option); // 開始觀察 io.observe(document.getElementById("example")); // 停止觀察 io.unobserve(element); // 關(guān)閉觀察器 io.disconnect();
- 參數(shù)callback,創(chuàng)建一個新的IntersectionObserver對象后,當(dāng)其監(jiān)聽到目標(biāo)元素的可見部分穿過了一個或多個閾thresholds時,會執(zhí)行指定的回調(diào)函數(shù)。
- 參數(shù)option,IntersectionObserver構(gòu)造函數(shù)的第二個參數(shù)是一個配置對象,其可以設(shè)置以下屬性:
- threshold屬性決定了什么時候觸發(fā)回調(diào)函數(shù),它是一個數(shù)組,每個成員都是一個門檻值,默認(rèn)為[0],即交叉比例intersectionRatio達(dá)到0時觸發(fā)回調(diào)函數(shù),用戶可以自定義這個數(shù)組,比如[0, 0.25, 0.5, 0.75, 1]就表示當(dāng)目標(biāo)元素0%、25%、50%、75%、100%可見時,會觸發(fā)回調(diào)函數(shù)。
- root屬性指定了目標(biāo)元素所在的容器節(jié)點即根元素,目標(biāo)元素不僅會隨著窗口滾動,還會在容器里面滾動,比如在iframe窗口里滾動,這樣就需要設(shè)置root屬性,注意,容器元素必須是目標(biāo)元素的祖先節(jié)點。
- rootMargin屬性定義根元素的margin,用來擴(kuò)展或縮小rootBounds這個矩形的大小,從而影響intersectionRect交叉區(qū)域的大小,它使用CSS的定義方法,比如10px 20px 30px 40px,表示top、right、bottom和left四個方向的值。
- 屬性IntersectionObserver.root只讀,所監(jiān)聽對象的具體祖先元素element,如果未傳入值或值為null,則默認(rèn)使用頂級文檔的視窗。
- 屬性IntersectionObserver.rootMargin只讀,計算交叉時添加到根root邊界盒bounding box的矩形偏移量,可以有效的縮小或擴(kuò)大根的判定范圍從而滿足計算需要,此屬性返回的值可能與調(diào)用構(gòu)造函數(shù)時指定的值不同,因此可能需要更改該值,以匹配內(nèi)部要求,所有的偏移量均可用像素pixel、px或百分比percentage、%來表達(dá),默認(rèn)值為0px 0px 0px 0px。
- 屬性IntersectionObserver.thresholds只讀,一個包含閾值的列表,按升序排列,列表中的每個閾值都是監(jiān)聽對象的交叉區(qū)域與邊界區(qū)域的比率,當(dāng)監(jiān)聽對象的任何閾值被越過時,都會生成一個通知Notification,如果構(gòu)造器未傳入值,則默認(rèn)值為0。
- 方法IntersectionObserver.disconnect(),使IntersectionObserver對象停止監(jiān)聽工作。
- 方法IntersectionObserver.observe(),使IntersectionObserver開始監(jiān)聽一個目標(biāo)元素。
- 方法IntersectionObserver.takeRecords(),返回所有觀察目標(biāo)的IntersectionObserverEntry對象數(shù)組。
- 方法IntersectionObserver.unobserve(),使IntersectionObserver停止監(jiān)聽特定目標(biāo)元素。
此外當(dāng)執(zhí)行callback函數(shù)時,會傳遞一個IntersectionObserverEntry對象參數(shù),其提供的信息如下。
- time:可見性發(fā)生變化的時間,是一個高精度時間戳,單位為毫秒。
- target:被觀察的目標(biāo)元素,是一個DOM節(jié)點對象。
- rootBounds:根元素的矩形區(qū)域的信息,是getBoundingClientRect方法的返回值,如果沒有根元素即直接相對于視口滾動,則返回null。
- boundingClientRect:目標(biāo)元素的矩形區(qū)域的信息。
- intersectionRect:目標(biāo)元素與視口或根元素的交叉區(qū)域的信息。
- intersectionRatio:目標(biāo)元素的可見比例,即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方法能夠接受一個函數(shù),這個函數(shù)將在瀏覽器空閑時期被調(diào)用,這使開發(fā)者能夠在主事件循環(huán)上執(zhí)行后臺和低優(yōu)先級工作,而不會影響延遲關(guān)鍵事件,如動畫和輸入響應(yīng),函數(shù)一般會按先進(jìn)先調(diào)用的順序執(zhí)行,如果回調(diào)函數(shù)指定了執(zhí)行超時時間timeout,則有可能為了在超時前執(zhí)行函數(shù)而打亂執(zhí)行順序,兼容性https://caniuse.com/?search=requestIdleCallback。
const handle = window.requestIdleCallback(callback[, options]);
- requestIdleCallback方法返回一個ID,可以把它傳入window.cancelIdleCallback()方法來結(jié)束回調(diào)。
- 參數(shù)callback,一個在事件循環(huán)空閑時即將被調(diào)用的函數(shù)的引用,函數(shù)會接收到一個名為IdleDeadline的參數(shù),這個參數(shù)可以獲取當(dāng)前空閑時間以及回調(diào)是否在超時時間前已經(jīng)執(zhí)行的狀態(tài)。
- 參數(shù)options可選,包括可選的配置參數(shù),具有如下屬性:
- timeout: 如果指定了timeout,并且有一個正值,而回調(diào)在timeout毫秒過后還沒有被調(diào)用,那么回調(diào)任務(wù)將放入事件循環(huán)中排隊,即使這樣做有可能對性能產(chǎn)生負(fù)面影響。
- timeout: 如果指定了timeout,并且有一個正值,而回調(diào)在timeout毫秒過后還沒有被調(diào)用,那么回調(diào)任務(wù)將放入事件循環(huán)中排隊,即使這樣做有可能對性能產(chǎn)生負(fù)面影響。
實現(xiàn)
實際上編寫組件主要是搞清楚如何使用這兩個主要的API就好,首先關(guān)注IntersectionObserver,因為考慮需要使用動態(tài)組件<component />,那么我們向其傳值的時候就需要使用異步加載組件() => import("component")的形式。監(jiān)聽的時候,可以考慮加載完成之后即銷毀監(jiān)聽器,或者離開視覺區(qū)域后就將其銷毀等,這方面主要是策略問題。在頁面銷毀的時候就必須將Intersection Observer進(jìn)行disconnect,防止內(nèi)存泄漏。使用requestIdleCallback就比較簡單了,只需要將回調(diào)函數(shù)執(zhí)行即可,同樣也類似于Promise.resolve().then這種異步處理的情況。
這里是簡單的實現(xiàn)邏輯,通常observer的使用方案是先使用一個div等先進(jìn)行占位,然后在observer監(jiān)控其占位的容器,當(dāng)容器在視區(qū)時加載相關(guān)的組件,相關(guān)的代碼在https://github.com/WindrunnerMax/webpack-simple-environment的vue--first-screen-optimization分支,請盡量使用yarn進(jìn)行安裝,可以使用yarn.lock文件鎖住版本,避免依賴問題。使用npm run dev運(yùn)行之后可以在Console中看到這四個懶加載組件created創(chuàng)建的順序,其中A的observer懶加載是需要等其加載頁面渲染完成之后,判斷在可視區(qū),才進(jìn)行加載,首屏使能夠直接看到的,而D的懶加載則是需要將滾動條滑動到D的外部容器出現(xiàn)在視圖之后才會出現(xiàn),也就是說只要不滾動到底部是不會加載D組件的,另外還可以通過component-params和component-events將attrs和listeners傳遞到懶加載的組件,類似于$attrs和$listeners,至此懶加載組件已簡單實現(xiàn)。
<!-- 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>
每日一題
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
總結(jié)
到此這篇關(guān)于Vue項目首屏性能優(yōu)化組件的文章就介紹到這了,更多相關(guān)Vue首屏性能優(yōu)化組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用vue插件axios傳數(shù)據(jù)的Content-Type及格式問題詳解
這篇文章主要介紹了使用vue插件axios傳數(shù)據(jù)的Content-Type以及格式問題,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-09-09Vue中使用echarts實現(xiàn)繪制人體動態(tài)圖
這篇文章主要為大家詳細(xì)介紹了Vue中如何使用echarts實現(xiàn)繪制人體動態(tài)圖,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03vue vantUI tab切換時 list組件不觸發(fā)load事件的問題及解決方法
這篇文章主要介紹了vue vantUI tab切換時 list組件不觸發(fā)load事件的解決辦法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02vue使用路由守衛(wèi)實現(xiàn)菜單的權(quán)限設(shè)置
我們使?vue-element-admin前端框架開發(fā)后臺管理系統(tǒng)時,?般都會涉及到菜單的權(quán)限控制問題,下面這篇文章主要給大家介紹了關(guān)于vue使用路由守衛(wèi)實現(xiàn)菜單的權(quán)限設(shè)置的相關(guān)資料,需要的朋友可以參考下2023-06-06