Hatena::ブログ(Diary)

Web系がおもしろい。

2010-06-06

指定した画像のロードが全て終わった後のタイミングで任意のスクリプトを実行するJavaScript

| 19:37 | 指定した画像のロードが全て終わった後のタイミングで任意のスクリプトを実行するJavaScriptのブックマークコメント

今会社にお金になるような仕事がなくて、ちょっと社長がピリピリしてて精神的にちょっと疲れるなぁ…といった毎日が続いてて、休日もあんまりJS書く状態にありつけませんでした。。なかなか。

そしてブログ書き込もうとしたらログアウトされてた関係で内容消えて書き直しになるし、踏んだり蹴ったり…うおー…


とりあえずソース

今のところIEでは実行できません。

Safari, Opera, Chrome, Firefoxで実行できます。

IEも対応しました。IE用にExplorerCanvasを用意してくださいませ。


HTML
<!DOCTYPE html>
<head>
<title>preload</title>
<meta charset="UTF-8">
<!--[if IE]><script type="text/javascript" src="excanvas.compiled.js"></script><![endif]-->
<script type="text/javascript" src="preload.js"></script>
<script type="text/javascript">

// 画像URLを指定しておく
var imageList = [
	"6c506c24.jpg",
	"f832866c.jpg",
	"e3a35892.jpg"
];

onload = function(){
	new preload(imageList, function(imageObjs){
		// ここに、画像読み込み完了時の処理
		var canvas = document.getElementById('c1');
		if ( ! canvas || ! canvas.getContext ) { return false; }
		var ctx = canvas.getContext('2d');
		ctx.drawImage(imageObjs[2], 0,0, 640,480);
	});
};
</script>
</head>
<body>
	<canvas id="c1" width="640" height="480" style="border:1px solid #CCC"></canvas>
</body>

preload.js
var preload = function(a,b){ this.initialize(a,b); };
preload.prototype = {
	loaded:[],
	ip:0,
	// クロスブラウザにaddEventListener
	addEvent: function(target, type, listener) {
		//@cc_on
		target./*@if (@_jscript_version < 5.9) attachEvent('on' +
		@else@*/addEventListener(/*@end@*/type, listener, false);
	},
	imageFunc:function(nextFunc, len){
		this.loaded[this.ip++] = true;
		
		var j=0;
		var loadedFlg = true;
		for(; j<len; j++){
			if(!this.loaded[j]){
				loadedFlg = false;
				break;
			}
		}
		if(loadedFlg)
			nextFunc(imageList);
	},
	// コンストラクタ
	initialize: function(imageList, nextFunc){
		var self = this;
		var i=0, len = imageList.length;
		for(; i<len; i++){
			self.loaded[i] = false;
			var img = new Image();
			img.src = imageList[i];
			imageList[i] = img;
			
			self.addEvent(imageList[i], "load", function(){ self.imageFunc(nextFunc,len); });
		}
	}
};

簡単な流れ

  • あらかじめ、画像のURL配列に入れておく
  • 全ての画像に対してリクエストを行うと同時に、各画像オブジェクトにonloadイベントを設定する

  • 一つ目の画像から、onloadイベントを設定→onloadされたら次の画像に対してonloadイベントを設定…を再帰的に繰り返す
  • 最後の画像まで終わったら、あからじめ引数として渡しておいた関数を実行する

  • タイマを設定し、onloadイベントによってロード完了フラグが全て立っていたらあらかじめ引数として渡しておいた関数を実行する
  • onloadイベントの最後で、ロード完了フラグをチェック。全てフラグが立っていたらあらかじめ引数として渡しておいた関数を実行する

実行してみる

f:id:esperia:20100606194116p:image

http://esperia.kitunebi.com/javascript/preload/preload.html

(preload.htmlの2つのうち1つは忍者toolsの広告です。)

初回アクセス時から画像がちゃんとcanvas要素に描かれていればOKです。

IEでもaddEventListener辺りをちゃんと解決させてあげるとうまくいきそうな気がします。時間がある時にクロスブラウザにしてみるかも。


ところで画像はどこから?

ハム速まとめから頂いてきました!みんな絵うまいなー。。

http://hamusoku.com/archives/93066.html


追記:ishiducaさんにフォローしていただいて、IEに対応しました!しかし、たまに画像がうまく表示されない時があるっぽいです。。うーん。

あとDOMContentLoadedですが、IE8以下(とSafari3.0.4等)は対応していないので、onload に変更しておきました。今回の主題とは外れるので詳しくは扱いません。

DOMContentLoadedをIE8以下で対応させる場合は、uupaaさんの 140文字以内で DOMContentLoaded - latest logなどをご覧下さい。

追記2:try-catchではなく、ifによる分岐に変更しました。IE9PPのaddEventListener無駄にすんなぁぁぁって言われそうではありますけども。。

追記3:タイマーを使って実装してみました!あと、名前空間をなるべく汚さないようにしてライブラリみたいにしてみました。これでどや!一応、前の版をこちらに残しておきます。

追記4:おぉぉぉ、普通にタイマーいらなかったです。。修正しました!あと、記事タイトルも少しカオスだったので修正。

追記5:若干まだ説明が古いままでした。修正しました。

ishiducaishiduca 2010/06/06 20:00 IEないから試してないから動くかな...
var addEvent = function (node, handle, func, flg) {
try {
node.attachEvent(('on' + handle), func);
} catch (e) {
node.addEventListener(handle, func, flg);
}
};
// addEvent(window, "DOMContentLoaded", function () { ... }, false);

esperiaesperia 2010/06/07 00:43 対応出来ました、ありがとうございます!

rikubarikuba 2010/06/07 12:45 addEvent関数は、先に標準のaddEventListenerをテストした方が良いと思います。
また、try-catch文より、if文の方がパフォーマンスが良さそうです。
http://dev.opera.com/articles/view/efficient-javascript-ja/?page=2#trycatch

ところで、preload関数は画像の読み込みにかかる時間が、
imageList[0]の画像<imageList[1]の画像<imageList[2]の画像<...
ということを前提にしているように思えます。
<q>たまに画像がうまく表示されない</q>のは、それが原因ではないでしょうか。

esperiaesperia 2010/06/07 22:59 ありがとうございます!IEだけなので条件付コンパイルでやってみました。IE9ごめん。。

>たまに画像がうまく表示されない
それが原因のようですね…。alertを再帰のところに挟んで遅延させると、IE意外でもうまくいかないことを確認しました。。
画像のリクエストを一つずつ出す方法もありますけど、これだとパラレルに画像をロードしてくれないので遅くなっちゃうんですよね…。ということで、今はタイマーを使った方法を検討してます。

esperiaesperia 2010/06/07 23:19 できたっぽいので日記も更新してみました!
これで大丈夫か…な?

rikubarikuba 2010/06/08 07:21 function addEvent(target, type, listener) {
//@cc_on
target./*@if (@_jscript_version < 5.9) attachEvent('on' +
@else@*/addEventListener(/*@end@*/type, listener, false);
}

・せっかくなら、imageListもコンストラクタ関数で受け取るようにするといいのでは。

・setIntervalを使わなくても、画像のloadイベントリスナー内でチェックできそうです。

esperiaesperia 2010/06/09 00:47 分岐が…す、すごすぎる><

教えていただいた関数を使い、setIntervalの中身をonloadイベントの方に移して、引数の受け渡しの部分を修正してみました!これで今度こそすっきり動くはず!
本当にお手数おかけしました。。ありがとうございます!

トラックバック - http://d.hatena.ne.jp/esperia/20100606/1275820663