I am trying to animate some cards that should enter the screen from the right, stop in the middle for a while, and then vanish to the left, in an infinite loop. This is what I tried:
function startAnimation(elem) { $('#' + elem).fadeIn(150).animate({ left: '0' }, 1500); } function endAnimation(elem) { $('#' + elem).animate({ left: '-200%' }, 1500); $('#' + elem).fadeOut(100).animate({ left: '200%' }, 300); } function scrollCards(elem, n) { startAnimation(elem); setTimeout(function() { endAnimation(elem); }, 700); elem += 1; elem = elem == n ? 0 : elem; return elem; } n = 3; var card = 0 var firstAnimationDone = false; $('#0').fadeIn(150); setInterval(function() { if (!firstAnimationDone) { endAnimation(card); card = 1; } card = scrollCards(card, n); firstAnimationDone = true; }, 4500);
/* (boxArticle is here just to keep static the part of the page where the animation takes place) */ .boxArticle { overflow: hidden; height: 100px; } .boxAchievements { position: relative; height: 100px; width: 100%; left: 200%; top: 5px; display: none; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <div class="boxArticle"> <article class="boxAchievements" id="0"> <h2>My achievements</h2> <p>Write 1</p> </article> <article class="boxAchievements" id="1"> <h2>My achievements</h2> <p>Write 2</p> </article> <article class="boxAchievements" id="2"> <h2>My achievements</h2> <p>Write 3</p> </article> </div>
When I add setTimeout
to the scrollCards
function it stops in the middle for a very long time, no matter how long is the interval I put in the method, and it desync the loop, so I have 2 cards moving simultaneously.
Advertisement
Answer
You could use the CSS animation
rule instead to achieve what you want with much less code. The solution below uses a trick that enables infinite animation to run with a delay between iterations (see, this Q&A, for example).
In short, animation duration is set with delay in mind, and @keyframes
controls the delay by keeping the same animated property value from some point to 100% (i.e. if it takes 2s, and the delay is 8s, then set the duration to 8+2=10s and finish the property change by 100*2/10=20%).
Then you add the class with animation
whenever you want. To align animations, add classes in sequence with a step equal to: duration + delay / number of elements.
Note that your CSS is changed to properly align <article>
elements because of the removal of fadeIn
/ fadeOut
method calls and display: none;
rule.
(() => { $('#0').addClass("middle"); setTimeout(() => $("#1").addClass("middle"), 5e3); setTimeout(() => $("#2").addClass("middle"), 9e3); })();
body { margin: 0; } :root { --middle : calc(50% - 25vw / 2); --left : calc(0% - 25vw); --duration : 12s; } .boxArticle { position: relative; overflow: hidden; height: 100vh; width: 100vw; } .boxAchievements { position: absolute; height: 100px; width: 25vw; left: 200%; top: 5px; } .middle { animation: middle var(--duration) linear 0s normal infinite forwards running, left var(--duration) linear 0s normal infinite forwards running; } @keyframes middle { 8.3%, 100% { left: var(--middle); } } @keyframes left { 8.3%, 24.9% { left: var(--middle); } 33.2%, 100% { left: var(--left); } }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <div class="boxArticle"> <article class="boxAchievements" id="0"> <h2>My achievements</h2> <p>Write 1</p> </article> <article class="boxAchievements" id="1"> <h2>My achievements</h2> <p>Write 2</p> </article> <article class="boxAchievements" id="2"> <h2>My achievements</h2> <p>Write 3</p> </article> </div>
There are also a couple of notes about the code in your snippet:
Do not mix types of variables. Although JavaScript allows that, this is a source of nightmares for anyone who will read your code (including you a year from now). In particular,
scrollCards
has a parameterelem
which is supposed to be anElement
, not anumber
(or vice versa).Use a recursive
setTimeout
rather thansetInterval
– the latter queues up a function call regardless of whether the previous animation is finished or not (there are other reasons to use recursivesetTimeout
that are outside of the question scope).Declare
n
withvar
(better still – do not declare any global variables, but at least avoid creating implied globals by omitting a declaration keyword).setTimeout
calls are not guaranteed to run after a specified amount of time as they are asynchronous – depending on a page load, the risk of completely desynchronized animations increases with time.One way to mitigate that is to use promises to wait until the timeout fires, but aligning item animations with that will likely be a difficult task. As an illustration, here is how you make
scrollCards
wait forendAnimation
to happen:
(() => { const now = () => new Date().toISOString(); const startAnimation = (elem) => console.log(`started animation at ${now()}`); const endAnimation = (elem) => console.log(`ended animation at ${now()}`); async function scrollCards(elem, n) { startAnimation(elem); //assuming endAnimation is synchronous await new Promise((resolve) => setTimeout((elem) => resolve(endAnimation(elem)), 700, elem)); elem += 1; //see #1 - this is error-prone elem = elem == n ? 0 : elem; return elem; }; scrollCards(0,1); })();