1. HOME
  2. JavaScript
  3. JavaScriptで文字列を1文字ずつspanタグで囲む方法:CSSアニメーションへの応用
JavaScript - 2022-01-23

JavaScriptで文字列を1文字ずつspanタグで囲む方法:CSSアニメーションへの応用

Webサイトに印象的なテキストアニメーションを実装したいと考えたことはありませんか? 例えば、文字が一つずつ浮かび上がってきたり、カーソルを合わせると文字が個別に反応したりするようなエフェクトです。

このような「一文字ずつ」のアニメーションを実現するための基本となるのが、JavaScriptを使って文字列を個別の<span>タグで囲むテクニックです。この記事では、その基本的な方法から、よりモダンで効率的な書き方、そして実際にCSSと組み合わせたアニメーションの実例までを詳しく解説します。

なぜ文字を<span>で囲むのか?

HTMLの<p>タグや<h1>タグは、通常、その中のテキスト全体を一つの塊として扱います。そのため、CSSでスタイルやアニメーションを適用すると、テキスト全体に一度に適用されてしまいます。

<p class="title">Hello World</p>

このままでは、「H」「e」「l」「l」「o」…といった一文字ずつに異なる動きをさせることはできません。

そこで、JavaScriptを使ってHTML構造を動的に以下のように変換します。

<p class="title">
  <span>H</span><span>e</span><span>l</span><span>l</span><span>o</span> 
  <span>W</span><span>o</span><span>r</span><span>l</span><span>d</span>
</p>

このように各文字が独立した<span>タグ(インライン要素)で囲まれることで、CSSのセレクタ(例:.title span)を使って、それぞれの文字を個別にスタイリングしたり、アニメーションの遅延(animation-delay)を設定したりすることが可能になるのです。

実装方法 (class対応)

ID (#id) ではなくクラス (.class) を使うことで、同じ効果を複数の要素に適用でき、より再利用性の高いコードになります。

ここでは、ES6以降のモダンなJavaScript (mapjoin) を使い、パフォーマンスを意識した効率的なコードを紹介します。

HTML

<p class="js-text-split">Hello World!</p>
<h1 class="js-text-split">This is also animated.</h1>

JavaScript

// 1. 対象となるクラスを持つすべての要素を取得
const elements = document.querySelectorAll('.js-text-split');

// 2. 取得した各要素に対して処理を実行
elements.forEach(element => {
  const text = element.textContent;

  // 3. テキストを1文字ずつの配列に変換し、spanタグで囲む
  //    空白文字はアニメーション対象外とするため、そのまま半角スペースとして扱う
  const newHtml = text.split('').map(char => {
    return char.trim() === '' ? ' ' : `<span>${char}</span>`;
  }).join('');

  // 4. 生成したHTMLで要素の中身を一度に書き換える
  element.innerHTML = newHtml;
});

コードの解説

  • document.querySelectorAll('.js-text-split'): 指定したクラス名を持つすべての要素をリスト(NodeList)として取得します。
  • elements.forEach(...): 取得した要素のリストをループし、一つ一つの要素に対して分割処理を実行します。これにより、同じクラスを持つ要素すべてに同じ効果を適用できます。
  • text.split('').map(...).join(''): この一連の処理がモダンな実装の核です。
    • split(''): 文字列を1文字ずつの配列に分解します。
    • map(...): 配列の各文字を<span>タグで囲んだ新しい配列を生成します。三項演算子 char.trim() === '' ? ' ' : ... を使い、文字が空白の場合は<span>で囲まず、そのまま半角スペースにしています。
    • join(''): mapで生成された配列(例: ['<span>H</span>', '<span>e</span>', ...])を、区切り文字なしで結合し、単一のHTML文字列を生成します。
  • element.innerHTML = newHtml;: DOMの書き換えをループの外で一度だけ行います。何度もinnerHTMLを書き換える古い方法に比べ、ブラウザの再描画が一度で済むため、パフォーマンスが大幅に向上します。

実用例:JavaScriptで動的にアニメーション遅延を設定する

文字数が増えるたびにCSSで:nth-of-typeを書き足すのは大変です。JavaScriptを使えば、この遅延設定も自動化できます。

CSS

/* アニメーションの定義 */
@keyframes text-in {
  0% {
    transform: translateY(1em);
    opacity: 0;
  }
  100% {
    transform: translateY(0);
    opacity: 1;
  }
}

.js-text-split span {
  display: inline-block; /* transformを適用するため */
  opacity: 0; /* 初期状態は非表示 */
  animation: text-in 0.5s forwards; /* アニメーションを適用 */
}

JavaScript (改良版)

document.querySelectorAll('.js-text-split').forEach(element => {
  const text = element.textContent;
  element.innerHTML = ''; // 中身を空にする

  text.split('').forEach((char, index) => {
    const span = document.createElement('span');
    span.textContent = char;

    // アニメーションの遅延を0.05秒ずつずらして設定
    span.style.animationDelay = `${index * 0.05}s`;

    element.appendChild(span);
  });
});

変更点のポイント

  • この改良版では、innerHTMLを一気に書き換える代わりにdocument.createElementappendChildを使用しています。これにより、各<span>をDOMノードとして直接操作できます。
  • span.style.animationDelay = ...: ループのインデックス (index) を利用して、各span要素のstyle属性に直接animation-delayを書き込んでいます。
  • index * 0.05の部分で遅延時間を計算しており、0秒、0.05秒、0.1秒…と自動的にずれていくため、CSSで:nth-of-typeを大量に記述する必要がなくなります。

まとめ

一見シンプルですが、JavaScriptで文字列を<span>タグに分解するテクニックは、アニメーションを取り入れたサイトでよく使われます。

クラスセレクタの活用、パフォーマンスを意識したモダンな書き方、そしてJavaScriptによる動的なスタイル設定までを理解し、ぜひあなたのWebサイト制作に活かしてみてください。