Reputation: 198218
I want to invoke jquery.animate
directly to change the effect of a div, but found it doesn't have any effect.
Instead, I need to put it inside a setTimeout(..., 0)
to make it work.
I wonder why do I need to do this, and is it the best approach?
http://jsbin.com/docahu/2/edit
Or here:
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>
You can see height is changed but the width is not.
PS:
Also I found $(document).ready()
is also working:
$(document).ready(function() {
$("#foo").animate({height: '100px'});
});
Which one is better to use?
Upvotes: 0
Views: 571
Reputation: 487
If you've properly scoped your backbone view, you should be able to reference the element that is currently in memory when you are trying to change the width or height (pre-render).
You can do this by doing something similar to: this.$el.find(#foo") to obtain access / manipulate to your markup before it is added to the DOM.
Upvotes: 0
Reputation: 5053
Using $(document).ready
is equivalent to placing the JavaScript at the end of the document…
JSBin (which is were the OP posted his sample code) will execute the JavaScript after all the HTML elements render, but before the $(document).ready
event. Binding the jQuery.animate
in $(document).ready
is the same as firing it anytime after the view is appended to the DOM.
The simple and stable solution is to simply invoke
$("#foo").animate({width: '200px'});
on the last line of the OP's code. (Read the end of the Answer to see a more formal way of binding the jQuery.animate
function)
To answer the OP's original quesiton: setTimeout()
works in your case because of the way async functions are queue in the JavaScript runtime. If the <body>
has already been loaded, then using setTimeout(0)
the way the OP does, will have the same effect as placing the animation binding in $(document).ready
.
The first thing to understand is that JavaScript is not a multi-threaded framework. While you certainly can invoke non-synchronous functions, asynchronous, async functions don't actually run parallel to the synchronous operations. Instead, async functions are queued to run as soon as the runtime is free.
For example, take the following three synchronous functions.
function1();
function2();
function3();
As you'd expect function1will fire first, followed, in order, by
function1,
function2then
function3. However, if I place
function1` in an asynchronous call
setTimeout(function1,0);
function2();
function3();
then function1
will be placed on a queue, leaving function2
and then function3
to fire. As soon as the event loop is finished function1
is invoked. That is, it fires last! You can see this in action in this fiddle.
In the OP's example, setTimeout(function() { $("#foo").animate({height: '100px'});}, 0);
was fired immediately after the runtime executed $(document.body).append(combinedView);
and so jQuery was able to find the #foo
element, so technically this is a correct way to do what the OP wants. This is true because of the way JSBin works. That is, it loads the JavaScript from its JavaScript Module after the DOM has loaded (but before the $(document).ready
event).
$(document).ready
function...in generalI think there's some confusion regarding how $(document).ready
function solves the OP's problem. Most of the confusion probably stems from the complexity of how different web page elements (HTML, CSS, JavaScript) affect the rendering of the DOM.
There is a main parsing and rendering thread used by browsers. This is where your HTML is processed, your CSS stylesheets are fetched and parsed, and your JavaScript is fetched/parsed. All of these operations are executed as they are encountered and will be blocking (unless async
or defer
is specified in your <link>
/<script>
tags).
The order in which you pace all of these tags is essential. If your script tag is written at the top of your document (say within the <head>
tag) it will be executed before any HTML is injected into the DOM.
In essence, using $(document).ready
is equivalent to placing the JavaScript at the end of the document… Since JSBin (which is were the OP posted his sample code) will execute the JavaScript after all the HTML elements render, but before the $(document).ready
function, binding the jQuery.animate
in $(document).ready
is the same as firing it anytime after the view is appended to the DOM.
Instead, the simple and stable solution is to simply invoke
$("#foo").animate({width: '200px'});
after both the fooView
and the barView
have been attached to the DOM. Or more formally:
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();
Upvotes: 0
Reputation: 5052
Well seems like it works by chance. The reason the first one is not working is probably because the object are still not loaded on the screen. The second one is working because after the timer was dispatched and ended (this does not really take 0 time) the page was loaded by another thread on the computer. So the overhead of creating the timer and calling back the procedure is apparently enough to finish loading the page.
You should use $(document).ready
to make sure it is always called after the document is fully loaded, because like I said, it is now working by chance, and a different browser\machine may not run any of the two (or both).
Background: JavaScript starts getting executed while the page is loaded, and the DOM is build at the same time (just at the time the HTML and JavaScript text is downloaded). So if you reference DOM objects from JavaScript code like you are doing now, you get a race condition where the outcome is not defined. To avoid that there is the $(document).ready
callback.
See this question. Also the Udacity course is really cool to understand what is going on.
Upvotes: 1
Reputation: 1032
It's because it's trying to animate the width before the element is in the DOM. If you put a selector in that position, you'll probably find it's not getting anything.
Doing a timeout of 0 (so javascript finishes rendering the things THEN tries the animation) or waiting for the document to finish rendering fixes that
Things happen in this order:
Upvotes: 2