How do I pause an animation?

I am working on a code for a subway map that has moving trains.
I have already made an animation loop, but I want to make it pause for 10 seconds at every station on the map.
So basically I am looking for a sequence that goes like this:

Move 20 px.
Stop for 10 sec.
Move 20 px.
Stop for 10 sec.

window.onload = () => {
    startSetTrain0Animation();
    startSetTrain1Animation();
  };
  
  function startSetTrain0Animation() {
    const refreshRate = 1000 / 60;
    const maxXPosition = 470;
    let rect = document.getElementById('rect0');
    let speedX = 0.02;
    let positionX = 25;
  
    window.setInterval(() => {
      positionX = positionX + speedX;
      if (positionX > maxXPosition || positionX < 25) {
        speedX = speedX * (-1);
      }
      rect.style.top = positionX + 'px';
    }, refreshRate);

  }

Hi,

As you are doing your animating inside a setInterval, it’s going to be tricky to introduce a delay between animations. What you’ll need to do is to cancel the interval once 20px has been moved, introduce a delay, then kick the animation function back off again.

Let me explain that a bit more with a simplified example.

Here’s an animation function similar to yours that bounces a square from left to right over and over again.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Animation without delay</title>
  <style>
    #container {
      width: 400px;
      height: 50px;
      position: relative;
      background: yellow;
    }
    .square {
      width: 50px;
      height: 50px;
      position: absolute;
      background-color: red;
    }
  </style>
</head>
<body>
  <div id="container">
    <div class="square"></div>
  </div>

  <script>
    function animate(elem) {
      let pos = 0;
      let dir;

      setInterval(() => {
        if (pos === 0) dir = 'right';
        if (pos === 350) dir = 'left';

        dir === 'right' ? pos++ : pos--;
        elem.style.left = `${pos}px`;
      }, 5);
    }

    const square = document.querySelector('.square');
    animate(square);
  </script>
</body>
</html>

As in your example, the animation is being done using setInterval.

Now imagine we wanted to introduce a delay once the square had reached its maximum position at either end, we’d need to modify the script like so:

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function animate(elem, pos=0, dir='right') {
  const i = setInterval(async () => {
    dir === 'right' ? pos++ : pos--;
    elem.style.left = `${pos}px`;

    if (pos === 0 || pos === 350) {
      dir = dir === 'right' ? 'left' : 'right';
      clearInterval(i);
      await sleep(2000);
      animate(elem, pos, dir);
    }
  }, 5);
}

const square = document.querySelector('.square');
animate(square);

The sleep function simulates a delay by using the setTimeout method to resolve a Promise after a given number of milliseconds.

The animate function has also been modified to make the anonymous callback passed to setInterval async. This means that within the callback, we can await the sleep function (i.e. pause execution until the allotted time has passed). After that we then tell the function to call itself with the correct parameters.

This gives you:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Animation with delay</title>
  <style>
    #container {
      width: 400px;
      height: 50px;
      position: relative;
      background: yellow;
    }
    .square {
      width: 50px;
      height: 50px;
      position: absolute;
      background-color: red;
    }
  </style>
</head>
<body>
  <div id="container">
    <div class="square"></div>
  </div>

  <script>
    function sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    function animate(elem, pos=0, dir='right') {
      const i = setInterval(async () => {
        dir === 'right' ? pos++ : pos--;
        elem.style.left = `${pos}px`;

        if (pos === 0 || pos === 350) {
          dir = dir === 'right' ? 'left' : 'right';
          clearInterval(i);
          await sleep(2000);
          animate(elem, pos, dir);
        }
      }, 5);
    }

    const square = document.querySelector('.square');
    animate(square);
  </script>
</body>
</html>

Which you should hopefully be able to adapt to do what you want.

1 Like

Thank you so much!
I am going to try it out =)

Ah, one more question.

so I did manage to give the container more stops, by writing ‘right’? ‘right’ : ‘right’ but how do I make it go back again? I tried ‘right’ ? ‘right’ ? ‘right’ ? ‘left’ : ‘right’; But that does not work

        if (pos === 0 || pos === 150  || pos === 250) || pos === 350 {
          dir = dir === 'right' ? 'right' : 'right';
 

Can you post a working example in CodePen (for example), then I’ll take a look.

Sure. I am sorry it looks messy though.
This is for an exam hand-in, that is due in a few hours, so I didn´t have much time to filter out all the content. I have only deleted all the other html content so that you only have the part I wanna work on.

The list elements got messed up during the transfer to codepen, but I think you get the idea of what I am trying to do. I have a subway map with 12 stops going from top to bottom. I tried adjusting the container so that it would move from top to bottom, The container and square styling is located at line 432 in the css

I see no animation. Am I missing something? I was under the impression that you had it working in one direction.

I did when I used your html and script as an isolated example. But once I started mixing it with my own code, it got messed up.
Here is a clean example.

Eventually this is what I am going for.
line

Nor I. I see no JavaScript.

I’m fairly sure it could be done with CSS keyframes alone with no JavaScript. If the task is to learn some JavaScript rather than achieve a result, do you have some?

I posted a new test code above. The other got messed up because I included it in my big project file, which has a ton of ES6 modules and other stuff =)

I know it can be done with CSS, but the requirement for this project is that everything needs to be coded in ES6.
This is the last part of the code I am struggling with before finishing the project

Aha! the CSS isn’t in the CSS pane and the JavaScript isn’t in the JS pane. AFAIK, that sometimes makes a difference. anyway …

  <style>
    #container {
      width: 400px;
      height: 50px;
      position: relative;
      background: yellow;
    }
    .square {
      width: 50px;
      height: 50px;
      position: absolute;
      background-color: red;
    }
  </style> 
 <script>
    function sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    function animate(elem, pos=0, dir='right') {
      const i = setInterval(async () => {
        dir === 'right' ? pos++ : pos--;
        elem.style.left = `${pos}px`;

        if (pos === 0 || pos === 150  || pos === 350) {
          dir = dir === 'right' ? 'right' :'right';
          clearInterval(i);
          await sleep(2000);
          animate(elem, pos, dir);
        }
      }, 5);
    }
    const square = document.querySelector('.square');

    animate(square);
  </script>

Sorry I’m lost. It looks like the square moves with pauses OK for me. What is it you want to do?

Hi and thank you for helping me.
In the example you gave me, the container moves from pos === 0 to pos === 150 and then back.
I wanted to try and make it stop a few more times, like I would on my map.
So I tried pos === 0 || pos === 150 || pos === 350.
The problem is that in order for it to continue from 150 to 350, I had to change

dir = dir === 'right' ? 'left' : 'right';

to

dir = dir === 'right' ? 'right' : 'right';

But then, after reaching pos 350, it keeps moving right, until it leaves the screen. I wanted it to go back to 150, and then back to 0. But efter when I add ‘left’ it does not work.

Does it makes sense?

1 Like

I’m glad I asked. I thought it might be you wanted the right side to get the background color.

Something like ?

  • when sq < pos, move sq right
  • when sq = pos, pause
  • after pause, move sq right
  • continue until sq = end
  • when sq = end, pause
  • after pause, reset sq, repeat

Hi there marc87dk,

you can see a CSS example here…

https://codepen.io/coothead/full/oNgZBOP

coothead

1 Like

Yeah exactly.
I actually just uploaded my exam project, which sadly has become unfinished, because I spent 3 days trying to figure this out. I would rather have spent that on making the page resposinve, sort of my css files etc.
http://handig.dk/EXAM2019/index.html
If you scroll down to the button saying “tjek driften”, then you will see the current animation that I have, which is the ES6 code I posted in my first post.

I wish I could just use the CSS solution, because that is exactly what I need, but my teacher insist that we ONLY rely on ES6. Even using jquery or react will mean that I fail the exam.
It is night time in my country now, so I am trying to save this project while it is not being checked. It is my first web project, so it annoys the heck out of me that I had to leave it unfinished because a simple animation…

What I want is similar to what you wrote.

In my case I want it to:

move down.
pause
move down
pause
move down
pause

and then once it reach the bottom, I want the same animation, but in reverse,
so:

move up
pause
move up
pause
move up.

Hi,

Sorry to hear that.

If you’d still like to figure out how the animation is done, please make a CodePen with only the code for the animation (i.e. what you see if you hit the “tjek driften”button).

I’m sure it won’t be too hard to have it do what you want.

Hi James.

I have set up the code in Codepen now, and have arranged it like it is on my site. I would still very much like to figure out how to get this done.

Hi there marc87dk,

here is a working example…

https://codepen.io/coothead/full/RwNpYrO

…for you to play with. :biggrin:

coothead

1 Like

Thanks for the Code Pen.

As you can see, coothead has given you a solution already, but his version only deals with the one train, so I thought I’d post what I had.

There are a couple of things to be aware of.

  • You initially had <div> elements nested in a <ul> element. This is invalid markup, so I moved them outside of it.
  • The code that you posted had two more-or-less identical functions to animate each train. That’s not usually what you want to do (imagine if you had fifty trains). Rather, you should try and write more generic functions and pass in values as variables with sensible names.

That said, here’s a demo.

And here’s teh codes. I’ve commented the JavaScript so you can see what’s going on

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Animated trains, innit?</title>
  <style>
    .drift li a{
      position:absolute;
      color: Black;
      text-decoration:none;
      padding-left: 50px;
      width: 200px;
    }

    .drift{
      position:absolute;
      margin: 0;
      padding: 0;
      width:auto%;
      background-color: white;
      padding-bottom: 10px;
    }

    .drift li:hover{
      background-color:#fff;
    }

    .drift li {
      position:relative;
      text-decoration:none;
      list-style-type:none;
      width:30px;
      height:30px;
      border:5px solid #900;
      border-radius: 100%;
      margin-right:50px;
      font-size: 1.0em;
      margin-top: 10px;
    }

    .drift li::before {
      position: absolute;
      content: "";
      width:42px;
      border-left:5px solid #900;
      border-right:0px solid #900;
      height:15px;
      margin-left:12px;
      margin-top:30px;
    }

    .drift li:last-child::before {
      display:none;
    }

    .drift li:last-child {
      margin-right:0px;
    }

    .vertical ul{
      position:absolute;
      width:100%;
      padding-left:0px;
    }

    .vertical li a{
      position:absolute;
      color: white;
      padding-left: 40px;
      width: 200px;
      color:white;
      text-decoration:none;
      letter-spacing: 1px;
    }

    .rect {
      position: absolute;
      transform: translate(-26%, -50%);
      width: 34px;
      height: 34px;
      border-radius: 50%;
      margin-left: 12px;
      z-index: 3000;
    }

    .train0, .train1 {
      background:green;
    }
  </style>
</head>
<body>
  <div id="rect0" class="rect train0"></div>
  <div id="rect1" class="rect train1"></div>

  <ul class="drift">
    <li><a href="">Christiansbjerg</a></li>
    <li><a href="">Katrinebjerg</a></li>
    <li><a href="">Universitetet</a></li>
    <li><a href="">Fynsgade</a></li>
    <li><a href="">Den Gamle By</a></li>
    <li><a href="">Folkestedet</a></li>
    <li><a href="">Kunsthallen</a></li>
    <li><a href="">Bispetorvet</a></li>
    <li><a href="">Banetorvet</a></li>
    <li><a href="">Ndr. Kirkegård</a></li>
    <li><a href="">Risskov</a></li>
    <li><a href="">Vejlby</a></li>
  </ul>

  <script>
    function sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    function startAnimation(opts){
      let { el, startPos, endPos, step, dir, duration } = opts;
      el.style.top = `${startPos}px`;

      function animateToNextStation(currPos) {
        // Work out position of next stop
        let nextPos;

        if (dir === 'down') {
          nextPos = currPos + step;
        } else {
          nextPos = currPos - step;
        }

        // Move 50px, then pause for 1 second
        const i = setInterval(async () => {

          dir === 'down' ? currPos++ : currPos--;
          el.style.top = `${currPos}px`;

          if (currPos === nextPos) {
            // We have reached the next station
            clearInterval(i);
            await sleep(5000);

            // Do we need to change direction?
            if(startPos < endPos) {
              // Our initial position was at the top of the line
              if(nextPos >= endPos || nextPos <= startPos) dir = dir === 'up' ? 'down' : 'up';
            } else {
              // Our initial position was at the bottom of the line
              if(nextPos <= endPos || nextPos >= startPos) dir = dir === 'up' ? 'down' : 'up';
            }

            animateToNextStation(nextPos);
          }
        }, duration / step);
      }

      animateToNextStation(startPos);
    }

    window.onload = () => {
      const train0 = document.querySelector('.train0');
      const train1 = document.querySelector('.train1');

      startAnimation({
        el: train0,
        startPos: 38,
        endPos: 588,
        step: 50,
        dir: 'down',
        duration: 10000
      });

      startAnimation({
        el: train1,
        startPos: 588,
        endPos: 38,
        step: 50,
        dir: 'up',
        duration: 10000
      });
    };
  </script>
</body>
</html>

HTH. Any questions, let me know.

1 Like

Yes, that is very true. :wonky:

Unfortunately, with two trains on a single line there
is a head on collision midway between Folkestedet
and Kunsthallen. :eek:

With safety considerations foremost in my mind, it
seemed prudent to have just one train to make the
journey and when it had reached it’s destination
then allow it to return safely home. :biggrin:

coothead

2 Likes