vue3学习随便记13
Posted sjg20010414
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue3学习随便记13相关的知识,希望对你有一定的参考价值。
过渡与动画
进入过渡和离开过渡
在插入、更新或从DOM移除项时,Vue提供了多种方法实现转换效果:
- 自动为CSS过渡和动画应用class
- 集成第三方CSS动画库,例如 Animate.css | A cross-browser library of CSS animations.
- 在过渡钩子期间使用 JS 直接操作 DOM
- 集成第三方 JS 动画库
单个元素或组件的过渡
Vue提供了 transition 封装组件(内置组件),在下列情形下,可以给任何元素或者组件添加进入或离开过渡效果:
- 使用 v-if 条件渲染
- 使用 v-show 条件展示
- 动态组件
- 组件根节点
下面是一个条件渲染淡入淡出的例子
<style>
.fade-enter-active,
.fade-leave-active
transition: opacity 0.5s ease;
.fade-enter-from,
.fade-leave-to
opacity: 0;
</style>
<div id="app">
<button @click="show = !show">切换</button>
<transition name="fade">
<p v-if="show">你好</p>
</transition>
</div>
<script>
const createApp = Vue
const app = createApp(
data()
return
show: true
)
const vm = app.mount('#app')
</script>
我们<p v-if="...">...</p> 条件渲染元素封装在 transition 内置组件中,当插入或删除该元素时,Vue将做以下处理:
- 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加或删除 CSS 类名
- 如果过渡组件提供了 JS 钩子函数,这些钩子将在恰当的时机被调用
- 如果既没有 JS钩子 也没有 CSS 过渡或动画,DOM插入或删除在下一帧立即执行
我们的例子中是上述情形1,内置组件 transition 本身不会渲染新的 DOM,它是把合适的 CSS类应用到了被包裹的元素(或组件这样的自定义元素)上。transition组件name属性为fade,从而自动拓展为 .fade-enter-from,.fade-enter-active,.fade-enter-to,.fade-leave-from,.fade-leave-active,.fade-leave-to 等6个 CSS过渡类(如果name属性值没有指定,则其值为v)。实现进入过渡是设置好前三个CSS类,而离开过渡是后三个CSS类。
(上图引用自 Vue官网)
CSS动画也是使用 transition 组件,只是设置CSS类时,不是设置 transition,而是设置 animation,并且 v-enter-from 类在节点插入 DOM 后不会立刻移除,直到 animationend 事件触发时才移除。
前面说的6个CSS过渡类的名字是可以自定义的,方法是在 transition 组件的属性 enter-from-class,enter-active-class,enter-to-class,leave-from-class,leave-active-class,leave-to-class 中进行覆盖。这么做的好处是可以把 Vue 的过渡机制和一些现成的 CSS动画库结合起来,自己就不用费神编写过渡类的CSS了,例如,在前面的代码中我们可以引入 Animate.css
<link
href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.0/animate.min.css"
rel="stylesheet"
type="text/css"
/>
然后,去掉自己编写的 <style> 内的4个类,将 transition 组件改成
<transition name="custom-classes-transition"
enter-active-class="animate__animated animate__tada"
leave-active-class="animate__animated animate__bounceOutRight">
<p v-if="show">你好</p>
</transition>
这样就能完成过渡或动画效果了。
transition组件的 type属性可以区分使用的是动画还是过渡,duration属性可以自定义过渡持续的时间。transition组件还提供了 before-enter、enter、after-enter……等JS钩子,可以把transition组件的 css属性设置为 false(:css="false"),不使用CSS过渡类,然后在钩子中实现 JS动画(不过 enter 和 leave 钩子中必须使用 done 回调),调用 GreenSock 那样的动画库会省事。
初始渲染的过渡
给 transition 组件设置 appear属性(默认值false)就能获得初始渲染过渡效果
<transition appear>
<!-- ... -->
</transition>
多个元素之间的过渡
transition组件包裹的,可以是 v-if...v-else-if....v-else 那样的多个中选一个的元素,也可以是动态可变的一个元素(使用:key属性)。在多个元素的过渡切换中,我们有时希望新元素先进行进入过渡,完成之后老元素过渡离开(称为in-out模式),更多时候希望老元素先进行离开过渡,完成之后新元素过渡进入(称为out-in模式),我们可以用 transition组件的 mode属性指定要使用哪一种模式。
多个组件之间的过渡
组件之间的过渡更简单,只要让 transition组件包裹一个动态组件(使用<component>和:is属性)
我们把以前的动态组件例子(3个tab页)改一改
<transition name="fade" mode="out-in">
<keep-alive>
<component :is="currentTabComponent" class="tab"></component>
</keep-alive>
</transition>
然后添加相关的过渡类
.fade-enter-active,
.fade-leave-active
transition: opacity 0.3s ease;
.fade-enter-from,
.fade-leave-to
opacity: 0;
这样就能实现多个组件的过渡了。(注意,keep-alive也要包裹在transition组件内,因为缓存的东西要一块儿过渡切换)
列表过渡
前面提到的过渡,或者是单个节点,或者是多个节点,但每次只渲染一个。这里需要考虑同时渲染整个列表的情形,这也是 <transition-group> 组件的用武之地,<transition-group>有下述特点:
- 默认情况下,它不会渲染包裹的一个元素,但你可以用tag属性指定渲染一个元素
- 过渡模式不可用,因为这里没有切换特定的一个的概念了
- 内部元素总是需要提供唯一的 key 属性值进行标识
- CSS过渡类应用在内部的元素中,而不是这个组(容器)本身
列表的进入/离开过渡
<body>
<div id="app">
<button @click="add">添加</button>
<button @click="remove">移除</button>
<transition-group name="list" tag="p">
<span v-for="item in items" :key="item" class="list-item">
item
</span>
</transition-group>
</div>
<script>
const createApp = Vue
const app = createApp(
data()
return
items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
nextNum: 10
,
methods:
randomIndex()
return Math.floor(Math.random() * this.items.length)
,
add()
this.items.splice(this.randomIndex(), 0, this.nextNum++)
,
remove()
this.items.splice(this.randomIndex(), 1)
)
const vm = app.mount('#app')
</script>
列表 name 属性 list,tag属性值p表示指定渲染一个<p>作为容器,里面的每个 span 都包含了 :key 属性唯一识别,列表的进入和离开过渡通过CSS指定:
.list-item
display: inline-block;
margin-right: 10px;
.list-enter-active,
.list-leave-active
transition: all 1s ease;
.list-enter-from,
.list-leave-to
opacity: 0;
transform: translateY(30px);
这个例子中,新增的元素进入,或者被删的元素离开,都非常平滑,但插入或者移除时,周围元素的移动显得突兀,这涉及列表的移动过渡
列表的移动过渡
<transition-group> 组件除了进入和离开的一些CSS类(.XXX-enter-from,.XXX-enter-active,.XXX-enter-to,.XXX-leave-from,.XXX-leave-active,.XXX-leave-to),还有 .XXX-move 类,它会应用在元素改变定位的过程中。(前缀XXX可以通过name属性定义,也可以通过 move-class 属性手动设置)
修改CSS,使得插入和移除时,左右移动都是平滑的
.list-item
display: inline-block;
margin-right: 10px;
transition: all 0.8s ease; /* 移除时平滑移动 */
/* .list-enter-active,
.list-leave-active
transition: all 1s ease;
*/
.list-leave-active
position: absolute; /* 移除时平滑移动 */
.list-enter-from,
.list-leave-to
opacity: 0;
transform: translateY(30px);
.list-move
transition: transform 0.8s ease; /* 插入时平滑移动 */
官网还给出列表中块元素 shuffle 时的平滑移动例子 (列表过渡 | Vue.js)
列表的交错过渡
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.4/gsap.min.js"></script>
<div id="app">
<input v-model="query" />
<transition-group
name="staggered-fade"
tag="ul"
:css="false"
@before-enter="beforeEnter"
@enter="enter"
@leave="leave"
>
<li
v-for="(item, index) in computedList"
:key="item.msg"
:data-index="index"
>
item.msg
</li>
</transition-group>
</div>
<script>
const createApp = Vue
const app = createApp(
data()
return
query: '',
list: [
msg: '张三' ,
msg: '李四' ,
msg: '王五' ,
msg: '李六' ,
msg: '张七'
]
,
computed:
computedList()
var vm = this
return this.list.filter(item =>
return item.msg.indexOf(vm.query) !== -1
)
,
methods:
beforeEnter(el)
el.style.opacity = 0
el.style.height = 0
,
enter(el, done)
gsap.to(el,
opacity: 1,
height: '1.6em',
delay: el.dataset.index * 0.15,
onComplete: done
)
,
leave(el, done)
gsap.to(el,
opacity: 0,
height: 0,
delay: el.dataset.index * 0.15,
onComplete: done
)
)
const vm = app.mount('#app')
</script>
计算属性 computedList 是依赖 query的,所以,(双向绑定的)query 输入值一改,computedList 就重新计算,列表就刷新了。交错过渡效果中,使用了强悍的 gsap JS动画库(https://greensock.com/)。
可复用的过渡
Vue的组件系统可以实现过渡效果的复用,也就是把过渡效果做成组件。要创建一个可以复用的过渡组件,只要将 <transition> 或 <transition-group> 作为根组件,然后将任何子组件放置其中就可以了。我们把上述例子改一改,把过渡效果做成 <my-transition-group> 组件:
<div id="app">
<input v-model="query" />
<my-transition-group>
<li v-for="(item, index) in computedList" :key="item.msg" :data-index="index">
item.msg
</li>
</my-transition-group>
</div>
<script>
const createApp = Vue
const app = createApp(
data()
return
query: '',
list: [
msg: '张三' ,
msg: '李四' ,
msg: '王五' ,
msg: '李六' ,
msg: '张七'
]
,
computed:
computedList()
var vm = this
return this.list.filter(item =>
return item.msg.indexOf(vm.query) !== -1
)
,
)
app.component('my-transition-group',
template: '\\
<transition-group\\
name="staggered-fade"\\
tag="ul"\\
:css="false"\\
@before-enter="beforeEnter"\\
@enter="enter"\\
@leave="leave"\\
>\\
<slot></slot>\\
</transition-group>\\
',
methods:
beforeEnter(el)
el.style.opacity = 0
el.style.height = 0
,
enter(el, done)
gsap.to(el,
opacity: 1,
height: '1.6em',
delay: el.dataset.index * 0.15,
onComplete: done
)
,
leave(el, done)
gsap.to(el,
opacity: 0,
height: 0,
delay: el.dataset.index * 0.15,
onComplete: done
)
)
const vm = app.mount('#app')
</script>
上面的代码中,组件 <my-transition-group> 模板中把 <transition-group> 作为根,用插槽 <slot> 来容纳子组件。对于可复用的过渡组件,使用函数式组件更合适:
这部分还没搞定(test42.html)
动态过渡
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<div id="app">
淡入:<input type="range" v-model="fadeInDuration" min="0" :max="maxFadeDuration" />
淡出:<input type="range" v-model="fadeOutDuration" min="0" :max="maxFadeDuration" />
<transition :css="false" @before-enter="beforeEnter" @enter="enter" @leave="leave">
<p v-if="show">你好</p>
</transition>
<button v-if="stop" @click="stop = false; show = false">
动画开始
</button>
<button v-else @click="stop = true">停止动画!</button>
</div>
<script>
const createApp = Vue
const app = createApp(
data()
return
show: true,
fadeInDuration: 1000,
fadeOutDuration: 1000,
maxFadeDuration: 1500,
stop: true
,
mounted()
this.show = false
,
methods:
beforeEnter(el)
el.style.opacity = 0
,
enter(el, done)
var vm = this
Velocity(
el,
opacity: 1 ,
duration: this.fadeInDuration,
complete: function ()
done()
if (!vm.stop) vm.show = false
)
,
leave(el, done)
var vm = this
Velocity(
el,
opacity: 0 ,
duration: this.fadeOutDuration,
complete: function ()
done()
vm.show = true
)
)
const vm = app.mount('#app')
</script>
动态过渡的一般用法是某个 attribute (最基础的是 name属性) 绑定动态的值,而上面的代码是利用事件钩子和 velocity JS动画库来实现的淡入淡出。
状态过渡
状态动画与侦听器
通过侦听器(watcher)能监听到任何数值 property 的更新,即监听vm的数据变量的变化。下面的例子,用侦听器监听 vm.number 的变化,把 tweenedNumber 动画方式修改为新值,而显示的 animatedNumber 是依赖 tweenedNumber 的计算属性,从而数值的状态变化就产生动画效果。
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js"></script>
<div id="app">
<input v-model.number="number" type="number" step="20" />
<p> animatedNumber </p>
</div>
<script>
const App =
data()
return
number: 0,
tweenedNumber: 0
,
computed:
animatedNumber()
return this.tweenedNumber.toFixed(0)
,
watch:
number(newValue)
gsap.to(this.$data, duration: 0.5, tweenedNumber: newValue )
Vue.createApp(App).mount('#app')
</script>
动态状态过渡
下面的例子,用SVG 绘制多边形,侦听器监听多边形有关变量,更新时用了状态过渡,实现了动态的过渡效果。
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js"></script>
<div id="app">
<svg width="200" height="200">
<polygon :points="points"></polygon>
<circle cx="100" cy="100" r="90"></circle>
</svg>
<label>边数: sides </label>
<input type="range" min="3" max="500" v-model.number="sides" />
<label>最小半径: minRadius %</label>
<input type="range" min="0" max="90" v-model.number="minRadius" />
<label>更新间隔: updateInterval 毫秒</label>
<input type="range" min="10" max="2000" v-model.number="updateInterval" />
</div>
<script>
const defaultSides = 10;
const stats = Array.apply(null, length: defaultSides ).map(() => 100);
const App =
data()
return
stats: stats,
points: generatePoints(stats),
sides: defaultSides,
minRadius: 50,
interval: null,
updateInterval: 500
;
,
watch:
sides(newSides, oldSides)
var sidesDifference = newSides - oldSides;
if (sidesDifference > 0)
for (var i = 1; i <= sidesDifference; i++)
this.stats.push(this.newRandomValue());
else
var absoluteSidesDifference = Math.abs(sidesDifference);
for (var i = 1; i <= absoluteSidesDifference; i++)
this.stats.shift();
,
stats(newStats)
gsap.to(this.$data, this.updateInterval / 1000,
points: generatePoints(newStats)
);
,
updateInterval()
this.resetInterval();
,
mounted()
this.resetInterval();
,
methods:
randomizeStats()
var vm = this;
this.stats = this.stats.map(() => vm.newRandomValue());
,
newRandomValue()
return Math.ceil(this.minRadius + Math.random() * (100 - this.minRadius));
,
resetInterval()
var vm = this;
clearInterval(this.interval);
this.randomizeStats();
this.interval = setInterval(() =>
vm.randomizeStats();
, this.updateInterval);
Vue.createApp(App).mount('#app')
function valueToPoint(value, index, total)
var x = 0;
var y = -value * 0.9;
var angle = ((Math.PI * 2) / total) * index;
var cos = Math.cos(angle);
var sin = Math.sin(angle);
var tx = x * cos - y * sin + 100;
var ty = x * sin + y * cos + 100;
return x: tx, y: ty ;
function generatePoints(stats)
var total = stats.length;
return stats.map(function (stat, index)
var point = valueToPoint(stat, index, total);
return point.x + "," + point.y;
).join(" ");
</script>
把过渡放到组件里
前面的整数动画效果是针对一个数字的,如果页面有多个数字,都去管理状态,会非常复杂,幸好很多的动画效果都可以提取到专门的子组件中,用组件复用来实现动画效果复用。
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js"></script>
<div id="app">
<input v-model.number="firstNumber" type="number" step="20" /> +
<input v-model.number="secondNumber" type="number" step="20" /> = result
<p>
<animated-integer :value="firstNumber"></animated-integer> +
<animated-integer :value="secondNumber"></animated-integer> =
<animated-integer :value="result"></animated-integer>
</p>
</div>
<script>
const App =
data()
return
firstNumber: 20,
secondNumber: 40
,
computed:
result()
return this.firstNumber + this.secondNumber
const app = Vue.createApp(App)
app.component('animated-integer',
template: '<span> fullValue </span>',
props:
value:
type: Number,
required: true
,
data()
return
tweeningValue: 0
,
computed:
fullValue()
return Math.floor(this.tweeningValue)
,
methods:
tween(newValue, oldValue)
gsap.to(this.$data,
duration: 0.5,
tweeningValue: newValue,
ease: 'sine'
)
,
watch:
value(newValue, oldValue)
this.tween(newValue, oldValue)
,
mounted()
this.tween(this.value, 0)
)
app.mount('#app')
</script>
以上是关于vue3学习随便记13的主要内容,如果未能解决你的问题,请参考以下文章