キャンバス上の地図

少し前までは、あるプロジェクトでは、次の要件を満たすマップを作成する必要がありました。


この問題を解決する最善の方法を決めるのに数日費やさなければなりませんでした。
最後に、私はキャンバスに落ち着きました。
インターネットで同様の解決策を探すのに長い時間を費やしましたが、驚いたことに、この種のものは見つかりませんでした。
その結果、私はすべてをゼロから自分で書くことにしました。
残念なことに、最初のバージョンは非常に遅くなること判明し、一部のブラウザーではマップの動きがけいれん的でした。

新しいバージョンでは、すべてのエラーを考慮に入れ、最終的にカードが規定の要件を満たしていることを確認しました。

準備する


準備段階については説明しませんが、ハブで既に何度も説明されているので、問題が発生した場所に注意を払います。
マップの核となる基盤はcore.jsファイルにあります。canvasを操作するために、別のcanvas.jsファイルがあります。

マップを初期化するには、 index.htmlファイルで、マップのサイズと初期座標を転送するオブジェクトを作成します。
var map = new Zig.Map.Core($('body').width(), $('body').height(), 100, 100); map.addEventListener('change', function(data){ $('#coord').html(' : ' + data.x + ':' + data.y); }); 


初期化プロセス中に、キャンバスの操作を担当するオブジェクトが作成されます。 現時点では、それを操作するためのすべての機能は公開されていますが、
しかし将来は、ほとんどの関数をプライベートにして、誰もキャンバスに描画できないようにする予定です。

キャンバスの配列を作成します。最初のキャンバスは画面上にあるメインのキャンバスで、残りはすべてバッファーです。後でそれらが非常に多い理由を説明します。
初期化の直後に、特定の座標への移行関数goto(x、y、コールバック)が呼び出され、要求された座標の周りのマップ領域がロードされます。
これはプロトタイプであるという事実により、私はajaxによるカードの本格的な受領を行わず、アナログに置き換えました。

 _get_ajax_map : function(coords, callback) { setTimeout(function(){ //    var map = {}; for(var x = Math.min(coords.x1, coords.x2); x <= Math.max(coords.x1, coords.x2); x++) { for(var y = Math.min(coords.y1, coords.y2); y <= Math.max(coords.y1, coords.y2); y++) { if (typeof map[x] == 'undefined') { map[x] = {}; } if (x < 0 || y < 0) { //  (, , ,   ) map[x][y] = { image : null }; } else { map[x][y] = { image : 'img/' + (((y * 200 + x) % 7 + 2) + '.png') }; } } } callback && callback(map); }.bind(this), 0); } 

setTimeoutを使用して、応答の非同期受信をエミュレートします。

レンダリング


レンダリングはいくつかの部分に分割され、画面上の後続のレンダリングの呼び出しはcanvas.jsで行われ、主な作業は
すべての種類の計算はcore.jsで行われます。

 render : function(buffer, buffer2, mouse) { this._checkMoveMap(mouse); if (this._rebuild_buffer) { //   this._rebuild_buffer = false; this._rebuild_buffer2 = false; this._rebuildBuffer(buffer); this._rebuildBuffer2(buffer2); } else if (this._rebuild_buffer2) { this._rebuild_buffer2 = false; this._rebuildBuffer2(buffer2); } return this._options.pos.offset; } 


まず、変数this._rebuild_buffer = false;に割り当てられた2つのバッファーが入力されます。 これは
次の対策では、バッファを更新する必要はありません。
この変数がtrueになった場合、バッファは次のクロックサイクルで再構築されます。 もう一度ブラウザに負担をかけないようにするためにこれを行いました
不要な作業。

この関数の実行を再構築した後、メインバッファーを単純にクリーンアップし、その上に2つのバッファーを描画します。応答で受け取ったオフセットを使用します。

マウスイベントキャプチャ


マップの最初のバージョンでは、大きな問題がありました。 マウスボタンが押された状態でウィンドウ内に動きがあったというイベントを受け取った直後、
私は多くの再計算を実行し、さらにバッファーを再構築しました。 マウスからのイベントは毎秒60回よりも頻繁に発生する可能性があると言う必要はないと思います。
新しいバージョンでは、エラーを考慮し、すべてのマウス操作を記憶し始め、レンダリング時にそれらを拾い始めました。 その結果、何回イベントが発生しても、
処理は1秒間に60回まで行われます。

これは、画面上のマウスの動きを覚えている方法です。
 _move: function(e) { var x = e.offsetX || e.layerX, y = e.offsetY || e.layerY; this.diff.x += Math.abs(this.pos.x - x); this.diff.y += Math.abs(this.pos.y - y); if (this.pressed) { this._addToAction('drag', this.pos.x - x, this.pos.y - y); } else { this._action.move = {x : x, y : y}; } this.pos.x = x; this.pos.y = y; }, _addToAction : function(key, x, y) { if (typeof this._action[key] == 'undefined') { this._action[key] = {x : 0, y : 0}; } this._action[key].x += x; this._action[key].y += y; } 


ご覧のとおり、2つのドラッグアンドムーブイベントがあります。これにより、マップをドラッグする場所と、マウスで単純に駆動する場所を区別できます。
これらのイベントを取り除くと、変数はクリアされます。

 getAction : function() { var action = this._action; this._action = {}; return action; } 


カードの動き


まず、少しの理論。
画面上にキャンバスがあり、そのサイズは初期化中に設定します。また、メインバッファーの2倍の大きさの3つのバッファーがメモリ内にあります。
これは、カードのわずかな動きでバッファを再構築しないために行われます。 そのため、バッファはマージン付きで構築され、安全に移動できます。
それらを正しく配置するために、オフセットを使用します。 つまり メインキャンバスの値が0:0の場合、バッファーには512:512などの値があります。



図では、黄色の四角がメインキャンバス、赤がバッファ、黒のドットが要求された座標です。
カードを横に移動するには、バッファをわずかに移動するだけです。
マップがどれだけオフセットされているかを正確に知るために、デフォルトで等しい2つの変数があります。

 offset : { x : _ * 4, y : _ * 4 } 

実際、デフォルトのオフセットは、赤と黄色の正方形の左上隅間の距離に等しくなります。

マップが移動すると、これらの値にデルタを追加するだけです。
  this._options.pos.offset.x += act.drag.x; this._options.pos.offset.y += act.drag.y; 


また、左上の正方形の位置も変更します。
  this._options.pos.px.x += act.drag.x; this._options.pos.px.y += act.drag.y; 

これは、マウスを追加するだけで、問題なく常にマウスの位置を特定できるようにするためです。
マウス座標値。

このようにして、バッファをどこに描画するかを常に知っているので、可視ポイントがその場所に留まります。

ただし、カードを遠くに移動すると、バッファーは終了します。 そして、これが起こらないようにするには、バッファを適時に更新する必要があります。 それを再構築
目に見える細胞が外部に適所に残るように。
これを実現するために、デフォルト値をオフセットに割り当てるだけでなく、式に従って計算を実行して見つけます
ディスプレイスメントのデフォルト値をどの程度、どの方向に変更する必要があるか。これにより、目に見えるセルが所定の位置に残ります。
これを明確に説明するために、「角」によって、メインキャンバス-aの左上隅を表示し、
「正方形」-「角」にある点が属する正方形、すなわち 「角」の座標は、この「正方形」内のどこかにあります。

「角」の座標が「正方形」の左上隅の座標と一致する可能性はゼロに近いです。
この点で、それらの差を計算し、デフォルトのオフセットに追加します。

  this._options.pos.offset.x = w * 4 + (p.px.x - (xy.x + 4) * w); this._options.pos.offset.y = h * 4 + (p.px.y - (xy.y + 4) * h); 

どこで


3番目のバッファー


現在、3番目のバッファーを使用していませんが、次の場合にバッファーを完全に更新しないように作成しました。
カードが動いています。 最初のバッファーが完全に消去されるのではなく、オフセットを付けて3番目のバッファーに挿入されるようにする予定です。
そして変位の空隙のみが埋められます。
さらに高速に動作します。

おわりに


このプロジェクトに興味がありました。 実際には、JavaScriptを使用せずにキャンバスを学習するのは面白かったです
サードパーティのライブラリ。
私の記事が私が最初のバージョンで犯したのと同じ間違いを軽減するのに役立つことを願っています。

ソースコード


Bitbucket
デモ

Source: https://habr.com/ru/post/J186672/


All Articles