はじめに
モダンなサイトでよく見かける「テキストを1文字ずつ表示するアニメーション」の実装方法を解説します。
実装
サンプル
繰り返し動作を確認したい場合は右下の「Rerun」ボタンをクリックしてください。
HTMLの実装
コンテナーの中に二行のテキストを入れています。行数は好きなだけ増やすことができます。
HTML
<div class="container">
<div class="line">Lorem ipsum dolor sit amet, consectetur adipisicing elit,</div>
<div class="line">sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>
</div>
Slim
.container
.line Lorem ipsum dolor sit amet, consectetur adipisicing elit,
.line sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
CSSの実装
まずはCSSの全文を掲載します。
後述するJavaScriptの実装でテキストの文字一つひとつをspan
タグで囲み、それらに対して順番にアニメーションディレイを設定します。このとき、Sassの@for
による繰り返し処理が必要なので通常のCSSではなくSassで記述してください。
SCSS
.container {
.line {
overflow: hidden;
span {
display: inline-block;
transform: translateY(2rem);
animation: slideText 1s forwards;
}
}
.line:nth-of-type(1) {
@for $i from 1 through 100 {
span:nth-of-type(#{$i}) {
animation-delay: #{$i * .02}s;
}
}
}
.line:nth-of-type(2) {
@for $i from 1 through 100 {
span:nth-of-type(#{$i}) {
animation-delay: #{.25 + $i * .02}s;
}
}
}
}
@keyframes slideText {
100% { transform: translateY(0); }
}
一つひとつ分解して説明していきます。
SCSS
.line {
overflow: hidden;
}
要素内容のはみ出し部分を非表示にします。
SCSS
span {
display: inline-block;
transform: translateY(2rem);
animation: slideText 1s forwards;
}
@keyframes slideText {
100% { transform: translateY(0); }
}
初期表示位置をY軸方向に2rem
(2文字分)ずらします。transform
はインライン要素(span
タグのデフォルト)には効かないのでインラインブロック要素にします。 slideText
という表示位置を戻すアニメーションを1秒かけて実行します。
SCSS
.line:nth-of-type(1) {
@for $i from 1 through 100 {
span:nth-of-type(#{$i}) {
animation-delay: #{$i * .02}s;
}
}
}
.line:nth-of-type(2) {
@for $i from 1 through 100 {
span:nth-of-type(#{$i}) {
animation-delay: #{.25 + $i * .02}s;
}
}
}
一行目と二行目でスタイルを分けています。Sassの@for
を使ってspan
タグに0.02秒ずつずらしてアニメーションディレイを設定します。一個目は0.02秒、二個目は0.04秒、三個目は0.06秒...という具合です。二行目は一行目より少しずらして開始させるため、さらに0.25秒足しています。
JavaScriptの実装
まずはJavaScriptの全文を掲載します。指定した要素のテキストを1文字ずつspan
タグで囲む処理を実装します。
JavaScript
/**
* @class SpanWrapText
* @description テキストを1文字ずつspanで囲む
* @argument target 対象のテキストを含む要素
*/
class SpanWrapText {
constructor(target) {
this.target = target;
this.nodes = this.target.childNodes;
this.convert();
}
/**
* @function convert
* @description テキストを1文字ずつspanで囲む
*/
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((accumulator, currentValue) => {
currentValue = currentValue.replace(' ', ' ');
return accumulator + `<span>${currentValue}</span>`;
}, '');
} else { // テキスト以外の場合
// brなどの要素はそのまま連結
spanWrapText = spanWrapText + node.outerHTML;
}
});
this.target.innerHTML = spanWrapText;
}
}
document.addEventListener('DOMContentLoaded', event => {
document.querySelectorAll('.container .line').forEach(element => {
new SpanWrapText(element);
});
});
一つひとつ分解して説明していきます。
JavaScript
constructor(target) {
this.target = target;
this.nodes = this.target.childNodes;
this.convert();
}
SpanWrapText
クラスのコンストラクターでは、対象のテキストを含む要素から子ノードを取得し、convert
メソッドを呼び出します。
JavaScript
this.nodes.forEach((node) => {
if (node.nodeType == 3) { // テキストの場合
// 改行コードを削除
const text = node.textContent.replace(/\r?\n/g, '');
// spanタグで囲んで連結
spanWrapText = spanWrapText + text.split('').reduce((accumulator, currentValue) => {
currentValue = currentValue.replace(' ', ' ');
return accumulator + `<span>${currentValue}</span>`;
}, '');
} else { // テキスト以外の場合
// brなどの要素はそのまま連結
spanWrapText = spanWrapText + node.outerHTML;
}
});
this.target.innerHTML = spanWrapText;
取得したノードの種類を判定し、テキストとそれ以外で処理を分けます。ノードの種類がテキストの場合は1文字ずつspan
タグで囲み再連結します。ノードの種類がテキスト以外の場合は何もせずそのまま連結します。
ノードの種類については以下を参照してください。
JavaScript
document.addEventListener('DOMContentLoaded', event => {
document.querySelectorAll('.container .line').forEach(element => {
new SpanWrapText(element);
});
});
最後に、対象のテキストを含むすべての行に対してSpanWrapText
クラスを呼び出します。
まとめ
複雑だと思われる「テキストを1文字ずつ表示するアニメーション」ですが、やっていることは1文字ずつspan
タグで囲み少しずつずらしてアニメーションを実行しているだけです。JavaScriptの処理を見て「うわ、長い...」と思われるかもしれませんが、1/3くらいはコメントなので実際の処理はそれほど長くありません。
まずはコピペでも結構ですので、本記事を参考にして実装していただければと思います。