Search code examples
javascripttimingrequestanimationframe

How to properly paused a requestAnimationFrame slideshow?


I'm trying to learn how to use requestAnimationFrame API to control animation using js, in this case I'm using it in the context of a slideshow, the slideshow slides alright, it even has delay functionality, but I don't know how to properly pause & resume it, most of the time an element would got freezed when I hit pause and then resume, can anyone explain why this is happening & what's the solution to properly pause & resume a RAF. Thank y'all.

Here' my code:

var elems = document.querySelectorAll('.slider');
var total = elems.length;
var opacity = 1;
var dist = 300;
var duration = 1000;
var delay = 2000;
var index = 0;
var startTime;
var slideRaf;

function startRaf(timestamp) {
  var timestamp = timestamp;
  var runtime = timestamp - startTime;
  if (runtime <= duration) {
    var progress = Math.min((runtime / duration), 1);
    if (index === 0) {
      elems[index].style.transform = 'translate3d(' + (dist * progress) + 'px, 0px, 0px)';
      elems[index].style.opacity = (opacity * progress);
      elems[total - 1].style.transform = 'translate3d(' + (dist - (dist * progress)) + 'px, 0px, 0px)';
      elems[total - 1].style.opacity = opacity - (opacity * progress);
    } else {
      elems[index].style.transform = 'translate3d(' + (dist * progress) + 'px, 0px, 0px)';
      elems[index].style.opacity = (opacity * progress);
      elems[index - 1].style.transform = 'translate3d(' + (dist - (dist * progress)) + 'px, 0px, 0px)';
      elems[index - 1].style.opacity = opacity - (opacity * progress);
    }
  } else if (runtime > delay) {
    if (index < (total - 1)) {
      index += 1;
    } else {
      index = 0;
    }
    startTime = timestamp;
  }
  slideRaf = requestAnimationFrame(startRaf);
}

requestAnimationFrame(function (timestamp) {
  startTime = timestamp;
  startRaf(timestamp);
});

document.getElementById('start').addEventListener('click', function () {
  requestAnimationFrame(startRaf);
}, false);

document.getElementById('pause').addEventListener('click', function () {
  cancelAnimationFrame(slideRaf);
}, false);
* {
  box-sizing: border-box;
}
html, body {
  padding: 0;
  margin: 0;
  font-family: sans-serif;
  background: yellow;
  overflow: hidden;
}
.slider {
  position: relative;
  width: 150px;
  height: 50px;
  left: 50%;
  margin-left: -75px;
  background: rgb(168, 39, 219);
  opacity: 0;
}
#start {
  position: absolute;
  top: 20px;
  left: 20px;
}
#pause {
  position: absolute;
  top: 50px;
  left: 20px;
}
<div class="slider"></div>
<div class="slider"></div>
<div class="slider"></div>
<div class="slider"></div>
<div class="slider"></div>

<button id="start">START</button>
<button id="pause">PAUSE</button>


Solution

  • I finally got it, now it can pause and resume without having some element stuck in view, I use a variable to inform the pause/resume state (by doing this thus I exclude the cancelAnimationFrame), now it also slides into the exact distance, some things to note though are the code somewhat cluttered and the animation couldn't be paused in the middle of a transition so I make sure that after user hit pause button the last element would still slide to the target distance but after that stop from continuing until user hit start button to resume the slideshow. Here's the new code (if you have a better alternative feel free to share your solution in this post):

    var elems = document.querySelectorAll('.slider');
    var total = elems.length;
    var opacity = 1;
    var dist = 300;
    var duration = 1000;
    var delay = 2000;
    var index = 0;
    var startTime;
    var slideRaf;
    var clear = 0;
    var pause = false;
    
    function startRaf(timestamp) {
      var timestamp = timestamp;
      var runtime = timestamp - startTime;
      var progress = Math.min((runtime / duration), 1);
      if (index === 0) {
        var last = total - 1;
      } else {
        var last = index - 1;
      }
      if (runtime <= duration) {
        elems[index].style.transform = 'translate3d(' + (dist * progress) + 'px, 0px, 0px)';
        elems[index].style.opacity = (opacity * progress);
        elems[last].style.transform = 'translate3d(' + (dist - (dist * progress)) + 'px, 0px, 0px)';
        elems[last].style.opacity = (opacity - (opacity * progress));
      } else if (runtime > delay) {
        elems[index].style.transform = 'translate3d(' + dist + 'px, 0px, 0px)';
        elems[index].style.opacity = opacity;
        elems[last].style.transform = 'translate3d(0px, 0px, 0px)';
        elems[last].style.opacity = 0;
        if (pause === false) {
          if (index < (total - 1)) {
            index += 1;
          } else {
            index = 0;
          }
          clear = 0;
          startTime = timestamp;
        }
      } else {
        if (clear === 0) {
          elems[index].style.transform = 'translate3d(' + dist + 'px, 0px, 0px)';
          elems[index].style.opacity = opacity;
          elems[last].style.transform = 'translate3d(0px, 0px, 0px)';
          elems[last].style.opacity = 0;
          clear = 1;
        }
      }
      slideRaf = requestAnimationFrame(startRaf);
    }
    
    requestAnimationFrame(function (timestamp) {
      startTime = timestamp;
      startRaf(timestamp);
    });
    
    document.getElementById('start').addEventListener('click', function () {
      pause = false;
      requestAnimationFrame(startRaf);
    }, false);
    
    document.getElementById('pause').addEventListener('click', function () {
      pause = true;
    }, false);
    * {
      box-sizing: border-box;
    }
    html, body {
      padding: 0;
      margin: 0;
      font-family: sans-serif;
      background: yellow;
      overflow: hidden;
    }
    .slider {
      position: relative;
      width: 150px;
      height: 50px;
      left: 50%;
      margin-left: -187px;
      background: rgb(168, 39, 219);
      opacity: 0;
    }
    #start {
      position: absolute;
      top: 20px;
      left: 20px;
    }
    #pause {
      position: absolute;
      top: 50px;
      left: 20px;
    }
    <div id="slider" class="slider"></div>
    <div id="slider2" class="slider"></div>
    <div id="slider3" class="slider"></div>
    <div id="slider4" class="slider"></div>
    <div id="slider5" class="slider"></div>
    <div id="slider6" class="slider"></div>
    <div id="slider7" class="slider"></div>
    <div id="slider8" class="slider"></div>
    <div id="slider9" class="slider"></div>
    <div id="slider10" class="slider"></div>
    <div id="slider11" class="slider"></div>
    <div id="slider12" class="slider"></div>
    <div id="slider13" class="slider"></div>
    <div id="slider14" class="slider"></div>
    <div id="slider15" class="slider"></div>
    
    <button id="start">START</button>
    <button id="pause">PAUSE</button>