###### 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` 必須要是所有目標元素的父元素(或祖父層的元素) ![](https://i.imgur.com/XJacX8s.png) - `rootMargin`:設定 root 周圍的 margin — 能有效的**擴大或縮小這個用來觀察的盒子範圍**。設定的值就類似設定一般 margin:"30px 30px 30px 30px"(上右下左),也能縮寫成一個值:30px ![](https://i.imgur.com/uc8h2nl.png) - `threshold`:設定目標元素的可見度達到多少比例時,觸發 callback 函式。可以帶入單一一個值:**只想在可見度達一個比例時觸發**;也可帶入一個陣列:**想在可見度達多個比例時觸發** - 預設值為 0:一但目標進入或目標的最後一個 px 離開觀察範圍時就觸發 - 設定為 0.5 :一但可見度為 50% 時就觸發 - 設定為 [0, 0.25, 0.5, 0.75, 1]:可見度每跳 25% 時就觸發 - 設定為 1:可見度達 100% 或一但往下掉低於 100% 時就觸發 ![](https://i.imgur.com/C68dtXk.png) - `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`: 目標元素和盒子重疊的區塊 ![](https://i.imgur.com/yOg66o8.png) - `intersectionRatio`:目標元素有多少比例和用來觀察的盒子重疊(白話的來說:目標元素的可見度為多少)。計算方式:*intersectionRect / boundingClientRect* :::info :warning: threshold 與 intersectionRatio 兩者差異 ? - threshold 當中所設定的單一或多個值就是在設定一個個 intersectionRatio 值作為門檻,一但跨越這個門檻(或這些門檻),就觸發 callback 函式 - options 所設定的 threshold 是想訂定「何時觸發」;intersectionRatio 則是當目標元素進出(intersect)變化當下的即時資訊 ::: ![](https://i.imgur.com/MulbGt7.png) - `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)