PJCHENder 未整理筆記

[JS30] Day13: Slide In on Scroll

2017-09-26

[JS30] Day13: Slide In on Scroll

@([WesBos] JS30)[JavaScript]

keywords: scroll, detection, 捲軸, 滾動, debounce, 投影片效果

CSS 部分

圖片左右排版

這裡圖片排版的方式就是使用 float 來達到讓圖片左右排列的效果。

1
2
3
4
5
6
7
8
9
.align-left {
float: left;
margin-right: 20px;
}

.align-right {
float: right;
margin-left: 20px;
}

圖片滑入

之所以能製作出圖片滑入的效果,主要是使用 opacity (0 -> 1), transform (translateX) 和 transition

一開始的圖片樣式opacity 是 0 ,translateX 則向右(左)往外偏出一些:

1
2
3
4
5
6
7
8
9
10
11
.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);
}

當使用者捲軸滾到那張圖片的時候,我們會在該圖片上添加 .active 這個 class,讓圖片回到原來文章中的位置,並且顯示出來(opacity):

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

JS 部分

原理其實不難,就是當視窗的捲軸捲動到「特定的高度」時,就要為該張圖片增添 active 的 class

我們會用到幾個屬性和方法:

1
2
3
4
window.scrollY        // 取的瀏覽器視窗捲軸 Y 的高度(捲軸在最上方時是 0)
window.innerHeight // 瀏覽器內視窗的高度
element.offsetTop // 元素距離外層容器上方的距離
element.height // 元素的高度

window @ WebAPIs in MDN
HTMLElement @ WebAPIs in MDN

偵測瀏覽器底部捲軸的距離

由於 window.scrollY 給的是視窗上方到捲軸最上面的距離,但是我們想要的是視窗下方到捲軸最上面的距離,因此我們要再加上 window.innerHeight

1
const scrollAt = window.scrollY + window.innerHeight

找出要為圖片添加 class 的特定高度

我們希望視窗捲軸高度到該張圖片的一半高時添加 .active class(高度可以自訂),因此:

1
const imageMiddleOffset = image.offsetTop + image.height / 2

這裡有一個要留意的地方是, offset 指的就是相對於 offsetParent 的距離。一般來說offsetParent都會是 body,但是如果這個元素的外層有另外設定 position 的話,那麼 offsetParent 就可能改變。

因此,再使用 offset 屬性前,盡量先用 offsetParent 來查看該元素的父層對應到的是哪個元素。

達到特定高度時添加 class

再來就是當我們捲軸的高度超過特定高度(圖片一半)時,就添加 class:

1
2
3
if (scrollAt > imageMiddleOffset) {
sliderImage.classList.add('active')
}

debounce 函式

另外,因為我們監聽的是 window 上的 scroll 事件,所以只要瀏覽器一有 scroll 的情況都會觸發這個事件,這可能導致瀏覽器的效能下降,因此在這堂範例中額外提供 debounce 函式,這個函式的功能是在「特定的時間內,只會觸發某事件一次」。

這種 debounce 函式常常應用在可能會多次觸發事件的時間點,例如 scroll, keydown 等等。在 lodash.js 中亦可找到此函式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* debounce function
* 讓某函式在一定時間內只能觸發一次,目的是提升效能
**/
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 function 中:

1
window.addEventListener("scroll", debounce(scrollHandler));

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// STEP1 選擇所有圖片
const sliderImages = document.querySelectorAll(".slide-in");

// STEP2 監聽 window 捲軸滾動事件
window.addEventListener("scroll", debounce(scrollHandler));

// STEP3 處理捲軸滾動
function scrollHandler () {
sliderImages.forEach((sliderImage, index) => {
const imageMiddleOffset = sliderImage.offsetTop + (sliderImage.height / 2); // 取得每一張圖片中間的 offsetY 位置
const scrollAt = window.scrollY + window.innerHeight
if (scrollAt > imageMiddleOffset) {
sliderImage.classList.add('active')
}
})
}
/**
* debounce function
* 讓某函式在一定時間內只能觸發一次,目的是提升效能
**/
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);
};
}

完成作品

Day13: Slide In on Scroll

掃描二維條碼,分享此文章