如何在不使用 transform 或 top/left 的情况下转换列表中项目的位置

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

前几天我偶然发现了使用 Vue.js 的 an example,但我的问题更多是关于 Vue 用来实现之间转换的 CSS 和 HTML州。

卡片暂时得到了class .shuffleMedium-move 加了一个transition: transform 1s 并且DOM中的节点顺序发生了变化,但我不明白为什么发生转换是因为 transform 属性 似乎从未设置,并且项目仅使用 float:left.

定位

我做 CSS 已经有一段时间了,我总是不得不结合使用 JavaScript position: absolutetransform 来实现类似的结果。 Vue 的解决方案看起来很优雅,但我不明白它是如何工作的。

来自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.

来自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.

分步示例

这样,我们就可以检查动画过程中每一步的变化。

在实时播放的时候,transform 真的很快就内联添加了,然后又立即删除了,所以看起来好像从来没有设置过。

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>