I have a problem integrating javascript "percentage" output in css..
I am new to javascript and i just can't get this to work...
var number = 65;
var total = 170;
var percentage = (number / total * 100).toFixed(2);
document.getElementById("svgcircle").style.strokeDasharray = percentage;
#svgcircle {
stroke-width: 5px;
stroke: red;
stroke-dasharray: 80, 100;
stroke-linecap: round;
}
<svg id='svgcircle' height="50" width="50">
<circle cx="25" cy="25" r="16"/>
</svg>
stroke-dasharray takes 2 arguments
<dash-length>, <gap-length>
if you want a percentage output just put 100 to gap-length
var number = 65;
var total = 170;
var percentage = ((number / total) * 100).toFixed(2);
document.getElementById("svgcircle").style.strokeDasharray = percentage+', '+100; // <<<< add gap-length here
#svgcircle {
stroke-width: 5px;
stroke: red;
stroke-dasharray: 80,100;
stroke-linecap: round;
}
<svg id='svgcircle' height="50" width="50">
<circle cx="25" cy="25" r="16"/>
</svg>
Related
I am new to javascript and am having an issue repositioning animated text as the string length varies. I have an SVG element and a string within it, where that string needs to be centered within that SVG. Using ' | ' as a center reference, the centering would look like:
| | |
g g g g g g
If I start the animation with a str of len 3, it will be centered properly for Len 3 strs, but then other lens would be equivalent to:
| |
g g g
Example code:
function animateValue(obj, start, end, duration) {
let startTimestamp = null;
const step = (timestamp) => {
if (!startTimestamp) startTimestamp = timestamp;
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
const str = obj.innerHTML;
// console.log(`${str.length}` );
if (`${str.length}`==="1"){
obj.style.x = '200px';
}
obj.innerHTML = Math.floor(progress * (end - start) + start);
if (progress < 1) {
window.requestAnimationFrame(step);
}
};
window.requestAnimationFrame(step);
}
const obj = document.getElementById("heading");
animateValue(obj, 100, 0, 5000);
svg {
position: absolute ;
width: 40%;
border: 1px solid rgba(255,255,255,0.3);
margin-left: 30%;
border-radius: 50%;
}
#roseline, #majline {
stroke: #eee;
stroke-width: .5;
}
text {
font-family: Montserrat, sans-serif;
font-size: 10;
fill: #eee;
}
text.heading1{
font-size:4.5em;
fill: #0ee;
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 500 500" id="compassrose">
<defs>
<symbol>
<line x1="40" y1="250" x2="50" y2="250" id="roseline" />
<line x1="40" y1="250" x2="60" y2="250" id="majline" />
<path d="M10,250a240,240 0 1,0 480,0a240,240 0 1,0 -480,0" id="rosecircle" transform='rotate(90 250 250)' />
</symbol>
</defs>
<div class="triangle-container">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 500 500" id="compassrose">
<polygon points="250,40 280,0 220,000" class="triangle" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 500 500" >
<polygon points="0,260 0,220 40,240" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 500 500" >
<polygon points="500,260 500,220 460,240" />
<text class="heading1" id="heading" x='190px' y='250px'
fontSize="36">100 </text>
</svg>
</div>
</svg>
I have tried re-arranging the divs to allow for the absolute and relative positioning, however that was not properly maintaining size relationships as needed.
If you use dominant-baseline="middle" text-anchor="middle" on the text element and position it in the middle of the SVG (250,250) it should work.
function animateValue(obj, start, end, duration) {
let startTimestamp = null;
const step = (timestamp) => {
if (!startTimestamp) startTimestamp = timestamp;
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
obj.innerHTML = Math.floor(progress * (end - start) + start);
if (progress < 1) {
window.requestAnimationFrame(step);
}
};
window.requestAnimationFrame(step);
}
const obj = document.getElementById("heading");
animateValue(obj, 200, 0, 5000);
svg {
display: block;
position: absolute;
width: 40%;
border: 1px solid rgba(255, 255, 255, .3);
margin-left: 30%;
border-radius: 50%;
}
#roseline,
#majline {
stroke: #eee;
stroke-width: .5;
}
text {
font-family: Montserrat, sans-serif;
font-size: 10;
fill: #eee;
}
text.heading1 {
fill: #0ee;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" id="compassrose">
<defs>
<symbol>
<line x1="40" y1="250" x2="50" y2="250" id="roseline" />
<line x1="40" y1="250" x2="60" y2="250" id="majline" />
<path d="M10,250a240,240 0 1,0 480,0a240,240 0 1,0 -480,0"
id="rosecircle" transform='rotate(90 250 250)' />
</symbol>
</defs>
<polygon points="250,40 280,0 220,000" class="triangle" />
<polygon points="0,260 0,220 40,240" />
<polygon points="500,260 500,220 460,240" />
<text class="heading1" id="heading" x="250" y="250" font-size="60"
dominant-baseline="middle" text-anchor="middle">100</text>
</svg>
This querySelectorAll does not work for multiple circular progress bar. When I use querySelector it works for only one circle. How can i use this function for multiple circular progress bar.
Again, why this error -> "Uncaught TypeError: circle.getAttribute is not a function" ?
const circle = document.querySelectorAll(".progress");
const number = document.querySelectorAll(".progress-percent");
const val = circle.getAttribute("data-value");
let counter = 0;
let cir = 377;
setInterval(() => {
if (counter == val) {
clearInterval();
} else {
counter += 1;
circle.style.strokeDashoffset = (cir - (cir / 100) * val);
number.innerHTML = counter;
}
}, 25);
body {
background-color: #f50057;
display: flex;
justify-content: center;
align-items: center;
}
.progress {
stroke: #fff;
stroke-width: 2;
stroke-dasharray: 377;
stroke-dashoffset: 377;
transition: 2.5s ease-out;
}
.progress-percent {
color: #fff;
}
<section>
<div>
<svg>
<circle r="60" cx="60" cy="60"></circle>
<circle r="60" cx="60" cy="60" class="progress" data-value="80"></circle>
</svg>
<p><span class="progress-percent"></span>%</p>
</div>
<div>
<svg>
<circle r="60" cx="60" cy="60"></circle>
<circle r="60" cx="60" cy="60" class="progress" data-value="90"></circle>
</svg>
<p><span class="progress-percent"></span>%</p>
</div>
<div>
<svg>
<circle r="60" cx="60" cy="60"></circle>
<circle r="60" cx="60" cy="60" class="progress" data-value="70"></circle>
</svg>
<p><span class="progress-percent"></span>%</p>
</div>
</section>
unlike .querySelector, .querySelectorAll returns many results. The previous actions that you would call on a single circle will not automatically apply to each of your circles. However, you can use the .forEach method and pass a function with each item as an argument.
you can read more about .forEach here
If you have any questions, please ask. Hopefully this helps point you in the right direction 👋
const circles = document.querySelectorAll(".progress");
const numbers = document.querySelectorAll(".progress-percent");
const cir = 377;
circles.forEach((circle, index) => {
let counter = 0;
const val = circle.getAttribute("data-value");
setInterval(() => {
if (counter == val) {
clearInterval();
} else {
counter += 1;
circle.style.strokeDashoffset = (cir - (cir / 100) * val);
numbers[index].innerHTML = counter;
}
}, 25);
});
body {
background-color: #f50057;
display: flex;
justify-content: center;
align-items: center;
}
.progress {
stroke: #fff;
stroke-width: 2;
stroke-dasharray: 377;
stroke-dashoffset: 377;
transition: 2.5s ease-out;
}
.progress-percent {
color: #fff;
}
<section>
<div>
<svg>
<circle r="60" cx="60" cy="60"></circle>
<circle r="60" cx="60" cy="60" class="progress" data-value="80"></circle>
</svg>
<p><span class="progress-percent"></span>%</p>
</div>
<div>
<svg>
<circle r="60" cx="60" cy="60"></circle>
<circle r="60" cx="60" cy="60" class="progress" data-value="90"></circle>
</svg>
<p><span class="progress-percent"></span>%</p>
</div>
<div>
<svg>
<circle r="60" cx="60" cy="60"></circle>
<circle r="60" cx="60" cy="60" class="progress" data-value="70"></circle>
</svg>
<p><span class="progress-percent"></span>%</p>
</div>
</section>
im using svg for circular progress bar , i want to make one end curve not the both end . how is this possible ?
how can i implement one end curve in svg?
svg {
height: 80vh;
margin: 10vh auto;
border: 1px solid red;
display: block;
transform: rotate(-90deg);
}
svg circle {
stroke-width: 10;
fill: transparent;
}
#outer {
stroke: lightgrey;
}
#inner {
stroke: blue;
animation: value 2.5s linear forwards;
stroke-linecap: round;
}
#keyframes value {
0% {
stroke-dasharray: 0 100;
}
100% {
stroke-dasharray: 90 100;
}
}
<svg viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<circle id="outer" cx="50" cy="50" r="40" />
<circle id="inner" pathLength="100" cx="50" cy="50" r="40" />
</svg>
The easiest way would be to mask the starting point of the blue circle.
For this you will need a <mask> like so:
<mask id="m">
<rect width="100" height="100" fill="white"/>
<rect x="85" y="40" width="10" height="10" />
</mask>
Please observe that the first rectangle is white and it covers the whole chart. (Everything under a white pixel will be visible). The smaller rectangle is black and covers the starting point of the blue circle. Everything under a black pixel will be invisible.
svg {
height: 80vh;
margin: 10vh auto;
border: 1px solid red;
display: block;
transform: rotate(-90deg);
}
svg circle {
stroke-width: 10;
fill: transparent;
}
#outer {
stroke: lightgrey;
}
#inner {
stroke: blue;
animation: value 2.5s linear forwards;
stroke-linecap: round;
}
#keyframes value {
0% {
stroke-dasharray: 0 100;
}
100% {
stroke-dasharray: 90 100;
}
}
<svg viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<mask id="m">
<rect width="100" height="100" fill="white"/>
<rect x="85" y="40" width="10" height="10" />
</mask>
<circle id="outer" cx="50" cy="50" r="40" />
<circle id="inner" pathLength="100" cx="50" cy="50" r="40" mask="url(#m)" />
</svg>
Try this code:
svg {
height: 80vh;
margin: 10vh auto;
border: 1px solid red;
display: block;
transform: rotate(-90deg);
border-top-right-radius: 20px;
}
svg circle {
stroke-width: 10;
fill: transparent;
}
#outer {
stroke: lightgrey;
}
#inner {
stroke: blue;
animation: value 2.5s linear forwards;
stroke-linecap: round;
}
#keyframes value {
0% {
stroke-dasharray: 0 100;
}
100% {
stroke-dasharray: 90 100;
}
}
<svg viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<circle id="outer" cx="50" cy="50" r="40" />
<circle id="inner" pathLength="100" cx="50" cy="50" r="40" />
</svg>
I came up with an alternative solution than enxaneta suggested. The problem with using a mask is that when your value goes over 96% or so, the circle isn't completely filled and the mask is revealed.
Instead, you can set a rounded progress line on top of another progress line that has flat endcaps. By rotating the rounded progress line by roughly 5 degrees, the flat end is revealed.
Here's how to do that in React Native with react-native-svg:
const radius = 60;
let myPercentage = 40;
const circleCircumference = 2 * Math.PI * radius;
const valueOffset = circleCircumference -
(circleCircumference * myPercentage * 0.98) / 100;
<Svg height={radius * 2 + 30} width={radius * 2 + 30}>
<G rotation={-90} originX={radius + 15} originY={radius + 15}>
// Background gray circle
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="rgb(60, 60, 60)"
fill="transparent"
strokeWidth="10"
strokeDasharray={circleCircumference}
strokeLinecap="butt"
/>
// Background progress circle with flat ends
<Circle
cx="50%"
cy="50%"
r={radius}
stroke={"rgb(0, 51, 204)"}
fill="transparent"
strokeWidth="10"
strokeDasharray={circleCircumference}
strokeDashoffset={valueOffset}
strokeLinecap="butt"
/>
// Progress circle with round ends rotated by 5 degrees
<Circle
cx="50%"
cy="50%"
r={radius}
stroke={rgb(0, 51, 204)}
fill="transparent"
rotation={5}
originX={radius + 15}
originY={radius + 15}
strokeWidth="10"
strokeDasharray={circleCircumference}
strokeDashoffset={valueOffset}
strokeLinecap="round"
/>
</G>
</Svg>
This question already has answers here:
Passing parameters to css animation
(3 answers)
Closed 2 years ago.
i have this svg with the respective css which animates the path.
HTML
<div>
<svg version="1.1" baseProfile="full" width="400" height="300" xmlns="http://www.w3.org/2000/svg">
<path class="grey" fill="none" stroke="#B7C4D2" stroke-width="10" d="M 136 277 A 100 100 0 1 1 264 277"></path>
<path class="blue" fill="none" stroke="#30A7F4" stroke-width="10" d="M 136 277 A 100 100 0 1 1 264 277"></path>
</svg>
</div>
CSS
.blue {
stroke-dasharray: 490;
stroke-dashoffset: 490;
animation: draw 2s linear forwards;
}
#keyframes draw {
to {
stroke-dashoffset: 260;
}
}
i want to make the "stroke-dashoffset: 260" value dynamic, any way to do that via JS?
One performant way of changing CSS with JS is using CSS variables.
you'd then have:
.blue {
stroke-dasharray: 490;
stroke-dashoffset: 490;
animation: draw 2s linear forwards;
}
#keyframes draw {
to {
stroke-dashoffset: var(--stroke-dashoffset);
}
}
and in your JS:
const theBlue = document.querySelectorAll('svg .blue')[0]
theBlue.style.setProperty('--stroke-dashoffset', 260)
you can inject internal style and you can send offest value to setAnimationOffest(120) dynamically.
function addAnimation(name, body) {
if (!dynamicStyles) {
dynamicStyles = document.createElement('style');
dynamicStyles.type = 'text/css';
document.head.appendChild(dynamicStyles);
}
dynamicStyles.sheet.insertRule(`#keyframes ${ name } {
${ body }
}`, dynamicStyles.length);
}
let dynamicStyles = null;
function addAnimation(name, body) {
if (!dynamicStyles) {
dynamicStyles = document.createElement('style');
dynamicStyles.type = 'text/css';
document.head.appendChild(dynamicStyles);
}
dynamicStyles.sheet.insertRule(`#keyframes ${ name } {
${ body }
}`, dynamicStyles.length);
}
function setAnimationOffest(offest){
addAnimation('draw', `
to { stroke-dashoffset: ${offest}; }
`);
};
setAnimationOffest(120);
.blue {
stroke-dasharray: 490;
stroke-dashoffset: 490;
animation: draw 2s linear forwards;
}
#keyframes draw {
to {
stroke-dashoffset: 260;
}
}
<div>
<svg version="1.1" baseProfile="full" width="400" height="300" xmlns="http://www.w3.org/2000/svg">
<path class="grey" fill="none" stroke="#B7C4D2" stroke-width="10" d="M 136 277 A 100 100 0 1 1 264 277"></path>
<path class="blue" fill="none" stroke="#30A7F4" stroke-width="10" d="M 136 277 A 100 100 0 1 1 264 277"></path>
</svg>
</div>
I have a page with different SVG elements that react when hovering over them. When hovering, the element increases in size, covering neighbouring elements. My trouble is that some of the neighbours have been drawn later and won't be covered. [Example]
I tried to fix the issue by using appendChild() when hovering over to make it the last drawn element, but this removes the smooth transition effect I set with CSS.
Example:
for (var i = 0; i < 20; i++) {
for (var n = 0; n < 5; n++) {
var new_rect = document.getElementById("0").cloneNode(true);
new_rect.setAttributeNS(null, "cx", i * 20 + 10);
new_rect.setAttributeNS(null, "cy", n * 20 + 10);
new_rect.setAttributeNS(null, "id", i + n);
document.getElementById("mainG").appendChild(new_rect);
}
}
function expand(evt) {
//evt.target.parentNode.appendChild(evt.target);
evt.target.setAttributeNS(null, "r", "25");
}
function shrink(evt) {
evt.target.setAttributeNS(null, "r", "10");
}
.circle {
fill: hsl(100, 30%, 80%);
-webkit-transition: .1s ease-in-out;
}
.circle:hover {
fill: hsl(0, 50%, 70%);
}
<svg version="1.1" baseProfile="full" width="440" height="150" xmlns="http://www.w3.org/2000/svg">
<g id="mainG">
<circle id="0" cx="10" cy="10" r="10" stroke="none" fill="white" class="circle" onmouseover="expand(evt)" onmouseout="shrink(evt)"></circle>
</g>
<g id="cloneG"></g>
</svg>
How can I both get the element to be drawn on top while still having smooth transitions between states?
You can force a reflow with the following..
var test = evt.target.offsetHeight;
Do this just before changing the radius
for (var i = 0; i < 20; i++) {
for (var n = 0; n < 5; n++) {
var new_rect = document.getElementById("0").cloneNode(true);
new_rect.setAttributeNS(null, "cx", i * 20 + 10);
new_rect.setAttributeNS(null, "cy", n * 20 + 10);
new_rect.setAttributeNS(null, "id", i + n);
document.getElementById("mainG").appendChild(new_rect);
}
}
function expand(evt) {
evt.target.parentNode.appendChild(evt.target);
var test = evt.target.offsetHeight;
evt.target.setAttributeNS(null, "r", "25");
}
function shrink(evt) {
evt.target.setAttributeNS(null, "r", "10");
}
.circle {
fill: hsl(100, 30%, 80%);
-webkit-transition: 1s ease-in-out;
}
.circle:hover {
fill: hsl(0, 50%, 70%);
}
<svg version="1.1" baseProfile="full" width="440" height="150" xmlns="http://www.w3.org/2000/svg">
<g id="mainG">
<circle id="0" cx="10" cy="10" r="10" stroke="none" fill="white" class="circle" onmouseover="expand(evt)" onmouseout="shrink(evt)"></circle>
</g>
<g id="cloneG"></g>
</svg>
in this way you preserve the CSS transition :
for (var i = 0; i < 20; i++) {
for (var n = 0; n < 5; n++) {
var new_rect = document.getElementById("0").cloneNode(true);
new_rect.setAttributeNS(null, "cx", i * 20 + 10);
new_rect.setAttributeNS(null, "cy", n * 20 + 10);
new_rect.setAttributeNS(null, "id", i + n);
document.getElementById("mainG").appendChild(new_rect);
}
}
function expand(evt) {
evt.target.parentNode.appendChild(evt.target);
evt.target.setAttributeNS(null, "r", "25");
evt.target.style.fill='hsl(0, 50%, 70%)';
}
function shrink(evt) {
evt.target.setAttributeNS(null, "r", "10");
evt.target.style.fill='hsl(100, 30%, 80%)';
}
.circle {
fill: hsl(100, 30%, 80%);
-webkit-transition: .4s ease-in-out;
}
<svg version="1.1" baseProfile="full" width="440" height="150" xmlns="http://www.w3.org/2000/svg">
<g id="mainG">
<circle id="0" cx="10" cy="10" r="10" stroke="none" fill="white" class="circle" onmouseover="expand(evt)" onmouseout="shrink(evt)"></circle>
</g>
<g id="cloneG"></g>
</svg>
or (same thing with CSS added through JS)
for (var i = 0; i < 20; i++) {
for (var n = 0; n < 5; n++) {
var new_rect = document.getElementById("0").cloneNode(true);
new_rect.setAttributeNS(null, "cx", i * 20 + 10);
new_rect.setAttributeNS(null, "cy", n * 20 + 10);
new_rect.style.fill='hsl(100, 30%, 80%)';
new_rect.setAttributeNS(null, "id", i + n);
document.getElementById("mainG").appendChild(new_rect);
}
}
function expand(evt) {
evt.target.parentNode.appendChild(evt.target);
evt.target.setAttributeNS(null, "r", "25");
evt.target.style.fill='hsl(0, 50%, 70%)';
evt.target.style.transition= '.4s ease-in-out';
}
function shrink(evt) {
evt.target.setAttributeNS(null, "r", "10");
evt.target.style.fill='hsl(100, 30%, 80%)';
}
<svg version="1.1" baseProfile="full" width="440" height="150" xmlns="http://www.w3.org/2000/svg">
<g id="mainG">
<circle id="0" cx="10" cy="10" r="10" stroke="none" fill="white" class="circle" onmouseover="expand(evt)" onmouseout="shrink(evt)"></circle>
</g>
<g id="cloneG"></g>
</svg>