Related
I'm trying to morph shapes here. I got it to work with polygons. I also got morphing to work with a simple path that has 3 points , but when I'm trying to do something with my actual SVG it just throws me a bunch of errors and is super buggy. All I did was slightly move some points and anchor handles. The final result after the morphing is correct but the actual animation is not at all what I desire. The morphed path is a copy of the original with some points altered. So the amount of points and the path direction should be the same. I even checked in Illustrator.
let test = anime({
targets: `.test path:last-of-type`,
d: [
{ value: 'M113.14,116.59a8,8,0,0,1-3.26-15.31,45.79,45.79,0,0,0,0-83.69A8,8,0,1,1,116.38,3a61.79,61.79,0,0,1,0,112.93A8,8,0,0,1,113.14,116.59Z' },
{ value: 'M113.14,116.59a8,8,0,0,1-3.26-15.31c16.52-7.35,13.84-33.54,27.19-41.85s-10.67-34.5-27.19-41.84A8,8,0,1,1,116.38,3c22.29,9.91,44.73,38.64,21.45,82.6,0,24.39.84,20.41-21.45,30.33A8,8,0,0,1,113.14,116.59Z' }
],
easing: 'easeOutSine',
duration: 2000,
loop: false,
complete: function(anim) {
anim.reverse();
},
autoplay: false
});
Live example
To implement smooth animation, it is necessary that the number of node points and their type match in all paths.
Look at the paths in the vector editor.
The large semicircle has one node point more than the small semicircle. An extra point is indicated in the figure with a red arrow.
An extra point must be removed so that the number of node points is the same on both paths.
After editing, save the file and copy the paths to another file to create animation.
Animation with SVG SMIL
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="25vw" hieght="25vh" viewBox="0 0 153.07 118.86">
<path d="M75.75,8.72V110.15a8.7,8.7,0,0,1-14.83,6.18L35.13,90.74a11.26,11.26,0,0,0-8-3.27H11.29A11.29,11.29,0,0,1,0,76.18V42.68A11.28,11.28,0,0,1,11.29,31.4H27.18a11.27,11.27,0,0,0,8-3.28L60.92,2.54A8.7,8.7,0,0,1,75.75,8.72Z"/>
<path d="M98.63,86.72a8,8,0,0,1-2.85-15.48,12.62,12.62,0,0,0,0-23.61,8,8,0,0,1,5.68-15,28.63,28.63,0,0,1,0,53.52A7.94,7.94,0,0,1,98.63,86.72Z">
<animate
attributeName="d"
begin="0s"
dur="1s"
repeatCount="indefinite"
values="
m 98.63,86.72 c -8.896613,0.01429 -11.168355,-12.324863 -2.85,-15.48 10.87779,-4.111221 10.87779,-19.498779 0,-23.61 C 91.637862,46.06151 89.551515,41.432137 91.120003,37.290001 92.688492,33.147866 97.317862,31.06151 101.46,32.63 c 11.10918,4.225054 18.45261,14.874508 18.45261,26.76 0,11.885492 -7.34343,22.534946 -18.45261,26.76 -0.90058,0.361333 -1.859762,0.554526 -2.83,0.57 z;
m 120.63,116.72 c -13.84288,4.29526 -17.65743,-9.06575 -10.85,-13.48 35.6227,-19.637776 34.69936,-61.423453 7.12758,-81.035919 C 110.18826,18.209024 104.23624,11.222661 108.01033,5.3312852 110.87272,0.86306533 113.31786,1.06151 117.46,2.63 c 10.57498,4.7592492 35.07508,19.514212 34.45261,56.76 -0.37039,22.162492 -8.1082,37.551535 -18.83964,49.45716 -0.90058,0.36133 -9.08042,7.1019 -12.44297,7.87284 z" />
</path>
<path d="m 98.63,86.72 c -8.896613,0.01429 -11.168355,-12.324863 -2.85,-15.48 10.87779,-4.111221 10.87779,-19.498779 0,-23.61 C 91.637862,46.06151 89.551515,41.432137 91.120003,37.290001 92.688492,33.147866 97.317862,31.06151 101.46,32.63 c 11.10918,4.225054 18.45261,14.874508 18.45261,26.76 0,11.885492 -7.34343,22.534946 -18.45261,26.76 -0.90058,0.361333 -1.859762,0.554526 -2.83,0.57 z"/></svg>
Animation morph with AnimeJS
const small = 'm 98.63,86.72 c -8.896613,0.01429 -11.168355,-12.324863 -2.85,-15.48 10.87779,-4.111221 10.87779,-19.498779 0,-23.61 C 91.637862,46.06151 89.551515,41.432137 91.120003,37.290001 92.688492,33.147866 97.317862,31.06151 101.46,32.63 c 11.10918,4.225054 18.45261,14.874508 18.45261,26.76 0,11.885492 -7.34343,22.534946 -18.45261,26.76 -0.90058,0.361333 -1.859762,0.554526 -2.83,0.57 z';
const big = 'm 120.63,116.72 c -13.84288,4.29526 -17.65743,-9.06575 -10.85,-13.48 35.6227,-19.637776 34.69936,-61.423453 7.12758,-81.035919 C 110.18826,18.209024 104.23624,11.222661 108.01033,5.3312852 110.87272,0.86306533 113.31786,1.06151 117.46,2.63 c 10.57498,4.7592492 35.07508,19.514212 34.45261,56.76 -0.37039,22.162492 -8.1082,37.551535 -18.83964,49.45716 -0.90058,0.36133 -9.08042,7.1019 -12.44297,7.87284 z';
var timeline = anime.timeline({
autoplay: true,
direction: "alternate",
loop: true
});
timeline.add({
targets: ".test",
d: {
value: [
small,
big
],
duration: 1000,
easing: "easeOutSine"
},
offset: 200
});
.test {
position: fixed;top:0;
left:0;height: 300px;width:300px;
}
div {
width:25vw;
height:auto;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/animejs/2.2.0/anime.min.js'></script>
<div >
<svg viewBox="0 0 153.07 118.86">
<path d="M75.75,8.72V110.15a8.7,8.7,0,0,1-14.83,6.18L35.13,90.74a11.26,11.26,0,0,0-8-3.27H11.29A11.29,11.29,0,0,1,0,76.18V42.68A11.28,11.28,0,0,1,11.29,31.4H27.18a11.27,11.27,0,0,0,8-3.28L60.92,2.54A8.7,8.7,0,0,1,75.75,8.72Z"/>
<path d="m 98.63,86.72 c -8.896613,0.01429 -11.168355,-12.324863 -2.85,-15.48 10.87779,-4.111221 10.87779,-19.498779 0,-23.61 C 91.637862,46.06151 89.551515,41.432137 91.120003,37.290001 92.688492,33.147866 97.317862,31.06151 101.46,32.63 c 11.10918,4.225054 18.45261,14.874508 18.45261,26.76 0,11.885492 -7.34343,22.534946 -18.45261,26.76 -0.90058,0.361333 -1.859762,0.554526 -2.83,0.57 z"/>
<path class="test" d="m 120.63,116.72 c -13.84288,4.29526 -17.65743,-9.06575 -10.85,-13.48 35.6227,-19.637776 34.69936,-61.423453 7.12758,-81.035919 C 110.18826,18.209024 104.23624,11.222661 108.01033,5.3312852 110.87272,0.86306533 113.31786,1.06151 117.46,2.63 c 10.57498,4.7592492 35.07508,19.514212 34.45261,56.76 -0.37039,22.162492 -8.1082,37.551535 -18.83964,49.45716 -0.90058,0.36133 -9.08042,7.1019 -12.44297,7.87284 z"/>
</svg>
</div>
I have an svg file that has all the countries in the world. Each country has their own individual paths for example:
<path
inkscape:connector-curvature="0"
id="PH"
data-name="Philippines"
data-id="PH"
d="m 1684.6,518.6 -0.6,-2.3 -0.8,-3.2 -4.8,-3 0.8,4.9 -3.9,0.2 -0.7,2.8 -4.2,1.7 -2.2,-2.8 -2.8,2.4 -3.4,1.7 -1.9,5.4 1.1,1.9 3.9,-3.6 2.7,0.3 1.5,-2.7 3.8,3 -1.5,3.1 1.9,4.6 6.8,3.7 1.4,-3 -2.1,-4.7 2.4,-3.2 2.5,6.4 1.5,-5.8 -0.6,-3.5 -0.8,-4.3 z m -14.5,-11.8 0,-6.1 -3.6,6.1 0.5,-4.2 -3,0.3 -0.3,4 -1.2,1.8 -1,1.7 3.8,4.4 1.6,-1.9 1.4,-4 1.8,-2.1 z m -30.1,6.1 2.6,-4.4 3.4,-3.5 -1.5,-5.2 -2.4,6.3 -2.9,4.4 -3.8,4 -2.4,4.4 7,-6 z m 17.4,-16.4 1.2,3 -0.1,3.3 0.5,2.9 3.3,-1.9 2.4,-2.7 -0.2,-2.6 -3.6,0 -3.5,-2 z m 20,-1.7 -1.8,-2.4 -5.4,-0.1 4,4.8 0.3,2.4 -3.3,-0.5 1.2,3.9 1.7,0.3 0.7,4.5 2.5,-1.4 -1.7,-4 -0.4,-2.1 4.5,1.7 -2.3,-7.1 z m -22.9,-5.8 -2.2,-2.3 -4.8,-0.2 3.4,4.8 2.8,3.2 0.8,-5.5 z m -6.4,-34.6 -3.3,0 -0.9,5.8 1.1,9.9 -2.6,-2 1.2,6 1.2,2.8 3.3,3.7 0.4,-2.3 1.8,1.4 -1.5,1.7 0.1,2.6 2.9,1.4 5,-0.9 4,3.8 1.1,-2.4 2.5,3.4 4.8,3.1 0.2,-2.9 -2,-1.6 0.1,-3.4 -7.5,-3.6 -2.3,0.8 -3.1,-0.7 -2,-5.1 0.1,-5.1 3,-2.1 0.6,-5.3 -2.7,-4.6 0.4,-2.6 -0.7,-1.6 -1.5,1.6 -3.7,-1.8 z"
style="fill:#1abc9c;fill-rule:evenodd;" />
<path
inkscape:connector-curvature="0"
id="PL"
data-name="Poland"
data-id="PL"
d="m 1069.4,228.3 -4.6,-0.1 -0.5,-1.4 -4.8,-1.1 -5.7,2.1 -7.1,2.8 -3.1,1.7 1.4,3.1 -1.2,1.6 2,2.2 1.4,3.3 -0.1,2.1 2.3,3.9 2.4,1.9 3.7,0.6 -0.1,1.7 2.7,1.2 0.6,-1.5 3.4,0.6 0.7,2 3.6,0.3 2.6,3.1 0.3,0.4 1.9,-0.9 2.7,2.2 2.8,-1.3 2.4,0.6 3.4,-0.8 4.9,2.3 1.1,0.4 -1.6,-2.8 3.8,-5.1 2.3,-0.7 0.3,-1.8 -3.1,-5.3 -0.5,-2.7 -1.9,-2.9 2.7,-1.2 -0.3,-2.4 -1.7,-2.3 -0.6,-2.7 -1.4,-1.9 -2.5,-0.6 -8.7,0.1 -5.9,-0.7 z"
style="fill:#f2f2f2;fill-rule:evenodd" />
Now I want to access each country through a function in JS. So I added a href before the country:
<a href = "#" onclick = "SelectCountry('ph');" id = 'country_ph'>
<path
inkscape:connector-curvature="0"
id="PH"
data-name="Philippines"
data-id="PH"
d="m 1684.6,518.6 -0.6,-2.3 -0.8,-3.2 -4.8,-3 0.8,4.9 -3.9,0.2 -0.7,2.8 -4.2,1.7 -2.2,-2.8 -2.8,2.4 -3.4,1.7 -1.9,5.4 1.1,1.9 3.9,-3.6 2.7,0.3 1.5,-2.7 3.8,3 -1.5,3.1 1.9,4.6 6.8,3.7 1.4,-3 -2.1,-4.7 2.4,-3.2 2.5,6.4 1.5,-5.8 -0.6,-3.5 -0.8,-4.3 z m -14.5,-11.8 0,-6.1 -3.6,6.1 0.5,-4.2 -3,0.3 -0.3,4 -1.2,1.8 -1,1.7 3.8,4.4 1.6,-1.9 1.4,-4 1.8,-2.1 z m -30.1,6.1 2.6,-4.4 3.4,-3.5 -1.5,-5.2 -2.4,6.3 -2.9,4.4 -3.8,4 -2.4,4.4 7,-6 z m 17.4,-16.4 1.2,3 -0.1,3.3 0.5,2.9 3.3,-1.9 2.4,-2.7 -0.2,-2.6 -3.6,0 -3.5,-2 z m 20,-1.7 -1.8,-2.4 -5.4,-0.1 4,4.8 0.3,2.4 -3.3,-0.5 1.2,3.9 1.7,0.3 0.7,4.5 2.5,-1.4 -1.7,-4 -0.4,-2.1 4.5,1.7 -2.3,-7.1 z m -22.9,-5.8 -2.2,-2.3 -4.8,-0.2 3.4,4.8 2.8,3.2 0.8,-5.5 z m -6.4,-34.6 -3.3,0 -0.9,5.8 1.1,9.9 -2.6,-2 1.2,6 1.2,2.8 3.3,3.7 0.4,-2.3 1.8,1.4 -1.5,1.7 0.1,2.6 2.9,1.4 5,-0.9 4,3.8 1.1,-2.4 2.5,3.4 4.8,3.1 0.2,-2.9 -2,-1.6 0.1,-3.4 -7.5,-3.6 -2.3,0.8 -3.1,-0.7 -2,-5.1 0.1,-5.1 3,-2.1 0.6,-5.3 -2.7,-4.6 0.4,-2.6 -0.7,-1.6 -1.5,1.6 -3.7,-1.8 z"
style="fill:#1abc9c;fill-rule:evenodd;" />
</a>
and access them here:
var phCountry = document.getElementById("PH");
function SelectCountry(country)
{
alert(country);
}
However I want to scale the country the user clicks on. I tried
phCountry.setAttribute("height", "100px");
phCountry.style.height = "100px";
I'm very new to svg and js so I may probably be doing this in the wrong way.
How can I fix this? Thanks a ton!
<path> elements do not have a width or height attribute you can change. What you have to do is apply a transform that scales the country paths when you click on them.
Also, you don't need to add <a> tags to your SVG. Just add a click handler to each path.
See the example below. I've described how it works in the code comments.
// Look for all <path> elements in the SVG and add a click handler to each one
document.querySelectorAll("svg path").forEach(function(item) {
item.addEventListener("click", countryClick);
});
function countryClick(e) {
// e.target is the path we clicked on.
// classList.toggle("enlarge") will cause the "enlarge" class to be added then removed on alternate clicks
e.target.classList.toggle("enlarge");
}
.enlarge {
/* Use the bounds of the path to calculate the transform origin */
transform-box: fill-box;
/* Set the origin for scaling to the centre of the path bounds */
transform-origin: 50% 50%;
/* Scale up by 2 in both the x and y directions */
transform: scale(2, 2);
}
<svg viewBox="1000 200 800 400">
<path
inkscape:connector-curvature="0"
id="PH"
data-name="Philippines"
data-id="PH"
d="m 1684.6,518.6 -0.6,-2.3 -0.8,-3.2 -4.8,-3 0.8,4.9 -3.9,0.2 -0.7,2.8 -4.2,1.7 -2.2,-2.8 -2.8,2.4 -3.4,1.7 -1.9,5.4 1.1,1.9 3.9,-3.6 2.7,0.3 1.5,-2.7 3.8,3 -1.5,3.1 1.9,4.6 6.8,3.7 1.4,-3 -2.1,-4.7 2.4,-3.2 2.5,6.4 1.5,-5.8 -0.6,-3.5 -0.8,-4.3 z m -14.5,-11.8 0,-6.1 -3.6,6.1 0.5,-4.2 -3,0.3 -0.3,4 -1.2,1.8 -1,1.7 3.8,4.4 1.6,-1.9 1.4,-4 1.8,-2.1 z m -30.1,6.1 2.6,-4.4 3.4,-3.5 -1.5,-5.2 -2.4,6.3 -2.9,4.4 -3.8,4 -2.4,4.4 7,-6 z m 17.4,-16.4 1.2,3 -0.1,3.3 0.5,2.9 3.3,-1.9 2.4,-2.7 -0.2,-2.6 -3.6,0 -3.5,-2 z m 20,-1.7 -1.8,-2.4 -5.4,-0.1 4,4.8 0.3,2.4 -3.3,-0.5 1.2,3.9 1.7,0.3 0.7,4.5 2.5,-1.4 -1.7,-4 -0.4,-2.1 4.5,1.7 -2.3,-7.1 z m -22.9,-5.8 -2.2,-2.3 -4.8,-0.2 3.4,4.8 2.8,3.2 0.8,-5.5 z m -6.4,-34.6 -3.3,0 -0.9,5.8 1.1,9.9 -2.6,-2 1.2,6 1.2,2.8 3.3,3.7 0.4,-2.3 1.8,1.4 -1.5,1.7 0.1,2.6 2.9,1.4 5,-0.9 4,3.8 1.1,-2.4 2.5,3.4 4.8,3.1 0.2,-2.9 -2,-1.6 0.1,-3.4 -7.5,-3.6 -2.3,0.8 -3.1,-0.7 -2,-5.1 0.1,-5.1 3,-2.1 0.6,-5.3 -2.7,-4.6 0.4,-2.6 -0.7,-1.6 -1.5,1.6 -3.7,-1.8 z"
style="fill:#1abc9c;fill-rule:evenodd;" />
<path
inkscape:connector-curvature="0"
id="PL"
data-name="Poland"
data-id="PL"
d="m 1069.4,228.3 -4.6,-0.1 -0.5,-1.4 -4.8,-1.1 -5.7,2.1 -7.1,2.8 -3.1,1.7 1.4,3.1 -1.2,1.6 2,2.2 1.4,3.3 -0.1,2.1 2.3,3.9 2.4,1.9 3.7,0.6 -0.1,1.7 2.7,1.2 0.6,-1.5 3.4,0.6 0.7,2 3.6,0.3 2.6,3.1 0.3,0.4 1.9,-0.9 2.7,2.2 2.8,-1.3 2.4,0.6 3.4,-0.8 4.9,2.3 1.1,0.4 -1.6,-2.8 3.8,-5.1 2.3,-0.7 0.3,-1.8 -3.1,-5.3 -0.5,-2.7 -1.9,-2.9 2.7,-1.2 -0.3,-2.4 -1.7,-2.3 -0.6,-2.7 -1.4,-1.9 -2.5,-0.6 -8.7,0.1 -5.9,-0.7 z"
style="fill:#f2f2f2;fill-rule:evenodd" />
</svg>
However there is one extra step you'll inevitably need to do. If you clicked on the square I have added in the first example, then you will have discovered it already. Elements in an SVG have a draw order. Earlier paths in the file are drawn before later ones. So you will find when you enlarge one country, it may be behind another un-enlarged country.
That's probably not what you want. So to fix that, you also need to move the country you are enlarging to the front. You do that by moving it to the end of the SVG.
In the next example, I've added the code to do that.
// Look for all <path> elements in the SVG and add a click handler to each one
document.querySelectorAll("svg path").forEach(function(item) {
item.addEventListener("click", countryClick);
});
function countryClick(e) {
// e.target is the path we clicked on.
// classList.toggle("enlarge") will cause the "enlarge" class to be added then removed on alternate clicks
e.target.classList.toggle("enlarge");
// If we are enlarging, then also move the path to the end of the SVG
if (e.target.classList.contains("enlarge")) {
var svg = e.target.ownerSVGElement;
// appendChild() adds an element to the end of the SVG.
// If it is already in the SVG, it gets moved from it's current position in the order.
svg.appendChild(e.target);
}
}
.enlarge {
/* Use the bounds of the path to calculate the transform origin */
transform-box: fill-box;
/* Set the origin for scaling to the centre of the path bounds */
transform-origin: 50% 50%;
/* Scale up by 2 in both the x and y directions */
transform: scale(2, 2);
}
<svg viewBox="1000 200 800 400">
<path
inkscape:connector-curvature="0"
id="PH"
data-name="Philippines"
data-id="PH"
d="m 1684.6,518.6 -0.6,-2.3 -0.8,-3.2 -4.8,-3 0.8,4.9 -3.9,0.2 -0.7,2.8 -4.2,1.7 -2.2,-2.8 -2.8,2.4 -3.4,1.7 -1.9,5.4 1.1,1.9 3.9,-3.6 2.7,0.3 1.5,-2.7 3.8,3 -1.5,3.1 1.9,4.6 6.8,3.7 1.4,-3 -2.1,-4.7 2.4,-3.2 2.5,6.4 1.5,-5.8 -0.6,-3.5 -0.8,-4.3 z m -14.5,-11.8 0,-6.1 -3.6,6.1 0.5,-4.2 -3,0.3 -0.3,4 -1.2,1.8 -1,1.7 3.8,4.4 1.6,-1.9 1.4,-4 1.8,-2.1 z m -30.1,6.1 2.6,-4.4 3.4,-3.5 -1.5,-5.2 -2.4,6.3 -2.9,4.4 -3.8,4 -2.4,4.4 7,-6 z m 17.4,-16.4 1.2,3 -0.1,3.3 0.5,2.9 3.3,-1.9 2.4,-2.7 -0.2,-2.6 -3.6,0 -3.5,-2 z m 20,-1.7 -1.8,-2.4 -5.4,-0.1 4,4.8 0.3,2.4 -3.3,-0.5 1.2,3.9 1.7,0.3 0.7,4.5 2.5,-1.4 -1.7,-4 -0.4,-2.1 4.5,1.7 -2.3,-7.1 z m -22.9,-5.8 -2.2,-2.3 -4.8,-0.2 3.4,4.8 2.8,3.2 0.8,-5.5 z m -6.4,-34.6 -3.3,0 -0.9,5.8 1.1,9.9 -2.6,-2 1.2,6 1.2,2.8 3.3,3.7 0.4,-2.3 1.8,1.4 -1.5,1.7 0.1,2.6 2.9,1.4 5,-0.9 4,3.8 1.1,-2.4 2.5,3.4 4.8,3.1 0.2,-2.9 -2,-1.6 0.1,-3.4 -7.5,-3.6 -2.3,0.8 -3.1,-0.7 -2,-5.1 0.1,-5.1 3,-2.1 0.6,-5.3 -2.7,-4.6 0.4,-2.6 -0.7,-1.6 -1.5,1.6 -3.7,-1.8 z"
style="fill:#1abc9c;fill-rule:evenodd;" />
<path
inkscape:connector-curvature="0"
id="PL"
data-name="Poland"
data-id="PL"
d="m 1069.4,228.3 -4.6,-0.1 -0.5,-1.4 -4.8,-1.1 -5.7,2.1 -7.1,2.8 -3.1,1.7 1.4,3.1 -1.2,1.6 2,2.2 1.4,3.3 -0.1,2.1 2.3,3.9 2.4,1.9 3.7,0.6 -0.1,1.7 2.7,1.2 0.6,-1.5 3.4,0.6 0.7,2 3.6,0.3 2.6,3.1 0.3,0.4 1.9,-0.9 2.7,2.2 2.8,-1.3 2.4,0.6 3.4,-0.8 4.9,2.3 1.1,0.4 -1.6,-2.8 3.8,-5.1 2.3,-0.7 0.3,-1.8 -3.1,-5.3 -0.5,-2.7 -1.9,-2.9 2.7,-1.2 -0.3,-2.4 -1.7,-2.3 -0.6,-2.7 -1.4,-1.9 -2.5,-0.6 -8.7,0.1 -5.9,-0.7 z"
style="fill:#f2f2f2;fill-rule:evenodd" />
<path d="M 1100,228 h 30 v 30 h -30 z" fill="#ccaa88"/>
</svg>
I have a SVG Pattern which I use as fill in many other SVGs.
Of course, when I change attributes in the pattern afterwards, it is changed in every SVG using this pattern.
Is there a possibility to modify the pattern attributes in a specific instance/use only?
I would like to avoid creating the pattern in 100 different versions before.
EDIT with sample:
This is a sample pattern. I use this icon as fill for a circle on a country on a world map. The circle inside the pattern is used to set the background color.
<svg width="0" height="0">
<defs>
<pattern id="infantry_svg" patternUnits="objectBoundingBox" width="100% " height="100%">
<circle r="10" fill="transparent"></circle>
<path d="M 19.328,3.097 19.025,2.633 18.35,3.045 17.638,2.148 17.555,3.563 15.188,5.011 C 14.818,4.596 14.555,4.484 14.555,4.484 L 10.343,7.079 10.039,6.959 9.871,6.7 13.072,4.919 C 12.811,4.496 12.29,3.65 12.29,3.65 L 9.295,5.893 9.173,5.968 9.028,5.732 8.342,6.155 8.488,6.393 8.276,6.523 7.838,5.811 7.152,6.234 7.59,6.946 7.293,7.129 7.146,6.891 6.46,7.314 6.606,7.551 6.584,7.566 3.881,8.993 4.556,10.089 7.139,8.339 C 7.26,8.536 7.353,8.687 7.353,8.687 l -1.748,1.074 0.293,0.456 -0.373,1.185 -1.518,1.412 c 0,0 -2.772,1.469 -4.007,2.152 0.308,0.691 0.964,1.57 1.443,2.214 0,0 4.855,-4.879 6.706,-4.765 l 1.837,-1.136 c 0,0 -0.752,-0.704 -0.5,-0.864 1.583,1.752 3.062,1.704 3.062,1.704 0.442,-0.568 0.528,-1.221 0.78,-1.834 -1.195,-0.286 -1.226,-0.103 -2.25,-1.111 l 4.673,-2.88 C 15.821,6.048 15.48,5.467 15.48,5.467 l 3.848,-2.37 z m -9.83,8.165 -1.203,0.742 c 0,0 -0.281,-0.437 -0.435,-0.688 l 0.265,-0.16 c 0.168,0.165 0.502,0.417 0.834,0.213 l -0.124,-0.2 C 8.662,11.275 8.461,11.146 8.333,11.03 l 0.743,-0.453 0.422,0.685 z"
fill="#030104" transform="translate(1,1.1) scale(0.29)" stroke="white" stroke-width="0.4" />
</pattern>
</defs>
</svg>
Now when I set this pattern inside a circle on a country, it looks like this:
The circle on the country is generated like this with Snap.svg:
var circle = xs.circle(x, y, 4).attr({ id: "blabla", fill: "url(#infantry_svg)", stroke: "black", strokeWidth: 1 }).appendTo(xs.select('g'));
Directly after this, I set the background color of the pattern used for this circle:
$("#infantry_svg circle").attr({fill: troopOwner_color]});
Now every circle using "infantry_svg" pattern, has "troopOwner_color" as background color of the circle.
But I would like to change only this single instance of the pattern usage.
Whilst you can't do what you want with the specific requirement of the question, ie using a pattern. I'm not sure why you need to use a pattern.
Why not just use a 'use' element for the defs statement, and have a path & circle in there. Then you can just clone it and change the fill...
<defs>
<g id="infantry_svg" >
<circle cx="30" cy="30" r="40" stroke="red" stroke-width="5"/>
<path>path stuff</path>
</defs>
var g = s.g().use( Snap.select('#infantry_svg') );
var g1 = s.g( g ).attr({ fill: 'yellow' });
var g2 = g.clone().attr({ fill: 'blue', transform: 't200,0' })
var g2 = g.clone().attr({ fill: 'green', transform: 't100,0' })
jsfiddle
Just include the bits that don't change in the defs element, and create the bits that will be unique.
I'm using Snap.svg in order to animate an SVG image on hover. In this case, I'm trying to make the image a little bit bigger (1.1x) and then change the fill color. On hover, the image scales correctly, but the fill color is unchanged. I have a suspicion it's because the image consists of three child elements which is where the fill colors reside, but I'm unable to figure out how to access them.
Here's the html:
<svg id="mySvg" width="200" height="200"
viewBox="0 0 2000 2000" preserveAspectRatio="none"></svg>
And here's the javascript:
var s = Snap("#mySvg");
var g = s.group();
var tux = Snap.load("3d_glasses.svg", function (loadedFragment) {
g.append(loadedFragment);
g.hover(hoverover, hoverout);
});
var hoverover = function () {
g.animate({
transform: 'scale(1.1)',
fill: '#FF0000'
}, 1000, mina.bounce);
};
var hoverout = function () {
g.animate({
transform: 'scale(1)',
fill: '#252525'
}, 1000, mina.bounce);
};
My SVG image file (3d_glasses.svg) looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="1024px" height="1024px" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="nonzero" clip-rule="evenodd" viewBox="0 0 10240 10240" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>3D_glasses icon</title>
<path id="curve2" fill="#252525" d="M6669 6080c-89,-175 -174,-356 -261,-531 -39,-78 -85,-152 -137,-222 -131,-175 -191,-356 -191,-575l0 -592 2560 0 0 1920 -1971 0z"/>
<path id="curve1" fill="#252525" d="M3571 6080c89,-175 174,-356 261,-531 39,-78 85,-152 137,-222 131,-175 191,-356 191,-575l0 -592 -2560 0 0 1920 1971 0z"/>
<path id="curve0" fill="#252525" d="M960 3200l8320 0c176,0 320,144 320,320l0 3200c0,176 -144,320 -320,320l-2804 0c-249,0 -462,-132 -573,-354l-354 -707c-83,-167 -243,-266 -429,-266 -186,0 -346,99 -429,266l-354 707c-111,222 -324,354 -573,354l-2804 0c-176,0 -320,-144 -320,-320l0 -3200c0,-176 144,-320 320,-320zm5162 2492c120,240 249,708 547,708l1971 0c176,0 320,-144 320,-320l0 -1920c0,-176 -144,-320 -320,-320l-2560 0c-176,0 -320,144 -320,320l0 592c0,288 83,536 255,767 41,54 76,113 107,173zm-2551 708c123,0 229,-65 285,-175 90,-177 174,-356 262,-533 31,-60 67,-119 107,-173 172,-231 255,-479 255,-767l0 -592c0,-176 -144,-320 -320,-320l-2560 0c-176,0 -320,144 -320,320l0 1920c0,176 144,320 320,320l1971 0z"/>
</svg>
defghi is correct in that the fill value of the elements is causing the issue.
I'm going to give an alternative answer though, and I guess its down to personal preference in terms of one may work better with the rest of your code and solution.
Rather than remove the fill, I would probably use a css transition, that way the original fills are left intact. So it would work something like...
s.select("g").hover( hoverFunc, hoverOut );
function hoverFunc() {
this.selectAll('path').forEach( function( el ){
el.attr({ class: 'redfadein' }) } );
this.animate({ transform: 's1.1,1.1' }, 1000);
};
function hoverOut() {
this.selectAll('path').forEach( function( el ){
el.attr({ class: 'redfadeout' }) } );
this.animate({ transform: 's1,1' }, 1000);
};
and css
redfadein {
transition: fill 1s ease;
fill:red;
}
.redfadeout {
transition: fill 1s ease;
}
jsfiddle
You may also just want to put a white filled background (or small opacity) on the space in the middle of the path so it doesn't hover out when over the white space. You could also move the scale to css as well to keep it all in one place.
You should remove fill attributes from path elements in source SVG file.
When path elements have fill attribute, the fill value of g element contains these path is ignored for rendering.
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1024px" height="1024px" fill-rule="nonzero" viewBox="0 0 10240 10240" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>3D_glasses icon</title>
<path id="curve2" d="M6669 6080c-89,..."/>
<path id="curve1" d="M3571 6080c89,-175 174..."/>
<path id="curve0" d="M960 3200..."/>
</svg>
If you can't edit SVG file, you can remove fill attributes by calling removeAttribute method of SVGDOM.
I'm developing a map, in Javascript using SVG to draw the lines.
I would like to add a feature where you can search for a road, and if the road is found, a circle appears on the map.
I know i can draw a circle in SVG, but my problem is that, the size of the circle should not change depending on the zoom-level. In other words the circle must have the same size at all times.
The roads on my map have this feature, all i had to do was add
vector-effect="non-scaling-stroke"
to the line attributes..
A line looks like this.
<line vector-effect="non-scaling-stroke" stroke-width="3" id = 'line1' x1 = '0' y1 = '0' x2 = '0' y2 = '0' style = 'stroke:rgb(255,215,0);'/>
The circle looks like this.
<circle id = "pointCircle" cx="0" cy="0" r="10" stroke="red" stroke-width="1" fill = "red"/>
Is it possible to define the circle as "non-scaling" somehow?
It took me a while, but I finally got the math clean. This solution requires three things:
Include this script in your page (along with the SVGPan.js script), e.g.
<script xlink:href="SVGPanUnscale.js"></script>
Identify the items you want not to scale (e.g. place them in a group with a special class or ID, or put a particular class on each element) and then tell the script how to find those items, e.g.
unscaleEach("g.non-scaling > *, circle.non-scaling");
Use transform="translate(…,…)" to place each element on the diagram, not cx="…" cy="…".
With just those steps, zooming and panning using SVGPan will not affect the scale (or rotation, or skew) of marked elements.
Demo: http://phrogz.net/svg/scale-independent-elements.svg
Library
// Copyright 2012 © Gavin Kistner, !#phrogz.net
// License: http://phrogz.net/JS/_ReuseLicense.txt
// Undo the scaling to selected elements inside an SVGPan viewport
function unscaleEach(selector){
if (!selector) selector = "g.non-scaling > *";
window.addEventListener('mousewheel', unzoom, false);
window.addEventListener('DOMMouseScroll', unzoom, false);
function unzoom(evt){
// getRoot is a global function exposed by SVGPan
var r = getRoot(evt.target.ownerDocument);
[].forEach.call(r.querySelectorAll(selector), unscale);
}
}
// Counteract all transforms applied above an element.
// Apply a translation to the element to have it remain at a local position
function unscale(el){
var svg = el.ownerSVGElement;
var xf = el.scaleIndependentXForm;
if (!xf){
// Keep a single transform matrix in the stack for fighting transformations
// Be sure to apply this transform after existing transforms (translate)
xf = el.scaleIndependentXForm = svg.createSVGTransform();
el.transform.baseVal.appendItem(xf);
}
var m = svg.getTransformToElement(el.parentNode);
m.e = m.f = 0; // Ignore (preserve) any translations done up to this point
xf.setMatrix(m);
}
Demo Code
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Scale-Independent Elements</title>
<style>
polyline { fill:none; stroke:#000; vector-effect:non-scaling-stroke; }
circle, polygon { fill:#ff9; stroke:#f00; opacity:0.5 }
</style>
<g id="viewport" transform="translate(500,300)">
<polyline points="-100,-50 50,75 100,50" />
<g class="non-scaling">
<circle transform="translate(-100,-50)" r="10" />
<polygon transform="translate(100,50)" points="0,-10 10,0 0,10 -10,0" />
</g>
<circle class="non-scaling" transform="translate(50,75)" r="10" />
</g>
<script xlink:href="SVGPan.js"></script>
<script xlink:href="SVGPanUnscale.js"></script>
<script>
unscaleEach("g.non-scaling > *, circle.non-scaling");
</script>
</svg>
If you are looking for a fully static way of doing this, you might be able to combine non-scaling-stroke with markers to get this, since the markers can be relative to the stroke-width.
In other words, you could wrap the circles in a <marker> element and then use those markers where you need them.
<svg width="500" height="500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 2000">
<marker id="Triangle"
viewBox="0 0 10 10" refX="0" refY="5"
markerUnits="strokeWidth"
markerWidth="4" markerHeight="3"
orient="auto">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
<path d="M 100 100 l 200 0" vector-effect="non-scaling-stroke"
fill="none" stroke="black" stroke-width="10"
marker-end="url(#Triangle)" />
<path d="M 100 200 l 200 0"
fill="none" stroke="black" stroke-width="10"
marker-end="url(#Triangle)" />
</svg>
The same can also be viewed and tweaked here. The svg spec isn't fully explicit about what should happen in this case (since markers are not in SVG Tiny 1.2, and vector-effect isn't in SVG 1.1). My current line of thinking was that it should probably affect the size of the marker, but it seems no viewers do that at the moment (try in a viewer that supports vector-effect, e.g Opera or Chrome).
Looks like some work was done in webkit (maybe related to this bug: 320635) and the new transform doesn't stick around when simply appended like that
transform.baseVal.appendItem
This seems to work better. Even works in IE 10.
EDIT: Fixed the code for more general case of multiple translate transformations in the front and possible other transformations after. First matrix transformation after all translates must be reserved for unscale though.
translate(1718.07 839.711) translate(0 0) matrix(0.287175 0 0 0.287175 0 0) rotate(45 100 100)
function unscale()
{
var xf = this.ownerSVGElement.createSVGTransform();
var m = this.ownerSVGElement.getTransformToElement(this.parentNode);
m.e = m.f = 0; // Ignore (preserve) any translations done up to this point
xf.setMatrix(m);
// Keep a single transform matrix in the stack for fighting transformations
// Be sure to apply this transform after existing transforms (translate)
var SVG_TRANSFORM_MATRIX = 1;
var SVG_TRANSFORM_TRANSLATE = 2;
var baseVal = this.transform.baseVal;
if(baseVal.numberOfItems == 0)
baseVal.appendItem(xf);
else
{
for(var i = 0; i < baseVal.numberOfItems; ++i)
{
if(baseVal.getItem(i).type == SVG_TRANSFORM_TRANSLATE && i == baseVal.numberOfItems - 1)
{
baseVal.appendItem(xf);
}
if(baseVal.getItem(i).type != SVG_TRANSFORM_TRANSLATE)
{
if(baseVal.getItem(i).type == SVG_TRANSFORM_MATRIX)
baseVal.replaceItem(xf, i);
else
baseVal.insertItemBefore(xf, i);
break;
}
}
}
}
EDIT2:
Chrome killed getTransformToElement for some reason, so the matrix needs to be retrieved manually:
var m = this.parentNode.getScreenCTM().inverse().multiply(this.ownerSVGElement.getScreenCTM());
It's discussed here and here
It looks like current browsers don't do the expected thing, so one needs to apply the inverse transform of the zoom (scale) on the contents of the <marker>, eg. transorm: scaleX(5) on the user of the <marker> etc. will need to be accompanied by a transform: translate(...) scaleX(0.2) inside the <pattern>, also factoring in possible x/y/width/height/transform-origin values inside the pattern if needed