スポンサーリンク
概要
前回はTypeScript(HTML5 + JavaScript)を用いてサウンドノベル風の文章表示を行ってみました。今回は引き続き機能を拡張していきます。
今回はキャレットと自動改ページを追加していきます。
スポンサーリンク
今回やりたいこと
キャレットの表示
下図のように文章の末尾に点滅するキャレットを表示させます。
自動改ページ
文章を表示する際に、現在のページに全ての文字を表示しきれない場合には先に改ページを行ってから文章を表示するようにします。
動くサンプルはこちらになります。
スポンサーリンク
スポンサーリンク
キャレットの表示
キャレットの表示する際には以下のことを考慮します。
1.キャレットの表示位置の取得(次の位置か次の行かなど判定します。)
2.キャレットの点滅(タイマーを用いて表示、非表示を繰り返す)
clearRectで消す際になぜかブラウザによってはキャレットが残ってしまう?のでy座標にオフセットを無理やり追加しています。
ちょっとすっきりしませんが、解決しなかったのであきらめました・・・。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
//入力受付中のキャレットを表示 private showCaret() { //自動改行で無い場合には次の位置に表示もしくは改行する if (!this.isAutoNewLine) { //次の文字を表示した場合の幅を取得 var currentWidth = this.canvas2D.measureText( this.sentences[this.currentPage][this.currentRow] + this.caretNormal).width; //最大幅を超えたら改行 if (currentWidth > this.maxWidth) { //改行か改ページか判定 this.changeRowOrPage(); } } //キャレット表示位置を取得 var x = this.canvas2D.measureText(this.sentences[this.currentPage][this.currentRow]).width;; var y = this.rowHeight * (this.currentRow + 1); //キャレットを描画 var isShowCaret = true; this.canvas2D.fillText(this.caretNormal, x, y); //キャレット点滅 this.caretTimer = setInterval(() => { if (isShowCaret) { this.canvas2D.fillText(this.caretNormal, x, y); isShowCaret = false; } else { this.hideCaret(x, y); isShowCaret = true; } }, this.caretBlinkIntervall); } //キャレットを隠す private hideCaret(x :number, y: number) { var craretmetrix = this.canvas2D.measureText(this.caretNormal); //なぜかブラウザによってキャレットがうまく消えないので5オフセット追加 this.canvas2D.clearRect(x, y - this.rowHeight +5, craretmetrix.width, this.rowHeight); } //キャレットを消す private clearCaret() { //点滅を消す clearInterval(this.caretTimer); //キャレット表示位置を取得 var x = this.canvas2D.measureText(this.sentences[this.currentPage][this.currentRow]).width;; var y = this.rowHeight * (this.currentRow + 1); this.hideCaret(x, y); } |
自動改ページ
自動改ページするためには以下を考慮する必要があります。
1.現在の表示位置を取得
2.これから描画する文字列を描画した場合に増加する高さを取得
3.1と2を足して、画面の高さを越えるかどうかを判定して、超える場合にあらかじめ改ページする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
//次の文章を表示するのに改ページが必要ならば改ページする private changePageIfNeeded(nextStr: string) { //現在の高さを取り出す var currentHeight = (this.currentRow + 1) * (this.rowHeight); //描画するのに必要となる行数を取り出す var lineCount = 0; var currentRowTxt = this.sentences[this.currentPage][this.currentRow]; for (var i = 0; i < nextStr.length; i++) { var char = nextStr.charAt(i); var currentWidth = this.canvas2D.measureText(currentRowTxt + char).width; //最大幅を超えたら改行カウント if (currentWidth > this.maxWidth) { lineCount++; currentRowTxt = ""; } currentRowTxt += char; } //自動改行の場合には+1 if (this.isAutoNewLine) { lineCount++; } //改行数だけ高さを加算 var nextHeight = currentHeight + (lineCount) * (this.rowHeight); //最大高さを超えたらページ変更 if (nextHeight > this.maxHeight) { this.changePage(); } } |
機能組み込み
上記の2つの処理を前回作成した文章の表示処理の中に組み込みます。
キャレットの処理タイミングとしては
1.文章表示が終わるとキャレットを表示(点滅)させる
2.次の文章の表示が始まるときにまず、キャレットを消す。
といったことを行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
//1文字列を表示。描画中の場合はfalseを返す public showOneString(str: string) { //表示中のクリックを受け付けないようにする this.isEnableSendText = false; //これから表示する文字に改ページが必要かを判定して必要なら改ページする this.changePageIfNeeded(str); //キャレットを消す this.clearCaret(); //1文字ずつタイマー表示 var strCount = 0; this.timer = setInterval(() => { if (str.length > strCount) { this.showOnechar(str[strCount]); strCount++; } else { //タイマーをクリア clearInterval(this.timer); this.timer = 0; //自動改行の場合は改行を追加 if (this.isAutoNewLine) { this.changeRowOrPage(); } //文章表示が終わったらキャレット表示 this.showCaret(); this.isEnableSendText = true; } }, this.displayInterval) } |
コード全体
今回の機能を組み込んだクラスとしては以下のようになります。
動くサンプルはこちらになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
class NovelDisplay{ //表示文字列[ページ数][行数] private sentences: string[][]; //キャンバス private canvas: HTMLCanvasElement; private canvas2D: CanvasRenderingContext2D; //表示幅,高さ private maxWidth: number; private maxHeight: number; //1行の高さ private rowHeight: number; //表示インターバル private displayInterval: number; //文字表示用タイマー private timer: number; //表示中の行と列とページ private currentColumn: number; private currentRow: number; private currentPage: number; //キャレットの文字 private caretNormal: string = "▶"; //キャレットタイマー private caretTimer: number; //キャレットの表示インターバル private caretBlinkIntervall: number; //文字列二重表示可能判定 public isEnableSendText: boolean; //1文字列表示後に自動改行するかどうか public isAutoNewLine: boolean; constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; this.canvas2D = canvas.getContext("2d"); //位置の初期化 this.currentColumn = 0; this.currentRow = 0; this.currentPage = 0; //表示領域の指定 this.maxHeight = 500; this.maxWidth = 500; //配列を初期化 this.sentences = new Array(); this.sentences[0] = new Array(); this.sentences[0][0] = ""; //表示間隔とタイマーの初期化 this.displayInterval = 20; this.timer = 0; //フォント指定と行の高さ指定 this.canvas2D.font = "20pt Gothic"; this.rowHeight = 30; this.isAutoNewLine = false; this.isEnableSendText = true; this.caretBlinkIntervall = 300; this.caretTimer = 0; } //1文字列を表示。描画中の場合はfalseを返す public showOneString(str: string) { //表示中のクリックを受け付けないようにする this.isEnableSendText = false; //これから表示する文字に改ページが必要かを判定して必要なら改ページする this.changePageIfNeeded(str); //キャレットを消す this.clearCaret(); //1文字ずつタイマー表示 var strCount = 0; this.timer = setInterval(() => { if (str.length > strCount) { this.showOnechar(str[strCount]); strCount++; } else { //タイマーをクリア clearInterval(this.timer); this.timer = 0; //自動改行の場合は改行を追加 if (this.isAutoNewLine) { this.changeRowOrPage(); } //文章表示が終わったらキャレット表示 this.showCaret(); this.isEnableSendText = true; } }, this.displayInterval) } //一文字を表示 private showOnechar(oneChar: string) { //次の文字を表示した場合の幅を取得 var currentWidth = this.canvas2D.measureText( this.sentences[this.currentPage][this.currentRow] + oneChar).width; //最大幅を超えたら改行 if (currentWidth > this.maxWidth) { //改行か改ページか判定 this.changeRowOrPage(); } //文字を表示して格納 var x = this.canvas2D.measureText(this.sentences[this.currentPage][this.currentRow]).width; var y = this.rowHeight * (this.currentRow +1); this.canvas2D.fillText(oneChar, x, y); this.currentColumn++; this.sentences[this.currentPage][this.currentRow] += oneChar; } //次の文章を表示するのに改ページが必要ならば改ページする private changePageIfNeeded(nextStr: string) { //現在の高さを取り出す var currentHeight = (this.currentRow + 1) * (this.rowHeight); //描画するのに必要となる行数を取り出す var lineCount = 0; var currentRowTxt = this.sentences[this.currentPage][this.currentRow]; for (var i = 0; i < nextStr.length; i++) { var char = nextStr.charAt(i); var currentWidth = this.canvas2D.measureText(currentRowTxt + char).width; //最大幅を超えたら改行カウント if (currentWidth > this.maxWidth) { lineCount++; currentRowTxt = ""; } currentRowTxt += char; } //自動改行の場合には+1 if (this.isAutoNewLine) { lineCount++; } //改行数だけ高さを加算 var nextHeight = currentHeight + (lineCount) * (this.rowHeight); //最大高さを超えたらページ変更 if (nextHeight > this.maxHeight) { this.changePage(); } } //行もしくはページを変更します private changeRowOrPage() { var nextHeight = (this.currentRow + 1) * (this.rowHeight) + this.rowHeight; //最大高さを超えたら改行 if (nextHeight > this.maxHeight) { this.changePage(); } else { this.changeRow(); } } //行を変更 private changeRow() { //列数を追加して行情報をクリア this.currentRow++; this.currentColumn = 0; this.sentences[this.currentPage][this.currentRow] = ""; } //ページを変更 private changePage() { //ページをインクリメントして表示位置をリセット this.currentPage++; this.currentRow = 0; this.currentColumn = 0; this.sentences[this.currentPage] = new Array(); this.sentences[this.currentPage][0] = ""; //表示をクリア this.canvas2D.clearRect(0, 0, this.maxWidth, this.maxHeight); } //入力受付中のキャレットを表示 private showCaret() { //自動改行で無い場合には次の位置に表示もしくは改行する if (!this.isAutoNewLine) { //次の文字を表示した場合の幅を取得 var currentWidth = this.canvas2D.measureText( this.sentences[this.currentPage][this.currentRow] + this.caretNormal).width; //最大幅を超えたら改行 if (currentWidth > this.maxWidth) { //改行か改ページか判定 this.changeRowOrPage(); } } //キャレット表示位置を取得 var x = this.canvas2D.measureText(this.sentences[this.currentPage][this.currentRow]).width;; var y = this.rowHeight * (this.currentRow + 1); //キャレットを描画 var isShowCaret = true; this.canvas2D.fillText(this.caretNormal, x, y); //キャレット点滅 this.caretTimer = setInterval(() => { if (isShowCaret) { this.canvas2D.fillText(this.caretNormal, x, y); isShowCaret = false; } else { this.hideCaret(x, y); isShowCaret = true; } }, this.caretBlinkIntervall); } //キャレットを隠す private hideCaret(x :number, y: number) { var craretmetrix = this.canvas2D.measureText(this.caretNormal); //なぜかブラウザによってキャレットがうまく消えないので5オフセット追加 this.canvas2D.clearRect(x, y - this.rowHeight +5, craretmetrix.width, this.rowHeight); } //キャレットを消す private clearCaret() { //点滅を消す clearInterval(this.caretTimer); //キャレット表示位置を取得 var x = this.canvas2D.measureText(this.sentences[this.currentPage][this.currentRow]).width;; var y = this.rowHeight * (this.currentRow + 1); this.hideCaret(x, y); } } |
まとめ
ということで、文章を表示する箇所を一通り実装してみました。
ちょっとクラスが肥大化してきてしまったのでリファクタリングしたほうが良くなってきました・・・。
次は画像表示などを組み込んで見たいと思います。