25
24

【React】ライブラリーを使わないでパララックスを使って、リッチなサイトを作る

Last updated at Posted at 2022-06-17

概要

みなさんは、パララックス(parallax)と聞いたことがあるでしょうか?

最近のサイトは、要素がふわっとフェードインしたり、要素の移動速度が違ったり、
スクロールに応じて色々なアニメーションが発火します。

このようなアニメーションを実装するのは大変です。
そのため、多くのライブラリーが公開され、簡単に実装できるようになっています。

ただ今回は、そんなライブラリーを使わずにパララックスのアニメーションを作成し、
リッチに見えるサイトを作成しました。

↓作ったサイトはこちらです。ご覧ください。

※ Qiita Engeer Festa 2022にご参加もお願いします。

パララックス(parallax)についてとは?

まずパララックス(parallax)にて解説します。

パララックス(parallax)とは?

パララックスとは、視差効果のことで、
Webサイトにおいては、パララックスはスクロールなどの動作に応じて、
複数のレイヤー(層)にある要素を異なるスピードで動かすことで、
立体感や奥行きの演出」、「フェード・拡大縮小・回転などの視覚的エフェクト」を演出する
アニメーションを指します。

パララックス(parallax)を使ったサイトの例

1. Instagramの公式サイト
https://about.instagram.com/ja-jp/

2. 星野リゾートの界タビ20s
https://www.hoshinoresorts.com/sp/kaitabi20s/

3. パララックスアニメーションのライブラリー
https://dixonandmoe.com/rellax/

パララックス(parallax)の作り方

1. パララックス(parallax)の基本・構造を理解する

パララックス(parallax)の基本・構造のポイントは以下の通りです。

ポイント
手前にあるものは、スクロール量より早く移動する
奥にあるものは、スクロール量よりゆっくり移動する

image1.png

イメージしてください。🤔

電車に乗って窓から景色を見ている時、遠くに見える家はどのように移動して見えるでしょうか?
→ 遠くに見える家はゆっくり移動しているようにみえますよね!?

では反対に、近くに見える家はどのように移動して見えるでしょうか?
→ 近くに見える家は一瞬で移動しているようにみえますよね!?

この現象をUI上に反映させるのが、パララックス(parallax)になります。

↓ つまり...

手前にくるような画像や要素は、スクロール量より早く移動し、
奥にくるようは背景画像や要素は、スクロール量よりゆっくり移動させることで、
基本的なHTML要素と視差が起こり、パララックスを表現することができます。

2. パララックス(parallax)を作成する

パララックス(parallax)の基本・構造を理解するで記載したように、
パララックス(parallax)を実装するためには以下の要素が必要です。

  • スクロール量を取得する
  • オブジェクトの変化量targetFactorを個別に設定できるようにする
  • パララックスしたいオブジェクトを操作できるようにする
  • スクロールごとに、スクロール量と変化量targetFactorを計算する
  • 計算した値に位置を変更する

では早速、これをもとにパララックスを作っていきましょう。

1. スクロール量を取得する

useEffectをつかい、document.addEventListener('scroll', onScroll)でスクロールするごとにonScrollを発火させ、const scrollY = window.pageYOffsetでスクロール量を取得します。

const ParallaxItem = (children:Props) => {
  const onScroll = () => {
    const scrollY = window.pageYOffset // スクロール量を取得する
  }

  useEffect(() => {
    // スクロールするごとにonScrollを発火する
    document.addEventListener('scroll', onScroll)
  })

  return (
    <></>
  )
}

2. オブジェクトの変化量targetFactorを個別に設定できるようにする

propsでtargetFactorを渡せるように ParallaxPropsを作ります。
ただ、毎回targetFactorを渡すのは大変なので、デフォルト値を決めておきます。

interface ParallaxProps {
  targetFactor?: number
}

const ParallaxItem = (props: ParallaxProps) => {
  // targetFactorのデフォルト値を決めておく
  const targetFactor = props.targetFactor ? props.targetFactor : 0.15

  const onScroll = () => {
    const scrollY = window.pageYOffset
  }

  useEffect(() => {
    document.addEventListener('scroll', onScroll)
  })

  return (
    <></>
  )
}

3. パララックスしたいオブジェクトを操作できるようにする

パララックスしたいオブジェクトのポジションを変更させるため、
useRefを使いdomを操作できるようにしておきます。

interface ParallaxProps {
  // パララックスしたいオブジェクトを個別に設定できるようにする
  children: ReactNode
  targetFactor?: number
}

const ParallaxItem = (props: ParallaxProps) => {
  // useRefを使いdomを操作できるようにする
  const domRef = useRef<HTMLDivElement>(null)
  const targetFactor = props.targetFactor ? props.targetFactor : 0.15

  const onScroll = () => {
    // ついでにdomRef.currentがnullではなかった時にスクロール量を取得するようにする。
    if (domRef.current !== null) {
      const scrollY = window.pageYOffset
    }
  }

  useEffect(() => {
    document.addEventListener('scroll', onScroll)
  })

  return (
    <div ref={domRef}>
      {props.children}
    </div>
  )
}

4. スクロールごとに、スクロール量と変化量targetFactorを計算する

translateYの位置をスクロールロール量に合わせて変更できるように計算する。

interface ParallaxProps {
  children: ReactNode
  targetFactor?: number
}

const ParallaxItem = (props: ParallaxProps) => {
  const domRef = useRef<HTMLDivElement>(null)
  const targetFactor = props.targetFactor ? props.targetFactor : 0.15
  const [offsetY, setOffsetY] = useState(0) // 計算結果

  const onScroll = () => {
    if (domRef.current !== null) {
      const scrollY = window.pageYOffset
      setOffsetY(scrollY * targetFactor * -1)
    }
  }

  useEffect(() => {
    document.addEventListener('scroll', onScroll)
  })

  return (
    <div ref={domRef}>
      {props.children}
    </div>
  )
}

5. 計算した値に位置を変更する

計算した値をOffsetYをオブジェクトのtranslateYに指定する

interface ParallaxProps {
  children: ReactNode
  targetFactor?: number
}

const ParallaxItem = (props: ParallaxProps) => {
  const domRef = useRef<HTMLDivElement>(null)
  const targetFactor = props.targetFactor ? props.targetFactor : 0.15
  const [offsetY, setOffsetY] = useState(0)

  const onScroll = () => {
    if (domRef.current !== null) {
      const scrollY = window.pageYOffset
      setOffsetY(scrollY * targetFactor * -1)
    }
  }

  useEffect(() => {
    document.addEventListener('scroll', onScroll)
  })

  return (
    <div
      ref={domRef}
      style={{
        transform: `translateY(${offsetY}px)`,
      }}
    >
      {props.children}
    </div>
  )
}

6. 完成

その他の対応1
verticalPositionverticalOffsetでY軸方向の基準の位置を決めます。
horizontalPositionhorizontalOffsetでX軸方向の基準の位置を決めます。
objectSizeオブジェクトの大きさを決める。

その他の対応2
スタイルで使わない要素を除く。

その他の対応3

interface ParallaxProps {
  children: ReactNode
  targetFactor?: number
  // 基準の位置・大きさを決める
  verticalPosition: 'top' | 'bottom'
  verticalOffset: number
  horizontalPosition: 'left' | 'right'
  horizontalOffset: number
  objectSize: number
}

// スタイルで使わない要素を除く
type ParallaxStyleType = Omit<ParallaxProps, 'children' | 'targetFactor'>

const ParallaxItem = (props: ParallaxProps) => {
  const domRef = useRef<HTMLDivElement>(null)
  const targetFactor = props.targetFactor ? props.targetFactor : 0.15
  const [offsetY, setOffsetY] = useState(0)

  const onScroll = () => {
    if (domRef.current !== null) {
      const scrollY = window.pageYOffset // scroll量を取得
      setOffsetY(scrollY * targetFactor * -1)
    }
  }

  useEffect(() => {
    document.addEventListener('scroll', onScroll)
  })

  return (
    <div
      ref={domRef}
      css={parallaxItemStyle(props)} //propsでスタイルを渡す。
      style={{
        transform: `translateY(${offsetY}px)`,
      }}
    >
      {props.children}
    </div>
  )
}

const parallaxItemStyle = (style: ParallaxStyleType) => {
  return css({
    position: 'absolute',
    top: style.verticalPosition === 'top' ? style.verticalOffset : '',
    bottom: style.verticalPosition === 'bottom' ? style.verticalOffset : '',
    left: style.horizontalPosition === 'left' ? style.horizontalOffset : '',
    right: style.horizontalPosition === 'right' ? style.horizontalOffset : '',
    width: style.objectSize,
    height: style.objectSize,
  })
}

パララックス(parallax)を使って作ったサイト

↓作ったサイトはこちらです。ご覧ください。

※ Qiita Engeer Festa 2022にご参加もお願いします。

まとめ

この記事では、パララックスの作り方と使い方について解説しました。

少しは、パララックスについての理解ができたのではないのでしょうか???


最後まで読んでくださってありがとうございます!

普段はデザインやフロントエンドを中心にQiitaに記事を投稿しているので、ぜひQiitaのフォローとX(Twitter)のフォローをお願いします。

25
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
24