微信小程序循环渲染canvas动态图表

article/2025/11/6 4:14:37

需求:数据驱动图表渲染,先看效果图
在这里插入图片描述
我们知道,微信小程序中是没有图表组件的,使用echarts也会有很多问题,比如echarts的图表总是在屏幕最上层,也就是说,会在导航栏和菜单栏上方,这当然不是我们想要的,所以只能自己画一个。在这里我选择用canvas画。

首先画一组图表,数据由data驱动

  • wxml部分

     <view class="section flex"><view class='circleBar' bindtap="toModalDetail"><view class="wrap"><view class="top"><canvas class="cir" style=' width:106px; height:106px;' canvas-id="leak"></canvas><view class="centerWord-num">{{resultComment}}</view><view class="centerWord">漏接量</view></view></view>
    </view>
    <view class='circleBar'  bindtap="toModalDetail"><view class="wrap"><view class="top"><canvas class="cir" style=' width:106px; height:106px;' canvas-id="businessbanli"></canvas><view class="centerWord-num-business">{{resultCommentbusiness}}</view><view class="centerWord-business">业务办理量</view></view></view>
    </view>
    
  • wxss部分

    .circleBar {margin:50rpx;width: 250rpx;height: 250rpx;overflow: hidden;position: relative;
    }.cir {display: inline-block;background-color: #fff;border-radius: 100%;
    }.top {text-align: left;
    }
    .centerWord-num,.centerWord,.centerWord-num-business,.centerWord-business{width: 100%;position: absolute;left: -15rpx;text-align: center;
    }
    .centerWord-num{top: 30px;color: #3686ff;
    }
    .centerWord {top: 60px;color: #3686ff;
    }
    .centerWord-num-business{top: 30px;color: #FFD395;
    }
    .centerWord-business {top: 60px;color: #FFD395;
    }
    
  • js部分

    Page({data: {timer: '',timerbusiness:''},onLoad: function(options) {let that = this;// 以下两个是项目监控测试数据let totalItems = 100;let leakItems = 80;let businessItems=30;let completePercent = parseInt((leakItems / totalItems) * 100);let completePercentbusiness = parseInt((businessItems / totalItems) * 100);that.getResultComment(completePercent);that.getResultCommentbusiness(completePercentbusiness);that.showScoreAnimation(leakItems, totalItems);that.showScoreAnimationbusiness(businessItems, totalItems);},showScoreAnimation: function (leakItems, totalItems) {let that = this;let copyleakItems = 0;that.setData({timer: setInterval(function () {copyleakItems++;if (copyleakItems == leakItems) {clearInterval(that.data.timer)} else {// 页面渲染完成// 这部分是灰色底层let cxt_arc = wx.createCanvasContext('leak');//创建并返回绘图上下文context对象。cxt_arc.setLineWidth(6);//绘线的宽度cxt_arc.setStrokeStyle('#fff');//绘线的颜色cxt_arc.setLineCap('round');//线条端点样式cxt_arc.beginPath();//开始一个新的路径cxt_arc.arc(53, 53, 50, 0, 2 * Math.PI, false);//设置一个原点(53,53),半径为50的圆的路径到当前路径cxt_arc.stroke();//对当前路径进行描边//这部分是蓝色部分cxt_arc.setLineWidth(6);cxt_arc.setStrokeStyle('#3ea6ff');cxt_arc.setLineCap('round')cxt_arc.beginPath();//开始一个新的路径cxt_arc.arc(53, 53, 50, -Math.PI * 1 / 2, 2 * Math.PI * (copyleakItems / totalItems) - Math.PI * 1 / 2, false);cxt_arc.stroke();//对当前路径进行描边cxt_arc.draw();}}, 20)})},showScoreAnimationbusiness: function (businessItems, totalItems) {let that = this;let copyleakItems = 0;that.setData({timerbusiness: setInterval(function () {copyleakItems++;if (copyleakItems == businessItems) {clearInterval(that.data.timerbusiness)} else {// 页面渲染完成// 这部分是灰色底层let cxt_arc = wx.createCanvasContext('businessbanli');//创建并返回绘图上下文context对象。cxt_arc.setLineWidth(6);//绘线的宽度cxt_arc.setStrokeStyle('#fff');//绘线的颜色cxt_arc.setLineCap('round');//线条端点样式cxt_arc.beginPath();//开始一个新的路径cxt_arc.arc(53, 53, 50, 0, 2 * Math.PI, false);//设置一个原点(53,53),半径为50的圆的路径到当前路径cxt_arc.stroke();//对当前路径进行描边//这部分是蓝色部分cxt_arc.setLineWidth(6);cxt_arc.setStrokeStyle('#FFD395');cxt_arc.setLineCap('round')cxt_arc.beginPath();//开始一个新的路径cxt_arc.arc(53, 53, 50, -Math.PI * 1 / 2, 2 * Math.PI * (copyleakItems / totalItems) - Math.PI * 1 / 2, false);cxt_arc.stroke();//对当前路径进行描边cxt_arc.draw();}}, 20)})},getResultComment: function (completePercent) {let that = this;that.setData({resultComment: completePercent})},getResultCommentbusiness: function (completePercentbusiness) {let that = this;that.setData({resultCommentbusiness: completePercentbusiness})}
    });

    效果为:
    在这里插入图片描述
    这样就能画出两个图表了,但是,我们需要以数据来决定图表的个数,确切来说是组数(两个为一组),假设有三组数据,暂且这样表示:

    data: {timer: '',timerbusiness: '',modalNum:[{ leak: 50, business: 90 },{ leak: 30, business: 70 },{ leak: 20, business: 60 }]},
    

    我们需要按照这组数据循环渲染,外面包上一层block再用wx:for

    <view class="section"><view class="text">5元100M流量监控模型</view><block wx:for="{{modalNum}}" wx:key="{{index}}"><view class="flex" data-index="{{index}}"><view class='circleBar'  bindtap="toModalDetail"><view class="wrap"><view class="top"><canvas class="cir" style=' width:106px; height:106px;' canvas-id="leak-{{index}}"></canvas><view class="centerWord-num">{{item.leak}}</view><view class="centerWord">漏接量</view></view></view></view><view class='circleBar'  bindtap="toModalDetail"><view class="wrap"><view class="top"><canvas class="cir" style=' width:106px; height:106px;' canvas-id="business-{{index}}"></canvas><view class="centerWord-num-business">{{item.business}}</view><view class="centerWord-business">业务办理量</view></view></view></view></view></block>
    </view>
    

    因为每个canvas需要有独立的id,所以给每个id后面都加上了当前的index。js部分也循环一遍:

    onLoad: function (options) {let that = this;this.data.modalNum.map((value,index)=>{let totalItems = 100;let leakItems = value.leak;let businessItems = value.business;that.showScoreAnimation(leakItems, totalItems,index);that.showScoreAnimationbusiness(businessItems, totalItems,index);})},showScoreAnimation: function (leakItems, totalItems,index) {let that = this;let copyleakItems = 0;that.setData({timer: setInterval(function () {copyleakItems++;if (copyleakItems == leakItems) {clearInterval(that.data.timer)} else {// 页面渲染完成// 这部分是白色底层let cxt_arc = wx.createCanvasContext("leak-"+index);//创建并返回绘图上下文context对象。cxt_arc.setLineWidth(6);//绘线的宽度cxt_arc.setStrokeStyle('#fff');//绘线的颜色cxt_arc.setLineCap('round');//线条端点样式cxt_arc.beginPath();//开始一个新的路径cxt_arc.arc(53, 53, 50, 0, 2 * Math.PI, false);//设置一个原点(53,53),半径为50的圆的路径到当前路径cxt_arc.stroke();//对当前路径进行描边//这部分是蓝色部分cxt_arc.setLineWidth(6);cxt_arc.setStrokeStyle('#3ea6ff');cxt_arc.setLineCap('round')cxt_arc.beginPath();cxt_arc.arc(53, 53, 50, -Math.PI * 1 / 2, 2 * Math.PI * (copyleakItems / totalItems) - Math.PI * 1 / 2, false);cxt_arc.stroke();cxt_arc.draw();}}, 20)})},showScoreAnimationbusiness: function (businessItems, totalItems,index) {let that = this;let copyleakItems = 0;that.setData({timerbusiness: setInterval(function () {copyleakItems++;if (copyleakItems == businessItems) {clearInterval(that.data.timerbusiness)} else {// 页面渲染完成// 这部分是白色底层let cxt_arc = wx.createCanvasContext("business-"+index);//创建并返回绘图上下文context对象。cxt_arc.setLineWidth(6);//绘线的宽度cxt_arc.setStrokeStyle('#fff');//绘线的颜色cxt_arc.setLineCap('round');//线条端点样式cxt_arc.beginPath();//开始一个新的路径cxt_arc.arc(53, 53, 50, 0, 2 * Math.PI, false);//设置一个原点(53,53),半径为50的圆的路径到当前路径cxt_arc.stroke();//对当前路径进行描边//这部分是橙色部分cxt_arc.setLineWidth(6);cxt_arc.setStrokeStyle('#FFD395');cxt_arc.setLineCap('round')cxt_arc.beginPath();//开始一个新的路径cxt_arc.arc(53, 53, 50, -Math.PI * 1 / 2, 2 * Math.PI * (copyleakItems / totalItems) - Math.PI * 1 / 2, false);cxt_arc.stroke();//对当前路径进行描边cxt_arc.draw();}}, 20)})}
    

    显然,这样做有一个很大的问题,每一组数据用的都是同一个定时器,这样会导致混乱,只能关闭最后一个定时器,如下图:
    在这里插入图片描述
    控制台打印index值:
    在这里插入图片描述
    前两组数据无法关闭定时器而陷入死循环,copyleakItems的值不断增大却始终无法结束。所以我们需要给每一个canvas一个单独的定时器:

    onLoad: function (options) {let that = this;this.data.modalNum.map((value,index)=>{let totalItems = 100;let leakItems = value.leak;let businessItems = value.business;that.showScoreAnimation(leakItems, totalItems,index);that.showScoreAnimationbusiness(businessItems, totalItems,index);})},showScoreAnimation: function (leakItems, totalItems,index) {let that = this;let copyleakItems = 0;let itemData = "timer"+index;that.setData({[itemData]: setInterval(function () {copyleakItems++;if (copyleakItems == leakItems) {clearInterval(that.data[itemData])} else {// 页面渲染完成// 这部分是白色底层let cxt_arc = wx.createCanvasContext("leak-"+index);//创建并返回绘图上下文context对象。cxt_arc.setLineWidth(6);//绘线的宽度cxt_arc.setStrokeStyle('#fff');//绘线的颜色cxt_arc.setLineCap('round');//线条端点样式cxt_arc.beginPath();//开始一个新的路径cxt_arc.arc(53, 53, 50, 0, 2 * Math.PI, false);//设置一个原点(53,53),半径为50的圆的路径到当前路径cxt_arc.stroke();//对当前路径进行描边//这部分是蓝色部分cxt_arc.setLineWidth(6);cxt_arc.setStrokeStyle('#3ea6ff');cxt_arc.setLineCap('round')cxt_arc.beginPath();cxt_arc.arc(53, 53, 50, -Math.PI * 1 / 2, 2 * Math.PI * (copyleakItems / totalItems) - Math.PI * 1 / 2, false);cxt_arc.stroke();cxt_arc.draw();}}, 20)})},showScoreAnimationbusiness: function (businessItems, totalItems,index) {let that = this;let copyleakItems = 0;let itemData = "timerbusiness" + index;that.setData({[itemData]: setInterval(function () {copyleakItems++;if (copyleakItems == businessItems) {clearInterval(that.data[itemData])} else {// 页面渲染完成// 这部分是白色底层let cxt_arc = wx.createCanvasContext("business-"+index);//创建并返回绘图上下文context对象。cxt_arc.setLineWidth(6);//绘线的宽度cxt_arc.setStrokeStyle('#fff');//绘线的颜色cxt_arc.setLineCap('round');//线条端点样式cxt_arc.beginPath();//开始一个新的路径cxt_arc.arc(53, 53, 50, 0, 2 * Math.PI, false);//设置一个原点(53,53),半径为50的圆的路径到当前路径cxt_arc.stroke();//对当前路径进行描边//这部分是橙色部分cxt_arc.setLineWidth(6);cxt_arc.setStrokeStyle('#FFD395');cxt_arc.setLineCap('round')cxt_arc.beginPath();//开始一个新的路径cxt_arc.arc(53, 53, 50, -Math.PI * 1 / 2, 2 * Math.PI * (copyleakItems / totalItems) - Math.PI * 1 / 2, false);cxt_arc.stroke();//对当前路径进行描边cxt_arc.draw();}}, 20)})}
    

    这样就可以用数据渲染多个canvas了


http://chatgpt.dhexx.cn/article/PpbJsRLY.shtml

相关文章

使用map优化for循环

使用map优化for循环 微信公众号&#xff1a;前端程序猿之路关注可了解更多的前端知识&#xff0c;反馈问题或建议&#xff0c;请公众号留言。如果你觉得公众号内容对你有帮助&#xff0c;欢迎关注并转载 之前做的一个东西是点击一键升级按钮&#xff0c;修改后台返回的json数据…

通过js获取本地IP地址

通过js获取本地IP地址 最近在做pc项目&#xff0c;需要根据用户的IP地址定位城市。IP地址需要通过js获取&#xff1a; 网上有很多查询接口可以获取到IP&#xff0c;我这里用的是搜狐的&#xff1a; http://pv.sohu.com/cityjson?ieutf-8 在浏览器中&#xff0c;直接输入这…

利用JS获取用户当前ip地址

新浪的IP地址查询接口&#xff1a;http://int.dpool.sina.com.cn/iplookup/iplookup.php?formatjs &#xff08;默认为纯文本格式&#xff0c;根据format的参数定义&#xff0c;还可以返回JS、Json格式&#xff09;。新浪多地域测试方法&#xff1a; http://int.dpool.sina.c…

js获取用户浏览器信息和ip地址以及位置

获取浏览器信息代码 function userAgent () {let browserReg {Chrome: /Chrome/,IE: /MSIE/,Firefox: /Firefox/,Opera: /Presto/,Safari: /Version\/([\d.]).*Safari/,360: /360SE/,QQBrowswe: /QQ/,Edge: /Edg/};let deviceReg {iPhone: /iPhone/,iPad: /iPad/,Android: /A…

js方法获取本机IP

记录一次工作中需求 获取本机IP getUserIP(onNewIP) {// onNewIp - your listener function for new IPs//compatibility for firefox and chromevar myPeerConnection window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;var pc …

【2023亲测可用】JS 获取电脑本地IP 和 电脑网络IP(外网IP|公网IP)

1、了解&#xff1a;电脑本地的IP地址&#xff08;内网IP&#xff09;和电脑本机在网络上的IP地址&#xff08;外网IP|公网IP&#xff09; 在运行窗口输入“cmd”&#xff0c;在弹出的界面里输入“ipconfig/all”。弹出的数据中&#xff0c;IPv4地址&#xff0c;就是电脑本地的…

C语言中的,“与”,“或”,“异或”解释

int main() {int a2;//取二进制010int b4;//取二进制100//int ca && b;int ca&b;//”&“这个叫“与”&#xff0c;与门中的与&#xff0c;两个都为1的时候才为1&#xff0c; 0与任何数都为0&#xff0c;“|”这个叫“或”&#xff0c;两个都为0的时候才为0&…

C语言位运算符及作用:与、或、异或、取反、左移和右移

一、& 按位与 如果两个相应的二进制位都为1&#xff0c;则该位的结果值为1&#xff0c;否则为0 应用&#xff1a;&#xff08;1&#xff09;清零 若想对一个存储单元清零&#xff0c;即使其全部二进制位为0&#xff0c;只要找一个二进制数&#xff0c;其中各个位符合一下条…

C/运算符(按位与、按位或、按位异或)

本文主要介绍C语言中按位与、按位或、按位异或三个操作符的使用。 该三个操作符中的位&#xff0c;代表的是二进制位。 按位与&#xff1a;& 运算规则&#xff1a;只有两个数的二进制同时为1&#xff0c;结果才为1&#xff0c;否则为0。&#xff08;负数按补码形式参加按…

C | 妙用异或

CSDN话题挑战赛第2期 参赛话题&#xff1a;学习笔记 啊我摔倒了..有没有人扶我起来学习.... 目录 前言一、问题的引出二、异或实现俩变量值的交换三、总结 前言 异或也叫半加运算&#xff0c;其运算法则相当于不带进位的二进制加法&#xff1a;二进制下用1表示真&#xff0c;0…

C语言中的异或 - 运算符^

最近在完成程序设计入门-C语言中需要计算异或&#xff0c;查阅了很多资料终于明白计算计算原理&#xff0c;记录一下方便以后查询。 流程 举例 为了方便理解&#xff0c;下面结合流程举例说明 a1 6 &#xff0c; 转化成二进制 a2 0110 b1 10&#xff0c;转化成二进制 b2 …

异或^操作符(C语言)

一、 简介 1. 异或^操作符&#xff1a;是一个位操作符&#xff0c;针对于二进制位&#xff08;比特位&#xff09;的操作。 2. 规则&#xff1a;两个数在同一个二进制位&#xff08;比特位&#xff09;&#xff0c;相同为0&#xff1b;不同为1。 3. 异或操作符的一些性质&…

c语言异或(c语言异或符号)

请帮我讲解一下C语言中的异或运算 首先&#xff0c;我们看一下异或的原理&#xff1a; a 3 ^ 5; 3的二进制是0011&#xff0c;5的二进制是0101。异或发现两者的不同之处&#xff0c;所以a最终为0110b(4)。 了解了异或的基本原理后&#xff0c;接下来看上述的代码。 a^b; 这一句…

C语言异或操作详解(小小异或,大大作用~)

文章目录 *按位异或"^"(1)何为“^”&#xff1a;①“^”的介绍 (2)用于算法的经典案例&#xff1a; 1.数组nums包含从0到n的所有整数&#xff0c;但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗&#xff1f;&#xff08;源自leetcode面…

【c语言操作符系列1】^(异或操作符)讲解和多种例题详解

目录 一、^ 是什么&#xff08;^称为异或&#xff09; 二、^的规律(特点) 三、可利用^秒杀的常见例题&#xff08;重点&#xff09; 1、消失的数字 2、不一样的人生密码 3、交换两个数&#xff08;不能创建中间变量&#xff09; 4、找出只出现一个的两个数字 一、^ 是什么…

C语言中位运算异或“∧”的作用

前言&#xff1a; 为了方便查看博客&#xff0c;特意申请了一个公众号&#xff0c;附上二维码&#xff0c;有兴趣的朋友可以关注&#xff0c;和我一起讨论学习&#xff0c;一起享受技术&#xff0c;一起成长。 1.概念 异或运算符"∧"也称XOR运算符。它的规则是若参…

此公众号并没有这些scope的权限,错误码:10005

有时候在使用微信公众号时会出错&#xff0c;被告知没有权限&#xff0c;如下图所示&#xff1a; 出现这问题有以下原因&#xff1a; 订阅号没有相关的权限账号没有认证&#xff0c;没有相关的权限scope 参数位置错误 解决方案&#xff1a; 需要在OAuth2.0网页授权中配置授权…

Discuz 论坛 手机端微信登录报错:此公众号并没有这些scope的权限,错误码:10005

抛出问题 当discuz绑定微信公众号时&#xff0c;可以控制微信公众号的一些操作&#xff1a; 解决问题 而出现这种错误的原因一般由三种&#xff1a; 订阅号没有相关的权限账号没有认证&#xff0c;没有相关的权限scope 参数位置错误 排查问题&#xff1a; Discuz的微信…

10005---2017年国内开源项目Top50

国内开源项目&#xff0c;不错&#xff0c;大力支持&#xff0c;顶&#xff01;&#xff01;&#xff01; 我要是有钱&#xff0c;一定出资赞助他们啊。 2017 年度码云热门项目排行榜 TOP 50 出炉啦&#xff01;我们根据所有开源项目在码云的用户关注度、活跃度、访问量等信息…