jquery 获取页面矩形区域内的DOM元素

py49o6xq  于 2023-10-17  发布在  jQuery
关注(0)|答案(2)|浏览(114)

给定网页上的两个点和一组DOM元素,如何找出位于这两个点定义的矩形区域内的DOM元素的子集?
我正在做一个基于网络的画廊,其中每一张照片都被 Package 在一个li标签中。当用户用鼠标拖出一个矩形区域时,矩形内的所有li元素都被标记为选中。
更喜欢jQuery解决方案,因为它不那么冗长,而且是一种有效的方式。

hgc7kmma

hgc7kmma1#

试试这样的方法:

// x1, y1 would be mouse coordinates onmousedown
// x2, y2 would be mouse coordinates onmouseup
// all coordinates are considered relative to the document
function rectangleSelect(selector, x1, y1, x2, y2) {
    var elements = [];
    jQuery(selector).each(function() {
        var $this = jQuery(this);
        var offset = $this.offset();
        var x = offset.left;
        var y = offset.top;
        var w = $this.width();
        var h = $this.height();

        if (x >= x1 
            && y >= y1 
            && x + w <= x2 
            && y + h <= y2) {
            // this element fits inside the selection rectangle
            elements.push($this.get(0));
        }
    });
    return elements;
}

// Simple test
// Mark all li elements red if they are children of ul#list
// and if they fall inside the rectangle with coordinates: 
// x1=0, y1=0, x2=200, y2=200
var elements = rectangleSelect("ul#list li", 0, 0, 200, 200);
var itm = elements.length;
while(itm--) {
    elements[itm].style.color = 'red';
    console.log(elements[itm]);
}

对于vanilla JS解决方案,请查看此笔:https://codepen.io/ArtBIT/pen/KOdvjM

6uxekuva

6uxekuva2#

嘿@powerboy几年前我有一个类似的用例,让我列出你的选择:

使用库

这将是一样简单:

const ds = new DragSelect({
  selectables: document.querySelectorAll('.item')
});

举例来说:

const ds = new DragSelect({
  selectables: document.querySelectorAll('.item')
});
* { user-select: none; }

.item {
  width: 50px;
  height: 50px;
  position: absolute;
  color: white;
  border: 0;
  background: hotpink;
  top: 10%;
  left: 10%;
}

.ds-selected {
  outline: 3px solid black;
  outline-offset: 3px;
  color: black;
  font-weight: bold;
}

.item:nth-child(2),
.item:nth-child(4) { top: 50% }
.item:nth-child(3),
.item:nth-child(4) { left: 50% }
<script src="https://unpkg.com/dragselect@latest/dist/ds.min.js"></script>
<button type="button" class="item one">1</button>
<button type="button" class="item two">2</button>
<button type="button" class="item three">3</button>
<button type="button" class="item four">4</button>

该库附带拖放功能,但如果你只是想选择,没有拖放,你可以关闭它设置draggability: false,(文档|example)。在文档中,你还可以找到一个关于如何将它与你的自定义选择库一起使用的部分。
即:

const ds = new DragSelect({
  selectables: document.querySelectorAll('.item'),
  draggability: false
});

上下文:十多年前,当我面临这个挑战时,我写了这个方便的选择库。它现在非常成熟,可以解决您的用例。我强烈推荐使用它,而不是自己写,因为这是一个非常非常困难的挑战,仍然有很多可能出错。
在编写DragSelect时,我尝试了各种方法:

点下元素

理论上,我们有document.elementFromPoint(x, y);(),所以我们可以做以下事情(简化的伪代码):

const div = document.getElementById('myDiv');
const rect = div.getBoundingClientRect();
const elements = [];

for (let x = rect.left; x <= rect.right; x++) {
  for (let y = rect.top; y <= rect.bottom; y++) {
    const el = document.elementFromPoint(x, y);
    if (el && !elements.includes(el)) {
      elements.push(el);
    }
  }
}

这段代码遍历myDiv元素边界内的每个点,并使用document.elementFromPoint()获取该点的元素。然后将元素添加到数组中(如果尚未添加)。这将给予一个数组,其中包含方形div边界内的所有元素。
这似乎是最简单的方法,但它也有缺点:

  • 仅适用于页面上可见的元素。
  • 只接受该点的最上面的元素,使用elementsFromPoint返回所有
  • 在一个更大的正方形上的性能是相当糟糕的,因为基于选择正方形的大小,复杂度是O(n^2)。在我的测试中,这是性能最差的方法。

然而,如果你只需要一个点下面的元素,这是很棒的。
知道了这一点,我们可以通过检查可选元素的中点是否有ref矩形来改进算法:

const boxes = document.querySelectorAll(".box");
const ref = document.querySelector("#ref");
const answer = document.querySelector("#answer");

document.addEventListener("mousemove", (e) => {
  ref.style.left = `${e.clientX}px`;
  ref.style.top = `${e.clientY}px`;
  
  let isColliding = false;
  boxes.forEach((box, index) => {
    const rect = box.getBoundingClientRect();
    const el = document.elementFromPoint((rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2);
    if (el === ref) {
      isColliding = true;
      box.style.background = "blue";
    } else {
      box.style.background = "grey";
    }
  });
  answer.innerText = isColliding ? "Collision with box" : "No collision";
});
.box {
  position: absolute;
  top: 20%;
  left: 20%;
  width: 15%;
  height: 15%;
  background: grey;
}
.box:nth-child(2),
.box:nth-child(4){ top: 60% }
.box:nth-child(3),
.box:nth-child(4){ left: 60% }

#ref {
  width: 15%;
  height: 15%;
  background: hotpink;
  position: fixed;
}
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div id="ref"></div>
<div id="answer"></div>
  • 时间复杂度:O(n)
  • 这是很好的和有趣的,但方式不如使用碰撞算法灵活:

使用碰撞算法

轴对齐最小包围盒

一个增强这一个是什么DragSelect是使用引擎盖下。我在这里详细解释了它,还有一篇关于2D碰撞的MDN文章。

const boxes = document.querySelectorAll(".box");
const ref = document.querySelector("#ref");
const answer = document.querySelector("#answer");

document.addEventListener("mousemove", (e) => {
  ref.style.left = `${e.clientX}px`;
  ref.style.top = `${e.clientY}px`;
  const refRect = ref.getBoundingClientRect();

  let isColliding = false;
  boxes.forEach((box, index) => {
    const boxRect = box.getBoundingClientRect();
    if (AABBCollision(refRect, boxRect)) {
      isColliding = true;
      box.style.background = "blue";
    } else {
      box.style.background = "grey";
    }
  });
  answer.innerText = isColliding ? "Collision with box" : "No collision";
});

const AABBCollision = (a, b) => {
  if (
    a.left < b.right && // 1.
    a.right > b.left && // 2.
    a.top < b.bottom && // 3.
    a.bottom > b.top // 4.
  ) {
    return true;
  } else {
    return false;
  }
};
.box {
  position: absolute;
  top: 20%;
  left: 20%;
  width: 15%;
  height: 15%;
  background: grey;
}

.box:nth-child(2),
.box:nth-child(4) {
  top: 60%
}

.box:nth-child(3),
.box:nth-child(4) {
  left: 60%
}

#ref {
  width: 15%;
  height: 15%;
  background: hotpink;
  position: fixed;
}
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div id="ref"></div>
<div id="answer"></div>
  • 为了简单起见,一个方块只是跟随鼠标,当有碰撞时,方块将从灰色变为蓝色,反之亦然。

让我解释一下:
轴对齐边界框碰撞检测。想象一下下面的例子:

b01
     a01[1]a02
        b02      b11
              a11[2]a12
                 b12

为了检查这两个盒子是否碰撞,我们进行AABB计算:

  1. a01 < a12(左边界位置box 1小于右边界位置box 2)
  2. a02 > a11(右边界位置box 1大于左边界位置box 2)
  3. b 01 < b12(上边框pos box 1小于下边框pos box 2)
  4. b 02> b11(底部边框pos box 1大于顶部边框pos box 2)
    因为你必须对每个可能发生冲突的元素进行检查,所以理论上这具有O(n)的复杂度(假设你只有1个引用元素),这是很大的,因为并且随着输入的数量线性扩展。
    这在DragSelect中被证明是有效的,最多可以处理
    30.000
    个元素,然后它开始变慢,因为在JavaScript中获取边界框是昂贵的,我正在寻找一个更好的算法,因为我们幸运地说,对于99.99%的用例,30 k元素就足够了:)
    但会让你更新未来的发现(即。其他算法或广泛和狭窄的阶段可能是有趣的)
    希望能帮上忙,干杯!

相关问题