在网页上给定两个点和一组DOM元素,如何找到那些位于由这两个点定义的矩形区域内的DOM元素子集?
我正在开发一个基于Web的画廊,在该画廊中,每张照片都包含在一个 li
标签中。当用户使用鼠标拖出一个矩形区域时,所有位于该矩形内的 li
元素将被标记为已选中。
最好使用jQuery解决方案,以实现简洁高效。
在网页上给定两个点和一组DOM元素,如何找到那些位于由这两个点定义的矩形区域内的DOM元素子集?
我正在开发一个基于Web的画廊,在该画廊中,每张照片都包含在一个 li
标签中。当用户使用鼠标拖出一个矩形区域时,所有位于该矩形内的 li
元素将被标记为已选中。
最好使用jQuery解决方案,以实现简洁高效。
尝试使用类似以下的代码:
// 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]);
}
如果需要纯JS的解决方案,可以查看这个代码pen:https://codepen.io/ArtBIT/pen/KOdvjM
这将非常简单:
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
来关闭它(文档 | 示例)。在文档中,你还会找到如何与自定义选择库一起使用的部分。
i.e.:
const ds = new DragSelect({
selectables: document.querySelectorAll('.item'),
draggability: false
});
背景:十多年前,当我面临这个挑战时,我编写了这个方便的选择库。现在它已经非常成熟,并且能够解决您的使用情况。我强烈建议您使用它,而不是自己编写,因为这是一个非常非常困难的挑战,很容易出错。
在编写DragSelect时,我尝试了各种方法:
document.elementFromPoint(x, y);
(MDN) 所以我们可以做以下操作(简化伪代码):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);
}
}
}
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>
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
selector
支持的原因,以减少您需要处理的元素数量。 - ArtBITtop
、right
、bottom
、left
、width
和height
属性的对象。 - Yuval A.ul#list
元素的引用,然后选择任何作为其子级的li
元素,并检查它们是否落在选择矩形内。尝试传递后代选择器,这将帮助jQuery限制搜索范围。 - ArtBIT