利用"交叉觀察者"這個小寶貝兒,輕鬆實現懶加載、吸頂、觸底 ❗

能夠先看一下MDN中的介紹:css

IntersectionObserver接口,提供了一種異步觀察目標元素與其祖先元素或頂級文檔視窗(viewport)交叉狀態的方法,祖先元素與視窗(viewport)被稱爲根(root);html

直接進入正題,IntersectionObserver 翻譯爲 "交叉觀察者",它的任務就是監聽目標元素指定父元素(用戶可指定,默認爲viewport)是否在發生交叉行爲,簡單理解就是監聽目標元素是否進入或者離開了指定父元素的內部(理解這句就好了,管他交不交叉呢),我好像在開車,可是大家沒有證據 ... 😐前端

如下的目標元素簡稱爲目標指定父元素簡稱爲父親交叉行爲簡稱爲交叉viewport簡稱爲視窗 👌git

下面會有動圖介紹,先忍忍!github

用法

1. 構造函數

new IntersectionObserver(callback, options);
複製代碼

2. callback

發生交叉的回調,接受一個entries參數,返回當前已監聽而且發生了交叉目標集合(後面會舉例說明爲何是"且發生了交叉"):瀏覽器

new IntersectionObserver(entries => {
  entries.forEach(item => console.log(item));
  // ...
});
複製代碼

咱們看看item裏面包含哪些經常使用屬性:bash

屬性 說明
boundingClientRect 空間信息
intersectionRatio 元素可見區域的佔比
isIntersecting 字面理解爲是否正在交叉,可用作判斷元素是否可見
target 目標節點,就跟event.target同樣

注意:頁面初始化的時候會觸發一次callbackentries全部已監聽的目標集合微信

3. options

顧名思義,它是一個配置參數,對象類型,非必填,經常使用屬性以下:異步

屬性 說明
root 指定父元素,默認爲視窗
rootMargin 觸發交叉的偏移值,默認爲"0px 0px 0px 0px"(上左下右,正數爲向外擴散,負數則向內收縮)
new IntersectionObserver(callback, {
  root: document.querySelector("xx"),
  rootMargin: "0px 0px -100px 0px"
});
複製代碼

若是設置rootMargin爲"20px 0px 30px 30px",那麼元素未到達視窗時,就已經切換爲可見狀態了: 函數

4. 經常使用方法

名稱 說明 參數
observe 開始監聽一個目標元素 節點
unobserve 中止監聽一個目標元素 節點
takeRecords 返回全部監聽的目標元素集合
disconnect 中止全部監聽

例子

1. 假設頁面上有一個class="box"的盒子且父元素爲視窗

let box = document.querySelector(".box");

let observer = new IntersectionObserver(entries => {
  entries.forEach(item => {
    let tips = item.isIntersecting ? "進入了父元素的內部" : "離開了父元素的內部";
    console.log(tips);
  });
});

observer.observe(box); // 監聽一個box
複製代碼

效果以下:

2. 假設頁面上有多個class="box"的盒子且父元素爲視窗

let box = document.querySelectorAll(".box");

let observer = new IntersectionObserver(entries => console.log(`發生交叉行爲,目標元素有${entries.length}個`));

box.forEach(item => observer.observe(item)); // 監聽多個box
複製代碼

當全部盒子距離視窗頂部距離一致時,效果以下:

當全部盒子距離視窗頂部距離不一致時,效果以下:

爲何要舉例以上兩種狀況呢,由於entries是返回當前已監聽而且發生了交叉目標集合,第一種狀況,你們都一塊兒發生交叉,固每次返回的集合長度都爲;第二種狀況則是每一個目標輪流發生交叉,且當前只觸發了一個,因此每次返回的集合長度只有

3. 指定父元素

假設html以下:

<div class="parent">
  <div class="child"></div>
</div>
複製代碼

而後開始監聽:

let child = document.querySelector(".child");

let observer = new IntersectionObserver(entries => {
  entries.forEach(item => {
    console.log(item.isIntersecting ? "可見" : "不可見");
  });
}, {
  root: document.querySelector(".parent")
});

observer.observe(child); // 開始監聽child
複製代碼

效果以下:

實際應用

1. 圖片懶加載

之前都是監聽瀏覽器滾動,而後遍歷拿到每一個圖片的空間信息,而後判斷一些位置信息從而進行圖片加載;而如今只須要交給交叉觀察者去作;

假設html結構以下:

// 多個
<img src="" data-origin="圖片連接">
複製代碼

而後開始監聽:

let images = document.querySelectorAll("img.lazyload");

let observer = new IntersectionObserver(entries => {
  entries.forEach(item => {
    if (item.isIntersecting) {
      item.target.src = item.target.dataset.origin; // 開始加載圖片
      observer.unobserve(item.target); // 中止監聽已開始加載的圖片
    }
  });
});

images.forEach(item => observer.observe(item));
複製代碼

效果以下:

把網速調慢:

設置rootMargin偏移值爲"0px 0px -100px 0px"(底部向內收縮):

該方法還有一個好處,那就是當頁面上某個節點存在橫向滾動條的時候,同樣應對自如:

傳統的懶加載只是監聽全局滾動條的滾動,像這種小細節仍是沒法實現的(傳統的實現方法並非判斷目標是否出如今視窗,因此橫向的圖片會一塊兒加載,即便你沒有向左滑動),因此這也是交叉觀察者的一大優勢✅

2. 觸底

咱們在列表底部放一個參照元素,而後讓交叉觀察者去監聽;

假設html結構以下:

<!-- 數據列表 -->
<ul>
  <li>index</li>
</ul>

<!-- 參照元素 -->
<div class="reference"></div>
複製代碼

而後監聽參照元素:

new IntersectionObserver(entries => {
  let item = entries[0]; // 拿第一個就行,反正只有一個
  if (item.isIntersecting) console.log("滾動到了底部,開始請求數據");
}).observe(document.querySelector(".reference")); // 監聽參照元素
複製代碼

效果以下:

3. 吸頂

實現元素吸頂的方式有不少種,如css的position: sticky,兼容性較差;若是用交叉觀察者實現也很方便,一樣也要放一個參照元素

假設html結構以下:

<!-- 參照元素 -->
<div class="reference"></div>

<nav>我能夠吸頂</nav>
複製代碼

假設scss代碼以下:

nav {
  &.fixed {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
  }
}
複製代碼

開始監聽:

let nav = document.querySelector('nav');
let reference = document.querySelector(".reference");

new IntersectionObserver(entries => {

  let item = entries[0];
  let top = item.boundingClientRect.top;

  // 當參照元素的的top值小於0,也就是在視窗的頂部的時候,開始吸頂,不然移除吸頂
  if (top < 0) nav.classList.add("fixed");
  else nav.classList.remove("fixed");

}).observe(reference);
複製代碼

效果以下:

可是有個問題,當你滾動的慢的時候,會掉進一個死循環:

爲了方便觀察,咱們把參考元素加一個高度跟顏色:

問題很明顯,當給nav增長fixed定位時,nav脫離了文檔流,天然參考元素會往下掉,而後往下掉又發生了交叉,從而去除fixed定位,陷入一個死循環;

思考了一會,解決辦法是,讓參考元素絕對定位至nav的上方:

let nav = document.querySelector('nav');
let reference = document.querySelector(".reference");

reference.style.top = nav.offsetTop + "px";

// 如下代碼不變 ...
複製代碼

這樣,即便nav脫離的文檔流,也不會影響參考元素的位置:

4. 動畫展現

相信不少人都須要過這種需求,當某個元素出現的時候就給該元素加個動畫,好比漸變、偏移等;

假設html結構以下:

<ul>
  <li></li>
</ul>
複製代碼

假設scss代碼以下:

ul {
 li {
   &.show {
    // 默認從左邊進來
    animation: left 1s ease;
    
    // 偶數從右邊進來
    &:nth-child(2n) {
      animation: right 1s ease;
    }
   }
 }
}

@keyframes left {
  from {
    opacity: 0;
    transform: translate(-20px, 20px); // right動畫改爲20px, 20px便可
  }

  to {
    opacity: 1;
  }
}
複製代碼

而後開始監聽:

let list = document.querySelectorAll("ul li");

let observer = new IntersectionObserver(entries => {
  entries.forEach(item => {
    if (item.isIntersecting) {
      item.target.classList.add("show"); // 增長show類名
      observer.unobserve(item.target); // 移除監聽
    }
  });
});

list.forEach(item => observer.observe(item));
複製代碼

效果以下:

兼容性

IE不兼容,不過有官方的polyfill

最後

暫時就發現這麼多用途啦,值得注意的是,必須是子元素跟父元素髮生交叉,若是你想檢查兩個非父子關係的交叉,那是不行的嘻嘻,若是你以爲這篇文章不錯,請別忘記點個關注哦~😊

交流

公衆號「前端宇宙情報局」,將不定時更新最新、實用的前端技巧/技術性文章,對了偶爾還會有互聯網中的趣事趣聞🍻

關注公衆號,回覆"1"獲取微信羣聊二維碼,一塊兒學習、一塊兒交流、一塊兒摸魚🌊

相關文章
相關標籤/搜索