微信小程序中实现自定义圆形进度条

前言:记录项目中使用的两种圆形进度条,一切尽在不言中

案例一:圆内按百分比展示【完整代码及注释】

>

javascript 复制代码
// pages/demo_checkin_progress/demo_checkin_progress.js

/**
 * 康复打卡进度圆形进度条示例页面
 * 
 * 功能说明:
 * 1. 使用 Canvas 绘制圆形进度条(背景圆环 + 进度圆环)
 * 2. 中心显示"当前次数/总次数"格式(如:17/28)
 * 3. 右侧显示"还需 X 次打卡"说明文字
 * 4. 支持动态更新进度,通过控制面板测试不同进度
 * 
 * 适用场景:
 * - 打卡进度展示
 * - 任务完成度展示
 * - 目标进度可视化
 */

Page({
  /**
   * 页面数据配置
   */
  data: {
    // 打卡次数配置
    currentCount: 17,    // 当前打卡次数
    totalCount: 28,      // 总打卡次数
    targetCount: 20,     // 目标打卡次数
    remainingCount: 3,   // 还需打卡次数(自动计算)
    
    // Canvas 配置(单位:px)
    canvasSize: 180,     // Canvas 画布尺寸(宽高相等)- 减小尺寸
    circleRadius: 70,    // 圆环半径 - 减小半径
    lineWidth: 16,       // 圆环线条宽度 - 减小线宽
    
    // 输入框数据
    inputCurrent: '17',
    inputTotal: '28'
  },

  /**
   * 页面加载完成后的回调
   * 用于初始化 Canvas 绘制
   */
  onReady() {
    // 延迟绘制,确保 Canvas 已经渲染完成
    setTimeout(() => {
      this.drawCheckinCircle();
    }, 300);
  },

  /**
   * 页面显示时重新绘制
   * 确保数据已更新后重新绘制圆环
   */
  onShow() {
    setTimeout(() => {
      this.drawCheckinCircle();
    }, 200);
  },

  /**
   * 绘制打卡圆形进度条
   * 核心方法:使用 Canvas API 绘制圆环
   */
  drawCheckinCircle() {
    // 获取当前打卡数据
    const { currentCount, totalCount } = this.data;
    
    /**
     * 计算进度百分比
     * 公式:percentage = currentCount / totalCount
     * 例如:17 / 28 ≈ 0.607 (60.7%)
     */
    const percentage = totalCount > 0 ? (currentCount / totalCount) : 0;
    
    console.log('开始绘制圆形进度条,当前进度:', percentage);
    
    /**
     * 创建 Canvas 绘图上下文
     * canvas-id 对应 wxml 中 canvas 标签的 canvas-id 属性
     */
    const ctx = wx.createCanvasContext('checkinCanvas');
    
    /**
     * Canvas 基础配置
     * 注意:这里使用像素(px)为单位,不是 rpx
     */
    const size = this.data.canvasSize;        // 画布尺寸
    const centerX = size / 2;                 // 圆心 X 坐标(画布中心)
    const centerY = size / 2;                 // 圆心 Y 坐标(画布中心)
    const radius = this.data.circleRadius;    // 圆环半径
    const lineWidth = this.data.lineWidth;    // 线条宽度
    
    /**
     * 第一步:清空画布
     * 清除之前绘制的内容,避免重叠
     */
    ctx.clearRect(0, 0, size, size);
    
    /**
     * 第二步:绘制背景圆环(灰色底环)
     * 绘制一个完整的圆形作为背景
     */
    ctx.beginPath();  // 开始新的路径
    ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);  // 绘制完整圆形
    ctx.setStrokeStyle('rgba(255, 255, 255, 0.3)');  // 设置边框颜色(半透明白色)
    ctx.setLineWidth(lineWidth);     // 设置线条宽度
    ctx.setLineCap('round');         // 设置线条端点为圆形
    ctx.stroke();  // 描边绘制
    
    /**
     * 第三步:绘制进度圆环(白色进度)
     * 根据进度百分比计算需要绘制的弧度
     * 
     * 角度说明:
     * - 起始角度:-Math.PI / 2(-90 度,即 12 点钟方向)
     * - 结束角度:起始角度 + (进度百分比 × 360 度)
     * 
     * 例如:
     * - 进度 0% → 结束角度 = 起始角度
     * - 进度 50% → 结束角度 = 起始角度 + π(180 度)
     * - 进度 100% → 结束角度 = 起始角度 + 2π(360 度)
     */
    const startAngle = -Math.PI / 2;  // 从 12 点钟方向开始
    const endAngle = startAngle + (percentage * 2 * Math.PI);
    
    // 绘制进度圆弧
    ctx.beginPath();  // 开始新的路径
    ctx.arc(centerX, centerY, radius, startAngle, endAngle, false);  // 绘制圆弧
    ctx.setStrokeStyle('#ffffff');  // 设置边框颜色(白色)
    ctx.setLineWidth(lineWidth);     // 设置线条宽度
    ctx.setLineCap('round');         // 设置线条端点为圆形
    ctx.stroke();  // 描边绘制
    
    /**
     * 第四步:提交绘制到画布
     * 将之前所有的绘制操作真正渲染到 Canvas 上
     * 
     * ctx.draw(reserve, callback)
     * - reserve: 是否保留之前的绘制内容(false 表示清空重绘)
     * - callback: 绘制完成后的回调函数
     */
    ctx.draw(false, () => {
      console.log('Canvas 绘制完成!进度:', percentage);
    });
  },

  /**
   * 更新打卡数据
   * 根据新的打卡次数重新计算并绘制
   */
  updateCheckinData(current, total) {
    // 计算还需打卡次数
    const remaining = Math.max(0, this.data.targetCount - current);
    
    // 更新页面数据
    this.setData({
      currentCount: current,
      totalCount: total,
      remainingCount: remaining,
      inputCurrent: String(current),
      inputTotal: String(total)
    }, () => {
      // 数据更新完成后重新绘制圆环
      this.drawCheckinCircle();
    });
  },

  /**
   * 控制面板按钮事件:设置进度为 10%
   */
  setProgress10() {
    const current = Math.round(this.data.totalCount * 0.1);
    this.updateCheckinData(current, this.data.totalCount);
  },

  /**
   * 控制面板按钮事件:设置进度为 30%
   */
  setProgress30() {
    const current = Math.round(this.data.totalCount * 0.3);
    this.updateCheckinData(current, this.data.totalCount);
  },

  /**
   * 控制面板按钮事件:设置进度为 60%
   */
  setProgress60() {
    const current = Math.round(this.data.totalCount * 0.6);
    this.updateCheckinData(current, this.data.totalCount);
  },

  /**
   * 控制面板按钮事件:设置进度为 100%
   */
  setProgress100() {
    this.updateCheckinData(this.data.totalCount, this.data.totalCount);
  },

  /**
   * 输入框事件:监听当前打卡次数输入
   */
  onInputCurrent(e) {
    this.setData({
      inputCurrent: e.detail.value
    });
  },

  /**
   * 输入框事件:监听总打卡次数输入
   */
  onInputTotal(e) {
    this.setData({
      inputTotal: e.detail.value
    });
  },

  /**
   * 应用自定义设置
   */
  applyCustom() {
    const current = parseInt(this.data.inputCurrent) || 0;
    const total = parseInt(this.data.inputTotal) || 28;
    
    if (current < 0 || total <= 0) {
      wx.showToast({
        title: '请输入有效数字',
        icon: 'none'
      });
      return;
    }
    
    this.updateCheckinData(current, total);
    
    wx.showToast({
      title: '已更新',
      icon: 'success'
    });
  }
});
html 复制代码
<!--pages/demo_checkin_progress/demo_checkin_progress.wxml-->
<!-- 
  康复打卡进度圆形进度条示例页面
  功能:展示打卡进度的圆环,中心显示"当前次数/总次数"格式
-->

<view class="page-container">
  <!-- 页面标题 -->
  <view class="page-header">
    <text class="page-title">康复打卡进度示例</text>
  </view>

  <!-- 打卡进度卡片 -->
  <view class="progress-card">
    <!-- 卡片内容:圆形进度条 -->
    <view class="card-content">
      <!-- 圆形进度条 -->
      <view class="circle-wrapper">
        <!-- Canvas 画布:绘制圆环背景和进度 -->
        <canvas 
          canvas-id="checkinCanvas" 
          class="checkin-canvas"
          style="width: 180px; height: 180px;">
        </canvas>
        
        <!-- 中心文字:显示打卡次数 -->
        <view class="circle-center-text">
          <view class="count-wrapper">
            <text class="current-count">{{currentCount}}</text>
            <text class="total-count">/{{totalCount}}</text>
          </view>
          <text class="count-label">已打卡次数</text>
        </view>
      </view>
    </view>
  </view>

  <!-- 控制面板:用于测试不同进度 -->
  <view class="control-section">
    <text class="section-title">测试控制面板</text>
    
    <view class="control-buttons">
      <button class="control-btn" bindtap="setProgress10">10% 进度</button>
      <button class="control-btn" bindtap="setProgress30">30% 进度</button>
      <button class="control-btn" bindtap="setProgress60">60% 进度</button>
      <button class="control-btn" bindtap="setProgress100">100% 进度</button>
    </view>

    <view class="custom-input">
      <text class="input-label">自定义设置:</text>
      <input 
        class="input-field" 
        type="number" 
        placeholder="当前打卡次数"
        value="{{inputCurrent}}"
        bindinput="onInputCurrent"
      />
      <input 
        class="input-field" 
        type="number" 
        placeholder="总打卡次数"
        value="{{inputTotal}}"
        bindinput="onInputTotal"
      />
      <button class="apply-btn" bindtap="applyCustom">应用</button>
    </view>
  </view>

  
</view>
css 复制代码
/* pages/demo_checkin_progress/demo_checkin_progress.wxss */

/**
 * 页面容器样式
 * 使用渐变背景,模拟真实页面效果
 */
 .page-container {
  min-height: 100vh;
  background: linear-gradient(180deg, #e0f2fe 0%, #f0f9ff 100%);
  padding: 40rpx;
  box-sizing: border-box;
}

/**
 * 页面头部样式
 */
.page-header {
  margin-bottom: 40rpx;
  text-align: center;
}

.page-title {
  font-size: 40rpx;
  font-weight: bold;
  color: #1e293b;
}

/**
 * 打卡进度卡片样式
 * 使用蓝色渐变背景,与【我的】页面保持一致
 */
.progress-card {
  background: linear-gradient(135deg, #4a90d9 0%, #35c7d9 100%);
  border-radius: 32rpx;
  padding: 40rpx;
  margin-bottom: 40rpx;
  box-shadow: 0 8rpx 32rpx rgba(74, 144, 217, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
}

/**
 * 卡片头部样式
 * 包含标题和目标标签
 */
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-shrink: 0;
  margin-bottom: 20rpx;
}

/**
 * 卡片标题样式
 */
.card-title {
  font-size: 32rpx;
  font-weight: bold;
  color: #ffffff;
}

/**
 * 目标标签样式
 * 半透明白色背景
 */
.card-tag {
  font-size: 24rpx;
  color: rgba(255, 255, 255, 0.85);
  background-color: rgba(255, 255, 255, 0.2);
  padding: 8rpx 20rpx;
  border-radius: 32rpx;
}

/**
 * 卡片内容区域样式
 * 使用 flex 布局,左侧圆环,右侧文字
 */
.card-content {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: flex-start;
}

/**
 * 圆形进度条外层容器
 * 相对定位,用于内部绝对定位
 */
.circle-wrapper {
  position: relative;
  width: 180px;
  height: 180px;
  flex-shrink: 0;
}

/**
 * Canvas 画布样式
 * 绝对定位,铺满整个容器
 */
.checkin-canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
}

/**
 * 圆环中心文字样式
 * 绝对定位居中,显示打卡次数
 */
.circle-center-text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  z-index: 2;
  pointer-events: none;
  text-align: center;
}

/**
 * 次数显示容器
 * 横向排列当前次数和总次数
 */
.count-wrapper {
  display: flex;
  align-items: baseline;
  justify-content: center;
}

/**
 * 当前打卡次数样式
 * 大号字体,加粗显示
 */
.current-count {
  font-size: 56rpx;
  font-weight: bold;
  color: #ffffff;
  line-height: 1;
}

/**
 * 总打卡次数样式
 * 较小字体,半透明白色
 */
.total-count {
  font-size: 28rpx;
  color: rgba(255, 255, 255, 0.85);
  line-height: 1;
}

/**
 * 次数标签样式
 * 显示"已打卡次数"文字
 */
.count-label {
  font-size: 24rpx;
  color: rgba(255, 255, 255, 0.8);
  margin-top: 8rpx;
}

/**
 * 右侧进度说明文字样式
 * 距离左侧三分之一间距
 */
.progress-text {
  flex: 2;
  padding-left: 60rpx;
  display: flex;
  flex-direction: column;
  gap: 16rpx;
}

/**
 * 主要文字行样式
 * "还需 X 次打卡"
 */
.text-main {
  display: flex;
  align-items: baseline;
  gap: 8rpx;
}

/**
 * "还需"文字样式
 */
.text-prefix {
  font-size: 32rpx;
  font-weight: bold;
  color: #ffffff;
}

/**
 * 高亮数字样式
 * 大号字体显示剩余次数
 */
.text-highlight {
  font-size: 56rpx;
  font-weight: bold;
  color: #ffffff;
}

/**
 * "次打卡"文字样式
 */
.text-suffix {
  font-size: 32rpx;
  font-weight: bold;
  color: #ffffff;
}

/**
 * 次要说明文字样式
 * "即可完成打卡"
 */
.text-sub {
  font-size: 28rpx;
  color: rgba(255, 255, 255, 0.85);
}

/**
 * 控制面板区域样式
 */
.control-section {
  background-color: #ffffff;
  border-radius: 32rpx;
  padding: 40rpx;
  margin-bottom: 40rpx;
  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}

.section-title {
  display: block;
  font-size: 32rpx;
  font-weight: bold;
  color: #333;
  margin-bottom: 32rpx;
}

/**
 * 控制按钮区域样式
 */
.control-buttons {
  display: flex;
  gap: 20rpx;
  flex-wrap: wrap;
  margin-bottom: 32rpx;
}

.control-btn {
  flex: 1;
  min-width: 200rpx;
  background-color: #4a90d9;
  color: #fff;
  font-size: 28rpx;
  padding: 24rpx 32rpx;
  border-radius: 16rpx;
  border: none;
}

.control-btn:active {
  opacity: 0.8;
}

/**
 * 自定义输入区域样式
 */
.custom-input {
  display: flex;
  flex-direction: column;
  gap: 16rpx;
}

.input-label {
  font-size: 28rpx;
  color: #666;
  font-weight: bold;
}

.input-field {
  background-color: #f5f5f5;
  border-radius: 12rpx;
  padding: 20rpx 24rpx;
  font-size: 28rpx;
  border: 2rpx solid #e0e0e0;
}

.input-field:focus {
  border-color: #4a90d9;
}

.apply-btn {
  background-color: #35c7d9;
  color: #fff;
  font-size: 28rpx;
  padding: 24rpx 32rpx;
  border-radius: 16rpx;
  border: none;
  margin-top: 8rpx;
}

.apply-btn:active {
  opacity: 0.8;
}

案例二:圆内展示 当前任务数 / 总任务数量【完整代码及注释】

javascript 复制代码
// pages/demo_circle_progress/demo_circle_progress.js

/**
 * 圆形进度条示例页面
 * 
 * 功能说明:
 * 1. 使用 Canvas 绘制圆形进度条
 * 2. 支持动态更新进度
 * 3. 包含背景圆环和进度圆环两层
 * 4. 中心显示当前进度百分比
 */

Page({
  /**
   * 页面数据
   */
  data: {
    currentProgress: 60  // 当前进度百分比(0-100)
  },

  /**
   * 页面加载完成后的回调
   * 用于初始化 Canvas 绘制
   */
  onReady() {
    // 延迟绘制,确保 Canvas 已经渲染完成
    setTimeout(() => {
      this.drawCircleProgress();
    }, 500);
  },

  /**
   * 页面显示时重新绘制(确保数据已更新)
   */
  onShow() {
    setTimeout(() => {
      this.drawCircleProgress();
    }, 300);
  },

  /**
   * 绘制圆形进度条
   * 核心方法:使用 Canvas API 绘制圆环
   */
  drawCircleProgress() {
    console.log('开始绘制圆形进度条,当前进度:', this.data.currentProgress);
    
    // 获取当前进度值
    const progress = this.data.currentProgress;
    
    /**
     * 创建 Canvas 绘图上下文
     * canvas-id 对应 wxml 中 canvas 标签的 canvas-id 属性
     */
    const ctx = wx.createCanvasContext('progressCanvas');
    
    console.log('Canvas 上下文创建成功:', ctx);
    
    /**
     * 画布基础配置
     * 注意:这里使用像素(px)为单位,不是 rpx
     */
    const size = 200;        // 画布尺寸(宽高相等,正方形)
    const centerX = size / 2; // 圆心 X 坐标(画布中心)
    const centerY = size / 2; // 圆心 Y 坐标(画布中心)
    const radius = 80;        // 圆环半径(决定圆的大小)
    const lineWidth = 16;     // 线条宽度(决定圆环粗细)
    
    /**
     * 第一步:清空画布
     * 清除之前绘制的内容,避免重叠
     */
    ctx.clearRect(0, 0, size, size);
    
    /**
     * 第二步:绘制背景圆环(灰色底环)
     * 绘制一个完整的圆形作为背景
     */
    ctx.beginPath();  // 开始新的路径
    ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);  // 绘制完整圆形
    ctx.setStrokeStyle('#e0e0e0');  // 设置边框颜色(浅灰色)
    ctx.setLineWidth(lineWidth);     // 设置线条宽度
    ctx.setLineCap('round');         // 设置线条端点为圆形
    ctx.stroke();  // 描边绘制
    
    /**
     * 第三步:绘制进度圆环(彩色进度)
     * 根据进度百分比计算需要绘制的弧度
     */
    
    // 计算起始角度(-90 度,即 12 点钟方向)
    const startAngle = -Math.PI / 2;
    
    // 计算结束角度(根据进度百分比)
    // 进度 0% → 结束角度 = 起始角度
    // 进度 100% → 结束角度 = 起始角度 + 360 度(2π)
    const endAngle = startAngle + (progress / 100 * 2 * Math.PI);
    
    // 绘制进度圆弧
    ctx.beginPath();  // 开始新的路径
    ctx.arc(centerX, centerY, radius, startAngle, endAngle, false);  // 绘制圆弧
    ctx.setStrokeStyle('#4a90d9');  // 设置边框颜色(蓝色)
    ctx.setLineWidth(lineWidth);     // 设置线条宽度
    ctx.setLineCap('round');         // 设置线条端点为圆形
    ctx.stroke();  // 描边绘制
    
    /**
     * 第四步:提交绘制到画布
     * 将之前所有的绘制操作真正渲染到 Canvas 上
     */
    ctx.draw(false, () => {
      console.log('Canvas 绘制完成!');
    });
  },

  /**
   * 增加进度
   * 每次点击增加 10%,超过 100% 后重置为 0
   */
  increaseProgress() {
    let newProgress = this.data.currentProgress + 10;
    
    // 如果超过 100%,重置为 0
    if (newProgress > 100) {
      newProgress = 0;
    }
    
    // 更新数据并重新绘制
    this.setData({
      currentProgress: newProgress
    }, () => {
      // 数据更新完成后重新绘制圆环
      this.drawCircleProgress();
    });
  },

  /**
   * 重置进度
   * 将进度重置为 0
   */
  resetProgress() {
    this.setData({
      currentProgress: 0
    }, () => {
      // 数据更新完成后重新绘制圆环
      this.drawCircleProgress();
    });
  }
});
html 复制代码
<!--pages/demo_circle_progress/demo_circle_progress.wxml-->
<!-- 圆形进度条示例页面 -->

<view class="container">
  <view class="section">
    <text class="section-title">基础圆形进度条</text>
    
    <!-- 圆形进度条容器 -->
    <view class="progress-wrapper">
      <!-- Canvas 画布:用于绘制圆环 -->
      <canvas 
        canvas-id="progressCanvas" 
        class="progress-canvas"
        style="width: 200px; height: 200px;">
      </canvas>
      
      <!-- 中心文字内容:显示进度数值 -->
      <view class="center-text">
        <text class="progress-num">{{currentProgress}}%</text>
        <text class="progress-label">已完成</text>
      </view>
    </view>
    
    <!-- 控制按钮 -->
    <view class="control-panel">
      <button class="btn" bindtap="increaseProgress">增加进度</button>
      <button class="btn" bindtap="resetProgress">重置进度</button>
    </view>
  </view>
</view>
css 复制代码
/* pages/demo_circle_progress/demo_circle_progress.wxss */
/* 页面容器样式 */
.container {
  padding: 40rpx;
  background-color: #f5f5f5;
  min-height: 100vh;
}

/* 区块样式 */
.section {
  background-color: #ffffff;
  border-radius: 20rpx;
  padding: 40rpx;
  margin-bottom: 40rpx;
  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}

/* 区块标题样式 */
.section-title {
  font-size: 32rpx;
  font-weight: bold;
  color: #333;
  display: block;
  margin-bottom: 40rpx;
  text-align: center;
}

/* 圆形进度条外层容器 */
.progress-wrapper {
  position: relative;
  width: 200px;
  height: 200px;
  margin: 0 auto 40rpx;
}

/* Canvas 画布样式 */
.progress-canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
}

/* 中心文字容器样式 */
.center-text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  z-index: 2;
  pointer-events: none;
}

/* 进度百分比数字样式 */
.progress-num {
  font-size: 48rpx;
  font-weight: bold;
  color: #333;
  line-height: 1;
}

/* 进度标签样式 */
.progress-label {
  font-size: 24rpx;
  color: #999;
  margin-top: 10rpx;
}

/* 控制面板样式 */
.control-panel {
  display: flex;
  gap: 20rpx;
  justify-content: center;
}

/* 按钮样式 */
.btn {
  background-color: #4a90d9;
  color: #fff;
  font-size: 28rpx;
  padding: 20rpx 40rpx;
  border-radius: 10rpx;
  border: none;
}

.btn:active {
  opacity: 0.8;
}
相关推荐
好赞科技2 小时前
2026年五大精选微信小程序,革新效率体验提升智能生活品质
大数据·微信小程序
雯0609~3 小时前
微信小程序的原生开发项目如何转至uni-app
微信小程序·小程序·uni-app
好赞科技19 小时前
2026年最佳健身小程序推荐榜单,帮你解锁智能运动新体验
大数据·微信小程序
好赞科技21 小时前
026年五大汽车保养预约小程序推荐榜单,让养车更轻松省心
大数据·微信小程序
azhou的代码园1 天前
基于微信小程序的图片识别科普系统的设计与实现
vue.js·spring boot·微信小程序·小程序·毕业设计·科普·图片识别
好赞科技1 天前
深度测评2026年最佳GEO流量精准获客工具排行榜,解锁你的营销新高度
大数据·微信小程序
深邃的眼1 天前
微信小程序从 0-1:从本地开发到部署服务器上线整体流程保姆式教学
阿里云·微信小程序·个人开发
喜欢南方姑娘1 天前
微信小程序热更新-用户打开小程序时检测版本自动更新
微信小程序·小程序·notepad++
一叶星殇1 天前
高颜值微信小程序 UI 组件库大盘点,助你轻松开发!
微信小程序·小程序