原生微信小程序圆形倒计时svg组件

#写在最前#
微信小程序不支持svg标签,需要把svg转成base64用background属性展示,缺点是执行倒计的时候,圆形轨道无法做动画,只能很生硬的展示每一帧;优点时非常清晰,不会有锯齿。


#效果截图#


#组件封装#

将svg封装成子组件,代码结构如下:

具体代码:
 1、svg-timer.js 


Component({

  /**
   * 页面的初始数据
   */
  data: {
    fadeAnimation: false, // 渐变动画过程
    svgTimer: null,
    PI: 3.1415926,
    svgWidth: 0, // 父容器宽度
    countdown: 30, //外部传入倒计时(s)
    showSeconds: 0, // 回显(s)
    warning: false, // 是否警告
    warnSecond: 5, // 警告时间,低于该值时变色
    strokeWidth: 4, // 轨道宽度
    strokeDasharray: 0, // 圆周长
    strokeDashoffset: 0, // 渐变偏移量
    svgBase64: ''
  },
  methods: {
    /**
     * 
     * @param {svg标签, 不能有注释} str 
     * @returns 
     */
    base64_encode: function (str) {
      var c1, c2, c3;
      var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
      var i = 0, len = str.length, string = '';

      while (i < len) {
        c1 = str.charCodeAt(i++) & 0xff;
        if (i == len) {
          string += base64EncodeChars.charAt(c1 >> 2);
          string += base64EncodeChars.charAt((c1 & 0x3) << 4);
          string += "==";
          break;
        }
        c2 = str.charCodeAt(i++);
        if (i == len) {
          string += base64EncodeChars.charAt(c1 >> 2);
          string += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
          string += base64EncodeChars.charAt((c2 & 0xF) << 2);
          string += "=";
          break;
        }
        c3 = str.charCodeAt(i++);
        string += base64EncodeChars.charAt(c1 >> 2);
        string += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
        string += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
        string += base64EncodeChars.charAt(c3 & 0x3F)
      }
      return string
    },
    confirm: function (strokeWidth, containerWidth, strokeDasharray, strokeDashoffset, warning) {
      let text = `
      <svg class="box" xmlns="http://www.w3.org/2000/svg" version="1.1" width="${containerWidth}" height="${containerWidth}" viewBox="0 0 ${containerWidth} ${containerWidth}">
        <defs>
          <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
            <stop offset="0%" style="stop-color:rgb(77, 230, 255);stop-opacity:1" />
            <stop offset="100%" style="stop-color:rgb(55, 145, 255);stop-opacity:1" />
          </linearGradient>
        </defs>
        <defs>
          <linearGradient id="grad2" x1="0%" y1="0%" x2="100%" y2="0%">
            <stop offset="0%" style="stop-color:rgb(255, 159, 81);stop-opacity:1" />
            <stop offset="100%" style="stop-color:rgb(255, 252, 81);stop-opacity:1" />
          </linearGradient>
        </defs>
        <defs>
          <linearGradient id="grad3" x1="0%" y1="0%" x2="100%" y2="0%">
            <stop offset="0%" style="stop-color:rgb(65, 86, 181);stop-opacity:1" />
            <stop offset="100%" style="stop-color:rgb(65, 86, 181);stop-opacity:1" />
          </linearGradient>
        </defs>
        <defs>
          <linearGradient id="grad4" x1="0%" y1="0%" x2="100%" y2="0%">
            <stop offset="0%" style="stop-color:rgb(144, 82, 93);stop-opacity:1" />
            <stop offset="100%" style="stop-color:rgb(144, 82, 93);stop-opacity:1" />
          </linearGradient>
        </defs>
        <circle class="progress2" cx="${containerWidth / 2}" cy="${containerWidth / 2}" r="${containerWidth / 2 - strokeWidth}" fill="rgba(34, 1, 106, 1)" stroke="url(${warning ? '#grad4' : '#grad3'})" stroke-width="${strokeWidth}"></circle>
        <circle class="progress" cx="${containerWidth / 2}" cy="${containerWidth / 2}" r="${containerWidth / 2 - strokeWidth}" fill="transparent" stroke="url(${warning ? '#grad2' : '#grad1'})" stroke-dasharray="${strokeDasharray}" stroke-dashoffset="${strokeDashoffset}" stroke-width="${strokeWidth}" stroke-linecap="round"></circle>
      </svg>`;
      let svgBase64 = this.base64_encode(text);
      const toSvgXmlHeader = 'data:image/svg+xml;base64,';

      let b = toSvgXmlHeader + svgBase64;
      // console.log('base64', b);
      this.setData({
        svgBase64: b
      })
    },
    rePlay: function(countdown, warnSecond) {
      clearInterval(this.data.svgTimer) // 清除上次计时器
      this.setData({
        fadeAnimation: false
      })

      countdown && this.setData({
        countdown: countdown
      })
      warnSecond && this.setData({
        warnSecond: warnSecond
      })
    
      this.setData({
        warning: false
      })
      this.setData({
        svgBase64: ''
      })
      console.log('重画')
      
      this.play()
    },
    play: function (countdown, warnSecond) {
      // debugger
      this.setData({
        fadeAnimation: true
      })
      countdown && this.setData({
        countdown: countdown
      })
      warnSecond && this.setData({
        warnSecond: warnSecond
      })

      if (this.data.countdown < this.data.warnSecond) {
        console.error('警告时间不能小于传入倒计时');
        return;
      }

      wx.createSelectorQuery().in(this).select('.svg-test')
        .fields({
          node: true,
          size: true,
        })
        .exec((res) => {
          // debugger
          this.setData({
            svgWidth: res[0].width
          })
          this.setData({
            strokeWidth: Math.floor(res[0].width / 20)
          })
          
          console.log('svg父容器宽度', res[0].width);
          console.log('svg轨道宽度', this.data.strokeWidth);

          /**
           * 计算圆周长
           * this.data.svgWidth - this.data.strokeWidth * 2是为了计算内部填充圆的直径,否则圆会溢出
           */
          const strokeDasharray = this.data.PI * (this.data.svgWidth - this.data.strokeWidth * 2);
          this.setData({
            strokeDasharray: strokeDasharray
          })
          this.setData({
            showSeconds: this.data.countdown
          })
          // 第一帧先于定时器绘制
          this.confirm(this.data.strokeWidth, this.data.svgWidth, this.data.strokeDasharray, 0, false);
        })

      // 定时器动画
      let loadingIndex = 0
      this.data.svgTimer = setInterval(() => {
        this.setData({
          strokeDashoffset: -((loadingIndex + 1) * this.data.strokeDasharray / this.data.countdown)
        })

        loadingIndex += 1
        this.setData({
          showSeconds: this.data.countdown - loadingIndex
        })

        this.setData({
          warning: this.data.showSeconds <= this.data.warnSecond
        })

        this.confirm(this.data.strokeWidth, this.data.svgWidth, this.data.strokeDasharray, this.data.strokeDashoffset, this.data.showSeconds <= this.data.warnSecond);

        // 进度列表的值取完就停止定时器
        if (loadingIndex === this.data.countdown) {
          this.data.svgTimer && clearInterval(this.data.svgTimer)
        }
      }, 1000);
    },
  }
})

上面代码提示:
svg标签字符串不能有其他注释,否则转换的产物会有问题。wx.createSelectorQuery().in(this).select('.svg-test')加in(this)是为了区别与父组件的关系,不加则获取到的node结果为null。

 2、svg-timer.json

{
    "component": true,
    "usingComponents": {}
}

3、svg-timer.wxml

<view class="svg-container flex1 flex-center container ft24 {{fadeAnimation ? 'fade-animation' : 'opacity0'}}">

    <view wx:key="{{seconds}}" class="svg-test" style="background:url('{{ svgBase64 }}') no-repeat center"></view>
    <view class="text">
      剩余时间
    </view>
    <view class="{{warning ? 'second-orange' : 'second-blue'}}">{{showSeconds}}</view>
    <!-- <view bindtap="play">点击生成</view> -->
</view>

上面代码提示:

svgBase64是将纯净的svg标签转成base64字符串的产物,如果转换后格式正常,可直接在浏览器地址栏展示该图片。

4、svg-timer.wxss

.flex-center {
    display: flex;
    align-items: center;
    justify-content: center;
}
.flex1 {
    display: flex;
    flex-direction: column;
}

.svg-container {
    width: 100%;
    height: 100%;
    position: relative;
}
.svg-test {
    width: 193rpx;
    height: 193rpx;
    position: absolute;
    z-index: -1;
    transform: rotate(-90deg);
}

.text {
    font-size: 24rpx;
    font-weight: normal;
    color: rgba(255,255,255,0.85);
    line-height: 43rpx;
}
.second-blue {
    font-size: 58rpx;
    font-weight: normal;
    color: #4AD9FF;
    line-height: 68rpx;
    /* animation: fadenum 0.4s 1; */
}
.second-orange {
    font-size: 58rpx;
    font-weight: normal; 
    color: #FFCB51;
    line-height: 68rpx;
    /* animation: fadenum 0.4s 1; */
}
.fade-animation {
    animation: fadenum 1s linear;
}
.opacity0 {
    opacity: 0;
}
@keyframes fadenum{
    0% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}


#父组件声明和调用代码#
1、父组件.wxml

<view>
     <svgTimer id="svg-timer" style="width: 100%;height: 100%;"></svgTimer>
</view>

2、父组件.js

// 获得svg计时器组件
this.svgTimer= this.selectComponent("#svg-timer");
// 绘制svg
this.svgTimer.play(30, 5);

// 其他地方需要重复绘制svg时
this.svgTimer.rePlay(持续时间, 警告时间);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值