I would like to animate an image on a path and show the route of the object (e.g a plane flying over a map). It should look like on this image:
But the dashes should apply after the object has reached the position, so the dashes were shown after the object.
I have tried multiple times, but I can do only once. Dash animation or plane on path . Does someone knows a solution.
animate a mask over the dashed path
move the plane along the same path
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1" height="200" width="400">
<defs>
<path id="basePath" d="M 50,150 A 280 500 0 0 1 350,150" />
<mask id="mask">
<use xlink:href="#basePath" stroke-width="3" stroke="white"
stroke-dasharray="1000,0" fill="none">
<animate attributeName="stroke-dasharray" from="0,348.5" to="348.5,0"
begin="0s" dur="5s" fill="freeze" />
</use>
</mask>
</defs>
<circle r="4" cx="50" cy="150" fill="grey" />
<circle r="4" cx="350" cy="150" fill="grey" />
<use xlink:href="#basePath" stroke-width="2" stroke-dasharray="10"
stroke="grey" fill="none" mask="url(#mask)"/>
<path d="M 27,3 H 21 L 13,15 H 9 L 12,3 H 5 L 3,7 H -1 L 1,0 -1,-7 H 3 L 5,-3 H 12 L 9,-15 H 13 L 21,-3 H 27 C 33,-3 33,3 27,3 Z"
fill="white" stroke="black" stroke-width="1.5">
<animateMotion rotate="auto" begin="0s" dur="5s" fill="freeze">
<mpath xlink:href="#basePath"/>
</animateMotion>
</path>
</svg>
The main thing to do is to calculate the length of the path, so you can set the stroke-dasharray values for the mask animation such that they keep pace with the animated plane. You can get that length in Javascript with
document.querySelector('#basePath').getTotalLength()
Related
I have two SVG paths that have a gap between them.
From reading through other questions (in particular this one) I understand this is because of the native anti-aliasing properties of SVGs.
So I added shapeRendering="crispEdges"
This does remove the gap. However it results in jagged edges because of the removal of anti-aliasing.
<svg height="300" width="300" shapeRendering="crispEdges">
<path
d="M150 10 a120 120 0 0 1 103.9230 60"
fill="none"
stroke="green"
stroke-width="20"
/>
<path
d="M253.9230 70 a120 120 0 0 1 0 120"
fill="none"
stroke="green"
stroke-width="20"
/>
</svg>
I've also tried the suggestion in this question to add crispEdges to the parent svg of the path and add shapeRendering="optimizeQuality" to the path but that didn't work.
How can I remove the gap AND keep the smooth edges of my svg path?
You could also mitigate this gap rendering effect by applying a subtle svg feMorphology dilate filter – resulting in slightly expanded strokes closing thin gaps between paths:
SVG feMorphology filter
svg {
overflow: visible;
}
.chart path {
filter: url(#outline);
}
path:hover {
stroke: red;
}
<svg height="300" width="300" shape-rendering="geometricPrecision">
<g class="original">
<path d="M150 10 a120 120 0 0 1 103.9230 60" fill="none" stroke="green" stroke-width="20" />
<path d="M253.9230 70 a120 120 0 0 1 0 120" fill="none" stroke="green" stroke-width="20" />
</g>
<text x="50%" y="50%">Original</text>
</svg>
<svg height="300" width="300">
<filter filterUnits="userSpaceOnUse" id="outline" >
<feMorphology in="SourceGraphic" result="DILATED" operator="dilate" radius="0.5" />
</filter>
<g class="chart">
<path d="M150 10 a120 120 0 0 1 103.9230 60" fill="none" stroke="green" stroke-width="20" />
<path d="M253.9230 70 a120 120 0 0 1 0 120" fill="none" stroke="green" stroke-width="20" />
</g>
<text x="50%" y="50%">Dilate filter</text>
</svg>
But this approach will also introduce slightly rounded edges (you can see this effect on hover).
More importantly, svg filters are quite expensive with regards to rendering performance – rather negligible if you only display a few elements per page view.
Add concatenated background path
As suggested by #Robert Longson: you could also prepend a background path based on concatenated d path data.
This task could be achieved with a javaScript helper method cloning the first path and displaying the concatenated paths.
addBgPaths(".addBGPath");
function addBgPaths(selector) {
let addPathSvgs = document.querySelectorAll(selector);
addPathSvgs.forEach(function(svg) {
let paths = document.querySelectorAll(".addBGPath path");
let firstPath = paths[0];
let firstPathCloned = firstPath.cloneNode();
//cloned elements shouldn't have ids to avoid non unique ids
firstPathCloned.removeAttribute("id");
let dArr = [firstPath.getAttribute("d")];
for (let i = 1; i < paths.length; i++) {
let path = paths[i];
let d = path.getAttribute("d");
dArr.push(d);
}
firstPathCloned.setAttribute("d", dArr.join(" "));
svg.insertBefore(firstPathCloned, svg.children[0]);
});
}
<svg height="300" width="300" shape-rendering="geometricPrecision">
<path d="M150 10 a120 120 0 0 1 103.9230 60" fill="none" stroke="green" stroke-width="20" />
<path d="M253.9230 70 a120 120 0 0 1 0 120" fill="none" stroke="green" stroke-width="20" />
<text x="0" y="50%">Original</text>
</svg>
<svg class="addBGPath" height="300" width="300" shape-rendering="geometricPrecision">
<path id="first2" d="M150 10 a120 120 0 0 1 103.9230 60" fill="none" stroke="green" stroke-width="20" />
<path d="M253.9230 70 a120 120 0 0 1 0 120" fill="none" stroke="green" stroke-width="20" />
<text x="0" y="50%">Add bg path</text>
</svg>
<svg class="addBGPath" height="300" width="300" shape-rendering="geometricPrecision">
<path id="first" d="M150 10 a120 120 0 0 1 103.9230 60" fill="none" stroke="green" stroke-width="20" />
<path d="M253.9230 70 a120 120 0 0 1 0 120" fill="none" stroke="red" stroke-width="20" />
<text x="0" y="50%">Add bg path (red)</text>
</svg>
<svg height="300" width="300" shape-rendering="geometricPrecision">
<path d="M150 10 a120 120 0 0 1 103.9230 60" fill="none" stroke="green" stroke-width="20" />
<path d="M253.9230 70 a120 120 0 0 1 0 120" fill="none" stroke="red" stroke-width="20" />
<text x="0" y="50%">Original (red)</text>
</svg>
If all your path segments have the same color, this is probably the most elegant solution.
But this approach will also introduce colored "halos" when segments use different stroke colors (example #3 compared to #4).
If you able to edit the svg in editor, you can overlap like this. The darker green is the intersection between two paths.
As a quick fix, you can make the ends overlap with stroke-linecap="square"
But ideally, you need to create a single path instead of two separate paths.
<svg height="300" width="300" shapeRendering="crispEdges">
<path
d="M150 10 a120 120 0 0 1 103.9230 60"
fill="none"
stroke="green"
stroke-width="20"
stroke-linecap="square"
/>
<path
d="M253.9230 70 a120 120 0 0 1 0 120"
fill="none"
stroke="green"
stroke-width="20"
stroke-linecap="square"
/>
</svg>
I'm building a Gauge chart in a presentational component in React.
I just need to pass it a percentage and let the component do the rest. I can't use any animations because I'm taking a screenshot of the component to place the image in a Powerpoint presentation.
Here's a screenshot of what I'm trying to do:
As you can see in my code snippet, the circle <marker> is being positioned at the end of the grey <path> instead of at the end of the green <path>. How could I position the circle so it sits at the stroke-linecap of the green <path> as in the image above?
Here's the HTML code I have so far:
<div style="width:400px">
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker
id="dot"
viewBox="0 0 10 10"
refX="4"
refY="4"
markerWidth="4"
markerHeight="4"
>
<circle
cx="4"
cy="4"
r="2"
fill="#FFFFFF"
stroke="#008000"
stroke-width="2"
/>
</marker>
</defs>
<path d="M20,60a35,35 0 1,1 60,0" stroke="#D3D7DB" stroke-width="4" fill="none" stroke-linecap="round"></path>
<path d="M20,60a35,35 0 1,1 60,0" stroke="#008000" stroke-width="6" pathLength="100" fill="none" stroke-linecap="round" stroke-dasharray="75 35" marker-end="url(#dot)"></path>
</svg>
</div>
You can do it all in SVG by setting pathLength and using animationMotion to position the circle.
Some JavaScript and a W3C standard Web Component (supported in all modern Browsers) help in putting multiple gauges on screen and making them dynamic.
customElements.define("svg-gauge", class extends HTMLElement {
connectedCallback() {
let speed = 0.5; // set to 0.0001 for instant display!
let arc = "M20,60a35,35 0 1,1 60,0";
let col = this.getAttribute("color") || "green";
this.innerHTML =
`<input type="range" min="0" max="100" step="5" value="0"`+ // delete 2 lines
` oninput="this.parentNode.percent=this.value" /><br>`+ // just for demo
`<svg viewbox="0 0 100 100">
<path d="${arc}" stroke="grey" stroke-width="6" fill="none" stroke-linecap="round"></path>
<path id="P" d="${arc}" stroke="${col}" stroke-width="8" pathLength="100" fill="none" stroke-linecap="round" stroke-dasharray="75 35"/>
<circle stroke="${col}" cx="0" cy="0" fill="#fff" r="4" stroke-width="3">
<animateMotion dur="${speed}s" path="${arc}" keyPoints="0;0.75" fill="freeze" keyTimes="0;1" calcMode="linear"/>
</circle>
<text x="50%" y="40%" text-anchor="middle">XXX</text>
</svg>`;
this.style.display='inline-block';
this.percent = this.getAttribute("value") || "50";
}
set percent(val = 0) {
this.setAttribute("value", val);
let dash = val + " " + (105 - val);
this.querySelector("#P").setAttribute('stroke-dasharray', dash);
this.querySelector("animateMotion").setAttribute('keyPoints', '0;'+val/100);
this.querySelector("text").innerHTML =`${val} %`;
this.querySelector("animateMotion").beginElement();
this.querySelector("input").value = val;
}
})
<svg-gauge value="35" color="red" ></svg-gauge>
<svg-gauge value="50" color="orange"></svg-gauge>
<svg-gauge value="75" color="green" ></svg-gauge>
I went ahead and accepted Danny '365CSI' Engelman's answer above, but just in case anyone wants to do this without the animations here is how I ended up implementing it:
<div style="width:400px">
<svg viewBox="0 -10 100 100" xmlns="http://www.w3.org/2000/svg">
<path d="M20,60a35,35 0 1,1 60,0" stroke="#D3D7DB" stroke-width="4" fill="none" stroke-linecap="round"></path>
<path d="M20,60a35,35 0 1,1 60,0" stroke="#008000" stroke-width="6" pathLength="100" fill="none" stroke-linecap="round" stroke-dasharray="50 85"></path>
<circle
cx="0"
cy="0"
r="6"
stroke-width="6"
fill="#FFFFFF"
stroke="#008000"
>
<animateMotion
begin="0s"
dur="infinite"
repeatCount="infinite"
keyPoints="0.5;0.5"
fill="freeze"
keyTimes="0;1"
calcMode="linear"
path="M20,60a35,35 0 1,1 60,0"
></animateMotion>
</circle>
</svg>
</div>
I am new to SVG and stuck with a small task.
I would like to fill a custom SVG to a specific percentage.
Here is my initial SVG
<svg width="233" height="9" viewBox="0 0 233 9" fill="none" xmlns="http://www.w3.org/2000/svg"><path id="base1" d="M0.0104229 4.53685C0.585564 0.0541843 77.5774 0.498692 134.721 1.59593C171.989 2.31113 232.913 -0.235688 232.75 4.22739C232.525 10.3451 134.045 7.87626 89.0013 7.23356C39.1891 6.52242 -0.727053 10.2816 0.0104229 4.53685Z" fill="#DADBDD"></path></svg>
Here is my final SVG
<svg width="233" height="9" viewBox="0 0 233 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.0104229 4.53685C0.585564 0.0541843 77.5774 0.498692 134.721 1.59593C171.989 2.31113 232.913 -0.235688 232.75 4.22739C232.525 10.3451 134.045 7.87626 89.0013 7.23356C39.1891 6.52242 -0.727053 10.2816 0.0104229 4.53685Z" fill="#DADBDD" />
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="233" height="9">
<path d="M0.0104229 4.53685C0.585564 0.0541843 77.5774 0.498692 134.721 1.59593C171.989 2.31113 232.913 -0.235688 232.75 4.22739C232.525 10.3451 134.045 7.87626 89.0013 7.23356C39.1891 6.52242 -0.727053 10.2816 0.0104229 4.53685Z" fill="#DADBDD" />
</mask>
<g mask="url(#mask0)">
<ellipse rx="49.9644" ry="25.4799" transform="matrix(0.943377 0.331722 -0.657906 0.7531 34.4845 13.7852)" fill="#ED718F" />
</g>
</svg>
I want to fill this with the percentage area as entered by the user.
Any help will be highly appreciated
As I've commented: if you want to use a maska mask you can use a thick line (stroke-width="9" - for example) instead of the ellipse, and you can mask this line. Then you can change the stroke-dasharray according to the percentage you have.
//the total length of the line which in this case is as long as the shape
let tl = theLine.getTotalLength();
// the percentage for the progress
let xperc = itr.value;
onInput();
itr.addEventListener("input", onInput);
// a function that is setting the value for the stroke-dasharray of the line according with the progress
function onInput() {
xperc = itr.value;
theLine.setAttribute("stroke-dasharray", `${tl * xperc} ${tl - tl * xperc}`);
}
<input id="itr" type="range" min="0" max="1" value="0.35" step=".001" /><br>
<svg width="233" height="9" viewBox="0 0 233 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<path id="theShape" d="M0.0104229 4.53685C0.585564 0.0541843 77.5774 0.498692 134.721 1.59593C171.989 2.31113 232.913 -0.235688 232.75 4.22739C232.525 10.3451 134.045 7.87626 89.0013 7.23356C39.1891 6.52242 -0.727053 10.2816 0.0104229 4.53685Z" />
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="233" height="9">
<use xlink:href="#theShape" fill="#fff" />
</mask>
</defs>
<use xlink:href="#theShape" fill="#DADBDD" />
<path id="theLine" d="M0 4.5L233 4.5" stroke="#ED718F" stroke-width="9" mask="url(#mask0)" />
</svg>
svg:
<path id="svg_1" stroke-width="0.5" stroke="black" fill="none" d="m50.610001,63.470001l12.869999,0m0,0l0,464.709991m0,0l-12.869999,0m0,0l0,-464.709991"/>
<path id="svg_2" stroke-width="0.5" stroke="black" fill="none" d="m66.529999,260.670013l12.870003,0m0,0l-0.029999,267.519989m0,0l-12.870003,0m0,0l0.029999,-267.519989"/>
<path id="svg_3" stroke-width="0.5" stroke="black" fill="none" d="m66.519997,275.440002l-3.039997,0"/>
html:
<div ng-include="'example.svg'"></div>
I would like to change fill color for id="svg_2" for example on button click, How to do it?
The problem are your paths. After every line you are using a move to command (m) where you are moving to the last point. I've rewritten the first 2 paths bu removing the move to (m0,0) Now the paths can be filled. The third path is just a line.
Now you can fill the paths using css or bu resetting the value of the attribute fill
path{fill:red}
<svg viewBox="0 0 130 600" width="100">
<path id="svg_1" stroke-width="0.5" stroke="black" fill="none" d="M50.610001,63.470001l12.869999,0
l0,464.709991
l-12.869999,0
l0,-464.709991"/>
<path id="svg_2" stroke-width="0.5" stroke="black" fill="none" d="M66.529999,260.670013l12.870003,0
l-0.029999,267.519989
l-12.870003,0
l0.029999,-267.519989"/>
<path id="svg_3" stroke-width="0.5" stroke="black" fill="none" d="m66.519997,275.440002l-3.039997,0"/>
</svg>
There is an imaginary square which I want to draw as two halves, i.e. two triangles. Whilst they should perfectly fit together and make a square, a tiny line caused by anti-aliasing appears.
These triangles should be of different color, but I left both them black in the given example to make the line more visible.
<svg width="100" height="100"
viewPort="0 0 100 100" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="first">
<path d="M 0 0 L 100 100 L 0 100 Z" fill="red"/>
</clipPath>
<clipPath id="second">
<path d="M 0 0 L 100 0 L 100 100 Z" fill="red"/>
</clipPath>
</defs>
<rect width="100" fill="black" height="100"
clip-path="url(#first)"/>
<rect width="100" fill="black" height="100"
clip-path="url(#second)"/>
</svg>
JSFiddle
I am open to solutions - canvas, WebGL etc. I just want to know possible solutions which would improve rendering.
I'm not sure why you are seeing the line using clip paths, but if I just change it around to using regular polygons and use shape-rendering: crispEdges the line doesn't appear:
<svg width="100" height="100" viewPort="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<polygon points="0,0 100,0 100,100" style="fill:black;shape-rendering:crispEdges;" />
<polygon points="0,0 100,100 0,100" style="fill:black;shape-rendering:crispEdges;" />
</svg>