Related
I'm new at coding.
I'm studying the way to make an animated portfolio like Sean Halpin or Stephanie Walter one. I want to put a face, in which, blinking eyes and a moving the mouth would be the animated elements. Basically, I want to play with the visibility of the closed eyes and open mouth. As an example, I drew a face as follows:
<svg id="Capa_1" data-name="Capa 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 429 429">
<defs>
<style>
.cls-1 {
fill: #fff;
}
.cls-2 {
fill: none;
stroke: #000;
stroke-miterlimit: 10;
}
</style>
</defs>
<g id="face">
<path class="cls-1" d="M611,608.5a214,214,0,1,1,151.32-62.68A212.6,212.6,0,0,1,611,608.5Z" transform="translate(-396.46 -180)" />
<path d="M611,181a212.9,212.9,0,1,1-83.1,16.78A212.11,212.11,0,0,1,611,181m0-1c-118.46,0-214.5,96-214.5,214.5S492.5,609,611,609s214.5-96,214.5-214.5S729.43,180,611,180Z" transform="translate(-396.46 -180)" />
</g>
<g id="eyes">
<g id="eye_r">
<circle class="cls-1" cx="319.15" cy="128.63" r="48.5" />
<path d="M715.61,260.62a48,48,0,1,1-48,48,48.06,48.06,0,0,1,48-48m0-1a49,49,0,1,0,49,49,49,49,0,0,0-49-49Z" transform="translate(-396.46 -180)" />
</g>
<g id="iris_r">
<circle cx="319.15" cy="128.63" r="19" />
</g>
<g id="eye_l">
<circle class="cls-1" cx="109.85" cy="128.63" r="48.5" />
<path d="M506.32,260.62a48,48,0,1,1-48,48,48.06,48.06,0,0,1,48-48m0-1a49,49,0,1,0,49,49,49,49,0,0,0-49-49Z" transform="translate(-396.46 -180)" />
</g>
<g id="iris_l">
<circle cx="109.85" cy="128.63" r="19" />
</g>
<line id="closed_eye_l" class="cls-2" x1="62.04" y1="128.5" x2="158.04" y2="128.5" />
<line id="closed_eye_r" class="cls-2" x1="270.69" y1="128.23" x2="366.69" y2="128.23" />
</g>
<g id="closed_mouth">
<ellipse cx="214.5" cy="309" rx="108.5" ry="11.5" />
<path d="M611,478c29.08,0,56.41,1.25,77,3.51,30.68,3.38,31,7.32,31,7.49s-.35,4.11-31,7.49C667.37,498.75,640,500,611,500s-56.41-1.25-77-3.51c-30.69-3.38-31-7.32-31-7.49s.35-4.11,31-7.49c20.55-2.26,47.88-3.51,77-3.51m0-1c-60.2,0-109,5.37-109,12s48.8,12,109,12,109-5.37,109-12-48.8-12-109-12Z" transform="translate(-396.46 -180)" />
</g>
</svg>
So, I thought three ways to do this:
Place the svg inside an tag, calling then a function that takes into consideration the loading of the file. An example of what I'm saying is found in the following resource: https://www.petercollingridge.co.uk/tutorials/svg/interactive/javascript/, in the "External SVG + External JavaScript" part. It didn't work. The contentDocument ALWAYS returns "null".
In my example, it would be:
HTML:
<object id="face" data="path/to/face.svg" type="image/svg+xml"></object>
JS:
<script type="text/javascript">
window.addEventListener("load", function() {
var svgObject = document.getElementById('face').contentDocument;
var svg = svgObject.getElementById('closed_eye_r');
svg.style.visibility="hidden";
});
</script>
Inline SVG - call a "transform" property. Sean Halpin does it but I'm not sure what he does.
HTML: https://www.seanhalpin.design/
JS: https://www.seanhalpin.design/js/scripts.js
Inline SVG, use getElementById and apply functions to animate the internal parts of the SVG file.
Questions:
a. Is Inline SVG a good practice?
b. Which is the best way to animate an SVG?
I hope to have been clear. Let me know if something is not well presented, I want to learn to edit questions in order to make them as clear as possible.
Thanks!
I'm beginner in SVG. I'm trying to change style of multiple <use> elements on hover at a specific <use> element with css, but I can't, because <use> elements using Shadow DOM.
I have the following <defs>:
<defs>
<filter id="Sjax0b81q1" filterUnits="userSpaceOnUse">...</filter>
<circle cx="0" cy="0" r="40" id="action-circle" style="cursor: move; fill: #fff;" filter="url('#Sjax0b81q1')" class="el action-el"></circle>
<g id="condition-rhombus" style="cursor: move; fill: #fff;" class="el condition-el" transform="matrix(1,0,0,1,0,0)">
<circle cx="0" cy="0" r="40" fill="none"></circle>
<path d="M -35 0, L 0 -35, L 35 0, L 0 35 L -35 0" style="stroke-linecap: round; stroke: white;" filter="url('#Sjax0b81q1')" class="condition-rhombus"></path>
</g>
<g id="svg-plus-button">
<circle cx="0" cy="40" r="10" id="svg-plus-circle" fill="none" style="fill-opacity: 1;" class="svg-plus-circle"></circle>
<path d="M956.8,408.....408.5z" id="svg-plus-sign" fill="none" transform="matrix(0.008,0,0,0.008,-4,36)" style="pointer-events: none;" class="svg-plus-sign"></path>
</g>
<rect x="-20" y="-20" width="40" height="40" id="rect-active-layer" fill="none" style="pointer-events: visible;" class="rect-active-layer"></rect>
<path d="M252.967.....2v1Z" id="api-svg" class="cls-1"></path>
</defs>
And I have a group of elements that contains several <use> elements:
<g id="action-group-2" class="external action-group" transform="matrix(1,0,0,1,420,180)">
<g class="internal action-group">
<rect x="-40" y="-40" width="80" height="80" fill="none"></rect>
</g>
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#action-circle"></use>
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#svg-plus-button" id="useSjax0b81q1k"></use>
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#rect-active-layer"></use>
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#api-svg"></use>
</g>
For example, I need to change the <path> fill in element with id #api-svg, when I hover on the #action-circle.
How can I do this? Maybe there is another way to render and styling reusable elements on hover.
Define the path to have fill="inherit", then you should be able to set fill="whatever" on the <use> element's styles and it will work.
use:hover {
fill: red;
}
<svg>
<defs>
<circle id="test" fill="inherit" cy="10" r="10" />
</defs>
<use xlink:href="#test" transform="translate(10,0)" />
<use xlink:href="#test" transform="translate(30,0)" />
<use xlink:href="#test" transform="translate(50,0)" />
<text y="40">Hover over the circles!</text>
</svg>
There's nothing overly complex here. Just change the 'use' element, rather than anything in the defs area (unless you want it to affect everything that references it).
You can either style the use element via normal css, via a css selector, e.g it's id is possibly simplest.
Or you can set the fill svg attribute on the svg 'use' element you are handling.
You shouldn't need fill inherit or anything unless I'm missing something.
So I chose to create an object to put an SVG object into, because I want to replicate it 100 times or more. Here is the SVG:
<svg xmlns="http://www.w3.org/2000/svg" class="ballsvg" viewBox="0 0 98 98">
<g>
<circle class="changeCircle" cx="50%" cy="51%" r="48%" stroke="red" stroke-width="3" fill="blue" />
<text class="changeText" text-anchor="middle" x="50%" y="65%" font-family="Verdana" font-size="30" fill="white" ><tspan>0</tspan> </text>
</g>
</svg>
And my JavaScript that builds out the objects to the div #svgMain:
for(var i=0; i<addingTo; i++){
var obj = '<div class="clickBall" data-id="'+String(i+1+totalBalls)+'" ><object class="ball" data="ball.svg"></object></div>'
appendHTMLNodes += obj;
}
Then I found online how to dynamically change the text on that object:
$( "object.ball" ).each(function( index ) {
var textNode = $(this.contentDocument);
textNode = textNode.find(".changeText");
textNode = textNode.children().first();
textNode.text(index+1);
});
Then I wanted to change the fill color when the item is clicked:
$(document).on('click', '.clickBall', function(event) {
var mySVG = $(this).find("object.ball");
mySVG = $(mySVG.contentDocument);
mySVG = mySVG.find(".changeCircle");
console.log(mySVG);
mySVG.attr("fill", "#FF0000");
});
But I cannot figure out how to get the right element, because it never changes the fill, even though I could change the text before.
Thanks in advance for any help.
You can directly select the .changeCircle and use $(this) property to fill svg. Try this code:
$(".changeCircle").on('click', function() {
$(this).css({ fill: "#ff0000" });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" class="ballsvg" viewBox="0 0 98 98">
<g>
<circle class="changeCircle" cx="50%" cy="51%" r="48%" stroke="red" stroke-width="3" fill="blue" />
<text class="changeText" text-anchor="middle" x="50%" y="65%" font-family="Verdana" font-size="30" fill="white" ><tspan>0</tspan> </text>
</g>
</svg>
UPDATE:
tcoulson wanted to update SVG inside a object tag. Here is how you can access SVG object:
$(function(){
$("#obj").contents().find(".changeCircle").attr({"fill":"lime"});
console.log($("#obj").contents().find(".changeCircle"));
});
I'm kinda new with the whole SVG business, now I'm stumbling upon a problem that I really don't understand.
I'm animating icons at the moment and noticed that repeatedly selecting g's and applying them doesn't work for me. For the sake of the question I've made an example. What I would like it to do is: Load svg > Apply the first 'G' with mask > on click animate the child 'G' upwards > clear > Select a specific 'G' (this time again the first one) > animate it from the bottom to the center
For some reason in stead of grabbing the first 'G' it iterates over the 'G's in the SVG. I've tried this with using Select with Id's, but it's giving me the same troubles.
Sooo what am I doing wrong?
html
<svg id="svg"></svg>
js
var s = Snap("#svg");
s.attr({ viewBox: "0 0 300 300" });
var bigCircle = s.circle(150, 150, 100);
var bigCircle2 = s.circle(150, 150, 100);
bigCircle2.attr({
fill:"none",
stroke: "#0000e6",
strokeWidth: 6
});
bigCircle.attr({
fill: "#fff",
stroke: "#fff",
strokeWidth: 6
});
Snap.load("images/numbers.svg", function (f) {
var apply = function(number){
g = f.select("svg g:nth-child("+number+")");
s.append(g);
g.attr({
mask: bigCircle,
});
p = g.select("g");
};
apply(1); //Please give me the first g in the svg
var g_animate = function(){
p.animate({ transform: 't0,-200' }, 500, mina.easeout, function(){
p.remove();
apply(1); //Let's repeat the first g in the svg
p.attr({transform: 't0,200'});
p.animate({ transform: 't0,0' }, 500, mina.easein)
})
};
$("#svg").click(function(){
g_animate();
});
});
loaded svg
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="300px" height="300px" viewBox="0 0 300 300" enable-background="new 0 0 300 300" xml:space="preserve">
<g>
<g>
<path d="M162.367,212.803v-34.396h-58.358v-11.016l56.04-80.193h18.357v78.068h17.584v13.141h-17.584v34.396H162.367z
M162.367,165.266v-41.932c0-6.57,0.193-13.14,0.58-19.71h-0.58c-3.865,7.343-6.957,12.753-10.436,18.551l-30.724,42.705v0.387
H162.367z"/>
<rect fill="none" width="300" height="300"/>
</g>
</g>
<g>
<g>
<path d="M115.411,193.479c4.83,2.898,15.846,7.73,27.826,7.73c21.642,0,28.599-13.721,28.406-24.35
c-0.193-17.584-16.039-25.119-32.464-25.119h-9.469v-12.754h9.469c12.367,0,28.019-6.377,28.019-21.256
c0-10.049-6.377-18.938-22.029-18.938c-10.048,0-19.71,4.444-25.12,8.309l-4.638-12.367c6.764-4.83,19.517-9.662,33.043-9.662
c24.734,0,35.943,14.687,35.943,29.952c0,13.141-7.924,24.155-23.189,29.759v0.387c15.459,2.898,27.826,14.493,28.02,32.077
c0,20.098-15.846,37.682-45.796,37.682c-14.106,0-26.474-4.443-32.657-8.502L115.411,193.479z"/>
<rect fill="none" width="300" height="300"/>
</g>
</g>
<g>
<g>
<path d="M109.903,213.865v-10.435l13.334-12.947c32.077-30.531,46.764-46.764,46.764-65.7c0-12.754-5.991-24.542-24.735-24.542
c-11.4,0-20.869,5.798-26.666,10.628l-5.411-11.98c8.503-7.149,21.062-12.754,35.362-12.754c27.054,0,38.454,18.551,38.454,36.522
c0,23.188-16.812,41.933-43.285,67.439l-9.854,9.275v0.387h56.231v14.106H109.903z"/>
<rect fill="none" width="300" height="300"/>
</g>
</g>
<g>
<g>
<path d="M154.541,103.043h-0.387l-21.835,11.787l-3.285-12.946l27.439-14.687h14.494v125.605h-16.426V103.043z"/>
<rect fill="none" width="300" height="300"/>
</g>
</g>
</svg>
It won't keep repeating the same g element, as you remove it from the DOM with this...
p.remove();
So next time it selects, it the 2nd initial g will now be the first one.
Edit:
Its also worth looking at what happens with 'append'. If you append an element from a fragment (the data loaded from the file) into the DOM, then that element is no longer in the fragment f. So if you now select a new element, it will be the next g element (as the other one has been removed and added to the DOM). So if you wanted the SAME element, you want to use a css selector on s rather than f.
I'd be tempted to use a selectAll initially on the svg to get all of the g elements into an array and use those to reference them, it may feel more intuitive.
I have this HTML code, which is invoking my javascript code. The code is for a gauge. In the javascript code, I am trying to access a SVG file, and modifying the needle (of the gauge) to display the desired value. The code is working fine. However, I do not wish to call "object id" in HTML. I want to access SVG file through javascript directly, instead of using object id in HTML. I tried using el.setAttribute('data', 'gauge.svg'); But then svg_doc isn't able to retrieve the SVG image and modify the needle. Any help would be highly appreciated.
PS : I tried my best to be as thorough in explaining the problem. However, please let me know if I am unclear somewhere.
This is Gauge.png image which is embedded in the svg code I have pasted below https://sphotos-b.xx.fbcdn.net/hphotos-snc6/179594_10150982737360698_1827200234_n.jpg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g name="gauge" width="122px" height="127px">
<image xlink:href="gauging.png" width="122" height="127"/>
<circle id="led" cx="39" cy="76" r="5" style="fill: #999; stroke: none">
<animateColor id="ledAnimation" attributeName="fill" attributeType="css" begin="0s" dur="1s"
values="none;#f88;#f00;#f88;none;" repeatCount="0"/>
</circle>
<g id="needle" transform="rotate(0,62,62)">
<circle cx="62" cy="62" r="4" style="fill: #c00; stroke: none"/>
<rect transform="rotate(-130,62,62)" name="arrow" x="58" y="38" width="8" height="24" style="fill: #c00; stroke: none"/>
<polygon transform="rotate(-130,62,62)" points="58,39,66,39,62,30,58,39" style="fill: #c00; stroke: none"/>
</g>
<text id="value" x="51" y="98" focusable="false" editable="no" style="stroke:none; fill:#fff; font-family: monospace; font-size: 12px"></text>
</g>
</svg>
HTML+Javascript code
<head>
<title>SVG Gauge example</title>
<script>
function update1(){
var scale=100;
var value;
var value1 = 69;
var el=document.getElementById('gauge1');
if (!el) return;
/* Get SVG document from HTML element */
var svg_doc = el.contentDocument;
if (!svg_doc) return;
/* Rotate needle to display given value */
var needle_el = svg_doc.getElementById('needle');
if (!needle_el) return;
/* Calc rotation angle (0->0%, 260->100%) */
value = parseInt(value1);
scale = parseInt(scale);
if (value > scale) value = scale;
var angle = value / scale * 260;
/* On-the-fly SVG transform */
needle_el.setAttribute('transform','rotate('+angle+',62,62)');
}
document.addEventListener('load', update1, true);
</script>
</head>
<div>
<object id="gauge1" type="image/svg+xml" data="gauge.svg" width="127" height="122"/>
</div>
</html>
As robertc already mentioned, you can embed the javascript code into your SVG file:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g name="gauge" width="122px" height="127px">
<image xlink:href="gauging.png" width="122" height="127"/>
<circle id="led" cx="39" cy="76" r="5" style="fill: #999; stroke: none">
<animateColor id="ledAnimation" attributeName="fill" attributeType="css" begin="0s" dur="1s"
values="none;#f88;#f00;#f88;none;" repeatCount="0"/>
</circle>
<g id="needle" transform="rotate(0,62,62)">
<circle cx="62" cy="62" r="4" style="fill: #c00; stroke: none"/>
<rect transform="rotate(-130,62,62)" name="arrow" x="58" y="38" width="8" height="24" style="fill: #c00; stroke: none"/>
<polygon transform="rotate(-130,62,62)" points="58,39,66,39,62,30,58,39" style="fill: #c00; stroke: none"/>
</g>
<text id="value" x="51" y="98" focusable="false" editable="no" style="stroke:none; fill:#fff; font-family: monospace; font-size: 12px"></text>
</g>
<script type="text/javascript">
var scale=100;
var value;
var value1 = 69;
/* Rotate needle to display given value */
var needle_el = document.getElementById('needle');
/* Calc rotation angle (0->0%, 260->100%) */
value = parseInt(value1);
scale = parseInt(scale);
if (value > scale) value = scale;
var angle = value / scale * 260;
/* On-the-fly SVG transform */
needle_el.setAttribute('transform','rotate('+angle+',62,62)');
</script>
</svg>
I've put the code below the actual SVG contents so that the document is already loaded when the script is executed.
Then, you can view the SVG file directly e.g. in Firefox (I've tested it right now).