Taro封装兼容H5及微信小程序的圆形进度条组件
一、背景
由于各种原因,九月异动换了一个部门。技术栈也从
vue、微前端
换成了react,taro
;从做tob系统
换成了小程序、h5多端
。算算,现在在新部门已经待了两个月,整体感觉换部门还算是正确的选择,接触到一些新技术及场景,部门整体氛围也不错...更多就不再这里赘述了,打算年终总结的时候好好写一写。
好了,以下才是本文真正的背景:
笔者现在主要做c端业务,技术栈taro+ts
,流量主要在小程序+H5
。在业务高速发展中,我们意识到,沉淀一个组件库的必要性。熟悉Taro的朋友,应该知道Taro官方提供了Taro-ui,用过的朋友肯定也知道,好用的组件是在是没几个...很多都得自己封装,业内除了taro-ui也无其他适配多端的组件库可用,所以我们决定自己搭建一个组件库。感兴趣的朋友可以关注本专栏,后续会持续更新文章。
本文讲解如何用taro封装一个圆形进度条组件,重难点在技术实现选型以及如何用svg适配H5及微信小程序
二、效果图
三、参数设计
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
percent | 百分比 | number | 0 |
size | 圆环直径,单位为px | number | 100 |
color | 进度条颜色 | string | #DC8D20 |
layerColor | 轨道背景颜色 | string | #EFEFEF |
text | 文字 | string | - |
strokeWidth | 进度条宽度,单位为px | number | 4 |
四、技术实现
常见实现方案:
canvas
svg
(1)canvas
我首先尝试用canvas实现,taro提供了canvas API,画出来之后,发现小程序是没有问题的,但是H5控制台直接报错 仔细翻看文档后,发现canvas API果然不支持H5
(2)svg
于是我想到了svg,简单的熟悉了下svg之后,我尝试拿svg画图,代码实现如下
用circle
标签画圆,首先解释下标签的基本属性
svg
前两个参数是对svg元素做位移使用,通常设置为0;
后两个参数表示svg元素可容纳的大小,通常设置为宽高
height:svg元素的高
width:svg元素的宽
viewBox:
circle
cx:圆心的x坐标位置
cy:圆心的y坐标位置
r: 表示半径
fill: 圆的填充色
stroke-width: 边框宽度
stroke:边框填充色
stroke-dasharray:控制画笔的虚实,通过实线和虚线的来控制画
stroke-dashoffset:相对于绘制的起点偏移的量
stroke-linecap:在开放子路径被设置描边的情况下,用于开放自路径两端的形状
可以看到有两个circle标签,一个是背景色,一个是进度条色,如下图
height
,width
均用props-size(圆环直径)
;那么圆心的x,y坐标
就均是size / 2
,这很好理解半径r
这里需要注意,等于(size - strokeWidth * 2) / 2
,也就是(直径-边框宽度*2) / 2
,而非直径 / 2
圆的填充色
fill
均是none,因为是空心圆strokeWidth
边框宽度直接用props-strokeWidth
即可stroke
,边框填充色,背景圆用props-layerColor
,上面的圆用props-color
以上属性画好了两个圆,一个是背景色的圆,一个是进度条圆,背景圆整个圆周长都填充,进度条圆,需要根据百分比填充,如何实现呢,利用strokeDasharray
、strokeDashoffset
进行偏移
strokeDasharray
设为圆周长2*π*r
,也就是2 * Math.PI * r
strokeDashoffset
偏移圆周长 * (1 - percent / 100)
,比如percent=25,偏移圆周长*0.75的长度
props具体含义在二、参数设计中已说明
import { View } from '@tarojs/components' render() { const { percent, size, strokeWidth, layerColor, color, text } = this.props const r = (size - strokeWidth * 2) / 2 const circumference = 2 * Math.PI * r return ( <View> <svg height={size} width={size} viewBox={`0 0 ${size} ${size}`}> <circle id="circleBg" cx={size / 2} cy={size / 2} r={r} fill="none" strokeWidth={strokeWidth} stroke={layerColor} /> <circle id="circle" style={{ transform: 'rotate(-90deg)', transformOrigin: ' 50% 50%' }} cx={size / 2} cy={size / 2} r={r} fill="none" strokeWidth={strokeWidth} stroke={color} strokeDasharray={circumference} strokeDashoffset={circumference * (1 - percent / 100)} strokeLinecap='round' /> </svg> </View> ) } 复制代码
画完之后,尴尬的事情来了,H5是没问题的,而微信小程序出不来,微信小程序控制台报错如下
于是,我又仔细的去翻看文档,查到微信小程序目前不支持 SVG 标签,仅仅支持加载 SVG 之后 作为 background-image 进行展示,如 background-image: url("data:image/svg+xml.......)
,或者 base64 后作为 background-image 的 url,可见社区官方文章
于是尝试用background-image: url("")
的方式尝试兼容小程序
静态的svg可以直接在自动转换的网站上转换成base64,由于这里的svg是有变量的,所以我们手动转换,查到<
替换成%3C
,>
替换成%3E
;但是怎么也出不来,最后在这个网站与自动替换静态svg的对比,发现是颜色值的#
也需要相应的替换成%23
,替换了就可以了!!!代码如下
private transColor = (color) => { return color.replace('#', '%23') } render() { const style: React.CSSProperties = { backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='${size}' width='${size}' viewBox='0 0 ${size} ${size}'%3E%3Ccircle id='circleBg' cx='${size / 2}' cy='${size / 2}' r='${r}' fill='none' stroke-width='${strokeWidth}' stroke='${this.transColor(layerColor)}'%3E%3C/circle%3E%3Ccircle id='circle' cx='${size / 2}' cy='${size / 2}' r='${r}' fill='none' stroke-width='${strokeWidth}' stroke='${this.transColor(color)}' stroke-dasharray='${circumference}' stroke-dashoffset='${circumference * (1 - percent / 100)}' stroke-linecap='round' style='transform: rotate(-90deg); transform-origin: 50%25 50%25;'%3E%3C/circle%3E%3C/svg%3E")`, width: size + 'px', height: size + 'px' } return( <view style={style} > </view> ) } 复制代码
到此也算是封装完成了,用svg的方式兼容了H5及小程序
五、组件基本使用及完整代码
(1)基本使用
基础用法
<ProgressCircle percent={25} text="25%" /> 复制代码
自定义颜色
<ProgressCircle percent={25} color="#FF2929" text="自定义颜色" /> 复制代码
自定义圆环直径
<ProgressCircle percent={25} size={200} text="自定义圆环直径" /> 复制代码
自定义进度条宽度
<ProgressCircle percent={25} size={200} strokeWidth={10} text="自定义进度条宽度" /> 复制代码
(2)完整代码
// import React from 'react' import { View, Text } from '@tarojs/components' import { ProgressCircleProps } from '../../../types/progress-circle' import Taro from '@tarojs/taro' export default class ProgressCircle extends React.Component<ProgressCircleProps> { public static defaultProps: ProgressCircleProps private transColor = (color) => { return color.replace('#', '%23') } public render(): JSX.Element | null { const { percent, size, strokeWidth, layerColor, color, text } = this.props const r = (size - strokeWidth * 2) / 2 const circumference = 2 * Math.PI * r const isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP const style: React.CSSProperties = { // backgroundImage这一句在掘金这个编写器会导致下面的代码高亮异常,所以注释掉,其实是有用的哈 // backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='${size}' width='${size}' viewBox='0 0 ${size} ${size}'%3E%3Ccircle id='circleBg' cx='${size / 2}' cy='${size / 2}' r='${r}' fill='none' stroke-width='${strokeWidth}' stroke='${this.transColor(layerColor)}'%3E%3C/circle%3E%3Ccircle id='circle' cx='${size / 2}' cy='${size / 2}' r='${r}' fill='none' stroke-width='${strokeWidth}' stroke='${this.transColor(color)}' stroke-dasharray='${circumference}' stroke-dashoffset='${circumference * (1 - percent / 100)}' stroke-linecap='round' style='transform: rotate(-90deg); transform-origin: 50%25 50%25;'%3E%3C/circle%3E%3C/svg%3E")`, width: size + 'px', height: size + 'px' } return ( <View className="circle" style={`width:${size}px; height:${size}px`}> {!isWeapp && <svg height={size} width={size} viewBox={`0 0 ${size} ${size}`}> <circle id="circleBg" cx={size / 2} cy={size / 2} r={r} fill="none" strokeWidth={strokeWidth} stroke={layerColor} /> <circle id="circle" style={{ transform: 'rotate(-90deg)', transformOrigin: ' 50% 50%' }} cx={size / 2} cy={size / 2} r={r} fill="none" strokeWidth={strokeWidth} stroke={color} strokeDasharray={circumference} strokeDashoffset={circumference * (1 - percent / 100)} strokeLinecap='round' /> </svg>} {isWeapp && <View> <view style={style} > </view> </View>} <Text className="circle-text">{text}</Text> </View> ) } } ProgressCircle.defaultProps = { percent: 0, size: 100, strokeWidth: 4, layerColor: '#EFEFEF', color: '#DC8D20' } 复制代码
export interface ProgressCircleProps { /** * 百分比 * @default 0 */ percent: number /** * 圆环直径,单位为px * @default 100 */ size: number /** * 进度条颜色 * @default #DC8D20 */ color: string /** * 轨道背景颜色 * @default #EFEFEF */ layerColor: string /** * 文字 */ text?: string /** * 进度条宽度,单位为px * @default 4 */ strokeWidth: number } 复制代码
.circle { position: relative; &-text { position: absolute; width: 100%; top: 50%; left: 50%; text-align: center; transform: translate(-50%, -50%); } }
作者:jjjona0215
链接:https://juejin.cn/post/7027739818666754079