Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animation paused with transition

I have an animation moving the background-position of an image with keyframes, and this works fine.

Although, when the user click a button, I would like to pause the animation of the background but with a transition, slowing down then stopping the background-position from moving.

I searched for something similar to that but I didn't find anything, so I was wondering if any of you would have an idea how I could do that?

Thanks.

like image 601
alexmngn Avatar asked Mar 13 '23 20:03

alexmngn


1 Answers

NO. You cannot achieve this with CSS animations while using only one element for a few reasons and they are as follows:

  • An animation's play state change to paused is an immediate action.
  • Once an animation is applied on an element, it takes control of the property until it is removed. So, changing background-position with JS will also have no effect (even if it is actually paused).

    From W3C Spec:

    CSS Animations affect computed property values. During the execution of an animation, the computed value for a property is controlled by the animation. This overrides the value specified in the normal styling system.

    A sample can be seen in the below snippet. Along with the animation-play-state: paused, the class that is toggled on also sets a specific value for background-position (with important) but it has absolutely no effect on the element. The commented out portions of the JS is how I had tried to produce a gradual stop at a slower rate but it just does not work.

    var el = document.getElementsByTagName('div')[0];
    var btn = document.getElementById('stop');
    var i = 0,
      bgPosX,
      interval,
      state = 'running';
    
    btn.addEventListener('click', function() {
    /*  state = (state == 'running') ? 'paused' : 'running';
      console.log(state);
      if (state == 'paused') {
        var currPos = window.getComputedStyle(el).backgroundPositionX;
        bgPosX = parseInt(currPos.replace('px', ''), 10);
        interval = setInterval(function() {
          slowPause(bgPosX);
        }, 1);
      } else {
        el.style.backgroundPosition = null;
      } */
      el.classList.toggle('paused');
    });
    
    /*function slowPause(currPosX) {
      el.style.backgroundPosition = currPosX + (i * 0.2) + 'px 0%';
      console.log(el.style.backgroundPositionX);
      i++;
      if (i >= 1000) {
        clearInterval(interval);
        i = 0;
        console.log('cleared');
      }
    }*/
    .animated-bg-pos {
      height: 100px;
      width: 200px;
      background-image: linear-gradient(to right, red 25%, blue 25%, blue 50%, orange 50%, orange 75%, green 75%);
      background-size: 800px 100%;
      animation-name: animate-bg-pos;
      animation-timing-function: linear;
      animation-iteration-count: infinite;
      animation-duration: 4s;
    }
    body div.animated-bg-pos.paused {
      animation-play-state: paused;
      background-position: 200px 0% !important;
    }
    @keyframes animate-bg-pos {
      from {
        background-position: 0px 0%;
      }
      to {
        background-position: 800px 0%;
      }
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
    <div class='animated-bg-pos'></div>
    
    <button id='stop'>Stop Animation</button>
  • Removal of the animation will cause the element to snap back to its original position immediately. It does not transition back gradually. You can read a bit about it here.

  • Applying transition and animation on the same property doesn't work well across browsers. It still results in the animation taking full control and renders the transition useless. Read more here.
  • Finally, even if you somehow get the removal of animation and use JS to slowly transition it to an intermediate state this will not resemble the "pause" behavior. The reason is because when you re-apply the animation it will again start from the first frame and not where you stopped it.

Considering all this, you would have to rewrite your entire animation using jQuery or JavaScript if you have to get the required behavior. A very rough implementation with timers and constant change of the background-position is available in the below snippet.

/* initial variable declarations */
var el = document.getElementsByTagName('div')[0];
var btn = document.getElementById('stop');
var i = 0,
  currPos = 0,
  pauseInterval, runningInterval,
  running = true,
  bgSize = parseInt(window.getComputedStyle(el).backgroundSize.split('px')[0], 10);

/* toggle button listener */
btn.addEventListener('click', function() {
  running = !running; // toggle state
  if (!running) {
    i = 0;
    currPos = parseInt(el.style.backgroundPositionX.replace('px', ''), 10);
    clearInterval(runningInterval); /* stop the normal animation's timer */
    pauseInterval = setInterval(function() {
      slowPause(currPos); /* call the slow pause function */
    }, 10);
  } else {
    i = 0;
    currPos = parseInt(el.style.backgroundPositionX.replace('px', ''), 10);
    clearInterval(pauseInterval); /* for safer side, stop pause animation's timer */
    runningInterval = setInterval(function() {
      anim(currPos); /* call the normal animation's function */
    }, 10);
  }
});

/* slowly pause when toggle is clicked */
function slowPause(currPosX) { 
  el.style.backgroundPosition = (currPosX + (i * .025)) % bgSize  + 'px 0%';
  i += 10;
  if (i >= 1000) { /* end animation after sometime */
    clearInterval(pauseInterval);
    i = 0;
  }
}

/* increment bg position at every interval */
function anim(currPosX) { 
  el.style.backgroundPosition = (currPosX + (i * .25)) % bgSize + 'px 0%';
  i += 10;
}

/* start animation on load */
runningInterval = setInterval(function() { 
  anim(currPos);
}, 10);
.animated-bg-pos {
  height: 100px;
  width: 200px;
  background-image: linear-gradient(to right, red 25%, blue 25%, blue 50%, orange 50%, orange 75%, green 75%);
  background-size: 800px 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class='animated-bg-pos'></div>

<button id='stop'>Toggle Animation State</button>

Note: The above snippet is only a rough implementation to give an idea. I am not a JS expert and feel that the JS used in the snippet can be optimized a lot.


If you don't have a problem with adding extra wrapper elements around the one that is being animated then you can make use of the approach mentioned in vals' answer here.

var el = document.getElementsByTagName('div')[1];
var btn = document.getElementById('stop');

btn.addEventListener('click', function() {
  el.classList.toggle('paused');
});
.cover {
  position: relative;
  width: 200px;
  height: 100px;
  border: 1px solid;
  overflow: hidden;
}
.animated-bg-pos {
  position: absolute;
  top: 0px;
  left: 0px;
  height: 100px;
  width: 240px;
  left: -40px;
  background-image: linear-gradient(to right, red 25%, blue 25%, blue 50%, orange 50%, orange 75%, green 75%);
  background-size: 800px 100%;
  animation: animate-bg-pos 4s linear infinite;
  transition: transform 1s ease-out;
}
.paused {
  animation-play-state: paused;
  transform: translateX(40px);
}
@keyframes animate-bg-pos {
  from {
    background-position: 0px 0%;
  }
  to {
    background-position: 800px 0%;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class='cover'>
  <div class='animated-bg-pos'></div>
</div>
<button id='stop'>Stop Animation</button>

Note: All credits for the above approach goes to vals. I have only adapted his approach to make this answer more complete.

like image 180
Harry Avatar answered Mar 21 '23 00:03

Harry