4

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.

alexmngn
  • 9,107
  • 19
  • 70
  • 130
  • 1
    Post the code you are using to see if i can replicate it.. if you are not able to make it work with the code i provided – Guille Dec 21 '15 at 01:18
  • 1
    I think that this answer could help you http://stackoverflow.com/a/29682311/1926369 – vals Dec 21 '15 at 12:05

3 Answers3

6

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.

Community
  • 1
  • 1
Harry
  • 87,580
  • 25
  • 202
  • 214
  • 2
    Can be achieved, but using several elements. All the problems that you explain are absolutely true, but apply only to trying to solve it in a single element (I am not saying that it's the best way to do it .... Just that it is possible.) See my comment above. – vals Dec 21 '15 at 12:10
  • Interesting @vals. I could swear that I tried the exact same thing and it didn't work here. I am going to try it once again. – Harry Dec 21 '15 at 12:23
  • It works brilliantly @vals. I guess I must have made some mistake. Thank you very much for the suggestion/link. Would you mind if I edit in a snippet based on your answer (and ofcourse link to your original one) within this answer? – Harry Dec 21 '15 at 12:34
  • 1
    Of course I don't mind. Feel free to do whatever you want – vals Dec 21 '15 at 14:00
  • Thanks again @vals. I would have deleted my answer if it had the exact same information as in the other thread but I felt that my original points could atleast help future users understand why it is not possible using single element. Am wondering if I should ask a mod to merge this answer into that one. – Harry Dec 21 '15 at 14:15
1

Make a class with a CSS transition for the background position, and when the button is clicked, add that class and remove your animation class at the same time. Make sure you use the same units (pixels, percentages...) in both cases.

Shomz
  • 37,421
  • 4
  • 57
  • 85
  • That's what I tried, but it doesn't work. I'm adding the class to the element that pause the animation and create the transition, then I add the background-position using jQuery to the element but it doesn't want to transition and stop completely... – alexmngn Dec 21 '15 at 01:17
  • No, this approach will not work because once you remove the animation, the elements would snap back to their original position. Have a look at this answer - http://stackoverflow.com/questions/32142484/combination-of-animation-and-transition-not-working-properly/32142949#32142949. Firefox also handled it gracefully only sometimes (like first time or something like that) and not always. – Harry Dec 21 '15 at 03:58
  • @Harry, wow, I could swear I've seen it work like that. Are you sure it can't be done using `animation-play-state`? I just tried it, but didn't work... it would be really stupid if something like this can't be done, or works only occassionally. – Shomz Dec 21 '15 at 04:12
  • @Shomz: Nope. It doesn't work. Play state change to pause is an immediate action and so the pause approach is not at all possible. The solution provided in answer is the ideal one but unfortunately browsers don't support it (or maybe somewhere the spec says it should not - I couldn't pinpoint this in the spec). Another thing to note is that, animations and transitions generally don't work well together. Here is a bugzilla thread which still exists - https://bugzilla.mozilla.org/show_bug.cgi?id=666464 – Harry Dec 21 '15 at 04:14
  • Wow, since 2011... Thank you, why don't you write an answer of your own here? – Shomz Dec 21 '15 at 04:27
  • 1
    @Shomz: Am trying all hacks to see if something can be done. So far not good, I don't think even JS solutions work in this case but want to absolutely make sure of it before adding an answer. Just a *No, it can't be done* is not of much use. – Harry Dec 21 '15 at 04:38
  • 1
    Well, one thing that comes to my mind is to rewrite the keyframes to transitions and use timeouts, it would be helpful if we had the original code though. – Shomz Dec 21 '15 at 04:40
  • 1
    @Shomz: I ended up adding a separate answer but it is still a *no* :( – Harry Dec 21 '15 at 05:34
0

You can probably achieve it by using java script to remove and add a new class after a button is clicked.

something like this. Basically just create a new css class with new that sets a specific amount of time for the animation to run.

For Example if the animation you have running, runs indefinitely, then just create a new_class with similar values but with a definite time like 2s or 4s etc. After those 4s the animation should stop.

 $(document).click(function (e) {
  $el = $(e.target);
 if ($el.hasClass('Button_class')) {
      $(".moving_image_class").toggleClass('new_class');

  } else {
      $(".moving_image_class").removeClass('New_class');
  }

});

Guille
  • 652
  • 1
  • 5
  • 19