Vue | 19 过渡&动画-状态过渡
内容提要:
- 动画的状态与监听器
- 动态状态过渡
- 在组件内组织过渡
- 为设计赋予生命
Vue的过渡系统提供了许多简单的方式去实现动画的进入、离开和列表的动效。但是怎么样对你的数据本身进行动画处理呢?例如:
- 数字和计算
- 颜色显示
- 可缩放的矢量图形节点的位置
- 元素的大小和其他属性
所有这些或者被存储为原始数据或者被转化为数字。一旦我们这样做了,我们可以使用第三方库去把这些改变的状态做成动画,再结合Vue’s的响应性和组件系统。
动画状态与监听器
监听器允许我们去捕捉将任意数字属性变为另一个属性的动画。这可能听起来很复杂,让我们使用GreenSock研究一个例子:
<script src="https://unpkg.com/[email protected]"></script>
<script src="https://unpkg.com/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
<div id="animated-number-demo">
<input v-model.number = "number" type="number" step="20">
<p>{{ animatedNumber }}</p>
</div>
new Vue({
el: '#animated-number-demo',
data: {
number: 0,
tweenedNumber: 0
},
computed: {
animatedNumber: function() {
return this.tweenedNumber.toFixed(0);
}
},
watch: {
number: function(newValue) {
TweenLite.to(this.$data,0.5, {tweenedNumber: newValue});
}
}
})
当你更新数字的时候,改变动画在输入框的下面。这是一个很好的演示,但有些东西不是直接作为数字存储的。例如我们使用任何一项有效的CSS颜色值作为例子?这里我们使用 Tween.js 和Color.js实现:
<script src="https://unpkg.com/[email protected]"></script>
<script src="https://unpkg.com/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<div id="example-7">
<input
v-model="colorQuery"
v-on:keyup.enter="updateColor"
placeholder="Enter a color"
>
<button v-on:click="updateColor">Update</button>
<p>Preview:</p>
<span
v-bind:style="{ backgroundColor: tweenedCSSColor }"
class="example-7-color-preview"
></span>
<p>{{ tweenedCSSColor }}</p>
</div>
var Color = net.brehaut.Color
new Vue({
el: '#example-7',
data: {
colorQuery: '',
color: {
red: 0,
green: 0,
blue: 0,
alpha: 1
},
tweenedColor:{}
},
created: function() {
this.tweenedColor = Object.assign({}, this.color)
},
watch: {
color: function () {
function animate () {
if (TWEEN.update()){
requestAnimationFrame(animate)
}
}
new TWEEN.Tween(this.tweenedColor)
.to(this.color, 750)
.start()
animate()
}
},
computed: {
tweenedCSSColor: function () {
return new Color({
red: this.tweenedColor.red,
green: this.tweenedColor.green,
blue: this.tweenedColor.blue,
alpha: this.tweenedColor.alpha
}).toCSS()
}
},
methods: {
updateColor: function () {
this.color = new Color(this.colorQuery).toRGB()
this.colorQuery = ''
}
}
})
.example-7-color-preview {
display: inline-block;
width: 50px;
height: 50px;
}
动态状态过渡
正如Vue过渡组件一样,数据背后状态过渡会实时更新,这对于原型设计是特别有用的!当你修改一些变量, 即使一个简单的SVG多边形,你也能实现许多效果。
<script src="https://unpkg.com/[email protected]"></script>
<script src="https://unpkg.com/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.5/TweenLite.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: {{ sides }}</label>
<input
type="range"
min="3"
max="500"
v-model.number="sides"
>
<label>Minimum Radius: {{ minRadius }}%</label>
<input
type="range"
min="0"
max="90"
v-model.number="minRadius"
>
<label>Update Interval: {{ updateInterval }} milliseconds</label>
<input
type="range"
min="10"
max="2000"
v-model.number="updateInterval"
>
</div>
new Vue({
el: '#app',
data: function () {
var defaultSides = 10
var stats = Array.apply(null, { length: defaultSides })
.map(function () { return 100 })
return {
stats: stats,
points: generatePoints(stats),
sides: defaultSides,
minRadius: 50,
interval: null,
updateInterval: 500
}
},
watch: {
sides: function (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: function (newStats) {
TweenLite.to(
this.$data,
this.updateInterval / 1000,
{ points: generatePoints(newStats) }
)
},
updateInterval: function () {
this.resetInterval()
}
},
mounted: function () {
this.resetInterval()
},
methods: {
randomizeStats: function () {
var vm = this
this.stats = this.stats.map(function () {
return vm.newRandomValue()
})
},
newRandomValue: function () {
return Math.ceil(this.minRadius + Math.random() * (100 - this.minRadius))
},
resetInterval: function () {
var vm = this
clearInterval(this.interval)
this.randomizeStats()
this.interval = setInterval(function () {
vm.randomizeStats()
}, this.updateInterval)
}
}
})
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(' ')
}
svg { display: block; }
polygon { fill: #41B883; }
circle {
fill: transparent;
stroke: #35495E;
}
input[type="range"] {
display: block;
width: 100%;
margin-bottom: 15px;
}
把过渡放在组件内
在Vue的实例或组件中管理多个状态过渡会让复杂度很快提升。幸运的是,许多动画能够被提取到专用的子组件中。让我们使用之前例子中的整数动画来改写这个:
<script src="https://unpkg.com/[email protected]"></script>
<script src="https://unpkg.com/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<div id="example-8" class="demo">
<input v-model.number="firstNumber" type="number" step="20"> + <input v-model.number="secondNumber" type="number" step="20"> = {{ result }}
<p>
<animated-integer v-bind:value="firstNumber"></animated-integer> + <animated-integer v-bind:value="secondNumber"></animated-integer> = <animated-integer v-bind:value="result"></animated-integer>
</p>
</div>
// 我们希望动画在我们的应用中这个复杂的tweening逻辑能够在任何数字之间复用。组件也提供了一个清楚的界面用于配置更多动态过渡和复杂的过渡策略
Vue.component('animated-integer',{
template: '<span>{{ tweeningValue }}</span>',
props: {
value: {
type:Number,
required:true
}
},
data: function () {
return {
tweeningValue: 0
}
},
watch: {
value: function (newValue, oldValue) {
this.tween(oldValue, newValue)
}
},
mounted: function () {
this.tween(0, this.value)
},
methods: {
tween: function (startValue, endValue) {
var vm = this
function animate () {
if (TWEEN.update()) {
requestAnimationFrame(animate)
}
}
new TWEEN.Tween({ tweeningValue: startValue})
.to({ tweeningValue: endValue}, 500)
.onUpdate(function () {
vm.tweeningValue = this.tweeningValue.toFixed(0)
})
.start()
animate()
}
}
})
// 在主Vue实例中我们移除了所有的复杂性
new Vue({
el: '#example-8',
data: {
firstNumber: 20,
secondNumber: 40
},
computed: {
result: function () {
return this.firstNumber + this.secondNumber
}
}
})
在子组件内,我们能够使用这页介绍过的任何的联合过渡策略,和被Vue的提供的 built-in transition system一起。总之,实现各种过渡很少有限制。
赋予设计以生命
对于动画,一旦定义,意味着带来生命。不幸的是,当设计者创建了图标,logos,和吉祥物,他们常常通过图片或静态的SVGs做出。所以尽管GitHub的章鱼猫,Twitter的鸟,和其他许多logos像活生生的生物,他们看起来并不真的活着。
Vue能给予这方面的帮助。由于SVGs仅仅是数据,我们只需要这些生物兴奋,思考的,或惊慌的样子,然后Vue能帮我们在这些状态间过渡,组成你的欢迎页,加载指示器,和更多吸引人的通知。
Sarah Drasner展示了下面这个demo,这个demo结合了时间和交互相关的状态改变: