How do I draw a small circle at the end tip of a progress circle, plus adding a small text block below / above it?
Example img:
<div class="radial-progress" data-progress="0">
<div class="circle">
<div class="img"></div>
<div class="mask full">
<div class="fill"></div>
</div>
<div class="mask half">
<div class="fill"></div>
<div class="fill fix"></div>
</div>
<div class="shadow"></div>
</div>
<div class="inset">
<div class="percentage">
<div class="numbers"><span>-</span><span>0%</span><span>1%</span> <!--- lots of spans --->
</div>
</div>
</div>
Thanks to Andre's medium.com article, I've already edited his version a little - an UPDATED version illustrates what I want to achieve dynamically for a given % value: http://codepen.io/Inlesco/pen/pgKXeG
However, the compiled CSS is way too much. For each % out of 100, there'll be too much CSS for positioning things. The current CSS (@codepen example) already weights ~50 KB. Not the best practice.
I've already done some progress on a JS Canvas-based variant of this, positioning the vert&horz centered img above the canvas. But is it really the only way and best practice for a good-looking responsive website?
CSS is definitely not the right tool for doing this and I would urge you to give up on that idea. Canvas is definitely a good option but for a responsive website, I would recommend you to use SVG. With SVG, it is easy to draw the progress circle based on user input and also add the circle/dot at the tip of it.
The following are the steps that would have to be performed to first create the progress circle:
- Get the user input for the progress circle (0 to 100) and then calculate the arc's angle based on it. The formula would be (input * 360/100) because a circle has 360 degrees.
- SVG arcs generally start from the 3's position in a clock and so the calculated angle of arc has to be offset by 90 degree in the negative. That is, the arc has to be from -90 degree to 270 degree.
- Convert the calculated angle into radians using the formula - (Angle in Degrees * PI / 180).
Once the angle in radians is calculated, find a point on the circle based on the angle using simple trigonometric functions:
- X coordinate = Cos(Angle in Radians) * Radius + X coordinate of center point.
- Y coordinate = Sin(Angle in Radians) * Radius + Y coordinate of center point.
Once the point is found, we need to create the path such that it starts at the point that was found in the previous step, goes to the center of circle, then to the starting point of the arc and from that point draw the arc again to the original point. The path is created this way because we need the path to end at the required point (because we will be attaching the marker to the end point).
- One thing to note with arc creation is that any angle > 180 degree would need two arc commands to create. The first arc would be 12's position in clock to 6's position and the next arc would be for the rest. So, we make use of an if/else loop.
For adding the dot/circle at its tip, the following need to be done:
- A
marker
element is added to the SVG and a marker in the form of a circle is created using thecircle
element. Thecircle
element takes 3 attributes - cx, cy are the center points of the dot and r is the radius of the dot. - Using the
marker-end
attribute, this marker is then added to thepath
. Setting this attribute will mean that any path that is created would have this dot automatically appended at its ending point. No other coding needs to be done for this because SVG automatically takes care of positioning.
Text can also be added using the text
element and then its position can be set using the x
and y
attributes. (This bit still needs tuning in the below snippet.)
Demo:
Below is a very rough demo of the implementation.
window.onload = function() {
var btn = document.querySelector('button'),
inp = document.querySelector('#progress'),
path = document.querySelector('#p'),
text = document.querySelector('#val'),
rect = document.querySelector('rect'),
output = document.querySelector('#path-output');
var x = 0,
y = 0,
r = 30,
cx = 50,
cy = 50,
d = '',
fill = 'yellowgreen',
stroke = 'transparent';
btn.addEventListener('click', function() {
progress = (inp.value == '') ? 0 : inp.value;
arcRad = ((progress * 360 / 100) - 90) * Math.PI / 180;
x = Math.cos(arcRad) * r + cx;
y = Math.sin(arcRad) * r + cy;
if (progress > 0 && progress <= 50) {
d = 'M' + x + ',' + y + ' L' + cx + ',' + cy + ' L' + cx + ',' + (cy - r) + ' A' + r + ',' + r + ' 0 0,1' + x + ',' + y;
setColors(fill, stroke);
setOutput();
} else if (progress > 50 && progress <= 100) {
d = 'M' + x + ',' + y + ' L' + cx + ',' + cy + ' L' + cx + ',' + (cy - r) + ' A' + r + ',' + r + ' 0 0,1' + cx + ',' + (cy + r) + ' A' + r + ',' + r + ' 0 0,1' + x + ',' + y;
setColors(fill, stroke);
setOutput();
} else {
text.innerHTML = '';
path.setAttribute('d', '');
output.innerHTML = 'Enter a value between 0 and 100';
setColors('transparent', 'transparent');
}
if (progress > 0 && progress <= 10) {
rect.setAttribute('x', x);
rect.setAttribute('y', y + 7.5);
text.setAttribute('x', x + 2);
text.setAttribute('y', y + 15);
} else if (progress > 10 && progress <= 62) {
rect.setAttribute('x', x - 5);
rect.setAttribute('y', y + 7.5);
text.setAttribute('x', x - 3.5);
text.setAttribute('y', y + 15);
} else if (progress > 62 && progress <= 100) {
rect.setAttribute('x', x - 5);
rect.setAttribute('y', y - 15);
text.setAttribute('x', x - 3.5);
text.setAttribute('y', y - 7.5);
}
});
function setColors(fill, stroke) {
rect.setAttribute('fill', fill);
rect.setAttribute('stroke', stroke);
path.setAttribute('fill', fill);
path.setAttribute('stroke', stroke);
}
function setOutput() {
path.setAttribute('d', d);
text.innerHTML = progress;
output.innerHTML = 'Angle in Radians: ' + arcRad + '<br/>';
output.innerHTML += 'Point in Circle: ' + x + ',' + y + '<br/>';
output.innerHTML += 'Path d attribute: ' + d;
}
}
svg {
width: 200px;
height: 200px;
}
.output {
min-height: 20px;
}
h4 {
border-bottom: 1px solid;
}
<input id='progress' type='number' />
<button>Generate</button>
<br/>
<svg viewBox='0 0 100 100'>
<defs>
<marker id='dot' viewBox='0 0 10 10' markerHeight='10' markerWidth='10' refX='5' refY='5'>
<circle cx='5' cy='5' r='2' />
</marker>
</defs>
<path d='' marker-end='url(#dot)' id='p' stroke='transparent' fill='transparent' />
<rect height='10' width='10' x='10' y='10' stroke='transparent' fill='transparent' />
<text x='10' y='10' id='val' font-family='Arial' font-size='6'></text>
</svg>
<div class='output'>
<h4>Output:</h4>
<output id='path-output'></output>
</div>
Further Reading:
You can read more about SVG, its elements and attributes in the following links:
来源:https://stackoverflow.com/questions/35132864/progress-circle-draw-a-small-arc-at-the-end-tip-of-the-circle-more