最近有一个数据可视化大屏开发,需要一个动画效果。列表环绕旋转动画效果。这个效果和上一个效果gsap补间动画环绕运行效果比较类似,只不过上一个效果是单个组件实现动画,当前效果是多个组件实现动画。客户看了以后增加了一项要求,就是动画中的两行高度是不一样的,上面的高一些,下面的矮一些,gsap 补间动画列表两种高度环绕旋转动画效果。
效果演示
效果要求:
列表以矩形方式展示,可以顺时针环绕旋转也可以逆时针环绕旋转。
实现思路:
之前学习了gsap补间动画,所以选择gsap来实现这种效果。
通过fromTo来实现动画效果。动画分为逆时针和顺时针两种方式
顺时针
顺时针分为4个动作,分别是从左到右的动画,从上到下的动画,从右到左的动画,从下到上的动画,一共4步组成一个闭环。
逆时针
逆时针和顺时针一样,只不过方向是反着的。
gsap.fromTo(this.$refs.listItem, {
top: this.$refs.listItem.offsetHeight + 'px',
left: '0%',
}, {
duration: 1,
top: '0%',
left: '0%',
});
父组件
首先写一个父组件,用来渲染列表,添加顺时针和逆时针的点击事件。然后添加一个基数,通过监听这个基数的变化来触发动画。
<template>
<div class="items">
<div class="listMain">
<div class="infoleft" @click="getPrev">左侧</div>
<div class="list">
<item ref="item" :isNext="isNext" :base="base" :index="index" :list="list" v-for="(item,index) in list"
:key="index"></item>
</div>
<div class="infoleft" @click="getNext">右侧</div>
</div>
</div>
</template>
<script>
import item from './item.vue'
export default {
name: "item1",
data() {
return {
height: 0,
base: 0,
isNext: true,
list: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
},
components: {item},
watch: {},
mounted() {
this.base = 1000 * this.list.length
},
methods: {
getNext() {
this.base = this.base + 1
this.isNext = true
},
getPrev() {
this.base = this.base - 1
this.isNext = false
},
},
}
</script>
<style lang="scss" scoped>
.items {
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
align-content: flex-start;
.listMain {
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
align-content: flex-start;
.infoleft {
width: 50px;
position: relative;
height: 100%;
background: red;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
align-content: flex-start;
}
.list {
position: relative;
width: calc(100% - 100px);
height: 100%;
}
}
}
</style>
子组件
子组件用来完成补间动画
首先要监听父组件的参数变化,参数变化后触发动画,根据父组件的参数来判断是顺时针动画还是逆时针动画。然后根据基数来判断当前组件的位置,用来给组件添加动画事件。
<template>
<div class="listItem" ref="listItem" :class="{greens:index==0}" :style="{left:left+'%',top:top+'%'}">
<div class="listItems">
<!-- <p>l:{{ left }}</p>-->
<p>b:{{ baseNum }}</p>
<p>i:{{ index }}</p></div>
</div>
</template>
<script>
import gsap from 'gsap'
export default {
name: "item",
data() {
return {
runBackwards: false,
isFirst: 0
}
},
props: {
index: {
type: Number,
default() {
return 0;
}
},
base: {
type: Number,
default() {
return 0;
}
},
isNext: {
type: Boolean,
default() {
return true;
}
},
list: {
type: Array,
default() {
return [];
}
},
},
watch: {
base(from, to) {
console.log(this.base)
if (this.isNext) {
this.getNext()
} else {
this.getPrev()
}
},
},
computed: {
left: function () {
var left = 0
var length = (this.list.length) / 2
if (this.index < length) {
left = this.index * (100 / length)
} else {
left = (this.list.length - 1 - this.index) * (100 / length)
}
return left
},
top: function () {
var top = 0
var length = (this.list.length) / 2
if (this.index < length) {
top = 0
} else {
top = 50
}
return top
},
baseNum: function () {
var baseNum = ((Math.abs(this.base) + this.index) % this.list.length)
return baseNum
},
baseNum2: function () {
var baseNum = ((Math.abs(this.base) + this.index) % this.list.length)
return baseNum
},
},
methods: {
getNext() {
var that = this;
// 从下到上
if (this.baseNum == 0) {
gsap.fromTo(this.$refs.listItem, {
top: this.$refs.listItem.offsetHeight + 'px',
left: '0%',
}, {
duration: 1,
top: '0%',
left: '0%',
});
} else if (this.baseNum < (this.list.length) / 2 && this.baseNum > 0) {
// 从左到右的动画
gsap.fromTo(this.$refs.listItem, {
left: this.$refs.listItem.offsetWidth * (this.baseNum - 1) + 'px',
top: 0,
}, {
duration: 1,
top: 0,
left: this.$refs.listItem.offsetWidth * (this.baseNum) + 'px',
});
} else if (this.baseNum == (this.list.length) / 2) {
// 从上到下的动画
gsap.fromTo(this.$refs.listItem, {
top: 0,
left: '80%',
}, {
duration: 1,
left: '80%',
top: this.$refs.listItem.offsetHeight + 'px',
});
} else if (this.baseNum > (this.list.length) / 2) {
// 从右到左的动画
gsap.fromTo(this.$refs.listItem, {
left: this.$refs.listItem.offsetWidth * ((this.list.length) - this.baseNum) + 'px',
top: '50%',
}, {
duration: 1,
left: this.$refs.listItem.offsetWidth * ((this.list.length) - this.baseNum - 1) + 'px',
top: '50%',
});
}
},
getPrev() {
var that = this;
console.log('index:' + this.index + ':' + this.baseNum2)
console.log('baseNum2')
if (this.baseNum2 == 0) {
gsap.fromTo(this.$refs.listItem, {
top: '0%',
left: '0%',
}, {
duration: 1,
top: this.$refs.listItem.offsetHeight + 'px',
left: '0%',
});
} else if (this.baseNum2 < (this.list.length) / 2 && this.baseNum2 > 0) {
gsap.fromTo(this.$refs.listItem, {
left: this.$refs.listItem.offsetWidth * (this.baseNum2) + 'px',
top: 0,
}, {
duration: 1,
top: 0,
left: this.$refs.listItem.offsetWidth * (this.baseNum2 - 1) + 'px',
});
} else if (this.baseNum2 == (this.list.length) / 2) {
gsap.fromTo(this.$refs.listItem, {
left: '80%',
top: this.$refs.listItem.offsetHeight + 'px',
}, {
left: '80%',
duration: 1,
top: 0,
});
} else if (this.baseNum2 > (this.list.length) / 2) {
gsap.fromTo(this.$refs.listItem, {
top: '50%',
left: this.$refs.listItem.offsetWidth * ((this.list.length) - this.baseNum2 - 1) + 'px',
}, {
duration: 1,
left: this.$refs.listItem.offsetWidth * ((this.list.length) - this.baseNum2) + 'px',
top: '50%',
});
}
},
},
}
</script>
<style lang="scss" scoped>
.listItem {
width: 20%;
height: 50%;
position: absolute;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
align-content: flex-start;
.listItems {
position: relative;
width: 80%;
height: 80%;
background: red;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
flex-direction: column;
align-content: flex-start;
font-size: 40px;
color: green;
}
}
.greens {
background: green !important;
}
</style>
至此,列表环绕旋转动画效果这个组件就基本完成了。