elementui源码学习之仿写一个el-progress(横向进度条)

1,308 阅读2分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

本篇文章记录仿写一个el-progress组件细节,从而有助于大家更好理解饿了么ui对应组件具体工作细节。本文是elementui源码学习仿写系列的又一篇文章,后续空闲了会不断更新并仿写其他组件。源码在github上,大家可以拉下来,npm start运行跑起来,结合注释有助于更好的理解。

GitHub仓库地址:github.com/shuirongshu…

网站效果演示:ashuai.work:8888/#/myProgres…

组件分析

组件效果图

我们先看下组件效果图:

0.gif

这里笔者没有考虑垂直方向的进度条,读者们将代码拷贝下来进行改造哦,动动手多敲敲

组件介绍

进度条组件一般分为三种,一种是横向进度条(最为常用)、另两种是圆形进度条和仪表盘形进度条,本文说一下横向进度条的实现方式,值得一提的是无论是饿了么还是iview还是antd是吧三种进度条融合在一个组件中去的。

为了便于大家更好理解,我这里把进度条进行更为细致的拆分:

  • my-progress1 横向进度条
  • my-progress2 圆形进度条&仪表盘进度条

本文单说横向进度条(my-progress1),后文会说另外两种进度条

备注:若大家感觉仪表盘进度条不太适合,也可使用echarts中的仪表盘,总之实现方案有多种

组件结构

在封装一个组件之前,我们首先要考虑组件的dom结构,然后才是组件接收的props参数,一个横向进度条的dom结构分为:

  1. 进度条容器
  2. 进度条
  3. 文字内容区
  4. 最后再用一个大盒子包着形成一个整体即可

如下图:

1.png

对应html结构就是:

<!-- 4.大盒子包起来 -->
<div class="myProgressWrap">
    <!-- 1.进度条容器 -->
    <div class="progressBar">
      <!-- 2.进度条(背景色的变换) -->
      <div :class="['progressBarBg', status]" :style="barStyle"></div>
    </div>
    <!-- 3.文字内容区(要考虑自定义内容使用插槽) -->
    <div class="progressText" v-if="showText">
      <span v-if="!$slots.text">{{ percentage }}%</span>
      <slot v-else name="text"></slot>
    </div>
</div>

组件的功能

  • 预设几种默认样式进度条颜色 status参数
  • 百分比从0到100 percentage参数
  • 可自定义设置进度条的颜色 字符串color参数
  • 可让进度条在不同数值下显示不同颜色 函数color参数

新知识点css中通过var函数使用js中的变量

关于进度条组件没有太多新的知识点,都是笔者曾经封装组件提到过的,不过这里有一个新的知识点

什么是css中的var函数

关于css中的var函数,这里不赘述,忘记知识点的同学,可以看下官方文档:

developer.mozilla.org/zh-CN/docs/…

这里简述一下使用var函数的步骤

第一步 html标签中使用:style绑定

<div :class="['progressBarBg', status]" :style="
    {
        width: "60%",
        "--displayVal": displayVal,  // 注意这里的key需要使用--xxx开头value则是data中的数据
    }
"></div>

第二步 在data或者props或者computed中要定义的有值

data() {
    return {
      displayVal: "none", // css中使用js中变量的具体值
    };
  },

第三步 css中使用var函数读取使用

  .progressBarBg::before {
      display: var(--displayVal); 
      // 相当于:display: none
      content: "";
      ...
    }
  • 为什么需要提到要在css中使用js中的变量数据呢?因为进度条有一个动画效果,当外界传进来showDongHua变量为true时,需要展示这个动画,为false时就不要这个动画。
  • 这里就是js中的一个变量去控制css中的具体值
  • 换句话说,就是css通过使用js中的变量数据,去操作动画是否显示
  • 即:css中使用js中的变量

代码详情

这里大家复制粘贴,结合注释进行调试,就能够理解了😄

笔者就不啰嗦啦😉

使用时的代码

<template>
  <div>
    <h3>进度条</h3>
    <br>
    <h5>percentage百分比,status三种自带颜色,默认normal</h5>
    <h4><my-progress1 :percentage="30"></my-progress1></h4>
    <h4><my-progress1 :percentage="40" status="normal"></my-progress1></h4>
    <h4><my-progress1 :percentage="50" status="success"></my-progress1></h4>
    <h4><my-progress1 :percentage="60" status="fail"></my-progress1></h4>
    <br>
    <h5>自定义颜色(字符串)</h5>
    <h4><my-progress1 color="pink" :percentage="70"></my-progress1></h4>
    <br>
    <h5>自定义颜色(函数,可根据百分比设置不同的颜色)</h5>
    <h4><my-progress1 :color="colorFn" :percentage="60"></my-progress1></h4>
    <h4><my-progress1 :color="colorFn" :percentage="50"></my-progress1></h4>
    <br>
    <h5>进度条右侧文本区域插槽</h5>
    <h4>
      <my-progress1 :percentage="40">
        <span slot="text">₍ᐢ..ᐢ₎</span>
      </my-progress1>
    </h4>
    <h4>
      <my-progress1 :percentage="100">
        <span slot="text"></span>
      </my-progress1>
    </h4>
    <br />
    <h5>隐藏右侧文本区域插槽</h5>
    <h4>
      <my-progress1 :percentage="100" :showText="false"></my-progress1>
    </h4>
    <br>
    <h5>百分比增加减少,需要控制一下增加和减少的边界值0和100</h5>
    <h4>
      <my-progress1 :percentage="percentage" :color="diffColor"></my-progress1>
      <button @click="add">增加</button>
      <button @click="subtra">减少</button>
    </h4>
    <br />
    <h5>开启动画,默认动画关闭的</h5>
    <h4><my-progress1 :percentage="70" showDongHua></my-progress1></h4>
  </div>
</template>

<script>
export default {
  name: "myProgress1Name",
  data() {
    return {
      percentage: 15,
    };
  },
  methods: {
    colorFn(val) {
      // console.log("不同百分比区间显示不同颜色", val);
      if (val <= 50) {
        return "green";
      }
      if (val <= 100) {
        return "purple";
      }
    },
    add() {
      if (this.percentage + 10 >= 100) {
        this.percentage = 100;
      } else {
        this.percentage = this.percentage + 10;
      }
    },
    subtra() {
      if (this.percentage - 10 <= 0) {
        this.percentage = 0;
      } else {
        this.percentage = this.percentage - 10;
      }
    },
    diffColor(val) {
      if (val <= 10) {
        return "red";
      }
      if (val <= 20) {
        return "orange";
      }
      if (val <= 30) {
        return "yellow";
      }
      if (val <= 40) {
        return "green";
      }
      if (val <= 50) {
        return "cyan";
      }
      if (val <= 60) {
        return "blue";
      }
      if (val <= 70) {
        return "purple";
      }
      if (val <= 80) {
        return "pink";
      }
      if (val <= 90) {
        return "#baf";
      }
      if (val <= 100) {
        return "#F24E1E";
      }
    },
  },
};
</script>

封装组件的代码

<template>
  <div class="myProgressWrap">
    <div class="progressBar">
      <div :class="['progressBarBg', status]" :style="barStyle"></div>
    </div>
    <div class="progressText" v-if="showText">
      <!-- 没传递text插槽就显示百分比,反之显示插槽 -->
      <span v-if="!$slots.text">{{ percentage }}%</span>
      <slot v-else name="text"></slot>
    </div>
  </div>
</template>

<script>
// 自带三种状态颜色,默认normal
const statusArr = ["success", "fail", "normal"];
export default {
  name: "myProgress1",
  props: {
    percentage: {
      type: Number,
      default: 0,
      validator: (val) => {
        return (val >= 0) & (val <= 100);
      },
    },
    status: {
      type: String,
      validator: (val) => {
        return statusArr.includes(val);
      },
    },
    // 进度条颜色设置,字符串或函数
    color: {
      type: [String, Function],
    },
    // 默认展示文本区域内容
    showText: {
      type: Boolean,
      default: true,
    },
    // 是否开启动画
    showDongHua: {
      type: Boolean,
      default: false,
    },
  },
  watch: {
    showDongHua: {
      handler(newVal) {
        if (newVal) {
          this.displayVal = "";
        } else {
          this.displayVal = "none"; // 障眼法,隐藏伪元素,就没动画了
        }
      },
      immediate: true,
    },
  },
  data() {
    return {
      displayVal: "", // css中使用js中变量的具体值
    };
  },
  computed: {
    barStyle() {
      // style是对象,故返回一个对象
      return {
        width: this.percentage + "%",
        backgroundColor: this.setBg(), // 计算得出背景色
        /**
         * css中使用var函数步骤:
         * 1. :style中使用--xxx绑定一个js中数据的值
         * 2. js中要定义这个值便于能找到
         * 3. css中key: value键值对中 value使用var函数替换一下var(--xxx)
         * */
        "--displayVal": this.displayVal,
      };
    },
  },
  methods: {
    setBg() {
      if (typeof this.color === "string") {
        return this.color;
      }
      if (typeof this.color === "function") {
        // 可根据不同百分比计算出对应背景色
        return this.color(this.percentage);
      }
    },
  },
};
</script>

<style ref='xxx' lang='less' scoped>
.myProgressWrap {
  width: 100%;
  display: flex;
  align-items: center;
  .progressBar {
    flex: 1;
    height: 6px;
    background-color: #f3f3f3;
    border-radius: 6px;
    overflow-x: hidden;
    .progressBarBg {
      height: 100%;
      border-radius: 6px;
      // 默认normal
      background-color: #1677ff;
      // 加一个过渡,看着宽度变化自然些
      transition: width 0.36s ease;
      position: relative;
    }
    .progressBarBg::before {
      display: var(--displayVal);
      content: "";
      opacity: 0;
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: #fff;
      border-radius: 10px;
      animation: donghua 2s infinite;
    }
    .success {
      background-color: #52c41a;
    }
    .fail {
      background-color: #ff4d4f;
    }
  }
  .progressText {
    margin-left: 6px;
    width: fit-content; // 宽度随内容区自适应
  }
}

@keyframes donghua {
  0% {
    opacity: 0.5;
    width: 0;
  }
  100% {
    opacity: 0;
    width: 100%;
  }
}
</style>

总结

  • A bad pen is better than a good memory
  • 完整代码在github上哦,也可以直接访问网址看效果(还有其他笔者封装的组件)