检查是否可以与DOM元素进行交互

时间:2018-01-30 21:36:50

标签: javascript jquery css dom

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

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

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

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

2 个答案:

答案 0 :(得分:2)

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

对我来说,难以理解的是尝试获取每个元素的z-index以确定元素是否实际位于另一个元素的顶部或底部。首先,我们检查定义的z-index,如果没有设置,我们检查父元素,直到我们到达文档。如果我们在没有找到定义的z-index的情况下一直到达文档,我们就知道首先渲染的项目(文档中的标记更高)将在下面。

我已将此作为jQuery插件实现.. $("#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");
})
&#13;
#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
}
&#13;
<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>
&#13;
&#13;
&#13;

答案 1 :(得分:1)

以下是一个非常粗略的实现 - 它使用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在上方显示。因此,除青色和灰色外,每个元素都将显示为可点击。

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

这是演示:

&#13;
&#13;
// 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]);
  }
});
&#13;
.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;
}
&#13;
<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>
&#13;
&#13;
&#13;

相关问题