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.
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.
var $el = $('.target');
var el = $el.get(0);
var data = {};
function first() {
data.first = el.getBoundingClientRect();
console.log('First: get initial position', data.first.left, 'px');
}
function last() {
$el.toggleClass('last');
data.last = el.getBoundingClientRect();
console.log('Last: get new position', data.last.left, 'px');
}
function invert() {
var invert = data.first.left - data.last.left;
el.style.transform = `translateX(${invert}px)`;
console.log('Invert: applies a transform to place the item where it was.');
}
function play() {
requestAnimationFrame(function() {
$el.addClass('animate');
el.style.transform = '';
});
console.log('Play: adds the transition class and removes the transform.');
}
function end() {
$el.removeClass('animate');
console.log('End: removes the transition class.');
}
var steps = [first, last, invert, play, end];
var step = 0;
function nextStep() {
steps[step++ % steps.length]();
}
$('button').click(nextStep);
.last {
margin-left: 35px;
}
.animate {
transition: transform 1s;
}
.target {
display: inline-block;
padding: 5px;
border: 1px solid #aaa;
background-color: #6c6;
}
<div class="target">target</div>
<br>
<button type="button">Next</button>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>