Transition <line> x1, y1, etc. attributes in SVG using CSS - css

I have an inline SVG in a svelte component that contains some <line>s which change their x1, x2, y1 and y2 attributes based on state. I want to transition between the changes with CSS but it just jumps to the new state instead of transitioning. I would like if there's a native solution with CSS but I can also use svelte transitions.
<button aria-pressed={active} on:click={toggleActive} {...$$restProps}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
{#if active}
<line x1="10" y1="10" x2="20" y2="20" />
<line x1="10" y1="30" x2="30" y2="10" />
<line x1="20" y1="20" x2="30" y2="30" />
{:else}
<line x1="5" y1="10" x2="35" y2="10" />
<line x1="5" y1="20" x2="35" y2="20" />
<line x1="5" y1="30" x2="35" y2="30" />
{/if}
</svg>
</button>
svg line {
stroke: black;
stroke-width: 5;
transition-property: x1, x2, y1, y2, all;
transition-duration: 5s;
}
I have tried using style attribute to set the coordinates based on this answer and that didn't work either.

Even if the attributes could be transitioned, this would not work because the attributes are not changed: The previous elements are discarded and fully replaced.
One thing you could do is use tweened stores, though there are probably better methods. E.g.
<script>
import { cubicOut } from 'svelte/easing';
import { tweened } from 'svelte/motion';
let active = false;
const stateActive = [10, 10, 20, 20, 10, 30, 30, 10, 20, 20, 30, 30];
const stateInactive = [5, 10, 35, 10, 5, 20, 35, 20, 5, 30, 35, 30];
const anim = stateInactive.map(v => tweened(v));
const [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12] = anim;
$: anim.forEach((store, i) => store.set(
(active ? stateActive : stateInactive)[i],
{ duration: 500, easing: cubicOut }
));
</script>
<button aria-pressed={active} on:click={() => active = !active}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
<line x1={$v1} y1={$v2} x2={$v3} y2={$v4} />
<line x1={$v5} y1={$v6} x2={$v7} y2={$v8} />
<line x1={$v9} y1={$v10} x2={$v11} y2={$v12} />
</svg>
</button>
REPL

When conditionally setting d= on the same path element the transition seems to be working in Chrome(109) and Firefox(110) but not in Safari (16). The mentioned d: path() in the comments doesn't seem to be supported yet caniuse Not sure if that's what's relevant in this case.
REPL
<script>
let active = true
</script>
<button on:click={() => active = !active}>
toggle
</button>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
<path d="{active ? 'M10 10 L20 20' : 'M5 10 L35 10'}" />
<path d="{active ? 'M10 30 L30 10' : 'M5 20 L35 20'}" />
<path d="{active ? 'M20 20 L30 30' : 'M5 30 L35 30'}" />
</svg>
<style>
svg {
width: 200px;
}
path {
stroke: black;
stroke-width: 5;
transition: all 400ms;
}
</style>

Related

how to create an Angular circular progress bar using SVG

I have created a design for angular circular progress bar. The issue is that i want to give the percentage value dynamic . I have got the dynamic value from API but i have no idea how should i implement it to create a circular progress bar using SVG. I'll share my html skeleton and the css i use.
.track,
.filled {
stroke-width: 10;
fill: none;
}
.track {
stroke: rgba(160, 215, 231, 0.85);
}
.filled {
stroke: blue;
stroke-dashoffset: 202;
stroke-dasharray: 440;
}
<div class="col-lg-3">
<div class="iconbox-singleD7 dashboard-height">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-35 0 190 190" class="mw">
<circle class="track" cx="60" cy="60" r="55" />
<text x="40" y="60" fill="#fff">Patients</text>
<text x="50" y="75" fill="#fff">{{totalPatient}}</text>
<circle class="filled" cx="60" cy="60" r="55" />
<circle cx="30" cy="136" r="5" stroke="blue" stroke-width="3" fill="blue" /><text x="40" y="140" fill="#fff">Male {{malePercentage}}%</text>
<circle cx="30" cy="152" r="5" stroke="rgba(160, 215, 231, 0.85)" stroke-width="3" fill="rgba(160, 215, 231, 0.85)" /><text x="40" y="155" fill="#fff">Female {{femalePercentage}}%</text>
</svg>
</div>
</div>
What i want is that. Lets say i get a male percentage = 70% or any value from an API i want to set the progress bar according to the percentage i get from API. How can i achieve that .i'm not familier with SVG. So any help will be appreciated. Thanks
Hello here is the code pen: https://codepen.io/Echyzen/pen/LYBraGB
var percentage = 75;
var a = -3.46;
var b = 440;
function changePer() {
const finalOffset = Math.round(a*percentage+b)
const concernedCircle = document.getElementsByClassName('filled')[0];
concernedCircle.style.strokeDashoffset = finalOffset;
}
which extract the a and b of the affine function.
You have to click on the button to execute the 75% percentage.
Or using querySelector:
var percentage = 70;
var a = -3.46;
var b = 440;
function changePer() {
const finalOffset = Math.round(a*percentage+b)
const concernedCircle = document.querySelector('.filled');
concernedCircle.style.strokeDashoffset = finalOffset;
}
Start with the <progress-circle> native Web Component I built : https://pie-meister.github.io/
It is unlicensed Open Source code; do with it whatever your want. Documentation at Dev.to
<progress-circle value="100%">Web Components</progress-circle>
<progress-circle value="71%" stroke="red">React</progress-circle>
<progress-circle value="90%" stroke="orange">Others</progress-circle>
<script src="https://pie-meister.github.io/PieMeister-with-Progress.min.js"></script>
<style>
progress-circle {
font-family: Arial;
width: 180px; /* StackOverflow viewport */
}
progress-circle::part(label) {
font-size:70px;
}
</style>

How to make svg RTL

I want to make svg RTL, meaning i want to change the origin from 'Top Left' to 'Top Right' in a way that when i say draw a path M 0 0 L 100 100, it creates a line that starts with top: 0, right: 0 and goes to top: 100, right: 100
I tried most common solution for changing origin but none of them work as i want it to
My code:
<svg
style={{ width: "100%", height: "100%", textAlign: "right" }}
//preserveAspectRatio="xMaxYMin meet"
>
<path stroke="green" strokeWidth="3" fill="none" d="M 0 0 L 100 100" />
</svg>
And here is a working example of the code
Translate and scale the canvas, though be warned any text will display RTL too.
<svg width="200" height="200">
<g transform="translate(200, 0) scale(-1, 1)">
<path stroke="green" strokeWidth="3" fill="none" d="M 0 0 L 100 100" />
</g>
</svg>

Display arrow head for an animated line

I am animating a line from Point 1 to Point 2 with https://jsfiddle.net/arungeorgez/r9x6vhcb/4/. How do I add an arrowhead to the same line?
<style>
.key-anim1 {
-webkit-animation: Drawpath 1s linear forwards;
animation: Drawpath 1s linear forwards;
stroke-dasharray: 0, 600;
}
#-webkit-keyframes Drawpath {
from {
stroke-dasharray: 0, 600;
}
to {
stroke-dasharray: 600, 600;
}
}
#keyframes Drawpath {
from {
stroke-dasharray: 0, 600;
}
to {
stroke-dasharray: 600, 600;
}
}
</style>
<animate attributeType="XML"
attributeName="opacity"
from="0" to="1"
dur=".08s" begin=".23s"
repeatCount="0" fill="freeze"
/>
seems to produce a decent effect for your case, since the arrowhead is small. However, for a more fine-tuned solution one could use <animateTransform> or <animateMotion>, instead of <animate>, depending on case.
Here's the spec for SMIL Animations.
While the effect is easily achievable with CSS animations (in the end, I'm only animating opacity above), I tend to recommend SMIL Animations for <svg>s, as they provide a lot more options for controlling the different aspects of animations, far superior to CSS options, IMHO.
function makeSVG(tag, attrs) {
var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
for (var k in attrs)
el.setAttribute(k, attrs[k]);
return el;
}
$(document).ready(function() {
var line = makeSVG('line', {
id: "-",
class: "key-anim1",
x1: 20,
y1: 20,
x2: 120,
y2: 120,
stroke: 'black',
'stroke-width': 2,
'marker-end': "url(#arrow)"
});
document.getElementById("svg").appendChild(line);
});
.key-anim1 {
-webkit-animation: Drawpath 1s linear forwards;
animation: Drawpath 1s linear forwards;
stroke-dasharray: 0, 600;
}
#-webkit-keyframes Drawpath {
from {
stroke-dasharray: 0, 600;
}
to {
stroke-dasharray: 600, 600;
}
}
#keyframes Drawpath {
from {
stroke-dasharray: 0, 600;
}
to {
stroke-dasharray: 600, 600;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg height="600" width="600" id="svg">
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth" opacity="0">
<path d="M0,0 L0,6 L9,3 z" fill="#000" />
<animate attributeType="XML" attributeName="opacity" from="0" to="1" dur=".08s" begin=".23s" repeatCount="0" fill="freeze" />
</marker>
</defs>
</svg>
Note: the easy way to fine-tune any animation is to decrease the speed 10 times. This way you'll be able to make it perfect, and to increase it back after you're happy with how it performs 10 times slower. Sometimes, when speeding it back up you need to make minor adjustments from how it would be "technically correct" to counterbalance optical illusions (but this is often times far into the land of "invisible details").
If you want the maker to be visible and move along with the line, you need to drop the dasharray (because now your line has the same length from start to end of animation, but it's drawn with a dashed line and you're simply moving the gaps in that dashed line so it looks like it's growing). Instead you need to make it short initially and animate it towards being longer.
Here's an example:
<svg height="600" width="600" id="svg">
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="0" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="#000" opacity="0">
<animate attributeType="XML" attributeName="opacity" from="0" to="1" dur=".1s" repeatCount="0" fill="freeze" />
</path>
</marker>
</defs>
<line id="-" x1="20" y1="20" x2="21" y2="21" stroke="black" stroke-width="2" marker-end="url(#arrow)">
<animate attributeType="XML" attributeName="x2" from="21" to="120" dur="1s" repeatCount="0" fill="freeze" />
<animate attributeType="XML" attributeName="y2" from="21" to="120" dur="1s" repeatCount="0" fill="freeze" />
</line>
</svg>

Create SVG components with scoped CSS

I am creating React components that will render out various SVGs:
const Close = ({
fill, width, height, float,
}) => (
<svg width={ `${width}px` } height={ `${height}px` } viewBox="0 0 14.48 14.48" style={ { float: `${float}`, cursor: 'pointer' } }>
<title>x</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Background">
<line style={ { fill: 'none', stroke: `${fill}`, strokeMiterlimit: 10 } } x1="14.13" y1="0.35" x2="0.35" y2="14.13" />
<line style={ { fill: 'none', stroke: `${fill}`, strokeMiterlimit: 10 } } x1="14.13" y1="14.13" x2="0.35" y2="0.35" />
</g>
</g>
</svg>
);
It's very convenient to be able to supply various attributed to this component to control dimensions, colour, etc...
What I don't have a good solution for, however, is handling the styles in a DRY manner. Note the line elements have the same value for style. I presently have them written inline because if I added an embedded stylesheet, then I would get class name collisions with other SVGs I render elsewhere on the page (our SVG software uses the same classes over and over).
<style scoped> has been removed from the spec: https://github.com/whatwg/html/issues/552
Shadow DOM is not yet supported by Edge: https://caniuse.com/#feat=shadowdomv1
Is there any other alternative for scoping styles?
To combine the best of both worlds, you could create an external styles file, as you would for CSS, but with exported style objects. You could then import it into any file that needs it.
As example, main file:
import React, { Component } from 'react';
import { render } from 'react-dom';
import * as Styles from './svgstyles';
class App extends Component {
render() {
return (
<div>
<svg width="100" height="200" viewBox="0 0 100 200">
<rect x="0" y="0" width="10" height="10" style={Styles.style1} />
<rect x="15" y="0" width="10" height="10" style={Styles.style2} />
<rect x="30" y="0" width="10" height="10" style={Styles.style3} />
<rect x="45" y="0" width="10" height="10" style={Styles.style4} />
<rect x="0" y="15" width="10" height="10" style={Styles.style4} />
<rect x="15" y="15" width="10" height="10" style={Styles.style3} />
<rect x="30" y="15" width="10" height="10" style={Styles.style2} />
<rect x="45" y="15" width="10" height="10" style={Styles.style1} />
</svg>
</div>
);
}
}
render(<App />, document.getElementById('root'));
And an external styles file:
export const style1 = {
stroke: 'red',
strokeWidth: "1",
fill: "blue",
}
export const style2 = {
stroke: 'red',
strokeWidth: "1",
fill: "green",
}
export const style3 = {
stroke: 'red',
strokeWidth: "1",
fill: "yellow",
}
export const style4 = {
...style3,
fill: "pink",
}
Live example here
If you just want to DRY up the code, you could create one style-object and reuse it:
const Close = ({
fill, width, height, float,
}) => {
const style = { fill: 'none', stroke: `${fill}`, strokeMiterlimit: 10 }
return (
<svg width={ `${width}px` } height={ `${height}px` } viewBox="0 0 14.48 14.48" style={ { float: `${float}`, cursor: 'pointer' } }>
<title>x</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Background">
<line style={ style } x1="14.13" y1="0.35" x2="0.35" y2="14.13" />
<line style={ style } x1="14.13" y1="14.13" x2="0.35" y2="0.35" />
</g>
</g>
</svg>
);
}
This would also result in a small performance improvement since fewer objects would be created in each render cycle.
Actually, if I was in your place, Surely I use fonts instead of SVGs, but for your exact question, I prefer to use constant variables inside of my arrow function, just like below:
import React from 'react';
const Close = ({ fill, width, height, float }) => {
const constStyle = { fill: 'none', stroke: `${fill}`, strokeMiterlimit: 10 };
return (
<svg
width={`${width}px`}
height={`${height}px`}
viewBox="0 0 14.48 14.48"
style={{ float: `${float}`, cursor: 'pointer' }}
>
<title>x</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Background">
<line style={constStyle} x1="14.13" y1="0.35" x2="0.35" y2="14.13" />
<line style={constStyle} x1="14.13" y1="14.13" x2="0.35" y2="0.35" />
</g>
</g>
</svg>
);
};
export default Close;
Even, I make the line dimension variables as props, but I don't know what is your case exactly.
Hope this answer helps you.

Animate dashed SVG line

I would like to animate a dashed line in a SVG-file. The line should »grow« from 0 length to full length. All the methods I found are not suitable for me.
Does anyone have an idea, how to solve this?
That's the path in my svg file:
<path class="path" fill="none" stroke="#FFFFFF" stroke-miterlimit="10" d="M234.3,119
c-31-0.7-95,9-128.7,50.8"/>
To animate the line as straight line i did:
.path {
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: dash 5s linear forwards;
}
#keyframes dash {
to {
stroke-dashoffset: 0;
}
}
Of course, this is not working, when I want the line to be dashed.
Is there anybody who has an idea how to solve this?
That's my codepen: http://codepen.io/anon/pen/WpZNJY
PS: I can't use two paths over each other to hide the path underneath because I'm having a coloured background.
You can also do this just using masks as so:
.paths {
fill: none;
stroke: black;
stroke-dasharray: 5;
}
.mask {
fill: none;
stroke: white;
stroke-width: 10;
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: dash 5s linear forwards;
}
#keyframes dash {
to {
stroke-dashoffset: 0;
}
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 830 500" >
<defs>
<path id="first" d="M234.3,119c-31-0.7-95,9-128.7,50.8"/>
<path id="vienna" d="M382.8,243.8c2.9-36.1,15.8-110.3,110.1-145.4"/>
<path id="budapest" d="M550.6,319.4c34-2.7,109-2.1,174.8,50.5"/>
<path id="salzburg" d="M265,323c21.5,12.1,57.2,39.5,60.7,85.1"/>
<path id="tyrol" d="M147.8,319.5c-27.1,7-79.7,31.3-92.8,74.2"/>
<mask id="first-mask"><use class="mask" xlink:href="#first" /></mask>
<mask id="vienna-mask"><use class="mask" xlink:href="#vienna" /></mask>
<mask id="budapest-mask"><use class="mask" xlink:href="#budapest" /></mask>
<mask id="salzburg-mask"><use class="mask" xlink:href="#salzburg" /></mask>
<mask id="tyrol-mask"><use class="mask" xlink:href="#tyrol" /></mask>
</defs>
<g class="paths">
<use xlink:href="#first" mask="url(#first-mask)" />
<use xlink:href="#vienna" mask="url(#vienna-mask)" />
<use xlink:href="#budapest" mask="url(#budapest-mask)" />
<use xlink:href="#salzburg" mask="url(#salzburg-mask)" />
<use xlink:href="#tyrol" mask="url(#tyrol-mask)" />
</g>
</svg>
This is also available as:
codepen of single dotted path
codepen of these plane tracks
Enjoy!
But note ... make sure you test your code cross-browser and fall back to animate tags or javascript if you have issues.
One of the ways to do this is with Javascript. It duplicates a path by creating a polyline.
Try the example below:
<!DOCTYPE HTML>
<html>
<head>
<style>
polyline{
stroke-dasharray:8;
stroke:black;
fill:none;
stroke-width:1;
}
</style>
</head>
<body >
This builds a smooth/dashed polylines that replicates your paths.<br>
<button onClick=animateDashPaths()>Animate Paths</button><br>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 830 500" enable-background="new 0 0 830 500" xml:space="preserve">
<path class="path" fill="none" stroke="#000000" stroke-miterlimit="10" d="M234.3,119
c-31-0.7-95,9-128.7,50.8"/>
<!-- Vienna Dash -->
<path id="pathVienna" display="none" stroke-miterlimit="10" d="M382.8,243.8
c2.9-36.1,15.8-110.3,110.1-145.4"/>
<!-- Budapest Dash -->
<path id="pathBudapest" display="none" stroke-miterlimit="10" d="M550.6,319.4
c34-2.7,109-2.1,174.8,50.5"/>
<!-- Salzburg Dash -->
<path id="pathSalzburg" display="none" stroke-miterlimit="10" d="M265,323
c21.5,12.1,57.2,39.5,60.7,85.1"/>
<!-- Tyrol Dash -->
<path id="pathTyrol" display="none" stroke-miterlimit="10" d="M147.8,319.5
c-27.1,7-79.7,31.3-92.8,74.2"/>
</svg>
<script>
//---button---
function animateDashPaths()
{
var NS="http://www.w3.org/2000/svg"
//----Vienna----------------
var endLengthVienna=pathVienna.getTotalLength()
var lengthDeltaVienna=endLengthVienna/200
var polylineVienna=document.createElementNS(NS,"polyline")
Layer_1.appendChild(polylineVienna)
var pntListVienna=polylineVienna.points
var iTVienna=setInterval(drawPathVienna,5)
var cntVienna=0
function drawPathVienna()
{
var len=lengthDeltaVienna*cntVienna++
if(len<endLengthVienna)
{
var pnt=pathVienna.getPointAtLength(len)
pntListVienna.appendItem(pnt)
}
else
clearInterval(iTVienna)
}
//----Budapest----------------
var endLengthBudapest=pathBudapest.getTotalLength()
var lengthDeltaBudapest=endLengthBudapest/200
var polylineBudapest=document.createElementNS(NS,"polyline")
Layer_1.appendChild(polylineBudapest)
var pntListBudapest=polylineBudapest.points
var iTBudapest=setInterval(drawPathBudapest,5)
var cntBudapest=0
function drawPathBudapest()
{
var len=lengthDeltaBudapest*cntBudapest++
if(len<endLengthBudapest)
{
var pnt=pathBudapest.getPointAtLength(len)
pntListBudapest.appendItem(pnt)
}
else
clearInterval(iTBudapest)
}
//----Salzburg----------------
var endLengthSalzburg=pathSalzburg.getTotalLength()
var lengthDeltaSalzburg=endLengthSalzburg/200
var polylineSalzburg=document.createElementNS(NS,"polyline")
Layer_1.appendChild(polylineSalzburg)
var pntListSalzburg=polylineSalzburg.points
var iTSalzburg=setInterval(drawPathSalzburg,5)
var cntSalzburg=0
function drawPathSalzburg()
{
var len=lengthDeltaSalzburg*cntSalzburg++
if(len<endLengthSalzburg)
{
var pnt=pathSalzburg.getPointAtLength(len)
pntListSalzburg.appendItem(pnt)
}
else
clearInterval(iTSalzburg)
}
//----Tyrol----------------
var endLengthTyrol=pathTyrol.getTotalLength()
var lengthDeltaTyrol=endLengthTyrol/200
var polylineTyrol=document.createElementNS(NS,"polyline")
Layer_1.appendChild(polylineTyrol)
var pntListTyrol=polylineTyrol.points
var iTTyrol=setInterval(drawPathTyrol,5)
var cntTyrol=0
function drawPathTyrol()
{
var len=lengthDeltaTyrol*cntTyrol++
if(len<endLengthTyrol)
{
var pnt=pathTyrol.getPointAtLength(len)
pntListTyrol.appendItem(pnt)
}
else
clearInterval(iTTyrol)
}
}
</script>
</body>
</html>
I think you should just be able to add stroke-dasharray to your animation and lower the dash array in your css. An updated version of your codepen; http://codepen.io/harvey89/pen/NpaWBE
stroke-dashoffset: 1000;
stroke-dasharray: 10;
#keyframes dash {
to {
stroke-dashoffset: 0;
stroke-dasharray: 10;
}
}
You'll probably have to play around with numbers to get your desired effect though

Resources