# 3.21 Progress 进度条

### 简介

组件 `Progress` 用于展示操作的当前进度，告知用户当前状态和预期。本文将深入分析组件源码，剖析其实现原理，耐心读完，相信会对您有所帮助。

组件源码文件 `packages/progress/src/progress.vue`。 🔗[gitee repo](https://gitee.com/ElemeFE/element/blob/dev/packages/progress/src/progress.vue) 🔗[组件文档 Progress](https://element.eleme.cn/#/zh-CN/component/progress)

更多组件剖析详见 👉 [**📚 Element 2 源码剖析组件总览**](https://juejin.cn/post/6994721241194037255) 。

### 源码实现

#### Props

组件`prop`声明如下：

```js
<script>
  export default {
    name: 'ElProgress',
    props: {
      type: {
        type: String,
        default: 'line',
        validator: val => ['line', 'circle', 'dashboard'].indexOf(val) > -1
      },
      percentage: {
        type: Number,
        default: 0,
        required: true,
        validator: val => val >= 0 && val <= 100
      },
      status: {
        type: String,
        validator: val => ['success', 'exception', 'warning'].indexOf(val) > -1
      },
      strokeWidth: {
        type: Number,
        default: 6
      },
      strokeLinecap: {
        type: String,
        default: 'round'
      },
      textInside: {
        type: Boolean,
        default: false
      },
      width: {
        type: Number,
        default: 126
      },
      showText: {
        type: Boolean,
        default: true
      },
      color: {
        type: [String, Array, Function],
        default: ''
      },
      format: Function
    },
  };
</script>
```

各属性功能说明。

| 参数             | 说明                                          | 类型                    | 可选值                       | 默认值   |
| -------------- | ------------------------------------------- | --------------------- | ------------------------- | ----- |
| percentage     | 百分比（必填）                                     | number                | 0-100                     | 0     |
| type           | 进度条类型                                       | string                | line/circle/dashboard     | line  |
| stroke-width   | 进度条的高度，单位 px                                | number                | —                         | 6     |
| text-inside    | 进度条显示文字内置在进度条内（只在 type=line 时可用）            | boolean               | —                         | false |
| status         | 进度条当前状态                                     | string                | success/exception/warning | —     |
| color          | 进度条背景色（会覆盖 status 状态颜色）                     | string/function/array | —                         | ''    |
| width          | 环形进度条画布宽度（只在 type 为 circle 或 dashboard 时可用） | number                |                           | 126   |
| show-text      | 是否显示进度条文字内容                                 | boolean               | —                         | true  |
| stroke-linecap | circle/dashboard 类型路径两端的形状                  | string                | butt/round/square         | round |
| format         | 指定进度条文字内容                                   | function(percentage)  | —                         | —     |

`type` 、`percentage`、 `status` 供了自定义验证函数，验证属性值是否符合可选值或者可选范围。 当验证失败的时候，(开发环境构建版本的) Vue 将会产生一个控制台的警告。

#### Template

```js
<template>
  <div
    class="el-progress"
    :class="[
      'el-progress--' + type,
      status ? 'is-' + status : '',
      {
        'el-progress--without-text': !showText,
        'el-progress--text-inside': textInside,
      }
    ]"
    role="progressbar"
    :aria-valuenow="percentage"
    aria-valuemin="0"
    aria-valuemax="100"
  >
    <div class="el-progress-bar" v-if="type === 'line'">
       // ...
    </div>
    <div class="el-progress-circle" v-else>
      // ...
    </div>
    <div class="el-progress__text" v-if="showText && !textInside">
        // ...
    </div>
  </div>
</template>
```

组件根节点一个类名`el-progress`的 `<div>`元素 ，根据组件设定的属性值动态绑定组件样式：

* 进度条类型 `el-progress--[line/circle/dashboard]`
* 进度条状态 `is-[success/exception/warning]`
* 不显示进度条文字 `el-progress--without-text`
* 显示文字内置在进度条内 `el-progress--text-inside`

属性 `role` 、`aria-valuemin` 、`aria-valuenow`、`aria-valuemax` 实现 ARIA 无障碍网页应用， 更多内容详见 ["ARIA",MDN](https://developer.mozilla.org/zh-CN/docs/Web/Accessibility/ARIA)

根节点下包含 3 个子节点：

* 类名`el-progress-bar`的 `<div>`元素用于渲染线形进度条功能。
* 类名`el-progress-circle`的 `<div>`元素用于渲染环形/仪表盘进度条功能。
* 类名`el-progress__text`的 `<div>`元素渲染外显文字内容。

到此组件主要实现结构已经介绍完毕，接下来将分析各功能源码实现逻辑。

### 线形进度条

线形进度条，提供了进度百分比显示、文字内显/外显、文字内容格式化、自定义颜色等功能。

组件 DOM 结构如下图： ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d1e8a2a19dcb4b088ba95725879c7c43~tplv-k3u1fbpfcp-watermark.image?)

* 1️⃣ `el-progress` 组件根节点
* 2️⃣ `el-progress-bar` 线形进度条节点
  * 3️⃣ `el-progress-bar__outer` 进度条背景
  * 4️⃣ `el-progress-bar__inner` 进度显示
  * 5️⃣ `el-progress-bar__innerText` 内显文字
* 6️⃣ `el-progress__text` 外显文字

```html
<div class="el-progress">
  <div class="el-progress-bar" v-if="type === 'line'">
    <div class="el-progress-bar__outer" :style="{height: strokeWidth + 'px'}">
      <div class="el-progress-bar__inner" :style="barStyle">
        <div class="el-progress-bar__innerText" v-if="showText && textInside">
          {{content}}
        </div>
      </div>
    </div>
  </div>
  <div class="el-progress-circle" v-else>// ..</div>
  <div
    class="el-progress__text"
    v-if="showText && !textInside"
    :style="{fontSize: progressTextSize + 'px'}"
  >
    <template v-if="!status">{{content}}</template>
    <i v-else :class="iconClass"></i>
  </div>
</div>
```

#### 1️⃣

根据组件设定的属性值绑定组件样式： 进度条类型、进度条状态 、不显示进度条文字、文字显示位置（内置/外部）。

#### 2️⃣

当传入`type`值为`line`或不设置 `type`时，组件渲染成线形进度条。

#### 3️⃣

属性`strokeWidth`用于设置进度条背景的高度。

#### 4️⃣

计算属性`barStyle`用于控制进度条进度和颜色。

`barStyle`根据 `percentage` 属性值动态生成样式对象 `{ width: "80%" , backgroundColor: "" }`。 进度显示通过宽度百分比控制，此处的 `backgroundColor` 为自定义颜色值（会覆盖 status 状态颜色），默认情况为空。

```js
barStyle() {
  const style = {};
  style.width = this.percentage + '%';
  style.backgroundColor = this.getCurrentColor(this.percentage);
  return style;
},
```

***

#### 自定义颜色

方法 `getCurrentColor`用于自定义进度条颜色，通过判断属性 `color` 值的类型实现各自逻辑处理。

`color`  可以接受颜色字符串，函数和数组。

* 若值为函数，函数第一参数应传入 `percentage` 属性值 （格式`function(percentage)`），直接调用函数即可`this.color(percentage)`。
* 若值为字符串，直接返回属性值 `this.color`，`color` 默认值为`""`, 不设置该属性，默认情况下执行此逻辑。
* 若值为数组（字符串或对象数组），根据 `percentage` 所属范围，返回对应颜色。

若值为数组,则调用方法 `getLevelColor` 。

```js
getCurrentColor(percentage) {
  if (typeof this.color === 'function') {
    return this.color(percentage);
  } else if (typeof this.color === 'string') {
    return this.color;
  } else {
    return this.getLevelColor(percentage);
  }
},
```

方法 `getColorArray`，返回对象数组，定义不同数值范围的背景颜色。若字符串数组，方法根据数组长度，自动切分构建成对象数组 `[{ color: "#f56c6c", percentage: 20 }]`。 若传入对象数组，对象格式应为 `{ color: string, percentage: number }`。

```js
getColorArray() {
  const color = this.color;
  const span = 100 / color.length;
  return color.map((seriesColor, index) => {
    // 字符串数组，构建成对象数组
    if (typeof seriesColor === 'string') {
      return {
        color: seriesColor,
        percentage: (index + 1) * span
      };
    }
    return seriesColor;
  });
}

// [
//   { color: "#f56c6c", percentage: 20 },
//   { color: "#e6a23c", percentage: 40 },
//   { color: "#5cb87a", percentage: 60 },
//   { color: "#1989fa", percentage: 80 },
//   { color: "#6f7ad3", percentage: 100 },
// ]
```

方法 `getLevelColor`中调用方法`getColorArray`并排序，比对`percentage`属于哪一区间，返回对应的背景颜色。

```js
getLevelColor(percentage) {
  const colorArray = this.getColorArray().sort((a, b) => a.percentage - b.percentage);

  for (let i = 0; i < colorArray.length; i++) {
    if (colorArray[i].percentage > percentage) {
      return colorArray[i].color;
    }
  }
  return colorArray[colorArray.length - 1].color;
},
```

因为比较使用了`>`,例如 `percentage` 值 100 时, 就会执行`return colorArray[colorArray.length - 1].color;` 返回数组最后对象的颜色。

```html
<el-progress :percentage="percentage" :color="customColors"></el-progress>

<script>
  export default {
    data() {
      return {
        percentage: 100,
        customColors: [
          { color: "#f56c6c", percentage: 20 },
          { color: "#e6a23c", percentage: 40 },
          { color: "#5cb87a", percentage: 60 },
          { color: "#1989fa", percentage: 80 },
          { color: "#6f7ad3", percentage: 100 },
        ],
      };
    },
  };
</script>
```

***

#### 5️⃣

内显内容计算属性 `content`。默认按照 `[percentage]%`格式显示进度百分比。 若设置了属性`format`,则使用函数处理返回文字内容。

组件通过属性 `showText` 、 `textInside` 控制文字显示状态和位置。

```js
content() {
  if (typeof this.format === 'function') {
    return this.format(this.percentage) || '';
  } else {
    return `${this.percentage}%`;
  }
}
```

#### 6️⃣

外显内容跟内显一样，计算属性`content`。若设置了进度条状态`status`，则显示图标。

```html
<template v-if="!status">{{content}}</template>
<i v-else :class="iconClass"></i>
```

计算属性 `iconClass` 返回不同状态 `success/exception/warning`的 icon, 不同类型下`success/exception` 对应图标有所区别。

```js
iconClass() {
  if (this.status === 'warning') {
    return 'el-icon-warning';
  }
  if (this.type === 'line') {
    return this.status === 'success' ? 'el-icon-circle-check' : 'el-icon-circle-close';
  } else {
    return this.status === 'success' ? 'el-icon-check' : 'el-icon-close';
  }
},
```

使用计算属性`progressTextSize`控制字体大小。 当类型为`line`时，基于进度条高度-属性`strokeWidth`；当类型为`circle/dashboard`时，基于环形进度条画布宽度-属性`width`。

```js
progressTextSize() {
  return this.type === 'line'
    ? 12 + this.strokeWidth * 0.4
    : this.width * 0.111111 + 2 ;
},
```

#### 自定义文字格式

![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c7f685a5c70d47d69319d6ce782cd9bd~tplv-k3u1fbpfcp-watermark.image?)

```html
// format 示例
<el-progress :percentage="100" :format="format"></el-progress>

<script>
  export default {
    methods: {
      format(percentage) {
        return percentage === 100 ? "满" : `${percentage}%`;
      },
    },
  };
</script>
```

### 组件样式

组件样式源码 `packages\theme-chalk\src\progress.scss` 使用 `scss` 的混合指令 `b`、 `e`、 `m`、 `when` 嵌套生成组件样式。

```

// 生成 .el-progress
@include b(progress) {
  // ...

  // 生成 .el-progress__text
  @include e(text) {
    // ...

    // 生成 .el-progress__text i
    i {
      // ...
    }
  }
  // 生成 .el-progress--circle, .el-progress--dashboard
  @include m((circle,dashboard)) {
    // ...

    // 生成
    // .el-progress--circle .el-progress__text,
    // .el-progress--dashboard .el-progress__text
    .el-progress__text {
      // ...

      // 生成
      // .el-progress--circle .el-progress__text i,
      // .el-progress--dashboard .el-progress__text i
      i {
        // ...
      }
    }
  }


  @include m(without-text) {
    // 生成 .el-progress--without-text .el-progress__text
    .el-progress__text {
      // ...
    }
    // 生成 .el-progress--without-text .el-progress-bar
    .el-progress-bar {
      // ...
    }
  }

  @include m(text-inside) {
    // 生成 .el-progress--text-inside .el-progress-bar
    .el-progress-bar {
      // ...
    }
  }

  // 状态样式
  @include when(success) {
    // 生成 .el-progress.is-success .el-progress-bar__inner
    .el-progress-bar__inner {
      // ...
    }

    // 生成 .el-progress.is-success .el-progress-bar__text
    .el-progress__text {
      // ...
    }
  }

  @include when(warning) {
    // ...
  }

  @include when(exception) {
    // ...
  }
}

// 生成 .el-progress-bar
@include b(progress-bar) {
  // ...

  // 生成 .el-progress-bar__outer
  @include e(outer) {
    // ...
  }
  // 生成 .el-progress-bar__inner
  @include e(inner) {
    // ...

    // 生成 .el-progress-bar__inner::after
    @include utils-vertical-center;
  }

  // 生成 .el-progress-bar__innerText
  @include e(innerText) {
    // ...
  }
}

// 该关键帧规则 代码中没有生效
@keyframes progress {
  0% {
    background-position: 0 0;
  }

  100% {
    background-position: 32px 0;
  }
}
```

在类`.el-progress-bar__inner`  定义了默认进度条颜色 `background-color: #409eff;`。

若指定了组件状态，会生成样式覆盖掉默认颜色。同时制定了外显文本的字体图标颜色。

```css
// success/exception/warning
.el-progress.is-success .el-progress-bar__inner {
  background-color: #67c23a;
}
.el-progress.is-success .el-progress__text {
  color: #67c23a;
}
```

若是使用属性`color`自定义颜色，会生成内联样式，会覆盖 status 状态颜色。

内显文本居右对齐，属性是在`.el-progress-bar__inner`的定义的。

```css
.el-progress-bar__inner {
  text-align: right;
  // ...
}
```

### 环形/仪表盘进度条

环形/仪表盘进度条实现主要使用了`<svg>`元素，会另开篇幅进行详细介绍。

#### 容器/画布

类名`el-progress-circle`的`div`元素创建环形进度条画布宽度（基于属性`width`）。

```html
<div class="el-progress">
  <div class="el-progress-bar" v-if="type === 'line'">// ...</div>
  <div
    class="el-progress-circle"
    :style="{height: width + 'px', width: width + 'px'}"
    v-else
  >
    <svg viewBox="0 0 100 100">
      <path
        class="el-progress-circle__track"
        :d="trackPath"
        stroke="#e5e9f2"
        :stroke-width="relativeStrokeWidth"
        fill="none"
        :style="trailPathStyle"
      ></path>
      <path
        class="el-progress-circle__path"
        :d="trackPath"
        :stroke="stroke"
        fill="none"
        :stroke-linecap="strokeLinecap"
        :stroke-width="percentage ? relativeStrokeWidth : 0"
        :style="circlePathStyle"
      ></path>
    </svg>
  </div>
  <div
    class="el-progress__text"
    v-if="showText && !textInside"
    :style="{fontSize: progressTextSize + 'px'}"
  >
    <template v-if="!status">{{content}}</template>
    <i v-else :class="iconClass"></i>
  </div>
</div>
```

环形/仪表盘进度条使用了`<svg>`元素，元素的`viewBox` 属性允许指定一个给定的一组图形伸展以适应特定的容器元素。`viewBox` 属性的值是一个包含 4 个参数的列表  `min-x`, `min-y`, `width` 、 `height`， 以空格或者逗号分隔开， 在用户空间中指定一个矩形区域映射到给定的元素，不允许宽度和高度为负值，0 则禁用元素的呈现。 此属性绝非三言两语能说清楚，在此不过多详述，推荐阅读 [理解 SVG viewport,viewBox,preserveAspectRatio 缩放](https://www.zhangxinxu.com/wordpress/2014/08/svg-viewport-viewbox-preserveaspectratio/)。

`<svg>`元素实现，使用了两个 `path` 元素创建背景环`el-progress-circle__track`和进度环`el-progress-circle__path`。

`svg`中的元素在浏览器渲染时会遵守 html 中有固定的排列顺序。先出现的元素会先绘制层级较低，而后绘制的元素层级依次增高，如果元素间的位置有重叠，则会现后绘制的元素会遮盖住先出现的元素（层级高遮盖层级低元素）。 渲染后图形叠加就实现了进度环效果。

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e79232be5f73461eabced09122495c51~tplv-k3u1fbpfcp-watermark.image?)

对比可知环形/仪表盘的背景环、以及进度环起始都不一样，组件使用了功能更加强大灵活 `<path>` 标签来实现，但理解起来有点难度。若只是环形进度条可以使用`<circle>`标签实现会更加简单。

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/132abaf5e3b54ccfb99f90d23c747ede~tplv-k3u1fbpfcp-watermark.image?)

#### 背景环 Path

接下先介绍几个基础且重要的计算属性。

属性 `relativeStrokeWidth`是圆形轮廓的宽度，值类型为 `percentage`, 是进度条的宽度跟画布宽度的百分比。 `strokeWidth` 默认值 6 , `width` 默认值 12, 所以 `relativeStrokeWidth` 默认值为 4.8 。

```js
relativeStrokeWidth() {
  return (this.strokeWidth / this.width * 100).toFixed(1);
},
```

属性 `radius` 是绘制圆形的半径，值类型为 `percentage`。

```js
radius() {
  if (this.type === 'circle' || this.type === 'dashboard') {
    // 画布一半 减去 轮廓宽度的一半
    return parseInt(50 - parseFloat(this.relativeStrokeWidth) / 2, 10);
  } else {
    return 0;
  }
},
```

属性 `radius` 是圆形的周长。

```js
perimeter() {
  return 2 * Math.PI * this.radius;
},
```

属性 `rate` 表示绘制圆形的弧长，类型 `dashboard`时只会绘制 `3/4` 周长长度的轮廓，也就是仪表盘进度条为什么有缺口。

```
rate() {
  return this.type === 'dashboard' ? 0.75 : 1;
},
```

背景环 `<path>` 元素中属性`d`创建一个圆形；属性`stroke-width`指定了圆形的轮廓的宽度，绑定计算属性`relativeStrokeWidth` ；属性`stroke`定义了给定图形元素的外轮廓的颜色`#e5e9f2`；属性 `fill` 用于填充轮廓内的形状的颜色，值为`none`无填充；计算属性 `trailPathStyle` 会生成 `stroke-dasharray`和`stroke-dashoffset`等属性用于设置描边的显示和偏移错位。

```js
<path
  class="el-progress-circle__track"
  :d="trackPath"
  stroke="#e5e9f2"
  :stroke-width="relativeStrokeWidth"
  fill="none"
  :style="trailPathStyle">
</path>
```

`fill` 属性填充效果如下：

![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/651dcbd5077f48989bbffb3d4bb97853~tplv-k3u1fbpfcp-watermark.image?)

**d 属性**

属性`d`使用计算属性`trackPath`，返回创建圆环的一系列路径描述。

```js
trackPath() {
  const radius = this.radius;
  const isDashboard = this.type === 'dashboard';
  return `
    M 50 50
    m 0 ${isDashboard ? '' : '-'}${radius}
    a ${radius} ${radius} 0 1 1 0 ${isDashboard ? '-' : ''}${radius * 2}
    a ${radius} ${radius} 0 1 1 0 ${isDashboard ? '' : '-'}${radius * 2}
    `;
},
```

不同类型对应的路径描述。

```js
// circle
M 50 50
m 0 -47
a 47 47 0 1 1 0 94
a 47 47 0 1 1 0 -94

// dashbord
M 50 50
m 0 47
a 47 47 0 1 1 0 -94
a 47 47 0 1 1 0 94
```

路径描述中用到了`Moveto`、`Arcto`这两个指令。

> 指令是大小写敏感的；一个大写的命令指明它的参数是绝对位置，而小写的命令指明相对于当前位置的点。可以指定一个负数值作为命令的参数：负角度将是逆时针的，绝对 x 和 y 位置将视为负坐标。负相对 x 值将会往左移，而负相对 y 值将会向上移。

`Moveto` 命令用作开始一个路径。可以被想象成拎起绘图笔，落脚到另一处。在上一个点和这个指定点之间没有线段绘制。

* `M x,y`  在这里 x 和 y 是绝对坐标，分别代表水平坐标和垂直坐标。
* `m dx,dy`  在这里 dx 和 dy 是相对于当前点的距离，分别是向右和向下的距离。

`Arcto` 命令用作绘制椭圆弧(圆是特殊的椭圆)。 指令格式 `A/a (rx ry x-axis-rotation large-arc-flag sweep-flag x y)` ，

* `A (绝对)| a (相对)`
* `rx ry`是椭圆的两个半轴的长度。
* `x-axis-rotation` 是椭圆相对于坐标系的旋转角度，角度数而非弧度数。
* `large-arc-flag` 是标记绘制大弧(1)还是小弧(0)部分。
* `sweep-flag` 是标记向顺时针(1)还是逆时针(0)方向绘制。
* `x y` 是圆弧终点的坐标。 关于 `large-arc-flag`、`sweep-flag`参数对应的绘制情况。因为绘制的是半圆，参数`large-arc-flag`的绘制大小弧对组件没有影响。

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/183505f6badd46ec917a411f054b6d6b~tplv-k3u1fbpfcp-watermark.image?)

**`circle` 路径图解**

`circle`类型的路径指令解读如下：

1. `M 50 50` 绘制从点位 1️⃣ 也就是`圆心`开始，绝对坐标 `(50,50)`。
2. `m 0 47` 从点位 1️⃣ 沿 y 轴下移 47，也就是圆的半径长度,移动到点位 2️⃣。
3. `a 47 47 0 1 1 0 -94` 从点位 2️⃣ 到点位 3️⃣ 绘制一个半圆弧，从点位 2️⃣ 沿 y 轴上移 94（圆直径长度）就到点位 3️⃣。
4. `a 47 47 0 1 1 0 94` 从点位 3️⃣ 到点位 4️⃣(也就是点位 2️⃣)绘制一个半圆弧，从点位 3️⃣ 沿 y 轴下移 94 就到点位 4️⃣，形成一个闭合圆。

![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a38dfd922a83449ba4ed3cd6f7fa0999~tplv-k3u1fbpfcp-watermark.image?)

**`dashbord` 路径图解**

`dashbord`类型的路径指令解读如下：

1. `M 50 50` 绘制从点位 1️⃣ 也就是`圆心`开始，绝对坐标 `(50,50)`。
2. `m 0 -47` 从点位 1️⃣ 沿 y 轴上移 47，也就是圆的半径长度,移动到点位 2️⃣。
3. `a 47 47 0 1 1 0 94` 从点位 2️⃣ 到点位 3️⃣ 绘制一个半圆弧，从点位 2️⃣ 沿 y 轴下移 94（圆直径长度）就到点位 3️⃣。
4. `a 47 47 0 1 1 0 -94` 从点位 3️⃣ 到点位 4️⃣(也就是点位 2️⃣)绘制一个半圆弧，从点位 3️⃣ 沿 y 轴上移 94 就到点位 4️⃣，形成一个闭合圆。

![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/db78c3b4e4dc47afb0fa7d9848c3d0d2~tplv-k3u1fbpfcp-watermark.image?)

不同类型的图形绘制起点(点位 2️⃣)，决定了进度条进度起点。当然`dashbord`类型到此还没有结束，它的缺口处理还没有。

**缺口实现**

`stroke-dasharray`和`stroke-dashoffset`等属性可以直接用作一个 CSS 样式表内部的属性。 计算属性  `trailPathStyle`  会生成一个样式对象，用于设置描边的显示和偏移错位。

```js
trailPathStyle() {
  return {
    strokeDasharray: `${(this.perimeter * this.rate)}px, ${this.perimeter}px`,
    strokeDashoffset: this.strokeDashoffset
  };
},
// circle
// {
//     stroke-dasharray: 295.31px, 295.31px;
//     stroke-dashoffset: -36.9137px;
// }

// dashbord
// {
//     stroke-dasharray: 221.482px, 295.31px;
//     stroke-dashoffset: -36.9137px;
// }
```

`stroke-dasharray`第一个属性值表是轮廓显示长度，`circle`为 `perimeter` 圆的周长，`dashbord`为 `0.75*perimeter`。

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a8976f367fc94fabae853036cf18ddb4~tplv-k3u1fbpfcp-watermark.image?)

计算属性`strokeDashoffset`用于起点的偏移，正数为 x 值向左偏移，负数为 x 值向右偏移。

```js
strokeDashoffset() {
  const offset = -1 * this.perimeter * (1 - this.rate) / 2;
  return `${offset}px`;
},
```

相当于顺时针旋转圆，旋转缺口弧度的一半，形成一个对称结构。 ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/14e2db3673e74b7d8b0a1b4a9f066e4d~tplv-k3u1fbpfcp-watermark.image?)

#### 进度环 Path

相较于背景环，进度环代码多了 `stroke-linecap`属性。

```html
<path
  class="el-progress-circle__path"
  :d="trackPath"
  :stroke="stroke"
  fill="none"
  :stroke-linecap="strokeLinecap"
  :stroke-width="percentage ? relativeStrokeWidth : 0"
  :style="circlePathStyle"
>
</path>
```

跟背景环一样路径创建，不同之处进度展示通过`stroke-dasharray` 第一个属性值进行控制，通过`绘制圆弧长度*percentage`来显示进度。同时加入了`transition`缓动效果，使视感更加真实。

```js
circlePathStyle() {
  return {
    strokeDasharray: `${this.perimeter * this.rate * (this.percentage / 100) }px, ${this.perimeter}px`,
    strokeDashoffset: this.strokeDashoffset,
    transition: 'stroke-dasharray 0.6s ease 0s, stroke 0.6s ease'
  };
},
```

属性 `stroke-linecap` 进度条边缘的形状为方形；\
![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f089e3810b164700a3408e74dc677929~tplv-k3u1fbpfcp-watermark.image?)

进度环的颜色使用计算属性`stroke`，支持自定义颜色、状态颜色，默认进度条背景色为`#20a0ff`。

```js
stroke() {
  let ret;
  if (this.color) {
    ret = this.getCurrentColor(this.percentage);
  } else {
    switch (this.status) {
      case 'success':
        ret = '#13ce66';
        break;
      case 'exception':
        ret = '#ff4949';
        break;
      case 'warning':
        ret = '#e6a23c';
        break;
      default:
        ret = '#20a0ff';
    }
  }
  return ret;
},
```

当 `percentage` 值为 0 时，`stroke-width`属性值为 0，元素将不绘制轮廓。

```js
:stroke-width="percentage ? relativeStrokeWidth : 0"
```

#### 外显文本

由前文可知，环形的内容显示为外显内容元素`el-progress__text`，计算属性`content`。若设置了进度条状态`status`，则显示图标。计算属性  `iconClass`  返回不同状态  `success/exception/warning`的 icon, 不同类型下`success/exception`  对应图标有所区别。

```html
<div
  class="el-progress__text"
  v-if="showText && !textInside"
  :style="{fontSize: progressTextSize + 'px'}"
>
  <template v-if="!status">{{content}}</template>
  <i v-else :class="iconClass"></i>
</div>
```

外显文本样式使用绝对定位，在画布水平垂直居中。

```css
.el-progress--circle .el-progress__text,
.el-progress--dashboard .el-progress__text {
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  text-align: center;
  margin: 0;
  -webkit-transform: translate(0, -50%);
  transform: translate(0, -50%);
}
.el-progress--circle .el-progress__text i,
.el-progress--dashboard .el-progress__text i {
  vertical-align: middle;
  display: inline-block;
}
```

### 📚 参考&&关联阅读

["SVG/Element/path",MDN](https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/path)\
["SVG/Attribute",MDN](https://developer.mozilla.org/zh-CN/docs/Web/SVG/Attribute)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://anduril.gitbook.io/learning-element/chapter03_source_code_analysis/21-progress.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
