スポンサーリンク
概要
前回はTypeScript(HTML5 + JavaScript)を用いてサウンドノベルっぽいキャレット表示と、自動改ページを行いました。
今回はページ戻し機能を追加します。
スポンサーリンク
今回やりたいこと
文章を表示中に以前のページの表示内容を表示させます。
戻り中は文字色を変更して一目で戻し中とわかるようにします。
動くサンプルはこちらになります。
スポンサーリンク
状態定義
今回は、文章の表示中と過去戻り中かを状態として保持するため、以下のようなenumを定義します。
1 2 3 4 5 6 7 |
//表示状態 enum State { //通常 Normal = 100, //戻り表示中 PastView = 200 } |
ページを戻す、進める
次に実際にページの戻しと進める機能を考えます。
この機能を実現するためにNovelDisplayクラスに以下の2つのメンバーを追加します。
1.戻りページ
2.現在の状態
1 2 3 4 5 6 7 |
class NovelDisplay{ //戻るページ private backPage: number; //現在の状態 private currentState: State; } |
次に以下の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 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 |
class NovelDisplay{ //戻るページ private backPage: number; //現在の状態 private currentState: State; //1つ前のページ表示 public showPreviousPage() { //過去表示状態に遷移 if (this.currentState == State.Normal) { //点滅を消す clearInterval(this.caretTimer); //文字色を変更 this.canvas2D.fillStyle = "#FFCC00"; //状態を変更 this.currentState = State.PastView; //戻るページをリセット this.backPage = 0; } //戻すページが存在すれば if (this.backPage < this.currentPage) { this.backPage++; //ページを戻す this.showPastPage(this.currentPage - this.backPage); } } //次のページを表示 public showNextPage() { //過去表示状態ならば if (this.currentState == State.PastView) { //戻すページが存在すれば if (this.backPage > 1) { this.backPage--; //ページを進める this.showPastPage(this.currentPage - this.backPage); } else { //現在のページに戻す this.showCurrentPage(); } } } //指定された過去ページを表示 private showPastPage(page: number) { //表示をクリア this.canvas2D.clearRect(0, 0, this.maxWidth, this.maxHeight); //ページデータを取得 var pageData = this.sentences[page]; this.showPageSentences(pageData); } //ページ文言を一括で表示 private showPageSentences(str: string[]) { //1列ずつ描画 for (var i = 0; i < str.length; i++) { var y = this.rowHeight * (i+1); this.canvas2D.fillText(str[i], 0, 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 |
//1文字列を表示。描画中の場合はfalseを返す public showOneString(str: string) { //過去モードならば元に戻す if (this.currentState == State.PastView) { this.showCurrentPage(); return; } //表示中のクリックを受け付けないようにする 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 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
//表示状態 enum State { //通常 Normal = 100, //戻り表示中 PastView = 200 } 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; //戻るページ private backPage: number; //現在の状態 private currentState: State; //文字列二重表示可能判定 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 = true; this.isEnableSendText = true; this.caretBlinkIntervall = 300; this.caretTimer = 0; this.currentState = State.Normal; } //1文字列を表示。描画中の場合はfalseを返す public showOneString(str: string) { //過去モードならば元に戻す if (this.currentState == State.PastView) { this.showCurrentPage(); return; } //表示中のクリックを受け付けないようにする 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); } //現在のページ描画に戻る private showCurrentPage() { //状態を変更 this.currentState = State.Normal; //戻るページをリセット this.backPage = 0; //文字色を変更 this.canvas2D.fillStyle = "#000000"; //表示をクリア this.canvas2D.clearRect(0, 0, this.maxWidth, this.maxHeight); //ページデータを取得 var pageData = this.sentences[this.currentPage]; this.showPageSentences(pageData); //キャレットを描画 this.showCaret(); //状態を戻す this.currentState == State.Normal } //1つ前のページ表示 public showPreviousPage() { //過去表示状態に遷移 if (this.currentState == State.Normal) { //点滅を消す clearInterval(this.caretTimer); //文字色を変更 this.canvas2D.fillStyle = "#FFCC00"; //状態を変更 this.currentState = State.PastView; //戻るページをリセット this.backPage = 0; } //戻すページが存在すれば if (this.backPage < this.currentPage) { this.backPage++; //ページを戻す this.showPastPage(this.currentPage - this.backPage); } } //次のページを表示 public showNextPage() { //過去表示状態ならば if (this.currentState == State.PastView) { //戻すページが存在すれば if (this.backPage > 1) { this.backPage--; //ページを進める this.showPastPage(this.currentPage - this.backPage); } else { //現在のページに戻す this.showCurrentPage(); } } } //指定された過去ページを表示 private showPastPage(page: number) { //表示をクリア this.canvas2D.clearRect(0, 0, this.maxWidth, this.maxHeight); //ページデータを取得 var pageData = this.sentences[page]; this.showPageSentences(pageData); } //ページ文言を一括で表示 private showPageSentences(str: string[]) { //1列ずつ描画 for (var i = 0; i < str.length; i++) { var y = this.rowHeight * (i+1); this.canvas2D.fillText(str[i], 0, y); } } } |
まとめ
ということで、過去表示する箇所を一通り実装してみました。
リファクタといいつつ結局コードをそのまま組み込んでしまいました・・・。
なんとかリファクタをやりたいと思います。