为什么我需要2个requestAnimationFrame调用来等待dom渲染

问题描述

我正在尝试查找新呈现的元素在鼠标指针下。 (*)

这是我的代码

<resources>
    <resource>
      <directory>assets</directory>
    </resource>
    <resource>
      **<directory>./internal-assets</directory>**
      <includes>
        <include>**</include>
      </includes>
      <skipPngCrush>true</skipPngCrush>
    </resource>
    <resource>
      <directory>data</directory>
    </resource>
  </resources>
btn.addEventListener('click',function () {
  btn.remove();
  for (let i = 0; i < 10; i++) {
    lst.appendChild(document.createElement('li')).textContent = 'Element ' + i;
  }
  requestAnimationFrame(function () { requestAnimationFrame(function () {
    const chosen = document.querySelector('li:hover');
    alert(chosen && 'Your mouse on ' + chosen.textContent); // do something more with chosen
  }); });
});
#btn { width: 200px; height: 200px; }
#lst { width: 200px; line-height: 20px; display: block; padding: 0; }
#lst li { display: block; height: 20px; width: 200px; overflow: hidden; }
#lst li:hover { background: #ccc; }

我很困惑,我需要2 <button id=btn>Click Me</button> <ul id=lst><ul>才能使我的代码正确执行。卸下一个raf,警报将改为显示requestAnimationFrame

代码对我来说也很难看。如何更优雅地实现它?


万一有人在意:我正在Firefox上运行我的代码。而且,作为我的Firefox扩展的一部分,该代码只需要定位到Firefox 60 +。

(*):背后的故事可能更复杂。但为了简单起见...

解决方法

这是您在此处发现的一个非常有趣的行为,即使我们强制重排或进行其他操作,浏览器似乎也不会在第二帧之前更新:hover

更糟糕的是,在Chrome中,如果您使用<button>隐藏display:none元素,它将一直停留在:hover元素上,直到鼠标移动为止(而通常display:none元素不可访问:hover

规范中没有详细介绍:hover的计算方式,因此很难说这本身就是一个“ bug”。

无论如何,最好的方法是通过document.elementsFromPoints方法找到该元素,该方法将同步工作。

btn.addEventListener('click',function ( evt ) {
  btn.remove();
  for (let i = 0; i < 10; i++) {
    lst.appendChild(document.createElement('li')).textContent = 'Element ' + i;
  }
  const chosen = document.elementsFromPoint( evt.clientX,evt.clientY )
    .filter( (elem) => elem.matches( "li" ) )[ 0 ];

  alert(chosen && 'Your mouse on ' + chosen.textContent); // do something more with chosen
});
#btn { width: 200px; height: 200px; }
#lst { width: 200px; line-height: 20px; display: block; padding: 0; }
#lst li { display: block; height: 20px; width: 200px; overflow: hidden; }
#lst li:hover { background: #ccc; }
<button id=btn>Click Me</button>
<ul id=lst><ul>

,

我不能完全回答为什么您需要2架步枪的问题。

但是我可以通过async / await为您提供更优雅的方式。创建一个名为nextTick的小函数,该函数返回一个诺言。因此,您等待下一个帧。

因此,您可以首先等待按钮消失,创建元素,然后再次等待下一个绘画周期,以确保元素可访问

btn.addEventListener('click',async function () {
  btn.remove();
  await nextTick();
  for (let i = 0; i < 10; i++) {
    lst.appendChild(document.createElement('li')).textContent = 'Element ' + i;
  }
  await nextTick()
  const chosen = document.querySelector('li:hover');
  alert(chosen && 'Your mouse on ' + chosen.textContent); // do something more with chosen
});

function nextTick() {
   return new Promise(requestAnimationFrame)
}
#btn { width: 200px; height: 200px; }
#lst { width: 200px; line-height: 20px; display: block; padding: 0; }
#lst li { display: block; height: 20px; width: 200px; overflow: hidden; }
#lst li:hover { background: #ccc; }
<button id=btn>Click Me</button>
<ul id=lst><ul>