Reputation: 4149
I have a timeline that can be zoomed by clicking a zoom in
or zoom out
button. This timeline doesn't all fit on the screen at once, so it is a scrollable div. When the user clicks to zoom, I want the position in the timeline to be the same, so I calculate a new scrollTop
for the scrollable div. Here's a simplified version of what I'm doing:
var self = this;
...
this.zoomIn = function() {
var offset = $("#scrollable").scrollTop();
self.increaseZoomLevel(); // Assume this sets the correct zoom level
var newOffset = offset * self.zoomLevel();
$("#scrollable").scrollTop(newOffset);
};
This works fine. Now I'd like to animate the scrolling. This almost works:
var self = this;
...
this.zoomIn = function() {
var offset = $("#scrollable").scrollTop();
self.increaseZoomLevel(); // Assume this sets the correct zoom level
var newOffset = offset * self.zoomLevel();
$("#scrollable").animate({ scrollTop: newOffset });
};
It works if it's clicked once. However, if a second call to zoomIn
happens while the animation is still running, the newOffset
calculation is wrong because the offset is set to scrollTop()
before scrollTop()
is correct since the animation is still manipulating it.
I've tried to use jQuery's queue in various ways to make this calculation happen first, and that seems to work sometimes:
var self = this;
...
this.zoomIn = function() {
$("#scrollable").queue(function(next) {
var offset = $("#scrollable").scrollTop();
self.increaseZoomLevel(); // Assume this sets the correct zoom level
var newOffset = offset * self.zoomLevel();
next();
}).animate({ scrollTop: newOffset });
};
I think I'm just not understanding queue
properly. How do I keep everything in order even when zoomIn is called repeatedly and rapidly? I want:
zoomIn
x 2 clicks
to give me:
calculate 1 -> animate 1 start -> animate 1 finish -> calculate 2 -> animate 2 start -> animate 2 finish
and not
calculate 1 -> animate 1 start -> calculate 2 -> animate 1 finish -> animate 2 start -> animate 2 finish
Because then animate 2 is based on incorrect calculations.
Thanks!
Upvotes: 1
Views: 158
Reputation: 4149
I very much appreciate the answers given. They both would work, but my unwritten requirements included animations that completed entirely as well as no loss of clicks. I know, I should have been more thorough in my question.
Anyway, I believe I have a solution that fits both of those requirements using jQuery queues. There were a couple of things I didn't realize about queues that I learned that got me going in the right direction. The biggest thing is this from the jQuery .animate
docs:
When a custom queue name is used the animation does not automatically start...
This allowed me to have complete control over the queue. I believe this is similar to (or maybe exactly what) @RobinJonsson's comment meant.
var top = 0;
var animating = false;
function calcAndAnimate(top) {
$("#block").queue("other", function() {
// Calculations go here
animating = true;
// This kicks off the next animation
$("#block").dequeue("other");
});
$("#block").animate({
top: top
}, {
duration: 2000,
queue: "other",
complete: function () {
animating = false;
// No need; it looks like animate dequeues for us, which makes sense.
// So the next calculation will be kicked off for us.
//$("#block").dequeue("other");
}
});
}
$("#queueButton").click(function() {
top += 20;
calcAndAnimate(top);
if (!animating) {
// Initial animation, need to kick it off
$("#block").dequeue("other");
}
});
There's a working example with log messages showing the enforced order at http://jsfiddle.net/cygnl7/6h3c2/3/
Upvotes: 0
Reputation: 54629
Here's an implementation of @RobinJonsson's comment, which would be my proposed solution too, using a boolean to allow a new zoom action only after the previous animation is complete:
var self = this;
...
this.zooming = false;
this.zoomIn = function() {
if(!self.zooming){
self.zooming = true;
var offset = $("#scrollable").scrollTop();
self.increaseZoomLevel(); // Assume this sets the correct zoom level
var newOffset = offset * self.zoomLevel();
$("#scrollable").animate({ scrollTop: newOffset },function(){
self.zooming = false;
});
}
};
Upvotes: 1
Reputation: 140
Hm... what about: stop(true,true)
? See: http://api.jquery.com/stop/
var self = this;
...
this.zoomIn = function() {
var offset = $("#scrollable").stop(true,true).scrollTop();
self.increaseZoomLevel(); // Assume this sets the correct zoom level
var newOffset = offset * self.zoomLevel();
$("#scrollable").animate({ scrollTop: newOffset });
};
Upvotes: 1