5

私は独自のドラッグ アンド ドロップ ファイル マネージャーを作成しています。これには、アクティブなときに交差する要素 (ファイル) を計算し、それらにクラスを追加することによってそれらを選択する JavaScript マーキー選択ボックスが含まれます。

私は現在、mousemove ハンドラー中にチェックを実行し、要素座標の配列をループして、ドラッグ アンド ドロップ選択ボックスと交差する要素を特定します。

関数は現在、次のようになっています。

selectItems : function(voidindex){

                        var self = this;
                        var coords = self.cache.selectioncoords;

    for(var i=0, len = self.cache.items.length; i<len; i++){
           var item = self.cache.items[i];
           var itemcoords = item.box_pos;

           if(coords.topleft.x < (itemcoords.x+201) && coords.topright.x > itemcoords.x && coords.topleft.y < (itemcoords.y+221) && coords.bottomleft.y > itemcoords.y){
               if(!item.selected){
                  item.selected = true;
                  item.html.addClass('selected').removeClass('activebutton');
                  self.cache.selecteditems.push(i);
                  self.setInfo();
               }
           }
           else{
               if(item.selected){
                  item.selected = false;
                  if(!voidindex || voidindex !== i){
                      item.html.removeClass('selected');
                  }
                  var removeindex = self.cache.selecteditems.indexOf(i);
                  self.cache.selecteditems.splice(removeindex, 1);
                  self.setInfo();
           }
       }
  }
},

上記のコードには、選択が変更された場合にのみ DOM が操作されるようにする汚いロジックがたくさんあります。これは質問とは関係がなく、除外できます。重要な部分は、要素の座標とマーキー選択ボックスの座標をチェックする交差ロジックです。

また、アイテムのサイズは幅 201 ピクセル、高さ 221 ピクセルに固定されていることに注意してください。

私はこれをテストし、すべてが完全に機能しますが、潜在的に数千のファイルをサポートする必要があるため、ある時点で UI パフォーマンスの低下が見られるようになります。

各要素の座標をループせずに交差検出を実行する方法があるかどうかを知りたいです。

マーキー ボックスの座標は、常に次のように定義されます。

 selectioncoords : {
                    topleft : {
                        x : 0,
                        y : 0
                    },
                    topright : {
                        x : 0,
                        y : 0
                    },
                    bottomleft : {
                        x : 0,
                        y : 0
                    },
                    bottomright : {
                        x : 0,
                        y : 0
                    },
                    width : 0,
                    height : 0
                }

また、self.cache.items 配列に格納されている各アイテムの座標は、次のように定義されます。

item : {
       box_pos : {
             x : 0,
             y : 0
       },
       grid_pos : {
              row : 1,
              column : 1
       }


    }

そのため、利用可能な情報は常に実際のグリッド位置 (行/列) と物理アイテムの位置 (グリッド内のピクセル単位の左と上オフセット) です。

要約すると、質問は、mousemove イベントが発生するたびにアイテム座標の配列全体をループせずに、上記で定義された一連のマーキー選択ボックス座標からアイテムの交差を検出する方法はありますか?

助けてくれてありがとう。

4

4 に答える 4

3

以下は、説明されている寸法のロックされたグリッドに依存します。

マウスで定義した四角形を静的なエッジ サイズのグリッドと比較しています。したがって、x 座標または y 座標が与えられた場合、その座標が (それぞれ) どの列または行に該当するかを非常に簡単に導き出すことができるはずです。

ユーザーが選択ボックスを開始したら、その x と y を取得し、開始の行/列を見つけます。選択ボックスを引っ張っている間にマウスを動かすと、仕上げの行/列が見つかります (そして更新されます)。そのボックスで定義された行内とそのボックスで定義された列内 (両端を含む) の両方にあるものはすべて選択されます。次に、選択可能な要素を行と列に従って2次元配列に保持すると、必要なものだけをそのように取得できるはずです。

これがどれだけ効率的であるか (または少ないか) は、予想される選択ボックスの合計サイズと比較したサイズ、およびグリッドにデータが入力されると予想される程度によって異なります。確かに、平均的な使用例が一度に半分ほどのオブジェクトを選択する場合、毎回見なければならないオブジェクトの数を効率的に削減するためにできることはあまりありません。

また、面倒ですが、mousemove ハンドラを毎回起動しないようにすることもできます。更新の合間に少し一時停止すると、この特定の関数の応答性がかなり低下しますが、使用されるリソースの量が大幅に削減されます。

于 2013-03-27T21:06:14.357 に答える
2

これにはいくつかの方法があります。これが1つです。まず、行と列ですばやく検索できる、ある種の整理された構造のアイテムが必要です。2 次元配列を使用することもできますが、簡単にするためにハッシュ テーブルを使用します。これは、 を作成すると同時にself.cache.items、または後で次のように行うことができます。

var cacheLookup = {};

function initCacheLookup() {
    var items = self.cache.items;
    for( var i = 0, n = items.length;  i < n;  i++ ) {
        var item = items[i];
        var key = [ item.grid_pos.row, item.grid_pos.column ].join(',');
        cacheLookup[key] = item;
    }
}

次に、長方形と交差するアイテムを取得したい場合は、次のようにすることができます。

var itemWidth = 201, itemHeight = 221;

var tl = selectioncoords.topleft, br = selectioncoords.bottomright;
var left = Math.floor( tl.x / itemWidth ) + 1;
var right = Math.floor( br.x / itemWidth ) + 1;
var top = Math.floor( tl.y / itemHeight ) + 1;
var bottom = Math.floor( br.y / itemHeight ) + 1;

var selecteditems = [];
for( var row = top;  row <= bottom;  row++ ) {
    for( var col = left;  col <= right;  col++ ) {
        var key = [ row, col ].join(',');
        var item = cacheLookup[key];
        if( item ) {
            selecteditems.push( item );
        }
    }
}
// Now selecteditems has the items intersecting the rectangle

ここにはおそらく 1 つまたは 2 つの誤差がありますが、これは近いはずです。

まあ、私が言ったように、それはそれを行う1つの方法です。self.cache.itemsまた、配列内の項目の順序に依存しないという、おそらく興味深い特性があります。しかし、そのcacheLookupハッシュ テーブルは、最も効率的なソリューションではないかもしれないというにおいがします。

推測してみましょう: その配列は、行と列 (またはその逆) によって既に正しい順序になっていませんか? たとえば、グリッドの幅が 4 の場合、一番上の行は配列要素 0 ~ 3、2 番目の行は 4 ~ 7、3 番目の行は 8 ~ 11 などになります。

行ごとの順序であると仮定すると、ハッシュテーブルはまったく必要ありません。そのinitCacheLookup()機能はなくなり、代わりに検索コードは次のようになります。

var nCols = 4/*whatever*/;  // defined somewhere else
var itemWidth = 201, itemHeight = 221;

var tl = selectioncoords.topleft, br = selectioncoords.bottomright;
var left = Math.floor( tl.x / itemWidth );
var right = Math.floor( br.x / itemWidth );
var top = Math.floor( tl.y / itemHeight ) * nCols;
var bottom = Math.floor( br.y / itemHeight ) * nCols;

var items = self.cache.items;
var selecteditems = [];
for( var iRow = top;  iRow <= bottom;  iRow += nCols ) {
    for( var col = left;  col <= right;  col++ ) {
        var index = iRow + col;
        if( index < items.length ) {
            selecteditems.push( items[index] );
        }
    }
}
// Now selecteditems has the items intersecting the rectangle

このコードは少し速くなり、シンプルになります。また、 と にはまったく依存しませitem.box_positem.grid_pos。これらのデータ フィールドは、アイテムのインデックス、グリッドの列数、アイテムの高さと幅から簡単に計算できるため、まったく必要ない場合があります。

関連するメモ:

201コード内にハードコーディングしないでください221。それらを変数に一度だけ保存し、アイテムの高さと幅が必要なときにそれらの変数を使用します。

データ構造には多くの重複があります。特に必要がない限り、重複データは容赦なく削除することをお勧めします。具体的には:

selectioncoords: {
    topleft: {
        x: 0,
        y: 0
    },
    topright: {
        x: 0,
        y: 0
    },
    bottomleft: {
        x: 0,
        y: 0
    },
    bottomright: {
        x: 0,
        y: 0
    },
    width: 0,
    height: 0
}

ここにあるデータの半分以上が重複しているか、計算可能です。必要なのはこれだけです:

selectioncoords: {
    left: 0,
    right: 0,
    top: 0,
    bottom: 0
}

私がこれを取り上げた理由は、コードで作業するときに少し混乱したためtopleft.xですbottomleft.x

また、前述のように、アイテムがシーケンシャル配列に格納されている場合、item.box_posanditem.grid_posはまったく必要ない場合があります。必要な場合は、1 つだけを保存して、そこからもう 1 つを計算できます。これは、2 つの間に直接的な関係があるためです。

box_pos.x === ( grid_pos.column - 1 ) * itemWidth
box_pos.y === ( grid_pos.row - 1 ) * itemHeight
于 2013-03-27T21:15:11.653 に答える
2

グリッド内の各アイテムを必要なだけ頻繁にインデックス化することで、チェックの範囲を制限できます。グリッドを使用して、X、Y 座標付近、またはX1、Y2、X1、Y2 の範囲にある要素のリストを表示できます。

始めるには...

var Grid = function(pixelWidth, pixelHeight, boxSize) {

  this.cellsIn = function(x1, y1, x2, y2) {
    var rv = [];
    for (var x = x1; x < x2; x += boxSize) {
      for (var y = y1; y < y2; y += boxSize) {
        var gx = Math.ceil(x/boxSize);
        var gy = Math.ceil(y/boxSize);
        rv.push(this.cells[gx][gy]);
      }
    }
    return rv;
  } // cellsIn()


  this.add = function(x1, y1, x2, y2, o) {
    var cells = this.cellsIn(x1, y1, x2, y2);
    for (var i in cells) {
      cells[i].push(o);
    }
  } // add()


  this.get = function(x1, y1, x2, y2) {
    var rv = [];
    var rv_index = {};
    var cells = this.cellsIn(x1, y1, x2, y2);
    for (var i in cells) {
      var cell = cells[i];
      for (var oi in cell) {
        if (!rv_index[cell[oi]]) {
          rv_index[cell[oi]] = 1;
          rv.push(cell[oi]);
        }
      }
    }
    return rv;
  } // get()


  this.cells = [];
  for (var x = 0; x < Math.ceil(pixelWidth/boxSize); x++) {
    this.cells[x] = [];
    for (var y = 0; y < Math.ceil(pixelHeight/boxSize); y++) {
      this.cells[x][y] = [];
    }
  }

};

したがって、考えられるすべてのオブジェクトを反復処理するのではなく、指定された座標に近い、または潜在的に存在するすべてのオブジェクトを反復処理します。

これには、アイテムの座標が変更されたときにグリッドを維持/再インデックスする必要があります。また、既存のオブジェクトを変更/移動するために、上記の (または同様の) Grid クラスにいくつかの機能を追加することをお勧めします。しかし、私の知る限りでは、この種のインデックスは、「空間内」のオブジェクトにインデックスを付ける最良の方法です。

免責事項: 上記のコードはテストされていません。しかし、私は同様のコードを持っています。ここで DemoGrid 関数クラスを参照してください: http://www.thepointless.com/js/ascii_monsters.js

私の DemoGrid の機能は似ています (私が覚えている限り、それはしばらく前のことです) が、x, y, radius代わりにパラメーターとして受け入れます。また、注目すべきは、イベントが発生するたびにマウス イベントがグリッドに触れないことです。チェックは、ゲーム/メイン ループによってレート制限されます。

于 2013-03-27T21:10:12.233 に答える
0

システムがそのように設定されている場合、

  • self.cache.items は左から右、上から下に並べられています
    • (0,0)、(1,0)、(2,0)、(0,1)、(1,1)、(1,2)、(0,2)、(1,2)、(2 ,2)
  • 各スペースにアイテムあり
    • 良い - (0,0),(1,0),(2,0),(0,1),(1,1),(1,2),(0,2),(1,2), (2,2)
    • 悪い - (0,0)、(2,0)(1,2)、(1,3)、(2,1)、(2,3)
  • 列の総数を知る必要があります。

それでは、始めるためのコードです。

// Some 'constants' we'll need.
number_of_columns = 4;
item_width = 201;
item_height = 221;

// First off, we are dealing with a grid system, 
// so that means that if given the starting x and y of the marquee,
// we can determine which element in the cache to start where we begin.
top_left_selected_index = Math.floor(selectioncoords.topleft.x / item_width) + (Math.floor(selectioncoords.topright.y / item_height) * number_of_columns );

// Now, because the array is in order, and there are no empty cache points, 
// we know that the lower bound of the selected items is `top_left_selected_index`
// so all we have to do is walk the array to grab the other selected.

number_columns_selected = (selectioncoords.bottomright.x - selectioncoords.topleft.x) / item_width;
// if it it doesn't divide exactly it means there is an extra column selected
if((selectioncoords.bottomright.x - selectioncoords.topleft.x) % item_width > 0){
  number_columns_selected += 1;
}

// if it it doesn't divide exactly it means there is an extra column selected
number_rows_selected = (selectioncoords.bottomright.y - selectioncoords.topleft.y) / item_height;
if((selectioncoords.bottomright.y - selectioncoords.topleft.y) % item_height > 0){
  number_rows_selected += 1;
}

// Outer loop handles the moving the pointer in terms of the row, so it
// increments by the number of columns.
// EX: Given my simple example array, To get from (1,0) to (1,1) 
// requires an index increase of 3
for(i=0; i < number_rows_selected; i++){
  // Inner loop marches through the the columns, so it is just one at a time.
  // Added j < number_of_columns in case your marquee stretches well past your content
  for(j=0; j < number_columns_selected && j < number_of_columns; j++){
    // Do stuff to the selected items.
    self.cache.items[top_left_selected_index + (i * number_of_columns) + j];
  }
}
于 2013-03-27T21:49:16.303 に答える