vue3 实现滚动进度条排行榜效果

数据可视化大屏素材 yekong

数据可视化大屏项目开发中,经常会需要需要展示进度条排行榜的效果。今天我们来整理一下其实现的思路。

vue3 进度条排行榜实例

获取占比

一般后端返回数据后,前端需要根据数值自行计算占比,占比方式有两种,一种是所有数据累加占比,一种是最大值占比。这就需要我们获取数组中的最大值,以及获取数据中的总数,然后我们根据需要来判断使用哪种方式。

获取数组中的最大值

getMaxNumByKey(list, key) {
  var maxNum = list[0][key];
  list.forEach((type) => {
    if (type[key] > maxNum) {
      maxNum = type[key]
    }
  });
  return maxNum
},

获取数组总和

getTotalByKey(list, key) {
  var total = 0;
  list.forEach((type) => {
    total = total + type[key]
  });
  return total
},

排序

后端返回的数据可能并不是按照我们需要的排序返回的,所以前端需要对数据进行计算,按照需求,让其按照从小到大或者从大到小的方式进行排序.

从小到大排序

sortKey(array, key) {
  var list = array.sort(function (a, b) {
    var x = a[key];
    var y = b[key];
    return ((x < y) ? -1 : (x > y) ? 1 : 0)
  })
  return list
},

从大到小排序

sortKey(array, key) {
  var list = array.sort(function (a, b) {
    var x = a[key];
    var y = b[key];
    return ((x < y) ? -1 : (x > y) ? 1 : 0)
  })
  return list.reverse()
},

进度条加载动画效果

为了让进度条更加生动,我们需要给一个进度条加载动画效果。这里我们可以通过gsap补间动画来实现这种动画。我们需要安装gsap 然后引入使用gsap来达到我们想要的效果。

pnpm i  gsap
import gsap from 'gsap'
setTimeout(() => {
  gsap.to(this.$refs.progressBarInner, {
    duration: 1.5, width: this.width + '%', onComplete: () => {
      console.log('动画完成')
    }
  })
}, 1000)

上下滚动效果

数据大屏中一般展示区域是有限的,所以我们可能需要滚动展示,vue3项目中我们可以使用vue3-seamless-scroll来实现我们想要的效果。

安装vue3-seamless-scroll

pnpm i vue3-seamless-scroll

使用

import {Vue3SeamlessScroll} from "vue3-seamless-scroll";
components: {Vue3SeamlessScroll},
<Vue3SeamlessScroll :step="0.5" :wheel="true" :hover="true" :list="sortList" class="tableBody">
  <div class="list">
    <div class="listItem" v-for="(item,index) in sortList" :key="index">
      <div class="listItemTop">
        <div class="listItemTopl">
          <span>TOP {{ index + 1 }}</span><span>{{ item.name }}</span>
        </div>
        <div class="listItemTopr"><span>{{ item.num }}</span></div>
      </div>
      <div class="listItemBottom">
        <progressBar :index="index" :total="total" :item="item"></progressBar>
      </div>
    </div>
  </div>
</Vue3SeamlessScroll>

完整实例代码

父组件

<template>
  <div class="itemBodys hideScrollBar">
    <Vue3SeamlessScroll :step="0.5" :wheel="true" :hover="true" :list="sortList" class="tableBody">
      <div class="list">
        <div class="listItem" v-for="(item,index) in sortList" :key="index">
          <div class="listItemTop">
            <div class="listItemTopl">
              <span>TOP {{ index + 1 }}</span><span>{{ item.name }}</span>
            </div>
            <div class="listItemTopr"><span>{{ item.num }}</span></div>
          </div>
          <div class="listItemBottom">
            <progressBar :index="index" :total="total" :item="item"></progressBar>
          </div>
        </div>
      </div>
    </Vue3SeamlessScroll>
  </div>
</template>

<script>
import progressBar from './components/progressBar.vue'
import {Vue3SeamlessScroll} from "vue3-seamless-scroll";

export default {
  name: "title",
  data() {
    return {}
  },
  components: {progressBar, Vue3SeamlessScroll},
  props: {
    // 类型 0所有数据累加作为基数进行占比计算  1取数据中最大的数值作为基数进行计算
    type: {
      type: Number,
      default() {
        return 0;
      }
    },
    list: {
      type: Array,
      default() {
        return [{
          name: '测试1',
          num: 200,
        }, {
          name: '测试2',
          num: 10,
        }, {
          name: '测试3',
          num: 40,
        }, {
          name: '测试4',
          num: 50,
        }, {
          name: '测试5',
          num: 100,
        }, {
          name: '测试6',
          num: 10,
        }, {
          name: '测试7',
          num: 80,
        }, {
          name: '测试8',
          num: 90,
        }];
      }
    },
  },
  computed: {
    sortList: function () {
      return this.sortKey(this.list, 'num')
    },
    total: function () {
      var total = 0
      if (this.type == 0) {
        total = this.getTotalByKey(this.list, 'num')
      } else {
        total = this.getMaxNumByKey(this.list, 'num')
      }
      return total
    },
  },
  watch: {},
  mounted() {
  },
  methods: {
    sortKey(array, key) {
      var list = array.sort(function (a, b) {
        var x = a[key];
        var y = b[key];
        return ((x < y) ? -1 : (x > y) ? 1 : 0)
      })
      return list.reverse()
    },
    getMaxNumByKey(list, key) {
      var maxNum = list[0][key];
      list.forEach((type) => {
        if (type[key] > maxNum) {
          maxNum = type[key]
        }
      });
      return maxNum
    },
    getTotalByKey(list, key) {
      var total = 0;
      list.forEach((type) => {
        total = total + type[key]
      });
      return total
    },
  },
}
</script>

<style lang="scss" scoped>
.itemBodys {
  position: relative;
  width: calc(100% - 35px - 20px);
  margin: 0 auto;
  height: calc(100% - 20px);
  overflow: hidden;

  .list {
    width: calc(100% - 0px);
    display: flex;
    justify-content: flex-start;
    align-items: center;
    flex-wrap: nowrap;
    flex-direction: column;
    align-content: flex-start;
    height: 100%;

    .listItem {
      font-size: 14px;
      display: flex;
      justify-content: flex-start;
      align-items: flex-start;
      flex-wrap: nowrap;
      flex-direction: column;
      align-content: flex-start;
      width: 100%;

      .listItemTop {
        display: flex;
        justify-content: space-between;
        align-items: center;
        flex-wrap: nowrap;
        width: 100%;
        flex-direction: row;
        height: 50px;
        align-content: flex-start;

        .listItemTopl {
          display: flex;
          justify-content: flex-start;
          align-items: center;
          flex-wrap: nowrap;
          flex-direction: row;
          align-content: flex-start;

          span {
            font-size: 16px;
            font-family: DIN-Bold;
            font-weight: 500;
            color: #FFFFFF;
            text-shadow: 0 0 10px #3873ad;
            margin-right: 20px;
          }
        }

        .listItemTopr {
          display: flex;
          justify-content: flex-end;
          align-items: center;
          flex-wrap: nowrap;
          flex-direction: row;
          align-content: flex-start;
          font-size: 16px;
          font-family: DIN-Bold;
          font-weight: 500;
          color: #FFFFFF;
          text-shadow: 0 0 10px #3873ad;
        }
      }

      .listItemBottom {
        width: 100%;
      }
    }
  }
}
</style>

进度条组件

/**
* @Author: 858834013@qq.com
* @Name: progressBar
* @Date: 2023年06月11日10:10:10
* @Desc: 进度条效果
*/
<template>
  <div class="progressBar">
    <div class="progressBarInner"
         ref="progressBarInner"></div>
    <div class="line"></div>
  </div>
</template>

<script>
import gsap from "gsap";

export default {
  name: "progressBarBody",
  props: {
    item: {
      type: Object,
      default() {
        return {}
      }
    },
    index: {
      type: Number,
      default() {
        return 0
      }
    },
    total: {
      type: Number,
      default() {
        return 0
      }
    },
  },
  data() {
    return {}
  },
  computed: {
    index2: function () {
      return Number(this.index) + 1
    },
    width: function () {
      return (this.item.num / this.total).toFixed(2) * 100
    },
  },
  mounted() {
    setTimeout(() => {
      gsap.to(this.$refs.progressBarInner, {
        duration: 1.5, width: this.width + '%', onComplete: () => {
          console.log('动画完成')
        }
      })
    }, 1000)
  },
  methods: {}
}
</script>

<style lang="scss" scoped>
.progressBar {
  width: calc(100% - 0px);
  height: 10px;
  background: #20385c;
  border-radius: 3px;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  flex-wrap: nowrap;
  flex-direction: row;
  align-content: flex-start;
  position: relative;

  .progressBarInner {
    height: 10px;
    background: linear-gradient(180deg, #04CEF7 0%, #1263FF 100%);
    opacity: 1;
    width: 0%;
    border-radius: 3px;
    max-width: calc(100% - 0px);
  }

  .line {
    background: url("./assets/dot.png");
    width: 26px;
    height: 26px;
    opacity: 1;
    margin-left: -13px;
  }
}


</style>

喜欢