# Intersection Observer [程式碼由該部影片產出](https://www.youtube.com/watch?v=2IbRtjez6ag&list=LL&index=1&ab_channel=WebDevSimplified) 這次要來介紹 IntersectionObserver API,有了該API可以很簡單的做到Infinite Scroll和Lazy Loading,在過去要偵測到元素是否已經進入使用者的畫面範圍需要花費許多功夫,而透過IntersectionObserver,我們可以確切的掌握元素是否已經進入或離開到使用者的畫面範圍, 效果如下圖。 ![](https://i.imgur.com/b3hjuCH.gif) ## 初始程式碼 先附上HTML與CSS ***index.html*** ```htmlembedded= <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Intersection Observer</title> <link rel="stylesheet" href="style.css" /> </head> <body> <div class="card-container"> <div class="card">First card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Card</div> <div class="card">Last card</div> </div> <script src="app.js"></script> </body> </html> ``` ***style.css*** ```css= .card-container { display: flex; flex-direction: column; gap: 1rem; align-items: flex-start; } .card { background: #fff; border: 1px solid #000; border-radius: 0.25rem; padding: 0.5rem; transform: translateX(100px); opacity: 0; transition: 150ms; } .card.show { transform: translateX(0); opacity: 1; } ``` ## 實作Lazy Loading 首先我們需要抓取到所有class為card的元素,接著我們實作IntersectionObserver API, IntersectionObserver內會自帶一個entries參數,我們先將它console出來。 ***app.js*** ```javascript= const cards = document.querySelectorAll(".card"); const observer = new IntersectionObserver((entries) => { console.log(entries) }); ``` 當打開console的時候會發現沒有任何東西輸出在console,這是因為我們還需要告訴observer我們要「觀察」哪個元素,當它出現的時候才會觸發console,實作方式也非常簡單。 ```javascript= const cards = document.querySelectorAll(".card"); const observer = new IntersectionObserver((entries) => { console.log(entries) }); observer.observe(cards[0]); //觀察第一個class為card的元素 ``` 這時候打開console後就會看到結果了,而可以看到圖中有個**target**,這是我們需要的東西,可以順便試試看將滾輪上下滑動的時候會不會再次出現該console。 ![](https://i.imgur.com/50QALjQ.png) 現在我們就直接來實作Lazy Loading吧 在css內我們有定義一個 **.card.show** 的選擇器,當card class被套上show class的時候,就會出現在畫面中。 ==程式碼第5行==:當指定的元素被「觀察」到的時候,**entry.isIntersecting**會為**true**,透過該方式就可以直接將Lazy Loading實作。 ```javascript= const cards = document.querySelectorAll(".card"); const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { entry.target.classList.toggle("show", entry.isIntersecting); }); }); cards.forEach((card) => { observer.observe(card); }); ``` ![](https://i.imgur.com/b3hjuCH.gif) 但我們發現,當滑鼠滾回原本經過的地方會重新觸發一次**show**的動畫, 所以可以將程式碼改成以下 ```javascript= const cards = document.querySelectorAll(".card"); const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { entry.target.classList.toggle("show", entry.isIntersecting); if (entry.isIntersecting) { observer.unobserve(entry.target); } }); }); cards.forEach((card) => { observer.observe(card); }); ``` ## preloading (rootMargin) 假設我們想要讓元素提早被「觀察」到,可以使用IntersectionObserver提供的rootMargin,可以根據自己需要的範圍調整rootMargin的值。 ```javascript= const cards = document.querySelectorAll(".card"); const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { entry.target.classList.toggle("show", entry.isIntersecting); if (entry.isIntersecting) { observer.unobserve(entry.target); } }); }, { rootMargin: "100px", } ); cards.forEach((card) => { observer.observe(card); }); ``` ## infinite scroll ```javascript= const cardContainer = document.querySelector(".card-container"); const cards = document.querySelectorAll(".card"); const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { entry.target.classList.toggle("show", entry.isIntersecting); if (entry.isIntersecting) { observer.unobserve(entry.target); } }); }, { rootMargin: "100px", } ); const lastCardObserver = new IntersectionObserver((entries) => { const lastCard = entries[0]; if (!lastCard.isIntersecting) return; loadNewCard(); lastCardObserver.unobserve(lastCard.target); lastCardObserver.observe(document.querySelector(".card:last-child")); }); lastCardObserver.observe(document.querySelector(".card:last-child")); cards.forEach((card) => { observer.observe(card); }); const loadNewCard = () => { for (let i = 0; i < 10; i++) { const card = document.createElement("div"); card.textContent = "card"; card.classList.add("card"); observer.observe(card); cardContainer.append(card); } }; ```