iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 13
0
Modern Web

JS30 錄系列 第 13

Day 13 - Slide in on Scroll

任務目標

如果一篇網頁文章長到需要滾動瀏覽器 Y 軸才能看完, 為了文章中的插圖能夠吸引讀者的注意,有時會希望滑鼠滑到圖片位置時, 圖片才出現。範例連結

今天就來試看看如何達到。

作法

CSS 設定

一開始圖片是用 transform: translateX()opacity 的方式隱藏在外側邊邊上待命,CSS 設定如下:

/* 圖片待命時為透明狀態 */
.slide-in {
  opacity:0;
  transition:all .5s;
}

/* 左側滑入與右側滑入的待命位置方向相反 */
.align-left.slide-in {
  transform:translateX(-30%) scale(0.95);
}
.align-right.slide-in {
  transform:translateX(30%) scale(0.95);
}

等到觸發滑入動作時,讓圖片現形並回到原來的位置,CSS如下:

.slide-in.active {
  opacity:1;
  transform:translateX(0%) scale(1);
}

JS 程式碼

有了圖片滑入的效果,接下來得決定兩件事:

  1. 滑入的時機點
  2. 滑出的時機點

對於瀏覽器視窗和網頁文件本身的關係,可以想像成拿著一塊中間割空的厚紙板在看一份遠處的畫卷一樣。

瀏覽器視窗所看到的東西,就是從厚紙板中空處看出去所看到的景象,只是畫卷(網頁)本身的一部分而已。

以網頁文件的最左上角為原點,網頁上的元素、瀏覽器視窗的位置等都有相對的座標對應。這些座標大多被記錄在 window 物件的屬性中,接下來我們要利用這些屬性來定義滑入與滑出的時機點。

在滑鼠滾軸往下滑動時,如果「圖片出現在視窗內」, 可以理解成目前「圖片的頂部」比「瀏覽器視窗的底部」還要接近網頁文件原點。

繼續往下滾動,直到「圖片消失在視窗上方」後,可以理解成「圖片的底部」比「瀏覽器視窗的頂部」還要接近網頁原點了.

不管是瀏覽器視窗的頂部,還是瀏覽器視窗的底部,都是會隨著滾動軸而改變的參數。

scroll 事件會在瀏覽器視窗滾動的瞬間觸發,只要在每次監聽到 scroll 事件觸發時,利用自訂函式來判斷目前瀏覽器視窗是否介於「圖片出現在視窗內」與「圖片尚未消失在視窗上方」區間,若介於區間則讓圖片出現,若離開區間則讓圖片消失即可。

程式碼架構如下:

function checkSlide() {
  // 每張圖片都要判斷
  sliderImages.forEach(sliderImage => {
    // 我們希望瀏覽器視窗移動到圖片一半的位置才觸發滑入
    // 因此將瀏覽器視窗底部位置減掉圖片一半高度作為觸發點
    const slideInAt = (window.scrollY + window.innerHeight) - sliderImage.height / 2;
    // 圖片底部位置
    const imageBottom = sliderImage.offsetTop + sliderImage.height;
    // 當瀏覽器底部跑到圖片一半位置下方時
    const isHalfShown = slideInAt > sliderImage.offsetTop;
    // 瀏覽器底部還沒通過圖片底部時
    const isNotScrolledPast = window.scrollY < imageBottom;
    // 若瀏覽器底部超過圖片的一半
    // 且未通過圖片底部
    // 就讓圖片現身
    // 反之隱藏
    if (isHalfShown && isNotScrolledPast) {
      sliderImage.classList.add('active');
    } else {
      sliderImage.classList.remove('active');
    }
  });
}

window.addEventListener('scroll', checkSlide);

在這裡我們讓「瀏覽器底部通過圖片一半的位置」做為圖片滑入的時機點,比起「圖片剛要出現在視窗內」就滑入,前者使用者較容易察覺圖片滑入的效果。

如此大致就完成我們想要的效果了。 但還有個小問題,假設每次滑動就必須讀取圖片並且操控DOM,對瀏覽器而言可能會較吃效能,這裡 JS30 原作者利用debounce函式來降低滑動時呼叫函式的頻率,程式碼如下:

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);
  };
};

// 回呼函式得先用 debounce 處理過
window.addEventListener('scroll', debounce(checkSlide));

debounce 作用的原理大致如下,設立一個計時器延後執行原本要執行的函式,在每次呼叫 debounce 時會先清空該計時器,然後再重新設立一個計時器。

如此一來,只有當這次呼叫 debounce 時間距離上一次呼叫 debounce 的時間大於延遲時間時,我們想執行的函式才會成功執行,也就是說,間隔過小的連續呼叫們都被濾掉了。

關於 debounce ,詳細的過程可以參考 Reference。

以上就是 JS30 第十三篇!

Reference

window.scrollY
Element.offsetTop
debounce 作用原理


上一篇
Day 12 - Key Sequence Detection
下一篇
Day 14 - Reference Copy or Value Copy ?
系列文
JS30 錄30

1 則留言

0

我要留言

立即登入留言