preface

Recently, the project used the circular progress bar, so I also found some articles to see how to achieve it. Articles on the market basically describe the implementation of two rectangular rotation and canvas, if you are interested, you can find it by yourself, baidu search a lot of 😂.

However, the project I am working on is a small program (the demo is vUE version). The canvas level is very high. Although the official canvas is provided, there are a lot of bugs in the official community, and it feels heavy, so I didn’t consider it. I have tried the way of rotation of two rectangles before, and there will be almost 1px spacing at the intersection of two rectangles on the real machine, which cannot be fixed all the time, nor can I tell the design that this is the only way.

Then accidentally saw such a demo, feel open a new world, the original circle progress bar can also be implemented!!

Linear-gradient implements the circle

So let’s take a look at what we’re trying to achieve

The diagram above can be broken down into four DOM structures, which is probably clearer for youThe bottom layer is dark red circle, and the red circle in the middle is used to cover the bottom circle, which visually forms the effect of the circle. The two black circles are used to realize the progress bar of the circle with an arc

Let’s start with a deep red circle at the bottom

<div class="progress-radial"></div>
Copy the code
.progress-radial {
    position: relative;
    width: 120rpx;
    height: 120rpx;
    border-radius: 50%;
    background-image: linear-gradient(90deg.#C40006 %, #FECC1C);
    line-height: normal;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1;
}
Copy the code

And then we get a circle that looks like thisOf course, this is definitely not the effect we want, we do not need this transition effect, so we can modify the nodes of Linear-gradient to make the nodes overlap to form a clear line

background-image: linear-gradient(90deg.#C40006 50%.#FECC1C 50%.#FECC1C);
Copy the code

Then we have a circle with two colors divided in half and no transition colorsIf we use yellow as the current progress, we can add a mask layer to the circle and get a 50% progress bar.

<div class="progress-radial">
	<div class="progress-text"></div>
</div>
Copy the code
.progress-radial {
    position: relative;
    width: 120rpx;
    height: 120rpx;
    border-radius: 50%;
    background-image: linear-gradient(90deg.#C40006 50%.#FECC1C 50%.#FECC1C);
    line-height: normal;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1;
}

.progress-radial .progress-text {
    width: 88rpx;
    height: 88rpx;
    background: #ED1937;
    border-radius: 50%;
}
Copy the code

Although… We got this 50% circle, but the progress can’t always be 50% 😭, we have to calculate the progress based on actual data, that is, we have to make the progress bar move.

So how do we get the progress bar moving

Assume that we first move the Linear-gradient Angle and make Angle = 145deg to get the following situationAlthough the progress is moving, the starting point of the progress bar is not on 90DEg. Is there any way that the starting point of the progress bar is on 90DEg, but the progress bar can still move?

The answer is definitely yes. We imagine this circle as the upper and lower parts. When the progress is 50% short, the upper and lower parts are as shown in the figure below

FIG. 1 Figure 2 Overlay figure 1 on top of Figure 2 to get an all-red circle

.progress-radial {
    position: relative;
    width: 120px;
    height: 120px;
    border-radius: 50%;
    background-image: linear-gradient(90deg.#C40006 50%, transparent 50%, transparent) , linear-gradient(90deg.#FECC1C 50%.#C40006 50%.#C40006);
    line-height: normal;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1;
    margin: 0 auto
}
Copy the code

Then we modified the Angle of Linear-gradient in Figure 2 to achieve the effect of modifying the progress. For example, we tried to change the Angle to 120deg. In fact, we can imagine that the following effect can be achieved after turning the above figure 2 by 30 degrees

.progress-radial {
    position: relative;
    width: 120px;
    height: 120px;
    border-radius: 50%;
    background-image: linear-gradient(90deg.#C40006 50%, transparent 50%, transparent) , linear-gradient(120deg.#FECC1C 50%.#C40006 50%.#C40006);
    line-height: normal;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1;
    margin: 0 auto
}
Copy the code

When the progress was greater than 50%, Figure 2 shifted to 270DEg, and the previous half-cut style was obtained. At this time, we changed Linear-gradient into a style greater than 50%, and the upper and lower cuts were respectively

FIG. 1 Figure 2 Again, overlay figure 1 on top of figure 2 to get a 50% circle, and then we rotate figure 1

.progress-radial {
    position: relative;
    width: 120px;
    height: 120px;
    border-radius: 50%;
    background-image: linear-gradient(-90deg.#FECC1C 50%, transparent 50%, transparent) , linear-gradient(270deg.#FECC1C 50%.#C40006 50%.#C40006);
    line-height: normal;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1;
    margin: 0 auto
}
Copy the code

Then we can modify the Angle of Linear-gradient in the first part of the figure to achieve the effect of modifying the progress. For example, we try to change the Angle to -60deg. It can be imagined that the following effect will be obtained after the above figure 1 is rotated by 30 degreesIn this way, we have achieved a circle that can achieve any percentage, and then we can directly combine the above two cases according to the data to complete the dynamic calculation of the progress of the circle

<template> <div id="app"> <div class="progress-radial" :style="point <= 50 ? 'background-image: linear-gradient(90deg, #C40006 50%, transparent 50%, transparent), linear-gradient('+deg+'deg, #FECC1C 50%, #C40006 50%, #C40006); ' : 'background-image: linear-gradient('+deg+'deg, #FECC1C 50%, transparent 50%, transparent), linear-gradient(270deg, #FECC1C 50%, #C40006 50%, #C40006)'"> <div class="progress-text"></div> </div> </div> </template> <script> export default { name: "App", data() { return { deg: 0, point: If (totalCount, totalCount, totalCount) = {finishCount: 1, totalCount: 3,}; if (totalCount, totalCount, totalCount) = {finishCount: 1, totalCount: 3,}; Math.floor((finishCount/totalCount) * 10000) / 100; // Let point = math.floor ((finishCount/totalCount) * 10000) / 100; let deg = 0; If (point <= 50) {deg = math. round(90 + point * 3.6); // Math.round(90 + point * 3.6); } else {deg = math.round (-90 + (point-50) * 3.6); } this.deg = deg this.point = point }, };Copy the code

At this point we are done with the effect of changing finishCount

Transform-origin implements head and tail arcs

The starting circle is always at the top, so as long as it is positioned well, the ending circle needs to calculate the real progress, and then rotate to the right position. Transform-origin is used to rotate the black circle to the center of the red circle. With rotate, a small black circle can be rotated around the edge of the circle. Then, the starting point of deG greater than 50 and deG less than 50 calculated above is different, so we also need to deal with this value. The final code and renderings are shown below

<template> <div id="app"> <input v-model="finishCount" @change="draw" type="number" class="input" /> <div class="progress-radial" :style=" point <= 50 ? 'background-image: linear-gradient(90deg, #C40006 50%, transparent 50%, transparent), linear-gradient(' + deg + 'deg, #FECC1C 50%, #C40006 50%, #C40006); ' : 'background-image: linear-gradient(' + deg + 'deg, #FECC1C 50%, transparent 50%, transparent), linear-gradient(270deg, #FECC1C 50%, #C40006 50%, #C40006)' " > <div class="progress-text"></div> <div class="progress-radial-end" :style="'transform: rotate('+(point <= 50 ? deg-90 : 270+deg)+'deg)'" ></div> <div class="progress-radial-start"></div> </div> </div> </template> <script> export default { name: "App", data() { return { finishCount: 1, totalCount: 3, deg: 0, point: 0, }; }, mounted() { this.draw(); }, methods: { draw() { let point = Math.floor((this.finishCount / this.totalCount) * 10000) / 100; let deg = 0; If (point <= 50) {deg = math.round (90 + point * 3.6); } else {deg = math.round (-90 + (point-50) * 3.6); } this.deg = deg; this.point = point; ,}}}; </script> <style> .input { margin: 30px auto; display: block; } .progress-radial { position: relative; width: 120px; height: 120px; border-radius: 50%; background-image: linear-gradient( 90deg, #c40006 50%, transparent 50%, transparent ), linear-gradient(90deg, #fecc1c 50%, #c40006 50%, #c40006); line-height: normal; display: flex; align-items: center; justify-content: center; z-index: 1; margin: 0 auto; } .progress-radial-end, .progress-radial-start { width: 16px; height: 16px; top: 0; left: 52rpx; border-radius: 50%; background: #000; position: absolute; z-index: 999; line-height: normal; transform-origin: center 60px; } .progress-radial .progress-text { width: 88px; height: 88px; background: #ed1937; border-radius: 50%; color: #fff; font-size: 28px; text-align: center; line-height: 88px; font-weight: bold; } </style>Copy the code

Reference address Document and Demo address

  • github
  • You really understand Linear-gradient of CSS

**