我正在编写自己的拖放文件管理器。这包括一个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。这与问题无关,可以被排除在外。重要的部分是交叉逻辑,它检查元素的坐标与选取框选择框的坐标。
另请注意,项目尺寸固定为 201px宽度,221px高度。
我已经对此进行了测试,并且一切都运行良好,但是我需要支持可能数千个文件,这意味着在某些时候我们将开始看到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事件触发时都遍历整个项目坐标数组?
提前感谢您的帮助。
答案 0 :(得分:3)
以下内容取决于具有所述尺寸的锁定网格。
您正在将鼠标定义的矩形与具有静态边缘大小的网格进行比较。因此,给定一个x坐标或一个y坐标,你应该能够很容易地得出坐标所属的列或行(<分别)。
当用户启动选择框时,抓住x和y,找到开头的行/列。当鼠标在拉动选择框的同时移动时,您会找到(然后更新)完成的行/列。选择在该框所定义的行内以及由该框(包括)定义的列内的任何内容。如果您根据行和列将可选元素保存在二维数组中,那么您应该能够以这种方式获取所需的元素。
请注意,这有多大(或更低)效率取决于预期选择框的大小与总大小的比较,以及您期望网格填充的程度。当然,如果平均用例一次选择对象的一半左右,那么就没有很多工作可以有效地减少每次必须查看的对象数量。
此外,尽管它很麻烦,但每次都可以使用mousemove处理程序。让它在更新之间暂停一点会降低这个特定功能的响应能力,但它会大大减少使用的资源量。
答案 1 :(得分:2)
您可以通过为网格中的每个项目建立索引来限制检查范围,这通常是必需和不再是。您可以使用网格为您提供 near 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
作为参数。另外值得注意的是,每次事件触发时,我的鼠标事件都不会触及网格。支票由游戏/主循环限制。
答案 2 :(得分: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
这里可能有一两个错误,但这应该很接近。
好吧,正如我所说,这是一种方法。并且它具有可能有趣的属性,它不依赖于self.cache.items
数组中的项的顺序。但是cacheLookup
哈希表有点像它可能不是最有效的解决方案。
让我猜一下:这个数组是不是按行和列的顺序正确(反之亦然)?例如,如果您的网格是四宽,那么顶行将是数组元素0-3,第二行4-7,第三行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_pos
和item.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_pos
和item.grid_pos
。如果需要它们,你可以只存储一个并从中计算另一个,因为两者之间存在直接关系:
box_pos.x === ( grid_pos.column - 1 ) * itemWidth
box_pos.y === ( grid_pos.row - 1 ) * itemHeight
答案 3 :(得分:0)
如果系统设置为
所以让你开始的代码。
// 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];
}
}