分割された画像を遅延ロードするコントロールを試作してみた。
Canvasを使って分割された画像を遅延ロードするコントロールを試作してみました。Google Mapの地図表示部分みたいなやつです。
機能
- 分割された画像のうち、表示される部分のみをダウンロードして表示します。残りの画像は、それが必要になったときにダウンロードします。
- ドラッグで画像の表示範囲を変更できます。
課題
以下の課題が残されています。(今日は力尽きたのでいずれ何とかしたい。)
- IEで動作しない
- ドラッグ周りの処理の違いが吸収できていません。orz.
- 描画周りは改善の余地あり。
- 必要になりそうな画像は先読みする、といった機能もきっと必要。
サンプル
サンプルはこちら。ソースは次のとおりです。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <script type="text/javascript" src="excanvas.js"></script> <script type="text/javascript"> /** * 分割された画像を遅延ロードするコントロール。 * @param {Object} screen canvas要素のID * @param {Object} loadingImage 読み込み中の画像のパス。 * 分割した画像と同じサイズであること。 * @param {Object} images 分割された画像のパスの配列。 二次元配列で指定。 * 読み込み中の画像と同じサイズであること。 */ function ImageClip( screen, loadingImage, images ) { this.canvas = document.getElementById(screen); this.ctx = this.canvas.getContext('2d'); this.point = [50, 50]; this.images = []; for( var i = 0; i < images.length; i++ ) { this.images[i] = []; for( var j = 0; j < images[i].length; j++ ) { this.images[i][j] = { src: images[i][j] }; } } // イベントを割り当て var thiz = this; this.canvas.onmousedown = function( ev ) { var event = ev || window.event; var y = ( event.offsetY || event.layerY ); var x = ( event.offsetX || event.layerX ); thiz.onStartDrag( x, y ); }; document.body.onmousemove = function( ev ) { var event = ev || window.event; var y = ( event.offsetY || event.layerY ); var x = ( event.offsetX || event.layerX ); thiz.onDrag(x, y); return false; }; document.body.onmouseup = function( ev ) { var event = ev || window.event; var y = ( event.offsetY || event.layerY ); var x = ( event.offsetX || event.layerX ); thiz.onEndDrag(x, y); }; // 読み込み中画像をロード this.loading = new Image(); this.loading.src = loadingImage; this.loading.onload = function(){ thiz.partWidth = thiz.loading.width; thiz.partHeight = thiz.loading.height; thiz.totalWidth = thiz.loading.width * images[0].length; thiz.totalHeight = thiz.loading.height * images.length; thiz.draw(); } } ImageClip.prototype = { /** * 画面を描画する。 */ draw: function( ) { // キャンバスをクリア this.ctx.clearRect( 0, 0, this.canvas.width, this.canvas.height ); var startX = Math.floor((this.point[0] ? this.point[0] : 1) / this.partWidth); // 描画開始列 var startY = Math.floor((this.point[1] ? this.point[1] : 1) / this.partHeight); // 描画開始行 var offsetX = this.point[0] % this.partWidth; var offsetY = this.point[1] % this.partHeight; var xindex = startX; var yindex = startY; var reload = false; for ( var h = 0; h < this.canvas.height; ) { xindex = startX; for ( var w = 0; w < this.canvas.width; ) { var srcX = 0; var srcY = 0; var srcW = this.partWidth; var srcH = this.partHeight; var x = w; var y = h; if ( w == 0 ) { srcX = offsetX; srcW = this.partWidth - offsetX; } if ( h == 0 ) { srcY = offsetY; srcH = this.partHeight - offsetY; } if ( w + this.partWidth > this.canvas.width ) { srcW = this.canvas.width - w; } if ( h + this.partHeight > this.canvas.height ) { srcH = this.canvas.height - h; } var thiz = this; var data = this.images[yindex][xindex]; if ( data.img && data.img.complete ) { // 読み込み済み this.ctx.drawImage(data.img, srcX, srcY, srcW, srcH, x, y, srcW, srcH ); } else { // まだ読んでない // とりあえず読み込み中を表示。 this.ctx.drawImage(this.loading, srcX, srcY, srcW, srcH, x, y, srcW, srcH ); if ( !data.img ) { data.img = new Image(); data.img.src = data.src; } reload = true; // 再描画が必要、としてマーク。 } w += srcW; xindex++; } h += srcH; yindex++; } if ( reload ) { // 再描画が必要な場合、300ms後に再描画。 setTimeout( function() { thiz.draw(); }, 300); } }, /** * ドラッグ開始 * @param {Object} x ドラッグを開始したx座標 * @param {Object} y ドラッグを開始したy座標 */ onStartDrag : function( x, y ) { this.startDrag = true; this.starty = this.point[1] + y; this.startx = this.point[0] + x; var thiz = this; this.timerId = setInterval( function(){ if ( thiz.change ) { thiz.draw(); thiz.change = false; } }, 200); }, /** * ドラッグされた * @param {Object} x ドラッグ後のx座標 * @param {Object} y ドラッグ後のy座標 */ onDrag : function(x,y) { if ( !this.startDrag ) { return; } var y = this.starty - y; var x = this.startx - x; if ( x <= 0 ) { x = 0; } else if ( x >= this.totalWidth - this.canvas.width ) { x = this.totalWidth - this.canvas.width; } if ( y <= 0 ) { y = 0; } else if ( y >= this.totalHeight - this.canvas.height ) { y = this.totalHeight - this.canvas.height; } if ( isNaN(x) || isNaN(y) ) { return; } if ( this.point[1] != y || this.point[0] != x ) { this.point = [x, y]; this.change = true; // document.getElementById("stdout").innerHTML += "---[" + this.point[0] + "," + this.point[1] +"]<br/>"; } }, /** * ドラッグ終了 * @param {Object} x ドラッグ終了時のx座標 * @param {Object} y ドラッグ終了時のy座標 */ onEndDrag : function(x, y) { if ( this.timerId ) { clearInterval( this.timerId); } this.startDrag = false; this.starty = 0; this.startx = 0; } } function onLoad () { // ImageClipの作成。 // 分割された画像を2次元配列で指定する。 new ImageClip( "canvas", "loading.jpg", [ ["./img/01.jpg", "./img/02.jpg","./img/03.jpg","./img/04.jpg"], ["./img/11.jpg", "./img/12.jpg","./img/13.jpg","./img/14.jpg"], ["./img/21.jpg", "./img/22.jpg","./img/23.jpg","./img/24.jpg"], ["./img/31.jpg", "./img/32.jpg","./img/33.jpg","./img/34.jpg"] ] ); } </script> </head> <body onload="onLoad()"> <!-- キャンバス要素を作る。 --> <canvas id="canvas" width="300" height="300" style="border:1px solid #999999;background-color:#FFFFFF;cursor:move;"> </canvas> <div id="stdout"></div> </body> </html>