SVG画个渐变进度甜甜圈
需求场景
经常画图的同学应该都知道,ECharts不仅会受版本影响而且各种类型的图表配置还些许不同,更何况版本不一时配置也会存在天差地别的可能,一想到这~~~就非常的让人秃然 ,阿布!秃头 😒😒😒。
于是不想使用ECharts图表,但又要实现评分、环比等进度环的图形样式,而且还需要增加过渡动效。起始点、、执行方向根据需求自行定义即可,废话少说,直接上图👇。
图例
代码分析
一、svg元素
javascript
<svg width="21vw" height="21vw" viewBox="0 0 280 280" preserveAspectRatio="xMidYMid meet" class="progress-ring-svg">
1. width / height: 设置SVG的宽度和高度,这里使用了相对单位vw,使其在不同设备上具有自适应的特性。
2. viewBox: 定义了可视区域,其格式为viewBox="minX minY width height"。在这里,它指定了一个280x280的坐标系统,SVG内容将根据这个区域进行缩放。
3. preserveAspectRatio: 设置图形在缩放时如何保持纵横比。xMidYMid meet表示图形在水平和垂直方向上居中,并保持原始比例。
二、定义渐变
javascript
<defs>
<linearGradient id="progressGradient" x1="0%" y1="0%" x2="100%" y2="100%" style="transform: rotate(-35deg)">
<stop offset="20.74%" style="stop-color: rgb(247, 121, 111); stop-opacity: 1"></stop>
<stop offset="54.15%" style="stop-color: rgb(201, 98, 230); stop-opacity: 1"></stop>
<stop offset="82.63%" style="stop-color: rgb(89, 89, 244); stop-opacity: 1"></stop>
</linearGradient>
</defs>
1. defs: 用于定义可复用的元素,如渐变、图形等。
2. linearGradient: 定义线性渐变,id是渐变的标识符,后面可以通过url(#progressGradient)引用它。
3. x1, y1, x2, y2: 定义渐变的起始和结束坐标(百分比)。
4. transform: 旋转渐变的方向。
5. stop: 定义渐变的颜色和透明度。
6. offset: 指定渐变中颜色的起始位置(相对于渐变的长度)。
7. stop-color: 设置颜色。
8. stop-opacity: 设置透明度。
三、圆形元素
javascript
<circle stroke="transparent" stroke-width="39" fill="transparent" r="119" cx="139" cy="141" class="progress-ring-circle"></circle>
1. stroke: 设置圆的边框颜色,这里是透明的。
2. stroke-width: 设置边框的宽度。
3. fill: 设置填充颜色,这里是透明的。
4. r: 圆的半径。
5. cx, cy: 圆心的坐标。
6. class: 用于样式和脚本的选择器。
四、进度圆
javascript
<circle stroke="url(#progressGradient)" stroke-width="39" fill="transparent" r="119" cx="140" cy="141.5" class="progress-ring-circle progress" style="stroke-dasharray: 904.699; stroke-dashoffset: 1436.004; stroke-linecap: round; transition: stroke-dashoffset 1s ease;"></circle>
1. stroke: 设置边框颜色为之前定义的渐变。
2. stroke-width: 边框的宽度,与背景圆相同。
3. r, cx, cy: 同样设置圆的半径和圆心坐标。
4. stroke-dasharray: 定义了虚线的模式,这里是一个完整的圆周长(周长根据设定的radius值进行动态计算>>2 *
Math.PI * radius),用于实现进度显示。
5. stroke-dashoffset: 定义了虚线的偏移量,控制可见部分的长度。该属性的设置和动画效果是关键要素,这些决定了动画的显示效果及其方向。如果其值较大(如747.699),将使得圆环的绘制方向呈现逆时针效果,模拟一种"从后往前"的绘制过程。
6. stroke-linecap: 设置圆角样式,这里为round,使得圆的端点为圆形。
7. transition: 设置动画效果,当stroke-dashoffset变化时,动画持续时间为1秒,使用ease过渡效果。
8. offset: 这是一个初始值设置,表示进度圆当前的stroke-dashoffset。这个值是圆环周长的一部分,用于控制可见部分的长度。
9. 逆时针动画: 如果stroke-dashoffset的初始值设置为零,则在动画过程中,圆环的进度将从圆环的起始位置(通常是顶部)开始向前(顺时针)绘制。但是,通过将初始offset设置为一个正值(如747.699),可以使进度在动画开始时向反方向(逆时针)延伸,从而避免在默认状态下看到未绘制的部分。
示例代码
javascript
<!--
简要总结viewBox的作用:
定义可视区域:viewBox属性的格式为viewBox="minX minY width height",它确定了SVG内容的可视区域和比例。
保证缩放:当你改变SVG的宽高时,viewBox确保图形在缩放时不会失去比例或被裁剪。
适应不同设备:使用preserveAspectRatio属性配合viewBox,能够让SVG在各种设备上自适应显示。
-->
<!--
<svg :width="h_w_" :height="h_w_" class="progress-ring-svg" style="transform: scaleX(-1) rotate(275deg);"
viewBox="0 0 280 280" preserveAspectRatio="xMidYMid meet">
<defs>
<linearGradient id="progressGradient" x1="0%" y1="0%" x2="100%" y2="100%"
style="transform: rotate(-35deg);">
<stop offset="20.74%" style="stop-color: #F7796F; stop-opacity: 1;"></stop>
<stop offset="54.15%" style="stop-color: #C962E6; stop-opacity: 1;"></stop>
<stop offset="82.63%" style="stop-color: #5959F4; stop-opacity: 1;"></stop>
</linearGradient>
</defs>
<circle stroke="transparent" :stroke-width="strokeWidth" fill="transparent" :r="radius" :cx="cx" :cy="cy"
class="progress-ring-circle"></circle>
<circle :stroke="'url(#progressGradient)'" :stroke-width="strokeWidth" fill="transparent" :r="radius"
:cx="cx" :cy="cy" class="progress-ring-circle progress"
:style="{ strokeDasharray: circumference, strokeDashoffset: offset, strokeLinecap: 'round' }">
</circle>
</svg>
-->
<div class="I-KUN">
<div class="progress-ring">
<svg
width="21vw"
height="21vw"
viewBox="0 0 280 280"
preserveAspectRatio="xMidYMid meet"
class="progress-ring-svg"
>
<defs>
<linearGradient
id="progressGradient"
x1="0%"
y1="0%"
x2="100%"
y2="100%"
style="transform: rotate(-35deg)"
>
<stop
offset="20.74%"
style="stop-color: rgb(247, 121, 111); stop-opacity: 1"
></stop>
<stop
offset="54.15%"
style="stop-color: rgb(201, 98, 230); stop-opacity: 1"
></stop>
<stop
offset="82.63%"
style="stop-color: rgb(89, 89, 244); stop-opacity: 1"
></stop>
</linearGradient>
</defs>
<circle
stroke="transparent"
stroke-width="39"
fill="transparent"
r="119"
cx="139"
cy="141"
class="progress-ring-circle"
></circle>
<circle
stroke="url(#progressGradient)"
stroke-width="39"
fill="transparent"
r="119"
cx="140"
cy="141.5"
class="progress-ring-circle progress"
style="
stroke-dasharray: 904.699;
stroke-dashoffset: 1436.004;
stroke-linecap: round;
transition: stroke-dashoffset 1s ease;
"
></circle>
</svg>
</div>
<div class="contet">
<div class="title">2.5<span class="K">KunBi</span></div>
<div class="desc opacity_half">练习时长两年半的前端</div>
</div>
<div class="tooltip">
<div class="row_1">
<span class="img_name"><span class="name">唱跳Rap篮球🏀</span></span
><span class="num">2.5<span class="P">Kun</span> </span>
</div>
<div class="row_2">
<span class="img_name"> <span class="name">你小子露鸡爪🤭</span></span
><span class="num">2.5<span class="P">Kun</span> </span>
</div>
</div>
</div>
data() { return { percentage: 0, r:119, cx: 139, cy: 140, offset: 747.699, //
默认赋周长值,否则 出现逆时针动画效果 percent: 0, foldableScreen:
foldableScreen(), h_w_: foldableScreen() ? '21vw' : '71.822667vw' } },
<style lang="scss">
.I-KUN {
width: 390px;
height: 390px;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(
98.51deg,
rgb(175 164 230 / 34%) 1.35%,
rgb(152 50 170 / 6%) 103.86%
);
}
.progress-ring {
position: absolute;
}
.progress-ring-circle {
transition: stroke-dashoffset 1s ease;
}
.contet {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
position: absolute;
width: 15vw;
height: 15vw;
border-radius: 50%;
background: #ffffff;
box-shadow: 1px 1px 38px #baadd8;
}
.title {
font-family: "PingFang SC";
font-size: 36.67px;
font-weight: 600;
line-height: 36.67px;
}
.K {
margin-left: 5px;
font-size: 22px;
line-height: 22px;
}
.desc {
margin-top: 9.6px;
font-family: "Alibaba PuHuiTi 2.0";
font-size: 13.67px;
font-weight: 400;
line-height: 13.93px;
letter-spacing: 0.01em;
color: #909090;
}
.tooltip {
padding: 18px 18px 18px 20px;
backdrop-filter: blur(18px);
box-shadow: 1.33px 1.33px 0px 0px #ffffff99 inset;
background: linear-gradient(
98.51deg,
rgba(248, 247, 253, 0.48) 1.35%,
rgba(248, 247, 253, 0.8) 103.86%
);
min-width: 260.33px;
height: auto;
border-radius: 20px;
position: absolute;
bottom: 1vw;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.row_1,
.row_2 {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
font-family: "Alibaba PuHuiTi 2.0";
font-size: 20px;
font-weight: 400;
line-height: 20.14px;
margin-bottom: 13.33px;
}
.row_2 {
margin-bottom: 0;
}
.name {
max-width: 30vw;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.num {
font-size: 18px;
font-weight: 700;
line-height: 20.14px;
}
.P {
font-size: 12px;
font-weight: 700;
line-height: 12px;
margin-left: 3px;
color: #a0a0a0;
}
</style>