onmouseenter event triggered on any movement in an SVG use element - javascript

When the "mouseenter" event is triggered on the green circle a 1 is printed in the console, when it is triggered on the blue circle a 2 is printed in the console. Note that when the mouse enters the green circle, 1 is printed exactly once. When the mouse enters the blue circle any mouse movement within the circle causes a 2 to be printed.
var gr = document.getElementById("greenOne");
var bl = document.getElementById("blueOneCopy");
gr.onmouseenter = function() {gr.parentNode.appendChild(gr); console.log(1);};
bl.onmouseenter = function() {bl.parentNode.appendChild(bl); console.log(2);};
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<svg height="500px" width="500px">
<defs>
<circle id="blueOne" cx="100" cy="100" r="50" style="fill: #0091EA"></circle>
</defs>
<use id="blueOneCopy" href="#blueOne"></use>
<circle id="greenOne" cx="150" cy="100" r="50" style="fill: #00C853"></circle>
</svg>
</body>
</html>

I've managed to get blueOneCopy to behave similarly to greenOne by checking whether the relatedTarget property of the mouse event isn't blueOne before executing the rest of the function. I'm not sure if this is the best solution, but it appears to work.
var gr = document.getElementById("greenOne");
var bl = document.getElementById("blueOneCopy");
gr.onmouseenter = function() {
gr.parentNode.appendChild(gr);
console.log(1);
};
bl.onmouseenter = function(e) {
if (e.relatedTarget.id !== "blueOne") {
bl.parentNode.appendChild(bl);
console.log(2);
}
};
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<svg height="500px" width="500px">
<defs>
<circle id="blueOne" cx="100" cy="100" r="50" style="fill: #0091EA"></circle>
</defs>
<use id="blueOneCopy" href="#blueOne"></use>
<circle id="greenOne" cx="150" cy="100" r="50" style="fill: #00C853"></circle>
</svg>
</body>
</html>

Another option is to check whether the element is the last one in the SVG before moving it there. See below
I expect that this issue will have something to do with how events behave when Shadow DOM elements are attached to the DOM. Since Chrome and Firefox behave the same, I expect it is behaviour described in the spec. I'm just too lazy right now to check :)
Note that, with both mine and RFoxtea's solutions, the mouseenter event is still being fired. We are just detecting the first occurrence. Be careful you don't slow down your app by putting code in the event that unnecessarily runs every time.
var gr = document.getElementById("greenOne");
var bl = document.getElementById("blueOneCopy");
gr.onmouseenter = bringToFront;
bl.onmouseenter = bringToFront;
function bringToFront(evt) {
let el = evt.target;
if (el.nextSibling != null) {
el.parentNode.appendChild(el);
console.log(el.id == 'greenOne' ? 1 : 2);
}
};
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<svg height="500px" width="500px">
<defs>
<circle id="blueOne" cx="100" cy="100" r="50" style="fill: #0091EA"></circle>
</defs>
<use id="blueOneCopy" href="#blueOne"></use>
<circle id="greenOne" cx="150" cy="100" r="50" style="fill: #00C853"></circle>
</svg>
</body>
</html>

Related

How to get the offsetLeft/offsetTop of the <g id="iqaluit"> and <circle id="circle"> elements on the page?

How to get offsetLeft/offsetTop of **<g id="iqaluit"> and <circle id="circle"> elements in javascript.
I tried this: but it gives an error TypeError: bar is null.
JS
let bar = document.getElementById('iqaluit');
console.log(bar.offsetLeft, bar.offsetTop)
let circle = document.getElementById('circle');
console.log(circle.offsetLeft, circle.offsetTop);
HTML
<div class="section-map">
<canvas class="svgfix__canvas" width="500" height="488"> </canvas>
<svg id="MAP" data-name="MAP" data-province="MAP" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 1072.93">
<g id="City_Labels_Canada" data-name="City Labels Canada">
<g id="iqaluit" data-name="Iqaluit" data-province="nunavut" class="city nohover">
<circle id="circle" cx="839.32" cy="390.34" r="3.1"></circle>
<text x="847" y="394" data-name="Iqaluit" class="en de fr es">Iqaluit</text>
</g>
</g>
</svg>
</div>
#Dio, you're on the right track. The svg elements do not support the properties you are looking for but you can access the elements, just a little differently from how you attempted, with the document. See below.
Also, the suggestion from #Syed Qasim Ahmed provides an object, but I'm not sure what you're trying to achieve with offsetLeft/offsetTop, so I can't say if it's equivalent or not.
const map = document.getElementById('MAP');
const bar = map?.querySelector('#iqaluit');
/**
* You can see the available
* methods and properties
* with a for in loop
*/
if (bar) {
for (let i in bar) {
console.log('what is i?', i);
console.log('what is bar[i]?', bar[i]);
}
const getBoundingClientRect = bar.getBoundingClientRect();
console.log({ getBoundingClientRect });
}
<div class="section-map">
<canvas class="svgfix__canvas" width="500" height="488"> </canvas>
<svg id="MAP" data-name="MAP" data-province="MAP" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 1072.93">
<g id="City_Labels_Canada" data-name="City Labels Canada">
<g id="iqaluit" data-name="Iqaluit" data-province="nunavut" class="city nohover">
<circle id="circle" cx="839.32" cy="390.34" r="3.1"></circle>
<text x="847" y="394" data-name="Iqaluit" class="en de fr es">Iqaluit</text>
</g>
</g>
</svg>
</div>

SVG graph and Javascript: Which is the best way to add animation to and SVG file?

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!

Get position of svg element

Why i can not get values of cx and cy ?
its print some array. i need only 2 values
<!DOCTYPE html>
<html>
<body>
<svg xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="50" fill="red" id="cir"/>
</svg>
<script>
console.log(document.getElementById("cir").cx);
console.log(document.getElementById("cir").cy);
</script>
</body>
</html>
The cx and cy properties are SVGAnimatedLength objects, not strings or numbers.
To get the actual value for cx, you need to do:
cx.baseVal.value
<!DOCTYPE html>
<html>
<body>
<svg xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="50" fill="red" id="cir"/>
</svg>
<script>
console.log(document.getElementById("cir").cx.baseVal.value);
console.log(document.getElementById("cir").cy.baseVal.value);
</script>
</body>
</html>
Use console.log() instead of Console.log()
console is defined by the browser, Console is not (JS, as most languages, is case-sensitive)

Adding Javascript events to SVG <def> elements instead of directly on the children?

I know that it is possible to attach plain-old Javascript event handlers to elements within an SVG tag. I am looking to duplicate very many instances of an object, so I am planning to use the <def> and <use> tags to simplify things a bit. However each of my children items will need to handle "click" and other events. I could define those events for each child, but it would be nice if I could somehow define them ONCE in the initial items, and then just "reuse" that click event. Is this possible?
https://jsfiddle.net/khyp1o0w/
<svg width="400" height="400" viewBox="0 0 400 400">
<defs>
<rect id="someDef" x="0" y="0" width="60" height="30" fill="#f00" />
<!-- ^^ would like to define an event here... -->
</defs>
<use x="150" y="150" xlink:href="#someDef" />
<use x="250" y="250" xlink:href="#someDef" />
<!-- ^^ ... that is used by these guys -->
</svg>
EDIT: Adding example SVG.
IIRC elements targeted by a use element should be internally copied into the <use> element just like if the <use> were an iframe, and the content were cloned.
So this means that events attached on the original node won't be copied onto the copies, unless this event is part of the node markup (inlined). But chrome don't really follow specs here and won't even make it work... So here is an example for FF :
// this won't work
document.getElementById('someDef').addEventListener('click', function() {
this.setAttribute('stroke', randomColor());
});
<svg width="400" height="400" viewBox="0 0 400 400">
<defs>
<script type="application/javascript">
function randomColor() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[~~(Math.random() * 15)];
}
return color;
}
</script>
<!-- this will work in FF -->
<rect id="someDef" onclick="this.setAttribute('fill',randomColor())" x="0" y="0" width="60" height="30" fill="#f00" />
</defs>
<use x="150" y="150" xlink:href="#someDef" />
<use x="250" y="250" xlink:href="#someDef" />
</svg>
You could of course use event delegation by listening for click events on your root svg element, but this would work only for direct target elements, not for nested ones :
var svg = document.querySelector('svg');
svg.addEventListener('click', function(e) {
var usedTargetID = e.target.getAttributeNS("http://www.w3.org/1999/xlink", 'href');
switch (usedTargetID) {
case '#direct':
clickDirect(e);
break;
case '#nested':
clickNested(e);
break;
default:
return;
}
});
function clickDirect(e) {
// what you seem to want
e.target.setAttribute('fill', randomColor());
}
function clickNested(e) {
// will set both nested...
e.target.setAttribute('fill', randomColor());
}
function randomColor() {
var letters = '0123456789ABCDEF'.split('');
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[~~(Math.random() * 15)];
}
return color;
}
<svg width="400" height="400" viewBox="0 0 400 400">
<defs>
<rect id="direct" x="0" y="0" width="60" height="30" />
<g id="nested">
<!-- you can only access this -->
<!-- not its content individually -->
<rect x="80" y="0" width="60" height="30" />
<rect x="20" y="70" width="60" height="30" />
</g>
</defs>
<use x="150" y="50" xlink:href="#direct" />
<use x="250" y="150" xlink:href="#nested" />
</svg>
Or you could also add an event per element, but this would have the same limitations as delegation...

SVG Clear group<g>

Suppose i have an svg element like so
<svg id="svgCanvas" class="pan" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="none">
<g id="viewport">
//Filled with an arbitrary amount of lines and cirles - examples below.
<line x1="632" y1="357.5" x2="682" y2="270.89745962155615" class="line" style="stroke: rgb(128, 128, 128); stroke-width: 1.3px;"></line>
<circle cx="82.08376766398476" cy="367.0988235405059" r="16.5" stroke="blue" fill="white" class="circle"></circle>
</g>
</svg>
how would i go about clearing everything from the that group, whilst also keeping the group element itself?
Just using DOM methods without the need for any framework you could go for:
var el = document.getElementById("viewport");
while (el.firstChild) {
el.removeChild(el.firstChild);
}
document.addEventListener('load', function(){
document.getElementById("viewport").innerHTML = "";
});

Categories

Resources