ホームページ > 記事 > ウェブフロントエンド > Vue アニメーションの操作に役立つ詳細な例
Vue は、アニメーション効果を実現しやすくするために、多くのアニメーション インターフェイスを提供します。トランジション アニメーションは、いくつかの単純なアニメーションを実装できます。アニメーションに複数の単純なアニメーションが含まれている場合は、アニメーション フックを使用する必要があります。この記事では、日常の開発で頻繁に遭遇するいくつかの例をリストします。
ToC プロジェクトの開発では、一般的なルーティング アニメーション、ポップアップ ボックス アニメーション、一部のビジネス アニメーションなど、多くのアニメーションを使用する必要があります。この記事ではアニメーションについてまとめています。今後の復習を容易にするためのまとめです。お役に立てれば幸いです。
まず、目的のアニメーション効果をすばやく実現できるように、Vue
が提供するアニメーションを確認してみましょう。 [関連する推奨事項: vuejs ビデオ チュートリアル ]
非表示のアニメーションを例に挙げます:
<div> <button> Toggle </button> <transition> <p>hello</p> </transition> </div>
アニメーションが開始されるとき、P タグはまだ非表示になっています。このとき、Vue は追加します。 2 つのクラス:
.fade-enter { opacity: 0; } .fade-enter-active { transition: opacity 0.5s; }
アニメーションが開始されると、.fade-enter
が削除されます (要素の挿入前、要素の挿入後に有効)次のフレームで削除されます)、このとき、P タグの opacity
は 1、つまり表示に戻ります。このとき、transition
がトリガーされ、opacity
が検出され、変更され、アニメーションが生成されます。アニメーションが終了すると、Vue によって追加されたクラス (v-enter-to, v-enter-active
) が削除されます。
上記のプロセスはどのように実装されますか?主に requestAnimationFrame
API を使用します。簡単なバージョンのアニメーションを自分で実装し、次のフレームを生成するときにクラスを追加または削除してアニメーション効果を形成できます。
nbsp;html> <title>Document</title> <style> .box { width: 100px; height: 100px; background-color: red; } .enter { opacity: 0; } .mov { transition: opacity 5s linear; } </style> <div></div> <script> var box = document.getElementById('box') // 第一帧之后执行 requestAnimationFrame(function() { box.setAttribute('class', 'box mov') }) </script>
次のフレームが生成されると、enter
クラスが削除され、div が表示され、transition
がトリガーされてアニメーション効果が生成されます。
以下に示すように、非表示にするときにアニメーションも生成されます。たった今
fade-leave(離脱トランジションがトリガーされるとすぐに有効になり、次のフレームで削除される) のスタイルが opacity
であるため、開始 P タグが表示されます。 2 番目のフレームに到達したら、fade-leave-to を追加します (これは、Leave トランジションがトリガーされた後の次のフレームで有効になり、同時に
fade-leave が実行されます)削除されます)、この時点
opacity は 0 です。属性が変化するため、
transition がそれを監視し、アニメーションを形成します。
このように表示したり非表示にしたりすると、完全なアニメーションが形成されます。
原則として、クリック イベントを通じて
などの CSS プロパティを変更すると、
transition がその変更を検出し、アニメーションを形成します。 CSS アニメーション
.fade-leave-to { opacity: 0; } .fade-leave-active { transition: opacity 0.5s; }
show を
false
.bounce-leave-active が P タグに追加されます。このクラスはアニメーションを実行し、アニメーションが完了したら
.bounce-leave-active を削除します。
JavaScript フック アニメーション
<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> <button>Toggle show</button> <transition> <p> 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>
JavaScript フック アニメーションは、一般に、より複雑なアニメーションに使用されます。シンプルなトランジションアニメーション
。後で、多くのスペースを使って、Javascript フック アニメーションを使用して複雑なアニメーションを完成させる方法をいくつかの例を通して説明します。初期レンダリングの遷移
CSS 遷移アニメーションにはトリガー条件が必要なので、たとえば、nbsp;html> ... <script></script> <script></script> <div> <button> Toggle </button> <transition> <p> 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>
CSS アニメーション (アニメーション) にはトリガー条件は必要ありません。 <h3 data-id="heading-7">多个元素的过渡</h3>
<p>一旦涉及到多个元素的过渡,那么就会出现旧元素和新元素进出的先后问题。<code><transition></transition>
的默认行为是进入和离开同时发生,但是这样就会产生一些不协调的效果,所以 Vue 提供了过渡模式:
in-out
:新元素先进行过渡,完成之后当前元素过渡离开(in-out
模式不是经常用到,但对于一些稍微不同的过渡效果还是有用的)。out-in
:当前元素先进行过渡,完成之后新元素过渡进入(这个用的比较多)。<transition> <!-- ... the buttons ... --> </transition>
但是这两个模式并不能完全满足实际需要,实际上我们可以定制我们要想的先后效果,比如后台管理系统中有一个面包屑导航栏,当改变路由的时候需要更改面包屑里面的内容,那么这个更改的动画可以让旧的元素向左滑出,新的元素从右边滑入。
<div> <div> <button> Toggle </button> </div> <transition> // 一定要设置key <div> if Demo </div> <div>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> <component></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-group>
组件,这个组件的几个特点:
<transition></transition>
,它会以一个真实元素呈现:默认为一个 <span></span>
。你也可以通过 tag
attribute 更换为其他元素。key
attribute 值。后面我们会通过一个例子演示如何使用<transition-group></transition-group>
。
在后台管理系统中,当路由变化时,对应的组件内容也会发生变化,当在变化时加上一个动画,让整个页面效果更加自然。
<transition> // 这里加了key <router-view> </router-view></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 页面开发中,一个常用的功能是点击一个按钮,隐藏的内容从屏幕底部弹出,同时弹出的时候有个动画效果,一般是缓慢上升。
<div> <transition> // 外面一层遮罩 <div> // 这里面才是内容 <div></div> </div> </transition> <div> 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> <transition-group> <li> <span></span> <span> 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 }
看下面的图片,这个动画该怎么实现呢?
一般复杂的动画,并不能用简单的 css 过渡或者 css 动画能实现的,需要使用 javascript钩子动画实现。
针对上面图片的动画进行拆解:
当从底部谈起的时候,页面顶部(向下的箭头和歌曲名称部分)从上往下滑入,同时有个回弹的效果,同时,底部(播放进度条的部分)从下往上滑入,也有一个回弹的效果。
左下角旋转的圆有两个动画效果,一个是从小变大,一个是从左下角滑动到中心部分
圆的旋转动画
代码结构:
<div> <transition> <div> <div> // 顶部区域... </div> <div> // 中间区域... </div> <div> // 底部区域... </div> </div> </transition> <transition> <div> // 内容区域... </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> <img alt="Vue アニメーションの操作に役立つ詳細な例" > </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)
首先对动画进行拆解:
当点击 + 的时候,有一个小圆从右侧向xiang左侧滚动出来到指定的位置,既有滚动的效果,也有从右往左移动的效果。同时,当点击 - 的时候,小圆从左侧滚动到右侧并消失。
当点击 + 的时候,会出现一个小球,这个小球会从点击的位置做一个抛物线运动轨迹到左下角的购物车中。同时,当我连续点击的时候,会出现多个小球同时做抛物线运行出现在屏幕中。
实现第一个动画
通过上面的学习,对这一个动画的实现应该不难。代码如下:
<div> // 小圆 - <transition> <div>0" @click.stop="decrease"> <span> - </span> </div> </transition> <div>0">{{food.count}}</div> // 小圆 + <div> + </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> <div> <transition> <div> <div></div> </div> </transition> </div> </div> <script> function createBalls() { let balls = [] for (let i = 0; i < BALL_LEN; i++) { balls.push({ show: false }) } return balls } data() { return { balls: createBalls() } }, </script>
这里创建是10个小球,小球开始的状态都是隐藏的。
点击 + 按钮的时候,触发一个小球弹出
// 点击加号调用这个函数,同时把加号的dom传递,这样就能知道小球运动的起点位置 onAdd(target) { // shopCart就是图中底部组件,执行drop函数 this.$refs.shopCart.drop(target) }, // 把加号对应的dom传入,并绑定到小球el属性上 drop(el) { for (let i = 0; i <p>因为小球的<code>ball.show</code>为true,那么就会触发对应的动画钩子函数,首先触发<code>beforeDrop</code>:</p><pre class="brush:php;toolbar:false">beforeDrop(el) { // 取出最后一个小球 const ball = this.dropBalls[this.dropBalls.length - 1] // 获取小球的起点位置,就是在哪个地方点击的加号按钮 const rect = ball.el.getBoundingClientRect() const x = rect.left - 32 const y = -(window.innerHeight - rect.top - 22) // 设置小球的位置,把小球设置到点击加号按钮的那个地方 el.style.display = '' // 外层动画,向下 el.style.transform = el.style.webkitTransform = `translate3d(0,${y}px,0)` const inner = el.getElementsByClassName(innerClsHook)[0] // 内层动画向左 inner.style.transform = inner.style.webkitTransform = `translate3d(${x}px,0,0)` }
接着执行enter
事件函数dropping
:
dropping(el, done) { // 触发浏览器重绘,把beforeDrop事件中设置的小球位置从底部位置移动到点击加号的位置,这样小球就会从上面往下面落下 this._reflow = document.body.offsetHeight // 设置小球落下的终点位置 el.style.transform = el.style.webkitTransform = `translate3d(0,0,0)` const inner = el.getElementsByClassName(innerClsHook)[0] inner.style.transform = inner.style.webkitTransform = `translate3d(0,0,0)` // 监听动画结束 el.addEventListener('transitionend', done) }
最后执行after-enter
事件函数afterDrop
:
afterDrop(el) { // 取出第一个小球,设置属性show为false,同时要设置el.style.display = 'none' const ball = this.dropBalls.shift() if (ball) { ball.show = false el.style.display = 'none' } }
我们来梳理下流程:
点击加号位置,触发drop
函数,然后把一个隐藏的小球设置为显示状态,存储在dropBalls
中,因为用户可以快速点击,所以dropBalls
里面可能有多个小球。
当把小球状态设置为显示状态,就会触发动画钩子before-enter
,enter
,after-enter
这三个钩子。
before-enter
的作用是把小球的位置设置到点击的位置,同时利用offsetHeight
触发浏览器重绘,这样就会把小球的位置放在点击的位置。
enter
的作用就是让小球从点击的位置落下,动画分为两层,一层是向下,另一层是向左,当两者结合就构成了一个从右上角到左下角斜线的动画轨迹,但是一个斜的直线动画轨迹比较丑,这里就使用了时间函数cubic-bezier
来改变动画轨迹,使其有一个先向上运动,最后向下运动的抛物线轨迹动画。
.ball position: fixed left: 32px bottom: 22px z-index: 200 transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41) .inner width: 16px height: 16px border-radius: 50% background: $color-blue transition: all 0.4s linear
after-enter
当小球到达目的后,需要把小球隐藏起来,所以取出第一个小球,然后设置show
的属性为false
,这样小球就隐藏起来,等待下一次动画执行。
所以函数的执行顺序是:drop
-> before-enter
-> enter
-> after-enter
-> drop
-> before-enter
-> enter
-> after-enter
...,这样就形成了在页面中同时出现多个小球的动画。
注意:当我们设置
show
的属性为false
就可以了,但是代码中同时也设置了el.style.display = 'none',如果不设置这个小球消失有一个延迟。
好了,整个 Vue 动画到此就结束了,动画其实是一个比较难的功能,特别是复杂动画。通过上面两个复杂动画可以给我们做一个借鉴,相信你也能写出自己想要的动画效果。
(学習ビデオ共有: Web フロントエンド開発、基本プログラミング ビデオ)
以上がVue アニメーションの操作に役立つ詳細な例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。