这里需要一些帮助。我是一名 UI 设计师,不擅长进行实验性 Web 表单设计中的数字,我需要知道哪个输入元素最接近网页上的点击点。我知道如何用点做最近邻,但输入元素是矩形而不是点,所以我陷入困境。
我正在使用 jQuery。我只需要这个小算法的帮助。一旦我完成了我的实验,我就会向你们展示我在做什么。
更新
我思考了它是如何运作的。看这个图:
每个矩形有 8 个重要的点(或者更确切地说 4 个点和 4 条线)。只有 x 值对于水平点(红点)有意义,只有 y 值对于垂直点(绿点)有意义。 x 和 y 对于角点都很重要。
橙色十字是要测量的点 - 在我的用例中单击鼠标。浅紫色线是橙色十字与可能的最近点之间的距离。
所以…对于任何给定的橙色十字,循环遍历每个矩形的 8 个点 n 中的每一个,找到每个矩形最接近橙色十字的最近边缘或角。值最低的矩形是最近的矩形。
我可以将其概念化和可视化,但无法将其放入代码中。帮助!
你的算法是正确的。由于您需要代码方面的帮助,而不是算法方面的帮助,因此代码如下:
这可能不是最有效的。但它有效。
// Define the click
var click = Array(-1, -2); // coodinates in x,y
// Define the buttons
// Assuming buttons do not overlap
var button0 = Array(
Array(0, 0), // bottom-left point (assuming x is horizontal and y is vertical)
Array(6, 6) // upper-right point
);
var button1 = Array(
Array(10, 11),
Array(17, 15)
);
var button2 = Array(
Array(-8, -5),
Array(-3, -1)
);
// Which button to trigger for a click
i = which(click, Array(button0, button1, button2));
alert(i);
function which(click, buttons){
// Check if click is inside any of the buttons
for (i in buttons){
var button = buttons[i];
var bl = button[0];
var tr = button[1];
if ( (click[0] >= bl[0] && click[0] <= tr[0]) &&
(click[1] >= bl[1] && click[1] <= tr[1]) ){
return i;
}
}
// Now calculate distances
var distances = Array();
for (i in buttons){
var button = buttons[i];
var bl = button[0];
var tr = button[1];
if ( (click[0] >= bl[0] && click[0] <= tr[0])) {
distances[i] = Math.min( Math.abs(click[1]-bl[1]), Math.abs(click[1]-tr[1]) );
}
else if ( (click[1] >= bl[1] && click[1] <= tr[1])) {
distances[i] = Math.min( Math.abs(click[0]-bl[0]), Math.abs(click[0]-tr[0]) );
}
else{
distances[i] = Math.sqrt(
(Math.pow(Math.min( Math.abs(click[0]-bl[0]), Math.abs(click[0]-tr[0]) ), 2)) +
(Math.pow(Math.min( Math.abs(click[1]-bl[1]), Math.abs(click[1]-tr[1]) ), 2))
);
}
}
var min_id = 0;
for (j in distances){
if (distances[j] < distances[min_id]){
min_id = j;
}
}
return min_id;
}
相对较新的 elementFromPoint() API 的添加让我们可以采取另一种可能更简单的方法:我们可以在鼠标光标周围进行测试,绕更大的圆圈,直到找到最近的元素。
我在这里整理了一个快速的非生产示例:http://jsfiddle.net/yRhhs/(仅限 Chrome/Safari,因为使用了 webkitMatchesSelector)。由于可视化算法中使用的点,性能可能会变得滞后。
除了轻性能优化和事件绑定之外,代码的核心是这一点:
function hitTest(x, y){
var element, i = 0;
while (!element){
i = i + 7; // Or some other threshold.
if (i > 250){ // We do want some safety belts on our while loop.
break;
}
var increment = i/Math.sqrt(2);
var points = [
[x-increment, y-increment], [x+increment, y-increment],
[x+increment, y+increment], [x-increment, y+increment]
];
// Pop additional points onto the stack as the value of i gets larger.
// ...
// Perhaps prematurely optimized: we're using Array.prototype.some to bail-out
// early once we've found a valid hit target.
points.some(function(coordinates){
var hit = document.elementFromPoint.apply(document, coordinates);
// isValidHit() could simply be a method that sees whether the current
// element matches the kinds of elements we'd like to see.
if (isValidHit(hit)){
element = hit;
return true;
}
});
}
您可以寻找所有矩形的最近的角点。这在大多数情况下都有效,快速且易于实施。只要您的矩形在规则网格上对齐,此方法就会为您提供最近的矩形。
我的方法不是用数字,而是用逻辑。
我假设您希望最终得到这样的结果:“如果 x 是最近的元素,那么当我单击其他位置时执行某些操作,然后对 x 执行某些操作”
如果您想要处理的每个元素都位于简单的
<div>
容器中,并且该容器比您想要处理的元素大,但不大于它所包含的对象与其下一个最近的对象之间的一半,则可以执行此操作。事实上是一个网格。
为所有容器指定相同的类。
然后你可以说,“如果单击 y,则对 x 执行某些操作”,你就已经知道每个容器中有哪个元素了。
我会写代码,但我要下班了......
如果要求二维网格上两点之间的距离,可以使用以下公式:
(对于 2D 点 A 和 B)
距离X = A.x - B.x
距离Y = A.y - B.y
总距离 = squareRoot ((distX * distX) + (distY * distY))
一旦您可以检查两点之间的距离,您就可以很容易地找出鼠标单击的矩形角最近。您可以做很多事情来优化您想要的算法,但这应该给您一个良好的开始。
哈哈,问题是你为什么要考虑形状? 你的问题实际上是“如果我点击一个坐标,找到距离我点击最近的节点/点”,这是一个遍历各个节点并计算距离的问题。
如果X相同,则使用y差
如果 y 相同,则使用 x 差值
否则使用假设
一旦找到最近的点,就能得到父形状吗? 这会起作用,因为您正在尝试捕捉到最近的点。因此它甚至可以处理星星等奇特的形状。
我想允许用户在段落之间添加内容。因此,对于我的用例,我只关心点击上方或下方的附近段落标签。
因此,我使用了这段代码:
const MAX_TRIES_PER_DIR = 10;
const PX_SHIFT_EACH_TRY = 2;
function findClosestPToClick(e) {
const above_res = checkNearbyPosForP(e.pageX, e.pageY, -PX_SHIFT_EACH_TRY, MAX_TRIES_PER_DIR);
const above_dist = above_res ? Math.abs(e.pageY - above_res.y) : Infinity;
const below_res = checkNearbyPosForP(e.pageX, e.pageY, PX_SHIFT_EACH_TRY, MAX_TRIES_PER_DIR);
const below_dist = below_res ? Math.abs(e.pageY - below_res.y) : Infinity;
if (above_dist <= below_dist) {
if (above_dist === 0) {
const el_height = above_res.el.offsetHeight;
if (Math.sign(e.offsetY - el_height / 2) < 0) {
return {
el: above_res?.el,
place_before: true,
}
}
return {
el: above_res?.el,
place_before: false,
}
}
return {
el: above_res?.el,
place_before: false,
}
}
return {
el: above_res?.el,
place_before: true,
};
}
function checkNearbyPosForP(x, y, shift, num_tries) {
const p = document.elementFromPoint(x, y)?.closest('p');
if (p) return { el: p, y };
if (num_tries > 1) {
return checkNearbyPosForP(x, y + shift, shift, --num_tries);
}
return;
}
document.addEventListener('click', function(e) {
const res = findClosestPToClick(e);
if (res.el) {
return console.log(res.el.innerText, res.place_before);
}
console.log('No p found near click');
});