Promiseオブジェクトを使うと「画像読み込み完了後に関数の戻り値を返す」などの非同期処理完了後の動作をJavaScriptで記述できる。
ソースコード
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="main.js"></script>
<title>画像を読み込んだあとに処理をする(複数画像)
</title>
<style>
*{
margin: 0;
padding: 0;
}
body{
margin: 50px;
}
</style>
</head>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
</body>
</html>
main.js
// 非同期関数(async function)loadImagesを宣言
async function loadImages(imgUrls){
const promises = [];
const images = [];
// 画像ファイルを1枚ずつ読み込み
imgUrls.forEach((url)=>{
// 画像読み込みimg.src = "***.jpg"; の処理は非同期なのでPromiseオブジェクトにする
let promise = new Promise((resolve)=>{
// イメージオブジェクト生成
const img = new Image();
// 画像読み込み完了後の処理
img.addEventListener("load", ()=>{
console.log("loaded: " + url);
resolve(); // 読み込み処理が完了したことをPromise(変数promise)に伝える
});
img.src = url; // 画像読み込み開始
images.push(img); // イメージオブジェクトを配列に格納
});
promises.push(promise); // 画像毎のPromiseオブジェクトを配列に格納
});
await Promise.all(promises); // awaitはasync宣言された場合利用可能: Promise.all()の処理を待っている
// Promise.all: 全てのPromiseが解決したか?
return images;
}
// HTML読み込み後にする処理
window.addEventListener("load", async ()=>{
const canvas = document.getElementById("canvas");
const g = canvas.getContext("2d");
console.log("Loading images...");
// 全ての画像を読み込んだあとイメージオブジェクトを返す
const images = await loadImages(["acty_posca.jpg", "lego_chokobo.jpg", "mustang_flog.jpg"]);
console.log("loading ... OK!");
// キャンバスに画像表示(全ての画像が読み込まれた後、画像表示をすることになる)
images.forEach((img, index)=>{
g.drawImage(img, 0, index*170, 256, 170);
console.log(`images[${index}] = ${img.src}`);
});
});
実行イメージ
解説
前提知識として、JavaScriptで画像ファイルをイメージオブジェクトに読み込む処理の基本的な記述は以下のようになる。
const img = new Image(); img.addEventListener("load", ()=> { // 画像img.src = "***"; で指定した画像が読み込み完了したときの処理を記述しているだけ console.log("画像読み込み完了しました"); }); img.src = "hoge.jpg"; // この時点で画像読み込みが開始する
大事な点は、img.src = “***”; で画像ファイルを指定したときに画像読み込みが開始するということ。
一瞬で画像が読み込まれているように感じるが、一定の時間が掛かっている。
読み込み完了を知るためには、img.addEventListener(“load”, ()=>{ // 処理 });のようにloadイベントの宣言をして完了後の処理を記述しておく必要がある。
実行した際のコンソール表示で確認すると、ページを更新するたびに画像読み込み完了の順序が変化しているのがわかる。
これを押さえておいた上で、loadImages関数について説明する。
非同期処理の戻り値を返す関数としてloadImages関数を宣言している。
async function <関数名>
と関数名の頭にasyncをつけて宣言するとawaitによる非同期処理完了後に実行したい処理を記述することができる。
asyncとawaitはセットで使うと考えると分かりやすい。
今回は、
:
await Promise.all(promises);return images;
として、画像読み込みがすべて完了したあとにイメージオブジェクトの配列を戻り値として返すようにしている。
loadImages関数は、awaitをつけて呼び出しているため、以下の処理では、読み込んだイメージオブジェクトが全て返ってくるまでその下の処理(青字部分)は実行されないことになる。(console.logやimages.forEach()…などの処理は画像読み込みが完了してから実行されているということ)
// 全ての画像を読み込んだあとimageエレメントオブジェクトを返す const images = await loadImages(["acty_posca.jpg", "lego_chokobo.jpg", "mustang_flog.jpg"]); console.log("loading ... OK!"); // キャンバスに画像表示(全ての画像が読み込まれた後、画像表示をすることになる) images.forEach((img, index)=>{ g.drawImage(img, 0, index*170, 256, 170); console.log(`images[${index}] = ${img.src}`); });
ちなみにawaitを使わずにthenを使って記述しても同じ意味となる。
:
:
// HTML読み込み後にする処理
window.addEventListener("load", async ()=>{
const canvas = document.getElementById("canvas");
const g = canvas.getContext("2d");
console.log("Loading images...");
// 全ての画像を読み込んだあとimageエレメントオブジェクトを返す
const images = await loadImages(["acty_posca.jpg", "lego_chokobo.jpg", "mustang_flog.jpg"]);
console.log("loading ... OK!");
// キャンバスに画像表示(全ての画像が読み込まれた後、画像表示をすることになる)
images.forEach((img, index)=>{
g.drawImage(img, 0, index*170, 256, 170);
console.log(`images[${index}] = ${img.src}`);
});
// thenを使ってPromise完了を受け取る記述も可能
loadImages(["acty_posca.jpg", "lego_chokobo.jpg", "mustang_flog.jpg"]).then((images)=>{
console.log("loading ... OK!");
images.forEach((img, index)=>{
g.drawImage(img, 0, index*170, 256, 170);
console.log(`images[${index}] = ${img.src}`);
});
});
});
thenはPromiseオブジェクトのメソッドであるため、こうした処理が可能となっている。
コメント