setTimeout(fn,0)在应用样式之前触发

时间:2017-02-14 14:49:45

标签: javascript css settimeout

当我想要继续执行我的javascript并允许浏览器应用样式等时,我倾向于使用setTimeout的常用技术,延迟为0回调在事件循环结束时排队。但是,我遇到的情况似乎并不可靠。

在下面的代码段中,我有一个active类,它将转换应用于chaser元素。

当我将鼠标悬停在目标div上时,我想从active元素中删除chaser类,将chaser移到新位置,然后重新应用active }类。效果应该是o应该立即消失,然后淡入其新位置。相反,opacitytop都应用了转换,因此o 从一个位置滑动大部分时间 >

如果我将内部超时的延迟增加到10,它会开始按照我原先的意图行事。如果我将其设置为5,那么它有时会做,有时也不会。

我希望任何setTimeout排队我的回调,直到应用了样式更新,但这里有一个明确的竞争条件。我错过了什么吗?有没有办法保证更新的顺序?

我在macOS和Windows上的Chrome 56上,还没有测试过其他浏览器。

(我知道我可以通过其他方式实现这一点,例如仅将转换应用于opacity属性 - 请考虑这是一个人为的例子来演示有关订购样式更新的特定问题。)



var targets = document.querySelectorAll('.target');
var chaser = document.querySelector('#chaser');
for (var i = 0; i < targets.length; i++) {
  targets[i].addEventListener('mouseenter', function(event) {
    chaser.className = '';
    setTimeout(function() {
      // at this point, I'm expecting no transition
      // to be active on the element
      chaser.style.top = event.target.offsetTop + "px";
      
      setTimeout(function() {
        // at this point, I'm expecting the element to
        // have finished moving to its new position

        chaser.className = 'active';
      }, 0);
    }, 0);
  });
}
&#13;
#chaser {
  position: absolute;
  opacity: 0;
}
#chaser.active {
  transition: all 1s;
  opacity: 1;
}
.target {
  height: 30px;
  width: 30px;
  margin: 10px;
  background: #ddd;
}
&#13;
<div id="chaser">o</div>
<div class="target">x</div>
<div class="target">x</div>
<div class="target">x</div>
<div class="target">x</div>
<div class="target">x</div>
&#13;
&#13;
&#13;

3 个答案:

答案 0 :(得分:1)

在进行任何其他操作之前,您需要监听的是transitionend事件。您可以阅读MDN about transitionend event。顺便说一下,setTimeout绝对不能用来保证计时。

编辑:这是在OP澄清之后供参考。每当元素发生样式更改时,都会有重排和/或重绘。你可以read more about them here。如果在第一次重排之前运行第二个setTimeout,那么您将获得滑动效果。 10ms将导致所需效果的原因是因为.active类在调整offsetTop属性后添加(导致在元素更改后transition属性应用offsetTop }})。通常,有60fps(即:每帧约16ms),这意味着在应用新样式之前,您有16 ms的窗口可以执行任何操作。这就是为什么5ms的小延迟有时会导致不同的结果。

TL:DR - 浏览器每隔16ms要求JS和CSS进行任何更新,并计算要绘制的内容。如果你错过了16ms的窗口,你可能会得到完全不同的结果。

答案 1 :(得分:0)

您正在另一个setTimeout()计时方法中调用setTimeout()计时方法。

我的想法是,为什么不分别调用这两个setTimeout()方法:

第一个setTimeout()方法应首先执行,然后在执行结束时,它应该调用第二个setTimeout()方法。

答案 2 :(得分:0)

这是一个用于移动追踪者的工作脚本:

function _( id ) { return document.getElementById( id ); }

window.addEventListener( 'load', function() {

 var targets = document.querySelectorAll('.target');
  var chaser = document.querySelector('#chaser');

 setTopPosition( targets );

function setTopPosition( targets ) {
    for (var i = 0; i < targets.length; i++) {
        targets[i].addEventListener('mouseenter', function(event) { 

            chaser.className = '';
            _( 'status' ).innerText = chaser.className; // to inspect the active class

           setTimeout(function() {
               /* at this point, I'm expecting no transition // to be active on the element */

              chaser.style.top = event.target.offsetTop + "px";
      }, 0); 

          // check if charser.className == ''
          if ( chaser.className == '') {
              setClassName();
          } else {
              alert( 0 );
          }

       }); // addEventListener
    } // for
} //function setTopPosition( targets )

function setClassName() {
    setTimeout(function() {

         /* at this point, I'm expecting the element to have finished moving to its new position */

        chaser.className = 'active';
        _( 'status' ).innerText = chaser.className;
         }, 0);
} // function setClassName()

});

HTML:

<div id="chaser">o</div> 

<div class="target">x</div> 
<div class="target">x</div> 
<div class="target">x</div> 
<div class="target">x</div> 
<div class="target">x</div>

<div id="status">status</div>

CSS:

#chaser { position: absolute; opacity: 0; }
#chaser.active { transition: all 1s; opacity: 1; }
.target { height: 30px; width: 30px; margin: 10px; background: #ddd; }