微信小程序-canvas 2d带动画的半圆形刻度进度条

效果图

在这里插入图片描述



实现思路

代码过多,但不复杂

  1. 采用Canvas 2D,文档请搜索h5 canvas 开发文档;
  2. 定义4个canvas,通过相对定位重叠在一起:
  <!-- 绘制背景半圆刻度 -->
  <canvas type="2d" id="bgLine" style="position:absolute;"></canvas>
  
  <!-- 绘制百分比刻度(带动画) -->
  <canvas type="2d" id="line" style="position:absolute;"></canvas>
  
  <!-- 绘制小三角(带动画) -->
  <canvas type="2d" id="mark" style="position:absolute;"></canvas>
  
  <!-- 绘制中间文字(带动画) -->
  <canvas type="2d" id="text" style="position:absolute;"></canvas>
  1. 利用cxt.setLineDash([10,10])设置为虚线,画圆弧;
  2. 通过修改中心点,旋转画布来实现动画;

(1)初始化
let scaleTimmer,markTimmer,textTimmer;
let bgLineCtx,markCtx,textCtx,lineCtx;
Component({
    data: {
        canvas: {
            size: 220,
            r: 100,  // 半径
            progress: 0, // 显示进度 (单位百分比)
            index: 0, // 开始刻度
            defaultColor: 'rgba(255,255,255,0.25)', // 背景刻度颜色
            activeColor: 'rgba(255,255,255,1)', // 进度条颜色
            collegeNum: 0, //推荐数量
            totle: 0, //推荐总量  2000
            time: 1000, //动画所需总时间  1000 = 1s
            timer: 20, //延迟间隔  1000 = 1s
            textUnit: '个', //文案单位   所/个
            text:'自定义文案', //文案 
        }
    },
    methods:{
        /**
         * 组件方法调用入口
         */
        loadArcProgress(data) {
            const that=  this;
            this.setData({
                'canvas.collegeNum':data.total,
                'canvas.totle':data.totleLimit,
                'canvas.progress':(data.total / data.totleLimit) * 100
            },()=>{
            	// 
                if(bgLineCtx && markCtx && textCtx && lineCtx){
                    that.drawBg();
                    that.scaleAnimation();
                    that.markAnimation();
                    that.textAnimation();
                }else{
                    that.initCtx().then(()=>{
                        that.drawBg();
                        that.scaleAnimation();
                        that.markAnimation();
                        that.textAnimation();
                    })
                }
            })
        },
        /**
         * 初始化 ctx 
         * 获取dom节点信息
         */
        initCtx(){
            return new Promise((resolve)=>{
                wx.createSelectorQuery().in(this).selectAll('#bgLine,#line,#mark,#text')
                .fields({ node: true, size: true })
                .exec((res) => {
                    const dpr = wx.getSystemInfoSync().pixelRatio
                    // #bgLine
                    let bgLineCanvas = res[0][0].node
                    bgLineCtx = bgLineCanvas.getContext('2d')
                    bgLineCanvas.width = res[0][0].width * dpr
                    bgLineCanvas.height = res[0][0].height * dpr
                    bgLineCtx.scale(dpr, dpr)
                    // #line
                    let lineCanvas = res[0][1].node
                    lineCtx = lineCanvas.getContext('2d')
                    lineCanvas.width = res[0][1].width * dpr
                    lineCanvas.height = res[0][1].height * dpr
                    lineCtx.scale(dpr, dpr)
                    // #mark
                    let markCanvas = res[0][2].node
                    markCtx = markCanvas.getContext('2d')
                    markCanvas.width = res[0][2].width * dpr
                    markCanvas.height = res[0][2].height * dpr
                    markCtx.scale(dpr, dpr)
                    // #text
                    let textCanvas = res[0][3].node
                    textCtx = textCanvas.getContext('2d')
                    textCanvas.width = res[0][3].width * dpr
                    textCanvas.height = res[0][3].height * dpr
                    textCtx.scale(dpr, dpr)
                    resolve()
                })
            })
        }
    }
})
(2)绘制背景半圆刻度

在这里插入图片描述

drawBg() {
            let ctx = bgLineCtx;
            let ctx1 = markCtx;
            let obj = this.data.canvas;
            ctx.clearRect(0, 0, obj.size, obj.size);
            ctx1.clearRect(0, 0, obj.size, obj.size);
            ctx.beginPath()
            ctx.strokeStyle = obj.defaultColor;
            ctx.lineWidth=2.5;
            ctx.setLineDash([2.5, 2.5]);
            ctx.arc(obj.size / 2,obj.size / 2,obj.size / 2-10,Math.PI,2*Math.PI);
            ctx.stroke();
        },
(3)绘制百分比刻度(带动画)

在这里插入图片描述

/**
         * 绘制白色刻度进度
         * 通过rotate旋转画布,修改中心点
         * draw保存上一次绘制内容
         */
        scaleAnimation() {
            let ctx = lineCtx;
            let obj = this.data.canvas;
            ctx.beginPath()
            ctx.strokeStyle = obj.activeColor;
            ctx.lineWidth=2.5;
            ctx.setLineDash([2.5, 2.5]);
            function draw(x){
                ctx.beginPath()
                ctx.clearRect(0,0,obj.size,obj.size / 2);
                ctx.arc(obj.size / 2,obj.size / 2,obj.size / 2-10,Math.PI,Math.PI+x);
                ctx.stroke();
            }
            let num = obj.progress * (Math.PI / 100); // 转到多少 π
            let addNum = num / (obj.time / obj.timer); // 每次转多少 π
            function animate(s){
                scaleTimmer = setInterval(function () {
                    s += addNum;
                    if (s > num) {
                        draw(num);
                        clearInterval(scaleTimmer);
                    } else {
                        draw(s);
                    }
                }, obj.timer)
            }
            clearInterval(scaleTimmer);
            if(obj.collegeNum>0){
                animate(obj.index)
            }else{
                ctx.clearRect(0,0,obj.size / 2,obj.size / 2);
            }
            
        },
(4)绘制小三角(带动画)

在这里插入图片描述

/**
         * 绘制小三角
         * rotate旋转画布,修改中心点
         * draw清空上一次内容
         */
        markAnimation() {
            let ctx = markCtx;
            let obj = this.data.canvas;
            function draw(x){
                ctx.clearRect(0,0,obj.size,obj.size);
                ctx.save();
                // 角度 = 弧度 * 180 / Math.PI
                let deg = (Math.PI/180)*x*180 / Math.PI;
                let offsetY = -(Math.sin(deg) * obj.r);
                let offsetX = -(Math.cos(deg) * obj.r);
                ctx.fillStyle='#fff';
                ctx.translate(obj.size / 2 + offsetX, obj.size / 2 + offsetY);
                ctx.rotate(deg);
                ctx.beginPath();
                ctx.moveTo(-11, -4);
                ctx.lineTo(-3, 0);
                ctx.lineTo(-11, 4);
                ctx.closePath();
                ctx.fill();
                ctx.restore();
            }
            let num = obj.progress * (Math.PI / 100); // 转到多少 π
            let addNum = num / (obj.time / obj.timer); // 每次转多少 π
            function animate(s){
                markTimmer = setInterval(function () {
                    s += addNum;
                    if (s > num) {
                        draw(num);
                        clearInterval(markTimmer);
                    } else {
                        draw(s);
                    }
                }, obj.timer)
            }
            clearInterval(markTimmer);
            if(obj.collegeNum>0){
                animate(obj.index);
            }else{
                draw(0);
            }
            
        },
(5)绘制中间文字(带动画)

在这里插入图片描述

/**
         * 绘制中间文字
         * 默认 000 三位起,当推荐数量4位时 默认 0000 四位起
         * draw清空上一次内容
         */
        textAnimation() {
            let ctx = textCtx;
            let obj = this.data.canvas
            obj.numFontSize = 12;
            obj.textFontSize = 39.6;
            ctx.beginPath();
            ctx.font = obj.textFontSize+'px Arial';
            obj.textY = obj.size / 2 - 25;
            let collegeNumLen = obj.collegeNum.toString().length;
            if (collegeNumLen == 4) {
                obj.textNumW = ctx.measureText('0000').width;
            } else {
                obj.textNumW = ctx.measureText('000').width;
            }
            ctx.beginPath();
            ctx.font = obj.numFontSize+'px Arial';
            obj.textSuoW = ctx.measureText('个').width;
            function drawText(num){
                ctx.clearRect(0,0,obj.size,obj.size)
                ctx.beginPath();
                ctx.fillStyle='#fff';
                ctx.textAlign='center';
                ctx.textBaseline='middle';
                ctx.font=obj.numFontSize+'px Arial';
                ctx.fillText(obj.text, obj.size / 2, obj.size / 2 - 10);
                ctx.beginPath();
                ctx.font = obj.textFontSize+'px Arial';
                ctx.textBaseline='bottom';
                ctx.fillText(num, obj.size / 2 - obj.textSuoW / 2, obj.textY + 9)
                ctx.beginPath();
                ctx.font = obj.numFontSize+'px Arial';
                ctx.textBaseline='bottom'
                ctx.fillText(obj.textUnit, obj.size / 2 + obj.textNumW / 2 + 2, obj.textY);
            }
            let addNum = Math.ceil(obj.collegeNum / (obj.time / obj.timer));
            function animate(s){
                textTimmer = setInterval(function () {
                    s = s + addNum;
                    if (s >= obj.collegeNum) {
                        let num = obj.collegeNum.toString();
                        num = num.length == 1 ? `00${num}` : num.length == 2 ? `0${num}` : num;
                        drawText(num);
                        clearInterval(textTimmer);
                    } else {
                        let num = s.toString();
                        if (collegeNumLen == 4) {
                            num = num.length == 1 ? `000${num}` : num.length == 2 ? `00${num}` : num.length == 3 ? `0${num}` : num;
                        } else {
                            num = num.length == 1 ? `00${num}`: num.length == 2 ? `0${num}` : num;
                        }
                        drawText(num);
                    }
                }, obj.timer)
            }
            if (collegeNumLen == 4) {
                drawText('0000');
            } else {
                drawText('000');
            }
            clearInterval(textTimmer);
            animate(obj.index);
        }

使用组件(组件整体代码在下面)

index.wxml

<arcProgress id="arcProgress"></arcProgress>

index.json

{
  "usingComponents": {
    "arcProgress":"/components/arcProgress/arcProgress"
  }
}

index.js

onLoad(){
	this.selectComponent('#arcProgress').loadArcProgress({
        total: 1293, //推荐数量
        totleLimit: 2000, //推荐总量
    });
 }

组件整体代码

/components/arcProgress.wxml

<view style="height:{{canvas.size/2}}px;width:{{canvas.size}}px;margin:auto;position:relative;line-height:1;color:#fff;">
  <view class="textFS" style="position:absolute;bottom:0;right:{{canvas.size}}px;">0</view>
  <view class="textFS" style="position:absolute;bottom:0;left:{{canvas.size}}px;">{{canvas.totle}}</view>
  <canvas type="2d" id="bgLine" style="width:{{canvas.size}}px;height:{{canvas.size/2}}px;position:absolute;"></canvas>
  <canvas type="2d" id="line" style="width:{{canvas.size}}px;height:{{canvas.size/2}}px;position:absolute;"></canvas>
  <canvas type="2d" id="mark" style="width:{{canvas.size+10}}px;height:{{canvas.size/2+20}}px;position:absolute;"></canvas>
  <canvas type="2d" id="text" style="width:{{canvas.size}}px;height:{{canvas.size/2}}px;position:absolute;"></canvas>
</view>

/components/arcProgress.js

/**
 * 默认  调用组件方法
 * 
 * this.selectComponent('#arcProgress').loadArcProgress({
      total: 0, //推荐数量
      totleLimit: 0, //推荐总量
    });

 * 获取到数据后  调用组件方法
 * 
 * this.selectComponent('#arcProgress').loadArcProgress({
      total: 1293, //推荐数量
      totleLimit: 2000, //推荐总量
    });
 */
let scaleTimmer,markTimmer,textTimmer;
let bgLineCtx,markCtx,textCtx,lineCtx;
Component({
    data: {
        canvas: {
            size: 220,
            r: 100,  // 半径
            progress: 0, // 显示进度 (单位百分比)
            index: 0, // 开始刻度
            defaultColor: 'rgba(255,255,255,0.25)', // 背景刻度颜色
            activeColor: 'rgba(255,255,255,1)', // 进度条颜色
            collegeNum: 0, //推荐数量
            totle: 0, //推荐总量  2000
            time: 1000, //动画所需总时间  1000 = 1s
            timer: 20, //延迟间隔  1000 = 1s
            textUnit: '个', //文案单位   所/个
            text:'自定义文案', //文案 
        }
    },
    methods: {
        /**
         * 初始化 ctx 
         * 获取dom节点信息
         */
        initCtx(){
            return new Promise((resolve)=>{
                wx.createSelectorQuery().in(this).selectAll('#bgLine,#line,#mark,#text')
                .fields({ node: true, size: true })
                .exec((res) => {
                    const dpr = wx.getSystemInfoSync().pixelRatio
                    // #bgLine
                    let bgLineCanvas = res[0][0].node
                    bgLineCtx = bgLineCanvas.getContext('2d')
                    bgLineCanvas.width = res[0][0].width * dpr
                    bgLineCanvas.height = res[0][0].height * dpr
                    bgLineCtx.scale(dpr, dpr)
                    // #line
                    let lineCanvas = res[0][1].node
                    lineCtx = lineCanvas.getContext('2d')
                    lineCanvas.width = res[0][1].width * dpr
                    lineCanvas.height = res[0][1].height * dpr
                    lineCtx.scale(dpr, dpr)
                    // #mark
                    let markCanvas = res[0][2].node
                    markCtx = markCanvas.getContext('2d')
                    markCanvas.width = res[0][2].width * dpr
                    markCanvas.height = res[0][2].height * dpr
                    markCtx.scale(dpr, dpr)
                    // #text
                    let textCanvas = res[0][3].node
                    textCtx = textCanvas.getContext('2d')
                    textCanvas.width = res[0][3].width * dpr
                    textCanvas.height = res[0][3].height * dpr
                    textCtx.scale(dpr, dpr)
                    resolve()
                })
            })
        },
        loadArcProgress(data) {
            const that=  this;
            this.setData({
                'canvas.collegeNum':data.total,
                'canvas.totle':data.totleLimit,
                'canvas.progress':(data.total / data.totleLimit) * 100
            },()=>{
                if(bgLineCtx && markCtx && textCtx && lineCtx){
                    that.drawBg();
                    that.scaleAnimation();
                    that.markAnimation();
                    that.textAnimation();
                }else{
                    that.initCtx().then(()=>{
                        that.drawBg();
                        that.scaleAnimation();
                        that.markAnimation();
                        that.textAnimation();
                    })
                }
            })
        },
        drawBg() {
            let ctx = bgLineCtx;
            let ctx1 = markCtx;
            let obj = this.data.canvas;
            ctx.clearRect(0, 0, obj.size, obj.size);
            ctx1.clearRect(0, 0, obj.size, obj.size);
            ctx.beginPath()
            ctx.strokeStyle = obj.defaultColor;
            ctx.lineWidth=2.5;
            ctx.setLineDash([2.5, 2.5]);
            ctx.arc(obj.size / 2,obj.size / 2,obj.size / 2-10,Math.PI,2*Math.PI);
            ctx.stroke();
        },
        /**
         * 绘制白色刻度进度
         * 通过rotate旋转画布,修改中心点
         * draw保存上一次绘制内容
         */
        scaleAnimation() {
            let ctx = lineCtx;
            let obj = this.data.canvas;
            ctx.beginPath()
            ctx.strokeStyle = obj.activeColor;
            ctx.lineWidth=2.5;
            ctx.setLineDash([2.5, 2.5]);
            function draw(x){
                ctx.beginPath()
                ctx.clearRect(0,0,obj.size,obj.size / 2);
                ctx.arc(obj.size / 2,obj.size / 2,obj.size / 2-10,Math.PI,Math.PI+x);
                ctx.stroke();
            }
            let num = obj.progress * (Math.PI / 100); // 转到多少 π
            let addNum = num / (obj.time / obj.timer); // 每次转多少 π
            function animate(s){
                scaleTimmer = setInterval(function () {
                    s += addNum;
                    if (s > num) {
                        draw(num);
                        clearInterval(scaleTimmer);
                    } else {
                        draw(s);
                    }
                }, obj.timer)
            }
            clearInterval(scaleTimmer);
            if(obj.collegeNum>0){
                animate(obj.index)
            }else{
                ctx.clearRect(0,0,obj.size / 2,obj.size / 2);
            }
            
        },
        /**
         * 绘制小三角
         * rotate旋转画布,修改中心点
         * draw清空上一次内容
         */
        markAnimation() {
            let ctx = markCtx;
            let obj = this.data.canvas;
            function draw(x){
                ctx.clearRect(0,0,obj.size,obj.size);
                ctx.save();
                // 角度 = 弧度 * 180 / Math.PI
                let deg = (Math.PI/180)*x*180 / Math.PI;
                let offsetY = -(Math.sin(deg) * obj.r);
                let offsetX = -(Math.cos(deg) * obj.r);
                ctx.fillStyle='#fff';
                ctx.translate(obj.size / 2 + offsetX, obj.size / 2 + offsetY);
                ctx.rotate(deg);
                ctx.beginPath();
                ctx.moveTo(-11, -4);
                ctx.lineTo(-3, 0);
                ctx.lineTo(-11, 4);
                ctx.closePath();
                ctx.fill();
                ctx.restore();
            }
            let num = obj.progress * (Math.PI / 100); // 转到多少 π
            let addNum = num / (obj.time / obj.timer); // 每次转多少 π
            function animate(s){
                markTimmer = setInterval(function () {
                    s += addNum;
                    if (s > num) {
                        draw(num);
                        clearInterval(markTimmer);
                    } else {
                        draw(s);
                    }
                }, obj.timer)
            }
            clearInterval(markTimmer);
            if(obj.collegeNum>0){
                animate(obj.index);
            }else{
                draw(0);
            }
            
        },
        /**
         * 绘制中间文字
         * 默认 000 三位起,当推荐数量4位时 默认 0000 四位起
         * draw清空上一次内容
         */
        textAnimation() {
            let ctx = textCtx;
            let obj = this.data.canvas
            obj.numFontSize = 12;
            obj.textFontSize = 39.6;
            ctx.beginPath();
            ctx.font = obj.textFontSize+'px Arial';
            obj.textY = obj.size / 2 - 25;
            let collegeNumLen = obj.collegeNum.toString().length;
            if (collegeNumLen == 4) {
                obj.textNumW = ctx.measureText('0000').width;
            } else {
                obj.textNumW = ctx.measureText('000').width;
            }
            ctx.beginPath();
            ctx.font = obj.numFontSize+'px Arial';
            obj.textSuoW = ctx.measureText('个').width;
            function drawText(num){
                ctx.clearRect(0,0,obj.size,obj.size)
                ctx.beginPath();
                ctx.fillStyle='#fff';
                ctx.textAlign='center';
                ctx.textBaseline='middle';
                ctx.font=obj.numFontSize+'px Arial';
                ctx.fillText(obj.text, obj.size / 2, obj.size / 2 - 10);
                ctx.beginPath();
                ctx.font = obj.textFontSize+'px Arial';
                ctx.textBaseline='bottom';
                ctx.fillText(num, obj.size / 2 - obj.textSuoW / 2, obj.textY + 9)
                ctx.beginPath();
                ctx.font = obj.numFontSize+'px Arial';
                ctx.textBaseline='bottom'
                ctx.fillText(obj.textUnit, obj.size / 2 + obj.textNumW / 2 + 2, obj.textY);
            }
            let addNum = Math.ceil(obj.collegeNum / (obj.time / obj.timer));
            function animate(s){
                textTimmer = setInterval(function () {
                    s = s + addNum;
                    if (s >= obj.collegeNum) {
                        let num = obj.collegeNum.toString();
                        num = num.length == 1 ? `00${num}` : num.length == 2 ? `0${num}` : num;
                        drawText(num);
                        clearInterval(textTimmer);
                    } else {
                        let num = s.toString();
                        if (collegeNumLen == 4) {
                            num = num.length == 1 ? `000${num}` : num.length == 2 ? `00${num}` : num.length == 3 ? `0${num}` : num;
                        } else {
                            num = num.length == 1 ? `00${num}`: num.length == 2 ? `0${num}` : num;
                        }
                        drawText(num);
                    }
                }, obj.timer)
            }
            if (collegeNumLen == 4) {
                drawText('0000');
            } else {
                drawText('000');
            }
            clearInterval(textTimmer);
            animate(obj.index);
        }
    }
})
  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
### 回答1: 微信小程序提供了canvas 2d的API接口用于动态生成二维码。首先,我们需要引入QRCode.js,一个专门用于生成二维码的JavaScript库。将此库导入小程序中,调用它提供的API,在canvas板上生成二维码。 首先,我们需要在wxml文件中添加canvas板: ```html <canvas canvas-id="qrcode" style="width: 300rpx; height: 300rpx;"></canvas> ``` 接着,在js文件中获取canvas元素: ```javascript const qrcode = wx.createCanvasContext('qrcode', this); ``` 生成我们需要的二维码: ```javascript qrcode.clearRect(0, 0, 300, 300); qrcode.drawImage("../../utils/qrcode.js", { width: 300, height: 300, text: "https://www.example.com" }) ``` 其中,第一个参数代表清除的矩形区域的左上角的 x 坐标,第二个参数代表清除的矩形区域的左上角的 y 坐标,第三个和第四个参数分别是矩形区域的宽度和高度。 以上代码通过引入QRCode.js库生成了一个链接为"https://www.example.com"的二维码,通过在canvas元素上绘制图案,最终生成了二维码。 需要注意的是,由于canvas是属于html5标签,所以相对于小程序的系统来说,属于一种比较“沉重”的浏览器标签。因此在微信小程序中,canvas的渲染性能可能会存在一些问题。因此在渲染过程中注意控制生成图片的大小和数量,尽量避免出现性能问题。 ### 回答2: 微信小程序是一款越来越受欢迎的移动应用程序,它具有非常丰富的功能。在微信小程序中,通过canvas 2d生成二维码,可以为小程序增加一些非常实用的功能。 二维码是一种十分方便的识别标志,通过扫描二维码可以快速打开某个页面或实现某种功能。在微信小程序中,通过canvas 2d可以轻松生成二维码。首先,需要引入一个QRCode.js插件,该插件可以在小程序中使用canvas 2d进行二维码的生成操作。 在引入QRCode.js之后,就可以开始进行二维码的生成了。通过使用QRCode.js提供的QRCode方法,可以生成一个二维码对象。接下来,通过canvas 2d的操作,可以将二维码对象绘制在小程序的指定位置上。 在绘制二维码时,需要注意以下几点。首先,需要设置二维码的大小和样式,包括背景颜色、前景颜色等。其次,需要将二维码进行缩放和平移操作,以便使其适应小程序的窗口大小和布局。最后,需要将绘制好的二维码输出到小程序canvas布上,使其在小程序中显示出来。 通过canvas 2d生成二维码,可以为微信小程序增加很多强大的功能,如扫码登录、扫码支付等。这是一种非常方便有效的实现方式,也是目前越来越多小程序开发者选择的技术路线
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个......

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值