経緯
テキストアニメーションなどでよく使用される、文字を一文字ずつspan要素で囲むアレについて、文字の前後に余白ができたり空白が削除されてしまったりと上手くいかない場面が多く、型が定まっていなかったため、これを機会に整理したいと考えました。
これまで使用していたもの
JavaScript
const txtTitle = document.querySelector('.txtTitle');
const text = txtTitle.textContent;
let result = '';
for (let i = 0; i < text.length; i++) {
if (text.charAt(i) === ' ') {// charAt: 文字列の指定したインデックスにある文字を返す
result += '<p><span> </span></p>';
} else {// 空白要素以外は通常通りspanで囲む
result += '<p><span>' + text.charAt(i) + '</span></p>';
}
}
txtTitle.innerHTML = result;
結果
<!-- span挿入前(ソースコード) -->
<h1 class="txtTitle">
SAMPLE TEXT
</h1>
<!-- span挿入後(DOM) -->
<h1 class="txtTitle">
<p><span></span></p>
<p><span> </span></p>
<p><span> </span></p>
<p><span> </span></p>
<p><span> </span></p>
<p><span> </span></p>
<p><span> </span></p>
<p><span>S</span></p>
<p><span>A</span></p>
<p><span>M</span></p>
<p><span>P</span></p>
<p><span>L</span></p>
<p><span>E</span></p>
<p><span> </span></p>
<p><span>T</span></p>
<p><span>E</span></p>
<p><span>X</span></p>
<p><span>T</span></p>
<p><span></span></p>
<p><span> </span></p>
<p><span> </span></p>
<p><span> </span></p>
<p><span> </span></p>
</h1>
問題点
見たまんまですが、span要素が余分に生成されています。
原因はHTMLの書き方で、h1タグとテキスト文に改行とtabキーでスペースを挿入していることから、その部分にもspanwrapの処理が入ってしまっている状況です。
また、「ワイプ風に出現」、「下からパラパラと出現」、など同ページで複数のアニメーションを再現することも考慮して、span要素にクラス名を挿入するなどの工夫もしたいところです。
改善案
JavaScript
function splitText(els, className) {//--①
els.forEach((el) => {//--②
const chars = el.innerText.trim();// --③
let result = "";
for (let c of chars) { // --④
c = c.replace(" ", " ");// --⑤
result += `<p><span class="${className}">${c}</span></p>`;// --⑥
}
el.innerHTML = result;
});
}
const txtTitle = document.querySelectorAll('.txtTitle');
const className = '';// span要素に入れたいclass名を書く
splitText(txtTitle, className);
変更箇所はざっくり以下の通りです。
①引数を2つ取得→els(オブジェクトの入れ物)/className(囲むspanタグに挿入するクラス名)
②テキスト分割したい要素が複数あることを考慮してループ(forEach)を構成
③HTMLの改行やスペースで生まれる前後の余白を削除(trim)
④上長だったfor構文を修正(for of構文)
⑤文字間に空白が存在する場合は置き換える(replace)
⑥innerHTMLで挿入するspanタグに引数classNameを追加
結果
<h1 class="txtTitle">
<p><span class="">S</span></p>
<p><span class="">A</span></p>
<p><span class="">M</span></p>
<p><span class="">P</span></p>
<p><span class="">L</span></p>
<p><span class="">E</span></p>
<p><span class=""> </span></p>
<p><span class="">T</span></p>
<p><span class="">E</span></p>
<p><span class="">X</span></p>
<p><span class="">T</span></p>
</h1>
問題点は概ね解消されました。
今後を見据えてもう少し使いやすく
上記は関数で記述しましたが、より柔軟性を持たせるためにクラス宣言を用いた記述に修正します。
※クラス宣言についてはこちらのサイトを参考にしました。
JavaScript
class SplitText { // クラス宣言
constructor(els,className) { //定義したクラスからオブジェクトを生成
this.els = els;//NodeListを渡す
this.className = className
this.els.forEach((el) => {
this.chars = el.innerText.trim();
this.result = "";
el.innerHTML = this._splitText();
})
}
//テキスト分割のメソッドを定義
_splitText() {
for (let c of this.chars) {
c = c.replace(" ", " ");
this.result += `<p><span class="${this.className}">${c}</span></p>`;
}
return this.result;
}
}
const txtTitle = document.querySelectorAll('.txtTitle');
const className = '';
const splitText = new SplitText(txtTitle, className);
結果は同じですが、例えばSplitTextクラスに複数のメソッドが定義されていて、_splitTextメソッドのみをダイレクトに呼び出したいといった場合などに便利、、という認識です。
クラス宣言を使用するのは初でしたが、関数と違い、引数に対してもthisで逐一定義するあたりがまだ慣れません。勉強します、、
.
.
参考サイト
・JavaScriptのクラス(class)を理解する
・Zenn JS汎用クラスとかを作って貯めていきたい
・【javaScript】for…in、for…of、forEachの違いと用途