【JavaScript】スクロールで1文字ずつ現れるアニメーション

【JavaScript】スクロールで一文字ずつ現れるアニメーション

今回は、多くのWebサイトで見られる「スクロールで可視範囲に入るとテキストが一文字ずつ現れるアニメーション」のソースコード紹介していきたいと思います。

完成形

まずは完成形を見てみましょう。
テキストが下から一文字ずつポコポコと現れるデザインのアニメーションをつくりました。

See the Pen スクロールで1文字ずつ表示するアニメーション by Haruna Fujii (@haruna-f) on CodePen.

手順の確認

アニメーションを実装する手順を確認しておきましょう。
今回は、スクロールで対象のテキストが画面に入ったら、テキストにクラスを付与してアニメーションを実行する仕組みで実装していきます。

手順

  1. アニメーションさせたいテキストを一文字ずつ分割する
  2. アニメーションさせたいテキストにCSSでスタイルを指定する
  3. スクロールで実行する処理を設定する

コードの解説

① 一文字ずつ<span>で囲む(JS:1〜13行目)

まずはアニメーションさせたいテキストを取得して、一文字ずつ<span>タグで囲んで下準備をします。
ソースコードと手順を確認していきましょう。

<div class="text js-observerTarget">Text 1</div>
// ①-1. アニメーションさせるテキストを取得
const texts = document.querySelectorAll('.text');

// ①-2. ループですべてのテキストにした準備処理を実行する
texts.forEach((text) => {
  // ①-2-1. 下準備を終えたテキストを格納する変数を準備する
  let html = '';
  // ①-2-2. テキストを一文字ずつ分割する
  const strs = text.textContent.split('');

  // ①-2-3. ループで一文字ずつ下準備の処理を実行する
  strs.forEach((str) => {
    // ①-2-3-1. 半角スペースを正規表現に置き換える
    const replacedStr = str.replace(/\s| /g, ' ');
    // ①-2-3-2. 一文字ずつ<span>で囲む
    html += `<span>${replacedStr}</span>`;
  });
  // ①-2-4. .text要素に<span>で囲んだテキストを挿入する
  text.innerHTML = html;
});

①−1. アニメーションさせるテキストを取得する

アニメーションさせたいテキストに「.text」というクラス名を付与し、querySelectorAll() メソッドで取得します。

①-2. ループですべてのテキストに下準備の処理を実行する

アニメーションさせるテキスト(.text要素)が複数あるので、forEach()メソッドを使ってすべての.text要素に同じ処理を実行ます。

①-2-1. 下準備を終えたテキストを格納する変数を準備する

最後に分割したテキストを格納するための変数「html」を準備しておきましょう。

①-2-2. テキストを一文字ずつ分割する

textContentプロパティで.text要素の文字列を取得し、.split()メソッドで文字を一文字ずつ分割して配列にして、変数「strs」に格納します。

①-2-3. ループで一文字ずつ下準備の処理を実行する

再びforEach()メソッドで、「strs」の中身を一文字ずつ取り出しながら必要な処理を実行します。

①-2-3-1. 半角スペースを&nbsp;に置き換える

「strs」の中身は、[‘T’, ‘e’, ‘x’, ‘t’, ‘ ‘, ‘1’]となっています。
半角スペースなどの空白文字は、文字列として認識されず非表示になってしまうので、<span>で囲む前に「&nbsp;」に置き換えておきましょう。

文字列の置き換えは、replace()メソッドを使って「String.replace( 置換対象の文字, 新たに置換する文字 );」で置き換えます。
今回は、置換対象の文字に「/\s|&nbsp;/g」(すべての空白文字または&nbsp;)を指定し、新たに置換する文字に「&nbsp;」を指定します。

置き換えた文字列は、変数「replacedStr」に格納します

①-2-3-2. 一文字ずつ<span>で囲む

①-2-3-1で準備したテキストを<span>タグで囲んで、②-2-1で準備しておいた変数「html」に+=演算子で追加していきます。

①-2-4. .text要素に<span>で囲んだテキストを挿入する

最後に<span>で囲んだテキスト(変数 html)をinnnerHTMLプロパティでもとの.text要素に返して下準備が完了です!

② CSSでアニメーションを作成する(CSS:5〜35行目)

今回は、スクロールで対象の要素が画面の中に入ったら.text要素に「.is-show」というクラス名を付与してアニメーションを実行するので、CSSでは.text要素にクラス名が追加される前と後のスタイルを指定しましょう。

.text {
  // ②-1. 「.is-show」クラスが付与される前
  overflow-y: hidden;
  font-size: 24px;
  span {
    display: inline-block;
    transform: translateY(100%);
  }
  &.is-show {
    // ②-1. 「.is-show」クラスが付与された後
    span {
      animation: pop 0.4s ease-out forwards;
      // ②-2. 一文字ずつアニメーションの実行を遅延する
      @for $i from 1 through 6 {
        &:nth-child(#{$i}) {
          $delay: $i * 0.1 + s;
          animation-delay: $delay;
        }
      };
    }
  }
}

@keyframes pop {
  0% {
    transform: translateY(100%);
  }
  90% {
    transform: translateY(-10%);
  }
  100% {
    transform: translateY(0);
  }
}

②-1. アニメーションを作成する

クラスを追加してアニメーションさせる.text要素にCSSでスタイルを指定します。
文字ごとのスタイルは、①で分割した<span>要素に指定していきます。

今回は下から飛び出すアニメーションをtranslateを使って作成しました。
文字の<span>要素にtranslateY(100%)を指定して親の.text要素の外側(下)に移動しておきます。
移動するだけでは文字が見えてしまうので.text要素にoverflow:heddenを指定して、初めは.text要素の外側にあるが文字(<span>要素)が見えない状態にしておきます。

@keyframe popで、<span>要素がもとの位置に戻るようにアニメーションを作成します。

「.is-show」クラスが付与されるとanimationが実行され、.text要素の内側に文字が移動して、文字が見えるようになる仕組みです。

この部分は表現したいアニメーションによって、措定するスタイルが変わります。

②-2. 一文字ずつアニメーションの実行を遅延する

②-1だけでは、すべての文字が同時に動き出してしまうので、一文字ずつ順番に表示するには一文字ごとにアニメーションを実行するタイミングを遅らせる必要があります。

今回は、animationプロパティを使っているのでanimation-delayを一文字ごとに指定していく必要があります。
「span:nth-child(1)、span:nth-child(2)…」というようにひとつひとつ指定することもできますが、大変なのでSassの@forディレクティブでループ処理をしながら指定しました。

Sass公式ドキュメント-@for

③ スクロールで実行する処理を設定する(JS:15〜38行目)

「スクロールで画面の中に要素が入ったらアニメーションを実行する」処理は、IntersectionObserver(交差オブザーバーAPI)を使って実装します。
対象要素と画面の交差が交差したら指定の処理を実行する仕組みです。
IntersectionObserverを設定する際には、①実行する関数②関数を実行する位置③監視対象の要素を準備しましょう。

const setObserver = () => {
  // ③-1. スクロールで実行する関数を作成
  const callback = (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        entry.target.classList.add('is-show');
      };
    });
  };

  // ③-2. 関数を実行する位置の設定
  const options = {
    rootMargin: '0px 0px -25% 0px'
  };
  // ③-3. IntersectionObserverのインスタンスを作成
  const observer = new IntersectionObserver(callback, options);
  // ③-4. 監視対象の要素を登録
  const targets = document.querySelectorAll('.js-observerTarget');

  targets.forEach((target) => {
    observer.observe(target);
  });
};
// ③-5. 設定したIntersectionObserverを実行
setObserver();

③-1. スクロールで実行する関数を作成する(callback)

if(entry.isIntersecting)の中に実行する処理を書いていきましょう。
今回は.text要素に「.is-show」クラスを付与する処理を書いています。

③-2. 関数を実行する位置を指定する(options)

関数を実行する位置を指定する(rootMargin)

optionsに関数を実行する位置を指定します。

rootMarginで上右下左4辺の画面からの距離を指定します。
指定したエリア内にターゲット要素が入ったら(交差したら)、関数が実行される仕組みです。

optionsではrootMarginの他にも、root(監視位置の基準となる要素)とthershold(ターゲットと基準要素の交差量)も指定できます。

③-3. IntersectionObserverのインスタンスを作成する

IntersectionObserverのインスタンスを作成して、第一引数に実行する関数(callback)第二引数に関数を実行する位置(options)を入力し、登録します。

③-4. 監視対象の要素を登録する

③-3で作成したインスタンスに.observe()メソッドで監視対象の要素(targets/target)を登録しましょう。
今回は監視対象の.js-observerTarget要素が複数あるので、.forEach()で使ってすべてのjs-observerTarget要素が登録します。

③-5. IntersectionObserverを実行

最後に③-1〜3で設定した関数「setObserver()」を実行したら、出来上がりです!

おわりに

今回は、スクロールで一文字ずつ現れるアニメーションを紹介しました。
Intersection Observerは、イベントリスナーよりもパフォーマンスが良く簡単にスクロールイベントを実装できるそうで活用できる場面が多くなりそうですね。
CSSを変更したりして、一文字ずつ現れるアニメーションを作ってみてくださいね。

最後までお読みいただき、ありがとうございました。