gsap 补间动画列表环绕旋转动画效果

vue yekong

最近有一个数据可视化大屏开发,需要一个动画效果。列表环绕旋转动画效果。这个效果和上一个效果gsap补间动画环绕运行效果比较类似,只不过上一个效果是单个组件实现动画,当前效果是多个组件实现动画。客户看了以后增加了一项要求,就是动画中的两行高度是不一样的,上面的高一些,下面的矮一些,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>

至此,列表环绕旋转动画效果这个组件就基本完成了。

更多动画效果

gsap 动画效果汇总

喜欢