【Javascript】スクロールで背景や文字が動くパララックスの実装方法(自作)

css Javascript Programming

こんにちは。あっきーです。
初期不良で壊れたモニターを返品し再度購入したら、新品が値下げされてて、結果7,000円くらい安く買えたラッキー人間こと、フリーランスWebプログラマーです。

今回は、「Javascriptを使ってパララックスを実装してみよう」というテーマです。

「Webページが味気ない」とか「立体感のあるwebサイト作りたい」というときに使える方法で、簡単に実装できるので参考にしてください。

今回使う言語

  • HTML
  • CSS
  • Javascript(jQuery利用)
パララックスのライブラリにRellax.jsというのもあります。シンプルですぐ使えるんですが、後で出てくる「初期位置」の問題でズレる分だけ最初の位置を移動させておかないといけないデメリットがあったので、自作という道を選びました。

パララックスとは

Webサイトにおけるパララックスとはコンテンツを移動速度を変化させることで立体感を出す技法のことです。
例えば以下のサイト。
https://www.q-co.jp/
https://www.beckett.design/

どっちも立体感がありますよね。めっさオシャレです!
これは背景画像を遅く移動(または固定)させ、文字などの前面との移動速度に変化を与えることで立体感が出るという仕組みです。

一見難しそうですが、機能自体は誰でもすぐにできます。

というわけでやっていきましょう。

パララックスの実装

さっそくパララックスを実装してみましょう。
実装したものはこちら。

フォルダ構成等はマネしてもらってOKです!

HTML

index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>Title</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap Css -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <!-- Font Awesome CSS -->
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.1/css/all.css" integrity="sha384-vp86vTRFVJgpjF9jiIGPEEqYqlDwgyBgEF109VFjmqGmIY/Y4HV4d3Gp2irVfcrp" crossorigin="anonymous">
    <link rel="stylesheet" href="css/styles.css">
  </head>
  <body>
    <div class="wrap wrap-1">
      <div class="parallax">パララックス</div>
    </div>
    <div class="wrap wrap-2">
      <div class="parallax">
        <h1>見出し</h1>
        <p>パララックスしてます。パララックスしてます。</p>
      </div>
      <img loading="lazy" src="img/clouds-gdc63fad86_1280.jpg" width="400" height="300" alt="">
    </div>
    <div class="wrap wrap-3">
      <div class="parallax">パララックス</div>
    </div>

    <!-- jQuery and Popper.js -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="js/main.js"></script>
  </body>
</html>

Bootstrap(念のため), jQueryを使うのでこれらをCDN経由で読み込みます。

実際にパララックスが実装されるのは以下の部分です。

<div class="wrap wrap-1">
    <div class="parallax">パララックス</div>
</div>

CSS

/* パララックスのラッパー */
.wrap {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 120vh;
}
/*パララックスデフォルト設定 */
.parallax {
  position: absolute;
  font-size: 3rem;
  top: 25%;
}

/* 各ラッパー個別設定 */
.wrap-1 {
  background: url('../img/sunflowers-g86826ffa7_1280.jpg');
  background-size: cover;
  background-repeat: no-repeat;
}
.wrap-2 {
  background-size: cover;
  background-repeat: no-repeat;
  height: auto;
  height: 50vh;
  width: 70vw;
  margin: auto;
}
.wrap-2 .parallax {
  top: 0;
  left: 0;
  width: 300px;
  font-size: 1rem;
}
.wrap-3 {
  background: url('../img/tree-gbe5798a9c_1280.jpg');
  background-size: cover;
  background-repeat: no-repeat;
  color: #fff;
}

コードの中で重要な部分をピックアップします。

.wrap {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 120vh;
}
/*パララックスデフォルト設定 */
.parallax {
  position: absolute;
  font-size: 3rem;
  top: 25%;
}

wrapクラスではパララックスを適用する囲いのスタイルを作っています。
parallaxposition: absoluteを使うのでwrapクラスではposition: relative当てています。

また、中央寄せにするためのflexの設定や、高さの調整をしています。

parallaxが実際に移動速度が変化する要素です。
parallaxposition: absoluteにして、フォントサイズなどを適当にあてています。
このparallax要素をJavascriptでいじって移動速度を変化させます。

Javascript

$(function() {
  //ウィンドウ幅・高さを取得
  const windowWidth = $(window).width();
  const windowHeight = $(window).height();
  // .parallax要素を取得
  const $targets = $('.parallax');
  // イベント発火のタイミング
  const offset = 50;

  // 各パララックス要素に対し、イベント発火の地点を取得
  let targetPositions = []
  $targets.each(function(index, el) {
    targetPositions[index] = $(el).offset().top - offset;
  })

  //画面表示時にすでにパララックス要素が見える場合は.on-load-parallxクラスをつけ特別処理を後で行う
  for(let i = 0; i < targetPositions.length; i++) {
    if(targetPositions[i] < windowHeight) {
      const theTarget = $targets[i]
      $(theTarget).addClass('on-load-parallax')
    }
  }

  //スクロールイベント
  $(window).scroll(function() {
    //スクロール量を取得
    const scroll = $(window).scrollTop();
    for(let i = 0; i < targetPositions.length; i++) {
      if(scroll + windowHeight >= targetPositions[i]) { // スクロール量が発火地点に達したら要素のズラし開始
        //要素の移動速度
        let speed = 0.3
        const theTarget = $targets[i];
        if(windowWidth <= 768) {//スマホ時のスピード
          speed = speed / 2
        }

        let translateY = 0;
        if($(theTarget).hasClass('on-load-parallax')) {//画面表示時に見えてたパララックス要素にはスクロール量で計算
          translateY = scroll * speed
        } else { // 残りは「発火地点からのスクロール量」で計算
          translateY = (scroll - targetPositions[i] + windowHeight) * speed
        }
        // Y方向に位置をずらす
        $(theTarget).css('transform', 'translateY('+ translateY + 'px)')
      } else { // イベントに達していない場合、移行の処理はしない
        break;
      }
    }
  })
})

処理のポイントは2つ

  • 要素をtransformで動かすことでズレを生ませる
  • イベント発火地点を各要素で定めることで、初期位置の記述を減らす

これらを踏まえて1つ1つコードを見ていきます。

各要素のイベント発火位置を決める

// 各パララックス要素に対し、イベント発火の地点を取得
let targetPositions = []
$targets.each(function(index, el) {
  targetPositions[index] = $(el).offset().top - offset;
})

targetPositionsという配列を用意し、ここにそれぞれのパララックス要素($targets)のイベント発火位置を格納していきます。

実際にeach文の中でそれぞれの要素を取り出しているのが分かります。
その中の$(el).offset().topは要素のウィンドウ上端からの位置です。
offsetはイベント発火のタイミングを早める(遅くする)量です。
例えば100とすれば「要素が見える前の100px分早い段階で開始する」となりますし、-100ならその逆になります。

もちろん、スクロールを始めた全要素一斉に動くようにしても良いんですが、そうなると下にある要素はスクロールの度に動いてしまって、画面に見えるときには変な位置に表示されてしまいます。

それを修正するためにも各要素にいちいちCSSでtopやらを指定しないといけませんし、計算も面倒...

なので、各要素に開始位置を設定しておき、「ある要素が見えたらその要素を動かし始める」という形にしています。

スクロール量に応じて要素をずらす

//スクロールイベント
$(window).scroll(function() {
  //スクロール量を取得
  const scroll = $(window).scrollTop();
  for(let i = 0; i < targetPositions.length; i++) {
    if(scroll >= targetPositions[i] - windowHeight) { // スクロール量が発火地点に達したら要素のズラし開始
      //要素の移動速度
      let speed = 0.3
      const theTarget = $targets[i];
      if(windowWidth <= 768) {//スマホ時のスピード
        speed = speed / 2
      }

      let translateY = 0;
      if($(theTarget).hasClass('on-load-parallax')) {//画面表示時に見えてたパララックス要素にはスクロール量で計算
        translateY = scroll * speed
      } else { // 残りは「発火地点からのスクロール量」で計算
        translateY = (scroll - targetPositions[i] + windowHeight) * speed
      }
      // Y方向に位置をずらす
      $(theTarget).css('transform', 'translateY('+ translateY + 'px)')
    } else { // イベントに達していない場合、移行の処理はしない
      break;
    }
  }
})

スクロール量scroll = $(window).scrollTop()を使って、for文によって各要素に対して処理をしていきます。
if(scroll + windowHeight >= targetPositions[i])ではスクロール量がイベント発火位置に達しているかどうかを判定しています。
windowHeightがウィンドウの高さ、targetPositions[i]がさっき見た要素のイベント開始時です。
scrollは「画面の上」を基準にしているので、画面の高さを足して調節しています。

もし発火地点に届いていない場合はfor文をbreakで中断します。
これは、その要素より下にある要素も当然見えていないので処理するだけ無駄だからです。

移動はtransformプロパティをいじる

要素が見えた時点でずらしが始まるんですが、これは要素のtransformプロパティをいじることで実現させています。

// Y方向に位置をずらす
$(theTarget).css('transform', 'translateY('+ translateY + 'px)')

Y方向にどれだけ動かすかというのをがtranslateYなんですが、これを以下のように計算しています。

//要素の移動速度
let speed = 0.3
const theTarget = $targets[i];
if(windowWidth <= 768) {//スマホ時のスピード
  speed = speed / 2
}

let translateY = 0;
if($(theTarget).hasClass('on-load-parallax')) {//画面表示時に見えてたパララックス要素にはスクロール量で計算
  translateY = scroll * speed
} else { // 残りは「発火地点からのスクロール量」で計算
  translateY = (scroll - targetPositions[i] + windowHeight) * speed
}

speedは移動速度ですね。スクロール量を元にするんですが、スクロール量をそのまま使うと要素が動きすぎちゃうので補正しています。今回のコードでは±0.1 ~ 0.3くらいが良いです。

また、スマホ時のは設定したスピードの1/2になるように設定しています。
スマホではごちゃごちゃするのでパララックスを使いたくない場合は、ここを0に置き換えちゃってください。

let translateY = 0;
if($(theTarget).hasClass('on-load-parallax')) {//画面表示時に見えてたパララックス要素にはスクロール量で計算
  translateY = scroll * speed
} else { // 残りは「発火地点からのスクロール量」で計算
  translateY = (scroll - targetPositions[i] + windowHeight) * speed
}

ここで実際の計算が行われています。

if($(theTarget).hasClass('on-load-parallax'))はページ訪問時にパララックス要素が見えている場合か判定しています。
見えている場合は「スクロール量(scroll)x 速度(speed)」だけ要素を動かします。
見えていない場合は「(スクロール量(scroll) + ウィンドウ高さ(windowHeight) - 発火位置(targetPositions[i])) x 速度(speed)」です。

ちょっと複雑ですが、「要素の発火位置からのスクロール量」を元にしたいためこのような計算式になっています。

まとめ

というわけで、パララックスの実装方法の解説でした。
意外と簡単だったと思います。

自分なりにアレンジしてもらえればもっとカッコいいものができるのでやってみてください!

それでは、また。

スポンサードサーチ

オススメ英語学習用SNS "Our Dictionary"

人気記事英語学習用SNSをLaravelで作ってみた【システム解説あり】