updating SVG animateMotion path using JavaScript - javascript

I try changing an SVG motion path according to a html select value using JS. The path updates as expected, but the element, which uses the path as a motion path continues to move along the original path. What am I missing?
function changepath(selectObject) {
let value = selectObject.value;
let path = document.getElementById("planePath");
let plane = document.getElementById("animPath");
let rotation = "rotate(" + value + ")";
path.setAttribute("transform", rotation);
plane.setAttribute("transform", rotation);
}
body {
background: #eee;
}
.planePath {
opacity: 0.8;
stroke: darkslategrey;
stroke-width: 2px;
fill: none;
}
.plane {
transform: scale(0.15);
}
select {
margin-left: 2em;;
}
<svg viewBox="0 0 2000 200">
<!-- <path class="planePath" id="planePath" d="M 0 0 C 200 250 250 50 550 150 C 850 250 700 180 1000 200 " /> -->
<path class="planePath" id="planePath" d="M 50 100 c 14 -3 736 -115 1900 0" />
<g id="plane" class="plane">
<rect x="0" y="0" width="100" height="100"/>
</g>
<animateMotion xlink:href="#plane" dur="6s" repeatCount="indefinite" rotate="auto">
<mpath id="animPath" xlink:href="#planePath" />
</animateMotion>
</svg>
<select name="route" id="route" onchange="changepath(this)">
<option value="0">0°</option>
<option value="1">1°</option>
<option value="2">2°</option>
<option value="3">3°</option>
<option value="4">4°</option>
</select>

As Danny mentioned in his answer the mpath is using the untransformed path. If you need it to be transformed you can wrap both the path and the animated rect in a group and transform the group.
<svg viewBox="0 0 2000 200">
<g transform="rotate(5)">
<path id="path" fill="none" stroke="red" stroke-width="5"
d="M 50 100 c 14 -3 736 -115 1900 0" />
<rect id="block" x="0" y="0" width="20" height="20"/>
<animateMotion href="#block" dur="2s" repeatCount="indefinite"
rotate="auto" restart="always">
<mpath href="#path" />
</animateMotion>
</g>
</svg>

Looks like animateMotion mpath can't handle the transform
<svg viewBox="0 0 2000 200">
<path id="NO_rotate" fill="none" stroke="green" stroke-width="5"
d="M 50 100 c 14 -3 736 -115 1900 0"/>
<path id="path" fill="none" stroke="red" stroke-width="5"
d="M 50 100 c 14 -3 736 -115 1900 0"
transform="rotate(5)"/>
<rect id="block" x="0" y="0" width="20" height="20"/>
<animateMotion href="#block" dur="2s" repeatCount="indefinite"
rotate="auto" restart="always">
<mpath href="#path" />
</animateMotion>
</svg>

Related

svg animation of drawing line with marker

I'm trying to make an animation of a growing arrow. The path is drawn correctly, but the marker arrow is immediately at the end. Can you please tell me how can I attach a marker to the end of the path so that it moves with it?
<?xml version="1.0" encoding="UTF-8"?>
<svg width="291px" height="260px" viewBox="0 0 391 260" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Path</title>
<defs>
<marker id="Triangle" viewBox="0 0 10 10" refX="1" refY="5"
markerUnits="strokeWidth" markerWidth="4" markerHeight="3"
orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" fill="context-stroke"/>
</marker>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path marker-end="url(#Triangle)" d="M273.097656,120.507813 C201.899566,163.577543 130.777516,213.94793 50.8398438,240.160156 C36.9248074,244.723012 17.4914196,262.184399 8.2265625,250.84375 C-1.53762975,238.89189 20.198756,222.272258 24.0078125,207.316406 C27.3670238,194.126823 28.5689142,180.441602 29.6132812,166.871094 C30.9603726,149.366986 31.1766739,131.782428 31.171875,114.226563 C31.1623478,79.3735161 8.15793288,37.1795952 29.5703125,9.6796875 C43.1473611,-7.75730878 67.7544299,32.013528 87.5742187,41.7890625 C105.639606,50.6992894 124.365537,58.2317755 143.085938,65.6679688 C150.003672,68.4158594 157.202901,70.4330349 164.40625,72.3085938 C177.173796,75.6329203 190.335014,77.4306133 202.960938,81.2578125 C220.824973,86.6728004 237.747783,94.999359 255.734375,99.9921875 C266.927708,103.099302 278.679688,103.638021 290.152344,105.460938" id="Path" stroke="#979797" stroke-width="10"></path>
</g>
</svg>
html,
body {
display: grid;
place-items: center;
min-height: 100vh;
}
svg {
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
pointer-events: none;
animation: animateDash 2s linear forwards infinite;
}
#keyframes animateDash {
to {
stroke-dashoffset: 0;
}
}
code https://codepen.io/rostislav_blatman/pen/JjBOWMB
UPD:
Or perhaps there is another way how to achieve a similar result. Ideally, I want to bind the length of the arrow to the page scroll percentage
<marker> elements refer to the actual geometry of an element
Since you don't actually change/manipulate the <path> while animating – all markers will stick to their initial position.
Markers can only be aligned to:
element starting point: marker-start
path command end point: marker-mid
element end points: marker-end
In other words: you can't animate/move markers unless you're changing the element's geometry.
<h3>Animate path d: marker moves according to geometry change</h3>
<svg viewBox="0 0 391 260" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrow" overflow="visible" viewBox="0 0 10 10" refX="0" refY="0" markerUnits="strokeWidth" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
<path id="markerArrow" d="M -5 -5 l 10 5 l -10 5 z" opacity="0.5" />
</marker>
<marker id="markerStart" overflow="visible" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="strokeWidth" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
<circle cx="5" cy="5" r="5" fill="green"></circle>
</marker>
<marker id="markerMid" overflow="visible" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="strokeWidth" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
<circle cx="5" cy="5" r="5" fill="orange"></circle>
</marker>
</defs>
<g stroke-width="1" fill="none" stroke="#ccc">
<path marker-end="url(#arrow)" marker-mid="url(#markerMid)" marker-start="url(#markerStart)" id="motionPath3" pathLength="100" d="M0 50 l0 0 l0 0 l0 0">
<animate xlink:href="#motionPath3" attributeName="d" attributeType="XML"
from="M0 50 l0 0 l0 0 l0 0"
to="M20 50 l25 0 l50 0 l100 0"
dur="2s"
fill="freeze"
repeatCount="indefinite" />
</path>
</g>
</svg>
Animate an element via <mpath>
create a reusable <path> element for the motion path within a <defs> element
add a marker/arrow element here as well
add a svg SMIL animation via <animateMotion>:
<animateMotion dur="6s" repeatCount="indefinite" rotate="auto">
<mpath href="#motionPath" />
</animateMotion>
Alernative: Css offset-path
Spoiler: current browser support (2023) is unfortunately still a bit spotty.
body {
margin: 1em;
}
svg {
width: 20em;
border: 1px solid #ccc;
overflow: visible;
}
.strokeBG {
marker-start: url(#markerStart);
marker-mid: url(#markerRound);
stroke-width: 0.33%;
}
.strokeAni {
stroke-dasharray: 0 100;
animation: animateDash 6s linear forwards infinite;
}
#keyframes animateDash {
to {
stroke-dasharray: 100 100;
}
}
.strokeMarker2 {
offset-path: path( "M273.1 120.5c-71.2 43.1-142.3 93.4-222.3 119.7c-13.9 4.5-33.3 22-42.6 10.6c-9.7-11.9 12-28.5 15.8-43.5c3.4-13.2 4.6-26.9 5.6-40.4c1.4-17.5 1.6-35.1 1.6-52.7c0-34.8-23-77-1.6-104.5c13.5-17.5 38.2 22.3 58 32.1c18 8.9 36.8 16.4 55.5 23.9c6.9 2.7 14.1 4.7 21.3 6.6c12.8 3.3 25.9 5.1 38.6 9c17.8 5.4 34.7 13.7 52.7 18.7c11.2 3.1 23 3.6 34.5 5.5");
animation: followpath 6s linear infinite;
}
#keyframes followpath {
to {
motion-offset: 100%;
offset-distance: 100%;
}
}
<h3>Svg SMIL</h3>
<svg viewBox="0 0 391 260" xmlns="http://www.w3.org/2000/svg">
<defs>
<path id="motionPath" pathLength="100" d="M273.1 120.5c-71.2 43.1-142.3 93.4-222.3 119.7c-13.9 4.5-33.3 22-42.6 10.6c-9.7-11.9 12-28.5 15.8-43.5c3.4-13.2 4.6-26.9 5.6-40.4c1.4-17.5 1.6-35.1 1.6-52.7c0-34.8-23-77-1.6-104.5c13.5-17.5 38.2 22.3 58 32.1c18 8.9 36.8 16.4 55.5 23.9c6.9 2.7 14.1 4.7 21.3 6.6c12.8 3.3 25.9 5.1 38.6 9c17.8 5.4 34.7 13.7 52.7 18.7c11.2 3.1 23 3.6 34.5 5.5" />
<path id="marker" d="M -5 -5 l 10 5 l -10 5 z" />
</defs>
<g id="Page-1" stroke-width="1" fill="none">
<use class="strokeBG" href="#motionPath" stroke="#eee" stroke-width="10" />
<use class="strokeAni" href="#motionPath" stroke="#979797" stroke-width="10" />
<use class="strokeMarker" href="#marker" stroke="#979797" stroke-width="10">
<animateMotion dur="6s" repeatCount="indefinite" rotate="auto">
<mpath href="#motionPath" />
</animateMotion>
</use>
<use class="strokeMarker" href="#marker" fill="red" />
</g>
</svg>
<h3>Css offset path</h3>
<svg viewBox="0 0 391 260" xmlns="http://www.w3.org/2000/svg">
<defs>
<path id="motionPath2" pathLength="100" d="M273.1 120.5c-71.2 43.1-142.3 93.4-222.3 119.7c-13.9 4.5-33.3 22-42.6 10.6c-9.7-11.9 12-28.5 15.8-43.5c3.4-13.2 4.6-26.9 5.6-40.4c1.4-17.5 1.6-35.1 1.6-52.7c0-34.8-23-77-1.6-104.5c13.5-17.5 38.2 22.3 58 32.1c18 8.9 36.8 16.4 55.5 23.9c6.9 2.7 14.1 4.7 21.3 6.6c12.8 3.3 25.9 5.1 38.6 9c17.8 5.4 34.7 13.7 52.7 18.7c11.2 3.1 23 3.6 34.5 5.5" />
<path id="marker2" d="M -5 -5 l 10 5 l -10 5 z" />
</defs>
<g stroke-width="1" fill="none">
<use class="strokeAni" href="#motionPath2" stroke="#979797" stroke-width="10" />
<use class="strokeMarker2" href="#marker2" stroke="#979797" stroke-width="10">
</g>
</svg>
<!-- markers to show commands -->
<svg id="svgMarkers" style="width:0; height:0; position:absolute; z-index:-1;">
<defs>
<marker id="markerStart" overflow="visible" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="strokeWidth" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
<circle cx="5" cy="5" r="5" fill="green"></circle>
<marker id="markerRound" overflow="visible" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="strokeWidth" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
<circle cx="5" cy="5" r="2.5" fill="red"></circle>
</marker>
</defs>
</svg>

Restart SVG animation sequence from Javascript

I'm trying to restart SVG animation sequence from Javascript. Restart-button below will broke SVG animation sequence. How to restart/reset entire sequence?
document.getElementById("button").addEventListener("click", function() {
document.getElementById("fore").beginElement();
});
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 150">
<path id="track" fill="none" stroke="#000000" stroke-width="1" stroke-dasharray="10,10" d="M 50 100 L 950 50"/>
<path id="plane" d="M-10 -10 L10 0L-10 10z" fill="red" />
<animateMotion xlink:href="#plane"
id="fore"
begin="0s;back.end"
dur="2s"
fill="freeze"
repeatCount="1"
rotate="auto"
keyPoints="0;1"
keyTimes="0;1"
><mpath xlink:href="#track" /></animateMotion>
<animateMotion xlink:href="#plane"
id="back"
begin="fore.end"
dur="2s"
fill="freeze"
repeatCount="1"
rotate="auto-reverse"
keyPoints="1;0"
keyTimes="0;1"
><mpath xlink:href="#track" /></animateMotion>
</svg>
<button id="button">RESTART</button>
You can use setCurrentTime on the entire svg element. I added an id of svgEl to the svg node and then when rest is clicked we do:
document.getElementById('svgEl').setCurrentTime(0);
Have a look at this:
document.getElementById("button").addEventListener("click", function() {
document.getElementById('svgEl').setCurrentTime(0);
});
<svg id="svgEl" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 150">
<path id="track" fill="none" stroke="#000000" stroke-width="1" stroke-dasharray="10,10" d="M 50 100 L 950 50"/>
<path id="plane" d="M-10 -10 L10 0L-10 10z" fill="red" />
<animateMotion xlink:href="#plane"
id="fore"
begin="0s;back.end"
dur="2s"
fill="freeze"
repeatCount="1"
rotate="auto"
keyPoints="0;1"
keyTimes="0;1"
restart="always"
><mpath xlink:href="#track" /></animateMotion>
<animateMotion xlink:href="#plane"
id="back"
begin="fore.end"
dur="2s"
fill="freeze"
repeatCount="1"
rotate="auto-reverse"
keyPoints="1;0"
keyTimes="0;1"
restart="always"
><mpath xlink:href="#track" /></animateMotion>
</svg>
<button id="button">RESTART</button>

How do I animate a Svg path vertically?

Move.gif
I want to animate this path up and down (vertically). I use animationMotion for this, but my path is moving in different directions.
<svg width="698" height="745" viewBox="0 0 698 645" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="bottom-arrow" d="M404.767 578.541L414.523 592.852L397.251 594.146L404.767 578.541Z" stroke="#EEE8FB" stroke-width="2"
>
<animateMotion
path="M0,0 0 50 90 0 90 10"
begin="0s" dur="2s" repeatCount="indefinite"
/>
</path>
</svg>
Hope you got my point
You just need to give proper path for animateMotion. In your code it should be path="M0,0 0 90 0 0"
For more information, check this link
<svg width="698" height="745" viewBox="0 0 698 645" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="bottom-arrow" d="M404.767 578.541L414.523 592.852L397.251 594.146L404.767 578.541Z" stroke="#EEE8FB" stroke-width="2"
>
<animateMotion
path="M0,0 0 90 0 0"
begin="0s" dur="2s" repeatCount="indefinite"
/>
</path>
</svg>

SVG Animation works in Chrome, but doesn't in Firefox and other browsers

Okay, so I have an SVG that is basically a clock animation. It works as intended in Chrome when I'm testing it, but loses form when I try it in Firefox or Safari.
This is the SVG in question:
<svg id="svg" width="100%" viewBox="-400 -150 800 300" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1">
<defs>
<path id="sec" d="M0,-140A140,140 0 0,1 0,140A140,140 0 0,1 0,-140" stroke-dasharray="880" stroke-dashoffset="-880.1"
fill="none">
<animate id="second" attributeName="stroke-dashoffset" dur="1s" repeatCount="60" begin="0s;second.end" additive="sum"
accumulate="sum" calcMode="spline" values="0;-14.66" keyTimes="0;1" keySplines="0.42 0.0 0.58 1.0" />
</path>
<path id="min" d="M0,-130A130,130 0 0,1 0,130A130,130 0 0,1 0,-130" stroke-dasharray="817" stroke-dashoffset="-817.1"
fill="none">
<animate id="minute" attributeName="stroke-dashoffset" dur="60s" repeatCount="60" begin="0s;minute.end" additive="sum"
accumulate="sum" calcMode="spline" values="0;0;-13.613" keyTimes="0;0.9833;1" keySplines="0,0,1,1;0.42 0.0 0.58 1.0" />
</path>
<path id="hr" d="M0,-120A120,120 0 0,1 0,120A120,120 0 0,1 0,-120" stroke-dasharray="754" stroke-dashoffset="-754.1"
fill="none">
<animate id="hour" attributeName="stroke-dashoffset" dur="3600s" repeatCount="12" begin="0s;hour.end" additive="sum"
accumulate="sum" calcMode="spline" values="0;0;-62.83" keyTimes="0;0.9997222;1" keySplines="0,0,1,1;0.42 0.0 0.58 1.0" />
</path>
<mask id="mask" maskUnits="userSpaceOnUse" x="-150" y="-150" width="300" height="300">
<g stroke-width="10" stroke-linecap="round" stroke="white">
<use xlink:href="#sec" x="0" y="0" />
<use xlink:href="#min" x="0" y="0" />
<use xlink:href="#hr" x="0" y="0" />
</g>
</mask>
</defs>
<g stroke-width="7" stroke-linecap="round" mask="url(#mask)">
<g stroke="hsla(0, 95%, 25%, 1)">
<use xlink:href="#sec" />
</g>
<g stroke="hsla(188, 15%, 35%, 1)">
<use xlink:href="#min" />
</g>
<g stroke="hsla(218, 5%, 15%, 1)">
<use xlink:href="#hr" />
</g>
</g>
</svg>
<script>
window.onload = function () {
var now = new Date();
var h = now.getHours(), m = now.getMinutes(), s = now.getSeconds();
var curr = h * 60 * 60 + m * 60 + s;
svg.setCurrentTime(curr);
};
</script>
The second hand moves to the next position all the way from the top instead of animating from the previous place. You can test this yourself. This is the link to the code hosted on codepen.

update water level based on the value

I am trying to understand the svg and javascript implementation. I have a vessel with water to show the level of water. I need to update the water level based on the value I get from the input box. The path is used to show the water level and its very difficult to update the level. How should i update the water level?
Here is the code
// 100 - full
// 75 - average
// 50 - half
// 25 - Low
// 0 - Empty
document.getElementsByName('water-level')[0].addEventListener('change', updateWaterLevel);
function updateWaterLevel() {
console.log('e', this.value);
}
<input type="number" value="" name="water-level"/>
<svg viewBox="0 0 600 600" >
<defs>
<mask id="liquidMask">
<path id="tubeLiquidShape" fill="#FFFFFF" d="M246.6,358.9l110.2-0.6c0.6,0,1.1-0.2,1.5-0.6c0.1-0.1,0.2-0.3,0.3-0.4
c0.4-0.6,0.5-1.4,0.2-2c0,0-12.3-28.7-17.9-41.9c-28.6,6.6-55.4-3.9-78.1,0.1c-9,21-18.2,42.5-18.2,42.5c-0.2,0.7-0.2,1.4,0.2,2
C245.2,358.6,245.9,358.9,246.6,358.9z" />
<g id="bubbleGroup">
<circle cx="267.3" cy="371" r="10.3" fill="#000000" />
<circle cx="324.3" cy="390" r="10.3" fill="#111111" />
<circle cx="288.6" cy="386.3" r="6.6" fill="#7f7f7f" />
<circle cx="288.6" cy="368.5" r="7.6" fill="#2d2d2d" />
<circle cx="340.3" cy="370" r="3" fill="#333333" />
<circle cx="300" cy="378.3" r="3" />
<circle cx="279.4" cy="379.7" r="2" />
<circle cx="337.3" cy="363" r="2" />
<circle cx="309.7" cy="383.2" r="2" />
<circle cx="309.7" cy="371" r="4.3" />
<circle cx="327" cy="368.5" r="6.1" />
</g>
</mask>
</defs>
<g id="tubeGroup">
<path id="tubeOutline" fill="#444444" d="M268.4,224.7c-4.1,0-8,1.6-10.9,4.5c-2.9,2.9-4.5,6.8-4.5,10.9l0,12.3
c0,4.1,1.6,8,4.5,10.9c2,2,4.6,3.5,7.3,4.1l-35.4,82.8c-2.2,5.7-1.5,12.1,1.9,17.1c3.4,5.1,9.2,8.1,15.3,8.1l110.1-0.6
c4.8,0,9.3-1.8,12.7-5.1c0.1-0.1,0.3-0.3,0.3-0.3c0.9-0.9,1.7-1.9,2.4-2.9c3.4-5.2,4-11.8,1.5-17.4l-35-81.5c2.9-0.6,5.6-2,7.7-4.2
c2.9-2.9,4.5-6.8,4.5-10.9l0-12.3c0-4.1-1.6-8-4.5-10.9s-6.8-4.5-10.9-4.5L268.4,224.7z M283.3,255.5l-14.9,0
c-1.7,0-3.1-1.4-3.1-3.1l0-12.3c0-1.7,1.4-3.1,3.1-3.1l67.3,0c1.7,0,3.1,1.4,3.1,3.1v12.3c0,1.7-1.4,3.1-3.1,3.1l-15.4,0l42.2,98.3
c0.8,1.9,0.6,4.1-0.5,5.8c-0.3,0.4-0.6,0.8-0.9,1.1c-1.1,1.1-2.6,1.7-4.2,1.7L246.6,363c-2,0-3.9-1-5.1-2.7
c-1.1-1.7-1.4-3.8-0.6-5.7L283.3,255.5z" stroke-width="1" />
<g id="maskedLiquid" mask="url(#liquidMask)">
<path id="tubeLiquid" fill="#74ccf4" d="M246.6,358.9l110.2-0.6c0.6,0,1.1-0.2,1.5-0.6c0.1-0.1,0.2-0.3,0.3-0.4
c0.4-0.6,0.5-1.4,0.2-2c0,0-12.3-28.7-17.9-41.9c-28.6,6.6-55.4-3.9-78.1,0.1c-9,21-18.2,42.5-18.2,42.5c-0.2,0.7-0.2,1.4,0.2,2
C245.2,358.6,245.9,358.9,246.6,358.9z" />
</g>
</g>
<g id="waterLevel">
<text x="380" y="340" font-size="24" fill="#555">Low</text>
</g>
</svg>
I can read the value but no idea on how do i update the water level which uses complex path
Here's a simplified version of your SVG, with an adjustable water level.
// 100 - full
// 75 - average
// 50 - half
// 25 - Low
// 0 - Empty
document.getElementsByName('water-level')[0].addEventListener('change', updateWaterLevel);
function updateWaterLevel() {
//console.log('e', this.value);
// Top of bottle/liquid is at y=225. Bottom is at y=375.
// Liquid starts in the "full" position.
// So for water level 100, we move the liquid down 0.
// For water level 0, we move the liquid down 150 (375 - 225).
var fractionFull = this.value / 100;
var dy = (1 - fractionFull) * 150;
document.getElementById("tubeLiquid").setAttribute("transform", "translate(0," + dy +")");
}
svg {
width: 500px;
}
<input type="number" value="" name="water-level"/>
<svg viewBox="0 0 600 600" >
<defs>
<mask id="bottleMask">
<path fill="#FFFFFF" d="M 275,225 L 225,375 L 375,375 L 325,225 Z" />
</mask>
</defs>
<g id="maskedLiquid" mask="url(#bottleMask)">
<path id="tubeLiquid" fill="#74ccf4" d="M 225,225 Q 262,220, 300,225 Q 337,230, 375,225 L 375,375 L 225,375 Z" />
</g>
<path id="bottleShape" d="M 275,225 L 225,375 L 375,375 L 325,225 Z" fill="none" stroke="#444444" stroke-width="10"/>
<g id="waterLevel">
<text x="380" y="340" font-size="24" fill="#555">Low</text>
</g>
</svg>

Categories