Web production note

 【更新日 :

【JavaScript】Intersection Observer + requestAnimationFrameでパララックスを実装する

Category:
JavaScript

Intersection Observer + requestAnimationFrameでパララックスを実装したサンプルです。

※以前実装した以下のコードをベースにscrollイベントを省いたパターンです。

動作サンプル

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

codepen

See the Pen Intersection Observer + requestAnimationFrame Parallax Vanilla JS by web_nak (@web_walking_nak) on CodePen.

HTML

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

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

CSS

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

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

JavaScript

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

/**
 * Intersection Observer + requestAnimationFrame Parallax Vanilla JS
 */
const parallax = () => {
  'use strict';

  //表示エリア取得
  const targets = document.querySelectorAll('.js-parallax');
  //表示エリアが存在するかチェック
  if(targets.length === 0) {
    return;
  }

  //ウィンドウの高さ取得
  let timeoutId;
  let winHeight = window.innerHeight;
  window.addEventListener('resize',() => {
    if(timeoutId) {
      return
    };
    timeoutId = setTimeout(() => {
      timeoutId = 0;
      winHeight = window.innerHeight;
    }, 200);
  });

  targets.forEach(target => {
    //動かす要素を取得
    const child = target.querySelector('.js-parallax__item');
    //動かす要素が存在するかチェック
    if(!child) {
      return;
    }

    //処理用の設定を追加
    let isVisible = false;
    let frameId = 0;
    let lastPosition = null;
    let offsetTop, scrollRatio;
    const getPositionRatio = () => {
      offsetTop = target.getBoundingClientRect().top + window.pageYOffset;
      const targetHeight = target.clientHeight;
      //比率取得:スクロールできる最大量 / (ウィンドウの高さ + 表示エリアの高さ)
      scrollRatio = (child.clientHeight - targetHeight) / (winHeight + targetHeight);
    }
    getPositionRatio();
    /**
     * 表示エリアリサイズ監視
     */
    const resizeObserver = new ResizeObserver(getPositionRatio);
    resizeObserver.observe(target);


    /**
     * observer処理
     */
    const observerFunc = entries => {
      entries.forEach(entry => {
        //番号取得
        if(entry.isIntersecting) {
          //画面に表示されている要素のみアニメーション発火
          getPositionRatio();
          isVisible = true;
          child.style.willChange = 'transform';
          frameId = requestAnimationFrame(parallaxFunk.bind(entry.target));
        } else {
          //画面外のときはアニメーション削除
          isVisible = false;
          cancelAnimationFrame(frameId);
          child.style.willChange = '';
        }
      });
    }
    //observer設定
    let observer = new IntersectionObserver(observerFunc);
    //observer監視開始
    observer.observe(target);

    /**
     * パララックス位置調整関数
     */
    //予想フレームレートを設定する
    const framesPerSecond = 60;
    const interval = Math.floor(1000 / framesPerSecond);
    let startTime = performance.now();
    let previousTime = startTime;
    let currentTime = 0;
    let elapsed = 0;

    const parallaxFunk = timestamp => {
      //スクロール量(ウィンドウの上端)取得
      const scrollTop = Number(window.pageYOffset.toFixed(0));

      //スクロールしていない場合は何も処理しない
      if(lastPosition === scrollTop) {
        frameId = requestAnimationFrame(parallaxFunk);
        return;
      }

      //フレームレート設定
      currentTime = timestamp;
      elapsed = currentTime - previousTime;
      if (elapsed <= interval) {
        frameId = requestAnimationFrame(parallaxFunk);
        return;
      }
      previousTime = currentTime - (elapsed % interval);

      //スクロール量(ウィンドウの下端)取得
      const scrollBottom = scrollTop + winHeight;

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

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

      //ループアニメーション呼び出し
      if(isVisible) {
        frameId = requestAnimationFrame(parallaxFunk);
      }

      //処理したスクロール位置を保存
      lastPosition = scrollTop;
    }
  });
}
parallax();

関数の呼び出し

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

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

関連リンク

参考リンク