惯例BB
最近在做一个卡片弹窗的动画,怎么做都达不到想要的效果,查了查资料,发现通过一种叫FLIP的技术(也不算新技术,一种思路?),可以比较好的实现这个效果,而且这个过程中,也了解到了一些关于动画性能相关的知识,所以记录一下。
关于FLIP
FLIP是First、Last、Invert和Play四个单词首字母的缩写。
简单解释下这四个单词在实现思路中对应的意思
- first:指在过度效果发生前,目标对象的位置、大小
 - last:指过度效果发生后,目标对象的位置、大小
 - invert:目标对象过度前后的位置、大小差
 - play:通过invert得到的差值,来作为transform的参数进行动画
 
实现思路
1.动画前后的效果,用两个div写好
2.获取这两个div的大小比例和位置差
3.通过translate将动画后的div变成动画前的大小和移动到对应的位置
4.通过取消3中的translate和重新设置,加上过度就可以实现动画效果啦
手撕代码前
代码使用到的API
transform:translate/scale
- 偏移/缩放
 
getBoundingClientRect
- 获取dom元素的位置和大小
 
关于性能
上图中的layout和paint是非常影响性能的,比如position和display控制显示隐藏,都会触发这两步,所以在动画过程实现中,位置用transform的translate、显示隐藏用visibility/opacity 来做。因为他们只会触发最后一步。
代码演示
...
<style>
  body {
    margin: 0;
    position: relative;
  }
  div {
    text-align: center;
    background-color: rgb(139, 206, 248);
  }
  .before {
    width: 300px;
    height: 300px;
    line-height: 300px;
    margin: 0 auto;
    transition: all 1s;
  }
  .after {
    width: 100vw;
    height: 100vh;
    line-height: 100vh;
    position: absolute;
    top: 0;
  }
</style>
...
<body>
  <div class="before"></div>
  <div class="after"></div>
</body>
<script>
  let before = document.querySelector("#before");
  let after = document.querySelector("#after");
  // 获取过度前 位置大小
  let first = before.getBoundingClientRect();
  // 获取过度后 位置大小
  let last = after.getBoundingClientRect();
  console.log(first);
  console.log(last);
  // 记录前后大小位置差
  let invert = {
    left: first.left - last.left,
    top: first.top - last.top,
    width: first.width / last.width,
    height: first.height / last.height,
  };
  // 让after通过transform变成before的大小,以及移动到before的位置
  after.style.visibility = "hidden";
  after.style.transformOrigin = "top left";
  after.style.transform = `translate(${invert.left}px,${invert.top}px) scale(${invert.width},${invert.height})`;
  // 放大
  before.addEventListener("click", function () {
    after.style.transform = "";
    after.style.transition = " all 1s";
    after.style.visibility = "visible";
  });
  // 缩小
  after.addEventListener("click", function () {
    after.style.visibility = "hidden";
    after.style.transition = " all 1s";
    after.style.transform = `translate(${invert.left}px,${invert.top}px) scale(${invert.width},${invert.height})`;
  });
</script>
...