环形加载进度条怎么在Vue里实现动态旋转?

爱娜~ 阅读 21

我用Vue做了一个环形加载动画,但转起来特别卡,而且方向不对,明明写了顺时针却逆着转。是不是transform-origin没设对?

试过用CSS animation配合transition,也试过直接改style,都不太行。下面是我现在用的代码:

<template>
  <div class="loading-ring" :style="{ transform: rotate(${angle}deg) }"></div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
const angle = ref(0)
onMounted(() => {
  setInterval(() => angle.value += 6, 100)
})
</script>
我来解答 赞 2 收藏
二维码
手机扫码查看
1 条解答
晶晶 Dev
你的代码有几个问题,我逐个说。

问题一:模板字符串语法错误

你的 :style 写法有问题,Vue 的绑定语法不能直接嵌套模板字符串。应该这样写:

<div class="loading-ring" :style="{ transform: rotate(${angle}deg) }"></div>


问题二:性能卡顿的根源

用 setInterval 每 100ms 触发一次 Vue 响应式更新来改角度,这种做法太笨重了。每秒 10 次重新渲染,浏览器压力很大。环形加载动画应该直接用 CSS animation,JS 只需要控制动画的开始停止就行,完全不需要逐帧更新角度。

问题三:方向问题

纯 CSS 写 rotate(360deg) 是顺时针,负值才是逆时针。你说逆着转大概率是 transform-origin 没设对,或者你的环形是用 SVG 画的,SVG 坐标系和 CSS 坐标系方向相反。



正确的实现思路:

环形加载条有两种常见做法,我给你分别写一下:

方案一:纯 CSS + 双半圆(推荐)

这是最简单性能也最好的,用两个半圆 div 通过 animation 交替显示:

<template>
<div class="loading-container">
<div class="loading-ring">
<!-- 上面半圆 -->
<div class="ring-half top"></div>
<!-- 下面半圆 -->
<div class="ring-half bottom"></div>
</div>
</div>
</template>

<style scoped>
.loading-container {
width: 50px;
height: 50px;
position: relative;
}

.loading-ring {
width: 100%;
height: 100%;
position: relative;
}

/* 半圆基础样式 */
.ring-half {
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
border: 4px solid transparent;
border-top-color: #409eff; /* 蓝色 */
border-right-color: #409eff;
}

/* 上面半圆负责前半圈 */
.top {
animation: rotate-half-1 1s linear infinite;
}

/* 下面半圆负责后半圈 */
.bottom {
animation: rotate-half-2 1s linear infinite;
}

/* 0-180度:上面半圆转 */
@keyframes rotate-half-1 {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
100% {
transform: rotate(180deg);
}
}

/* 180-360度:下面半圆接着转 */
@keyframes rotate-half-2 {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(0deg);
}
100% {
transform: rotate(180deg);
}
}
</style>


方案二:SVG stroke-dasharray(更精确控制进度)

如果你的环形是要显示具体进度的,用 SVG 更合适:

<template>
<svg class="loading-svg" viewBox="0 0 50 50">
<!-- 背景圈 -->
<circle
class="bg-circle"
cx="25"
cy="25"
r="20"
fill="none"
stroke="#eee"
stroke-width="4"
></circle>
<!-- 进度圈 -->
<circle
class="progress-circle"
cx="25"
cy="25"
r="20"
fill="none"
stroke="#409eff"
stroke-width="4"
stroke-linecap="round"
:stroke-dasharray="circumference"
:stroke-dashoffset="dashOffset"
></circle>
</svg>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'

const radius = 20
const circumference = 2 * Math.PI * radius // 周长约 125.66

const progress = ref(0)
const dashOffset = computed(() => {
// progress 从 0 到 100,offset 从 circumference 减到 0
return circumference - (progress.value / 100) * circumference
})

let timer = null

onMounted(() => {
// 用 requestAnimationFrame 比 setInterval 流畅
const animate = () => {
progress.value = (progress.value + 1) % 100
timer = requestAnimationFrame(animate)
}
animate()
})

onUnmounted(() => {
if (timer) cancelAnimationFrame(timer)
})
</script>

<style scoped>
.loading-svg {
width: 50px;
height: 50px;
/* 这里加旋转动画让它转起来 */
animation: spin 1s linear infinite;
}

.progress-circle {
/* 关键:让旋转中心在圆心 */
transform-origin: center;
transition: stroke-dashoffset 0.1s ease;
}

@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>


关于方向的问题再说一下:

如果你用的是方案二的 SVG,记住 stroke-dashoffset 是顺时针减少的,SVG 的 y 轴方向和 CSS 不太一样。如果发现方向反了,把 stroke-dashoffset 的计算改成 progress.value / 100 * circumference 而不是减法,或者直接在 SVG 元素上加 transform: scaleX(-1) 翻转一下。

最后提醒一下,方案一完全不需要 JS 介入,纯 CSS 就能跑,性能最好。方案二如果你只需要无限循环的加载动画,也可以直接用 CSS animation 旋转 SVG,不需要 JS 计算 progress。
点赞
2026-03-11 18:10