【问题标题】:Detecting if an element is visible on the page with JavaScript or Puppeteer使用 JavaScript 或 Puppeteer 检测页面上是否可见元素
【发布时间】:2019-09-26 08:45:23
【问题描述】:

我正在构建一个框架,用于抓取托管在我们基础架构上的网站,以验证没有任何违反我们政策的行为。如果有被禁止的内容,我们会对其进行截图以及其他详细信息。

显然,如果该元素由于位于 overflow: hidden 父级内部或因为其上方有一个绝对元素而不可见,则您无法截取屏幕截图。

2019 年有没有办法知道一个元素是否可以被人眼看到?

由于它位于 Puppeteer 内部,因此我可以使用原生 Puppeteer API 或所需的任何 JavaScript 库,因为我可以将其注入页面。

问题示例:

$('#above').html(`You can see green: ${$('#below').is(':visible')}. But can you really?`)
#above {
  width: 600px;
  height: 600px;
  position: absolute;
  background-color: red;
  text-align: center;
  font-size: 30px;
  padding: 30px;
}

#below {
  width: 440;
  height: 200;
  background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div id="above"></div>
<div id="below"></div>

(是的,我看到有人问过这个question,但那是 7 年前的事了,此后发生了很大变化,我也可以使用 puppeteer,这在他的情况下是不可能的)。

【问题讨论】:

  • 有没有办法在 2019 年知道一个元素是否可以被人眼看到?绝对不能 100% 确定。
  • @MiXT4PE 好吧,50% 比 40% 好,所以无论提高几率对我都有帮助。

标签: javascript jquery html node.js puppeteer


【解决方案1】:

我从未这样做过,但如果元素不可见或“可悬停”,hover 函数将失败。你可以这样做:

let error = null;
await page.hover('yourSelector').catch(e => error = e);
if (!error) {
   //The element should be visible here.
}

【讨论】:

  • 我认为这个场景对 puppeteer 来说是可见的(它是用于屏幕截图),但我会尽快检查。
  • 正如我所怀疑的,它不被认为对 puppeteer 是不可见的。 :( 还是谢谢!
【解决方案2】:

我找到了一种方法(感谢 @hardkoded 将这个想法植入我的脑海......)
我没有在所有边缘情况下对其进行测试,但它似乎对我的示例有效。

基本上,我为mouseover 事件添加了一个eventListener,它设置了一个布尔标志,然后调用puppeteer 的hover 函数将鼠标移动到元素的中心。
如果设置了标志,则元素可见。

await page.goto('c:/temp/code.html')
const el = await page.$('#below')
await page.evaluate(el => {
  el.addEventListener('mouseover', function () {
    this.setAttribute('mouseover-worked', 'true')
    console.log('hovered!!!')
  })
}, el)
await el.hover()
const hovering_works = await page.evaluate(el => el.getAttribute('mouseover-worked'), el)
console.log(hovering_works)

如果有人发现缺陷,请告诉我...

【讨论】:

    【解决方案3】:

    puppeteer 库中内置了一种简单的方法来检查这一点。您可以为 page.waitForSelector 函数提供 { visible: false } 选项,该函数将检查或实际等待元素可见:

    const element = await page.waitForSelector('#selector', { visible: true });
    

    这将做一个体面的检查。查看函数here的完整代码。它将检查以下内容:

    • 元素是否存在?
    • 根据window.getComputedStyle,元素是否未隐藏(style.visibility !== 'hidden')?
    • 根据element.getBoundingClientRect(),元素a height/width/top/bottom 是否大于0

    后一种情况也将涵盖您的示例代码,因为#below 元素的高度应为0。所以这是一个不错的检查,但您将始终能够构建代码不起作用的情况。如果你想覆盖更多的边缘情况,你也可以扩展 puppeteer 开发者的方法。

    完整代码示例

    如果元素不存在,该函数将无法解析,因此您必须使用一个小助手 Promise 来超时检查:

    const element = await Promise.race([
        new Promise(resolve => setTimeout(() => resolve(), 200)), // resolves without value after 200ms
        page.waitForSelector('#selector', { visible: true })
    ]);
    if (element) {
        // element is visible
    }
    

    【讨论】:

    • 恐怕您建议的所有检查都认为它是可见的。我测试了它。你测试过你的建议了吗?
    • 我只是简单地测试了一下。我在扩展代码 sn-p 上打开了 Chrome 中的 DevTools 并执行了document.querySelector('#below').getBoundingClientRect(),它为我返回了{ height: 0, ... }。因此,我认为检查会起作用。我猜getBoundingClientRect() 的结果与您正在检查的元素不同。对不起:(
    • 这里不需要 promise 包装器,因为 page.waitForSelector 需要一个超时参数以及一个可见参数。
    【解决方案4】:

    也许你可以使用elementHandle.boundingBox()

    它将返回一个显示元素边界框的 Promise(相对于主框架),如果元素不可见,则返回 null。

    sn-p 示例:

          const loadMoreButton = await getDataPage.$(
            'button.ao-tour-reviews__load-more-cta.js-ao-tour-reviews__load-more-cta'
          );
    
          const buttonVisible = await loadMoreButton.boundingBox();
    
          if (buttonVisible) {
            await loadMoreButton.click().catch((e) => {
              console.log('???: ' + e)
            });
          }
    

    【讨论】:

      猜你喜欢
      • 2018-05-22
      • 1970-01-01
      • 2020-01-31
      • 1970-01-01
      • 1970-01-01
      • 2012-02-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多