vue3中JavaScript 动画的使用
在上一篇文章vue3中transition-group的使用中,我们使用transition-group
对列表使用了动画,现在又有一个新需求,当删除时需要实现一个图标飞到废纸篓的动画,如图所示:
要实现这个效果,使用css
过渡是无法实现的,因此就需要用到vue
提供的JavaScript
动画了。
实现的思路也很简单,我们放一个单独存在的动画元素并且藏起来,也就是那个图标文件。当点击删除图标的时候,我们把这个动画元素移动到鼠标的位置,再飞到废纸篓里藏起来就可以了。
在 Vue 的 transition
组件里,我们可以分别设置 before-enter
,enter
和 after-enter
三个函数来更精确地控制动画。
<template>
<span class="dustbin">
🗑
</span>
<div class="animate-wrap">
<transition @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter">
<div class="animate" v-show="animate.show">
📋
</div>
</transition>
</div>
</template>
<script setup>
let animate = reactive({
show:false,
el:null
})
function beforeEnter(el){
let dom = animate.el
let rect = dom.getBoundingClientRect()
let x = window.innerWidth - rect.left - 60
let y = rect.top - 10
el.style.transform = `translate(-${x}px, ${y}px)`
}
function enter(el,done){
document.body.offsetHeight
el.style.transform = `translate(0,0)`
el.addEventListener('transitionend', done)
}
function afterEnter(el){
animate.show = false
el.style.display = 'none'
}
function removeTodo(e,i){
// 把当前删除的元素赋值给动画el属性
animate.el = e.target
// 图标文件显示
animate.show = true
// 删除
todos.value.splice(i,1)
}
</script>
<style>
.dustbin {
font-size: 20px;
position: fixed;
right: 10px;
top: 10px;
}
.animate-wrap .animate{
position :fixed;
right :10px;
top :10px;
z-index: 100;
// 这里仍然使用了css的过渡
transition: all 0.5s linear;
}
</style>
当删除其中一项,把当前的dom
元素复制给animate.el
,同时设置animate.show = true
,此时图标文件显示,当显示时就会执行动画钩子函数:
beforeEnter
这个函数主要做了两件事:
- 计算删除元素在页面中的位置
dom.getBoundingClientRect()
- 把图标文件通过
transform
移动到删除元素所在位置
enter
这个函数也主要做了两件事:
- 首先执行
document.body.offsetHeight
,强制浏览器重绘一次,把图标文件显示在删除元素的位置上。 - 然后把图标文件移动到原有位置
translate(0,0)
,最后监听动画结束事件。
afterEnter
这个函数主要是让图标文件隐藏,这里两行代码的功能是一样的:
animate.show = false
el.style.display = 'none'
那为什么要写两次呢?只写animate.show = false
不行吗?
如果只写animate.show = false
,图标文件移动垃圾桶后会有一个停顿然后消失,如果加上el.style.display = 'none'
图标文件一到垃圾桶后会立刻消失,不会产生停顿,这样动画更加顺滑。
这样就完成了吗?当我们删除最后一个时,会产生一个bug,如图所示:
发现,删除最后一个时图标文件跑到左上角去了?这是为什么呢?
因为当只有最后一项时,删除它执行渲染操作,同时animate.show = true
,此时动画的元素图标文件出现,执行beforeEnter
函数,此时要删除的元素已经被删除了,所以dom.getBoundingClientRect()
的位置变为(0, 0)
,因为最后一项已经没有了。
因此el.style.transform = translate(-${x}px, ${y}px)
会在左上角出现。怎么办呢?
可以先让图标文件先出现,然后在下一个tick
执行删除todos.value.splice(i, 1)
操作即可。
setTimeout(() => {
todos.value.splice(i, 1)
}, 20)
除了使用setTimeout
外,还可以使用nextTick()
。
转载自:https://juejin.cn/post/7238423148554616893