在浏览器自动化应用程序中,我有一些条件(比如 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;
}
但是我想知道是否可能没有一种方法可以优化它,例如,首先检查整个元素是否被另一个元素隐藏。这可能吗?如果可能,怎么办?
答案 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){