Click to go next position in live animation

Viewed 150

I created an animation that goes from right to left, and I am trying to add some jump on click of next box button.

How can I apply it using JavaScript ?

nextBox function should apply the jump from box1 to box2 and so on... ( for example )

const nextBox = () => {
  alert("nextBox");
  // replace => Jump to next box position +=100px to right
};
body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

.container {
  background-color: aqua;
  height: fit-content;
  width: 500px;
  overflow-x: hidden;
  position: relative;
}

.anim {
  animation: anim 30s infinite linear;
  display: flex;
}

.anim > .box {
  width: 90px;
  height: 90px;
  margin: 5px;
  background-color: blue;
  flex-shrink: 0;
}

.container > button {
  position: absolute;
  left: 0px;
  top: 32px;
  width: 50px;
}

@keyframes anim {
  to {
    transform: translateX(-1000px);
  }
}
<div class="container">
  <div class="anim">
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
  </div>
  <button onclick="nextBox()">next box</button>
</div>

codepen here

3 Answers

The following implementation should work since it does what you want.

Here we have another boxContainer div to apply transition to and let the animation remain on anim class wrapper div. Also we can have another div of class pseudoBox whose width we increase by 100px to fill out the empty spacing we encounter later and to keep the order of box divs constant, we keep on appending the first box class div at the end in a cyclic fashion. I have numbered the boxes for better understanding. So our original no of box divs remain constant.

let jump = 0;
const anim = document.querySelector('.anim');
const boxContainer = document.querySelector('.boxContainer');
const pseudoBox = document.querySelector('.pseudoBox');

const animate = () => {
  boxContainer.style.transform = `translate(${jump}px)`;
  pseudoBox.style.width = `${jump*-1}px`;
  boxContainer.appendChild(boxContainer.children[1]);
}

const nextBox = () => {
  jump -= 100;
  requestAnimationFrame(animate);
};
body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

.container {
  background-color: aqua;
  height: fit-content;
  width: 500px;
  overflow-x: hidden;
  position: relative;
}

.anim {
  animation: anim 30s infinite linear;
}

.pseudoBox {
  flex-shrink: 0
}

.boxContainer {
  display: flex;
  transition: transform 0.2s;
}

.boxContainer>.box {
  width: 90px;
  height: 90px;
  margin: 5px;
  background-color: blue;
  flex-shrink: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
}

.container>button {
  position: absolute;
  left: 0px;
  top: 32px;
  width: 50px;
}

@keyframes anim {
  to {
    transform: translateX(-1000px);
  }
}
<div class="container">
  <div class="anim animateStuff">

    <div class="boxContainer">
      <div class="pseudoBox"></div>
      <div class="box">1</div>
      <div class="box">2</div>
      <div class="box">3</div>
      <div class="box">4</div>
      <div class="box">5</div>
      <div class="box">6</div>
      <div class="box">7</div>
      <div class="box">8</div>
      <div class="box">9</div>
      <div class="box">10</div>
      <div class="box">11</div>
      <div class="box">12</div>
      <div class="box">13</div>
      <div class="box">14</div>
      <div class="box">15</div>
      <div class="box">16</div>
    </div>
  </div>
  <button onclick="nextBox()">next box</button>
</div>

With my solution I converted the css keyframe to the javascript animation loop. You probably didn't want that but I just found it a lot easier to control this animation with javascript, as you wanted to go to the next box, rather than just +100. I used CSS variables in the :root to control the translateX of the anim element. Hope it's what you were after

<div class="container">
  <div class="anim">
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
  </div>
  <button>next box</button>
</div>
:root {
  --tX: 'transform: translateX(0px)';
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

.container {
  background-color: aqua;
  height: fit-content;
  width: 500px;
  overflow-x: hidden;
  position: relative;
}

.anim {
  transform: var(--tX);
  display: flex;
}

.anim > .box {
  width: 90px;
  height: 90px;
  margin: 5px;
  background-color: blue;
  flex-shrink: 0;
}

.container > button {
  position: absolute;
  left: 0px;
  top: 32px;
  width: 50px;
}

const nextBtn = document.querySelector('button')

const anim = document.querySelector('.anim')
  let computed = window.getComputedStyle(anim) // get the computed style (read the transform property)

nextBtn.addEventListener('click', (e) => {
  // Get the transform property
  let transform = computed['-webkit-transform']
  // read it from the css matrix(n,n,n,n,n,n)
  //  also negate the value to make rounding easier for me
  let tX = -1 * parseInt(transform.split(',')[4].trim());

  // this gives us the translateX value
  
  // round up to nearest 100 - box size
  //  take 10 - account for margin
  tX = (Math.ceil(tX/100)*100) - 10
  
  // negate again 
  tX *= -1
  
  console.log(tX)
  
  // set our CSS variable to this value
  document.documentElement.style.setProperty('--tX', `translateX(${tX}px)`)
})

// animation loop
function loop() {
  // get the transform property each frame
  let transform = computed['-webkit-transform']
  let tX = 0;
  try {
    tX = transform.split(',')[4].trim();    
  } catch (TypeError) { 
    // probably because of being not applied in css yet
    tX = 0;
  }
  
  // loop the translateX - don't go below -1000
  if (tX <= -1000) {
    tX = 0;
  }

  // perform animation by subtracting 2 from translateX each frame
  document.documentElement.style.setProperty('--tX', `translateX(${tX-2}px)`)
  
  // perform this at 60FPS
  setTimeout(() => {
    requestAnimationFrame(loop)    
  }, 1000 / 60)
}
loop()

also link to codepen https://codepen.io/sean-b765/pen/rNjjLxM

after I saw @Lakshya Thakur answer to my post I found another way to achieve it with clean html and CSS, without pseudoBox element only JavaScript way

let jump = 0;
let index = 0;

const boxContainer = document.querySelector(".boxContainer");

const animate = () => {
  boxContainer.style.transform = `translate(${jump}px)`;
  boxContainer.appendChild(boxContainer.children[index].cloneNode(true));
  index++;
};

const nextBox = () => {
  jump -= 100;
  requestAnimationFrame(animate);
};
body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

.container {
  background-color: aqua;
  height: fit-content;
  width: 500px;
  overflow-x: hidden;
  position: relative;
}

.anim {
  animation: anim 30s infinite linear;
}

.boxContainer {
  display: flex;
  transition: transform 0.2s;
}

.boxContainer > .box {
  width: 90px;
  height: 90px;
  margin: 5px;
  background-color: blue;
  flex-shrink: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
}

.container > button {
  position: absolute;
  left: 0px;
  top: 32px;
  width: 50px;
}

@keyframes anim {
  to {
    transform: translateX(-1000px);
  }
}
<div class="container">
  <div class="anim">

    <div class="boxContainer">
      <div class="box">1</div>
      <div class="box">2</div>
      <div class="box">3</div>
      <div class="box">4</div>
      <div class="box">5</div>
      <div class="box">6</div>
      <div class="box">7</div>
      <div class="box">8</div>
      <div class="box">9</div>
      <div class="box">10</div>
      <div class="box">11</div>
      <div class="box">12</div>
      <div class="box">13</div>
      <div class="box">14</div>
      <div class="box">15</div>
      <div class="box">16</div>
    </div>
  </div>
  <button onclick="nextBox()">next box</button>
</div>

credit to Lakshya Thakur for first idea

Related