【JavaScript】行ごとのスライドアップアニメーションを実装する

Category: css / js

行ごとにテキストがスライドアップするアニメーションをJavaScriptで実装したサンプルです。
Web Animations APIとIntersectionObserverを使っています。


See the Pen
Text slides up line by line Vanilla JS (Web Animations API)
by Kazuma Sakata (@sakata-kazuma)
on CodePen.


JS

//行ごとにスライドアップアニメーションさせる
const slideUpRowTexts = document.querySelectorAll('.js-slide-up-row');
if(slideUpRowTexts.length > 0) {

  //スライドアップアニメーション関数
  const textSlideUpAutoAnime = (target) => {
    //行ごとにアニメーション
    const texts = target.querySelectorAll('.js-slide-up-row__text');
    texts.forEach((text, index) => {
      //Web Animations APIでスライドアップアニメーション
      text.animate(
        {
          transform: 'translateY(0)',
        },
        {
          fill: 'forwards', //アニメーション後に値を維持する
          duration: 1600, //アニメーション時間
          easing: 'cubic-bezier(0.62,0.05,0.01,0.99)', //イージング
          delay: index * 120 //行ごとの遅延時間
        }
      );
    });
  }

  /*
    ■observerセットアップ
      ・初回ページロード時:画面全体を検知
      ・ロード後:rootMarginの値を変更
  */
  let observer = new IntersectionObserver(entries => {
    //監視対象の総数を取得
    const total = entries.length - 1;
    entries.forEach((entry, index) => {
      const target = entry.target;

      //アニメーションフラグ設定
      target.isFinished = false;

      //要素が画面内にある場合はアニメーション発火
      if(entry.isIntersecting) {
        textSlideUpAutoAnime(target);
        target.isFinished = true;
      }

      //要素の監視を解除
      observer.unobserve(target);

      //一通り監視し終わったら スクロール用のobserverをセットアップ
      if(total === index) {
        let observerScroll = new IntersectionObserver(entries => {
          entries.forEach(entry => {
            const target = entry.target;

            //要素が画面内にある場合はアニメーション発火
            if(entry.isIntersecting) {
              textSlideUpAutoAnime(target);
              //要素の監視を解除
              observerScroll.unobserve(target);
            }
          });

        //検知範囲を設定
        }, {rootMargin: '-20% 0px -20% 0px'});

        //アニメーションが終了していない要素のみ監視対象とする
        slideUpRowTexts.forEach(slideUpRowText => {
          if(!slideUpRowText.isFinished) {
            observerScroll.observe(slideUpRowText);
          }
        });
      }
    });
  });

  const setUpText = (slideUpRowText, baseText) => {
    //表示領域確保用の透明テキスト
    let html = '' + baseText + '';

    //表示領域全体の高さを取得
    const textHeight = slideUpRowText.clientHeight;

    //1行の高さを取得
    const styles = getComputedStyle(slideUpRowText);
    let lineHeight = styles.lineHeight;
    if(lineHeight === 'normal') {
      //line-heightが未設定だった場合は1行の高さをチェックする
      slideUpRowText.insertAdjacentHTML('beforeend', '');
      lineHeight = slideUpRowText.querySelector('.js-slide-up-row__checker').clientHeight;
    } else {
      lineHeight = parseFloat(lineHeight);
    }

    //現在何行なのか調べる
    const row =  textHeight / lineHeight;

    //アニメーション用 位置変更設定
    let translateY = textHeight;
    //アニメーションが終了していたら位置変更しない
    if(slideUpRowText.isFinished) {
      translateY = 0;
    }
    //行ごとにclip-pathを設定する
    for (let i = 0; i < row; i++) {
      const insetTop = lineHeight * i;
      let insetBottom = textHeight - (lineHeight * (i + 1));
      if(insetBottom < 0) {
        insetBottom = 0;
      }
      html += '';
    }

    //中身をリセット
    slideUpRowText.textContent = '';
    //定義したHTMLを反映
    slideUpRowText.insertAdjacentHTML('beforeend', html);
    //セットアップ完了classの追加
    slideUpRowText.classList.add('is-setup');
  }

  //初回セットアップ
  slideUpRowTexts.forEach(slideUpRowText => {
    //ベーステキストの取得
    const baseText = slideUpRowText.innerHTML;

    //アニメーション用マークアップ
    setUpText(slideUpRowText,baseText);

    //observer初回監視開始
    observer.observe(slideUpRowText);

    /*
      レスポンシブ対応
    */
    //表示エリアリサイズ監視 ResizeObserver
    const resizeObserver = new ResizeObserver(() => {
      //アニメーション用マークアップ再定義
      setUpText(slideUpRowText, baseText);
    });
    //リサイズ監視開始
    resizeObserver.observe(slideUpRowText,slideUpRowText);
  });
}

HTML

アニメーションさせたいテキスト要素に .js-slide-up-row を追加します

<p class="js-slide-up-row">
  行ごとにスライドアップするテキストアニメーションです。
</p>

CSS

.js-slide-up-row {
  position: relative;
  opacity: 0;
  width: fit-content;
}
.js-slide-up-row.is-setup {
  opacity: 1;
}
.js-slide-up-row__base {
  opacity: 0;
}
.js-slide-up-row__line,
.js-slide-up-row__checker {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  pointer-events: none;
}
.js-slide-up-row__text {
  display: block;
}
.u-visually-hidden {
  overflow: hidden;
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

Category : css / js