非常简单的JavaScript/jQuery示例:指令执行顺序意外评估

9
我对一个CSS3过渡规则通过jQuery应用后,能够实际动画化CSS属性变化感到惊讶。请看以下链接:http://jsfiddle.net/zwatf/3/
初始情况下,一个div元素由两个类样式定义,并且由于这两个类的默认CSS属性具有一定高度(200px)。然后,通过移除其中一个类,利用jQuery修改了该元素的高度:
$('.container').removeClass('active');

这将把高度从200px减少到15px。

之后,通过添加一个类来对容器应用过渡规则:

$('.container').addClass('all-transition');

发生的事情是高度的减小变得有了动画效果(至少在Firefox和Chrome上)。在我的世界中,如果指令的顺序有任何意义,这种情况不应该发生。
我猜这种行为可以很好地解释。那么这是为什么呢?我该如何防止这种情况发生?
这就是我想要实现的:
1. 用jQuery修改默认样式(不使用CSS3过渡进行动画!) 2. 使用jQuery应用过渡规则 3. 使用jQuery更改属性(由CSS3过渡进行动画)
(1)和(2)应该尽快完成,因此我不喜欢使用任意的延迟。

这真的很奇怪,可能是浏览器的一个 bug。我更新了 fiddle,使用了 removeClass,并在第一个函数之前加了一个调试器断点。http://jsfiddle.net/Danack/u9X4m/4/ 如果你跨过第一个函数,然后从那里运行 JS,一切都像你期望的那样工作。没有调试器语句,就会出现你看到的同样奇怪的动画效果。removeClass 中没有任何使用动画的代码。顺便说一下,我在 Chrome 上看到了这个问题。 - Danack
Chrome 25.0.1364.152在Mac上显示动画不正确。 Safari 5.1.7(6534.57.2)在Mac上显示动画不正确。 - Danack
@abbood:我在Firefox 20和Chrome 25上看到了一个动画,网址是http://jsfiddle.net/zwatf/3/。 - Dr. Jan-Philip Gehrcke
1
@abbood 同样地,请问您在哪个浏览器中没有看到任何动画? - Danack
Opera仅在DOMready时未应用类的情况下显示,但使用超时:http://jsfiddle.net/zwatf/6/ - Bergi
有没有人能在 Mac 上运行 WebKit 每夜构建 http://nightly.webkit.org/ 并查看是否出现这种情况?不幸的是,由于旧操作系统的原因,我无法这样做。 - Danack
3个回答

5

为什么会这样?

我猜是因为DOM看到它们一起应用了;立即连续的操作通常会被缓存而不是一个接一个地应用。

我该如何防止这种情况发生?

(非常小的)延时应该可以解决问题:http://jsfiddle.net/zwatf/4/

(来自评论)手动触发回流也有帮助:http://jsfiddle.net/zwatf/9/

我不喜欢使用任意延迟。

那么我认为唯一可能的方法是从动画的CSS属性中删除height,即您需要明确声明要动画的属性:http://jsfiddle.net/zwatf/5/


Bergi,你知道这个“缓存”行为是jQuery负责还是浏览器本身? - Dr. Jan-Philip Gehrcke
我猜是因为DOM看到它们一起应用了。这应该被发音为“这是一个浏览器的bug”。DOM应该遵循它所给定的规则,而不仅仅是按照它感觉实现它们。 - Danack
@Danack:我认为这是快速浏览器的一个功能,但您可以自由地为Gecko/Webkit引擎提交一些错误报告 :-) 如果您这样做,请在此处链接它们。 - Bergi
Bergi,你之所以添加了外部的 setTimeout(function(){ // for Opera ,只是为了看到首先出现的恶意/意外行为,对吧?没有它,一切都还好,我明白了。 - Dr. Jan-Philip Gehrcke
1
@Bergi 没错,这是“特性”,而不是“漏洞” https://bugs.webkit.org/show_bug.cgi?id=111718 “CSS 实际上并没有描述样式何时如何被解析,大多数浏览器会批量处理样式更改。” 这对我来说似乎有些疯狂,但是嘿,我算什么,只是希望 API 的行为能够可预测,而不是按它们的意愿随便变化。 - Danack
显示剩余6条评论

5
运行脚本时,浏览器通常会将DOM更改推迟到最后以防止不必要的绘制。您可以通过读写某些属性来强制重新布局:
var container = $('.container').removeClass('active');
var foo = container[0].offsetWidth;  //offsetWidth makes the browser recalculate
container.addClass('all-transition');

jsFiddle

或者您可以使用短时间的setTimeout,它通过让浏览器自动重新布局,然后添加类别,实现了基本相同的效果。


:-) 是的,谢谢@Bergi提到的相关问题, 它让我了解到了这个技术。 我将做一些测试。 到目前为止,这种“强制回流方法”对我来说似乎相当可靠。 - Dr. Jan-Philip Gehrcke
是的,它在Aurora和Canary中都对我起作用。IE10似乎一开始就没有这个bug,但添加这行也不会引起问题。 - Dennis
它有效,参见http://jsfiddle.net/zwatf/8/ -- Bergi比你早指出了这一点,所以你们两个都获得了绿色的勾号。我不知道该怎么办 ;) - Dr. Jan-Philip Gehrcke
这实际上是问题所在,你们两个都做出了贡献。我已经把它给了丹尼斯,因为他的答案核心似乎是最可靠的方法。关于你的答案,它只是评论的一部分。 - Dr. Jan-Philip Gehrcke

1
我认为,这一切发生得太快了。如果你这样做:
$('.container').toggleClass('active',function() {
    $('.container').addClass('all-transition');
});

我希望它能按照你的期望表现,对吗?

好的,没问题,我测试了一下,但是我没有意识到它没有按照预期工作。这里有一个替代方案来弥补:

$('.container').removeClass('active').promise().done(function(){
    $('.container').addClass('all-transition');

    $('.container').css('height','300'); //to see the easing
});

1
No. .toggleClass 没有回调参数。 - Bergi
Bergi是正确的。你没有尝试过你的代码,也没有查阅文档。我认为这不是因为事情发生得“太快”。事情是不受控制的。从其他语言来看,我本来期望removeClass返回时动作已经执行完毕。显然,我们正在处理异步执行。在这种情况下,有一个回调参数会很好。 - Dr. Jan-Philip Gehrcke
我还没有尝试过基于promise的方法,但从相应的文档来看,如果“一旦绑定到集合的某种类型的所有操作(无论是否排队)都已结束”,对于我们需要等待的CSS更改也是一个相当合理的方法。 - Dr. Jan-Philip Gehrcke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接