SVG 的每个路径命令的手动动画
Manual animation for each path command for an SVG
是否可以为 每个 path command 手动(使用按钮)而不是连续地在 SVG 的绘图属性上制作动画?
.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>
这有助于合并两个或多个路径命令并减少 SVG 中绘图路径命令的数量。
您需要获取每个路径段的长度,因为您是 animating/transitioning stroke-dasharray/stroke-dashoffset
个值。
这是一个简化的例子,使用的路径显示了一个圆周为 100(直径 = 31,832 * π)的完美圆。该路径由 4 个段组成,段长度应为 25 个单位。
获取所有段长度的辅助函数基于
@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
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>
预期的段数组将是:
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}
];
通过同时保存偏移值,我们可以过渡到一个片段(包括之前的片段),但也可以为单个片段设置动画。
stroke-dashoffset="0" stroke-dasharray="25 75"
将显示第一个(右下)段笔划。
获得所有段后,您可以将这些数据存储到一个数组或 json 静态。由于长度计算会创建一个临时 DOM 元素 – 因此您可能会遇到一些性能问题,具体取决于 svg 的复杂性。
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>
作为替代方案,您也可以将段存储在 svg 数据属性中 - 尽管数据属性仍然不符合规范。
但是,它们不应引入渲染问题。
这种方法的一个好处是,您的细分数据可以直接保存在您的 svg markup/file 中,同时减少您的 js 文件。
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>
是否可以为 每个 path command 手动(使用按钮)而不是连续地在 SVG 的绘图属性上制作动画?
.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>
这有助于合并两个或多个路径命令并减少 SVG 中绘图路径命令的数量。
您需要获取每个路径段的长度,因为您是 animating/transitioning stroke-dasharray/stroke-dashoffset
个值。
这是一个简化的例子,使用的路径显示了一个圆周为 100(直径 = 31,832 * π)的完美圆。该路径由 4 个段组成,段长度应为 25 个单位。
获取所有段长度的辅助函数基于 @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
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>
预期的段数组将是:
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}
];
通过同时保存偏移值,我们可以过渡到一个片段(包括之前的片段),但也可以为单个片段设置动画。
stroke-dashoffset="0" stroke-dasharray="25 75"
将显示第一个(右下)段笔划。
获得所有段后,您可以将这些数据存储到一个数组或 json 静态。由于长度计算会创建一个临时 DOM 元素 – 因此您可能会遇到一些性能问题,具体取决于 svg 的复杂性。
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>
作为替代方案,您也可以将段存储在 svg 数据属性中 - 尽管数据属性仍然不符合规范。
但是,它们不应引入渲染问题。
这种方法的一个好处是,您的细分数据可以直接保存在您的 svg markup/file 中,同时减少您的 js 文件。
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>