Parallax Scrolling 是指當您在滾動網頁時,讓特定元素以不同的速度移動,創造出一種視覺上的效果,稱為視差滾動。 這種技術能讓您的網站看起來更豐富、更時尚感、更具沉浸感,使用視差滾動製作的網頁,您很難不被它吸引。 該技術在早期的 Flash 動畫就已經很常見了,直到 2010 年之後才開始在網頁設計中出現。

直接來看看一些例子,可能會更有感覺。

看完後,是不是很吸引眼球呢? 上述網站要製作的困難度非常高,可以直接另開專案處理了。 而本文將介紹兩種相對簡單很多的方法,讓您的網站可以輕鬆使用。

方法一:使用 background-attachment: fixed

CSS background-attachment: fixed; 使用方法簡單,甚至完全不用使用到 JavaScript 就能實現。 雖然無法製作出太複雜的場景,但由於方法簡單,使用非常廣泛,在很多網頁中都能看得到,非常值得一學。

以下範例,我使用四張圖片,並在圖片正中央顯示圖片來源文字。 在移動卷軸的過程中,如果圖片碰到視窗最上方時,就將圖片 background-attachment 設定為 fixed,這樣就能實現最基本的視差滾動效果。 HTML 結構大致如下:

            
                <div class="parallax">
                    <div class="layer">
                        <div class="text">Photo by Nathan Anderson on Unsplash</div>
                    </div>
                    <div class="layer">
                        <div class="text">Photo by Alyssa Boobyer on Unsplash</div>
                    </div>
                    <div class="layer">
                        <div class="text">Photo by eberhard grossgasteiger on Unsplash</div>
                    </div>
                    <div class="layer">
                        <div class="text">Photo by Denis Degioanni on Unsplash</div>
                    </div>
                </div>
            
        

CSS 的重點是圖片必需用 background-image 載入,其他只是一些排版的 CSS 設定而已,可以根據自己的需求調整。

            
                .parallax {
                    width: 100%;
                    overflow: hidden;
                }
                .parallax .layer {
                    width: 100%;
                    height: 100vh;
                    position: relative;
                    background-size: cover;
                    background-position: center center;
                    background-repeat: no-repeat;
                }
                .parallax .layer:nth-child(1) {
                    background-image: url(1.jpg);
                }
                .parallax .layer:nth-child(2) {
                    background-image: url(2.jpg);
                }
                .parallax .layer:nth-child(3) {
                    background-image: url(3.jpg);
                }
                .parallax .layer:nth-child(4) {
                    background-image: url(4.jpg);
                }
                .parallax .layer .text {
                    position: absolute;
                    top: 0px;
                    bottom: 0px;
                    left: 0px;
                    right: 0px;
                    display: flex;
                    flex-direction: row;
                    align-items: center;
                    justify-content: center;
                    text-align: center;
                    padding-top: 30vh;
                    font-size: 3rem;
                    color: #FFFFFF;
                }
            
        

最後是 JavaScript 的部分,只要偵測到圖片碰到視窗最上方時,就將 background-attachment 設定為 fixed。

            
                window.addEventListener('load', (event) => {
                    const layers = Array.from(document.querySelectorAll('.layer'));

                    window.addEventListener('scroll', (event) => {
                        const scrollPositionY = this.pageYOffset;
                        layers.forEach(layer => {
                            layer.style.backgroundAttachment = scrollPositionY > layer.offsetTop ? 'fixed' : '';
                        });
                    })
                })
            
        
  • 行 5
    this.pageYOffset 可以取得目前捲軸位置。
  • 行 7
    layer.offsetTop 可以取得圖片距離網頁頂部的距離,藉由判斷 offsetTop 與 pageYOffset 的數值大小,就能知道圖片是否碰觸到視窗頂部。
不使用 JavaScript,直接使用 CSS 將 background-attachment 設定為 fixed 可能是更常見的做法,但兩者效果還是有些差異。

方法二:使用各種 transform

方法一的做法只能製作一些比較簡單的視差滾動,如果要製作複雜版本的話,就需要在使用者移動卷軸時,搭配各種 transform 來實現視差滾動,這也是比較常見的做法。 以下是比較常見的 transform 方法:

  • translate() 平移
  • scale() 縮放
  • rotate() 旋轉
  • skew() 歪斜

接續方法一的例子,修改成當使用者移動卷軸時,使用 translateY() 讓圖片往上移動變慢,並透過 scale() 讓中間的文字從沒有顯示慢慢放大。 為了實現這個效果,需要先在 CSS 中將文字縮放至最小,並將 padding-top 設定為 30vh。 這樣在捲動時,效果會比較好,30vh 只是一個大概的數值,您可以嘗試使用其他數值來讓效果更好。

            
                .parallax .layer .text {
                    position: absolute;
                    top: 0px;
                    bottom: 0px;
                    left: 0px;
                    right: 0px;
                    display: flex;
                    flex-direction: row;
                    align-items: center;
                    justify-content: center;
                    text-align: center;
                    padding-top: 30vh;
                    font-size: 3rem;
                    color: #FFFFFF;

                    // 加入這兩行
                    transform: scale(0);
                    padding-top: 30vh;
                }
            
        

JavaScript 的部分,必須盡可能地減少在 scroll 事件中操作 DOM 元素,大致上邏輯與方法一雷同,修改 JavaScript 如下:

            
                window.addEventListener('load', (event) => {
                    const layers = Array.from(document.querySelectorAll('.layer'));
                    const texts = layers.map(layer => layer.querySelector('.text'));

                    window.addEventListener('scroll', (event) => {
                        const scrollPositionY = this.pageYOffset;

                        for (let i = 0; i < 4; i++) {
                            const layer = layers[i];
                            const text = texts[i];
                            const offsetTop = layer.offsetTop;
                            if (scrollPositionY > offsetTop) {
                                layer.style.transform = `translateY(${(scrollPositionY - offsetTop) * 0.5}px)`;
                                text.style.transform = `translateY(${(scrollPositionY - offsetTop) * -0.5}px) scale(${((scrollPositionY - offsetTop) / layer.clientHeight) * 2})`;
                            } else {
                                layer.style.transform = '';
                                text.style.transform = '';
                            }
                        }
                    })
                })
            
        
  • 行 6
    this.pageYOffset 可以取得目前捲軸位置。
  • 行 12
    藉由判斷 layer.offsetTop 與 pageYOffset 的數值大小,就能知道圖片是否碰觸到視窗頂部。
  • 行 13
    根據以下公式並透過 translateY() 調整圖片的位移量,在上方範例中,將 speed 設定為 0.5,所以圖片會比正常還要慢一半的速度往上移動。
    位移公式 = (this.pageYOffset - layer.offsetTop) * speed
    speed 為 0 表示圖片不會有任何位移,也就是正常捲軸移動圖片跟著移動的模式。
    speed 為 1 表示圖片位移量等於捲軸位移量,也就是圖片會被釘在原地,不會跟著捲軸移動。
  • 行 14
    與行 13 使用相同方法,只是讓文字往反方向移動,不然如果再加上縮放的話,看起來文字會往下跑。接著再搭配以下公式,並使用 scale() 讓文字縮放,在上方範例中,將 speed 設定為 2,所以文字最終會放大到原來的兩倍。
    縮放公式 = ((this.pageYOffset - layer.offsetTop) / layer.clientHeight) * speed
    speed 為 0 表示文字不會縮放,所以會一直看不到文字。
    speed 為 1 表示文字會放大到 CSS 中,font-size 設定的文字大小。
    speed 為 2 表示文字會放大到 CSS 中,font-size 設定的文字大小的兩倍。

結語

製作較複雜的視差滾動效果需要計算各種數值,雖然基本原理都是根據卷軸位置與各種 transform 來實現,但我認為製作難度仍然很高,本文介紹的兩種方式已經是非常簡單的方法了。 如果大家實作上有遇到什麼困難的話,歡迎留言討論喔。