为什么我需要使用`setTimeout()`使`jquery.animate`在骨干视图中工作?

时间:2014-12-20 09:50:19

标签: javascript backbone.js settimeout

我想直接调用jquery.animate来改变div的效果,但发现它没有任何效果。

相反,我需要将其放在setTimeout(..., 0)内以使其正常工作。

我想知道为什么我需要这样做,这是最好的方法吗?

现场演示

http://jsbin.com/docahu/2/edit

或者在这里:



var FooView = Backbone.View.extend({
  id: 'foo',
});

var BarView = Backbone.View.extend({
  
  render: function() {
    $("#foo").animate({width: '200px'}); 

    // !!! HERE !!!
    setTimeout(function() {
       $("#foo").animate({height: '100px'}); 
    }, 0);

    return this;
  }
  
});


var fooView = new FooView();
var barView = new BarView();

var combinedView = $(fooView.render().el).append(barView.render().el);

$(document.body).append(combinedView);

#foo {
  width: 50px;
  height: 50px;
  background-color: blue;
  color: white;
}

<!DOCTYPE html>
<html>
<head>
<meta name="description" content="Jquery animate delay problem in backbone render" />
<script src="//code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="//jashkenas.github.io/underscore/underscore-min.js"></script>
<script src="//jashkenas.github.io/backbone/backbone-min.js"></script>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
  
</body>
</html>
&#13;
&#13;
&#13;

您可以看到高度已更改但宽度未更改。


PS:

我还发现$(document).ready()也在运作:

$(document).ready(function() {
   $("#foo").animate({height: '100px'}); 
});

哪一个更好用?

4 个答案:

答案 0 :(得分:2)

这是因为它试图在元素进入DOM之前设置宽度的动画。如果你把一个选择器放在那个位置,你可能会发现它没有得到任何东西。

执行超时0(因此javascript完成呈现事情然后尝试动画)或等待文档完成渲染修复

事情按此顺序发生:

  1. 您渲染视图,但它未附加到DOM
  2. 宽度动画运行。没有任何事情发生,因为“#foo”不在DOM上。
  3. 你把它贴在dom上。
  4. 您的高度动画运行。它起作用是因为'#foo'在DOM中。

答案 1 :(得分:1)

好像是偶然的。第一个不工作的原因可能是因为对象仍未加载到屏幕上。第二个是工作,因为在调度和结束计时器后(这实际上不需要0次),页面被计算机上的另一个线程加载。因此,创建计时器和回调过程的开销显然足以完成加载页面。

您应该使用$(document).ready来确保在文档完全加载后始终调用它,因为就像我说的那样,它现在正在工作,而另一个浏览器\机器可能无法运行任何两个(或两者兼有)。

背景:JavaScript在页面加载时开始执行,DOM同时构建(就在下载HTML和JavaScript文本时)。因此,如果您像现在一样从JavaScript代码引用DOM对象,则会得到race condition,其中未定义结果。为避免出现$(document).ready回调。

修改

请参阅this问题。此外,Udacity course真的很酷,无法理解正在发生的事情。

答案 2 :(得分:0)

更新

TL; DR

使用$(document).ready相当于将JavaScript放在文档的末尾......

JSBin(OP发布了他的示例代码)将在所有HTML元素呈现后执行JavaScript,但之前 $(document).ready事件。绑定jQuery.animate中的$(document).ready视图附加到DOM后随时触发相同。

简单而稳定的解决方案是简单地调用

$("#foo").animate({width: '200px'});
OP的代码的最后一行是

。 (阅读答案的结尾以查看绑定jQuery.animate函数的更正式方法)

回答OP的原始问题:setTimeout()适用于您的情况,因为异步函数在JavaScript运行时中的排队方式。如果已加载<body>,则以OP的方式使用setTimeout(0),与将动画绑定放在$(document).ready中具有相同的效果。


为什么setTimeout(0)有效

首先要理解的是JavaScript不是一个多线程框架。虽然您当然可以调用非同步函数,但异步,异步函数实际上并不与同步操作并行运行。相反,只要运行时空闲,异步函数就会排队等待运行。

例如,请执行以下三个同步功能。

function1();
function2();
function3();

正如你所期望的,异步调用中的function1 will fire first, followed, in order, by function1 , function2 then function3 . However, if I place function1`

setTimeout(function1,0);
function2();
function3();

然后function1将被放置在队列中,function2然后function3将被触发。一旦事件循环结束,就会调用function1。也就是说,它最后发射!您可以在this fiddle中看到这一点。

在OP的示例中,setTimeout(function() { $("#foo").animate({height: '100px'});}, 0);在运行时执行$(document.body).append(combinedView);后立即被触发,因此jQuery能够找到#foo元素,所以从技术上来说这是正确的方法做OP想要的事情。这是因为JSBin的工作方式。也就是说,它在加载DOM之后(但在$(document).ready事件之前)从JavaScript模块加载JavaScript。

编辑:

一般不要使用$(document).ready功能......

我认为$(document).ready函数如何解决OP的问题存在一些混淆。大多数混淆可能源于不同网页元素(HTML,CSS,JavaScript)如何影响DOM渲染的复杂性。

浏览器使用主要的解析和渲染线程。这是处理HTML,获取和解析CSS样式表以及获取/解析JavaScript的地方。所有这些操作都会在遇到时执行,并且会阻止(除非async / defer标记中指定了<link><script>

您调整所有这些标签的顺序至关重要。如果您的脚本标记写在文档的顶部(例如在<head>标记内),则会在将任何HTML注入DOM之前执行。

本质上,使用$(document).ready相当于将JavaScript放在文档的末尾 ...因为JSBin(OP发布了他的示例代码)将在执行后执行JavaScript所有HTML元素都会呈现,但之前 $(document).ready函数,绑定jQuery.animate $(document).ready与{em>之后 相同>视图附加到DOM。

相反,简单而稳定的解决方案是简单地调用

$("#foo").animate({width: '200px'});
fooViewbarView都附加到DOM之后

。或者更正式地说:

var BarView = Backbone.View.extend({

  render: function() {
    // process your html here

    return this;
  }

  bindTransitions: function {
    $("#foo").animate({width: '200px', height: '100px'}); 
  }

});


var fooView = new FooView();
var barView = new BarView();

var combinedView = $(fooView.render().el).append(barView.render().el);

$(document.body).append(combinedView);

barView.bindTransitions();

答案 3 :(得分:0)

如果您正确地确定了骨干视图的范围,那么当您尝试更改宽度或高度(预渲染)时,您应该能够引用当前在内存中的元素。

您可以通过执行以下操作来执行此操作:此。$ el.find(#foo“)在将标记添加到DOM之前获取对标记的访问/操作。