###### tags: `Performance Patterns`
# Import on Visibility
## 前言
- 常常有些components在初始時看不到,像是lazy loading images(圖片延遲載入)、無限滾動(infinite scrolling), 他在網頁初始的一開始看不到, 只有在使用者滑到網頁底部的時候才會get loaded. [vedio](https://www.patterns.dev/posts/import-on-visibility/)
- 我們不需要在網頁初始時 request 所有圖片, 這樣可以減少一開始的 loading time, 我們也可以對 components 做同樣的事
### 可使用 js API or libraries
#### `IntersectionObserver`
- `callback` 函式:當目標元素進入或離開指定外層或預設 viewport 時觸發
- `options`:設定在哪些情況下觸發`callback` 函式
```javascript=
// entries 能拿到所有目標元素進出(intersect)變化的資訊
let callback = (entries, observer) => {
// 取得每一個 entry 都是描述被觀察物件有 intersection change 的情況, 做一些處理或工作
entries.forEach(entry => {
entry.target
entry.isIntersecting
entry.intersectionRatio // 被觀察者(target)和觀察者(viewport)的重疊比例
// entry.boundingClientRect
// entry.intersectionRect
// entry.rootBounds
// entry.time
});
};
let options = {
root: document.querySelector('#scrollArea'), // 預設 document viewport
rootMargin: '0px', // 預設 0, 也可以設定 '10px 20px 30px 40px'
threshold: 1.0 // 預設 0, 也可以使用陣列 [0, 0.25, 0.5, 0.75, 1]
}
// 製作鈴鐺:建立一個 intersection observer,帶入相關設定資訊
let observer = new IntersectionObserver(callback, options);
// 設定觀察對象:告訴 observer 要觀察哪個目標元素
observer.observe(TARGET_ELEMENT)
}
```
- `root` 必須要是所有目標元素的父元素(或祖父層的元素)

- `rootMargin`:設定 root 周圍的 margin — 能有效的**擴大或縮小這個用來觀察的盒子範圍**。設定的值就類似設定一般 margin:"30px 30px 30px 30px"(上右下左),也能縮寫成一個值:30px

- `threshold`:設定目標元素的可見度達到多少比例時,觸發 callback 函式。可以帶入單一一個值:**只想在可見度達一個比例時觸發**;也可帶入一個陣列:**想在可見度達多個比例時觸發**
- 預設值為 0:一但目標進入或目標的最後一個 px 離開觀察範圍時就觸發
- 設定為 0.5 :一但可見度為 50% 時就觸發
- 設定為 [0, 0.25, 0.5, 0.75, 1]:可見度每跳 25% 時就觸發
- 設定為 1:可見度達 100% 或一但往下掉低於 100% 時就觸發

- `entry.target`: 取得是哪個目標元素進入或離開了 viewport
- `isIntersecting`:用來判別目標元素是否進入或離開了 viewport
- 值由 false 轉為 true:目標元素進入 viewport
- 值由 true 轉為 false:目標元素離開 viewport
```javascript=
let callback = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 只在目標元素進入 viewport 時執行這裡的工作
} else {
// 只在目標元素離開 viewport 時執行這裡的工作
}
})
}
```
- `rootBounds`: 用來觀察的盒子 (root + rootMargin)
- `boundingClientRect`: 目標元素
- `intersectionRect`: 目標元素和盒子重疊的區塊

- `intersectionRatio`:目標元素有多少比例和用來觀察的盒子重疊(白話的來說:目標元素的可見度為多少)。計算方式:*intersectionRect / boundingClientRect*
:::info
:warning: threshold 與 intersectionRatio 兩者差異 ?
- threshold 當中所設定的單一或多個值就是在設定一個個 intersectionRatio 值作為門檻,一但跨越這個門檻(或這些門檻),就觸發 callback 函式
- options 所設定的 threshold 是想訂定「何時觸發」;intersectionRatio 則是當目標元素進出(intersect)變化當下的即時資訊
:::

- `observer.unobserve(entry.target)`: 只想一次性偵測目標對象的進入或離開
```javascript=
let callback = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 目標元素進入 viewport 時做一些事情
...
// 完成後,結束觀察
observer.unobserve(entry.target)
}
})
}
```
[demo](https://codepen.io/ecolip/pen/xxYRQMz?editors=1010)
:warning: Jun 1, 2022 除了IE不支援, chrome, safari, firefox都支援
[Can I use Search?](https://caniuse.com/?search=Intersection%20Observer)
### React libraries
- **`react-lazyload`** [demo](https://codesandbox.io/s/lazy-loading-images-iu3fsr?file=/src/LazyImage.js)
- **`react-loadable-visibility`**, 當使用者點擊時import component [原始demo](https://codesandbox.io/embed/onvisibility-4ew4f)
### 優點
- 優化 performance, 減少一開始的loading time, 在需要 images 或 component 再載入與使用者多一些互動, 甚至讓使用者知道程式並未凍結像是點擊後 Emoji table 再出現, 當使用者點擊 icon 時需要等一下 (short time) 程式會 loaded, parsed, compiled, and executed module ! [vedio](https://www.patterns.dev/posts/import-on-visibility/)
### Reference
[Intersection Observer API MDN](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)
[Intersection Observer API:Lazy Loading, Infinite Scroll](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/%E8%AA%8D%E8%AD%98-intersection-observer-api-%E5%AF%A6%E4%BD%9C-lazy-loading-%E5%92%8C-infinite-scroll-c8d434ad218c)
[dynamic import HackMD](https://hackmd.io/@Emmacheng/HylTHqLEc)
[modules-dynamic-imports article](https://javascript.info/modules-dynamic-imports)