Interection Observer如何觀察變化

原文地址:css-tricks.com/an-explanat…
原文做者:Travis Almand
翻譯:劉輝css

有不少精彩的文章探討了如何使用Intersection Observer API,包括Phil Hawksworth,Preethi和Mateusz Rybczonek等。 我這篇文章將講一些不同的東西。 我在今年早些時候有幸向達拉斯VueJS聚會介紹了VueJS過渡組件,我在CSS-Tricks的第一篇文章就是以此爲基礎的。 在演講的問答環節中,有人問我基於滾動事件觸發過渡怎麼樣 - 我說固然能夠,可是一些聽衆建議我瞭解一下Intersection Observergit

這讓我開始思考。我對Intersection Observer有基本的瞭解,而且可以用其完成簡單的示例。 我是否知道它的工做原理而不只僅是使用它?它到底爲咱們開發人員提供了什麼? 做爲一個資深開發者,我如何向新手甚至不知道它存在的開發者解釋它的工做原理?github

在花了一些時間進行研究,測試和驗證後,我決定分享本身學到的東西。web

Intersection Observer 簡述

W3C公共工做草案摘要(日期爲2017年9月14日的初稿)將Intersection Observer API描述爲:數組

本規範描述了一個API,可用於瞭解DOM元素(targets)相對於包含元素或頂級視口(root)的可見性和位置。 該位置是異步傳遞的,對於理解元素的可見性以及實現DOM內容的預加載和延遲加載頗有用。瀏覽器

這個API的整體思路是提供一種觀察子元素並在其進入其父元素之一的邊界框內時獲得通知的方法。 目標元素滾動到根元素視圖中時最經常使用。 在引入Intersection Observer以前,此類功能是經過偵聽滾動事件來完成的。bash

儘管Intersection Observer是針對此類功能的更高性能的解決方案,但我不建議咱們將其視爲滾動事件的替代品。 相反,我建議咱們將此API視爲與滾動事件在功能上互補的額外工具。 在某些狀況下,二者能夠一塊兒解決特定的問題。服務器

基本示例

我知道我有可能重複其餘文章中已經講過的內容,不過仍是讓咱們先來看一個Intersection Observer的基本示例及其提供的能力。微信

Observer由四部分組成:異步

  1. root,是觀察者所綁定的父元素,能夠是viewport
  2. target,它是被觀察的子元素,能夠有多個
  3. options對象,它定義了觀察者某些方面的行爲
  4. 回調函數,每次觀察到父子元素的交集變化時都會調用

基本示例的代碼以下所示:

const options = {
  root: document.body,
  rootMargin: '0px',
  threshold: 0
}

function callback (entries, observer) {
  console.log(observer);

  entries.forEach(entry => {
    console.log(entry);
  });
}

let observer = new IntersectionObserver(callback, options);
observer.observe(targetElement);

複製代碼

代碼的第一部分是options對象,它具備rootrootMarginthreshold屬性。

root是父元素,通常是有滾動條的元素,其中包含被觀察的元素。根據須要,這幾乎能夠是頁面上的任何單個元素。若是不提供該屬性,或者該值設置爲null,跟元素就是viewport。

rootMargin描述了根元素的外邊距,由rootMargin規定的矩形的每一邊都會被添加至root元素的邊框盒(bounding box)的相應邊。它的行爲很像CSS margin屬性。你可使用相似10px 15px 20px的值,這使咱們的頂部邊距爲10px,左側和右側邊距爲15px,底部邊距爲20px。僅邊界框受影響,元素自己不受影響。請記住,惟一容許的長度是像素和百分比值,能夠是負數或正數。另請注意,若是root元素不是頁面上的實際元素(例如viewport),則rootMargin無效。

threshold是用於肯定什麼時候觸發交集改變事件的值。數組中能夠包含多個值,以便同一目標能夠屢次觸發交集改變事件。不一樣的值是使用0到1的百分比,很是相似於CSS中的不透明度,所以將0.5的值視爲50%,依此類推。這些值與目標的交叉比例有關,稍後將對其進行說明。閾值爲0時,目標元素的第一個像素與根元素相交就會觸發交集改變事件。閾值爲1時,整個目標元素都在根元素內部時纔會觸發交集改變事件。

代碼的第二部分是回調函數,只要觀察到交集改變,就會調用該函數。傳遞了兩個參數;entries是個數組,表明觸發交集更改的每一個目標元素。這提供了不少信息爲開發人員所用。第二個參數是有關觀察者自己的信息。若是目標綁定到多個觀察者,能夠經過此參數識別是哪一個觀察者。

代碼的第三部分是觀察者自己的建立以及觀察對象。建立觀察者時,回調函數和options對象能夠放在觀察者外部。 若是須要,能夠在多個觀察者之間使用相同的回調和options對象。而後,將須要觀察的目標元素傳遞給observe()方法。它只能接受一個目標,可是能夠在同一觀察者上針對多個目標重複調用該方法。

注意代碼中的console.log,能夠看看控制檯輸出了什麼。

觀察者對象

傳遞給回調函數的觀察者數據以下:

IntersectionObserver
  root: null
  rootMargin: "0px 0px 0px 0px"
  thresholds: Array [ 0 ]
  <prototype>: IntersectionObserverPrototype { }

複製代碼

...本質上是建立對象時傳遞給觀察者的選options對象。 這可用於肯定相交所綁定的根元素。 注意即便原始選項對象的rootMargin值爲0px,該對象也將其轉爲0px 0px 0px 0px,這是CSS邊距規範所須要的。而後是觀察者正在使用的一系列閾值。

entry對象

傳遞給回調函數的entry對象數據以下:

IntersectionObserverEntry
  boundingClientRect: DOMRect
    bottom: 923.3999938964844, top: 771
    height: 152.39999389648438, width: 411
    left: 9, right: 420
    x: 9, y: 771
    <prototype>: DOMRectPrototype { }
  intersectionRatio: 0
  intersectionRect: DOMRect
    bottom: 0, top: 0
    height: 0, width: 0
    left: 0, right: 0
    x: 0, y: 0
    <prototype>: DOMRectPrototype { }
  isIntersecting: false
  rootBounds: null
  target: <div class="item">
  time: 522
  <prototype>: IntersectionObserverEntryPrototype { }

複製代碼

能夠看到,這裏作了不少工做。

對於大多數開發人員而言,最可能有用的兩個屬性是intersectionRatioisIntersectingisIntersecting屬性是一個布爾值,在交集更改時目標元素與根元素是否相交。intersectionRatio是當前與根元素相交的目標元素的百分比。它也是零到一之間的百分比表示,很是相似於觀察者的options對象中threshold

三個屬性(boundingClientRectintersectionRectrootBounds)表示交集相關的三個方面的具體數據。 boundingClientRect屬性爲目標元素的邊界框提供從viewport左上角開始的bottom,left,right和top值,就像Element.getBoundingClientRect()同樣。而後,將目標元素的高度和寬度做爲X和Y座標提供。 rootBounds屬性爲根元素提供相同形式的數據。intersectionRect提供類似的數據,它描述了由目標元素在根元素內部的相交區域造成的矩形,該區域也被用於計算intersectionRatio值。傳統的滾動事件須要手動完成此計算。

要注意的是,表明這些不一樣元素的全部這些形狀始終都是矩形。不管所涉及元素的實際形狀如何,它們老是會縮小到包含該元素的最小矩形。

target屬性是指正在觀察的目標元素。在觀察者包含多個目標的狀況下,這是肯定哪一個目標元素觸發了此相交更改的簡便方法。

time屬性提供從首次建立觀察者到觸發此交集改變的時間(以毫秒爲單位)。經過這種方式,你能夠跟蹤觀看者遇到特定目標所花費的時間。即便稍後將目標再次滾動到視圖中,此屬性也會提供新的時間。這可用於跟蹤目標進入和離開根元素的時間。

除了每次觀察到交集改變時咱們能夠得到這些信息外,觀察者第一次啓動時也會向咱們提供這些信息。例如,在頁面加載時,頁面上的觀察者將當即調用回調函數,並提供它正在觀察的每一個目標元素的當前狀態。

Intersection Observer以很是高效的方式提供了有關頁面上元素之間關係的數據。

Intersection Observer 可用的方法

Intersection Observer 主要有三個方法:observe(),unobserve()和disconnect()。

  • observe():observe方法用來添加觀察者要監視的目標元素。 觀察者能夠具備多個目標元素,可是此方法一次只能接受一個目標。
  • unobserve():unobserve方法用來從觀察的元素列表中移除元素。
  • disconnect():disconnect方法用來中止觀察其全部目標元素。觀察者自己仍處於活動狀態,但沒有目標。在disconnect()以後,目標元素仍然能夠經過observe()傳遞給觀察者。

這些方法提供了監視和取消監視目標元素的功能,可是一旦建立,便沒法更改傳遞給觀察者的options對象。 若是須要修改,則必須手動從新建立觀察者。

Intersection Observer和滾動事件的性能對比

在探索Intersection Observer以及將其與使用滾動事件進行比較時,我須要進行一些性能測試。我只想大體瞭解二者之間的性能差別,爲此我建立了三個簡單的測試。

首先,我建立了一個樣本HTML文件,該文件包含一百個設置了高度的div,以此建立一個長滾動頁面。把頁面放在靜態服務器上,而後我用Puppeteer加載了HTML文件,啓動了跟蹤,讓頁面以預設的增量向下滾動到底部,一旦到達底部,就中止了跟蹤,最後保存跟蹤的結果。這樣測試能夠重複屢次並輸出每次的結果數據。而後,我複製了樣本HTML,併爲要運行的每種測試類型在腳本標籤中編寫了js。每一個測試都有兩個文件:一個用於Intersection Observer,另外一個用於滾動事件。

全部測試的目的是檢測目標元素什麼時候以25%的增量向上滾動經過視口。每次增長時,都會應用CSS類來更改元素的背景顏色。換句話說,每一個元素都應用了DOM修改,這將觸發重繪。每次測試都在兩臺不一樣的計算機上運行了五次:個人開發用的Mac是最新的設備,而個人我的Windows 7計算機多是當前的平均水平。記錄腳本,渲染,繪畫和系統的跟蹤結果,而後取平均值。

第一個測試有一個觀察者或一個滾動事件,每一個事件都有一個回調。對於觀察者和滾動事件,這是一個至關標準的設置。儘管在這種狀況下,滾動事件還有不少工做要作,由於滾動事件試圖模仿觀察者默認提供的數據。完成全部這些計算後,就像觀察者同樣,將數據存儲在條目數組中。而後,在二者之間刪除和應用類的功能徹底相同。另外我使用了requestAnimationFrame對滾動事件進行了節流處理。

第二個測試有100個觀察者或100個滾動事件,每種類型都有一個回調。每一個元素都分配有本身的觀察者和事件,但回調函數相同。這其實是低效的,由於每一個觀察者和事件的行爲都徹底相同,可是我想要一個簡單的壓力測試,而沒必要建立100個惟一的觀察者和事件-儘管我已經看到了許多以這種方式使用觀察者的示例。

第三次測試具備100個觀察者或100個滾動事件,每種類型具備100個回調。這意味着每一個元素都有其本身的觀察器,事件和回調函數。固然,這是極其低效的,由於這是存儲在巨大陣列中的全部重複功能。可是這種低效率是該測試的重點。

Intersection Observer和滾動事件的壓力測試對比

在上面的圖表中,你能夠看到,第一列表明咱們的基準,根本沒有運行JavaScript。接下來的兩列表明第一種測試類型。 Mac的運行都很是好,符合我對開發用高端計算機的預期。 Windows機器給了咱們一個不同的結果。對我來講,主要的興趣點是紅色所表明的腳本。在Mac上,觀察者的差別約爲88毫秒,而滾動事件的差別約爲300毫秒。在Mac上,每種測試的整體結果都至關接近,可是腳本在滾動事件方面表現出色。對於Windows機器,它要差得多得多。觀察者大約是150毫秒,而第一次和最簡單的測試是1400毫秒。

對於第二個測試,咱們開始看到滾動測試的效率變得更加明顯。 Mac和Windows機器都運行了觀察者測試,結果與之前幾乎相同。對於滾動事件測試,腳本陷入了更多困境,沒法完成給定的任務。 Mac躍升到幾乎一整秒的腳本編寫時間,而Windows計算機躍升到驚人的3200ms。

對於第三次測試,狀況沒有變壞。結果與第二項測試大體相同。要注意的一件事是,在全部三個測試中,觀察者的結果對於兩臺計算機都是一致的。儘管沒有爲提升觀察者測試的效率作出任何優化,但Intersection Observer的性能表現仍是遠遠超過了滾動事件。

所以,在我本身的兩臺機器上進行了非科學性測試以後,我感到對滾動事件和Intersection Observer之間的性能差別有一個不錯的瞭解。 我敢確定,我能夠經過一些努力使滾動事件更有效,但這值得嗎? 在某些狀況下,滾動事件的精度是必需的,可是在大多數狀況下,Intersection Observer就足夠了-尤爲是由於它看起來更加高效,而無需付出任何努力。

搞清intersectionRatio屬性

IntersectionObserverEntry給咱們提供的intersectionRatio屬性,表示目標元素在交集更改上的根元素邊界內的百分比。 我發現我一開始不太瞭解這個值的實際含義。 因爲某種緣由,我認爲這是目標元素外觀的一種簡單的0%到100%的表示形式。 它與建立時傳遞給觀察者的閾值相關。 例如,它可用於肯定哪一個閾值是剛剛觸發相交更改的緣由。 可是,它提供的值並不老是很簡單。

以這個demo爲例:

demo

在此demo中,已爲觀察者分配了父容器做爲根元素。 具備目標背景的子元素已分配爲目標元素。 已建立閾值數組,其中包含100個條目,其順序爲0、0.0一、0.0二、0.03,依此類推,直到1。觀察者觸發目標元素在根元素內部出現或消失的每個百分比,以便每當比率 更改至少百分之一,此框下方的輸出文本將更新。 若是您感到好奇,可使用如下代碼來完成此閾值:

[...Array(100).keys()].map(x => x / 100) }
複製代碼

我不建議你以這種方式爲項目中的具體用途設置閾值。

首先,目標元素徹底包含在根元素中,而且按鈕上方的輸出將顯示比率1。它應該是第一次加載的,可是咱們很快就會發現該比率並不老是精確的;該數字可能在0.99到1之間。這彷佛很奇怪,可是有可能發生,所以,若是你對等於特定值的比率進行檢查,請記住這一點。

單擊「left」按鈕將使目標元素向左轉換,以使其一半在根元素中,另外一半不在。而後,ratioRatio應該更改成0.5,或者接近0.5。如今咱們知道目標元素的一半與根元素相交,可是咱們不知道它在哪裏。之後再說。

單擊「top」按鈕具備相同的功能。它將目標元素轉換爲根元素的頂部,並再次將其移入和移出。再一次,交集比率應該在0.5左右。即便目標元素位於與之前徹底不一樣的位置,結果比率也相同。

再次單擊「corner」按鈕,會將目標元素轉換爲根元素的右上角。此時,目標元素中只有四分之一位於根元素內。intersectionRatio應以大約0.25的值反映出來。單擊「center」會將目標元素轉換回中心並徹底包含在根元素中。

若是單擊「large」按鈕,則將目標元素的高度更改成高於根元素。相交比應爲0.8左右。這是依賴intersectionRatio的棘手部分。根據提供給觀察者的閾值建立代碼可使閾值永遠不會觸發。在此「large」示例中,基於閾值1的任何代碼都將沒法執行。還要考慮能夠調整根元素大小的狀況,例如將視口從縱向旋轉爲橫向。

查找位置

那麼,咱們如何知道目標元素相對於根元素的位置呢?此數據由IntersectionObserverEntry提供,所以咱們只須要進行簡單的比較便可。

看這個demo:

demo2

該演示的設置與以前的設置大體相同。 父容器是根元素,內部具備目標背景的子容器是目標元素。 閾值是一個0、0.5和1的數組。在根元素中滾動時,將出現目標,而且其位置將在按鈕上方的輸出中報告。

下面執行這些檢查的代碼:

const output = document.querySelector('#output pre');

function io_callback (entries) {
  const ratio = entries[0].intersectionRatio;
  const boundingRect = entries[0].boundingClientRect;
  const intersectionRect = entries[0].intersectionRect;

  if (ratio === 0) {
    output.innerText = 'outside';
  } else if (ratio < 1) {
    if (boundingRect.top < intersectionRect.top) {
      output.innerText = 'on the top';
    } else {
      output.innerText = 'on the bottom';
    }
  } else {
    output.innerText = 'inside';
  }
}

複製代碼

我應該指出,我沒有遍歷entrys數組,由於我知道老是隻有一個條目,由於只有一個目標。我走了捷徑,使用entries[0]

您會發現比率爲零會將目標置於「外部」。小於1的比率將其放在頂部或底部。這樣一來,咱們就能夠查看目標的「頂部」是否小於交集矩形的頂部,這實際上意味着目標在頁面上更高,並被視爲「頂部」。實際上,檢查根元素的「頂部」也能夠解決此問題。從邏輯上講,若是目標不在頂部,則它必須在底部。若是比率剛好等於1,則它在根元素「內部」。除了使用left或right屬性檢查水平位置外,其餘檢查方法相同。

這是高效使用Intersection Observer的一部分。開發人員無需在節流的滾動事件上從多處請求此數據,而後進行計算。它是由觀察者提供的,所須要的只是一個簡單的if檢查。

首先,目標元素要比根元素高,所以永遠不會將其報告爲「內部」。單擊「切換目標大小」按鈕以使其小於根。如今,上下滾動時目標元素能夠位於根元素內部。

經過再次單擊「toggle target size」,而後單擊「toggle root size」按鈕,將目標元素恢復爲其原始大小。這將調整根元素的大小,使其比目標元素高。再次,當上下滾動時,目標元素可能位於根元素內部。

此demo演示了有關Intersection Observer的兩件事:如何肯定目標元素相對於根元素的位置以及調整兩個元素的大小時會發生什麼。這種對調整大小的響應讓咱們看到了Intersection Observer相對於滾動事件的另外一個優點-不用再單獨處理resize事件。

建立位置粘性事件

CSS position屬性的「sticky」是一個有用的功能,但在CSS和JavaScript方面卻有一些限制。粘性節點的樣式只能是一種設計,不管是處於其正常狀態仍是處於其粘性狀態內。沒辦法讓js知道這些變化。到目前爲止,尚未僞類或js事件使咱們知道元素的狀態變化。

我已經看到了使用滾動事件和Intersection Observer進行粘性定位事件的示例。使用滾動事件的解決方案始終存在與將滾動事件用於其餘目的類似的問題。觀察者的一般解決方案是用一個定位元素,僅做爲觀察者的目標元素使用。我喜歡避免使用諸如此類的單一目的的元素,所以我決定修改這個特定的想法。

在此demo中,上下滾動以查看章節標題對各自章節的粘性反應。

demo3

這個示例檢測粘性元素什麼時候位於滾動容器頂部,而後給其添加一個css類。 這是經過在給觀察者特定的rootMargin時利用DOM的一個有趣的特性來實現的。 給出的值是:

rootMargin: '0px 0px -100% 0px'
複製代碼

這樣會將根邊界的底部邊緣推到根元素的頂部,從而留下一小部分可用於相交檢測的零像素區域。 能夠說,即便目標元素碰觸到零像素區域,也會觸發相交變化,即便它不存在於數字中也是如此。 考慮一下,咱們能夠在DOM中具備摺疊高度爲零的元素。

該解決方案經過識別粘性元素始終位於根元素頂部的「粘性」位置來利用這一優點。 隨着滾動的繼續,粘性元素最終移出視野,而且相交中止。 所以,咱們根據輸入對象的isIntersecting屬性添加和刪除類。

下面是HTML:

<section>
  <div class="sticky-container">
    <div class="sticky-content">
      <span>&sect;</span>
      <h2>Section 1</h2>
    </div>
  </div>

  {{ content here }}

</section>

複製代碼

class爲sticky-container的外部div是觀察者的目標。 該div將被設置爲粘性元素並充當容器。 用於根據粘性狀態設置樣式和更改元素的元素是class爲sticky-content的div及其子元素。 這樣能夠確保實際的粘性元素始終與根元素頂部縮小的rootMargin接觸。

下面是CSS:

.sticky-content {
  position: relative;
  transition: 0.25s;
}

.sticky-content span {
  display: inline-block;
  font-size: 20px;
  opacity: 0;
  overflow: hidden;
  transition: 0.25s;
  width: 0;
}

.sticky-content h2 {
  display: inline-block;
}

.sticky-container {
  position: sticky;
  top: 0;
}

.sticky-container.active .sticky-content {
  background-color: rgba(0, 0, 0, 0.8);
  color: #fff;
  padding: 10px;
}

.sticky-container.active .sticky-content span {
  opacity: 1;
  transition: 0.25s 0.5s;
  width: 20px;
}
複製代碼

你會看到.sticky-container在top爲0的位置建立了咱們的粘滯元素。 其他部分是.sticky-content中的常規狀態和.active .sticky-content中的粘滯狀態樣式的混合。 一樣,您幾乎能夠在粘性內容div中作任何您想作的事情。 在此demo中,當粘滯狀態處於活動狀態時,在延遲的過渡中會出現一個隱藏的章節符號。沒有Intersection Observer之類的輔助手段,很難達到這種效果。

JavaScript:

const stickyContainers = document.querySelectorAll('.sticky-container');
const io_options = {
  root: document.body,
  rootMargin: '0px 0px -100% 0px',
  threshold: 0
};
const io_observer = new IntersectionObserver(io_callback, io_options);

stickyContainers.forEach(element => {
  io_observer.observe(element);
});

function io_callback (entries, observer) {
  entries.forEach(entry => {
    entry.target.classList.toggle('active', entry.isIntersecting);
  });
}
複製代碼

這其實是使用Intersection Observer完成此任務的很是簡單的示例。 惟一的例外是rootMargin中的-100%值。 請注意,這對於其餘三個方面也能夠重複; 它只須要一個具備本身獨特的rootMargin的新觀察者,對於相應方面,它具備-100%的值。 將會有更多獨特的粘性容器,它們具備本身的類,例如sticky-container-topsticky-container-bottom

這樣作的限制是,粘性元素的top,right,bottom或left屬性必須始終爲零。 從技術上講,你可使用其餘值,但隨後必須進行數學運算以找出rootMargin的正確值。 這很容易作到,可是若是調整大小,不只須要再次進行數學運算,還必須中止觀察者並使用新值從新啓動它。 將position屬性設置爲零,並使用內部元素以所需的方式設置樣式更加容易。

和滾動事件結合

到目前爲止,咱們已經在一些演示中看到了,intersectionRatio可能不精確且有些侷限。使用滾動事件能夠更精確,但會下降性能的效率。那把二者結合起來怎麼樣?

demo4

在此demo中,咱們建立了一個Intersection Observer,而且回調函數的惟一目的是添加和刪除偵聽根元素上的scroll事件的事件偵聽器。 當目標首次進入根元素時,將建立滾動事件偵聽器,而後在目標離開根元素時將其刪除。 滾動時,輸出僅顯示每一個事件的時間戳,以實時顯示事件的變化-比單獨的觀察者要精確得多。

下面是JavaScript。

const root = document.querySelector('#root');
const target = document.querySelector('#target');
const output = document.querySelector('#output pre');
const io_options = {
  root: root,
  rootMargin: '0px',
  threshold: 0
};
let io_observer;

function scrollingEvents (e) {
  output.innerText = e.timeStamp;
}

function io_callback (entries) {
  if (entries[0].isIntersecting) {
    root.addEventListener('scroll', scrollingEvents);
  } else {
    root.removeEventListener('scroll', scrollingEvents);
    output.innerText = 0;
  }
}

io_observer = new IntersectionObserver(io_callback, io_options);
io_observer.observe(target);
複製代碼

這是一個至關標準的例子。 請注意,咱們但願閾值爲零,由於若是閾值不止一個,咱們將同時得到多個事件監聽器。 回調函數是咱們感興趣的,甚至是一個簡單的設置:在if-else塊中添加和刪除事件監聽器。 事件的回調函數僅更新輸出中的div。 每當目標觸發相交變化而且不與根相交時,咱們會將輸出設置回零。

這個實例利用了Intersection Observer和滾動事件的優勢。 考慮使用一個滾動動畫庫,該動畫庫僅在頁面上須要它的部分實際可見時才起做用。 庫和滾動事件在整個頁面中並不是無效地活動。

瀏覽器的有趣差別

您可能想知道Intersection Observer有多少瀏覽器支持。 實際上,還蠻多的!

該瀏覽器支持數據來自Caniuse,更多信息。 數字表示瀏覽器支持該版本及更高版本的功能。

Caniuse

全部主要的瀏覽器都已經支持了一段時間。和預期同樣,IE在任何級別都不支持它,可是W3C提供了一個polyfill來解決這個問題。

當我使用Intersection Observer嘗試不一樣的想法時,我確實遇到了兩個示例在Firefox和Chrome之間的行爲有所不一樣。我不會在生產站點上使用這些示例,可是這些行爲頗有趣。

這是第一個示例:

example1

目標元素經過CSS transform屬性在根元素內移動。 該演示具備CSS動畫,該動畫可在水平軸上將目標元素移入和移出根元素。 當目標元素進入或離開根元素時,intersectionRatio會更新。

若是您在Firefox中查看此演示,則應在目標元素先後滑動時正確地看到intersectionRatio更新。 Chrome的行爲有所不一樣,徹底不更新intersectionRatio。 Chrome彷佛沒有保留使用CSS轉換過的目標元素的標籤。 可是,若是咱們在目標元素移入和移出根元素時在瀏覽器中四處移動鼠標,則intersectionRatio確實會更新。 個人猜想是,只有在存在某種形式的用戶交互時,Chrome纔會「激活」觀察者。

這是第二個示例:

example2

此次,咱們對一個剪裁路徑進行動畫處理,該剪裁路徑將一個正方形變成重複循環中的一個圓形。正方形與根元素的大小相同,所以咱們獲得的intersectionRatio將始終小於1。隨着剪裁路徑的動畫化,Firefox根本不會更新intersectionRatio。此次移動鼠標不起做用。Firefox只是忽略元素大小的變化。另外一方面,Chrome實際上會實時更新intersectionRatio顯示。即便沒有用戶交互,也會發生這種狀況。

之因此會發生這種狀況,是由於規範的一部分指出交集區域(intersectionRect)的邊界應包括剪裁目標元素。

若是容器具備溢出剪裁或css剪裁路徑屬性,請經過應用容器的剪裁來更新intersectionRect。

所以,當剪裁目標時,將從新計算相交區域的邊界。 Firefox顯然還沒有實現。

Intersection Observer, version 2

那麼,該API的將來前景如何?

Google提供了一些建議,這些建議會爲觀察者添加一個有趣的功能。 即便Intersection Observer告訴咱們目標元素什麼時候跨越根元素的邊界,也不必定意味着該元素實際上對用戶是可見的。 它可能具備零不透明度,或者可能被頁面上的另外一個元素覆蓋。 觀察者能不能被用來肯定這些事情?

請記住,咱們仍在早期階段才使用此功能,所以不該在生產代碼中使用它。 這是更新後的提案,其中突出顯示了與規範第一個版本的差別。

若是您一直在使用Chrome瀏覽本文中的演示,則可能已經注意到控制檯中的幾件事-例如Firefox中未出現的entries對象屬性。 這是Firefox在控制檯中打印內容的示例:

IntersectionObserver
  root: null
  rootMargin: "0px 0px 0px 0px"
  thresholds: Array [ 0 ]
  <prototype>: IntersectionObserverPrototype { }

IntersectionObserverEntry
  boundingClientRect: DOMRect { x: 9, y: 779, width: 707, ... }
  intersectionRatio: 0
  intersectionRect: DOMRect { x: 0, y: 0, width: 0, ... }
  isIntersecting: false
  rootBounds: null
  target: <div class="item">
  time: 261
  <prototype>: IntersectionObserverEntryPrototype { }

複製代碼

如今,這是來自Chrome中相同控制檯代碼的輸出:

IntersectionObserver
delay: 500
root: null
rootMargin: "0px 0px 0px 0px"
thresholds: [0]
trackVisibility: true
__proto__: IntersectionObserver

IntersectionObserverEntry
boundingClientRect: DOMRectReadOnly {x: 9, y: 740, width: 914, height: 146, top: 740, ...}
intersectionRatio: 0
intersectionRect: DOMRectReadOnly {x: 0, y: 0, width: 0, height: 0, top: 0, ...}
isIntersecting: false
isVisible: false
rootBounds: null
target: div.item
time: 355.6550000066636
__proto__: IntersectionObserverEntry

複製代碼

在一些屬性(例如targetprototype)的顯示方式上存在一些差別,可是它們在兩種瀏覽器中的操做相同。區別在於Chrome具備Firefox中不會顯示的一些其餘屬性。observer對象具備一個稱爲trackVisibility的布爾值,一個稱爲delay的數字,而且entry對象具備一個稱爲isVisible的布爾值。這些是新提議的屬性,這些屬性試圖肯定目標元素是否實際上對用戶可見。

我將對這些屬性進行簡要說明,但若是您須要更多詳細信息,請閱讀此文章

trackVisibility屬性是在options對象中提供給觀察者的布爾值。此屬性可使瀏覽器承擔肯定目標元素的真實可見性的任務。

delay屬性用途的猜想:它將交集改變的回調方法延遲指定的時間(以毫秒爲單位)。這有點相似於將回調函數的代碼包裝在setTimeout中。爲了使trackVisibility起做用,該值是必需的,而且必須至少爲100。若是未提供適當的值,則控制檯將顯示此錯誤,而且將不會建立觀察者。

Uncaught DOMException: Failed to construct 'IntersectionObserver': To enable the
'trackVisibility' option, you must also use a 'delay' option with a value of at
least 100. Visibility is more expensive to compute than the basic intersection;
enabling this option may negatively affect your page's performance. Please make sure you really need visibility tracking before enabling the 'trackVisibility' option. 複製代碼

目標entry對象中的isVisible屬性是報告可見性跟蹤輸出的布爾值。能夠將它用做任何代碼的一部分,就像使用isIntersecting同樣。

在我使用這些功能進行的全部實驗中,看到它實際上有時候有效有時候無效。 例如,當元素清晰可見時,延遲始終有效,可是isVisible並不老是報告true(至少對我而言)。 有時這是設計使然,由於規範確實容許出現第二類錯誤。這將有助於解釋不一致的結果。

我我的火燒眉毛地但願這項功能儘快完成,並在全部支持Intersection Observer的瀏覽器中都能正常工做。

寫在最後

我對Intersection Observer的研究到此結束。 我花了不少晚上研究,試驗和構建示例,以瞭解其工做原理。 這篇文章涉及了一些有關如何利用觀察者的不一樣功能的新想法。除此以外,我以爲我能夠清晰的解釋觀察者的工做原理。但願本文對你有所幫助。


若是你以爲這篇內容對你有價值,請點贊,並關注咱們的官網和咱們的微信公衆號(WecTeam),每週都有優質文章推送:

WecTeam
相關文章
相關標籤/搜索