【问题标题】:Check if DOM element can be interacted with检查 DOM 元素是否可以交互
【发布时间】:2018-07-09 21:35:15
【问题描述】:

在我的 html5 应用程序中,我做了很多动态 dom 元素的创建/操作。在某些情况下,我需要验证一个元素(例如 div)是否可以被用户“点击”。 “可点击”表示同时满足以下两个条件:

  • 它是计算出来的 CSS 样式意味着它是实际显示的(即元素及其所有父元素的 displayvisibility 属性)
  • 它不会被任何其他元素遮挡,无论是具有更高 z-index 的元素还是稍后创建的绝对定位元素 - 在任何级别的 DOM 上,而不仅仅是其兄弟。

我可以使用纯 JS 或 jQuery。使用 jQuery 可以很容易地检查第一部分(即使用 .is(':visible')。但是,如果我有一个元素被另一个元素遮挡,这仍然会返回 true

如何检查元素是否真正可点击?

【问题讨论】:

  • 我很好奇,当元素被其他东西覆盖时,你希望你的代码做什么?
  • @Sidney 实际上什么都没有。我只想在元素被其他东西覆盖(即以编程方式向其分派“点击”)时执行一些代码
  • 您可以查看第 2 部分的 elementFromPoint。尽管您需要测试元素的多个点 (x, y)(至少 4 个角)。
  • 谁投票结束这个过于宽泛,我很想知道为什么。
  • 只要找到这个github github.com/UseAllFive/true-visibility

标签: javascript jquery css dom


【解决方案1】:

这使用标准的视频游戏风格的碰撞测试来确定一个项目是否占据了另一个项目占用的全部空间。我不会费心解释那部分,你可以看到另一个答案。

对我来说,解决这个问题的困难部分是尝试获取每个元素的 z-index 以确定一个元素实际上是位于另一个元素的顶部还是下方。首先,我们检查定义的 z-index,如果没有设置,我们检查父元素,直到到达文档。如果我们在没有找到定义的 z-index 的情况下一直到文档,我们知道首先呈现的项目(文档中的标记较高)将位于下方。

我已经将它实现为一个 jQuery pluin.. $("#myElement").isClickable()

$.fn.isClickable = function() {
  if (!this.length) return false;

  const getZIndex = e => {
    if (e === window || e === document) return 0;
    var z = document.defaultView.getComputedStyle(e).getPropertyValue('z-index');
    if (isNaN(z)) return getZIndex(e.parentNode);
    else return z;
  };

  var width = this.width(),
    height = this.height(),
    offset = this.offset(),
    zIndex = getZIndex(this[0]),
    clickable = true,
    target = this[0],
    targetIsBefore = false;

  $("body *").each(function() {
    if (this === target) targetIsBefore = true;
    if (!$(this).is(":visible") || this === target) return;

    var e_width = $(this).width(),
      e_height = $(this).height(),
      e_offset = $(this).offset(),
      e_zIndex = getZIndex(this),

      leftOfTarget = offset.left >= e_offset.left,
      rightOfTarget = width + offset.left <= e_width + e_offset.left,
      belowTarget = offset.top >= e_offset.top,
      aboveTarget = height + offset.top <= e_height + e_offset.top,
      behindTarget = e_zIndex === zIndex ? targetIsBefore : e_zIndex > zIndex;

    if (leftOfTarget && rightOfTarget && belowTarget && aboveTarget && behindTarget) clickable = false;
  });

  return clickable;
};

$(".clickme").click(function() {
  alert("u clicked " + this.id)
});

$(".clickme").each(function() {
  console.log("#"+this.id, $(this).isClickable() ? "is clickable" : "is NOT clickable");
})
#item1 {
  background: rgba(230, 30, 43, 0.3);
  position: absolute;
  top: 3px;
  left: 4px;
  width: 205px;
  height: 250px;
}

#item2 {
  background: rgba(30, 250, 43, 0.3);
  position: absolute;
  top: 100px;
  left: 50px;
  width: 148px;
  height: 50px;
}

#item3 {
  background: rgba(30, 25, 110, 0.3);
  position: absolute;
  top: 23px;
  left: 101px;
  width: 32px;
  height: 100px;
}

#item4 {
  background: rgba(159, 25, 110, 0.3);
  position: absolute;
  top: 10px;
  left: 45px;
  width: 23px;
  height: 45px;
  z-index: -111
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="item1" class='clickme'></div>
<div id="item2" class='clickme'></div>
<div id="item3" class='clickme'></div>
<div id="item4" class='clickme'></div>

【讨论】:

  • 只是一个想法:完全披露,我不喜欢使用否定条件,我的大脑处理积极的更快(这里没有“错误”,但 $(selector).filter(':hidden') 与您的 !$(this).is(":visible") 但也请注意“可见”使用“:可见”,元素选择器元素将被视为:可见,如果它们有布局框。完整讨论:stackoverflow.com/a/17426800/125981(注意“这包括那些宽度和/或高度为零的元素。”)可能会在那里发挥作用至于什么是“可点击的”
  • 我怀疑$("body *").each(...) 是一种合理的方法。我的 DOM 有大约一百万个元素。测试一个元素是否被遮挡大约需要 2 秒。
  • 您也可以考虑在选择器$("body").find("*").filter(':visible').each( 上使用过滤器但是我不是这里插件的作者,也没有完全测试这个快速假设是否有效。
  • 请注意$("body").find("*") 是一种微优化,但在这里似乎与以这种方式定义上下文相关。回复:stackoverflow.com/a/16423239/125981
  • 另外关于我之前的评论 $(this).not(':visible').length 似乎与 !$(this).is(':visible') 相同,我没有检查速度差异。
【解决方案2】:

以下是一个非常粗略的实现——它使用document.elementFromPoint(x, y) method 并广泛扫描每个元素的位置以查看该元素是否可点击。

为了保持简单和提高性能,它会调查每个元素在 50 像素网格中的位置。例如,如果一个元素是 100x100 像素,它将进行 9 次检查(0 0、50 0、100 0、0 50、50 50、100 50、0 100、50 100 和 100 100)。可以调整此值以进行更详细的扫描。

您可能要考虑的另一个因素是,有多少元素是可点击的。例如,如果元素的 1 像素线是可见的,它真的可以点击吗?需要添加一些额外的检查来解决这些情况。

在下面的演示中,有 5 个方块 - 红色、绿色、蓝色、黄色、青色、黑色和灰色。青色元素隐藏在黄色元素之下。黑色元素位于灰色元素下方,但使用z-index 将其显示在上方。因此,除了青色和灰色之外的每个元素都将显示为可点击。

注意:绿色显示为不可点击,因为它隐藏在控制台日志后面(我相信)

这是演示:

// Create an array of the 5 blocks
const blocks = Array.from(document.querySelectorAll(".el"));

// Loop through the blocks
blocks.forEach(block => {
  // Get the block position
  const blockPos = block.getBoundingClientRect();
  let clickable = false;
  
  // Cycle through every 50-pixels in the X and Y directions
  // testing if the element is clickable
  for (var x = blockPos.left; x <= blockPos.right; x+=50) {
    for (var y = blockPos.top; y <= blockPos.bottom; y+=50) {
      // If clickable, log it
      if (block == document.elementFromPoint(x, y)) {
        console.log('clickable - ', block.classList[1])
        clickable = true;
        break;
      }
    }
    
    if (clickable) {
      break;
    }
  }
  
  if (!clickable) {
    console.log('not clickable - ', block.classList[1]);
  }
});
.el {
  position: absolute;
  width: 100px;
  height: 100px;
}

.red {
  top: 25px;
  left: 25px;
  background-color: red;
}

.green {
  top: 150px;
  left: 25px;
  background-color: green;
}

.blue {
  top: 75px;
  left: 75px;
  background-color: blue;
}

.yellow {
  top: 50px;
  left: 200px;
  background-color: yellow;
}

.cyan {
  top: 50px;
  left: 200px;
  background-color: cyan;
}

.black {
  top: 25px;
  left: 325px;
  z-index: 10;
  background-color: black;
}

.gray {
  top: 25px;
  left: 325px;
  z-index: 1;
  background-color: gray;
}
<div class="el red"></div>
<div class="el green"></div>
<div class="el blue"></div>
<div class="el cyan"></div>
<div class="el yellow"></div>
<div class="el black"></div>
<div class="el gray"></div>

【讨论】:

  • 这个颜色background-color: ycyanellow;和土拨鼠渡渡鸟一样吗?
  • Stack Overflow 不是代码编写服务。 stackoverflow.com/help/how-to-answer
  • @Rob - 你是说答案有问题吗?
  • 谢谢。使用elementFromPoint 也是我的想法。您的代码可能需要进行一些调整,因为getBoundingClientRect 返回视口内的坐标,而elementFromPoint 适用于相对于文档根的坐标 - 但总体而言,这个想法似乎没问题。我想知道它在实际场景中会变得多慢,我可能需要在循环中检查许多元素。
  • @Iwrestledabearonce。 document.getElementFromPoint() 在 x,y 位置返回较高的 z 索引元素,因此它考虑了 z 索引。如果您也想获得较低的 z-indexed 元素,那么您将使用 document.getElementsFromPoint()(注意复数),它将返回 x-y 位置的所有元素的 Array,按 z-index 排序。
猜你喜欢
  • 2018-07-09
  • 2017-12-19
  • 2021-02-06
  • 2013-11-09
  • 1970-01-01
  • 1970-01-01
  • 2018-11-27
  • 2020-08-15
  • 1970-01-01
相关资源
最近更新 更多