Skip to content

CircleProgress ​

17:06

Description ​

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 ​

PlatformSupport
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
vue
<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
vue
<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
vue
<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
vue
<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
vue
<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 ​

PropertyDescriptionTypeDefaultVersion
customStyleCustom root element styleUTSJSONObject--
customClassCustom root element style classstring''-
percentRing progress percentage, 0-100number0-
inactiveColorTrack (background) color of the ringstring''-
activeColorColor of the ring's active portionstring''-
widthWidth of the whole ring component in px (height equals width)number | string100-
borderWidthRing line (border) width in pxnumber | string7-
durationTime to draw one full ring, in msnumber | string1500-
typeTheme typestring''-
bgColorBackground color of the whole componentstring''-

Events ​

ux-circle-progress has no events.

Slots ​

SlotDescriptionVersion
defaultContent in the center of the ring (usually the percentage text), centered via absolute positioning-

Notes ​

  1. percent is clamped to 0–100 before drawing, so out-of-range values never overflow.
  2. type takes precedence over activeColor: once type is set, activeColor is ignored; when neither is set it falls back to the theme success color.
  3. The component is square (height equals width) and the ring radius is width / 2 - borderWidth; make sure borderWidth is smaller than width / 2, otherwise the ring degenerates.
  4. 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).
  5. duration is the time to draw one full circle; the actual animation time is proportional to this update's progress delta (for example, adding 5% takes about duration / 100 × 5 ms), not always the full duration.
  6. bgColor is transparent by default and only applied to the whole component when explicitly passed (to avoid a solid block around the ring).
  7. The canvas context is obtained asynchronously via uni.createCanvasContextAsync, so the first render animates the progress from 0 to the target value.