功能:
<template>
<div class="wrapper" :style="{ width: width, height: height }">
<div class="progress">
<div
class="bg"
:style="{
width: curProgress,
background: percent >= 100 ? '#00EE00' : background,
backgroundSize: backgroundSize,
}"
></div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, onBeforeUnmount } from "vue";
const props = defineProps({
width: {
type: Number, // 进度条总宽度
default: 300,
},
height: {
type: Number, // 进度条高度
default: 10,
},
percent: {
type: Number, // 当前进度
default: 50,
},
stripeAngle: {
type: Number, // 进度条线的角度 和-x的夹角,顺时针
default: 45,
},
stripe: {
type: Array, // 进度条斑马线数组
default: () => [
{
color: "#FF4500",
width: 15,
},
{
color: "#00EE00",
width: 15,
},
{
color: "#00FFFF",
width: 15,
},
],
},
gradient: {
type: Boolean, // 渐变
default: false,
},
duration: {
type: Number, //动画时间
default: 2,
},
animation: {
type: String,
default: "move",
},
});
const width = computed(() => {
return props.width ? `${props.width}px` : "100%";
});
const height = computed(() => {
return props.height ? `${props.height}px` : "100%";
});
const curProgress = computed(() => {
return (props.percent >= 100 ? 100 : props.percent) + "%";
});
const background = computed(() => {
const positions = [];
const stripeCopy = JSON.parse(JSON.stringify(props.stripe));
if (props.gradient) {
stripeCopy.push(stripeCopy[0]);
}
stripeCopy.forEach((s, index) => {
const sum = stripeCopy.slice(0, index).reduce((a, b) => a + b.width, 0);
if (!props.gradient) {
positions.push(sum);
}
positions.push(sum + s.width);
});
return `repeating-linear-gradient(
${props.stripeAngle}deg, ${stripeCopy
.map((s, index) => {
if (!props.gradient) {
return `${s.color} ${positions[index]}px, ${s.color} ${
positions[2 * index + 1]
}px`;
}
return `${s.color} ${positions[index]}px`;
})
.join(",")})`;
});
const totalStripeWidth = computed(() => {
return props.stripe.reduce((a, b) => a + b.width, 0);
});
const backgroundSize = computed(() => {
let size = "";
if (props.stripeAngle === 0) {
size = "";
}
if (props.stripeAngle % 90 === 0) {
size = `${totalStripeWidth.value}px ${props.height}px`;
} else {
size = `${
totalStripeWidth.value / Math.sin((props.stripeAngle * Math.PI) / 180)
}px ${props.height}px`;
}
return size;
});
onMounted(() => {
initAnimation();
});
onBeforeUnmount(() => {
document.styleSheets[0].deleteRule(0);
document.styleSheets[0].deleteRule(1);
});
const initAnimation = () => {
document.styleSheets[0].insertRule(
`@keyframes move{
from {
background-position: 0 0;
}
to {
background-position: ${
totalStripeWidth.value /
Math.sin((props.stripeAngle * Math.PI) / 180)
}px 0;
}
}`,
0
);
document.styleSheets[0].insertRule(
`.bg {
animation: ${props.animation} ${props.duration}s linear infinite
}`,
1
);
};
</script>
<style lang="scss" scoped>
.wrapper {
width: 100%;
position: relative;
display: flex;
align-items: center;
.progress {
height: 100%;
display: inline-block;
width: 100%;
background: #c7c7c7;
border-radius: 100px;
display: flex;
justify-content: space-between;
.bg {
height: 100%;
text-align: center;
border-radius: 100px;
transition: all 0.3s cubic-bezier(0.08, 0.82, 0.17, 1);
}
}
}
.percent {
font-size: 14px;
line-height: 24px;
font-weight: bold;
color: rgba(0, 0, 0, 0.45);
}
</style>
动图(省略)
<template>
<div class="wrapper" :style="{ width: width, height: height }">
<slot name="prefix">
<span v-if="prefix" class="prefix">
{{ prefix }}
</span>
</slot>
<div class="progress">
<div
class="bg"
:style="{
width: curProgress,
background: percent >= 100 ? '#00EE00' : background,
backgroundSize: backgroundSize,
}"
>
<span
v-if="position === 3 && percent >= 10"
class="percent"
:style="{
fontSize: height,
lineHeight: height,
color: 'rgba(0, 0, 0, 0.8)',
}"
>
{{ curProgress }}
</span>
</div>
<span
v-if="position === 2 || percent < 10"
class="percent"
:style="{
fontSize: height,
lineHeight: height,
marginRight: '10px',
}"
>
{{ curProgress }}
</span>
</div>
<span
v-if="position === 1"
class="percent"
:style="{
marginLeft: '10px',
}"
>
{{ curProgress }}
</span>
<slot name="suffix">
<span v-if="suffix" class="suffix">
{{ suffix }}
</span>
</slot>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, onBeforeUnmount } from "vue";
const props = defineProps({
width: {
type: Number, // 进度条总宽度
default: 300,
},
height: {
type: Number, // 进度条高度
default: 10,
},
percent: {
type: Number, // 当前进度
default: 50,
},
stripeAngle: {
type: Number, // 进度条线的角度 和-x的夹角,顺时针
default: 45,
},
stripe: {
type: Array, // 进度条斑马线数组
default: () => [
{
color: "#FF4500",
width: 15,
},
{
color: "#00EE00",
width: 15,
},
{
color: "#00FFFF",
width: 15,
},
],
},
gradient: {
type: Boolean, // 渐变
default: false,
},
prefix: {
type: String, //前缀
default: "",
},
suffix: {
type: String, //后缀
default: "",
},
position: {
type: Number, // 百分比位置 0 不显示 1 外部 2 内部 3 进度条上
default: 1,
},
duration: {
type: Number, //动画时间
default: 2,
},
animation: {
type: String,
default: "move",
},
});
const width = computed(() => {
return props.width ? `${props.width}px` : "100%";
});
const height = computed(() => {
return props.height ? `${props.height}px` : "100%";
});
const curProgress = computed(() => {
return (props.percent >= 100 ? 100 : props.percent) + "%";
});
const background = computed(() => {
const positions = [];
const stripeCopy = JSON.parse(JSON.stringify(props.stripe));
if (props.gradient) {
stripeCopy.push(stripeCopy[0]);
}
stripeCopy.forEach((s, index) => {
const sum = stripeCopy.slice(0, index).reduce((a, b) => a + b.width, 0);
if (!props.gradient) {
positions.push(sum);
}
positions.push(sum + s.width);
});
return `repeating-linear-gradient(
${props.stripeAngle}deg, ${stripeCopy
.map((s, index) => {
if (!props.gradient) {
return `${s.color} ${positions[index]}px, ${s.color} ${
positions[2 * index + 1]
}px`;
}
return `${s.color} ${positions[index]}px`;
})
.join(",")})`;
});
const totalStripeWidth = computed(() => {
return props.stripe.reduce((a, b) => a + b.width, 0);
});
const backgroundSize = computed(() => {
let size = "";
if (props.stripeAngle === 0) {
size = "";
}
if (props.stripeAngle % 90 === 0) {
size = `${totalStripeWidth.value}px ${props.height}px`;
} else {
size = `${
totalStripeWidth.value / Math.sin((props.stripeAngle * Math.PI) / 180)
}px ${props.height}px`;
}
return size;
});
onMounted(() => {
initAnimation();
});
onBeforeUnmount(() => {
document.styleSheets[0].deleteRule(0);
document.styleSheets[0].deleteRule(1);
});
const initAnimation = () => {
document.styleSheets[0].insertRule(
`@keyframes move{
from {
background-position: 0 0;
}
to {
background-position: ${
totalStripeWidth.value /
Math.sin((props.stripeAngle * Math.PI) / 180)
}px 0;
}
}`,
0
);
document.styleSheets[0].insertRule(
`.bg {
animation: ${props.animation} ${props.duration}s linear infinite
}`,
1
);
};
</script>
<style lang="scss" scoped>
.wrapper {
width: 100%;
position: relative;
display: flex;
align-items: center;
.prefix,
.suffix {
font-size: 14px;
color: #000;
position: absolute;
left: -70px;
}
.progress {
height: 100%;
display: inline-block;
width: 100%;
background: #c7c7c7;
border-radius: 100px;
display: flex;
justify-content: space-between;
.bg {
height: 100%;
text-align: center;
border-radius: 100px;
transition: all 0.3s cubic-bezier(0.08, 0.82, 0.17, 1);
}
}
}
.percent {
font-size: 14px;
line-height: 24px;
font-weight: bold;
color: rgba(0, 0, 0, 0.45);
}
</style>
默认效果:
改变斑马线角度:
改变斑马线宽度:
开启渐变:
改变百分比位置:
动图(省略)