Web production note

 【更新日 :

【JavaScript】追従要素が段々積み重なっていく動作を実装する

Category:
css / js

追従要素が段々積み重なっていく動作をposition: sticky;とJSで実装したサンプルです。

position: sticky;とJSで実装

codepen

See the Pen position: sticky; Stacking elements within a specific area Vanilla JS by web_nak (@web_walking_nak) on CodePen.

JavaScript

//position: sticky;で指定エリア内に要素を追従させ積み重ねる関数
const areaStacking = () => {
  //エリアチェック
  const areas = document.querySelectorAll('.js-stacking__area');
  if(areas.length === 0) {
    return;
  }

  //追従チェック関数
  const checkPosition = targets => {
    //ブロックごとの高さを取得
    const height = [];
    targets.forEach(target => {
      target.style.paddingTop = '';
      target.style.paddingBottom = '';
      height.push(target.offsetHeight);
    });
    const heightLen = height.length;

    //高さを設定
    targets.forEach((target, index) => {
      //ブロックのtop位置設定
      let topHeight = 0;
      if(index === 1) {
        topHeight = height[0];

      } else if(index > 1) {
        topHeight = height.slice(0,index);
        topHeight = topHeight.reduce((a, x) => a + x);
      }
      target.style.paddingTop = topHeight + 'px';

      //ブロックのbottom位置設定
      let bottomHeight = 0;
      if(index !== heightLen - 1) {
        bottomHeight = height.slice(index+1,heightLen);
        bottomHeight = bottomHeight.reduce((a, x) => a + x);
        target.style.paddingBottom = bottomHeight + 'px';
      }
    });
  }

  areas.forEach(area => {
    //エリア内に追従要素が存在する場合のみ処理する
    const targets = area.querySelectorAll('.js-stacking__item');
    if(targets.length > 0) {
      checkPosition(targets);

      /*
        レスポンシブ対応
      */
      //表示エリアリサイズ監視 ResizeObserver
      const resizeObserver = new ResizeObserver(() => {
        checkPosition(targets);
      });
      //リサイズ監視開始
      resizeObserver.observe(area);
    }
  });
};

HTML

アニメーションするエリアに .js-stacking__area を追加し、
アニメーションさせたい要素に.js-stacking__item を追加します。

<div class="js-stacking__area">
  <p class="js-stacking__item">テキスト1</p>
  <p class="js-stacking__item">テキスト2</p>
</div>

CSS

.js-stacking__item {
  position: sticky;
  top: 4em;
}

関数の実行

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

areaStacking();

position: fixed; とJSで実装

position: sticky;と同等の機能です。
overflow: hidden; との兼ね合いで stickyが利用できない時には利用できるかもしれません。

codepen

See the Pen Stacking elements within a specific area Vanilla JS by web_nak (@web_walking_nak) on CodePen.

JavaScript

//指定エリア内に要素を追従させ積み重ねる関数
const areaFixedText = () => {
  //エリアチェック
  const areas = document.querySelectorAll('.js-fixed-text__area');
  if(areas.length === 0) {
    return;
  }

  //追従チェック関数
  const checkFixed = (targets,area) => {
    //ブロックごとの高さを取得
    const height = [];
    targets.forEach(target => {
      target.style.paddingTop = '';
      target.style.paddingBottom = '';
      height.push(target.offsetHeight);
    });
    const heightLen = height.length;

    //追従エリアの情報を取得
    const areaPosi = area.getBoundingClientRect().top;
    const areaHeight = area.clientHeight;
    //追従の終了ポイント
    const endPosi = areaPosi + areaHeight;

    //ブロックごとの処理
    targets.forEach((target, index) => {
      //ブロックのtop位置設定
      let topHeight = 0;
      if(index === 1) {
        topHeight = height[0];

      } else if(index > 1) {
        topHeight = height.slice(0,index);
        topHeight = topHeight.reduce((a, x) => a + x);
      }
      target.style.paddingTop = topHeight + 'px';

      //ブロックのbottom位置設定
      let bottomHeight = 0;
      if(index !== heightLen - 1) {
        bottomHeight = height.slice(index+1,heightLen);
        bottomHeight = bottomHeight.reduce((a, x) => a + x);
        target.style.paddingBottom = bottomHeight + 'px';
      }

      //要素の位置と高さを取得
      let targetHeight = target.clientHeight;
      const parent = target.parentNode;
      const parentPosi = parent.getBoundingClientRect().top;
      const startPosi = parent.getBoundingClientRect().top;
      parent.style.height = '';
      const parentHeight = parent.clientHeight;
      if(targetHeight > parentHeight) {
        parent.style.height = targetHeight + 'px';
      }

      //エリア内の処理
      if(0 > startPosi && targetHeight < endPosi) {
        target.classList.add('is-fixed');
        target.style.top = '';

      //エリアより上の処理
      } else if(0 <= startPosi) {
        target.classList.remove('is-fixed');
        target.style.top = '';

      //エリアより下の処理
      } else {
        target.classList.remove('is-fixed');

        //停止位置を設定
        topHeight = 0;
        if(index < heightLen - 1) {
          topHeight = height.slice(index + 1,heightLen);
          topHeight = topHeight.reduce((a, x) => a + x);
        }
        target.style.top = (areaHeight - ((-areaPosi) - (-parentPosi)) - targetHeight + bottomHeight - topHeight) + 'px';
      }
    });
  }


  //エリアごとに処理
  areas.forEach(area => {
    //エリア内に追従要素が存在する場合のみ処理する
    const targets = area.querySelectorAll('.js-fixed-text__item');
    if(targets.length > 0) {
      /*
        要素が画面内に入ったら処理
      */
      const listener = {
        handleEvent: () => {
          checkFixed(targets,area);
        }
      };
      const observer = new IntersectionObserver(entries => {
        entries.forEach(entry => {
          if(entry.isIntersecting) {
            //画面に表示されているエリアのみスクロールイベント発火
            window.addEventListener('scroll', listener, {passive: true});

          } else {
            //画面外のときはスクロールイベント削除
            window.removeEventListener('scroll', listener, {passive: true});
          }
        });
      });
      //observer監視開始
      observer.observe(area);

      /*
        レスポンシブ対応
      */
      //表示エリアリサイズ監視 ResizeObserver
      const resizeObserver = new ResizeObserver(() => {
        checkFixed(targets,area);
      });
      //リサイズ監視開始
      resizeObserver.observe(area);
    }
  });
}

HTML

アニメーションするエリアに .js-fixed-text__area を追加します。

アニメーションさせたい要素は .js-fixed-text__item-area でグループ化します。
追従させるテキストに .js-fixed-text__item を追加し、
高さ確保用の .js-fixed-text__hidden 要素を追加してください。

<div class="js-fixed-text__area">
  <div class="js-fixed-text__item-area">
    <p class="js-fixed-text__hidden" aria-hidden="true">テキスト1</p>
    <p class="js-fixed-text__item">テキスト1</p>
  </div>
  <div class="js-fixed-text__item-area">
    <p class="js-fixed-text__hidden" aria-hidden="true">テキスト2</p>
    <p class="js-fixed-text__item">テキスト2</p>
  </div>
</div>

CSS

.js-fixed-text__item-area {
  position: relative;
}
.js-fixed-text__item {
  position: absolute;
  top: 0;
}
.js-fixed-text__item.is-fixed {
  position: fixed;
}
.js-fixed-text__hidden {
  opacity: 0;
  pointer-events: none;
}

関数の実行

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

areaFixedText();