持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
简介
首先,FLIP
动画基础并不是一个插件或者是一个库、框架,它仅仅只是一个动画的实现思路而已,基于这种动画思路,可以再封装成一个插件等。该思路在一些有名框架也有用到,例如:
在Vue
里面的内置组件TransitionGroup里面就有用到FLIP
技术。
思路
现在,来看一下实现FLIP
动画的思路,首先得明白FLIP
是4个单词:
FLIP
代表First
和Last
以及Invert
还有Play
四个单词的组合,意义如下:
-
First 转换中涉及的元素的初始状态。
-
Last 元素的最终状态。
-
Invert 把转换前的元素和转换后的元素状态进行交换。
-
Play 添加过渡,把
Invert
时交换的位置重置,这时因为过渡存在,动画就会产生。
实现
现在,就来看下如何实现一个简单的Flip
动画,首先做一个简单的方块移动页面:
const box = document.querySelector('.box')
const btn = document.querySelector('.btn')
btn.addEventListener('click', () => {
box.style.transition = ''
let rect = null
const w = window.innerWidth
const h = window.innerHeight
const rL = Math.random() * w
const rT = Math.random() * h
const l = rL > w - box.offsetWidth ? w - box.offsetWidth : rL
const t = rT > h - box.offsetHeight ? h - box.offsetHeight : rT
box.style.left = l + 'px'
box.style.top = t + 'px'
})
可以看到页面中每点击一次按钮时,方块就会到一个新的位置去,但是并没有动画就显得很生硬。现在,通过FLIP
思路,让每一次方块移动都会有一个动画效果,实现如下:
const box = document.querySelector('.box')
const btn = document.querySelector('.btn')
btn.addEventListener('click', () => {
let rect = null
box.style.transition = ''
const w = window.innerWidth
const h = window.innerHeight
rect = box.getBoundingClientRect()
const startLeft = rect.left
const startTop = rect.top
const rL = Math.random() * w
const rT = Math.random() * h
const l = rL > w - box.offsetWidth ? w - box.offsetWidth : rL
const t = rT > h - box.offsetHeight ? h - box.offsetHeight : rT
box.style.left = l + 'px'
box.style.top = t + 'px'
rect = box.getBoundingClientRect()
const endLeft = rect.left
const endTop = rect.top
box.style.transform = `translate(${startLeft - endLeft}px, ${startTop - endTop}px)`
requestAnimationFrame(() => {
box.style.transition = 'transform .5s'
box.style.transform = 'translate(0, 0)'
})
})
完成之后,元素就会出现动画效果,如下图:
动的原理
其实让方块实现动的原理很简单,就是在元素移动到目标位置之后,通过设置transform
又把元素移动到开始的位置(Invert),之后在requestAnimationFrame
把transform
移除,就会有一个动画的产生。
因为这个过程很快,用户感知不到,肉眼就看着就只有开头到结尾的过渡动画。
问题
虽然上面通过FLIP
动画思路实现了动画效果,但是实际上通过直接设置transition
属性,也能得到上面的效果。
那么FLIP
思路的有点又有什么呢?看看下面这个例子。
例子
接下来实现一个Demo
例子:生成一堆方块,然后随机删除一个,代码如下:
const list = document.querySelector('.box-list')
const btn = document.querySelector('.btn')
btn.addEventListener('click', () => {
const boxs = document.querySelectorAll('.box')
const rIdx = Math.floor(Math.random() * boxs.length)
list.removeChild(boxs[rIdx])
})
从下面效果图中,可以看到每个元素都设置了transition
过渡效果得,可是动画删除之后移动是没有过渡效果产生的,如下图:
此时,通过FLIP
思路来实现上面的功能,代码简单改造一下:
const list = document.querySelector('.box-list')
const btn = document.querySelector('.btn')
const flip = async (el, callback) => {
let rect = null
el.style.transition = ''
rect = el.getBoundingClientRect()
const startLeft = rect.left
const startTop = rect.top
await callback()
rect = el.getBoundingClientRect()
const endLeft = rect.left
const endTop = rect.top
el.style.transform = `translate(${startLeft - endLeft}px, ${startTop - endTop}px)`
requestAnimationFrame(() => {
el.style.transition = 'all .5s'
el.style.transform = 'translate(0, 0)'
})
}
btn.addEventListener('click', () => {
const boxs = document.querySelectorAll('.box')
const rIdx = Math.floor(Math.random() * boxs.length)
boxs.forEach(box => flip(box, () => new Promise(resolve => setTimeout(() => resolve()))))
list.removeChild(boxs[rIdx])
})
此时,再看看改造之后的效果图:
上面的效果图,通过纯JavaScript
也能实现,但是个人觉得比起FLIP
思路来说不够优雅。
注意
使用FLIP
思路完成动画有一些注意事项:
- 过
invert
时不要超过100ms时间,可以使用requestAnimationFrame
来完成; - 用
transform
时有可能会导致元素失真;
以上,就是基于Javascript
和CSS
的FLIP
动画实现了。