此为历史版本和 IPFS 入口查阅区,回到作品页
TigerCodingStudio
IPFS 指纹 这是什么

作品指纹

在 React 上實作視差滾動(Parallax)效果

TigerCodingStudio
·
·

最近工作上遇到parallax的需求,本來想用第三方套件來快速解決戰鬥,怎料網路上根本沒有輕量又符合需求的套件。最後只好動手實作,絞盡腦汁完成的成果如下:

Parallax Effect

完整的範例在這裡

解釋

我這個範例的要求是當目標<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 :

  1. https://stackoverflow.com/questions/25369487/background-image-that-moves-when-you-scroll-down-the-page
CC BY-NC-ND 2.0 授权