無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

分割された画像を遅延ロードするコントロールを試作してみた。

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>