首先我是被这个动画效果吸引到的, 效果好棒
说用到 FLIP (First, Last, Invert, Play) 不要被英文单词吓到, 原理可能比你想象的简单
虽然它很简单, 但也是为了解决一些问题才提出来的.
这个FLIP 要做的事情呢, 就是提高动画的帧率 但是呢, 浏览器这个东西吧, 就是比较矫情, 主要有一下2个特点
- 浏览器 transition 对
opacitytransform的动画会采用 GPU 加速 - 对
topleftwidthheight等 属性没有GPU加速, 也就是容易卡.
所以 FLIP 能就是想用有 GPU加持的属性 来模拟那些没有GPU加持的属性
分为以下几部
感谢getBoundingClientRect 这个API 可以轻松获取到 x y left top left bottom right
直接加class等一些列方法,把元素直接变成你想要的样子吧. 但是这个时候不要有 animation
然后还是使用 getBoundingClientRect 获取 数据 x y left top left bottom right
重点就是这里了, 通过 F 和 L 拿到 两个状态的 x y left top left bottom right
使用这2份数据 就可以使用 transilate scale 等一系列支持GPU加速的属性来描述这个动画过程了
不过呢, 和正常的动画过程不一样, 这个动画过程是要翻过来写的.
- F: A 元素本来
left = 0px - L: 然后忽然设置成了
left = 100px(A 元素被瞬间移动了100px) - I: 我不能让你这么突兀的直接过来呀, 我要使用 transform 属性把你
translateX(-100px), 虽然你left = 100px但是 你的translateX(-100px)抵消掉了你还是你的位置.
- P: 好了, 动起来吧, 给A加上 transition: 300ms, 再把 transform 清空, 执行动画吧(translateX(-100px) -> translateX(0px)).
这个时候 A元素 就会从 视觉 left = 0 以 300ms 的时间移动到 left = 100px 的位置.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>DEMO</title>
</head>
<body>
<style>
#demo {
width: 100px;
height: 100px;
background-color: #ccc;
position: absolute;
top: 0;
left: 0;
}
#demo.big {
width: 300px;
height: 500px;
}
#demo.move {
left: 300px;
top: 100px;
}
.on-transition {
transition: .3s;
}
</style>
<div id="demo">
</div>
<script>
document.onclick = () => {
const el = document.getElementById('demo')
const first = el.getBoundingClientRect();
el.classList.add('big');
el.classList.add('move');
const last = el.getBoundingClientRect();
const dy = first.top - last.top
const dx = first.left - last.left
const rx = first.width / last.width
const ry = first.height / last.height
el.style.transform = `translate(${dx}px, ${dy}px) scaleX(${rx}) scaleY(${ry})`;
el.style.transformOrigin = `${rx}% ${ry}%`;
// 等到下一帧,也就是其他所有的样式都已经被应用
requestAnimationFrame(function () {
el.classList.add('on-transition')
el.style.transform = '';
});
// 结束时清理
el.addEventListener('transitionend', () => console.log(1));
}
</script>
</body>
</html>所以其实很简单, 总结起来一句话, 拿到2个状态的数据, 然后通过支持GPU 的属性让移动后的元素保持为移动的状态. 最后再释放, 让他动起来