当我想要继续执行我的javascript并允许浏览器应用样式等时,我倾向于使用setTimeout
的常用技术,延迟为0
回调在事件循环结束时排队。但是,我遇到的情况似乎并不可靠。
在下面的代码段中,我有一个active
类,它将转换应用于chaser
元素。
当我将鼠标悬停在目标div上时,我想从active
元素中删除chaser
类,将chaser
移到新位置,然后重新应用active
}类。效果应该是o
应该立即消失,然后淡入其新位置。相反,opacity
和top
都应用了转换,因此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;
答案 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; }