Web production note

 【更新日 :

【JavaScript】Intersection Observer + scrollイベントでパララックスを実装する

Category:
JavaScript

Intersection Observer + scrollイベントでパララックスを実装したサンプルです。
画面に入った要素のみscrollイベントを発火させるので、scrollイベントのみの実装よりは低負荷になっていると思います。

※以前実装した以下のコードをベースにしています。

動作サンプル

表示エリアからはみ出している量だけ画像が動きます。
スクロール中に要素がエリアから飛び出さないように動く量はウィンドウの高さに応じて算出しています。

codepen

<p class="codepen" data-height="500" data-default-tab="result" data-slug-hash="gOjOPjd" data-user="web_walking_nak" style="height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/web_walking_nak/pen/gOjOPjd">
  Intersection Observer + Scroll Event Parallax Vanilla JS</a> by web_nak (<a href="https://codepen.io/web_walking_nak">@web_walking_nak</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

HTML

表示エリア .js-parallax-elm-box の中に、動かす要素 .js-parallax-elm を内包してください。
上記は画像を格納していますが、何が入っていても動きます。

<div class="js-parallax-elm-box">
  <div class="js-parallax-elm">
    <img src="画像パス" alt="">
  </div>
</div>

CSS

表示エリアからはみ出している要素を隠すため、最低限overflow: hidden;を設定してください。
※表示エリアのheightは実装するデザインに応じて適宜設定してください。

.js-parallax-elm-box {
  overflow: hidden;
}
.js-parallax-elm-box img {
  display: block;
}

JavaScript

対象class名を変更したい場合は、targetClass「表示エリア class」と、childClass「動かす要素 class」を編集してください。

//Intersection Observer + Scroll Event Parallax Vanilla JS
function parallax() {
  'use strict';

  //class設定
  //表示エリア class
  const targetClass = '.js-parallax-elm-box';
  //動かす要素 class
  const childClass = '.js-parallax-elm';

  //表示エリア取得
  const targets = Array.prototype.slice.call(document.querySelectorAll(targetClass),0);
  //表示エリアが存在するかチェック
  if(targets.length === 0) {
    return;
  }

  //observer設定
  let observer = new IntersectionObserver(observerFunc, {
    root: null,
    rootMargin: '0px',
    threshold: 0
  });

  //ウィンドウの高さ取得
  let winH = window.innerHeight;
  //スクロール量(ウィンドウの上端)取得
  let scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  //スクロール量(ウィンドウの下端)取得
  let scrollBottom = scrollTop + winH;

  //初期設定
  let setListener =  [];
  let settings =  [];
  targets.forEach((target,index) => {
    //Listener設定取得用の判別番号をセット
    target.setAttribute('data-index',index);
    //動かす要素を取得
    const child = target.querySelector(childClass);
    //動かす要素が存在するかチェック
    if(!child) {
      return;
    }
    //処理用の設定を追加
    settings.push({
      child: child,
      //比率取得:スクロールできる最大量 / (ウィンドウの高さ + 表示エリアの高さ)
      scrollRatio: (child.clientHeight - target.clientHeight) / (winH + target.clientHeight)
    });
    //scrollイベントへ渡すlistener設定
    setListener.push(
      {
        target: target,
        handleEvent: function handleEvent () {
          requestAnimationFrame(parallaxFunk.bind(target));
        }
      }
    );
    //初期表示の位置調整
    requestAnimationFrame(parallaxFunk.bind(target));
    //observer監視開始
    observer.observe(target);
  });

  //高さ更新
  let resizeID;
  window.addEventListener('resize', function() {
    clearTimeout(resizeID);
    resizeID = setTimeout(() => {
      //ウィンドウの高さ取得
      winH = window.innerHeight;
      //各要素の高さ更新
      targets.forEach((target,index) => {
        //動かす要素が存在するかチェック
        if(!settings[index].child) {
          return;
        }
        //比率取得:スクロールできる最大量 / (ウィンドウの高さ + 表示エリアの高さ)
        settings[index].scrollRatio = (settings[index].child.clientHeight - target.clientHeight) / (winH + target.clientHeight);
      });
    }, 200);
  });

  //スクロール量更新
  window.addEventListener('scroll', function() {
    //スクロール量(ウィンドウの上端)取得
    scrollTop = window.pageYOffset || document.documentElement.scrollTop;
    //スクロール量(ウィンドウの下端)取得
    scrollBottom = scrollTop + winH;
  }, {passive: true});

  //observer処理
  function observerFunc(entries) {
    entries.forEach(entry => {
      //listener設定取得
      const listener =  setListener[entry.target.getAttribute('data-index')];
      if(entry.isIntersecting) {
        //画面に表示されている要素のみスクロールイベントへ追加
        window.addEventListener('scroll', listener, {passive: true});
      } else {
        //画面外のときはスクロールイベント削除
        window.removeEventListener('scroll', listener, {passive: true});
      }
    });
  }

  //パララックス位置調整関数
  function parallaxFunk() {
    //番号取得
    const index =  Number(this.getAttribute('data-index'));

    //表示エリアの位置取得
    const targetPosi =  this.getBoundingClientRect().top + scrollTop;

    //ウィンドウの高さに対するスクロール量を取得(小数点第2以下は四捨五入)
    const setVal = ((scrollBottom - targetPosi) * settings[index].scrollRatio).toFixed(1);

    //スクロール値を設定
    settings[index].child.style.transform = 'translate3d(0,'+ (setVal * -1) +'px,0)';
  }
}

関数の呼び出し

任意の場所で以下を実行し関数を呼び出してください。

//パララックス呼び出し
parallax();

関連リンク

参考リンク