#过年不停更#HarmonyOS 基于JS封装渐变圆环进度条组件-鸿蒙开发者社区-51CTO.COM

#过年不停更#HarmonyOS 基于JS封装渐变圆环进度条组件 原创 精华

中软小助手
发布于 2022-2-11 14:29
浏览
6收藏

春节不停更,此文正在参加「星光计划-春节更帖活动」
作者:杨尚晓

前言

圆环进度条组件在开发过程中是经常会用到的组件,比如在loading加载、升级过程,下载过程等等都需要用到。本文是基于HarmonyOS JSPAI开发,使用canvas画布封装的组件。方便直接引入上手使用。

效果展示

#过年不停更#HarmonyOS 基于JS封装渐变圆环进度条组件-鸿蒙开发者社区

属性

属性名 类型 默认值 作用
cWidth Number 200 外环宽度
cHeight Number 200 外环高度
staticScale Number 4 缩放级,值越大,越清晰,渲染压力越大
cRate Number 0 当前进度
lineWidth Number 20 圆环宽度
cColor String #86C1FF,#2B5BF9 进度颜色(必须是hex),当只有一个颜色值时为纯色进度,最多只支持两个颜色,使用半角逗号分开
bgColor String #e9ebed 圆环背景颜色
text String - 显示的文本
textSize Number - 显示的文本字体大小

对应组件传入的prop参数

props: {
    // 圆环宽度
    cWidth: {
      type: Number,
      default: 200,
    },
    // 圆环高度
    cHeight: {
      type: Number,
      default: 200,
    },
    // 缩放级,值越大,越清晰,渲染压力越大
    staticScale:{
      type: Number,
      default: 4,
    },
    // 显示文本
    text: {
      type: String,
      default: '',
    },
    // 圆环宽度
    lineWidth: {
      type: Number,
      default: 20
    },
    // 当前进度
    cRate: {
      type: Number,
      default: 0
    },
    // 进度颜色,当只有一个颜色值时为纯色进度,最多只支持两个颜色,使用半角逗号分开
    cColor: {
      type: String,
      default: '#86C1FF,#2B5BF9'
    },
    // 背景色
    bgColor:{
      type: String,
      default: '#e9ebed'
    },
   // 文字大小 
    textSize: {
      type: Number,
      default: 32
    }
  },

调用组件

<element name="khCircle" src="../../../share/component/khCircle/khCircle"></element>
<div class="container">
    <khCircle
      c-width="{{cWidth}}"
      c-weight="{{cHeight}}"
      line-width="{{lineWidth}}"
      c-rate="{{cRate}}"
      static-scale="{{staticScale}}"
      c-color="{{cColor}}"
      bg-color="{{bgColor}}"
      text="{{cRate + '%'}}"
      text-size="40"
    ></khCircle>
</div>

实现原理

我们知道,在canvas中可以实现线性渐变和径向渐变,但是这些渐变都不够美观并且无法实现根据圆环方向线性渐变。
本文通过将圆环弧切片成若干等分绘制指定的颜色来实现的。

下面拆分各个步骤

  • 创建canvas对象并且设置canvas大小
  • 使用canvas的arc() API绘制一个圆充当圆环背景,给lineWidth设置圆环宽度的值
  • 计算两个颜色之间的渐变颜色值。确定需要切片的精度,来确定渐变颜色值之间的差级。
  • 在原来的背景圆环上,再绘制一个圆弧,圆弧是根据开始位置和结束位置,然后绘制一段圆弧之间的所有切片

1. 初始化canvas

这里需要有个关键点,getContext(contextType, contextAttributes)方法可以传入两个参数

  • contextType 上下文类型 可选 2d、webgl
  • contextAttributes 上下文属性

注意:在HarmonyOS 的JSPAI官网文档没有提到这两个属性,但是案例上有看到const ctx = el.getContext('2d', { antialias: true });这样一句话。这里似乎跟webAPI的有些区别。
antialias属性是设置是否开启抗锯齿,但是在webAPI中,只有在上下文类型为webgl的时候才有这个属性。

先写个canvas标签

<canvas id="canvas" class="canvas" ref="canvas"></canvas>

再初始化canvas对象

initCanvas(){
    let canvas = this.$refs.canvas;
    this.ctx = canvas.getContext('2d');
    canvas.width = this.cWidth * this.staticScale;
    canvas.height = this.cHeight * this.staticScale;
  },

2. 绘制背景圆环

通过canvas的arc(x,y,r,sAngle,eAngle,counterclockwise)方法绘制一个圆

参数值

参数 描述
x 圆的中心的 x 坐标。
y 圆的中心的 y 坐标。
r 圆的半径。
sAngle 起始角,以弧度计(弧的圆形的三点钟位置是 0 度)。
eAngle 结束角,以弧度计。
counterclockwise 可选。规定应该逆时针还是顺时针绘图。False = 顺时针,true = 逆时针。

在canvas绘制圆的其实位置是在x+r的位置开始,完整的圆弧为2PI。因为我们要在最顶端开始绘制,所以我们的开始位置是1.5PI,而我们的结束位置就不是2PI了,而是1.5PI + 2 * PI,这个才是绘制的完整的一个圆弧。最后

#过年不停更#HarmonyOS 基于JS封装渐变圆环进度条组件-鸿蒙开发者社区

this.ctx.beginPath();
this.ctx.lineCap = "round"; //向线条的每个末端添加圆形线帽
this.ctx.lineWidth = 20;  //绘制圆的边框
this.ctx.strokeStyle = '#ff9800'; //绘制边框的颜色
this.ctx.arc(100,100,100,Math.PI * 1.5,(Math.PI * 1.5 * Math.PI * 2));  
this.ctx.stroke();

上面代码我们这里绘制了一个圆心坐标在(100,100),半径为100,边框为20,边框颜色为 #ff9800的圆环

#过年不停更#HarmonyOS 基于JS封装渐变圆环进度条组件-鸿蒙开发者社区

3. 实现圆弧切片绘制方法

3.1. 获取两颜色之间的渐变值组

  • 首先我们需要把获取到的hex色值转换成rgb三原色值,
  • 然后通过计算开始颜色和结束颜色总差值来获取每一步的颜色值,
  • 最后再转换成hex设置

将hex色值转换成rgb三原色值方法

// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)
  hexToRgb(sColor){
    var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
    sColor = sColor.toLowerCase();
    if(sColor && reg.test(sColor)){
      if(sColor.length === 4){
        var sColorNew = "#";
        for(var i=1; i<4; i+=1){
          sColorNew += sColor.slice(i,i+1).concat(sColor.slice(i,i+1));
        }
        sColor = sColorNew;
      }
      //处理六位的颜色值
      var sColorChange = [];
      for(let i=1; i<7; i+=2){
        sColorChange.push(parseInt("0x"+sColor.slice(i,i+2)));
      }
      return sColorChange;
    }else{
      return sColor;
    }
  },

通过计算开始颜色和结束颜色总差值来获取每一步的颜色值

/**
   * @description: 封装颜色渐变之间值
   * @param {String} startColor 开始颜色hex
   * @param {Number} endColor 结束颜色hex
   * @param {Number} step 渐变精度
   * @return {Array}
   */
  gradientColor(startColor,endColor,step){
    let startRGB = this.hexToRgb(startColor);//转换为rgb数组模式
    let endRGB = this.hexToRgb(endColor);
    
    let sR = (endRGB[0]-startRGB[0])/step;//总差值
    let sG = (endRGB[1]-startRGB[1])/step;
    let sB = (endRGB[2]-startRGB[2])/step;
    var colorArr = [];
    for(var i=0;i<step;i++){
      //计算每一步的hex值
      var hex = this.rgbToHex('rgb('+parseInt((sR*i+startRGB[0]))+','+parseInt((sG*i+startRGB[1]))+','+parseInt((sB*i+startRGB[2]))+')');
      colorArr.push(hex);
    }
    return colorArr;
  },

将rgb三原色值转换成hex色值方法

// 将rgb表示方式转换为hex表示方式
  rgbToHex(rgb){
    var _this = rgb;
    var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
    if(/^(rgb|RGB)/.test(_this)){
      var aColor = _this.replace(/(?:(|)|rgb|RGB)*/g,"").split(",");
      var strHex = "#";
      for(var i=0; i<aColor.length; i++){
        var hex = Number(aColor[i]).toString(16);
        hex = hex<10 ? 0+''+hex :hex;// 保证每个rgb的值为2位
        if(hex === "0"){
          hex += hex;
        }
        strHex += hex;
      }
      if(strHex.length !== 7){
        strHex = _this;
      }
      return strHex;
    }else if(reg.test(_this)){
      var aNum = _this.replace(/#/,"").split("");
      if(aNum.length === 6){
        return _this;
      }else if(aNum.length === 3){
        var numHex = "#";
        for(let i=0; i<aNum.length; i+=1){
          numHex += (aNum[i]+aNum[i]);
        }
        return numHex;
      }
    }else{
      return _this;
    }
  }

3.2. 开始绘制一段圆弧之前的渐变值

这里有个渐变精度unit,这个值确定了我们渐变之间的缓和度,比如一段100像素的长方形,我们每一个像素绘制一个颜色,和每10个像素绘制一个颜色,效果是不一样的。

#过年不停更#HarmonyOS 基于JS封装渐变圆环进度条组件-鸿蒙开发者社区

因为我们需要绘制的整个圆弧是2 * PI ≈ 6.28318;那么就是说在开始颜色到结束颜色之间需要绘制长达6.28318的长度,这里设置unit = 0.01;就是每0.01个圆弧里绘制一个颜色。这样大约需要绘制6.28318 / 0.01 ≈ 628个渐变颜色就可以了,把两个颜色之间色值拆分成628个色值组,会让渐变更缓和,当然还可以调整更多,但是这样也会导致渲染压力正大,耗时,耗性能,而且在手机上其实也看不出更细的区别来。

每次绘制的时候,需要记录当前上一次绘制的结束位置,作为下一次绘制的开始位置,这样子就不需要每次绘制都从头开始,也可以拼接起来。

  /**
   * @description: 圆弧切片绘制
   * @param {Number} percent 圆环展示的进度(0~100
   * @param {String} startColor 开始颜色
   * @param {String} endColor 结束颜色
  */
  paint(percent,startColor,endColor){
    percent = percent / 100;
    !endColor ? endColor = startColor : '';

    // 渐变精度,值越小,渐变越缓和
    const unit = 0.01;

    let division = parseInt((Math.PI * 2)/unit);
    // 生成渐变色数组
    let gradient = this.gradientColor(startColor,endColor,division);
    let arr = gradient.slice(this.num, parseInt(percent*gradient.length));
    this.num = parseInt(percent*gradient.length);
    for(let i=0; i < arr.length; i++){
      this.endAngle = this.startAngle+unit;
      this.drawCircle(arr[i],this.startAngle,this.endAngle);
      this.startAngle+=unit;
    }
  },

最后附上hml、css代码

hml文件

<div class="kh-circle">
  <div class="circle-box" style="width: {{cWidth}}px; height: {{cHeight}}px;">
    <canvas id="canvas" class="canvas" ref="canvas"></canvas>
    <text class="text" show="{{text ? true : false}}" style="font-size: {{textSize}}px;">
      {{text}}
    </text>
  </div>
</div>

css文件

.circle-box{
  position: relative;
}
.canvas{
  width: 100%;
  height: 100%;
}
.text{
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%,-50%);
  z-index: 10;
  text-align: center;
}

最后再来看一下效果:

#过年不停更#HarmonyOS 基于JS封装渐变圆环进度条组件-鸿蒙开发者社区

总结

在开发过程中也遇到了一些坑,比如SDK版本问题:在SDK version7的时候渲染没有问题,但是IDE预览器经常崩溃掉。后来改回SDK6就好了。
以往在开发vue的时候,会直接把canvas对象赋值到data函数里,方便其他方法直接调用,但是在这里却发现不行。最后发现这里data并非是一个函数,而是一个对象,这里还没有深入研究。

在使用组件的时候未必一定需要使用text属性,可以使用slot插槽实现多样文本显示。

源码

基于JS封装渐变圆环进度条组件

更多原创内容请关注:中软国际 HarmonyOS 技术团队

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
已于2022-2-11 14:30:00修改
11
收藏 6
回复
举报
4条回复
按时间正序
/
按时间倒序
大梦初醒丶
大梦初醒丶

这方法真是太厉害了吧,高实在是高。

回复
2022-2-11 16:06:26
YanGo_LeBron
YanGo_LeBron

可以,很nice

1
回复
2022-2-11 17:29:09
John伟杰
John伟杰

太实用了

 

1
回复
2022-2-11 17:52:29
FrancisJ
FrancisJ

尚晓老师太强了

回复
2022-2-13 12:14:50
回复
    相关推荐