CircleProgress
17:06Description
A circular progress ring that expresses a 0–100 progress value through the arc length of the ring. It is drawn with canvas 2d on all platforms (the track arc and the progress arc share one canvas), animates smoothly according to duration when percent changes, and uses the default slot for custom content in the center of the ring (such as the percentage text).
Compatibility
| Platform | Support |
|---|---|
| App | ✅ Supported |
| H5 | ✅ Supported |
| WeChat Mini Program | ✅ Supported |
Examples
basic
Pass percent to set the progress value, and use the default slot to place the percentage text in the center of the ring.
View source
<template>
<view class="wrap">
<ux-circle-progress :percent="65" type="primary">
<text class="percent">65%</text>
</ux-circle-progress>
</view>
</template>
<style scoped>
.wrap {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 20px 0;
}
.percent {
font-size: 20px;
font-weight: bold;
color: #303133;
}
</style>type
Configure the five themes primary / success / warning / error / info via type; once set, active-color is ignored.
View source
<template>
<view class="row">
<view class="item" v-for="(item, index) in typeList" :key="index">
<ux-circle-progress :percent="70" :type="item.type" :width="80">
<text class="percent-small">70%</text>
</ux-circle-progress>
</view>
</view>
</template>
<script setup lang="uts">
const typeList = [
{ type: 'primary', label: 'primary' },
{ type: 'success', label: 'success' },
{ type: 'warning', label: 'warning' },
{ type: 'error', label: 'error' },
{ type: 'info', label: 'info' }
];
</script>
<style scoped>
.row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
padding: 10px 0 5px;
}
.item {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 15px;
padding: 0 12px;
}
.percent-small {
font-size: 13px;
font-weight: bold;
color: #303133;
}
</style>size
Set the overall ring width via width (height equals width) and the ring line width via border-width, both in px.
View source
<template>
<view class="row">
<view class="item">
<ux-circle-progress :percent="60" type="primary" :width="70" :border-width="4">
<text class="percent-small">60%</text>
</ux-circle-progress>
<text class="label">140</text>
</view>
<view class="item">
<ux-circle-progress :percent="60" type="primary" :width="100" :border-width="7">
<text class="percent">60%</text>
</ux-circle-progress>
<text class="label">200</text>
</view>
<view class="item">
<ux-circle-progress :percent="60" type="primary" :width="120" :border-width="12">
<text class="percent-small">60%</text>
</ux-circle-progress>
<text class="label">240</text>
</view>
</view>
</template>
<style scoped>
.row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
padding: 10px 0 5px;
}
.item {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 15px;
padding: 0 12px;
}
.percent {
font-size: 20px;
font-weight: bold;
color: #303133;
}
.percent-small {
font-size: 13px;
font-weight: bold;
color: #303133;
}
.label {
margin-top: 6px;
font-size: 12px;
color: #c0c4cc;
}
</style>dynamic
When percent is reactive, changes redraw the ring with animation; the demo increments and decrements it with buttons.
View source
<template>
<view class="wrap">
<ux-circle-progress :percent="percent" type="success">
<text class="percent">{{ percent + '%' }}</text>
</ux-circle-progress>
</view>
<view class="btn-row">
<view class="btn" @click="reduce">
<text class="btn-text">-</text>
</view>
<text class="btn-value">{{ percent + '%' }}</text>
<view class="btn" @click="add">
<text class="btn-text">+</text>
</view>
</view>
</template>
<script setup lang="uts">
const percent = ref<number>(65);
function add(): void {
percent.value = Math.min(percent.value + 10, 100);
}
function reduce(): void {
percent.value = Math.max(percent.value - 10, 0);
}
</script>
<style scoped>
.wrap {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 20px 0;
}
.percent {
font-size: 20px;
font-weight: bold;
color: #303133;
}
.btn-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-top: 10px;
}
.btn {
width: 40px;
height: 40px;
border-radius: 40px;
background-color: #2979ff;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.btn-text {
font-size: 22px;
color: #ffffff;
line-height: 22px;
}
.btn-value {
font-size: 18px;
font-weight: bold;
color: #303133;
margin: 0 20px;
min-width: 60px;
text-align: center;
}
</style>custom
Customize the active and track colors via active-color / inactive-color, set the component background via bg-color, and control the full-circle animation duration via duration.
View source
<template>
<view class="wrap">
<ux-circle-progress
:percent="percent"
:active-color="activeColor"
:inactive-color="inactiveColor"
:bg-color="bgColor"
:duration="duration"
>
<text class="percent">{{ percent + '%' }}</text>
</ux-circle-progress>
</view>
<view class="btn-row">
<view class="btn" @click="reduce">
<text class="btn-text">-</text>
</view>
<text class="btn-value">{{ percent + '%' }}</text>
<view class="btn" @click="add">
<text class="btn-text">+</text>
</view>
</view>
</template>
<script setup lang="uts">
const percent = ref<number>(80);
const activeColor = '#19be6b';
const inactiveColor = '#ebeef5';
const bgColor = '#f4f6f8';
const duration = 2000;
function add(): void {
percent.value = Math.min(percent.value + 10, 100);
}
function reduce(): void {
percent.value = Math.max(percent.value - 10, 0);
}
</script>
<style scoped>
.wrap {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 20px 0;
}
.percent {
font-size: 20px;
font-weight: bold;
color: #303133;
}
.btn-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-top: 10px;
}
.btn {
width: 40px;
height: 40px;
border-radius: 40px;
background-color: #19be6b;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.btn-text {
font-size: 22px;
color: #ffffff;
line-height: 22px;
}
.btn-value {
font-size: 18px;
font-weight: bold;
color: #303133;
margin: 0 20px;
min-width: 60px;
text-align: center;
}
</style>API
Props
| Property | Description | Type | Default | Version |
|---|---|---|---|---|
customStyle | Custom root element style | UTSJSONObject | - | - |
customClass | Custom root element style class | string | '' | - |
percent | Ring progress percentage, 0-100 | number | 0 | - |
inactiveColor | Track (background) color of the ring | string | '' | - |
activeColor | Color of the ring's active portion | string | '' | - |
width | Width of the whole ring component in px (height equals width) | number | string | 100 | - |
borderWidth | Ring line (border) width in px | number | string | 7 | - |
duration | Time to draw one full ring, in ms | number | string | 1500 | - |
type | Theme type | string | '' | - |
bgColor | Background color of the whole component | string | '' | - |
Events
ux-circle-progress has no events.
Slots
| Slot | Description | Version |
|---|---|---|
default | Content in the center of the ring (usually the percentage text), centered via absolute positioning | - |
Notes
percentis clamped to 0–100 before drawing, so out-of-range values never overflow.typetakes precedence overactiveColor: oncetypeis set,activeColoris ignored; when neither is set it falls back to the themesuccesscolor.- The component is square (
heightequalswidth) and the ring radius iswidth / 2 - borderWidth; make sureborderWidthis smaller thanwidth / 2, otherwise the ring degenerates. - The ring is drawn with canvas 2d, so the center content must be provided through the default slot; the component does not generate the percentage text itself (unlike the linear progress bar).
durationis the time to draw one full circle; the actual animation time is proportional to this update's progress delta (for example, adding 5% takes aboutduration / 100 × 5ms), not always the fullduration.bgColoris transparent by default and only applied to the whole component when explicitly passed (to avoid a solid block around the ring).- The canvas context is obtained asynchronously via
uni.createCanvasContextAsync, so the first render animates the progress from 0 to the target value.