vue为我们提供了很多动画接口方便我们实现动画效果,过渡动画可以实现一些简单的动画,如果一个动画包含几个简单的动画这个时候就需要使用动画钩子了。本文列出了几个例子,都是日常开发中经常遇到的。
在开发 toc 项目中,需要用到很多动画,比如常见的路由动画,弹出框动画以及一些业务动画等,本文对学过的动画做了一个总结,方便日后复习,如果对您有所帮助不甚荣幸。
首先我们来回顾下 vue 提供了那些动画,帮助我们快速实现想要的动画效果。【相关推荐:vuejs视频教程】
vue 动画css 过渡以显示和隐藏动画为例:
<div id="demo"> <button v-on:click="show = !show"> toggle </button> <transition name="fade"> <p v-if="show">hello</p> </transition></div>
从隐藏到显示当动画开始的时候,p 标签还是隐藏的,此时 vue 会给 p 加上两个class:
.fade-enter { opacity: 0;}.fade-enter-active { transition: opacity 0.5s;}
当动画开始后,会移除.fade-enter(在元素被插入之前生效,在元素被插入之后的下一帧移除),这个时候 p 标签的 opacity就恢复到 1,即显示,这个时候就会触发transition , 检测到opacity的变化,就会产生动画。当动画结束后,会移除 vue 加上的 class(v-enter-to, v-enter-active)。
上面这个过程是怎么实现的呢?它主要用到了requestanimationframe这个api,我们自己可以实现一个简易版的动画,当生成下一帧的时候添加或删除某个类,从而形成动画效果。
<!doctype html><html> <head> <title>document</title> <style type="text/css"> .box { width: 100px; height: 100px; background-color: red; } .enter { opacity: 0; } .mov { transition: opacity 5s linear; } </style> </head> <body> <div id="box" class="box mov enter"></div> <script> var box = document.getelementbyid('box') // 第一帧之后执行 requestanimationframe(function() { box.setattribute('class', 'box mov') }) </script> </body></html>
当生成下一帧的时候,会移除enter这个class,那么 div 就会显示出来,就会触发transition产生动画效果。
从显示到隐藏当隐藏的时候也产生动画,如下图:
.fade-leave-to { opacity: 0;}.fade-leave-active { transition: opacity 0.5s;}
刚开始 p 标签是显示的,因为此时fade-leave (在离开过渡被触发时立刻生效,下一帧被移除) 的样式是 opacity 是 1,执行到第二帧的时候加上fade-leave-to(在离开过渡被触发之后下一帧生效 ,与此同时 fade-leave 被删除),此时opacity 是 0,既然发生了属性的变化,transition就会监听到,从而形成动画。
这样显示和隐藏就形成了一个完整的动画。
原理是当你通过点击事件改变css属性,比如opacity时,transition会检测到这个变化,从而形成动画。
css 动画css 动画原理同 css 过渡类似,区别是在动画中 v-enter 类名在节点插入 dom 后不会立即删除,而是在 animationend 事件触发时删除。
<style> .bounce-enter-active { animation: bounce-in 3s; } .bounce-leave-active { animation: bounce-in 3s reverse; } @keyframes bounce-in { 0% { transform: scale(0); } 50% { transform: scale(1.5); } 100% { transform: scale(1); } }</style><div id="demo"> <button @click="show = !show">toggle show</button> <transition name="bounce"> <p v-if="show"> lorem ipsum dolor sit amet, consectetur adipiscing elit. mauris facilisis enim libero, at lacinia diam fermentum id. pellentesque habitant morbi tristique senectus et netus. </p> </transition></div>
当我们点击改变show为false时,会在 p 标签上添加.bounce-leave-active这个class,这个类就会执行animation 动画,当动画执行完成后删除.bounce-leave-active。
javascript 钩子动画<!doctype html><html> <head> ... <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script> </head> <body> <div id="demo"> <button @click="show = !show"> toggle </button> <transition v-on:before-enter="beforeenter" v-on:enter="enter" v-on:leave="leave" v-bind:css="false" > <p v-if="show"> demo </p> </transition> </div> <script> new vue({ el: '#demo', data: { show: true }, methods: { beforeenter: function(el) { el.style.opacity = 0 el.style.transformorigin = 'left' }, enter: function(el, done) { velocity(el, { opacity: 1, fontsize: '1.4em' }, { duration: 1000 }) velocity(el, { fontsize: '1em' }, { complete: done }) }, leave: function(el, done) { velocity( el, { translatex: '15px', rotatez: '50deg' }, { duration: 600 } ) velocity( el, { rotatez: '45deg', translatey: '30px', translatex: '30px', opacity: 0 }, { complete: done } ) } } }) </script> </body></html>
before-enter: 是指在动画之前执行的函数;
enter: 就是整个动画的过程, 执行完后要加一个done(),来告诉vue已经执行完毕;当只用 javascript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成。
after-enter: 动画结束之后执行;
javascript 钩子动画一般用在比较复杂的动画上,而不是简单的过渡动画。后面我们会用很大的篇幅通过几个例子来说明用 javascript 钩子动画是如何完成复杂动画的。
初始渲染的过渡因为css过渡动画需要有个触发条件,比如opacity必须有一个变化,如果没有变化就不会触发。那么,可以通过 appear attribute 设置节点在初始渲染的过渡:
<transition appear> <!-- ... --></transition>
css 动画(animation)则不需要触发条件。
多个元素的过渡一旦涉及到多个元素的过渡,那么就会出现旧元素和新元素进出的先后问题。<transition> 的默认行为是进入和离开同时发生,但是这样就会产生一些不协调的效果,所以 vue 提供了过渡模式:
in-out:新元素先进行过渡,完成之后当前元素过渡离开(in-out 模式不是经常用到,但对于一些稍微不同的过渡效果还是有用的)。out-in:当前元素先进行过渡,完成之后新元素过渡进入(这个用的比较多)。<transition name="fade" mode="out-in"> <!-- ... the buttons ... --></transition>
但是这两个模式并不能完全满足实际需要,实际上我们可以定制我们要想的先后效果,比如后台管理系统中有一个面包屑导航栏,当改变路由的时候需要更改面包屑里面的内容,那么这个更改的动画可以让旧的元素向左滑出,新的元素从右边滑入。
<div id="demo"> <div> <button @click="show = !show"> toggle </button> </div> <transition name="fade"> // 一定要设置key <div class="cls" v-if="show" key="1"> if demo </div> <div class="cls" v-else key="2">else demo</div> </transition></div><style> .cls { display: inline-block; } .fade-enter-active, .fade-leave-active { transition: all 1s; // 这个定位设置很关键 position: absolute; } .fade-enter { opacity: 0; transform: translatex(30px); } .fade-leave-to { opacity: 0; transform: translatex(-30px); }</style>
当有相同标签名的元素切换时,需要通过 key attribute 设置唯一的值来标记以让 vue 区分它们,否则 vue 为了效率只会替换相同标签内部的内容。即使在技术上没有必要,给在 transition 组件中的多个元素设置 key 是一个更好的实践。
多个组件的过渡多个组件的过渡简单很多 - 我们不需要使用 key attribute。相反,我们只需要使用动态组件:
<transition name="component-fade" mode="out-in"> <component v-bind:is="view"></component></transition>new vue({ el: '#transition-components-demo', data: { view: 'v-a' }, components: { 'v-a': { template: '<div>component a</div>' }, 'v-b': { template: '<div>component b</div>' } }}).component-fade-enter-active, .component-fade-leave-active { transition: opacity .3s ease;}.component-fade-enter, .component-fade-leave-to { opacity: 0;}
列表过渡上面讲的动画都是针对单个节点,或者同一时间渲染多个节点中的一个,那么怎么同时渲染整个列表,比如使用 v-for?
在这种场景中,使用 <transition-group> 组件,这个组件的几个特点:
不同于 <transition>,它会以一个真实元素呈现:默认为一个 <span>。你也可以通过 tag attribute 更换为其他元素。过渡模式不可用,因为我们不再相互切换特有的元素。内部元素总是需要提供唯一的 key attribute 值。css 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。后面我们会通过一个例子演示如何使用<transition-group> 。
案例路由动画在后台管理系统中,当路由变化时,对应的组件内容也会发生变化,当在变化时加上一个动画,让整个页面效果更加自然。
<transition name="fade-transform" mode="out-in"> // 这里加了key <router-view :key="key"></transition>computed: { key() { return this.$route.path }}.fade-transform-leave-active,.fade-transform-enter-active { transition: all 0.5s;}.fade-transform-enter { opacity: 0; transform: translatex(-30px)}.fade-transform-leave-to { opacity: 0; transform: translatex(30px)}
h5 页面弹框从底部弹出动画在 h5 页面开发中,一个常用的功能是点击一个按钮,隐藏的内容从屏幕底部弹出,同时弹出的时候有个动画效果,一般是缓慢上升。
<div id="list-demo"> <transition name="list-fade"> // 外面一层遮罩 <div class="playlist" v-show="showflag" @click="hide"> // 这里面才是内容 <div class="list-wrapper"></div> </div> </transition> <div @click="show" class="add"> add </div></div><style> .add { position: fixed; bottom: 0; left: 0; text-align: center; line-height: 40px; } .playlist { position: fixed; z-index: 200; top: 0; right: 0; bottom: 0; left: 0; background-color: rgba(0, 0, 0, 0.3); } .list-wrapper { position: absolute; bottom: 0; left: 0; width: 100%; height: 400px; background-color: #333; } // 针对最外面一层的遮罩 .list-fade-enter-active, .list-fade-leave-active { transition: opacity 0.3s; } // 针对类为list-wrapper的内容 .list-fade-enter-active .list-wrapper, .list-fade-leave-active .list-wrapper { transition: all 0.3s; } .list-fade-enter, .list-fade-leave-to { opacity: 0; } // 最开始内容是隐藏的,所以translate3d(0, 100%, 0) .list-fade-enter .list-wrapper, .list-fade-leave-to .list-wrapper { transform: translate3d(0, 100%, 0); }</style>
这个动画有两层,一层是最外层的内容,另一层是最里面部分的内容,效果如下:
列表删除动画在 h5 页面开发中,如果在一个列表页中删除其中一个子项,要求有一个删除动画效果。
<div id="list-demo"> <transition-group ref="list" name="list" tag="ul"> <li :key="item.id" class="item" v-for="(item, index) in arr"> <span class="text" v-html="item.name"></span> <span class="delete" @click="deleteone(item, index)"> delete </span> </li> </transition-group></div>.item { height: 40px;}.list-enter-active,.list-leave-active { transition: all 0.1s}.list-enter,.list-leave-to { height: 0}
复杂动画-案例1看下面的图片,这个动画该怎么实现呢?
一般复杂的动画,并不能用简单的 css 过渡或者 css 动画能实现的,需要使用 javascript钩子动画实现。
针对上面图片的动画进行拆解:
当从底部谈起的时候,页面顶部(向下的箭头和歌曲名称部分)从上往下滑入,同时有个回弹的效果,同时,底部(播放进度条的部分)从下往上滑入,也有一个回弹的效果。
左下角旋转的圆有两个动画效果,一个是从小变大,一个是从左下角滑动到中心部分
圆的旋转动画
代码结构:
<div> <transition name="normal" @enter="enter" @after-enter="afterenter" @leave="leave" @after-leave="afterleave" > <div class="normal-player" v-show="fullscreen"> <div class="top"> // 顶部区域... </div> <div class="middle" @touchstart.prevent="middletouchstart" @touchmove.prevent="middletouchmove" @touchend.prevent="middletouchend" > // 中间区域... </div> <div class="bottom"> // 底部区域... </div> </div> </transition> <transition name="mini"> <div class="mini-player" v-show="!fullscreen" @click="open"> // 内容区域... </div> </transition></div>
实现第一个动画效果
// 这是stylus的写法.normal-enter-active,.normal-leave-active transition: all 0.4s .top, .bottom // 通过这个白塞尔曲线,使得动画有个回弹的效果 transition: all 0.4s cubic-bezier(0.86, 0.18, 0.82, 1.32).normal-enter,.normal-leave-to opacity: 0 .top // 从上往下滑入 transform: translate3d(0, -100px, 0) .bottom // 从下往上滑入 transform: translate3d(0, 100px, 0)
通过第一章节部分的学习,看懂这段动画代码应该不难。
实现第二个动画效果
要实现这个动画效果,必须要计算左下角的圆到中心部分的圆的 x 轴和 y 轴方向上的距离,因为h5页面在不同的手机屏幕下,这个距离是不同的,所以一开始就不能写死,只能通过 javascript 去动态的获取。
// 计算从小圆中心到大圆中心的距离以及缩放比例_getposandscale() { const targetwidth = 40 const paddingleft = 40 const paddingbottom = 30 const paddingtop = 80 const width = window.innerwidth * 0.8 const scale = targetwidth / width const x = -(window.innerwidth / 2 - paddingleft) const y = window.innerheight - paddingtop - width / 2 - paddingbottom return { x, y, scale }}
这段代码细节可以不用看,只需要知道它是计算左下角小圆的原心到中心部分圆的原心的距离(x, y),以及根据圆的直径获取放大缩小倍数(scale)。
// 这个库可以让我们使用js来创建一个keyframe的动画,为什么要用js来生成呢?这是因为有些变化的属性需要动态的计算,而不是一开始就定好了import animations from 'create-keyframe-animation'// 动画钩子// done:当动画执行完后执行done函数,然后跳到afterenter钩子函数enter(el, done) { const { x, y, scale } = this._getposandscale() // 对于大圆来说,进入的时机就是从小圆到小圆 let animation = { 0: { // 一开始大圆相对于小圆的位置,所以x为负数,y为整数 transform: `translate3d(${x}px, ${y}px, 0) scale(${scale})` }, // scale: 1.1 这样圆就有个放大后变回原样的效果 60: { transform: 'translate3d(0, 0, 0) scale(1.1)' }, 100: { transform: 'translate3d(0, 0, 0) scale(1)' } } // 设置animation animations.registeranimation({ name: 'move', animation, presets: { duration: 400, easing: 'linear' } }) // 往dom上加上这个animation,并执行动画 animations.runanimation(this.$refs.cdwrapper, 'move', done)},// 动画结束之后把样式置为空afterenter() { animations.unregisteranimation('move') this.$refs.cdwrapper.style.animation = ''},leave(el, done) { this.$refs.cdwrapper.style.transition = 'all 0.4s' const { x, y, scale } = this._getposandscale() this.$refs.cdwrapper.style[ transform ] = `translate3d(${x}px,${y}px,0) scale(${scale})` // 这样写的目的是如果没有监听到动画结束的事件,那么我们自己就写一个定时器,400ms后执行done函数 const timer = settimeout(done, 400) // 监听动画结束 this.$refs.cdwrapper.addeventlistener('transitionend', () => { cleartimeout(timer) done() })}
这段代码的效果就是大圆的动画效果。当点击小圆的时候,大圆开始进入,进入的过程就是动画的过程。当点击向下的箭头,大圆将消失,消失的过程就是大圆退出的动画过程。
虽然有点复杂,但是也不难看懂,以后我们对于复杂动画可以模仿上面的代码。
那小圆的动画呢?它非常简单,就是一个显示隐藏的动画:
.mini-enter-active,.mini-leave-active transition: all 0.4s.mini-enter,.mini-leave-to opacity: 0
至此,就完成小圆和大圆的联动动画,整体效果还是很惊艳的。
实现第三个动画
// 模板部分<div class="cd" ref="imagewrapper"> <img ref="image" :class="cdcls" class="image" :src="currentsong.image" /></div>// 逻辑部分// 通过事件来控制playing的值,然后改变img标签的class,从而是动画停止和展示cdcls() { return this.playing ? 'play' : 'play pause'}// css部分.play animation: rotate 20s linear infinite.pause animation-play-state: paused @keyframes rotate 0% transform: rotate(0) 100% transform: rotate(360deg)
复杂动画-案例2
首先对动画进行拆解:
当点击 + 的时候,有一个小圆从右侧向xiang左侧滚动出来到指定的位置,既有滚动的效果,也有从右往左移动的效果。同时,当点击 - 的时候,小圆从左侧滚动到右侧并消失。
当点击 + 的时候,会出现一个小球,这个小球会从点击的位置做一个抛物线运动轨迹到左下角的购物车中。同时,当我连续点击的时候,会出现多个小球同时做抛物线运行出现在屏幕中。
实现第一个动画
通过上面的学习,对这一个动画的实现应该不难。代码如下:
<div class="cartcontrol"> // 小圆 - <transition name="move"> <div class="cart-decrease" v-show="food.count>0 @click.stop=decrease> <span class="inner"> - </span> </div> </transition> <div class="cart-count" v-show="food.count>0>{{food.count}}</div> // 小圆 + <div class="cart-add" @click.stop="add"> + </div></div>.move-enter-active, &.move-leave-active transition: all 0.4s linear.move-enter, &.move-leave-active // 外层动画是从右往左运动 opacity: 0 transform: translate3d(24px, 0, 0) .inner // 内层动画是旋转180° transform: rotate(180deg)
实现第二个动画
创建小球,因为这个动画就是对小球的动画
<div class="ball-container"> <div v-for="(ball,index) in balls" :key="index"> <transition @before-enter="beforedrop" @enter="dropping" @after-enter="afterdrop"> <div class="ball" v-show="ball.show"> <div class="inner inner-hook"></div> </div> </transition> </div></div><script>function createballs() { let balls = [] for (let i = 0; i enter -> after-enter -> drop -> before-enter -> enter -> after-enter...,这样就形成了在页面中同时出现多个小球的动画。注意:当我们设置show的属性为false就可以了,但是代码中同时也设置了el.style.display = 'none',如果不设置这个小球消失有一个延迟。
好了,整个 vue 动画到此就结束了,动画其实是一个比较难的功能,特别是复杂动画。通过上面两个复杂动画可以给我们做一个借鉴,相信你也能写出自己想要的动画效果。
(学习视频分享:web前端开发、编程基础视频)
以上就是实例详解,带你玩转 vue 动画的详细内容。