【更新日 :

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

Category: JavaScript

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

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

動作サンプル

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


See the Pen
Intersection Observer + requestAnimationFrame Parallax Vanilla JS
by Kazuma Sakata (@sakata-kazuma)
on CodePen.


HTML

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

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

CSS

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

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

パララックス関数

//Intersection Observer + requestAnimationFrame 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;
  }

  //ウィンドウの高さ取得
  let winH = window.innerHeight;

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

  //初期設定
  let settings =  [];
  targets.forEach((target,index) => {
    //判別番号をセット
    target.setAttribute('data-index',index);

    //動かす要素を取得
    const child = target.querySelector(childClass);
    //動かす要素が存在するかチェック
    if(!child) {
      return;
    }
    //処理用の設定を追加
    settings.push({
      isVisible: false,
      frameId: 0,
      lastPosi: null,
      child: child,
      //比率取得:スクロールできる最大量 / (ウィンドウの高さ + 表示エリアの高さ)
      scrollRatio: (child.clientHeight - target.clientHeight) / (winH + target.clientHeight)
    });

    //observer監視開始
    observer.observe(target);

    //パララックス処理 初回呼び出し
    requestAnimationFrame(parallaxFunk.bind(target))
  });

  //高さ更新
  let resizeID;
  window.addEventListener('resize', () => {
    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);
  });

  //observer処理
  function observerFunc(entries) {
    entries.forEach(entry => {
      //番号取得
      const index =  Number(entry.target.getAttribute('data-index'));
      if(entry.isIntersecting) {
        //画面に表示されている要素のみアニメーション発火
        settings[index].isVisible = true;
        settings[index].frameId = requestAnimationFrame(parallaxFunk.bind(entry.target));
      } else {
        //画面外のときはアニメーション削除
        settings[index].isVisible = false;
        cancelAnimationFrame(settings[index].frameId);
      }
    });
  }

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

    //画面外のときは処理しない
    if(!settings[index].isVisible) {
      return;
    }

    //スクロール量(ウィンドウの上端)取得
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;

    //スクロールしていない場合は何も処理しない
    if(settings[index].lastPosi === scrollTop) {
      settings[index].frameId = requestAnimationFrame(parallaxFunk.bind(this));
      return;
    }

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

    //表示エリアの位置取得
    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)';

    //ループアニメーション呼び出し
    settings[index].frameId = requestAnimationFrame(parallaxFunk.bind(this));

    //処理したスクロール位置を保存
    settings[index].lastPosi = scrollTop;
  }
}

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

関数の呼び出し

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

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

関連リンク

参考リンク

Category : JavaScript