zoukankan      html  css  js  c++  java
  • Vue/React圆环进度条

    数据展示,一直是各行各业乐此不疲的需求,具体到前端开发行业,则是各种各种图表数据展示,各种表格数据展示,烦不胜烦(繁不胜繁)!
    前几天刚做了折线图、柱状图、饼状图之类的图表数据展示效果,今天又碰到了类似圆环进度条的展示效果。天天跟数据打交道,天天跟接口打交道,项目做了不少,菜逼还是菜逼,都是泪啊!
    其实说白了,是自己对canvas不熟,对CSS3不熟,所以就找了一个现成的轮子:

    <template>
      <div class="content" ref="box">
        <svg style="transform: rotate(-90deg)" :width="width" :height="width" xmlns="http://www.w3.org/2000/svg">
          <circle :r="(width-radius)/2"
            :cy="width/2"
            :cx="width/2"
            :stroke-width="radius"
            :stroke="backgroundColor"
            fill="none"
          />
          <circle ref="$bar"
            :r="(width-radius)/2"
            :cy="width/2"
            :cx="width/2"
            :stroke="barColor"
            :stroke-width="radius"
            :stroke-linecap="isRound ? 'round' : 'square'"
            :stroke-dasharray="(width-radius)*3.14"
            :stroke-dashoffset="isAnimation ? (width-radius) * 3.14 : (width - radius) * 3.14 * (100 - progress) / 100"
            fill="none"
          />
        </svg>
        <div class="center_text" :style="{color, fontSize}">
          <p v-if="!$slots.default" class="title">{{progress}}%</p>
          <slot></slot>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      props: {
        radius: {
          type: [Number, String],
          default: 20
        }, // 进度条厚度
        progress: {
          type: [Number, String],
          default: 20
        }, // 进度条百分比
        barColor: {
          type: String,
          default: "#1890ff"
        }, // 进度条颜色
        backgroundColor: {
          type: String,
          default: "rgba(0,0,0,0.3)"
        }, // 背景颜色
        isAnimation: {
          // 是否是动画效果
          type: Boolean,
          default: true
        },
        isRound: {
          // 是否是圆形画笔
          type: Boolean,
          default: true
        },
        id: {
          // 组件的id,多组件共存时使用
          type: [String, Number],
          default: 1
        },
        duration: {
          // 整个动画时长
          type: [String, Number],
          default: 1000
        },
        delay: {
          // 延迟多久执行
          type: [String, Number],
          default: 200
        },
        timeFunction: {
          // 动画缓动函数
          type: String,
          default: "cubic-bezier(0.99, 0.01, 0.22, 0.94)"
        },
        circleWidth: {
          //圆环宽度
          type: Number,
          default: 100,
        },
        color: {
          //文字颜色
          type: String,
          default: '#000'
        },
        fontSize: {
          //文字大小
          type: String,
          default: '18px'
        }
      },
      data() {
        return {
           this.circleWidth,
          idStr: `circle_progress_keyframes_${this.id}`
        };
      },
      beforeDestroy() {
        // 清除旧组件的样式标签
        document.getElementById(this.idStr) &&
        document.getElementById(this.idStr).remove();
        window.addEventListener(() => {});
      },
      mounted() {
        let self = this;
        this.setCircleWidth();
        this.setAnimation();
        // 此处不能使用window.onresize
        window.addEventListener(
          "resize",
          debounce(function() {
            self.setCircleWidth();
            self.setAnimation(self);
          }, 300)
        );
      },
      methods: {
        setCircleWidth() {
          let box = this.$refs.box;
          let width = box.clientWidth;
          let height = box.clientHeight;
          let cW = width > height ? height : width;
          this.width = cW;
        },
        setAnimation() {
          let self = this;
          if (self.isAnimation) {
            // 重复定义判断
            if (document.getElementById(self.idStr)) {
              console.warn("vue-circle-progress should not have same id style");
              document.getElementById(self.idStr).remove();
            }
            // 生成动画样式文件
            let style = document.createElement("style");
            style.id = self.idStr;
            style.type = "text/css";
            style.innerHTML = `
          @keyframes circle_progress_keyframes_name_${self.id} {
          from {stroke-dashoffset: ${(self.width - self.radius) * 3.14}px;}
          to {stroke-dashoffset: ${((self.width - self.radius) *
            3.14 *
            (100 - self.progress)) /
            100}px;}}
          .circle_progress_bar${
            self.id
          } {animation: circle_progress_keyframes_name_${self.id} ${
              self.duration
            }ms ${self.delay}ms ${self.timeFunction} forwards;}`;
            // 添加新样式文件
            document.getElementsByTagName("head")[0].appendChild(style);
            // 往svg元素中添加动画class
            self.$refs.$bar.classList.add(`circle_progress_bar${self.id}`);
          }
        }
      }
    };
    </script>
    <style scoped>
    .content {height:100%;display:flex;justify-content:center;align-items: center;}
    .center_text {position:absolute;}
    </style>
    

    使用方法:

    <CircleProgress :id="'circle1'" :circleWidth="40" :radius="7" :progress="30" :isAnimation="true" :backgroundColor="'#E9E9E9'" :barColor="'#FF4F4F'" />
    <CircleProgress :id="'circle2'" :circleWidth="40" :radius="7" :progress="50" :isAnimation="true" :backgroundColor="'#E9E9E9'" :barColor="'#FF902A'" />
    <CircleProgress :id="'circle3'" :circleWidth="40" :radius="7" :progress="89" :isAnimation="true" :backgroundColor="'#E9E9E9'" :barColor="'#FFDB4F'" />
    <CircleProgress :id="'circle4'" :circleWidth="40" :radius="7" :progress="25" :isAnimation="true" :backgroundColor="'#E9E9E9'" :barColor="'#B8D87E'" />
    

    使用时需要注意一下,如果你的页面中同时使用了超过两个以上的这种圆环进度条,就需要给每个圆环进度条设置不同的id,否则,所有圆环最终展示的数据都会是最后一个圆环的数据。

    代码中有一个防抖动的函数,这里就贴一下这个函数:

    function debounce(func, wait, immediate) {
      let timeout, args, context, timestamp, result
    
      const later = function () {
        // 据上一次触发时间间隔
        const last = +new Date() - timestamp
    
        // 上次被包装函数被调用时间间隔last小于设定时间间隔wait
        if (last < wait && last > 0) {
          timeout = setTimeout(later, wait - last)
        } else {
          timeout = null
          // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
          if (!immediate) {
            result = func.apply(context, args)
            if (!timeout) context = args = null
          }
        }
      }
    

    本文参考的是npm上的一个圆环进度条的插件vue-circleprogressbar,之所以没有在项目中直接安装并使用这个插件,是因为这个插件有点不太符合我们开发的需求,比如这个插件不能设置圆环的宽度,不能设置文字的颜色,不能设置文字的大小,再比如这个插件仅仅为了防抖而依赖了lodash(这个lodash的体积还是很大的)。

    至于这个组件在react中的使用,按照react的生命周期,或者react hooks的语法,或者dva模式的语法,稍微改巴改巴就可以使用了,很简单,就不再展开了。

  • 相关阅读:
    自动生成接口文档
    Haystack全文搜索
    redis操作
    缓存及跨域问题
    url控制器、解析器、响应器、分页器
    频率组件
    序列化、认证、权限、视图回顾
    认证、权限、视图组件
    序列化组件
    Rest Framework
  • 原文地址:https://www.cnblogs.com/tnnyang/p/11655317.html
Copyright © 2011-2022 走看看