有没有一种简单的方法来判断一个元素是否不可点击?

时间:2021-06-01 04:08:35

标签: javascript html dom

在浏览器自动化应用程序中,我有一些条件(比如 xpath)可以让我找到我想要点击的元素。我不想通过 javascript(element.click() 方法)提供点击,而是通过使用 X11 的外部自动程序。那是因为 click 方法对我来说是不可靠的。所以我需要知道元素上可直接点击的屏幕位置 - 即点击既不会到达元素的子元素,也不会到达重叠和隐藏元素的其他元素。我可以通过搜索元素的每个像素来蛮力做到这一点,如下所示:

function findClickPosition(el) {
  const rect = el.getBoundingClientRect();
  for (let y = rect.y; y <= rect.y + rect.height; y++) {
    for (let x = rect.x; x <= rect.x + rect.width; x++) {
      if (el === document.elementFromPoint(x, y)) {
        return {
          x,
          y
        };
      }
    }
  }
  return null;
}

但是我想知道是否可能没有一种方法可以优化它,例如,首先检查整个元素是否被另一个元素隐藏。这可能吗?如果可能,怎么办?

2 个答案:

答案 0 :(得分:1)

没有其他防弹方法,没有。

举一个会放弃任何其他方法的情况,想想 CSS 剪辑路径规则,它能够在元素内部挖掘真正的洞,点击事件将通过这些洞。
如果此剪辑路径是由外部 SVG 定义的,则您的脚本可能无法读取其路径定义,从而无法计算孔的位置。

.as-console-wrapper { max-height: 100% !important; top: 0; }
div {
  position: absolute;
  width: 300px;
  height: 150px;
}
.target {
  background: red;
  cursor: pointer;
  left: 50px;
  top: 25px;
}
.over {
  background: green;
  --rect-size: 75px;
  clip-path: polygon( evenodd,
    /* outer rect */
    0 0, /* top - left */
    100% 0, /* top - right */
    100% 100%, /* bottom - right */
    0% 100%, /* bottom - left */
    0 0, /* and top - left again */
    /* do the same with inner rect */
    calc(50% - var(--rect-size) / 2) calc(50% - var(--rect-size) / 2),
    calc(50% + var(--rect-size) / 2) calc(50% - var(--rect-size) / 2),
    calc(50% + var(--rect-size) / 2) calc(50% + var(--rect-size) / 2),
    calc(50% - var(--rect-size) / 2) calc(50% + var(--rect-size) / 2),
    calc(50% - var(--rect-size) / 2) calc(50% - var(--rect-size) / 2)
  );
}


不过你可以稍微改进一下你的算法。

首先,检查您尝试点击的元素是否在视口中,如果不在,<div class="target"></div> <div class="over"></div> 将找不到它,我怀疑您的点击方法是否也能到达它。

然后,与其线性地遍历元素中的所有坐标,在大多数情况下,在这些坐标中随机搜索应该会更高效。这里的假设是,如果一个元素在一个像素上,它很有可能也会覆盖它的邻居。所以最好在其他地方完全搜索。

返回第一个的最后一点是不检查目标元素的所有坐标,而只检查视口中可见的坐标,为此,我们可以简单地计算元素的 BBox 和视口。

elementFromPoint()
document.querySelectorAll( "div:not(.cursor)" ).forEach( (el) => {
  el.style.cssText = `
    width: ${ (Math.random() * 100) + 50 }px;
    height: ${ (Math.random() * 100) + 50 }px;
    left: ${ (Math.random() * 250) }px;
    top: ${ (Math.random() * 250) }px;
  `;
} );

const target = document.querySelector(".target");
target.onclick = (evt) => {
  console.log( "clicked" );
}

const point = findClickPosition( target );
if( !point ) {
  console.log( "no clickable point found" );
}
else {
  const cursor = document.querySelector(".cursor");
  cursor.style.cssText = `
    left: ${ point.x }px;
    top: ${ point.y }px;
  `;
}

function findClickPosition( el ) {
  let i = 0; // for metrics only
  const el_rect = el.getBoundingClientRect();
  const win_rect = { right: window.innerWidth, bottom: window.innerHeight };
  const { top, left, width, height } = intersectRect( el_rect, win_rect );
  
  if( width <= 0 || height <= 0 ) {
    console.log( "early exit" ); // for metrics only
    return null; // out of screen
  }

  // build an Array of all the points
  const coords = Array.from( { length: width * height }, (_,i) => {
    const float = i / width;
    const y = Math.floor( float );
    const x = Math.round( (float - y) * width );
    return { x: x + left | 0, y: y + top | 0 };
  } );
  while( coords.length ) {
    // grab a random point in the list
    const [{ x, y }] = coords.splice( Math.random() * coords.length, 1 );
    i++; // for metrics only
    if (el === document.elementFromPoint(x, y)) {
      console.log( "looped", i ); // for metrics only
      return { x, y };
    }
  }
  console.log( "looped", i ); // for metrics only
  return null;
}
function intersectRect( rect1, rect2 ) {
  const top = Math.max( rect1.top || 0, rect2.top || 0 );
  const left = Math.max( rect1.left || 0, rect2.left || 0 );
  const bottom = Math.min( rect1.bottom || 0, rect2.bottom || 0 );
  const right = Math.min( rect1.right || 0, rect2.right || 0 );
  const width = right - left;
  const height = bottom - top;
  return { width, height, top, left, bottom, right };
}
.viewport > div {
  position: absolute;
  background: rgba(0,255,0,.2);
}
.viewport > .target { background: rgba(255,0,0,1); }
.viewport > .cursor {
  pointer-events: none;
  background: transparent;
  border: 1px solid;
  width: 10px;
  height: 10px;
  transform: translate(-5px, -5px);
  border-radius: 50%;
}
/* stacksnippet's console */
.as-console {
  pointer-events: none;
  opacity: 0.5;
}

我们可能还可以通过使用比随机性更智能的算法来进一步改进它,但我会将其留作练习,供任何想要这样做的人使用。


现在,我不得不指出,您必须求助于此类检查的事实是代码异味。在你的职位上,我会仔细检查让我相信我需要这个的原因。

答案 1 :(得分:-2)

理想情况下,您希望在按钮上具有一些指定其状态的属性。像残疾人。用户事件在 DOM 中的工作方式是,单击事件将“冒泡”到具有单击侦听器(即按钮)的容器。 否则,您可以通过执行以下操作来检查元素是否可点击 if(element.getAttribute('onclick')!=null){