<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          交叉觀察 API Intersection Observer

          共 27960字,需瀏覽 56分鐘

           ·

          2021-09-14 13:12

          前言

          前端開發(fā)肯定離不開判斷一個(gè)元素是否能被用戶看見,然后再基于此進(jìn)行一些交互。

          過(guò)去,要檢測(cè)一個(gè)元素是否可見或者兩個(gè)元素是否相交并不容易,很多解決辦法不可靠或性能很差。然而,隨著互聯(lián)網(wǎng)的發(fā)展,這種需求卻與日俱增,比如,下面這些情況都需要用到相交檢測(cè):

          • 圖片懶加載——當(dāng)圖片滾動(dòng)到可見時(shí)才進(jìn)行加載
          • 內(nèi)容無(wú)限滾動(dòng)——也就是用戶滾動(dòng)到接近內(nèi)容底部時(shí)直接加載更多,而無(wú)需用戶操作翻頁(yè),給用戶一種網(wǎng)頁(yè)可以無(wú)限滾動(dòng)的錯(cuò)覺
          • 檢測(cè)廣告的曝光情況——為了計(jì)算廣告收益,需要知道廣告元素的曝光情況
          • 在用戶看見某個(gè)區(qū)域時(shí)執(zhí)行任務(wù)或播放動(dòng)畫

          過(guò)去,相交檢測(cè)通常要用到事件監(jiān)聽,并且需要頻繁調(diào)用 Element.getBoundingClientRect() 方法以獲取相關(guān)元素的邊界信息。事件監(jiān)聽和調(diào)用 Element.getBoundingClientRect()  都是在主線程上運(yùn)行,因此頻繁觸發(fā)、調(diào)用可能會(huì)造成性能問(wèn)題。這種檢測(cè)方法極其怪異且不優(yōu)雅。

          上面這一段話來(lái)自 MDN ,中心思想就是現(xiàn)在判斷一個(gè)元素是否能被用戶看見的使用場(chǎng)景越來(lái)越多,監(jiān)聽 scroll 事件以及通過(guò) Element.getBoundingClientRect() 獲取節(jié)點(diǎn)位置的方式,又麻煩又不好用,那么怎么辦呢。于是就有了今天的內(nèi)容 Intersection Observer API。

          Intersection Observer API 是什么

          我們需要觀察的元素被稱為 目標(biāo)元素(target),設(shè)備視窗或者其他指定的元素視口的邊界框我們稱它為 根元素(root),或者簡(jiǎn)稱為  。

          Intersection Observer API 翻譯過(guò)來(lái)叫做 “交叉觀察器”,因?yàn)榕袛嘣厥欠窨梢姡?strong style="color: black;">通常情況下)的本質(zhì)就是判斷目標(biāo)元素和根元素是不是產(chǎn)生了 交叉區(qū)域 。

          為什么是通常情況下,因?yàn)楫?dāng)我們 css 設(shè)置了 opacity: 0,visibility: hidden 或者 用其他的元素覆蓋目標(biāo)元素 的時(shí)候,對(duì)于視圖來(lái)說(shuō)是不可見的,但對(duì)于交叉觀察器來(lái)說(shuō)是可見的。這里可能有點(diǎn)抽象,大家只需記住,交叉觀察器只關(guān)心 目標(biāo)元素 和 根元素 是否有 交叉區(qū)域, 而不管視覺上能不能看見這個(gè)元素。當(dāng)然如果設(shè)置了 display:none,那么交叉觀察器就不會(huì)生效了,其實(shí)也很好理解,因?yàn)樵匾呀?jīng)不存在了,那么也就監(jiān)測(cè)不到了。

          一句話總結(jié):Intersection Observer API 提供了一種異步檢測(cè)目標(biāo)元素與祖先元素或 viewport 相交情況變化的方法。 -- MDN

          現(xiàn)在不懂沒關(guān)系,咱們接著往下看,看完自然就明白了。

          Intersection Observer API 怎么玩

          生成觀察器

          // 調(diào)用構(gòu)造函數(shù) IntersectionObserver 生成觀察器
          const myObserver = new IntersectionObserver(callback, options);  
          復(fù)制代碼

          首先調(diào)用瀏覽器原生構(gòu)造函數(shù) IntersectionObserver ,構(gòu)造函數(shù)的返回值是一個(gè) 觀察器實(shí)例 。

          構(gòu)造函數(shù) IntersectionObserver 接收兩個(gè)參數(shù)

          • callback: 可見性發(fā)生變化時(shí)觸發(fā)的回調(diào)函數(shù)
          • options: 配置對(duì)象(可選,不傳時(shí)會(huì)使用默認(rèn)配置)

          構(gòu)造函數(shù)接收的參數(shù) options

          為了方便理解,我們先看第二個(gè)參數(shù) options 。一個(gè)可以用來(lái)配置觀察器實(shí)例的對(duì)象,那么這個(gè)配置對(duì)象都包含哪些屬性呢?

          • root: 設(shè)置目標(biāo)元素的根元素,也就是我們用來(lái)判斷元素是否可見的區(qū)域,必須是目標(biāo)元素的父級(jí)元素,如果不指定的話,則使用瀏覽器視窗,也就是 document。

          • rootMargin: 一個(gè)在計(jì)算交叉值時(shí)添加至根的邊界中的一組偏移量,類型為字符串 (string)  ,可以有效的縮小或擴(kuò)大根的判定范圍從而滿足計(jì)算需要。語(yǔ)法大致和CSS 中 margin 屬性等同,默認(rèn)值 “0px 0px 0px 0px” ,如果有指定 root 參數(shù),則 rootMargin 也可以使用百分比來(lái)取值。

          • threshold: 介于 0 和 1 之間的數(shù)字,指示觸發(fā)前應(yīng)可見的百分比。也可以是一個(gè)數(shù)字?jǐn)?shù)組,以創(chuàng)建多個(gè)觸發(fā)點(diǎn),也被稱之為 閾值。如果構(gòu)造器未傳入值, 則默認(rèn)值為 0 。

          • trackVisibility: 一個(gè)布爾值,指示當(dāng)前觀察器是否將跟蹤目標(biāo)可見性的更改,默認(rèn)為 false ,注意,此處的可見性并非指目標(biāo)元素和根元素是否相交,而是指視圖上是否可見,這個(gè)我們之前就已經(jīng)分析過(guò)了,如果此值設(shè)置為 false 或不設(shè)置,那么回調(diào)函數(shù)參數(shù)中 IntersectionObserverEntry 的 isVisible 屬性將永遠(yuǎn)返回 false 。

          • delay: 一個(gè)數(shù)字,也就是回調(diào)函數(shù)執(zhí)行的延遲時(shí)間(毫秒)。如果 trackVisibility 設(shè)置為 true,則此值必須至少設(shè)置為 100 ,否則會(huì)報(bào)錯(cuò)(但是這里我也只是親測(cè)出來(lái)的,并不知道為什么會(huì)設(shè)計(jì)成這樣,如果有大佬了解請(qǐng)指教一下)。

          構(gòu)造函數(shù)接收的參數(shù) callback

          當(dāng)元素可見比例超過(guò)指定閾值后,會(huì)調(diào)用一個(gè)回調(diào)函數(shù),此回調(diào)函數(shù)接受兩個(gè)參數(shù):存放 IntersectionObserverEntry 對(duì)象的數(shù)組和觀察器實(shí)例(可選)。

          ((doc) => {
            //回調(diào)函數(shù)
            const callback = (entries, observer) => {
              console.log('????~ 執(zhí)行了一次callback');
              console.log('????~ entries:', entries);
              console.log('????~ observer:', observer);
            };
            //配置對(duì)象
            const options = {};
            //創(chuàng)建觀察器
            const myObserver = new IntersectionObserver(callback, options);
            //獲取目標(biāo)元素
            const target = doc.querySelector(".target")
            //開始監(jiān)聽目標(biāo)元素
            myObserver.observe(target);
          })(document)

          我們把這兩個(gè)參數(shù)打印出來(lái)看一下,可以看到,第一個(gè)參數(shù)是一個(gè)數(shù)組,每一項(xiàng)都是一個(gè)目標(biāo)元素對(duì)應(yīng)的 IntersectionObserverEntry對(duì)象,第二個(gè)參數(shù)是觀察器實(shí)例對(duì)象 IntersectionObserver 。

          什么是 IntersectionObserverEntry 對(duì)象

          展開 IntersectionObserverEntry 看一下都有什么。

          • boundingClientRect: 一個(gè)對(duì)象,包含目標(biāo)元素的 getBoundingClientRect() 方法的返回值。

          • intersectionRatio: 一個(gè)對(duì)象,包含目標(biāo)元素與根元素交叉區(qū)域 getBoundingClientRect() 的返回值。

          • intersectionRect: 目標(biāo)元素的可見比例,即 intersectionRect 占 boundingClientRect 的比例,完全可見時(shí)為 1 ,完全不可見時(shí)小于等于 0 。

          • isIntersecting: 返回一個(gè)布爾值,如果目標(biāo)元素與根元素相交,則返回 true ,如果 isIntersecting 是 true,則 target 元素至少已經(jīng)達(dá)到 thresholds 屬性值當(dāng)中規(guī)定的其中一個(gè)閾值,如果是 false,target 元素不在給定的閾值范圍內(nèi)可見。

          • isVisible: 這個(gè)看字面意思應(yīng)該是 “是否可見” ,如果要讓這個(gè)屬性生效,那么在使用構(gòu)造函數(shù)生成觀察器實(shí)例的時(shí)候,傳入的 options 參數(shù)必須配置 trackVisibility 為 true,并且 delay 設(shè)置為大于 100 ,否則該屬性將永遠(yuǎn)返回 false 。

          • rootBounds: 一個(gè)對(duì)象,包含根元素的 getBoundingClientRect() 方法的返回值。

          • target:: 被觀察的目標(biāo)元素,是一個(gè) DOM 節(jié)點(diǎn)。在觀察者包含多個(gè)目標(biāo)的情況下,這是確定哪個(gè)目標(biāo)元素觸發(fā)了此相交更改的簡(jiǎn)便方法。

          • time: 該屬性提供從 首次創(chuàng)建觀察者 到 觸發(fā)此交集改變 的時(shí)間(以毫秒為單位)。通過(guò)這種方式,你可以跟蹤觀察器達(dá)到特定閾值所花費(fèi)的時(shí)間。即使稍后將目標(biāo)再次滾動(dòng)到視圖中,此屬性也會(huì)提供新的時(shí)間。這可用于跟蹤目標(biāo)元素進(jìn)入和離開根元素的時(shí)間,以及兩個(gè)閾值觸發(fā)的間隔時(shí)間。

          這里再看一下 boundingClientRect ,intersectionRatio , rootBounds 三個(gè)屬性展開的內(nèi)容都有什么。

          • bottom: 元素下邊距離頁(yè)面上邊的距離
          • left: 元素左邊距離頁(yè)面左邊的距離
          • right: 元素右邊距離頁(yè)面左邊的距離
          • top: 元素上邊距離頁(yè)面上邊的距離
          • width: 元素的寬
          • height: 元素的高
          • x: 等同于 left,元素左邊距離頁(yè)面左邊的距離
          • y: 等同于 top,元素上邊距離頁(yè)面上邊的距離

          用一張圖來(lái)展示一下這幾個(gè)屬性,特別需要注意的是 right 和 bottom ,跟我們平時(shí)寫 css 的 position 那個(gè)不一樣 。

          那么第二個(gè)參數(shù) IntersectionObserver 觀察器實(shí)例對(duì)象都有什么呢

          別著急,接著往下看,實(shí)例屬性部分。

          觀察器實(shí)例屬性

          上面留了一個(gè)坑,回調(diào)函數(shù)的第二個(gè)參數(shù) IntersectionObserver 觀察器實(shí)例對(duì)象都有什么呢?我們把實(shí)例對(duì)象打印出來(lái)看一下

          ((doc) => {
            //回調(diào)函數(shù)
            const callback = () => {};
            //配置對(duì)象
            const options = {};
            //創(chuàng)建觀察器
            const myObserver = new IntersectionObserver(callback, options);
            //獲取目標(biāo)元素
            const target = doc.querySelector(".target")
            //開始監(jiān)聽目標(biāo)元素
            myObserver.observe(target);
            console.log('????~ myObserver:', myObserver);
          })(document)

          可以看到,我們的觀察器實(shí)例上面包含如下屬性

          • root
          • rootMargin
          • thresholds
          • trackVisibility
          • delay

          是不是特別眼熟,沒錯(cuò),就是我們創(chuàng)建觀察者實(shí)例的時(shí)候,傳入的 options 對(duì)象,只不過(guò) options 對(duì)象是可選的,觀察器實(shí)例的屬性就使用我們傳入的 options 對(duì)象,如果沒傳就使用默認(rèn)值,唯一不同的是,options 中 的屬性 threshold 是單數(shù),而我們實(shí)例獲取到的 thresholds 是復(fù)數(shù)。

          值得注意的是,這里的所有屬性都是 只讀 的,也就是說(shuō)一旦觀察器被創(chuàng)建,則 無(wú)法 更改其配置,所以一個(gè)給定的觀察者對(duì)象只能用來(lái)監(jiān)聽可見區(qū)域的特定變化值。

          接下來(lái)我們就通過(guò)代碼結(jié)合動(dòng)圖演示一下這些屬性

          ((doc) => {
            let n = 0
            //獲取目標(biāo)元素
            const target = doc.querySelector(".target")
            //獲取根元素
            const root = doc.querySelector(".out-container")
            //回調(diào)函數(shù)
            const callback = (entries, observer) => {
              n++
              console.log(`????~ 執(zhí)行了 ${n} 次callback`);
              console.log('????~ entries:', entries);
              console.log('????~ observer:', observer);
            };
            //配置對(duì)象
            const options = {
              root: root,
              rootMargin'0px 0px 0px 0px',
              threshold: [0.5],
              trackVisibilitytrue,
              delay100
            };
            //創(chuàng)建觀察器
            const myObserver = new IntersectionObserver(callback, options);
            //開始監(jiān)聽目標(biāo)元素
            myObserver.observe(target);
            console.log('????~ myObserver:', myObserver);
          })(document)

          root這個(gè)沒什么說(shuō)的,就是設(shè)置指定節(jié)點(diǎn)為根元素rootMargin我們把 rootMargin 修改為 '50px 50px 50px 50px',可以看到,我們的目標(biāo)元素還沒有露出來(lái)的時(shí)候回調(diào)函數(shù)就已經(jīng)執(zhí)行了,也就是說(shuō)目標(biāo)元素距離根元素還有 50px 的 margin 時(shí),觀察器就認(rèn)為是發(fā)生了交叉。thresholds我們把 threshold 修改為 [0.1, 0.3, 0.5, 0.8, 1],可以看到,回調(diào)函數(shù)觸發(fā)了多次,也就是說(shuō)當(dāng)交叉區(qū)域的百分比,每達(dá)到指定的閾值時(shí)都會(huì)觸發(fā)一次回調(diào)函數(shù)。

          注意 Intersection Observer API 無(wú)法提供重疊的像素個(gè)數(shù)或者具體哪個(gè)像素重疊,他的更常見的使用方式是——當(dāng)兩個(gè)元素相交比例在 N% 左右時(shí),觸發(fā)回調(diào),以執(zhí)行某些邏輯。 -- MDN

          trackVisibility修改 trackVisibility 為 true ,可以看到, isVisible 屬性值為 true 。修改 css 屬性 為 opacity: 0,可以看到,雖然我們藍(lán)色小方塊并沒有出現(xiàn)在視圖中,但是回調(diào)函數(shù)已經(jīng)執(zhí)行了,并且 isVisible 屬性值為 false 而 isIntersecting 值為 true 。delay回調(diào)函數(shù)延遲觸發(fā),我們修改 delay 為 3000,可以看到 log 是 3000ms 以后才輸出的。

          觀察器實(shí)例方法

          通過(guò)此段代碼來(lái)演示觀察器實(shí)例方法,為了方便演示,我添加了幾個(gè)對(duì)應(yīng)的按鈕。

          ((doc) => {
            let n = 0
            //獲取目標(biāo)元素
            const target1 = doc.querySelector(".target1")
            const target2 = doc.querySelector(".target2")
            //添加幾個(gè)按鈕方便操作
            const observe = doc.querySelector(".observe")
            const unobserve = doc.querySelector(".unobserve")
            const disconnect = doc.querySelector(".disconnect")
            observe.addEventListener('click', () => myObserver.observe(target1))
            unobserve.addEventListener('click', () => myObserver.unobserve(target1))
            disconnect.addEventListener('click', () => myObserver.disconnect())
            //獲取根元素
            const root = doc.querySelector(".out-container")
            //回調(diào)函數(shù)
            const callback = (entries, observer) => {
              n++
              console.log(`????~ 執(zhí)行了 ${n} 次callback`);
              console.log('????~ entries:', entries);
              console.log('????~ observer:', observer);
            };
            //配置對(duì)象
            const options = {
              root: root,
              rootMargin'0px 0px 0px 0px',
              threshold: [0.10.20.30.5],
              trackVisibilitytrue,
              delay100
            };
            //創(chuàng)建觀察器
            const myObserver = new IntersectionObserver(callback, options);
            //開始監(jiān)聽目標(biāo)元素
            myObserver.observe(target2);
            console.log('????~ myObserver:', myObserver);
          })(document)

          observe

           const myObserver = new IntersectionObserver(callback, options);
           myObserver.observe(target);

          接受一個(gè)目標(biāo)元素作為參數(shù)。很好理解,當(dāng)我們創(chuàng)建完觀察器實(shí)例后,要手動(dòng)的調(diào)用 observe 方法來(lái)通知它開始監(jiān)測(cè)目標(biāo)元素。

          可以在同一個(gè)觀察者對(duì)象中配置監(jiān)聽多個(gè)目標(biāo)元素

          target2 元素是通過(guò)代碼自動(dòng)監(jiān)測(cè)的,而 target1 則是我們?cè)邳c(diǎn)擊了 observe 按鈕之后開始監(jiān)測(cè)的。通過(guò)動(dòng)圖可以看到,當(dāng)我單擊 observe 按鈕后,我們的 entries 數(shù)組里面就包含了兩條數(shù)據(jù),前文中說(shuō)到,可以通過(guò) target 屬性來(lái)判斷是哪個(gè)目標(biāo)元素。

          unobserve

           const myObserver = new IntersectionObserver(callback, options);
           myObserver.observe(target);
           myObserver.unobserve(target)
          復(fù)制代碼

          接收一個(gè)目標(biāo)元素作為參數(shù),當(dāng)我們不想監(jiān)聽某個(gè)元素的時(shí)候,需要手動(dòng)調(diào)用 unobserve 方法來(lái)停止監(jiān)聽指定目標(biāo)元素。通過(guò)動(dòng)圖可以發(fā)現(xiàn),當(dāng)我們點(diǎn)擊 unobserve 按鈕后,由兩條數(shù)據(jù)變成了一條數(shù)據(jù),說(shuō)明 target1 已經(jīng)不再接受監(jiān)測(cè)了。

          disconnect

           const myObserver = new IntersectionObserver(callback, options);
           myObserver.disconnect()

          當(dāng)我們不想監(jiān)測(cè)任何一個(gè)目標(biāo)元素時(shí),我們需要手動(dòng)調(diào)用 disconnect 方法停止監(jiān)聽工作。通過(guò)動(dòng)圖可以看到,當(dāng)我們點(diǎn)擊 disconnect 按鈕后,控制臺(tái)不再輸出 log ,說(shuō)明監(jiān)聽工作已經(jīng)停止,可以通過(guò) observe 再次開啟監(jiān)聽工作。

          takeRecords

          返回所有觀察目標(biāo)的 IntersectionObserverEntry 對(duì)象數(shù)組,應(yīng)用場(chǎng)景較少。

          當(dāng)觀察到交互動(dòng)作發(fā)生時(shí),回調(diào)函數(shù)并不會(huì)立即執(zhí)行,而是在空閑時(shí)期使用 requestIdleCallback 來(lái)異步執(zhí)行回調(diào)函數(shù),但是也提供了同步調(diào)用的 takeRecords 方法。

          如果異步的回調(diào)先執(zhí)行了,那么當(dāng)我們調(diào)用同步的 takeRecords 方法時(shí)會(huì)返回空數(shù)組。同理,如果已經(jīng)通過(guò) takeRecords 獲取了所有的觀察者實(shí)例,那么回調(diào)函數(shù)就不會(huì)被執(zhí)行了。

          注意事項(xiàng)

          構(gòu)造函數(shù) IntersectionObserver 配置的回調(diào)函數(shù)都在哪些情況下被調(diào)用?

          構(gòu)造函數(shù) IntersectionObserver 配置的回調(diào)函數(shù),在以下情況發(fā)生時(shí)可能會(huì)被調(diào)用

          • 當(dāng)目標(biāo)(target)元素與根(root)元素發(fā)生交集的時(shí)候執(zhí)行。
          • 兩個(gè)元素的相交部分大小發(fā)生變化時(shí)。
          • Observer 第一次監(jiān)聽目標(biāo)元素的時(shí)候。
          ((doc) => {
            //回調(diào)函數(shù)
            const callback = () => {
              console.log('????~ 執(zhí)行了一次callback');
            };
            //配置對(duì)象
            const options = {};
            //觀察器實(shí)例
            const myObserver = new IntersectionObserver(callback, options);
            //目標(biāo)元素
            const target = doc.querySelector("#target")
            //開始觀察
            myObserver.observe(target);
          })(document)

          可以看到,無(wú)論目標(biāo)元素是否與根元素相交,當(dāng)我們第一次監(jiān)聽目標(biāo)元素的時(shí)候,回調(diào)函數(shù)都會(huì)觸發(fā)一次,所以不要直接在回調(diào)函數(shù)里寫邏輯代碼,盡量通過(guò) isIntersecting 或者 intersectionRect 進(jìn)行判斷之后再執(zhí)行邏輯代碼。

          頁(yè)面的可見性如何監(jiān)測(cè)

          頁(yè)面的可見性可以通過(guò)document.visibilityState或者document.hidden獲得。頁(yè)面可見性的變化可以通過(guò)document.visibilitychange來(lái)監(jiān)聽。

          可見性和交叉觀察

          當(dāng) css 設(shè)置了opacity: 0,visibility: hidden 以及 用其他的元素覆蓋目標(biāo)元素 ,都不會(huì)影響交叉觀察器的監(jiān)測(cè),也就是都不會(huì)影響 isIntersecting 屬性的結(jié)果,但是會(huì)影響 isVisible 屬性的結(jié)果, 如果元素設(shè)置了 display:none 就不會(huì)被檢測(cè)了。當(dāng)然影響元素可視性的屬性不止上述這些,還包括positionmargin,clip 等等等等...就靠小伙伴們自行發(fā)掘了

          交集的計(jì)算

          所有區(qū)域均被 Intersection Observer API 當(dāng)做一個(gè) 矩形 看待。如果元素是不規(guī)則的圖形也將會(huì)被看成一個(gè)包含元素所有區(qū)域的最小矩形,相似的,如果元素發(fā)生的交集部分不是一個(gè)矩形,那么也會(huì)被看作是一個(gè)包含他所有交集區(qū)域的最小矩形。

          我怎么知道目標(biāo)元素來(lái)自視口的上方還是下方

          目標(biāo)元素滾動(dòng)的方向也是可以判斷的,原理是根元素的 entry.rootBounds.y 是固定不變的 ,所以我們只需要計(jì)算 entry.boundingClientRect.y 與 entry.rootBounds.y 的大小,當(dāng)回調(diào)函數(shù)觸發(fā)的時(shí)候,我們記錄下當(dāng)時(shí)的位置,如果 entry.boundingClientRect.y < entry.rootBounds.y,說(shuō)明是在視口下方,那么當(dāng)下一次目標(biāo)元素可見的時(shí)候,我們就知道目標(biāo)元素時(shí)來(lái)自視口下方的,反之亦然。

          let wasAbove = false;
          function callback(entries, observer{
              entries.forEach(entry => {
                  const isAbove = entry.boundingClientRect.y < entry.rootBounds.y;
                  if (entry.isIntersecting) {
                      if (wasAbove) {
                          // Comes from top
                      }
                  }
                  wasAbove = isAbove;
              });
          }

          應(yīng)用場(chǎng)景

          介紹完基礎(chǔ)知識(shí),總得來(lái)幾個(gè)實(shí)例(演示代碼采用VUE3.0),當(dāng)然實(shí)際場(chǎng)景要比這復(fù)雜的多,如何在自己的工作學(xué)習(xí)中應(yīng)用,還是要靠小伙伴們多多開動(dòng)聰明的大腦~

          數(shù)據(jù)列表無(wú)限滾動(dòng)

          <template>
            <div class="box">
              <div class="vbody"
                   v-for='item in list'
                   :key='item'>
          內(nèi)容區(qū)域{{item}}</div>
              <div class="reference"
                   ref='reference'>
          </div>
            </div>

          </template>

          <script lang='ts'>
          import { defineComponent, onMounted, reactive, ref } from 'vue'

          export default defineComponent({
            name: '',
            setup() {
              const reference = ref(null)
              const list = reactive([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
              onMounted(() => {
                let n = 10
                /
          /回調(diào)函數(shù)
                const callback = (entries) => {
                  const myEntry = entries[0]
                  if (myEntry.isIntersecting) {
                    console.log(`????~ 觸發(fā)了無(wú)線滾動(dòng),開始模擬請(qǐng)求數(shù)據(jù) ${n}`)
                    n++
                    list.push(n)
                  }
                }
                /
          /配置對(duì)象
                const options = {
                  root: null,
                  rootMargin: '0px 0px 0px 0px',
                  threshold: [0, 1],
                  trackVisibility: true,
                  delay: 100,
                }
                /
          /觀察器實(shí)例
                const myObserver = new IntersectionObserver(callback, options)
                /
          /開始觀察
                myObserver.observe(reference.value)
              })

              return { reference, list }
            },
          })
          </
          script>

          <style>
          * {
            margin0;
            padding0;
            box-sizing: border-box;
          }
          .reference {
            width100%;
            visibility: hidden;
          }
          .vbody {
            width100%;
            height200px;
            background-color: red;
            color: aliceblue;
            font-size40px;
            text-align: center;
            line-height200px;
            margin10px 0;
          }
          </style>

          我們只需要在底部添加一個(gè)參考元素,當(dāng)參考元素可見時(shí),就向后臺(tái)請(qǐng)求數(shù)據(jù),就可以實(shí)現(xiàn)無(wú)線滾動(dòng)的效果了。

          圖片預(yù)加載

          <template>
            <div class="box">
              <div class="vbody">內(nèi)容區(qū)域</div>
              <div class="vbody">內(nèi)容區(qū)域</div>
              <div class="header"
                   ref='header'>

                <img :src="url">
              </div>
              <div class="vbody">內(nèi)容區(qū)域</div>
            </div>
          </template>


          <script lang='ts'>
          import { defineComponent, onMounted, ref } from 'vue'

          export default defineComponent({
            name'',
            setup() {
              const header = ref(null)
              const url = ref('')
              onMounted(() => {
                //回調(diào)函數(shù)
                const callback = (entries) => {
                  const myEntry = entries[0]
                  if (myEntry.isIntersecting) {
                    console.log('????~ 預(yù)加載圖片開始')
                    url.value =
                      '//img10.360buyimg.com/imgzone/jfs/t1/197235/15/2956/67824/6115e076Ede17a418/d1350d4d5e52ef50.jpg'
                  }
                }
                //配置對(duì)象
                const options = {
                  rootnull,
                  rootMargin'200px 200px 200px 200px',
                  threshold: [0],
                  trackVisibilitytrue,
                  delay100,
                }
                //觀察器實(shí)例
                const myObserver = new IntersectionObserver(callback, options)
                //開始觀察
                myObserver.observe(header.value)
              })

              return { header, url }
            },
          })
          </script>


          <style>
          * {
            margin0;
            padding0;
            box-sizing: border-box;
          }
          .box {
          }
          .header {
            width100%;
            height400px;
            background-color: blue;
            color: aliceblue;
            font-size40px;
            text-align: center;
            line-height400px;
          }
          .header img {
            width100%;
            height100%;
          }
          .reference {
            width100%;
            visibility: hidden;
          }
          .vbody {
            width100%;
            height800px;
            background-color: red;
            color: aliceblue;
            font-size40px;
            text-align: center;
            line-height800px;
            margin10px 0;
          }
          </style>

          利用 options 的 rootMargin屬性,可以在圖片即將進(jìn)入可視區(qū)域的時(shí)間進(jìn)行圖片的加載,即避免了提前請(qǐng)求大量圖片造成的性能問(wèn)題,也避免了圖片進(jìn)入窗口才加載已經(jīng)來(lái)不及的問(wèn)題。

          吸頂

          <template>
            <div class="box">
              <div class="reference"
                   ref='reference'>
          </div>
              <div class="header"
                   ref='header'>
          吸頂區(qū)域</div>
              <div class="vbody">內(nèi)容區(qū)域</div>
              <div class="vbody">內(nèi)容區(qū)域</div>
              <div class="vbody">內(nèi)容區(qū)域</div>
            </div>

          </template>

          <script lang='ts'>
          import { defineComponent, onMounted, ref } from 'vue'

          export default defineComponent({
            name: '',
            setup() {
              const header = ref(null)
              const reference = ref(null)

              onMounted(() => {
                /
          /回調(diào)函數(shù)
                const callback = (entries) => {
                  const myEntry = entries[0]
                  if (!myEntry.isIntersecting) {
                    console.log('????~ 觸發(fā)了吸頂')
                    header.value.style.position = 'fixed'
                    header.value.style.top = '0px'
                  } else {
                    console.log('????~ 取消吸頂')
                    header.value.style.position = 'relative'
                  }
                }
                /
          /配置對(duì)象
                const options = {
                  root: null,
                  rootMargin: '0px 0px 0px 0px',
                  threshold: [0, 1],
                  trackVisibility: true,
                  delay: 100,
                }
                /
          /觀察器實(shí)例
                const myObserver = new IntersectionObserver(callback, options)
                /
          /開始觀察
                myObserver.observe(reference.value)
              })

              return { reference, header }
            },
          })
          </
          script>

          <style>
          * {
            margin0;
            padding0;
            box-sizing: border-box;
          }
          .header {
            width100%;
            height100px;
            background-color: blue;
            color: aliceblue;
            font-size40px;
            text-align: center;
            line-height100px;
          }
          .reference {
            width100%;
            visibility: hidden;
          }
          .vbody {
            width100%;
            height800px;
            background-color: red;
            color: aliceblue;
            font-size40px;
            text-align: center;
            line-height800px;
            margin10px 0;
          }
          </style>

          思路就是利用一個(gè)參考元素作為交叉觀察器觀察的對(duì)象,當(dāng)參考元素可見的時(shí)候,取消吸頂區(qū)域的 fixed 屬性,否則添加 fixed 屬性,吸底稍微復(fù)雜一點(diǎn),但是道理差不多,留給小伙伴們自行研究吧 ~ ~。

          埋點(diǎn)上報(bào)

          <template>
            <div class="box">
              <div class="vbody">內(nèi)容區(qū)域</div>
              <div class="vbody">內(nèi)容區(qū)域</div>
              <div class="header"
                   ref='header'>
          埋點(diǎn)區(qū)域</div>
              <div class="vbody">內(nèi)容區(qū)域</div>

            </div>

          </template>

          <script lang='ts'>
          import { defineComponent, onMounted, ref } from 'vue'

          export default defineComponent({
            name: '',
            setup() {
              const header = ref(null)

              onMounted(() => {
                /
          /回調(diào)函數(shù)
                const callback = (entries) => {
                  const myEntry = entries[0]
                  if (myEntry.isIntersecting) {
                    console.log('????~ 觸發(fā)了埋點(diǎn)')
                  }
                }
                /
          /配置對(duì)象
                const options = {
                  root: null,
                  rootMargin: '0px 0px 0px 0px',
                  threshold: [0.5],
                  trackVisibility: true,
                  delay: 100,
                }
                /
          /觀察器實(shí)例
                const myObserver = new IntersectionObserver(callback, options)
                /
          /開始觀察
                myObserver.observe(header.value)
              })

              return { header }
            },
          })
          </
          script>

          <style>
          * {
            margin0;
            padding0;
            box-sizing: border-box;
          }
          .header {
            width100%;
            height400px;
            background-color: blue;
            color: aliceblue;
            font-size40px;
            text-align: center;
            line-height400px;
          }
          .vbody {
            width100%;
            height800px;
            background-color: red;
            color: aliceblue;
            font-size40px;
            text-align: center;
            line-height800px;
            margin10px 0;
          }
          </style>

          通常情況下,我們統(tǒng)計(jì)一個(gè)元素是否被用戶有效的看到,并不是元素剛出現(xiàn)就觸發(fā)埋點(diǎn),而是元素進(jìn)入可是區(qū)域一定比例才可以,我們可以配置 options 的 threshold 為 0.5。

          等等等等。。。。

          這個(gè) api 可以說(shuō)是非常強(qiáng)大了,可玩性也是極高,大家自由發(fā)揮 ~ ~

          兼容性

          為什么有兩張兼容性的圖呢?因?yàn)?nbsp;trackVisibility 和 delay 兩個(gè)屬性是屬于 IntersectionObserver V2 的。所以小伙伴們?cè)谟玫臅r(shí)候一定要注意兼容性。當(dāng)然也有兼容解決方案,那就是 intersection-observer-polyfill

          參考資料

          [1] Can I Use:

          https://caniuse.com/?search=IntersectionObserver%20

          [2] MDN Intersection Observer:

          https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver

          [3] IntersectionObserver API 使用教程:

          https://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html

          [4] intersection-observer-polyfill:

          https://www.npmjs.com/package/intersection-observer-polyfill


          瀏覽 128
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  台湾成人在线观看 | 操骚逼视频 | 国产另类视频 | 成人在线中文免费视频 | 日本五十路垂乳熟年 |