IntersectionObserver:下篇 – 實際應用 lazyload、進場效果、無限捲動

IntersectionObserver:下篇 - 實際應用 lazyload、進場效果、無限捲動
IntersectionObserver:下篇 - 實際應用 lazyload、進場效果、無限捲動

使用資源

接續上一篇的 IntersectionObserver基本使用(以下簡稱IO),本篇來寫三個實際應用。

本篇用到的資源如下:

UI framewrok:Bootstrap4

css animation:animate.css

無限捲動的資料:JSONPlaceholder


lazy load 延遲載入(圖片、影片)

IO 最基本的應用就是延遲載入 lazy load 了,如果 Google 一下大部份也都是拿 lazy load 來當示範。

lazy load 的概念是這樣:

  1. img 的 src 不填,改填 data-src="圖片路徑"
  2. 當 img 的區塊出現在視窗上後,把 data-src 的值寫入到 src

以前對於「當img的區塊出現在視窗上」,大概就是用 onscroll 來監聽,現在有了 IO,就可以放下 onscroll 讓它成佛了。

因為概念就是 XX 區塊出現在視窗時,就替換 src,所以除了 img,也可以用成 Youtube iframe。

HTML

HTML 很簡單:

<div class="col-12 mb-5">
<!--
.lazy 是觀察器觀察的目標
data-src 圖片或影片的路徑
-->
<img class="lazy img-fluid img-thumbnail" data-src="dist/demo1.jpg"/>
</div>
<div class="col-12 mb-5">
<div class="embed-responsive embed-responsive-16by9">
<iframe class="lazy" data-src="https://www.youtube.com/embed/HK7SPnGSxLM">
</div>
</div>

JavaScript

JS 的部份…好吧,如果 上一篇 有看,也真的很簡單 XD~ 如下:

// lazy load IO callback
function callback_lazyload(entries) {
Array.prototype.forEach.call(entries, entry => {
if(entry.isIntersecting) {
// 將 data-src 的值寫進 src 中
entry.target.src = entry.target.dataset.src;
// 停止觀察
observerLazy.unobserve(entry.target);
}
})
}
// lazy load IO option
let option_lazyload = {
root: null,
rootMargin: '0px',
threshold: [0]
};
// lazy load create IO
let observerLazy = new IntersectionObserver(callback_lazyload, option_lazyload);
// lazyload observe all img.lazy
const lazyImg = document.querySelectorAll('.lazy');
Array.prototype.forEach.call(lazyImg, lazy => observerLazy.observe(lazy));

一個神奇的狀況(坑?)

話說在寫範例的時候,遇到了一個狀況,就是 IO 的 rootMargin 這個參數。

rootMargin,MDN 上說:

計算交叉時添加到根(root)邊界盒bounding box的矩形偏移量,可以有效的縮小或擴大根的判定範圍從而滿足計算需要。

Intersection Observer

是不是看不懂?對,August 也看不懂,原本的理解是,rootMargin 是一個可以放大、縮小觀察器的鏡頭大小的參數,比方我們的螢幕是 13 寸,但我想把觀察的範圍拉大到 15 寸,我們就在 rootMargin 上加大。

想像很美好,現實很殘酷,實際要做的時候,原本是想把鏡頭往上移螢幕大小的一半,讓 img 的部份出現在螢幕的一半時,才執行 callback。

結果,August 發現即便改便了 rootMargin,但卻一樣是 img 一出現在螢幕上,就執行 callback 了。

Google 了一下,發現也有人有遇到這問題,但還沒有人提供解答:

IntersectionObserver rootMargin’s positive and negative values are not working

這個發現 August 更新到上篇中了,懇請知道原因的大大可以在留言區中留言。

lazy load 的完整原始碼跟 Demo 會附在最後一段。


進場效果

進場效果的思考方式其實跟 lazy load 很像,不一樣的是,lazy load 是 img 出現在視窗時就替換 src。而進場效果則是區塊進入到視窗時,就把 class name 加進去。

是不是會了 IntersectionObserver,就等於會了很多功能?

HTML

<!--
.op-0 透明度改0,準備用animated效果
.js-animated 觀察器觀察的目標
data-animated 準備要加的class name,效果參考:https://daneden.github.io/animate.css/
-->
<div class="col-12 mb-5 animated op-0 js-animated" data-animated="bounceInLeft">
<img class="img-fluid img-thumbnail" src="https://picsum.photos/id/237/335/335"/>
</div>
<div class="col-12 mb-5 animated op-0 js-animated" data-animated="fadeInRight">
<img class="img-fluid img-thumbnail" src="https://picsum.photos/id/197/335/335"/>
</div>

本篇效果直接用 animate.css 的,就不另外手刻,因此只需要把 class name 寫在 data-animated,然後用 JS 加進去。

JavaScript

// animated IO callback
function callback_animated(entries) {
Array.prototype.forEach.call(entries, entry => {
if(entry.isIntersecting) {
// class 移除 .op-0,加入 data-animated 的值
entry.target.classList.remove('op-0');
entry.target.classList.add(entry.target.dataset.animated);
// 取消觀察
observerAnimated.unobserve(entry.target);
}
})
}
// animated IO option
let option_animated = {
root: null,
rootMargin: '0px',
threshold: [1] // img區塊進到視窗後才執行 animated
};
// animated create IO
let observerAnimated = new IntersectionObserver(callback_animated, option_animated);
// animated observe all .js-animated
const animatedIn = document.querySelectorAll('.js-animated');
Array.prototype.forEach.call(animatedIn, animated => observerAnimated.observe(animated));

JS 的部份跟 lazy load 很像,基本上就是 ctrl C、ctrl V 後再改個變數名(咦?)

進場效果的完整原始碼跟 Demo 會附在最後一段。


無限捲動

先聲明,August 是很不接受無限捲動這個方式的,除非你的網頁架構跟 FB、IG、Twitter 等社群一樣,架構扁平,沒有一般網站的首頁 > 列表 > 單文 這樣的層次,而且每張卡片的 HTML 都不長。

之前看過一篇,是在說明並非每個站都適合無限滾動的:

Infinite Scrolling Is Not for Every Website

但不知道為什麼,最近身邊人一窩蜂的都想要這功能,是只打算把自己的網站當作 FB 那樣消遣用的就是了。

本篇的無限滾動,是像 FB 那樣子,頁面每捲到底,就會多載入一張卡片進來。

HTML

因為實際上的卡片都是由 JS 塞進去的,所以 HTML 很單純:

<!-- #js-infinite-wrap:js 要 insert 的地方-->
<div class="col-12 mb-5" id="js-infinite-wrap"></div>
<!-- #js-detective 載入下一篇的觀察目標-->
<div id="js-detective"></div>

分成 2 個 div,一個是要給塞卡片的,一個是觸發 IO callback 的。

JavaScript

const infiniteWrap = document.getElementById('js-infinite-wrap');
let count = 1;
function callback_infinite(entries) {
Array.prototype.forEach.call(entries, entry => {
if(entry.isIntersecting) {
fetch('https://jsonplaceholder.typicode.com/posts/' + count)
.then(res => res.json())
.then(res => {
// 取消觀察,以免又觸發下一個 request
observerInfinite.unobserve(infinite);
// append html
let item = `
<div class="card animated flipInY text-left">
<img src="https://picsum.photos/id/1${ res.id }/335/335"/>
<div class="card-body">
<div class="card-title font-weight-bold">${ res.title }</div>
<div class="card-text">${ res.body }</div>
</div>
</div>`;
infiniteWrap.insertAdjacentHTML('beforeend', item);
count++;
})
.then(() => {
// 載入到 10 個,就關閉觀察器
if(count <= 10) {
observerInfinite.observe(infinite);
} else {
const end = `<div class="alert alert-warning mt-5 animated fadeInUp" role="alert">無限捲動到10張,結束。</div>`;
infiniteWrap.insertAdjacentHTML('beforeend', end);
observerInfinite.disconnect(); // 關閉觀察器
}
})
}
})
}
// infinite IO option
let option_infinite = {
root: null,
rootMargin: '0px',
threshold: [0]
};
// infinite create IO
let observerInfinite = new IntersectionObserver(callback_infinite, option_infinite);
// animated observe #js-infinite
const infinite = document.getElementById('js-detective');
observerInfinite.observe(infinite);

JS 的部份,August 這邊寫最多載入 10 篇,10 篇一到就關閉 IO。

另外很有趣的是,因為 insert 新的 HTML,是 insert 在 IO 觀察的那個 div 之上,所以即便是新增了 code 進去,IO.observe 也不必再重呼一次,因為目標就一直待在頁尾的部份。


原始碼、Demo

本篇的 3 個實作項目的 Demo 在這,全部都寫在同一頁:

https://letswritetw.github.io/letswrite-intersection-observer/

原始碼的部份整理在 GitHub:

https://github.com/letswritetw/letswrite-intersection-observer

Summary
IntersectionObserver:下篇 - 實際應用 lazyload、進場效果、無限捲動
Article Name
IntersectionObserver:下篇 - 實際應用 lazyload、進場效果、無限捲動
Description
本篇大綱:使用資源。lazy load 延遲載入(圖片、影片)、一個神奇的狀況(坑?)。進場效果。無限捲動。原始碼、Demo。接續上一篇的IntersectionObserver基本使用(以下簡稱IO),本篇來寫三個實際應用。
August
Let's Write
Let's Write
https://letswritetw.github.io/letswritetw/dist/img/logo_512.png
訂閱
通知
guest

2 Comments
最舊
最新
Inline Feedbacks
看所有留言
leo
leo
5 年 之前

最近剛好也在看lazy loading
發現IntersectionObserver rootMargin’s positive and negative values are not working這篇有人回覆了

\r\n <\/div>\r\n<\/div>\r\n","isUserRated":"0","version":"7.6.33","wc_post_id":"693","isCookiesEnabled":"1","loadLastCommentId":"0","dataFilterCallbacks":[],"phraseFilters":[],"scrollSize":"32","url":"https:\/\/www.letswrite.tw\/wp-admin\/admin-ajax.php","customAjaxUrl":"https:\/\/www.letswrite.tw\/wp-content\/plugins\/wpdiscuz\/utils\/ajax\/wpdiscuz-ajax.php","bubbleUpdateUrl":"https:\/\/www.letswrite.tw\/wp-json\/wpdiscuz\/v1\/update","restNonce":"b0101d2173","is_rate_editable":"0","menu_icon":"https:\/\/www.letswrite.tw\/wp-content\/plugins\/wpdiscuz\/assets\/img\/plugin-icon\/wpdiscuz-svg.svg","menu_icon_hover":"https:\/\/www.letswrite.tw\/wp-content\/plugins\/wpdiscuz\/assets\/img\/plugin-icon\/wpdiscuz-svg_hover.svg","is_email_field_required":"1"}; var wpdiscuzUCObj = {"msgConfirmDeleteComment":"\u60a8\u78ba\u5b9a\u8981\u522a\u9664\u6b64\u7559\u8a00\uff1f","msgConfirmCancelSubscription":"\u60a8\u78ba\u5b9a\u8981\u53d6\u6d88\u8a02\u95b1\uff1f","msgConfirmCancelFollow":"\u60a8\u78ba\u5b9a\u8981\u53d6\u6d88\u95dc\u6ce8\uff1f","additionalTab":"0"}; /* ]]> */ -->