スポンサーリンク
概要
前回はページ戻しや送りの機能を実装しました。
文章表示関連の処理は一旦止めて次は画面表示を実装して見ます。
スポンサーリンク
今回やりたいこと
文章の背景に画像を表示させます。
任意のタイミングで背景を変えられるようにします。
スポンサーリンク
実現方法
画像を描画する方法として、Canvas上に画像描画を行う機能が提供されているのでそれを用います。
ただし、Canvasの機能は特定の描画済みのオブジェクトだけを消すといった機能はありません。
そのため、既に文章を描画済み場合に背景を変更すると再度文章も含めて描画行う必要があり非常に面倒くさくなります。
そこで、現在は利用している文章表示するCanvasとは別のCanvasを用意して複数のCanvasを重ねることで実現します。
今回は具体的に以下のようなレイヤー構造を実現します。
背景画像表示Canvas:
背景画像を表示するCanvasです。このレイヤーを一番最下層にします。
フィルターCanvas:
画面フィルターを表示するCanvasです。このフィルターの目的は文章を読みやすくするためです。背景画像の下にそのまま文章を表示すると見づらくなるため、画面に対して一定の暗さを加えるフィルターを用意することで文章を見やすくします。
文章表示Canvas:
これは前回までに作成した文章表示を行うCanvasです。
ロード中アイコン表示Div:
これはCanvasではありませんが、画像のロード中にロード中のアイコンを表示させるためのDivタグとして要します。このタグの中にimgタグでgifアイコンを入れることでアニメーションを実現します。(Canvasタグではgifアニメーションは再現されないためimgタグを直接描画可能なdivタグを用意します。)
最下層の背景表示canvas以外は背景を透過させることで複数のレイヤーを重ねても下のレイヤーの内容が見えるようにします。
具体的に以下のようなHTMLを用意することで実現します。
1 2 3 4 |
<div id="loading" width="500" height="500" style="background-color: transparent;position: absolute; z-index: 3;"></div> <canvas id="sentenceCanvas" width="500" height="500" style="background-color: transparent;position: absolute; z-index: 2;"></canvas> <canvas id="filterCanvas" width="500" height="500" style="background-color: transparent;position: absolute; z-index: 1;"></canvas> <canvas id="backgroundCanvas" width="500" height="500" style="position: absolute; z-index: 0;"></canvas> |
styleの指定で設定を行います。
・背景色にtransparent(透明)にすることで透過させます。
・z-index: を指定することで階層を指定します。数が少ないほど、下の階層になります。
・position: absoluteで全てのCanvasが重なるようにします。
Typescript実装
次に実際のロジックを考えます。
画像読み込み
まずは画像の読み込み方法に関して考えます。
画像を読み込む処理としてはimageクラスを利用することで可能ですが、読み込み処理自体は非同期処理になり、読み込みが完了するとonloadイベントが呼び出されるといった形になります。
<参考>
画像表示の度にこのような非同期の読み込み処理を実行しているとレスポンスが悪いため、あらかじめ表示する画像を全て読み込んでおきます。
具体的には、以下のようなNovelImageDisplayクラスを作成します。
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 |
class NovelImageDisplay { //キャンバス private canvas: HTMLCanvasElement; private canvas2D: CanvasRenderingContext2D; //ロード済みイメージ private loadedImages: { [key: string]: HTMLImageElement; } = {}; constructor(canvas: HTMLCanvasElement, callBack) { this.canvas = canvas; this.canvas2D = canvas.getContext("2d"); } //ロード済み画像を表示します public showLoadedImage(image: string) { if (this.loadedImages[image]) { var img = this.loadedImages[image]; this.canvas2D.drawImage(this.loadedImages[image], 0, 0); } } //画像をロードします public loadImages(images: string[], callback) { //全ての画像を読み込んだらコールバックを返す this.preLoadImage(images, () => { callback(); }); } //画像を事前に全て読み込みます private preLoadImage(images : string[], callback){ var remaining = images.length; for (var i in images) { this.loadedImages[images[i]] = new Image(); this.loadedImages[images[i]].src = images[i]; this.loadedImages[images[i]].onload = (() => { //全ての画像を読み終わったらコールバック --remaining; if (remaining == 0) { callback(); } }) } } } |
loadImagesメソッドで読み込みを行いたい画像一覧を配列で渡し、非同期で読み込みを行います。
全ての読み込みが完了すると、引数で渡されたコールバックを呼び出して処理が完了したことを通知します。
読み込みが終わった画像は連想配列であるloadedImagesフィールドに画像名をKeyとして格納しておき、
showLoadedImageメソッドで表示依頼が行われた際に引き当てを行い、描画を行います。
デフォルト画像と読み込み中アイコン
次にこのクラスに画像を未読み込み時に表示するデフォルト画像と画像の読み込み中に表示するアイコンを表示させる処理を追加しましょう。
以下に処理を追加したコードを示します。
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 |
class NovelImageDisplay { //キャンバス private canvas: HTMLCanvasElement; private canvas2D: CanvasRenderingContext2D; //ロード中のアイコン表示 private loading: HTMLDivElement; //ロード済みイメージ private loadedImages: { [key: string]: HTMLImageElement; } = {}; //デフォルトイメージ private defaultImageName: string = "Image/nonImage.jpg"; private defaultImage: HTMLImageElement; //画像ロード中のイメージ private loadingImageName: string = "Image/loading.gif"; private loadingImage: HTMLImageElement; constructor(canvas: HTMLCanvasElement, loading: HTMLDivElement, callBack) { this.canvas = canvas; this.canvas2D = canvas.getContext("2d"); this.loading = loading; //デフォルトの黒背景とアイコンを読み込み this.loadDefaultImages(() => { //読み込みが完了したらコールバックを返す callBack(); }); } //ロード済み画像を表示します public showLoadedImage(image: string) { if (this.loadedImages[image]) { var img = this.loadedImages[image]; this.canvas2D.drawImage(this.loadedImages[image], 0, 0); } } //画像をロードします public loadImages(images: string[], callback) { this.showLoadingIcon(); //全ての画像を読み込んだらコールバックを返す this.preLoadImage(images, () => { this.hideLoadingIcon(); callback(); }); } //画像を事前に全て読み込みます private preLoadImage(images : string[], callback){ var remaining = images.length; for (var i in images) { this.loadedImages[images[i]] = new Image(); this.loadedImages[images[i]].src = images[i]; this.loadedImages[images[i]].onload = (() => { //全ての画像を読み終わったらコールバック --remaining; if (remaining == 0) { callback(); } }) } } //デフォルト画像を読み込みます private loadDefaultImages(callback) { this.defaultImage = new Image(); this.defaultImage.src = this.defaultImageName; this.loadingImage = new Image(); this.loadingImage.src = this.loadingImageName; this.defaultImage.onload = (() => { //描画 this.canvas2D.drawImage(this.defaultImage, 0, 0); this.loadingImage.onload = (() => { callback(); }); }); } //ロード中アイコンを表示します private showLoadingIcon() { this.loading.innerHTML = this.loadingImage.outerHTML; } //ロード中アイコンを消去します private hideLoadingIcon() { this.loading.innerHTML = ""; } } |
コンストラクタに前述のレイヤで説明したdivタグを渡しています。
また、デフォルトの画像をコンストラクタで読み込みを行う処理を追加しています。
次にshowLoadingIconとhideLoadingIconメソッドを用意してdivタグ内に画像を追加/削除する処理を呼び出して追加しています。
このメソッドを画像読み込みの開始と終了に実行することで読み込み中に画像を表示させることが出来ます。
最後に
少し長くなってしまったのでここで一旦区切ります。
次回は文章表示との統合を説明します。
サンプルは次回に公開します。