JavaScriptで一文字ずつspanで囲む方法【主に一文字ずつのアニメーションに使用】

文字を一文字ずつアニメーションさせようとする場合は一文字ずつspanタグで囲んでcssでアニメーションさせるのが一般的だと思いますが、そのアニメーションさせる文字数が5文字程度であれば手作業でspanタグで囲んでもいいのですが20や30文字と文字数が多くなるにつれてその作業は大変になります。

この記事では文字を span タグで手作業で囲む作業を JavaScript を使用して楽にする方法を紹介します。

前提

この記事では【簡易版】と【class構文版】を紹介します。
前提として以下のHTMLをサンプルとして使用します。

HTML

<p class="js-span-wrap-text">
  これは一文字ずつアニメーションする<br />
  もじのサンプルです
</p>

デモ

この記事で紹介するjsを使用してspanタグで一文字ずつ囲みCSSでのアニメーションデモ。

JavaScriptで一文字ずつspanで囲む方法(簡易版)

まずは簡易版を紹介します。

特徴

  • 特定の要素一つのみ対応
  • コピペですぐに使える
  • 改行コード削除
  • brなどのHTML要素は除外

JavaScript

以下のJavaScriptをコピペしてください。
それそれだけで動きます。

function spanWrap() {
  const target = document.querySelector('.js-span-wrap-text');
  const nodes = [...target.childNodes];

  let spanWrapText = ""

  nodes.forEach((node) => {
    if (node.nodeType == 3) {//テキストの場合
      const text = node.textContent.replace(/\r?\n/g, '');//テキストから改行コード削除
      //spanで囲んで連結
      spanWrapText = spanWrapText + text.split('').reduce((acc, v) => {
        return acc + `<span>${v}</span>`
      }, "");
    } else {//テキスト以外
      //<br>などテキスト以外の要素をそのまま連結
      spanWrapText = spanWrapText + node.outerHTML
    }
  })

  target.innerHTML = spanWrapText
}

spanWrap();

これだけでdocument.querySelector()に指定した要素(今回は.js-span-wrap-text)の中のテキストがspanで囲まれます。

実行結果

実行すると以下のように自動で全てのテキストをspanで囲ってくれます。

<p class="js-span-wrap-text"><span>こ</span><span>れ</span><span>は</span><span>一</span><span>文</span><span>字</span><span>ず</span><span>つ</span><span>ア</span><span>ニ</span><span>メ</span><span>ー</span><span>シ</span><span>ョ</span><span>ン</span><span>す</span><span>る</span><br><span>も</span><span>じ</span><span>の</span><span>サ</span><span>ン</span><span>プ</span><span>ル</span><span>で</span><span>す</span></p>

解説

要点のみを簡単に解説します。

要素のノードタイプごとに分割

const nodes = [...target.childNodes];

要素をノードタイプで分割します。

HTMLは「1」
テキストは「3」
を返します。

テキストかHTMLで処理を分岐

let spanWrapText = ""
nodes.forEach((node) => {
  if (node.nodeType == 3) {//テキストの場合
    const text = node.textContent.replace(/\r?\n/g, '');//テキストから改行コード削除
    //spanで囲んで連結
    spanWrapText = spanWrapText + text.split('').reduce((acc, v) => {
      return acc + `<span>${v}</span>`
    }, "");
  } else {//テキスト以外
    //<br>などテキスト以外の要素をそのまま連結
    spanWrapText = spanWrapText + node.outerHTML
  }
})
テキストの場合

テキストから空白を削除しspanで囲んで、変数spanWrapTextに追加します。
※空白を削除しないとカラのspanができてしまうため。

テキスト以外(HTML)

テキスト以外(<br>)などはそのまま変数spanWrapTextに連結します。

元テキストを差し替え

target.innerHTML = spanWrapText

変数spanWrapTextの中身を挿入。

JavaScriptで一文字ずつspanで囲む方法(ES6版(class構文))

続いてES6版(class構文)の場合を紹介します。

特徴

  • 複数の要素にも対応可能
  • jQueryオブジェクトや".js-span-wrap-text"のような文字列セレクターにも対応
  • 空白削除
  • brなどのHTML要素は除外

JavaScript

以下のJavaScriptをコピペしてください。

class SpanWrap {
  constructor(target) {

    this.target = this.convertElement(target);
    this.nodes = [...this.target.childNodes];

    this.convert();
  }

  convert() {

    let spanWrapText = ""

    this.nodes.forEach((node) => {
      if (node.nodeType == 3) {//テキストの場合
        const text = node.textContent.replace(/\r?\n/g, '');//テキストから改行コード削除
        //spanで囲んで連結
        spanWrapText = spanWrapText + text.split('').reduce((acc, v) => {
          return acc + `<span>${v}</span>`
        }, "");
      } else {//テキスト以外
        //<br>などテキスト以外の要素をそのまま連結
        spanWrapText = spanWrapText + node.outerHTML
      }
    })

    this.target.innerHTML = spanWrapText

  }

  //jQueryオブジェクトや文字列セレクターを変換
  convertElement(element) {
    if (element instanceof HTMLElement) {
      return element
    }
    if (element instanceof jQuery) {
      return element[0]
    }
    return document.querySelector(element);
  }

}

簡易版との違いは複数の要素に対しての処理を加えているだけです。

実行

new SpanWrap(".js-span-wrap-text");

複数の要素に適用する

以下のようなHTMLの場合

<p class="js-span-wrap-text">
  これは一文字ずつアニメーションする<br />
  もじのサンプルです
</p>
<p class="js-span-wrap-text">
  サンプルテキストです
</p>

forEachなどでループして実行することで複数の要素に適用可能です。

javascript

const targets = [...document.querySelectorAll(".js-span-wrap-text")]

targets.forEach( (target) => {
  new SpanWrap(target);
})

jQuery

$(".js-span-wrap-text").each(function(i, v){
  new SpanWrap(v);
})

参考にさせていただいた記事

テキストをタイピング風に一文字ずつ表示する

最後に

色々なことに言えますが、こういった同じ処理の繰り返しはプログラムを組んである程度自動化したほうが良いと思います。
今回のspanで囲む処理は元のテキストに変更があったとしてもテキストを差し替えるだけで新しいテキストにspanで囲む処理を適応されますので修正なども楽になると思います。