- 概要
- 全文コード
- 実装方法:1. 表示領域を制限して全文はスクロールして見られるようにする
- 実装方法:2. テキストは一行ずつ表示して、表示領域を超える場合は自動的にスクロールするようにする
- 実装方法:3. テキストを左からワイプ表示する
- 実装方法:4. テキスト内にhtmlタグが含まれる場合、そのままの形で表示する
- 以上
ここでは以下のようにテキストを一行ずつ左から右に表示するJavaScriptコードを載せています。

テキストを1文字ずつ表示する方法自体は結構他でも見つかりましたが、文字単位ではなく全体をスムーズに表示しつつ、htmlタグを維持する方法は見つからなかったのでここに書いています。
元々以下のことがやりたく実装したものです。
やりたかったこと
- VRoidナビゲーターのセリフが長すぎても画面を大幅に圧迫しないように、表示領域を制限して全文はスクロールして見られる
- 手動スクロールしなくても全文が読めるように、テキストは一行ずつ表示して、表示領域を超える場合は自動的にスクロールする
- テキストは一行ずつ表示するが、その際左の文字から徐々に表示する。また、テキストは一文字ずつなどの、文字単位での表示ではなくテキスト全体を一枚の画像のように見なして表示させる(=左からのワイプ)
- テキスト内にhtmlタグが含まれる場合、当然それはそのままの形で表示する
やる前は簡単に実装できると思ったのですが、「3」と「4」が思ったより難しく、想定よりもかなり時間がかかりました。
本記事はどのように実装したかの説明がメインではありますが、以下コードが実際に作成したモノです。
JavaScript / CSS全文コード
※こちらは返礼特典のコンテンツです
こちらのJavaScriptとCSSコードを読み込んで、TextShowAnimation.fadeLeftToRight(showElement, htmlText)メソッドを呼び出せば、一行ずつ表示されるようになると思います。
VRoidナビゲーターは(非表示にしない限りは)常に画面の下側に出ているものです。
なので、画面領域を大幅に圧迫するのを避ける必要があり、今までは長すぎるセリフを言わせないようにしてそれを制御していたのですが、最近は長すぎるセリフを登録できないのが重荷になってしまいました。
ということで長すぎるセリフを登録しても表示領域を圧迫しないように明示的に表示領域を制限し、全文は手動スクロールすることで見られるようにしました。
実装方法は以下です。
コード(CSS)
これはCSSの基本的なところなので難しいところは無いかもしれませんが一応……。
max-height: 180px;
要素の最大の高さ。この高さを超える場合はここで指定した値に高さが制限される。これやheightで高さを制限しないとoverflowが意味を為さない。
overflow-y: scroll;
要素の中身(=セリフ)が吹き出しの高さを超えた場合の処理定義。今回はスクロールするようにしている。
scrollbar-color: transparent transparent;
scrollbar-width: thin;
スクロールバーが出ないように(目立たないように)する。
overflow-wrap: break-word;
word-break: break-all;
テキストを自動で折り返すようにする。これは後々の処理のために定義しているところが大きい。
上にある通り、画面領域を圧迫しないように表示領域を制限して、全文を読む場合にはユーザが手動でスクロールするようにしています。
しかしこれだとわざわざ手動でスクロールしないといけなくなります。
VRoidナビゲーターは基本的にサイト内ニュースをながら見するための機能なので、このような手動操作は基本的に想定していません。
なので原則スクロールは自動でするようにします。
実装方法ですが、後述の処理で「テキストを一行ずつ表示」するようにするので、その処理の中に以下のコードを入れます。
コード(JS)
これも難しいところはないかもしれませんが一応……。
elementがテキストを表示する領域です。
element.scrollTopが縦方向のスクロール位置で、例えば0を設定すると一番上にスクロールされます。
element.scrollHeightがスクロール可能な領域全体の高さです。
element.scrollHeight = element.scrollTopとすることで、自動的に最下部にスクロールします。
実際には最下部よりさらに下にスクロールする気がしますが、最下部より大きい値を設定しても最下部ちょうどの値になるように丸められるそうです。
ちなみにoverflow-y: scrollになっていない場合はこの処理は無視されます。エラー回避の必要はありません。
前述の自動スクロール処理があるとは言っても、全行のテキストをまとめた出したら意味が無いのでテキストは一行ずつ表示する必要があります。
このとき10秒置きなどで各行をパッと一瞬で表示しても問題はないですが、より自然な表示になるように、各行は左から右へワイプのように表示します。
まずワイプするアニメーションですが、これはCSSのマスクを使っています。
コード(CSS)
mask-image: linear-gradient(to right, black 0%, black 50%, transparent 100%);でグラデーションした画像をマスクとして、そのマスクを幅0から200%まで徐々に広げることでワイプ表示を実現しています。
200%にしているのはマスク画像の右半分がグラデーションで透過されてしまっているので、100%にしてしまうと、テキストも同様にグラデーションされたままワイプアニメーションが完了してしまうためです。
animation-duration: 8s;として、8秒かけてテキストが表示されるようにしていますが、これでは画面幅に応じて表示速度が変わってしまいます(画面幅が狭いと露骨に表示速度が遅くなります)。
なので、animation-durationはJavaScript内で画面幅に応じて値を変える必要があります(後述)。
前項のCSSアニメーションとマスク画像を使用して表示する方法ですが、この方法だと要素全体が左から右に表示されます。
すなわちテキストが一行ずつではなく、すべての行が同時に左から右にワイプ表示されてしまいます。
なので各行をdivで分けて、一つ一つの行に対してワイプアニメーションを適用するようにしています。
「各行をdivで分けて、一つ一つの行に対してワイプアニメーションを適用する」ために以下のプロセスで実装しています。
1. 各行の文字数を割り出す
2. 割り出した文字数から、実際に各行ごとにテキストを表示する(前項のCSSアニメーションを適用するため各行ごとにdivで囲う)
以下は「1. 各行の文字数を割り出す」コードです(「2」の処理は、次項と大きく関わりがあるため次項に回します)。
コード(JS)
コード(CSS)
getElementInfoメソッドで各行の文字数を計算しています。
計算とは言いますが、ダミーの要素に実際にテキストを表示して、そこから各行の文字数を割り出しています。
getBoundingClientRectメソッドを使えば各文字の表示座標を取得できるので、その表示座標から何行目に表示されているか分かります。
こうして各行の文字数が分かれば、あとは元のテキストからその文字数分を各行に表示すればよいだけなので簡単です。
「ret.width = element.getBoundingClientRect().width;」は、画面幅を取っています。これは、前項のアニメーション速度の計算に必要なので取得しています。
まぁこれはそのままですね。
VRoidナビゲーターには特にaタグがよく含まれており、これを削除してただのプレーンテキストにするのは絶対NGです。

なのでhtmlタグを維持したまま画面上に表示する必要があります。
まず最初に各行の表示用html文を組み立て、その後実際にそのhtml文を表示する、2段階の構成に分けています。
前項のCSSアニメーションをするために、各行をdivで囲う必要があるのですが、そうすると例えばaタグが1行目から2行目にわたって表示されている場合、一度1行目でaタグを閉じて2行目で再びaタグを設置しないといけません。
例:aタグが複数行に跨る場合、aタグを分割しないといけない
これを実現しているのが以下のコードです。
コード(JS)
appendNodeメソッドでhtml文をNode単位に分けて、タグを追加しつつ各行にappendCharメソッドで文字を追加していっています。
appendNodeメソッドでhtml文を各Nodeに分けて、単なるテキストノードはappendCharメソッドで文字を追加し、aタグなどで囲われているノードはそのタグを追加した後にappendCharメソッドで文字を追加するようにしています。
ざっくりとして説明は以上で、コード内にコメントを記載しているので細かい内容はそこをご確認ください。
前項のsplitLinesメソッドで各行のhtml文は組み立てられたのであとはそれを表示するだけです。
以下はanimateLinesメソッドを再帰的に呼び出して1行ずつテキストを表示しています。