Home > Software engineering >  How to use javascript to dynamically set css animation's keyframe?
How to use javascript to dynamically set css animation's keyframe?

Time:02-05

I want to make an animation on my product page. When user clicks "add to cart" the product image will be animated moving and shrinking to the cart icon in the nav bar. Here is a sample html

$('div.test').on('animationend', (e) => {
  $(e.target).remove();
})
//user click
$('div.test').addClass('animateTest');
.test {
  position   : fixed;
  top        : 200px;
  left       : 600px;
  background : red;
  width      : 200px;
  height     : 300px;
  }
@keyframes toCart {
  25% {
    top    : 850px;
    left   : 550px;
    width  : 200px;
    height : 300px;
    }
  100% {
    top    : 100px;
    left   : 1100px;
    width  : 0;
    height : 0
    }
  }
.animateTest {
  animation : toCart 2s;
  /* animation-fill-mode: forwards; */
  }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div >

</div>

The hard part is, since users' viewports vary, I probably need to use javascript to get the cart icon's position(unless I can get it from CSS which I don't think is possible):

whereIsCart = $('#cartIcon').offset()

and I need to do something like

  100% {
    top    : whereIsCart.top;
    left   : whereIsCart.left;
    width  : 0;
    height : 0
  }

But how can I do this? Or, is there any better practice to achieve the same goal?

CodePudding user response:

It may be easier to use css transitions instead of keyframe animations:

.test {
    // ...
    transition: transform 1s ease-in-out;
}
// on click
whereIsCart = $('#cartIcon').offset();
$('div.test').css('transform', 'translate('   whereIsCart.left   'px, '   whereIsCart.top   'px) scale(0)');

CodePudding user response:

When working with CSS in JavaScript you may want to use the CSSOM API; more specifically, its factory functions for unit values, e.g. CSS.px(), CSS.percent().

Note that parts of the CSSOM API are still experimental, e.g. the factory functions. In production, you should make sure that the target browsers support the features you use.

Regardless of using CSS or JS for the animation itself: To get the element's current position in the viewport you can use Element.getBoundingClientRect(), or more generally Element.getClientRects() for all its relevant boxes.

CSS Custom Properties

You can use custom properties for the initial position. You can set them via JavaScript, and even provide a fallback value in CSS.

If you use them for the animating (not as animated) properties, it should just work:

const divTest = document.querySelector("div.test");
// Example for non-empty custom properties
divTest.style.setProperty("--top", CSS.px(20));
divTest.style.setProperty("--left", CSS.px(80));

// Should happen on click:
toCart(divTest);

function toCart(element) {
  const rect = element.getBoundingClientRect();

  element.style.setProperty("--top", CSS.px(rect.top));
  element.style.setProperty("--left", CSS.px(rect.left));
  element.classList.add("animateTest");
}
.test {
  position: fixed;
  top: var(--top, 10%);
  left: var(--left, 10%);
  width: 200px;
  height: 300px;
  background: red;
}
@keyframes toCart {
  25% {
    top: 80%;
    left: 50%;
    width: 200px;
    height: 300px;
  }
  100% {
    top: 10%;
    left: 100%;
    width: 0;
    height: 0;
  }
}
.animateTest {
  animation: toCart 2s;
}
<div ></div>

Sidenote: If you want to animate custom properties themselves, you have to define the in a @property rule. Otherwise CSS cannot animate it since its type may be anything (animating e.g. from a length to a color is impossible).

Web Animations API

In JavaScript, you can use the Web Animations API, which is essentially CSS animations but in JS.

You can define keyframes, duration, fill-mode and more. Since Animation.finished is a promise, you can simply react to the animation's end via await or Promise.then().

Example:

const divTest = document.querySelector("div.test");

// Should happen on click:
const animation = animateToCart(divTest);
animation.finished.then(() => console.log("Animation finished. This could start a new animation!"));

function animateToCart(element) {
  const rect = element.getBoundingClientRect();

  const keyframes = [
    {
      offset: .25,
      top: CSS.percent(80),
      left: CSS.percent(50),
      width: CSS.px(rect.width),
      height: CSS.px(rect.height)
    }, {
      top: CSS.percent(10),
      left: CSS.percent(100),
      width: 0,
      height: 0
    }
  ];
  
  return element.animate(keyframes,
    {
      duration: 2000,
      easing: "ease" // Is default in CSS, but not in Web Animations...
    }
  );
}
.test {
  position: fixed;
  top: 10%;
  left: 10%;
  width: 200px;
  height: 300px;
  background: red;
}
<div ></div>

Multi-step animations are also easily done with Web Animations, since you can start another animation after the first animation's promise has resolved.

CodePudding user response:

CSS variables sample code...

const
  bluElm = document.querySelector('#blue_elm')
, btAnim = document.querySelector('#bt-anim')
, btMovE = document.querySelector('#bt-movE')
, elTest = document.querySelector('.test')
  ;
btMovE.onclick = () =>
  { 
  bluElm.classList.toggle('move'); 
  }
btAnim.onclick = () =>
  {
  let rect = bluElm.getBoundingClientRect();

   /* change CSS variables values as style Property ------------- */
  elTest.style.setProperty('--p_top', `${rect.bottom}px`);
  elTest.style.setProperty('--p_left', `${rect.left}px`);

  elTest.classList.add('animateTest');
  }
elTest.onanimationend = () =>
  { 
  elTest.classList.remove('animateTest');
  }
#blue_elm {
  position : fixed;
  top      : 20px;
  left     : 300px;
  width         : 20px;
  height        : 20px;
  border-radius : 10px;
  background    : cornflowerblue;
  }
#blue_elm.move {
  top      : 50px;
  left     : 150px;
  } 
.test {
  position   : fixed;
  top        : 200px;
  left       : 600px;
  background : red;
  width      : 200px;
  height     : 300px;
  --p_top    : 0;       /* CSS variables  declaration */
  --p_left   : 0;
  }
.animateTest {
  animation : toCart 2s;
  }
@keyframes toCart {
  25% {
    top    : 850px;
    left   : 550px;
    width  : 200px;
    height : 300px;
    }
  100% {
    top    : var(--p_top);     /* CSS variables usage */
    left   : var(--p_left);
    width  : 0;
    height : 0
    }
  }
<button id="bt-anim"> show animation</button>

<button id="bt-movE"> move element  - 150px</button>

<div id="blue_elm"></div>

<div ></div>

  • Related