Is it possible to do the animation on the drawing attribute of an SVG manually (with a button) for each path command instead of continuously?
.path {stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: dash 5s linear alternate infinite;}
@keyframes dash {from {stroke-dashoffset: 822;}
to {stroke-dashoffset: 0;}
}
<svg width="200" height="200" viewBox="50 50 240 270">
<path class="path" fill="white" stroke="black" stroke-width="4"
d="M66.039,133.545c0,0-21-57,18-67s49-4,65,8 s30,41,53,27s66,4,58,32s-5,44,18,57s22,46,0,
45s-54-40-68-16s-40,88-83,48s11-61-11-80s-79-7-70-41 C46.039,146.545,53.039,128.545,66.039,133.545z"/>
</svg>
That could help one merge two or more path commands and reduce the number of drawing path commands in an SVG.
You will need to get the length for each path segment, since you're animating/transitioning stroke-dasharray/stroke-dashoffset
values.
Here's a simplified example using a path showing a perfect circle with a circumference of 100 (diameter=31,832 * π). The path consists of 4 segments that should have a segment length of 25 units.
The helper function to get all segments lengths is based on @bez997's answer
let svg = document.querySelector("svg");
let path = svg.querySelector(".path");
let pathLength = path.getTotalLength().toFixed(1) * 1;
/**
* save segments' path lengths to array
**/
let segments = getSegmentLengths(path);
let segmentJson = JSON.stringify(segments).replaceAll('"', "");
/**
* @bez997
https://stackoverflow.com/questions/21564905/svg-api-length-of-a-single-segment#answer-45793408
original pen: https://codepen.io/bez997/pen/dzJemZ?editors=1010
**/
function getSegmentLengths(path) {
let segList = path.pathSegList;
// temporary path for computing segments
let lengthPath = document.createElementNS(
"http://www.w3.org/2000/svg",
"path"
);
let lastLength = 0;
let segLengths = [];
let segSteps = [];
let segOffset = 0;
for (let i = 0; i < segList.numberOfItems; i += 1) {
let segObj = segList.getItem(i);
lengthPath.pathSegList.appendItem(segObj);
// rounding numbers
let currentLength = lengthPath.getTotalLength().toFixed(1) * 1;
let segmentLength = (currentLength - lastLength).toFixed(1) * 1;
// strip M and Z commands since, they don't have any length
if (segmentLength) {
lastLength = currentLength;
segSteps.push({
offset: i == 1 ? 0 : -(lastLength - segmentLength).toFixed(1) * 1,
dash: segmentLength,
currentLength: currentLength
});
}
}
return segSteps;
}
/**
* change stroke dash attributes for animation
**/
function strokeTo(path, pathLength, offset, dash) {
let gap = pathLength - dash;
path.setAttribute("stroke-dashoffset", offset);
path.setAttribute("stroke-dasharray", dash + " " + gap);
}
/**
* create navigation for each segment
**/
function getStrokeNav(svg, segments, singleSegment = true, label = "") {
let strokeNav = label;
let index = 0;
segments.forEach(function (el, i) {
index++;
let thisOffset = el.offset;
let thisDash = el.dash;
if (!singleSegment) {
thisOffset = 0;
thisDash = el.currentLength;
}
strokeNav +=
'<button type="button" onclick="strokeTo(path,' +
pathLength +
", " +
thisOffset +
", " +
thisDash +
')">' +
index +
"</button>";
});
return strokeNav;
}
// nav html output
let strokeToNav =
"<p>" +
getStrokeNav(svg, segments, false, "Animate to stroke segment ") +
"</p><p>" +
getStrokeNav(svg, segments, true, "Animate single segment ") +
"</p>";
document.body.insertAdjacentHTML("afterBegin", strokeToNav);
svg {
border: 1px solid #ccc;
display: inline-block;
font-size: calc( ( 25vw + 25vh )/ 2) ;
width: 1em;
}
.path{
transition:0.5s
}
<script src="https://cdn.rawgit.com/progers/pathseg/a1072a7b/pathseg.js"></script>
<svg id="svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 50 50" >
<path class="path" fill="none" stroke="#000" d="
M40.916,25
c0,8.79-7.125,15.916-15.916,15.916
S9.084,33.79,9.084,25
S16.21,9.084,25,9.084
S40.916,16.21,40.916,25
z" />
</svg>
The expected segment array would be:
let segments = [
{offset:0, dash:25, currentLength:25},
{offset:-25, dash:25, currentLength:50},
{offset:-50, dash:25, currentLength:75},
{offset:-75, dash:25, currentLength:100}
];
By also saving an offset value, we can transition to a segment (including previous segments) but also animate a single segment.
stroke-dashoffset="0" stroke-dasharray="25 75"
Would show the first (bottom right) segment stroke.
Once you got all segments' you could store this data to an array or json statically. Since the length calculation creates a temporary DOM element – so you might experience some performance hit, depending on the svg's complexity.
let svg = document.querySelector("svg");
let path = svg.querySelector(".path");
let pathLength = path.getTotalLength().toFixed(1) * 1;
/**
* save segments' path lengths to array
**/
let segments = [
{ offset: 0, dash: 78.6, currentLength: 78.6 },
{ offset: -78.6, dash: 68.8, currentLength: 147.4 },
{ offset: -147.4, dash: 65.1, currentLength: 212.5 },
{ offset: -212.5, dash: 81.9, currentLength: 294.4 },
{ offset: -294.4, dash: 66.3, currentLength: 360.7 },
{ offset: -360.7, dash: 61.4, currentLength: 422.1 },
{ offset: -422.1, dash: 76.5, currentLength: 498.6 },
{ offset: -498.6, dash: 116.9, currentLength: 615.5 },
{ offset: -615.5, dash: 89.3, currentLength: 704.8 },
{ offset: -704.8, dash: 90.2, currentLength: 795 },
{ offset: -795, dash: 26.6, currentLength: 821.6 }
];
/**
* change stroke dash attributes for animation
**/
function strokeTo(path, pathLength, offset, dash) {
let gap = pathLength - dash;
path.setAttribute("stroke-dashoffset", offset);
path.setAttribute("stroke-dasharray", dash + " " + gap);
}
svg {
border: 1px solid #ccc;
display: inline-block;
font-size: calc( ( 50vw + 50vh )/ 2) ;
width: 1em;
}
.path{
transition:0.5s
}
<svg width="200" height="200" viewBox="50 50 240 270">
<path class="path" fill="white" stroke="black" stroke-width="4"
d="M66.039,133.545c0,0-21-57,18-67s49-4,65,8 s30,41,53,27s66,4,58,32s-5,44,18,57s22,46,0,
45s-54-40-68-16s-40,88-83,48s11-61-11-80s-79-7-70-41 C46.039,146.545,53.039,128.545,66.039,133.545z"/>
</svg>
<p>Animate to stroke segment <button type="button" onclick="strokeTo(path,821.6, 0, 78.6)">1</button><button type="button" onclick="strokeTo(path,821.6, 0, 147.4)">2</button><button type="button" onclick="strokeTo(path,821.6, 0, 212.5)">3</button><button type="button" onclick="strokeTo(path,821.6, 0, 294.4)">4</button><button type="button" onclick="strokeTo(path,821.6, 0, 360.7)">5</button><button type="button" onclick="strokeTo(path,821.6, 0, 422.1)">6</button><button type="button" onclick="strokeTo(path,821.6, 0, 498.6)">7</button><button type="button" onclick="strokeTo(path,821.6, 0, 615.5)">8</button><button type="button" onclick="strokeTo(path,821.6, 0, 704.8)">9</button><button type="button" onclick="strokeTo(path,821.6, 0, 795)">10</button><button type="button" onclick="strokeTo(path,821.6, 0, 821.6)">11</button></p>
<p>Animate single segment <button type="button" onclick="strokeTo(path,821.6, 0, 78.6)">1</button><button type="button" onclick="strokeTo(path,821.6, -78.6, 68.8)">2</button><button type="button" onclick="strokeTo(path,821.6, -147.4, 65.1)">3</button><button type="button" onclick="strokeTo(path,821.6, -212.5, 81.9)">4</button><button type="button" onclick="strokeTo(path,821.6, -294.4, 66.3)">5</button><button type="button" onclick="strokeTo(path,821.6, -360.7, 61.4)">6</button><button type="button" onclick="strokeTo(path,821.6, -422.1, 76.5)">7</button><button type="button" onclick="strokeTo(path,821.6, -498.6, 116.9)">8</button><button type="button" onclick="strokeTo(path,821.6, -615.5, 89.3)">9</button><button type="button" onclick="strokeTo(path,821.6, -704.8, 90.2)">10</button><button type="button" onclick="strokeTo(path,821.6, -795, 26.6)">11</button></p>
As an alternative you might also store your segments in a svg data attribute - albeit data-attributes are still not valid by specs.
However, they shouldn't introduce rendering issues.
A benefit of this approach would be, your segment data could directly be saved in your svg markup/file while reducing your js file.
let svg = document.querySelector("svg");
let path = svg.querySelector(".path");
let pathLength = path.getTotalLength().toFixed(1)*1;
/**
* change stroke dash attributes for animation
**/
function strokeTo(path, pathLength, offset, dash) {
let gap = pathLength - dash;
path.setAttribute("stroke-dashoffset", offset);
path.setAttribute("stroke-dasharray", dash + " " + gap);
}
svg {
border: 1px solid #ccc;
display: inline-block;
font-size: calc( ( 50vw + 50vh )/ 2) ;
width: 1em;
}
.path{
transition:0.3s
}
<svg width="200" height="200" viewBox="50 50 240 270" data-segments='[{"offset":0,"dash":78.6,"currentLength":78.6},{"offset":-78.6,"dash":68.8,"currentLength":147.4},{"offset":-147.4,"dash":65.1,"currentLength":212.5},{"offset":-212.5,"dash":81.9,"currentLength":294.4},{"offset":-294.4,"dash":66.3,"currentLength":360.7},{"offset":-360.7,"dash":61.4,"currentLength":422.1},{"offset":-422.1,"dash":76.5,"currentLength":498.6},{"offset":-498.6,"dash":116.9,"currentLength":615.5},{"offset":-615.5,"dash":89.3,"currentLength":704.8},{"offset":-704.8,"dash":90.2,"currentLength":795},{"offset":-795,"dash":26.6,"currentLength":821.6}]'>
<path class="path" fill="white" stroke="black" stroke-width="4" d="M66.039,133.545c0,0-21-57,18-67s49-4,65,8 s30,41,53,27s66,4,58,32s-5,44,18,57s22,46,0,
45s-54-40-68-16s-40,88-83,48s11-61-11-80s-79-7-70-41 C46.039,146.545,53.039,128.545,66.039,133.545z"></path>
</svg>
<p>Animate single segment <button type="button" onclick="strokeTo(path,821.6, 0, 78.6)">1</button><button type="button" onclick="strokeTo(path,821.6, -78.6, 68.8)">2</button><button type="button" onclick="strokeTo(path,821.6, -147.4, 65.1)">3</button><button type="button" onclick="strokeTo(path,821.6, -212.5, 81.9)">4</button><button type="button" onclick="strokeTo(path,821.6, -294.4, 66.3)">5</button><button type="button" onclick="strokeTo(path,821.6, -360.7, 61.4)">6</button><button type="button" onclick="strokeTo(path,821.6, -422.1, 76.5)">7</button><button type="button" onclick="strokeTo(path,821.6, -498.6, 116.9)">8</button><button type="button" onclick="strokeTo(path,821.6, -615.5, 89.3)">9</button><button type="button" onclick="strokeTo(path,821.6, -704.8, 90.2)">10</button><button type="button" onclick="strokeTo(path,821.6, -795, 26.6)">11</button></p>