在 React 上實作視差滾動(Parallax)效果
最近工作上遇到parallax的需求,本來想用第三方套件來快速解決戰鬥,怎料網路上根本沒有輕量又符合需求的套件。最後只好動手實作,絞盡腦汁完成的成果如下:
完整的範例在這裡。
解釋
我這個範例的要求是當目標<div>的底部碰到視窗底部時,背景圖片就要移動到終點(最大值)。因此,在表現上,這個範例可能跟你在網路上看到的不太一樣,但我相信基於 JS 的視差滾動效果,原理應該大同小異。
重點:
- Scroll Event Listener
- HTML div element function: getBoundingClientRect()
- CSS style property: background-position
這邊貼上要講解的程式碼,方便解說 :
import React, { useEffect, useState, useRef } from "react"; import "./styles.scss"; const ParallaxContainer = ({ bottomOffset, ceiling, children }) => { const [divBottomDistance, setDivBottomDistance] = useState(bottomOffset); const parallaxDivRef = useRef(null); const handleScroll = () => { if (parallaxDivRef) { const { bottom } = parallaxDivRef.current.getBoundingClientRect(); const bottomDist = window.innerHeight - bottom; setDivBottomDistance(bottomDist); } }; useEffect(() => { window.addEventListener("scroll", handleScroll); return () => { window.removeEventListener("scroll", handleScroll); }; }, []); const getBackgroundPosition = () => { if (divBottomDistance <= bottomOffset) { return bottomOffset; } else { if (divBottomDistance <= ceiling) { return divBottomDistance; } else { return ceiling; } } }; return ( <div ref={parallaxDivRef} style={{ backgroundPosition: `left -400px bottom ${getBackgroundPosition()}px` }} className="container section-1" > {children} </div> ); }; export default ParallaxContainer;
在 CSS 檔案裡面,我們給 div 一個有背景圖片的 class
.section-1 { background-image: url(https://images-na.ssl-images-amazon.com/images/I/51CXg9puq8L.jpg); background-repeat: no-repeat; }
Scroll 事件
既然要做視差滾動效果,那理所當然要監聽網頁的 scroll 事件 :
window.addEventListener("scroll", handleScroll);
這邊我們加上一個 callback 方法 handleScroll。每當使用者滾動時,就會觸發一次 handleScroll 方法 。因為 scroll 事件經常觸發,而且每次觸發都會產生大量事件,建議這邊可以加上一個 throttle 方法把 handleScroll 包起來。
那 handleScroll 裡面發生甚麼事了 ?
const handleScroll = () => { if (parallaxDivRef) { const { bottom } = parallaxDivRef.current.getBoundingClientRect(); const bottomDist = window.innerHeight - bottom; setDivBottomDistance(bottomDist); } };
我們在每次滾動時,獲取一個值 bottomDist ,這是目標 div 底下邊線到視窗底部的距離。我們用 getBoundingClientRect() 裡面的 bottom 以及 window.innerHeight 加減獲得 bottomDist 。如下圖所示。
這個 bottomDist 會隨著頁面滾動變化,數值可正可負。我們要做的事就是把這個 bottomDist 連結 CSS 屬性 background-position 。
div.current.getBoundingClientRect() 是一個很方便的方法,透過這個方法,我們可以取得當前 div 的長寬以及 div 相對於視窗邊界的距離,詳細的介紹在此。
background-position
background-position 是一個控制背景方位的 CSS 屬性 ,我們這邊使用
background-position: left Xpx bottom Ypx;
對背景位置作簡單的設定。
我們給 left 一個固定數值 ,然後 bottom Ypx 的 Y 我們加入一個方法 getBackgroundPosition() 來回傳。
const getBackgroundPosition = () => { if (divBottomDistance <= bottomOffset) { return bottomOffset; } else { if (divBottomDistance <= ceiling) { return divBottomDistance; } else { return ceiling; } } };
當 divBottomDistance (亦即是bottomDist) 小於 bottomOffset (預先設定的背景圖片位置)時,我們不做任何事情,回傳 bottomOffset :
// divBottomDistance <= bottomOffset background-position: left Xpx bottom (bottomOffset)px;
直到 divBottomDistance 隨著滾動使 div 越來越靠近視窗底部時,我們回傳當下的 divBottomDistance 數值,而不再是 bottomOffset 。效果就相當於 div 的背景圖片被視窗底部往上推一般。
// divBottomDistance > bottomOffset background-position: left Xpx bottom (divBottomDistance)px;
這就是大致的解釋,然而因為方位及數值的變化涉及正負交錯的場景,邏輯運行時涉及的數字可能比這邊解釋的稍微複雜一些。但實際的道理就是在 div 隨頁面滾動來到指定範圍時,把背景圖片往上推動。
這邊我們針對背景圖片的移動還做了一個額外的處理 :
if (divBottomDistance <= ceiling) { return divBottomDistance; } else { return ceiling; }
這邊限制了背景圖片的最大可移動距離。
有興趣深入研究的朋友,不妨在 code sandbox 裡,自己嘗試看看。
結語
基於這個範例,朋友們應該也可以按照自己的需求,修改程式實作不同的視差滾動效果。
Happy Coding !
Reference :
喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!
- 来自作者
- 相关推荐