iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0
Modern Web

JS30 x 鐵人30 x MDN doc系列 第 13

[Day13] - Slide in on Scroll(JS30 x 鐵人 30 x MDN)

  • 分享至 

  • xImage
  •  

實作頁面滾動式視差設計

觀察 index-Start.html,裡面有一個作者幫我們準備好的Debounce函式,這是什麼呢?在解釋它之前要先來説説本日的重點,看題目名稱跟挖空的圖片應該就知道要透過滾輪滑動讓圖片滑進滑出,這就要用到Document: scroll event

舉例來說,如果我們針對視窗添加事件監聽器偵測滑鼠滾動事件,並且用console.count("scroll")印出觸發次數,那麼你會發現輕輕一滑,你就觸發了 4~50 次,所以假設你的觸發後要執行的函式邏輯非常複雜,那麼將影響效能,

function handleScroll(e) {
  console.count("scroll");
}
document.addEventListener("scroll", handleScroll);

這個時候我們就要借用到Debounce & Throttle,我們在真正要執行的函式外面在包裹一層限制器,去限制他觸發的頻率(雖然也是每次滾動會觸發多次這個限制器函式,但裡面只做了簡單的狀態判斷,只有符合設定時才會執行真正要做的主邏輯),詳細的介紹可以看這篇的例子Debounce & Throttle - Alex Ian

function debounce(func, wait = 20, immediate = true) {
  var timeout;
  //回傳包裹後的函式
  return function () {
    var context = this,
      args = arguments;
    var later = function () {
      timeout = null;
      if (!immediate) func.apply(context, args);
    };
    var callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) func.apply(context, args);
  };
}

於是我們宣告一個變數,並且將 handleScroll 包一層作者給的限制器

const debounceScroll = debounce(handleScroll);

接著我們將監聽器改帶入著個帶有限制器的 eventhandler 函式,你會發現只有在一開始滾動會觸發及停止滾動的最後才會再次觸發,那是因為判斷中有一句var callNow = immediate && !timeout;只有當停止滾動沒有 setTimout 後才會為true才能再次觸發,持續滾動則會一直重置這wait=20毫秒的 timeout 造成不會執行真正的邏輯,藉此能達到限制觸發的效果

document.addEventListener("scroll", debounceScroll);

前置作業終於完成,我們要開始處理照片的滑入滑出效果,這個經過前 10 天的各種節點操作事件監聽練習,相對上面的去抖動簡單許多了,很容易就能聯想到是要我們滑動到個別照片的特定位置時添加、移除 CSS 中的已經寫好的active class,實作開始

  1. 將所有圖片節點選取起來
const imgList = document.querySelectorAll("img");
  1. 改寫 handleScroll 函式裡面真正要執行的邏輯,我們先定義要掛上 class 的位置為圖片露出一半並且,滾動超過圖片底部時須移除 class。

    • slideInAt:計算各圖片該滑入位置。window.scrollY 表示視窗垂直方向上已經滾動的距離,window.innerHeight表示當前瀏覽器窗口的內部高度,img.height 則是圖片的高度。透過這三個數值算出當圖片的一半進入視窗時的位置。
    • imageBottom:計算圖片底部的位置。img.offsetTop表示圖片頂部相對於其父容器的上邊緣的距離,加上img.height(圖片的高度)就得到了圖片的底部位置。
    • isHalfShown:用來判斷是否有超過一半的圖片進入了視窗。如果slideInAt大於img.offsetTop,則表示圖片的一半已經進入了視窗,此時isHalfShown設置為true
    • isNotScrolledPast:用來判斷是否有圖片的一部分還沒有完全滾出視窗。如果window.scrollY(當前滾動位置)小於 imageBottom(圖片底部位置),則表示圖片還在視窗內,此時 isNotScrolledPast 設置為true
    • 最後的條件判斷:如果 isHalfShownisNotScrolledPast 都為true,表示圖片的一半已經進入視窗且還未完全滾出視窗,那個就掛上active這個 class name,反之則移除。
function handleScroll(e) {
  //觸發時每張圖片都要馬上判斷
  imgList.forEach((img) => {
    // 圖片滑入位置
    const slideInAt = window.scrollY + window.innerHeight - img.height / 2;
    // 圖片底部於視窗位置
    const imageBottom = img.offsetTop + img.height;
    //圖片是否已經露出一半於畫面中
    const isHalfShown = slideInAt > img.offsetTop;
    //滾動距離還沒超過圖片最底部(是否還看得到一點點圖片)
    const isNotScrolledPast = window.scrollY < imageBottom;
    //兩個判斷都成立時,掛上class name
    if (isHalfShown && isNotScrolledPast) {
      img.classList.add("active");
      //反之拔掉class name
    } else {
      img.classList.remove("active");
    }
  });
}

👉Github Demo 頁面 👈

👉 好想工作室 15th 鐵人賽看板 👈

參考資料

  1. Javascript 30 官網
    https://javascript30.com/
  2. MDN 官網
    https://developer.mozilla.org/en-US/
  3. Debounce & Throttle — Alex Ian

上一篇
[Day12] - Key Sequence Detection(JS30 x 鐵人 30 x MDN)
下一篇
[Day14] - JavaScript References VS Copying(JS30 x 鐵人 30 x MDN)
系列文
JS30 x 鐵人30 x MDN doc30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言