SVG 实现半环形进度条

要实现的效果图如下

image.png

svg 语法学习

可以参考https://developer.mozilla.org/zh-CN/docs/Web/SVG网站上的语法

元素参考

<path>

path元素用来定义形状的通用元素。
下面的命令可用于路径数据:

  • M = moveto
  • L = lineto
  • H = horizontal lineto
  • V = vertical lineto
  • C = curveto
  • S = smooth curveto
  • Q = quadratic Belzier curve
  • T = smooth quadratic Belzier curveto
  • A = elliptical Arc
  • Z = closepath
注释:以上所有命令均允许小写字母。大写表示绝对定位,小写表示相对定位。

例如:画个圆环

<path d="
        M 50 50
        m 0 -47
        a 47 47 0 1 1 0 94
        a 47 47 0 1 1 0 -94
        " stroke="#e5e9f2" stroke-width="4.8" fill="none" ></path>

解析:


image.png

<symbol>

symbol元素用来定义一个图形模板对象,它可以用一个<use>元素实例化。symbol元素对图形的作用是在同一文档中多次使用,添加结构和语义。结构丰富的文档可以更生动地呈现出来,类似讲演稿或盲文,从而提升了可访问性。注意,一个symbol元素本身是不呈现的。只有symbol元素的实例(亦即,一个引用了symbol<use>元素)才能呈现。

<use>

use元素在SVG文档内取得目标节点,并在别的地方复制它们。它的效果等同于这些节点被深克隆到一个不可见的DOM中,然后将其粘贴到use元素的位置,很像HTML5中的克隆模板元素。因为克隆的节点是不可见的,所以当使用CSS样式化一个use元素以及它的隐藏的后代元素的时候,必须小心注意。隐藏的、克隆的DOM不能保证继承CSS属性,除非你明文设置使用[CSS继承]
(https://developer.mozilla.org/en/CSS/inheritance)。
一般情况下symbol 和use配合着使用的。
例如: 效果中的小人,如果自己画的话,就非常耗时间,可以在iconfont 里找一个,然后引入进来;
在自己的svg 里引入

<use xlink:href="#male" x="180" y="135"/>
<use xlink:href="#female" x="230" y="135"/>

在阿里图标库复制svg,如下:两个小人

<svg class="icon" >
      <symbol id="male" viewBox="0 0 1024 1024" width="80" height="80" fill="#23C8F5">
        <path d="M631.091 661.333l-13.79 319.932A44.988 44.988 0 0 1 572.416 1024l-64.717-42.667L443.017 1024a44.988 44.988 0 0 1-44.852-42.735l-13.79-319.932h-27.682a45.807 45.807 0 0 1-45.397-42.564l-38.161-277.538a40.073 40.073 0 0 1 40.55-42.564h388.096c23.723 0 41.882 19.046 40.55 42.564l-38.126 277.538a45.807 45.807 0 0 1-45.398 42.564h-27.716zM507.733 256a128 128 0 1 1 0-256 128 128 0 0 1 0 256z"></path>
      </symbol>
      <symbol id="female" viewBox="0 0 1024 1024" width="80" height="80" fill="#f4ea2a">
        <path d="M512 160m-160 0a160 160 0 1 0 320 0 160 160 0 1 0-320 0Z" ></path>
        <path d="M384 800h256l-77.888 224H456.992z" p-id="2418" ></path>
        <path d="M480 320h63.424a96 96 0 0 1 88.768 59.456l144.928 352A96 96 0 0 1 688.32 864H335.04a96 96 0 0 1-88.8-132.544l144.96-352A96 96 0 0 1 480 320z"></path>
      </symbol>
    </svg>

解析结果:


image.png

<line>

line元素是一个SVG基本形状,用来创建一条连接两个点的线。

  • x1 属性在 x 轴定义线条的开始
  • y1 属性在 y 轴定义线条的开始
  • x2 属性在 x 轴定义线条的结束
  • y2 属性在 y 轴定义线条的结束
    例如:
<line
        x1="210"
        y1="174"
        x2="0"
        y2="174"
        stroke="rgba(35, 200, 245, 0.39)"
        stroke-dasharray="2,2"
        stroke-width="2"
      />

解析:


image.png

<text>

text元素定义了一个由文字组成的图形。
例如:

   <text x="0" y="160" fill="#23C8F5" class="svg-text">
        男:45%
      </text>

解析:


image.png

<linearGradient>

linearGradient元素用来定义线性渐变,用于图形元素的填充或描边。
例如:

<svg width="120" height="120"  viewBox="0 0 120 120"
     xmlns="http://www.w3.org/2000/svg" version="1.1"
     xmlns:xlink="http://www.w3.org/1999/xlink" >

    <defs>
        <linearGradient id="MyGradient">
            <stop offset="5%"  stop-color="green"/>
            <stop offset="95%" stop-color="gold"/>
        </linearGradient>
    </defs>

    <rect fill="url(#MyGradient)"
          x="10" y="10" width="100" height="100"/>
</svg>

解析:


svg 属性参考

<viewBox>

viewBox属性允许指定一个给定的一组图形伸展以适应特定的容器元素。
viewBox属性的值是一个包含4个参数的列表 min-x, min-y, width and height, 以空格或者逗号分隔开。

<stroke-dasharray>

stroke-dasharray 属性可控制用来描边的点划线的图案范式,作为一个外观属性,它也可以直接用作一个CSS样式表内部的属性,用于创建虚线,之所以后面跟的是array的,是因为值其实是数组。
例如:

stroke-dasharray = '10'
stroke-dasharray = '10, 5'
stroke-dasharray = '20, 10, 5'

解析:


image.png

stroke-dasharray为一个参数时: 其实是表示虚线长度和每段虚线之间的间距
  如:stroke-dasharray = '10' 表示:虚线长10,间距10,然后重复 虚线长10,间距10

<stroke-dashoffset >

offset:偏移的意思,这个属性是相对于起始点的偏移,正数偏移x值的时候,相当于往左移动了x个长度单位,负数偏移x的时候,相当于往右移动了x个长度单位。
需要注意的是,不管偏移的方向是哪边,要记得dasharray 是循环的,也就是 虚线-间隔-虚线-间隔。
这个属性要搭配stroke-dashoffset才能看得出来效果,非虚线的话,是无法看出偏移的。
\color{red}{注意:dashoffset 偏移是顺时针的字}

思路

半弧度进度条是最难的,可使用stroke-dasharray配合stroke-dashoffset 来画。

弧度讲解1.png
图2.png

进度红色部分AC为实线弧度,灰色部分BC为虚线弧度,空白部分AB弧度为间隔

  • 实线弧度AC = 圆周长P - 弧线弧长BC - 空白弧长AB
  • 偏移角度:左半边偏移量只和空白弧长AB有关,右半边偏移量和弧长BE+空白弧长AB有关
  • 空白弧长AB = 2Rarcsin(a/2R) (注意a为弦长AB)
  • 图 2 里的4个半弧长是有4个path 化成的
  • 由于弧长有宽度,弧长AB的半径 = 进度条弧长的半径- 弧长的宽度

实现代码

<template>
  <div class="circle-wrap">
    <svg class="circle" viewBox="0 0 480 350">
      <defs>
        <linearGradient id="greenGradient" x1="100%" y1="100%">
          <stop offset="0%" stop-color="rgba(32, 174, 214, 0.07)">
            <!-- <animate attributeName="stop-color" values="lightblue;blue;red;red;black;red;red;purple;lightblue" dur="14s" repeatCount="indefinite" /> -->
          </stop>
          <stop offset="100%" stop-color="rgba(8, 24, 88, 0.07)">
            <!-- <animate attributeName="stop-color" values="lightblue;orange;purple;purple;black;purple;purple;blue;lightblue" dur="14s" repeatCount="indefinite" />
            <animate attributeName="offset" values=".95;.80;.60;.40;.20;0;.20;.40;.60;.80;.95" dur="14s" repeatCount="indefinite" /> -->
          </stop>
        </linearGradient>
      </defs>
      <polygon
        points="80 260,400 260,480 340,0 340"
        fill="url(#greenGradient)"
      />
      <!-- 内圆 -->
      <path
        d="
        M 240 175
        m 0 110
        a 110 110 0 1 1 0 -220
        a 110 110 0 1 1 0 220
        "
        stroke="#09265E"
        stroke-width="1"
        fill="none"
        stroke-linecap="round"
        class="inside-circle"
        style="stroke-dasharray: 5px 5px"
      ></path>
      <!-- 中间两个半圆圆 -->
      <path
        d="
        M 240 175
        m 0 130
        a 130 130 0 1 1 0 -260
        a 130 130 0 1 1 0 260
        "
        stroke="#09265E"
        stroke-width="20"
        fill="none"
        stroke-linecap="round"
        class="left-below-circle"
        :style="{
          strokeDasharray:
            midBelowArcLength + 'px   ' + (midP - midBelowArcLength) + 'px',
          strokeDashoffset: -Math.floor(midIntervalLeng / 2) + 'px',
        }"
      ></path>

      <path
        d="
        M 240 175
        m 0 130
        a 130 130 0 1 1 0 -260
        a 130 130 0 1 1 0 260
        "
        stroke="#23C8F5"
        stroke-width="20"
        fill="none"
        stroke-linecap="round"
        v-show="leftMidSolidLine"
        class="left-above-circle"
        :style="{
          strokeDasharray:
            leftMidSolidLine + 'px   ' + leftMidDottedLine + 'px',
          strokeDashoffset: -Math.floor(midIntervalLeng / 2) + 'px',
        }"
      >
        >
      </path>
      <path
        d="
          M 240 175
          m 0 130
          a 130 130 0 1 1 0 -260
          a 130 130 0 1 1 0 260"
        stroke="#09265E"
        stroke-width="20"
        fill="none"
        stroke-linecap="round"
        class="right-below-circle"
        :style="{
          strokeDasharray:
            midBelowArcLength + 'px   ' + (midP - midBelowArcLength) + 'px',
          strokeDashoffset:
            Math.floor(midIntervalLeng / 2 + midBelowArcLength) + 'px',
        }"
      ></path>
      <path
        d="
          M 240 175
          m 0 130
          a 130 130 0 1 1 0 -260
          a 130 130 0 1 1 0 260"
        stroke="#E7B417"
        stroke-width="20"
        fill="none"
        stroke-linecap="round"
        class="right-above-circle"
        :style="{
          strokeDasharray:
            rightMidSolidLine + 'px   ' + rightMidDottedLine + 'px',
          strokeDashoffset:
            Math.floor(midIntervalLeng / 2 + rightMidSolidLine) + 'px',
        }"
      ></path>
      <!-- 外边圆 -->
      <path
        d="
        M 240 175
        m 0 145
        a 145 145 0 1 1 0 -290
        a 145 145 0 1 1 0 290
        "
        stroke="#09265E"
        stroke-width="2"
        fill="none"
        stroke-linecap="round"
        class="left-below-circle"
        :style="{
          strokeDasharray:
            outBelowArcLength + 'px   ' + (outP - outBelowArcLength) + 'px',
          strokeDashoffset: -Math.floor(outIntervalLeng / 2) + 'px',
        }"
      ></path>
      <path
        d="
        M 240 175
        m 0 145
        a 145 145 0 1 1 0 -290
        a 145 145 0 1 1 0 290
        "
        stroke="#23C8F5"
        stroke-width="2"
        fill="none"
        stroke-linecap="round"
        v-show="leftOutSolidLine"
        class="left-above-circle"
        :style="{
          strokeDasharray:
            leftOutSolidLine + 'px   ' + leftOutDottedLine + 'px',
          strokeDashoffset: -Math.floor(outIntervalLeng / 2) + 'px',
        }"
      ></path>
      <path
        d="
        M 240 175
        m 0 145
        a 145 145 0 1 1 0 -290
        a 145 145 0 1 1 0 290
        "
        stroke="#09265E"
        stroke-width="2"
        fill="none"
        stroke-linecap="round"
        class="right-below-circle"
        :style="{
          strokeDasharray:
            outBelowArcLength + 'px   ' + (outP - outBelowArcLength) + 'px',
          strokeDashoffset:
            Math.floor(outIntervalLeng / 2 + outBelowArcLength) + 'px',
        }"
      ></path>
      <path
        d="
          M 240 175
          m 0 145
          a 145 145 0 1 1 0 -290
          a 145 145 0 1 1 0 290"
        stroke="#E7B417"
        stroke-width="2"
        fill="none"
        stroke-linecap="round"
        class="right-above-circle"
        :style="{
          strokeDasharray:
            rightOutSolidLine + 'px   ' + rightOutDottedLine + 'px',
          strokeDashoffset:
            Math.floor(outIntervalLeng / 2 + rightOutSolidLine) + 'px',
        }"
      ></path>
      <text x="0" y="160" fill="#23C8F5" class="svg-text">
        男:{{ leftPer }}%
      </text>
      <text x="410" y="160" fill="#E7B417" class="svg-text">
        女:{{ rightPer }}%
      </text>
      <!-- 左边线 -->
      <line
        x1="210"
        y1="174"
        x2="0"
        y2="174"
        stroke="rgba(35, 200, 245, 0.39)"
        stroke-dasharray="2,2"
        stroke-width="2"
      />
      <!-- 右边线 -->
      <line
        x1="275"
        y1="174"
        x2="480"
        y2="174"
        stroke="rgba(216, 162, 18, .39)"
        stroke-dasharray="2,2"
        stroke-width="2"
      />
      <use xlink:href="#male" x="180" y="135" />
      <use xlink:href="#female" x="230" y="135" />
    </svg>

    <svg class="icon">
      <symbol
        id="male"
        viewBox="0 0 1024 1024"
        width="80"
        height="80"
        fill="#23C8F5"
      >
        <path
          d="M631.091 661.333l-13.79 319.932A44.988 44.988 0 0 1 572.416 1024l-64.717-42.667L443.017 1024a44.988 44.988 0 0 1-44.852-42.735l-13.79-319.932h-27.682a45.807 45.807 0 0 1-45.397-42.564l-38.161-277.538a40.073 40.073 0 0 1 40.55-42.564h388.096c23.723 0 41.882 19.046 40.55 42.564l-38.126 277.538a45.807 45.807 0 0 1-45.398 42.564h-27.716zM507.733 256a128 128 0 1 1 0-256 128 128 0 0 1 0 256z"
        ></path>
      </symbol>
      <symbol
        id="female"
        viewBox="0 0 1024 1024"
        width="80"
        height="80"
        fill="#f4ea2a"
      >
        <path
          d="M512 160m-160 0a160 160 0 1 0 320 0 160 160 0 1 0-320 0Z"
        ></path>
        <path d="M384 800h256l-77.888 224H456.992z" p-id="2418"></path>
        <path
          d="M480 320h63.424a96 96 0 0 1 88.768 59.456l144.928 352A96 96 0 0 1 688.32 864H335.04a96 96 0 0 1-88.8-132.544l144.96-352A96 96 0 0 1 480 320z"
        ></path>
      </symbol>
    </svg>
  </div>
</template>
<script>
export default {
  components: {},
  props: {},
  data() {
    return {
      pi: 3.1415926, // π
      midR: 130, // 中间圆半径
      midR2: 110,
      outR: 145, // 外圆半径
      midP: '', // 中间圆周长
      outP: '', // 外圆周长
      a: 70, // 弦长
      midBelowArcLength: '', // 中间半弧长
      outBelowArcLength: '', // 外面半弧长
      leftMidSolidLine: '', // 中间左半圆实线弧长
      leftMidDottedLine: '', // 中间左半圆虚线弧长
      rightMidSolidLine: '', // 中间右半圆实线弧长
      rightMidDottedLine: '', // 中间半圆虚线弧长
      rightMidrotate: '', // 中间半圆旋转角度
      leftOutSolidLine: '', // 外边左半圆实线弧长
      leftOutDottedLine: '', // 外边左半圆虚线弧长
      rightOutSolidLine: '', // 外边右半圆实线弧长
      rightOutDottedLine: '', // 外边半圆虚线弧长
      rightOutrotate: '', // 外边半圆旋转角度
      midIntervalLeng: '', // 中间空白弧长
      outIntervalLeng: '', // 外边空白弧长
      // leftAbovePer: 45, // 左边进度
      // rightAbovePer: 55, // 右边进度
    };
  },
  mounted() {
    
    this.getP();
    this.getArcLeng();
    this.getAboveArcLeng();
  },
  props:{
    leftPer: { // 左边进度
      type: Number,
      default() {
        return 45;
      }
    },
    rightPer:{ // 右边进度
      type: Number,
      default() {
        return 55;
      }
    }
  },
  watch:{
    leftPer(newVal,oldVal) {
      // this.rightPer = 100 - newVal;
      this.getAboveArcLeng()

    }
  },
  methods: {
    /**
     * 计算周长函数
     */
    getP() {
      this.midP = this.pi * 2 * this.midR;
      console.log('中间圆周长=', this.midP);
      this.outP = this.pi * 2 * this.outR;
      console.log('外边圆周长=', this.outP);
    },
    /**
     * 计算弦长函数
     */
    getArcLeng() {
      this.midIntervalLeng =
        2 * this.midR * Math.asin(this.a / (2 * this.midR2)); // 中间圆空白弧长
      console.log('中间圆空白弧长=', this.midIntervalLeng);
      this.midBelowArcLength = (this.midP - this.midIntervalLeng * 2) / 2; // 中间圆底层半弧长
      console.log('中间圆底层半弧长=', this.midBelowArcLength);

      this.outIntervalLeng =
        2 * this.outR * Math.asin(this.a / (2 * this.outR)); // 外边圆空白弧长
      console.log('外边圆空白弧长=', this.outIntervalLeng);
      this.outBelowArcLength = (this.outP - this.outIntervalLeng * 2) / 2; // 外边圆底层半弧长
      console.log('外边圆底层半弧长=', this.outBelowArcLength);
    },
    /**
     * 计算进度弧长
     */
    getAboveArcLeng() {
      // 中间圆左边实线弧长
      this.leftMidSolidLine = this.leftPer
        ? (this.midBelowArcLength / 100) * this.leftPer - 10
        : 0;
      // console.log('中间圆左边实线弧长=', this.leftMidSolidLine);
      // 中间圆左边虚线弧长
      this.leftMidDottedLine = this.midP - this.leftMidSolidLine;
      // console.log('中间圆左边虚线弧长=', this.leftMidDottedLine);

      // 中间圆右边实线弧长
      this.rightMidSolidLine = this.rightPer
        ? (this.midBelowArcLength / 100) * this.rightPer - 10
        : 0;
      // console.log('中间圆右边实线弧长=', this.rightMidSolidLine);
      // 中间圆右边虚线弧长
      this.rightMidDottedLine = this.midP - this.rightMidSolidLine;
      // console.log('中间圆右边虚线弧长=', this.rightMidDottedLine);

      // 外边圆左边实线弧长
      this.leftOutSolidLine = this.leftPer
        ? (this.outBelowArcLength / 100) * this.leftPer
        : 0;
      // console.log('外边圆左边实线弧长=', this.outBelowArcLength);
      // 外边圆左边虚线弧长
      this.leftOutDottedLine = this.outP - this.leftOutSolidLine;
      // console.log('外边圆左边虚线弧长=', this.leftOutDottedLine);

      // 外边圆右边实线弧长
      this.rightOutSolidLine = this.rightPer
        ? (this.outBelowArcLength / 100) * this.rightPer - 10
        : 0;
      // console.log('外边圆右边实线弧长=', this.rightOutSolidLine);
      // 外边圆右边虚线弧长
      this.rightOutDottedLine = this.outP - this.rightOutSolidLine;
      // console.log('外边圆右边虚线弧长=', this.rightOutDottedLine);
    },
  },
};
</script>
<style lang="scss" scoped>
.circle-wrap {
  position: relative;
  width: 100%;
  height: 100%;
}
.circle {
  width: 100%;
  height: 100%;
}

.svg-text {
  font-size: 16px;
}
</style>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349