Skip to content
Advertisement

How is it possible to transition the position of items in a list without using transform or top/left

The other day I stumbled onto an example that uses Vue.js, but my question is more about the CSS and HTML that Vue uses to achieve the transition between states.

The cards temporarily get the class .shuffleMedium-move which adds a transition: transform 1s and the order of the nodes change in the DOM, but I don’t understand why the transition occurs since the transform property never seems to get set and the items are positioned simply using float:left.

I’ve been doing CSS for quite a while and I’ve always had to resort to using a combination of JavaScript position: absolute and transform to achieve a similar result. Vue’s solution seems really elegant, but I don’t understand how it works.

Advertisement

Answer

From the documentation on list transition

This might seem like magic, but under the hood, Vue is using an animation technique called FLIP to smoothly transition elements from their old position to their new position using transforms.

From the FLIP article

FLIP stands for First, Last, Invert, Play.

Let’s break it down:

  • First: the initial state of the element(s) involved in the transition.
  • Last: the final state of the element(s).
  • Invert: here’s the fun bit. You figure out from the first and last how the element has changed, so – say – its width, height, opacity. Next you apply transforms and opacity changes to reverse, or invert, them. If the element has moved 90px down between First and Last, you would apply a transform of -90px in Y. This makes the elements appear as though they’re still in the First position but, crucially, they’re not.
  • Play: switch on transitions for any of the properties you changed, and then remove the inversion changes. Because the element or elements are in their final position removing the transforms and opacities will ease them from their faux First position, out to the Last position.

Step by step example

That way, we can inspect changes at each step of the animation process.

When it’s playing in real time, the transform is really quickly added inline and it’s then removed immediately, so it looks like it’s never set.

const el = document.getElementById('target');
const data = {};

function first() {
  data.first = el.getBoundingClientRect();
  console.log('First: get initial position', data.first.left, 'px');
}

function last() {
  el.classList.toggle('last');
  data.last = el.getBoundingClientRect();
  console.log('Last: get new position', data.last.left, 'px');
}

function invert() {
  el.style.transform = `translateX(${data.first.left - data.last.left}px)`;
  console.log('Invert: applies a transform to place the item where it was.');
}

function play() {
  requestAnimationFrame(() => {
    el.classList.add('animate');
    el.style.transform = '';
  });
  console.log('Play: adds the transition class and removes the transform.');
}

function end() {
  el.classList.remove('animate');
  console.log('End: removes the transition class.');
}

const steps = [first, last, invert, play, end];

let step = 0;

function nextStep() {
  steps[step++ % steps.length]();
}

document.getElementById('next').addEventListener('click', nextStep);
.last {
  margin-left: 35px;
}

.animate {
  transition: transform 1s;
}

#target {
  display: inline-block;
  padding: 5px;
  border: 1px solid #aaa;
  background-color: #6c6;
}

#next {
  margin-top: 5px;
}
<div id="target">target</div>
<br>
<button id="next" type="button">Next</button>
User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement