Svg实现三种形态的进度条,兼容小程序

前言

进度条在H5,小程序,管理后台的应用都是非常常见的,一般常见的圆形进度条,条形进度条在各个UI框架都是有组件的,可谓是拿来即用。那问题来了,需求要求实现半圆进度条和圆角矩形进度条这可怎么办呢?废话不多说,直接上实现效果以及代码。

技术

基于Taro+vue3+svg

实现效果

实现思路

因为svg在小程序并不能识别,所以我们在开发的时候需要把svg转成data:image/svg+xml格式的URL,再用img渲染出来。

公共方法代码

index.ts文件
import Taro from '@tarojs/taro';
export function stringToBase64(html) {
  const arrayBuffer = new Uint8Array(stringToArrayBuffer(html));
  const base64 = Taro.arrayBufferToBase64(arrayBuffer);
  // 构造data:image/svg+xml格式的URL
  return `data:image/svg+xml;base64,${base64}`;
}

// 字符串转为ArrayBuffer对象,参数为字符串
function stringToArrayBuffer(str) {
  const bytes = [] as any;
  let c;
  const len = str.length;
  for (let i = 0; i < len; i++) {
    c = str.charCodeAt(i);
    if (c >= 0x010000 && c <= 0x10ffff) {
      bytes.push(((c >> 18) & 0x07) | 0xf0);
      bytes.push(((c >> 12) & 0x3f) | 0x80);
      bytes.push(((c >> 6) & 0x3f) | 0x80);
      bytes.push((c & 0x3f) | 0x80);
    } else if (c >= 0x000800 && c <= 0x00ffff) {
      bytes.push(((c >> 12) & 0x0f) | 0xe0);
      bytes.push(((c >> 6) & 0x3f) | 0x80);
      bytes.push((c & 0x3f) | 0x80);
    } else if (c >= 0x000080 && c <= 0x0007ff) {
      bytes.push(((c >> 6) & 0x1f) | 0xc0);
      bytes.push((c & 0x3f) | 0x80);
    } else {
      bytes.push(c & 0xff);
    }
  }
  const array = new Int8Array(bytes.length);
  for (let i = 0; i <= bytes.length; i++) {
    array[i] = bytes[i];
  }
  return array.buffer;
}

圆形进度条代码

<template>
  <image :style="{ width: '100%', height: '100%' }" :src="dataURL"></image>
</template>
<script lang="tsx" setup>
  import { stringToBase64 } from './utils/index';
  import { ref, watchEffect } from 'vue';
  import { renderToString } from '@vue/server-renderer';
  interface IProps {
    defaultDurTime?: number; //进度条完结时间
    progress?: number; //当前进度0-100
    color?: object | string; //进度条颜色,传入对象格式可以定义渐变色
    size?: number; //直径
    layerColor?: string; //轨道颜色
    fill?: string; //填充颜色
    strokeWidth?: number; //线条宽度
    strokeLinecap?: string; //进度条端点(butt,round,square)
  }
  const props = withDefaults(defineProps<IProps>(), {
    defaultDurTime: 500,
    progress: 90,
    color: () => {
      return { '0': '#80EDFF', '100': '#4A7DFF' };
    },
    layerColor: '#e0e0e0',
    fill: 'none',
    size: 200,
    strokeWidth: 20,
    strokeLinecap: 'round',
  });
  const progressOffset = ref(0);
  const totalArcLength = ref(0);
  const strokeBgColor = ref<string | object>('#666666');
  let dataURL = ref('');
  async function handleSave() {
    const html = await renderToString(renderTitle());
    dataURL.value = stringToBase64(html);
    console.log(dataURL);
  }
  const renderTitle = () => {
    return (
      <svg
        width={props.size + props.strokeWidth}
        height={props.size + props.strokeWidth}
        xmlns="http://www.w3.org/2000/svg"
        style={{ transform: 'rotate(-90deg)', transformOrigin: 'center' }}
      >
        {Object.prototype.toString.call(props.color) === '[object Object]' && (
          <defs>
            <linearGradient id="progressGradient" x1="0%" y1="0%" x2="100%" y2="0%">
              {Object.keys(props.color).map((key) => (
                <stop offset={`${key}%`} stop-color={props.color[key]} key={key}></stop>
              ))}
            </linearGradient>
          </defs>
        )}
        <circle
          cx="50%"
          cy="50%"
          r={0.5 * props.size}
          style={{
            strokeWidth: props.strokeWidth,
            fill: 'none',
            stroke: props.layerColor,
          }}
        ></circle>
        <circle
          style={{
            strokeDashoffset: progressOffset.value,
            strokeDasharray: totalArcLength.value,
            strokeWidth: props.strokeWidth,
            fill: props.fill,
            strokeLinecap: props.strokeLinecap,
            stroke: strokeBgColor.value,
          }}
          cx="50%"
          cy="50%"
          r={0.5 * props.size}
        >
          <animate
            attributeName="stroke-dashoffset"
            dur={`${props.defaultDurTime}ms`}
            fill="freeze"
            from={totalArcLength.value}
            to={progressOffset.value}
          ></animate>
        </circle>
      </svg>
    );
  };
  watchEffect(() => {
    const circumference = Math.PI * props.size;
    totalArcLength.value = circumference;
    if (props.strokeLinecap !== 'butt' && props.progress !== 100 && props.progress > 95) {
      progressOffset.value =
        circumference - (props.progress / 100) * circumference + props.strokeWidth;
    } else {
      progressOffset.value = circumference - (props.progress / 100) * circumference;
    }
    if (Object.prototype.toString.call(props.color) === '[object Object]') {
      strokeBgColor.value = `url(#progressGradient)`;
    } else {
      strokeBgColor.value = props.color;
    }
    handleSave();
  });
</script>

props

参数 说明 类型 默认值
defaultDurTime 动画速度(也可以任务是进度条完结时间) number 500
progress 进度百分比(0-100) number 90
color 进度条颜色,传入对象格式可以定义渐变色 object 或 string { ‘0’: ‘#80EDFF’, ‘100’: ‘#4A7DFF’ }
size 直径 number 200
layerColor 轨道颜色 string #e0e0e0
fill 填充颜色 string ‘none’
strokeWidth 进度条宽度 number 20
strokeLinecap 进度条端点(butt,round,square) string ’round’

半圆形进度条

<script lang="tsx" setup>
  import { stringToBase64 } from './utils/index';
  import { ref, watchEffect } from 'vue';
  import { renderToString } from '@vue/server-renderer';
  interface IProps {
    defaultDurTime?: number; //进度条完结时间
    progress?: number; //当前进度0-100
    color?: object | string; //进度条颜色,传入对象格式可以定义渐变色
    size?: number; //直径
    layerColor?: string; //轨道颜色
    fill?: string; //填充颜色
    strokeWidth?: number; //线条宽度
    strokeLinecap?: string; //进度条端点(butt,round,square)
  }  
  const props = withDefaults(defineProps<IProps>(), {
    defaultDurTime: 1000, //进度条完结时间
    progress: 50,
    color: () => {
      return { '0': '#80EDFF', '100': '#4A7DFF' };
    },
    size: 200,
    layerColor: '#e0e0e0',
    fill: 'none',
    strokeWidth: 20,
    strokeLinecap: 'round',
  });
  const viewBox = ref('');
  const memoryTrackPath = ref('');
  const progressPath = ref('');
  const strokeDasharray = ref(0);
  const strokeDashoffset = ref(0);
  const strokeBgColor = ref<string | object>('#666666');
  let dataURL = ref('');
  async function handleSave() {
    const html = await renderToString(renderTitle());
    console.log(html);
    dataURL.value = stringToBase64(html);
    console.log(dataURL);
  }
  const renderTitle = () => {
    return (
      <svg viewBox={viewBox.value} xmlns="http://www.w3.org/2000/svg">
        {Object.prototype.toString.call(props.color) === '[object Object]' && (
          <defs>
            <linearGradient id="progressGradient" x1="0%" y1="0%" x2="100%" y2="0%">
              {Object.keys(props.color).map((key) => (
                <stop offset={`${key}%`} stop-color={props.color[key]} key={key}></stop>
              ))}
            </linearGradient>
          </defs>
        )}
        <path
          d={memoryTrackPath.value}
          style={{
            strokeWidth: props.strokeWidth,
            stroke: props.layerColor,
            fill: 'none',
            strokeLinecap: props.strokeLinecap,
          }}
        ></path>
        <path
          d={progressPath.value}
          style={{
            strokeWidth: props.strokeWidth,
            stroke: strokeBgColor.value,
            fill: props.fill,
            strokeLinecap: props.strokeLinecap,
            strokeDashoffset: strokeDashoffset.value,
            strokeDasharray: strokeDasharray.value,
          }}
        >
          <animate
            attributeName="stroke-dashoffset"
            dur={`${props.defaultDurTime}ms`}
            fill="freeze"
            from={strokeDasharray.value}
            to={strokeDashoffset.value}
          ></animate>
        </path>
      </svg>
    );
  };
  watchEffect(() => {
    const radius = props.size / 2;
    viewBox.value = `0 0 ${props.size + props.strokeWidth} ${0.5 * props.size + props.strokeWidth}`;
    const progressAngle = (props.progress / 100) * Math.PI; // 根据进度计算弧度
    const startX = props.strokeWidth / 2;
    const startY = radius + props.strokeWidth / 2;
    const endY = -Math.sin(progressAngle) * radius;
    const endX = radius - Math.cos(progressAngle) * radius;
    const largeArcFlag = progressAngle <= Math.PI ? '0' : '1';
    memoryTrackPath.value = `M ${startX} ${startY} a ${radius} ${radius} 0 1 1 ${props.size} 0`;
    progressPath.value = `M ${startX} ${startY} a ${radius} ${radius} 0 ${largeArcFlag} 1 ${endX} ${endY}`;
    strokeDasharray.value = radius * Math.PI;
    strokeDashoffset.value = strokeDasharray.value - (props.progress / 100) * strokeDasharray.value;
    if (Object.prototype.toString.call(props.color) === '[object Object]') {
      strokeBgColor.value = `url(#progressGradient)`;
    } else {
      strokeBgColor.value = props.color;
    }
    handleSave();
  });
</script>

props

参数 说明 类型 默认值
defaultDurTime 动画速度(也可以任务是进度条完结时间) number 1000
progress 进度百分比(0-100) number 50
color 进度条颜色,传入对象格式可以定义渐变色 object 或 string { ‘0’: ‘#80EDFF’, ‘100’: ‘#4A7DFF’ }
size 直径 number 200
layerColor 轨道颜色 string #e0e0e0
fill 填充颜色 string ‘none’
strokeWidth 进度条宽度 number 20
strokeLinecap 进度条端点(butt,round,square) string ’round’

矩形进度条

<script lang="tsx" setup>
  import { stringToBase64 } from './utils/index';
  import { ref, watchEffect } from 'vue';
  import { renderToString } from '@vue/server-renderer';
  interface IProps {
    defaultDurTime?: number; //进度条完结时间
    progress?: number; //当前进度0-100
    color?: object | string; //进度条颜色,传入对象格式可以定义渐变色
    width?: number; //矩形宽度
    height?: number; //矩形高度
    radian?: number; //矩形圆角弧度
    layerColor?: string; //轨道颜色
    fill?: string; //填充颜色
    strokeWidth?: number; //线条宽度
    strokeLinecap?: string; //进度条端点(butt,round,square)
  }
  const props = withDefaults(defineProps<IProps>(), {
    defaultDurTime: 1000, //进度条完结时间
    progress: 50,
    color: () => {
      return { '0': '#80EDFF', '100': '#4A7DFF' };
    },
    width: 140,
    height: 120,
    radian: 20,
    layerColor: '#e0e0e0',
    fill: 'none',
    strokeWidth: 10,
    strokeLinecap: 'round',
  });
  const progressOffset = ref(0);
  const allLen = ref(0);
  const strokeBgColor = ref<string | object>('#666666');
  let dataURL = ref('');
  async function handleSave() {
    const html = await renderToString(renderTitle());
    dataURL.value = stringToBase64(html);
    console.log(dataURL);
  }
  const renderTitle = () => 
    return (
      <svg
        width={props.width + props.strokeWidth}
        height={props.height + props.strokeWidth}
        xmlns="http://www.w3.org/2000/svg"
      >
        {Object.prototype.toString.call(props.color) === '[object Object]' && (
          <defs>
            <linearGradient id="progressGradient" x1="0%" y1="0%" x2="100%" y2="0%">
              {Object.keys(props.color).map((key) => (
                <stop offset={`${key}%`} stop-color={props.color[key]} key={key}></stop>
              ))}
            </linearGradient>
          </defs>
        )}
        <rect
          x={0.5 * props.strokeWidth}
          y={0.5 * props.strokeWidth}
          rx={props.radian}
          ry={props.radian}
          width={props.width}
          height={props.height}
          style={{ strokeWidth: props.strokeWidth, fill: props.fill, stroke: props.layerColor }}
        />
        {props.progress !== 0 && (
          <rect
            x={0.5 * props.strokeWidth}
            y={0.5 * props.strokeWidth}
            rx={props.radian}
            ry={props.radian}
            width={props.width}
            height={props.height}
            style={{
              strokeDashoffset: progressOffset.value,
              strokeDasharray: allLen.value,
              strokeWidth: props.strokeWidth,
              fill: props.fill,
              stroke: strokeBgColor.value,
              strokeLinecap: props.strokeLinecap,
            }}
          >
            <animate
              attributeName="stroke-dashoffset"
              dur={`${props.defaultDurTime}ms`}
              fill="freeze"
              from={allLen.value}
              to={progressOffset.value}
            ></animate>
          </rect>
        )}
      </svg>
    );
  };
  watchEffect(() => {
    allLen.value = (props.width + props.height) * 2 - props.radian * 8 + 2 * props.radian * Math.PI;
    if (props.strokeLinecap !== 'butt' && props.progress !== 100 && props.progress > 95) {
      progressOffset.value 
        allLen.value - (props.progress / 100) * allLen.value + props.strokeWidth;
    } else {
      progressOffset.value = allLen.value - (props.progress / 100) * allLen.value;
    }
    if (Object.prototype.toString.call(props.color) === '[object Object]') {
      strokeBgColor.value = `url(#progressGradient)`;
    } else {
      strokeBgColor.value = props.color;
    }
    handleSave();
  });
</script>

props

参数 说明 类型 默认值
defaultDurTime 动画速度(也可以任务是进度条完结时间) number 1000
progress 进度百分比(0-100) number 50
color 进度条颜色,传入对象格式可以定义渐变色 object 或 string { ‘0’: ‘#80EDFF’, ‘100’: ‘#4A7DFF’ }
width 宽度 number 140
height 高度 number 120
radian 圆角弧度(为0时是直角) number 20
layerColor 轨道颜色 string #e0e0e0
fill 填充颜色 string ‘none’
strokeWidth 进度条宽度 number 10
strokeLinecap 进度条端点(butt,round,square) string ’round’

最后希望对大家有所帮助哦!

原文链接:https://juejin.cn/post/7347905080508678171 作者:柠檬果茶

(0)
上一篇 2024年3月19日 下午4:27
下一篇 2024年3月19日 下午4:37

相关推荐

发表回复

登录后才能评论