用簡單的 CSS 和 JavaScript 輕鬆製造視差滾動(Parallax Scrolling)

神Q超人
Starbugs Weekly 星巴哥技術專欄
7 min readJun 21, 2021
Photo by Christin Hume on Unsplash

Hi!大家好,我是神 Q 超人!前一陣子在練習切版的時候,突然發現視差滾動在 Landing Page 上的出現率還滿高的,於是就想練習切一些關於視差滾動的頁面,本來是要直接用 ScrollMagic 的,但想說機會難得,就來了解一下它的原理,殊不知實作起來還滿容易的,一起看看怎麼處理的吧!

視差滾動 Parallax Scrolling

介紹

視差滾動簡單來說,就是讓頁面中的某些 element(可能是圖片或 div 等等)在你往下捲動網頁時候,會以不同的速度向上移動。像這樣所產生的視覺效果就叫做視差滾動。

如果覺得上方的敘述太抽象可以看下方的呈現:

https://codepen.io/ms314006/pen/RwpOWxP

上方範例中的月亮被我定住在左上角,所以不會因為捲軸往下捲而向上移動。另一方面,星星不只在捲軸往下捲的時候會向上移動,每個星星向上移動的速度也有些微的不同。像這樣子就是一種視差滾動的效果啦!接下來就來說說是怎麼實作的吧!

關鍵技術

要完成視差滾動有兩個要點。一個是我們需要知道現在的捲軸正在被捲動,第二個是要能夠在捲軸被捲動時,讓 element 知道在當前的捲軸位置下,它必須要在哪個位置。

根據上方兩點需求,關鍵的兩個語法分別是能夠對監聽捲軸位置移動的:

window.addEventListener('scroll', () => {});

還有可以控制元素在頁面中位置的:

transform: translateY(value);

貼心小提醒一下,會用 translateY 是因為在這篇文章中的例子是要呈現垂直的視差滾動,如果你想做的是水平的視差滾動,記得要換成用 translateX。那如果你不想像小孩子一樣,也可以全都要沒關係,因為我也是,OK! 👌

實作

首先先簡單切出一個區塊,然後裡面裝著一個月亮和很多星星的 Icon 就好(對了,這些 Icon 是使用 Font Awesome 提供的免費 Icon):

接下來幫它們上一下基本的 CSS,這些 CSS 主要是用來設置上方區塊的背景顏色、尺寸。至於 Icon 的部分就會用 position 把它們放在初始位置,像是月亮就是固定在 night 區塊的左上方,而星星只有在畫面往下捲才會出現,所以先讓它在 night 區塊外面:

這時候也許有些人會覺得奇怪,因為上方的 CSS 只對星星設置了 bottom,這樣所有的星星不都會在同一個位置嗎?

沒錯!因為這裡我有點懶,不想要為每個星星都寫下水平的固定位置,所以我用了一段 JavaScript 把星星隨機放在某個水平位置上:

上方的 setStarInitPosition 就是在幫星星指定 left(水平)的位置,先是取得視窗的寬度,再用 Random 產生視窗寬度內的隨機一個數字,只是要注意如果剛好隨機產生到的數字等於視窗的寬度,那星星就會在視窗外面或是被切到,像這樣子:

所以產生完隨機的數字後,還要扣掉星星的寬度,這樣子最多就是貼著視窗邊緣,也不會被切到。

最後就要來處理滾動視差的部分,先從簡單的月亮開始,為什麼捲軸在往下滾的時候它都不會往上移呢?其實不是它沒有往上移,而是在每次捲軸被滾動的時候,都去更新它的 translateY(垂直位置),只要一直把 translateY(垂直位置)的值設定成當前捲軸滾動的距離,那看起來就會像是被固定住一樣

下方是更新月亮位置的實作:

就如上所說,只要把月亮的垂直位置在捲軸捲動時,都更新成當前捲軸的高度就可以了。所以其實不是月亮被固定了,而是它的垂直位置隨著捲軸在修改:

在處理星星的移動前,先整理一下上方的邏輯,首先我們知道了:

  1. 如果將 element 的 translateY 一直更新成和捲軸高度一樣,那就代表 element 會和捲軸高度做一比一的「等速移動」,因為捲軸到哪,element 的垂直位置就到哪。
  2. 如果沒有替 element 更新 translateY 的話,把捲軸往下拉,element 就會跟著向上移動。所以 element 和捲軸高度沒有關係,不論現在捲到哪裡,element 的高度都一樣,是一比零的移動,也就是不跟著捲軸捲動而更新 translateY。

根據上方兩點,如果我們要建立一個變數 speed,並用 speed 來控制 element 的位置,那當 speed 為 0 的時候,element 的位置就不會跟著捲軸高度變化。另一方面,如果 speed 為 1 的話,element 的位置就會跟著捲軸的高度等速下降,產生一直固定在頁面上的效果,就像上方的月亮一樣。

既然這樣,那我們就可以試著用 data 屬性把 speed 寫在 HTML 的月亮上面,因為月亮垂直位置和捲軸高度是一比一的等速移動,所以 data-speed 是 1:

然後再到 JavaScript 中改寫成依照 speed 算出月亮在當前捲軸高度,應該要在哪個位置,像這樣子:

如果能夠理解上方所說的原理,那星星的部分應該難不倒你,和月亮不同的是,星星需要「快速的向上移動」,也就是負的速度。

但是要為每個星星 icon 都寫上 data-speed,我還是覺得有點懶,所以和設置星星的初始位置一樣,我另外寫一個方法來產生隨機的速度,放到星星身上:

上方用 -1 去減掉 Math.random 產生出來的 0 到 1 之間的小數,所以星星的速度就會是 -1 到 -2 之間,這是我試過覺得還滿適合的移動比例,大家可以根據自己的情境調整速度,只是不要忘記在監聽 scroll 的 callback 事件中加入星星,和月亮一起更新當下捲軸高度的位置:

這麼一來就大功告成啦!成果就會像本篇文章一開始 Demo 的:

在這裡附上 Code Pen 上的 Demo 連結,大家可以修改 data-speed 看看捲軸捲動時會有什麼差異。

總結

最後來複習一下吧!其實視差滾動的原理就是讓頁面上的每個 element 在捲軸捲動時,用不同的速度移動。

為了達到這個目的,只需要透過監聽捲軸捲動,取得當前捲軸的高度算出每個 element 應該要出現在哪裡,並更新 element 的 translateY,把 element 設置到新的位置上。

而算出 element 出現位置的方法,可以在 HTML 中替要做視差滾動效果的 element 加上 data-speed 的屬性,這樣子可以更通用的以「捲軸高度 Xelement 的移動速度」的公式來算出當前的位置。

希望這篇文章能讓人了解 Parallax Scrolling 的運作和實作原理,如果有問題或是文章中有搞不清楚的地方,再麻煩留言告訴我,我會盡快修改和補充的,非常感謝! 🙏

最後如果沒問題的話就可以繼續用 ScrollMagic 了!😂

參考資料

  1. https://www.youtube.com/watch?v=TUD9999TbS0
  2. https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
  3. https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translateY()

--

--