Run animations sequentially on multiple divs without timeout - javascript

I am trying to make a code learning app (like scratch but not block based) where users write code to create games and animations. I have given them a series of commands which they can use in any desired order to create an animation sequence on multiple characters.
So far, I have no issues applying animations on a single character. However when two or more characters (two divs) come in a picture, the animations are executed parallelly instead of sequentially
example:
$("#div1").turnLeft();
$("#div1").moveForward()
$("#div2").moveForward () // this should run when first two are complete
$("#div3").moveForward() // this should run when first three are complete
The challenge is the order of commands is not fixed. User may use commands in any order.
How do I make sure the commands entered by users are run one after another?
Thanks

you must do it in call back function of each animation
$("#div1").animate({...},()=>{
$("#div1").animate({...},()=>{
$("#div2").animate({...},()=>{
$("#div3").animate({...})
})
})
});
this is a simple sample for more explain:
$("button").click(function(){
$("div").animate({
left: '250px',
opacity: '0.5',
height: '150px',
width: '150px'
},()=>{alert("finish")});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button>Start Animation</button>
<p>By default, all HTML elements have a static position, and cannot be moved. To manipulate the position, remember to first set the CSS position property of the element to relative, fixed, or absolute!</p>
<div style="background:#98bf21;height:100px;width:100px;position:absolute;"></div>

You can use jquery's queue to queue animations against a different element, then they all queue in a single queue.
Adapting this answer and the info on the jquery help applying this to "body" gives you:
$.fn.moveToX = function(pos) {
var el = $(this);
$("body").queue(
function() {
el.animate({
left: pos + "px"
}, 500, () => {
$.dequeue(this);
});
});
}
$.fn.moveToY = function(pos) {
var el = $(this);
$("body").queue(
function() {
el.animate({
top: pos + "px"
}, 500, () => {
$.dequeue(this);
});
});
}
$("#d1").moveToX(100);
$("#d2").moveToX(200);
$("#d1").moveToY(50);
$("#d2").moveToY(100);
$("#d1").moveToX(0);
$("#d2").moveToX(0);
$("#d1").moveToY(0);
$("#d2").moveToY(0);
div {
position: absolute;
width: 50px;
height: 50px;
border: 1px solid magenta;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='d1'>1</div>
<div id='d2'>2</div>
To have multiple "single" queues (ie multiple queues that can have different elements applied to them (to give parallel animations)) you can supply the "queue[name]" parameter, eg:
$("body").queue("q1", function() { ...
$("body").queue("q2", function() { ...
so your move methods can be easily extended to provide parallel animations
$.fn.moveToX = function(pos, q) {
var el = $(this);
$("body").queue(
q || "fx",
function() {
el.animate({
left: pos + "px"
}, 500, () => {
$.dequeue(this, q);
});
});
}
$.fn.moveToY = function(pos, q) {
var el = $(this);
$("body").queue(
q || "fx",
function() {
el.animate({
top: pos + "px"
}, 500, () => {
$.dequeue(this, q);
});
});
}
$("#d1").moveToX(100);
$("#d2").moveToX(200);
$("#d1").moveToY(50);
$("#d2").moveToY(100);
$("#d1").moveToX(0);
$("#d2").moveToX(0);
$("#d1").moveToY(0);
$("#d2").moveToY(0);
$("#d3").moveToX(250, "q3");
$("#d3").moveToY(50, "q3");
$("#d3").moveToX(300, "q3");
$("#d3").moveToY(0, "q3");
$("body").dequeue("q3"); // queue "fx" auto dequeues
div {
position: absolute;
width: 50px;
height: 50px;
border: 1px solid magenta;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='d1'>1</div>
<div id='d2'>2</div>
<div id='d3' style='left:300px;'>3</div>

Related

jQuery animate scrollTop goes up then back down

I have a function that checks if there are any errors on the page and auto scrolls to them. The problem that I'm having is it scrolls up to them but then comes back down to where it was before. I'd like it to scroll up and stay there.
$(".submit_button").click(function (event) {
event.preventDefault();
var errorElements = $(".error").filter(":visible");
if (errorElements.size() > 0) {
target_top = $(errorElements).offset().top;
$('html, body').animate({
scrollTop: target_top
}, 800);
}
return false;
});
The problem is in your selector. I know why it's done. Any web dev that's been in this long enough has been using that for as much cross browser compat as possible, and yet still encountered this issue. The problem is, you're calling animate:scroll on 2 items consecutively using this selector.
The better way, in short, would be to check if it is a WebKit browser or not. Reason being is that non-WebKit tend to use html whereas WebKit browsers tend to use body (and sometime html). This can cause such confusion as you face now.
The simple short term solution is to use something like /WebKit/i.test(navigator.userAgent) in your click callback. This will help you assign only one selector to the animate call.
Example
var selector = /WebKit/i.test(navigator.userAgent) ? 'body' : 'html';
$(selector).animate( // ...
Test Snippet
$(function() {
// simply to make filler divs for scrolling
for (var i=0;i<10;i++) $('<div />', { 'id': 'div'+i, 'style': 'background-color: '+String.randColorHex()+';' }).append($('.temp').clone().removeClass('temp')).height($(window).height()).appendTo($('body'));
/*------------------------------------------*/
/***S*O*L*U*T*I*O*N***/
var divID = 0;
function btnCheck() { // IGNORE, simply to turn buttons on and off when reaching end
$('#btnScrollDown').prop('disabled', divID>=9);
$('#btnScrollUp').prop('disabled', divID<=0);
}
$(document)
.on('click', '#btnScrollDown', function() {
if (divID < 10) {
divID++;
// broke everything down so it's easy to see. You can shorten this in a few ways.
/*THIS HERE-> */var selector = /WebKit/i.test(navigator.userAgent) ? 'body' : 'html',
scrollSelector = '#div' + (divID),
scrollTop = $(scrollSelector).offset().top
props = { scrollTop: scrollTop },
time = 800;
$(selector).animate(props, time);
// simply to turn buttons on and off when reaching end
btnCheck();
}
})
.on('click', '#btnScrollUp', function() {
if (divID > 0) {
divID--
// broke everything down so it's easy to see. You can shorten this in a few ways.
/*THIS HERE-> */var selector = /WebKit/i.test(navigator.userAgent) ? 'body' : 'html',
scrollSelector = '#div' + (divID),
scrollTop = $(scrollSelector).offset().top
props = { scrollTop: scrollTop },
time = 800;
$(selector).animate(props, time);
// simply to turn buttons on and off when reaching end
btnCheck();
}
});
});
html, body, div { margin: 0; padding: 0; width: 100%; }
.buttons { display: inline-block; left: 1em; position: fixed; text-align: center; top: 1em; }
button { margin: .25em; padding: .1em .3em; width: 100%; }
.temp { dislpay: none; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/JDMcKinstry/String.randColorHex/0c9bb2ff/String.randColorHex.js"></script>
<section class="buttons">
<button id="btnScrollUp" disabled>Scroll To Next Div Up</button><br />
<button id="btnScrollDown">Scroll To Next Down</button>
<sub><i>this isn't fully managed, only use buttons to scroll!</i></sub>
</section>
<table class="temp"><tr><td></td></tr></table>

jquery - moving multiple instances of div across screen at random Y-position

I want to detect clicks and move elements from off-screen right to off-screen left with jquery.
I achieved the general idea already (fiddle) but only for one div, once.
HTML:
<div class = "outer">
<div id = "box">
</div>
</div>
CSS:
body {
overflow-x: hidden;
height: 500px;
}
#box{
height: 100px;
width: 100px;
background: #FF00FF;
position: absolute;
right: -100px;
}
JS:
$(document).ready(function(){
$(document).click(function(){
var bodyHeight = document.body.clientHeight;
var randPosY = Math.floor((Math.random()*bodyHeight));
$('#box').css('top', randPosY);
$("#box").animate({left: '-100px'}, 5000);
});
});
How can make a new instance div appear (at random y-position) per click on the document?
My random y-position is calculated in jQuery the following way, but gets its value from my css height: 500px; - how can I make this value responsive?
Use a function constructor to add object instances of a div, where each one uses .box class instead of #box:
fiddle
function SlidingDiv(bodyHeight){
this.randPosY = Math.floor((Math.random()*bodyHeight));
this.$div = $("<div>").addClass('box').appendTo('.outer');
};
SlidingDiv.prototype.slide = function(){
this.$div.css('top', this.randPosY);
this.$div.animate({left: '-100px'}, 5000);
};
$(document).ready(function(){
$(document).click(function(){
var bodyHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
var div = new SlidingDiv(bodyHeight);
div.slide();
});
});
Edit: To remove divs, you could try the complete function:
SlidingDiv.prototype.slide = function() {
this.$div.css('top', this.randPosY);
this.$div.animate({
left: '-100px',
duration: 5000,
complete: function() { this.$div.remove(); }.bind(this)
});
};

how to return animation to it's original size and position on a click

I am relatively new to all this so if you see anything I am doing wrong, or anyways to simplify any code please do not hesitate to say.
I have the following code to enlarge the div element:
var profilePostsClick = function () {
$('.largeBox, .smallBox, .longBox').click(function () {
$(this).animate({
width: '100%',
height: '40%'
}, 200);
$('.closePost', this).removeClass('closePostHide');
});
};
$(document).ready(profilePostsClick);
https://jsfiddle.net/jvkhmpbt/
I am wanting to close each div when the cross is clicked, returning it to it's original size and positioning (with height: auto if feasible).
Aslo is there a way to make it so each div opens above the smaller ones? (like the top left div does, i am aware this is because of it's positioning)
Thanks
You can do like following way by adding and removing class
JQuery:
$('.largeBox, .smallBox, .longBox').click(function (e) {
e.preventDefault();
$(this).addClass('increaseSize');
$('.closePost', this).removeClass('closePostHide');
});
$('.glyphicon-remove').click(function (e) {
e.stopPropagation()
$('.glyphicon-remove').parent().parent().removeClass('increaseSize');
$('.closePost', this).addClass('closePostHide');
});
CSS:
.increaseSize{
width: 100%;
height: 40%;
}
Check Fiddle Here.
You could save the animation properties/values in an cache-object and restore them after your animation.
http://jsfiddle.net/jvkhmpbt/4/
var animationResetCache = [];
var saveValues = function (node) {
animationResetCache.push({
node: node,
width: node.css('width'),
height: node.css('height')
});
};
var restoreValues = function (node) {
for (var i = 0; i < animationResetCache.length; ++i) {
var item = animationResetCache[i];
if (item.node.is(node)) {
return item;
}
}
};
var profilePostsClick = function () {
$('.largeBox, .smallBox, .longBox').click(function (e) {
var $this = $(this);
if ($this.hasClass('open')) return;
saveValues($this);
$this.addClass('open').animate({
width: '100%',
height: '40%'
}, 200);
$this.find('.closePost').removeClass('closePostHide');
});
$('.closePost').click(function () {
var $parent = $(this).parent('.largeBox, .smallBox, .longBox');
if ($parent.hasClass('open')) {
var cachedValues = restoreValues($parent);
$parent.animate({
width: cachedValues.width,
height: cachedValues.height
}, function () {
$parent.removeClass('open');
});
$parent.find('.closePost').addClass('closePostHide');
}
});
};
$(document).ready(profilePostsClick);
I think it's easier to use a toggle and do the animation in CSS3
$("img").click(function(){
$(this).toggleClass('expanded');
});
I would suggest to add one more identical class to each of smallBox,largeBox and longBox which will be called parentd to identify parent div and animate it back and add below js:
DEMO
$('.closePost').on('click',function(e)
{
$(this).closest('.parentd')
.animate({
width: '40%',
height: 'auto'
},200).removeAttr('style');
$(this).addClass('closePostHide');
e.stopPropagation();
});
If we continue on Rover his answer, we can use the switchClass function in jQuery Ui. (source)
This function let's you switch the classes of an object, creating an animation in the difference between those classes.
Example code: jsFiddle
<div class="large"></div>
CSS:
.large{
width: 100%;
height: 200px;
background-color: red;
}
.small {
width: 10%;
height: 50px;
background-color:green;
}
JS:
$("div").switchClass("large","small",500);

How can I make multiple jQuery animate() calls work one after another?

When I click the "aa" block, both "aa" and "bb" animate at the same time. Does javascript issue animate() functions non-blockingly into separate threads? Or this function is entered multiple times with thousands of callbacks which use blocking-type function calls? How can I make animate() work on items one by one when needed(maybe using a timer could do but do I have to use a timer always?)?
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js">
</script>
<script>
function growbits(i,j) {
$(i).animate({ height: "500px" }); // looks working concurrently
$(j).animate({ width: "500px"}); // with this one
};
</script>
</head>
<body>
<p id="bb" style="background: #f00; height: 50px; width: 255px; margin: 10px;">asdasd</p>
<p id="aa" onclick="growbits(aa,bb);" style="background: #f00; height: 50px; width: 255px; margin: 10px;">
gfhjjjgfhgkjfhkjtkjyhtkjyhkhfukhgkudfgk
</p>
</body>
</html>
Ive found the following code in the min.js file:
self.element.animate(
$.extend(style, top && left ? { top: top, left: left } : {}), {
duration: o.animateDuration,
easing: o.animateEasing,
step: function() {
var data = {
width: parseInt(self.element.css('width'), 10),
height: parseInt(self.element.css('height'), 10),
top: parseInt(self.element.css('top'), 10),
left: parseInt(self.element.css('left'), 10)
};
if (pr && pr.length) $(pr[0]).css({ width: data.width, height: data.height });
// propagating resize, and updating values for each animation step
self._updateCache(data);
self._propagate("resize", event);
}
}
);
From the jQuery documentation:
.animate( properties [, duration ] [, easing ] [, complete ] )
complete
Type: Function()
A function to call once the animation is complete.
So:
function growbits(i,j) {
$(i).animate({ height: "500px" }, {}, 400, function () {
$(j).animate({ width: "500px"});
});
};
Quentin's answer works, but these days I would suggest that it might be cleaner using the options version:
.animate( properties, options )
As such:
javascript
function growbits(i,j) {
$(i).animate({ height: "500px" }, {
duration: 400,
done: function () {
$(j).animate({ width: "500px"});
}
});
};
(done can be replaced by (the old option) complete, or always; always, done, and fail are the now-popular Promise events)
EDIT: Just to clarify, the reason I suggest this is three-fold:
1) You don't need to provide attributes that don't matter to you (in this case, easing),
2) If you decide other attributes matter, they're often trivial to add,
3) (Most importantly) when you're editing the code later you'll understand exactly what the nested function is for without having to think about it.

jQuery animating issues?

So far, I have a pretty decent code to animate a center box. For some reason, when I preview this on different browsers and computer (Mac and PC) I get different results. One may show a faster animation speed, while the other is perfect. I have also noticed that when the box is being animated from left to right, there is a stutter, and the animation jerks. I can't really explain it more than that. My code is below:
$(document).ready(function(){
isAnimating = false;
$('.wrapper').on('click', '.arrow-left', function() {
if(isAnimating) return;
isAnimating = true;
var $current = $(this).parents('.signupBox');
var $next = $(this).parents('.signupBox').next();
$current.stop(true,true).animate({
left: "200%"
}, 500, 'linear', function() {
$current.css({
left: "-200%"
}).appendTo('.wrapper'); // move to end of stack
$next.css({
left: "-200%"
}).stop(true,true).animate({
left: "0%"
}, 500, 'linear', function() {
isAnimating = false;
});
});
}).on('click', '.arrow-right', function() {
if(isAnimating) return;
isAnimating = true;
var $current = $(this).parents('.signupBox');
var $next = $(this).parents('.signupBox').siblings().last();
$current.stop(true,true).animate({
left: "-200%"
}, 500, 'linear', function() {
$current.css({
left: "200%"
});
$next.prependTo('.wrapper') // move to front of stack
.css({
left: "200%"
}).stop(true,true).animate({
left: "0%"
}, 500, 'linear', function() {
isAnimating = false;
});
});
});
});
Some CSS:
.signupBox:first-child {
display: block;
}
.signupBox {
display: none;
}
.wrapper
{
overflow: hidden;
width: 100%;
height: 100%;
margin: 0px;
padding: 0px;
position: absolute;
}
Here's a JSFiddle showing what happens, hopefully you can see what's the issue from there.
Then animation from right to left (click on the < symbol and you will see a slign change in speed.
Different browsers could have different results when using JS and css transitions, it actually depends on your machine speed, browser speed as well. It could depend on how many opened tabs you have in each browser, browsers plugins could freeze animations as well. Other JS events as well.
I have tested your code in Chrome, FF (wasn't able to check it in IE11, it seems there are JS errors on jsfiddler using jQuery). Didn't mentioned something strange.
I could recommend to use Greenshock JS animating library. http://www.greensock.com/get-started-js/
They say it's x20 times faster jQuery animate. But i think actually may be in 2,3 )
That libruary is based on Flash library that was used by Action Script coders to create beauty animations in Flash.

Categories

Resources