Tiny cart animation

by Julien Roche

HTML

<header class="main__header">
  <h1 class="main__header__label">My header</h1>
  <i class="main__header__cart"></i>
</header>

<section class="main__content">
  <div class="main__content__product">
    <img class="main__content__product__image" src="https://static.pexels.com/photos/126407/pexels-photo-126407.jpeg" />
    <div class="main__content__product__label">A</div>
  </div>
  
  <div class="main__content__product">
    <img class="main__content__product__image" src="https://static.pexels.com/photos/96938/pexels-photo-96938.jpeg" />
    <div class="main__content__product__label">B</div>
  </div>
  
  <div class="main__content__product">
    <img class="main__content__product__image" src="https://static.pexels.com/photos/127028/pexels-photo-127028.jpeg" />
    <div class="main__content__product__label">C</div>
  </div>
  
  <div class="main__content__product">
    <img class="main__content__product__image" src="https://static.pexels.com/photos/45170/kittens-cat-cat-puppy-rush-45170.jpeg" />
    <div class="main__content__product__label">D</div>
  </div>
</section>

CSS

.effect--move {
  background-color: white;
  background-position: top left;
  background-repeat: no-repeat;
  background-size: cover;
  position: fixed;
  transition: transform 250ms linear 150ms;
  z-index: 5000;
}

.effect--freeze {
  pointer-events: none;
}

.main {
  
}

.main__header {
  border-bottom: 1px solid gray;
  display: flex;
  padding-bottom: 1rem;
}

.main__header__label {
  flex: 1 1 auto;
  line-height: 48px;
  margin-right: 5px;
}

.main__header__cart {
  background-image: url('https://cdn4.iconfinder.com/data/icons/ionicons/512/icon-ios7-cart-outline-48.png');
  background-position: center center;
  background-repeat: no-repeat;
  flex: 0 0 48px;
}

.main__content {
  
}

.main__content__product {
  cursor: pointer;
  display: inline-block;
  margin: 10px 5px 10px 5px;
  outline: none;
}

.main__content__product__image {
  height: 80px;
  object-fit: cover;
  padding: 5px 5px 5px 5px;
  width: 80px;
}

.main__content__product__label {
  color: gray;
  font-size: 12px;
  margin: 5px 5px 5px 5px;
  text-align: center;
}

Babel + JSX

function move (imageElement, targetElement) {
	return new Promise((resolve, reject) => {
	  let animationElement = null;

	  function transitionHandler() {
		document.body.removeChild(animationElement);
		animationElement.removeEventListener('transitionend', transitionHandler, false);
		document.body.classList.remove('effect--freeze');
		resolve();
	  }

	  try {
		let imageElementPosition = imageElement.getBoundingClientRect();
		let targetElementPosition = targetElement.getBoundingClientRect();

		animationElement = document.createElement('div');
		animationElement.classList.add('effect--move');
		animationElement.style.backgroundImage = `url("${imageElement.src}")`;
		animationElement.style.height = `${imageElementPosition.height}px`;
		animationElement.style.left = `${imageElementPosition.left}px`;
		animationElement.style.top = `${imageElementPosition.top}px`;
		animationElement.style.width = `${imageElementPosition.width}px`;

		animationElement.addEventListener('transitionend', transitionHandler, false);

		document.body.classList.add('effect--freeze');
		document.body.appendChild(animationElement);
		animationElement.style.transform = `translate(${targetElementPosition.x - imageElement.x}px, ${targetElementPosition.y - imageElement.y}px)`;

	  } catch (error) {
		document.body.classList.remove('effect--freeze');
		reject(error);
	  }
	});
}

document.addEventListener('click', event => {
	let paths = event.path.map(p => p.className);
  let indexOfPath = paths.indexOf('main__content__product');

	if (indexOfPath >= 0) {
    move(
    	event.path[indexOfPath].querySelector('img'),
    	document.querySelector('.main__header__cart')
    );
	}
}, false)