JS: Scroll events
筆記學到的三種可以處理scroll事件的技巧,分別是:
- window.scrollTo() + window.pageYOffset
- Element.scrollIntoView()
- Intersection observer API
前兩個技巧用來實作smooth scrolling;第3個則是要實作捲動到某個位置出現固定導覽列(sticky bar)的效果
Smooth Scrolling
1. window.scrollTo()
第一種是過去常實作的smooth scrolling方法,適合用在舊版瀏覽器:
const divElem = document.querySelector('#divElem');
const divCoords = divElem.getBoundingClientRect();
window.scrollTo(
divCoords.left + window.pageXOffset,
divCoords.top + window.pageYOffset
);
// Smooth scrolling
// window.scrollTo({
// left: divCoords.left + window.pageXOffset,
// top: divCoords.top + window.pageYOffset,
// behavior: 'smooth'
// });
如果瀏覽器有支援方法2或3,儘量不要使用這種方法,因為 window.pageYOffset
這行會在捲動頁面時一直觸發捲動事件,造成頁面效能變差。
2. Element.scrollIntoView()
第二個實作smooth scrolling方法是現在比較常用的方法,不會一直觸發捲動事件又容易實作,只是要注意瀏覽器是否支援,尤其Safari並不支援方法的optional參數:
const divElem = document.querySelector('#divElem');
divElem.scrollIntoView();
// Smooth scrolling
// divElem.scrollIntoView({behavior: 'smooth'});
Showing Sticky Navigation Bar
3. Intersection API
第三個scroll事件要用Web API裡的Intersection API來實作「當使用者捲動頁面,使得頁面viewport一離開header元素頂部時,就顯示固定導覽列」的效果:
const header = document.querySelector('.header');
const navBar = document.querySelector('#navBar'); // target element
const navBarHeight = Number.parseInt(getComputedStyle(navBar).height) // 取得導覽列高度
// Intersection API setting
const callback = function(entries){
const [entry] = entries;
if(!entry.isIntersecting) {
navbar.styles.position = "fixed"
} else {
navbar.styles.position = "static"
}
}
const options = {
root: null // Intersected element (null represents monitoring the changes that target element intersecting to device's viewport)
, threshold: 0.9 // Intersection ratio, the degree of intersection between the target element and its root (between 0.0 and 1.0).
, rootMargin: '0px' // Margin around the root (0 is default value)
}
const headerElemObserver = new IntersectionObserver(callback, options);
headerElemObserver.observe(header);
thread: 0.9
是指 viewport 和 header元素(高度)的交集比例少於90%就會顯示固定導覽列;這裡我沒有設定成 thread: 1
,因為 1
代表只要 viewport 和 header 一沒有交集 就會出現固定導覽列,但一和整個header有交集就會讓導覽列恢復原狀,某些情況下容易導致衝突發生,因此設定成 thread: 0.9
。
(註:若要驗證設定成1會出現什麼狀況,可以慢慢改變視窗大小,會發現在某個視窗大小會反覆出現intersection衝突)
這裡要注意的是,headerElemObserver.observe(header)
裡不能給nav
,因為導覽列轉換成固定導覽列之後其實還是會和header有交集,如果這樣設定會導致衝突,因此這裡是給 header
。
個人覺得這是蠻酷的方法😂,根據MDN的說明,Intersection API是用asynchronous方式去追蹤變化:
The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
如此一來,比起第1個這種過去在main thread實作的常用方法,Intersection API顯得有效率多了~
另外,Intersection API不一定只能用在範例的捲動事件,有些常見的網頁效果也可以這個API去實作,像是:
- Lazy-loading of images or other content
- Infinite scrolling
- Reporting of visibility of advertisements
- Decisions for performing tasks or animation processes
References The Complete JavaScript Course 2022: From Zero to Expert! Window.scrollTo() Element.scrollIntoView() Intersection Observer API Building A Dynamic Header With Intersection Observer Sticky Header using Intersection Observer API