I have a JavaScript app that works with svg components. I have svg groups as:
<svg id="canvas" width="100%" height="100%" viewBox="0 0 1500 500">
<g class="node-element" x="0" y="0" height="20" width="300" id="node-c87">
<text class="node-element-text" x="12" y="15">person:object</text>
<image x="0" y="4" width="11" height="11" xlink:href="assets/images/object-icon.png"></image>
</g>
<g class="nested-group">
<g class="node-element" x="50" y="100" height="20" width="300" id="node-c87">
<text class="node-element-text" x="12" y="15">person:object</text>
<image x="0" y="4" width="11" height="11" xlink:href="assets/images/object-icon.png"></image>
</g>
</g>
</svg>
And I have defined CSS as follows(CSS on svg groups acts on all child elements of <g>.
.node-element {
display: inline;
}
.node-element :active {
opacity: 0.5;
}
.node-element:hover {
opacity: 0.5;
}
The problem is that it does not work properly in Firefox, whereas it works fine in Chrome. Why and how to fix it?
The node elements are in a tree-like structure where x values differ based on rank. In Firefox, the hover does not properly work on the first couple of node-elements. But works fine on the rest of the node-elements, regardless of the x values.
UPDATE: The problem was actually caused by a foreignObject component, which I have set the elements to display:none. The hover was actually working on the hidden component than the desired element. It was solved by setting the display:none to the foreignObject.
But I would like to know why this was acting differently in the two browsers, Chrome and Firefox?
You probably need to have all look at css pointer-events, documented here. With that you can specify what »region« of your graphic is used for hovers. This can be the AABB (axis aligned Bounding box, nothing or the shape of the graphic).
The problem was actually caused by a foreignObject component, which I have set the elements to display:none. The hover was actually working on the hidden component than the desired element. It was solved by setting the display:none to the foreignObject.
Related
I created a minimal example to demonstrate a Chrome bug I recently ran into:
const main = document.getElementById('main');
const topHover = document.getElementById('top-hover');
topHover.addEventListener('mouseenter', function(e) {
main.setAttribute('clip-path', 'url(#top-clip)');
});
topHover.addEventListener('mouseleave', function(e) {
main.setAttribute('clip-path', 'url(#everything)');
});
const bottomHover = document.getElementById('bottom-hover');
bottomHover.addEventListener('mouseenter', function(e) {
main.setAttribute('clip-path', 'url(#bottom-clip)');
});
bottomHover.addEventListener('mouseleave', function(e) {
main.setAttribute('clip-path', 'url(#everything)');
});
//main.setAttribute('clip-path', 'url(#everything)');
#main {
fill: #51D2C3;
}
#bg {
fill: #E1F5F2;
}
#top-hover {
fill: none;
}
#bottom-hover {
fill: none;
}
<svg height="200" width="300">
<path id="bg" d='M100 50 h50 v50 h50 v50 h-50 v50 h-50 v-50 h-50 v-50 h50 z'></path>
<path id="main" clip-path='url(#everything)' d='M100 50 h50 v50 h50 v50 h-50 v50 h-50 v-50 h-50 v-50 h50 z'></path>
<g>
<clipPath id="everything"></clipPath>
<rect id="top-hover" pointer-events="all" x="50" y="50" width="150" height="75"></rect>
<clipPath id="top-clip">
<use href="#top-hover"></use>
</clipPath>
<rect id="bottom-hover" pointer-events="all" x="50" y="125" width="150" height="75"></rect>
<clipPath id="bottom-clip">
<use href="#bottom-hover"></use>
</clipPath>
</g>
</svg>
The bug seems to be in how Chrome treats clip-path that's created at the same time as SVG element creation. It seems that whatever is applied to SVG element as initial clip-path becomes a permanent implicit clip-path that can't be unset via JavaScript. I reported it already here: https://bugs.chromium.org/p/chromium/issues/detail?id=1045915
in Chrome hover states don't work.
in Firefox they work as expected.
if you remove clip-path property from object with id="main" in HTML/SVG portion, and set it programmatically via JavaScript (see commented out line), hovering works as expected in Chrome as well.
if you don't set initial clip-path at all, hovering works as expected in Chrome (aside from starting out in "on" state for all regions)
if you change initial clip-path to something else (update HTML/SVG clip-path for id="main" element to either url(#top-clip) or url(#bottom-clip)), only the initially non-clipped region will render when new clip-path is applied.
From this, I conclude that that Chrome erroneously treats clip-path that's set at the time of SVG element creation as a permanent implicit clip-path that's applied on top of current clip-path.
This is a simplified example of my actual logic. In my logic I'm creating the above definition via d3.js, adding a delay to applying clip-path (even something as low as a microsecond), seems to solve the issue but seems like a hack. Is there a cleaner workaround or an alternative way to define similar functionality that mitigates this bug?
This is the code below to reproduce
<svg focused="true" width="338.35" height="338.35" version="1.2" viewBox="0 0 1200 1600" style="overflow:visible;">
<g onclick="alert(2)" transform="translate(0,0) rotate(0,375.7469534186119,500.9959378914826)">
<rect width="600" height="800" fill="red"></rect>
</g>
<g onclick="alert(1)" transform="translate(1537.23915382511058,0) rotate(0,375.7469534186119,500.9959378914826)">
<rect width="600" height="800"></rect>
</g>
</svg>
Fiddle http://jsfiddle.net/fb6swzba/
When a box clicked, it alerts a number. As you see, the red box works in both browsers. On chrome, both red and black box works. But on Safari red box works, but black box is not reactive at all.
When rect is out of the width defined in SVG, it's visible on the browser as style overflow:visible does that. But it's not clickable on safari browsers however the same example works in Chrome.
I have a problem with the use of a canvas in svg, using foreignObject.
The probleme is that the canvas is inserted in a group, which have a transformation (translation or rotation or scale), but the canvas isn't printed with the transformation.
I am on Chrome.
You can see my example there :
https://jsfiddle.net/Surre/qjrvxgos/
<body>
<svg width="400px" height="300px" viewBox="0 0 400 300"
xmlns="http://www.w3.org2000/svg">
<g transform='translate(250,10)rotate(40)'>
<foreignObject height="700" width="370" y="0" x="0">
<span xmlns="http://www.w3.org/1999/xhtml">
<canvas id="canvas" width="400px" height="300px" fill-style="#FF0000"></canvas>
<div>Comment</div>
</span>
</foreignObject>
</g>
</svg>
</body>
The "Comment" has the good transformation, but the canvas hasn't.
But if you use dev tools and inspect element canvas, you will see that it's like he is in the good place, with the transformation.
Hoping you can help me.
Thanks
From what I understand, this is actually a bug in chrome that's been known about since 2015 https://bugs.chromium.org/p/chromium/issues/detail?id=467484 There's talk of fixing it
I needed a solution too though, so here's what I've put together; I use the image element (which functions fine in svg) in conjunction with a hidden canvas. The system draws all it likes to the canvas, then transfers the canvas data to the image element.
imageElement.setAttribute('href',hiddenCanvas.toDataURL("image/png"));
It's an extra step, but it produces essentially the same result. In my project I've bundled this all together into an JS element maker
this.canvas = function(id=null, x=0, y=0, width=0, height=0, angle=0, res=1){
var canvas = document.createElement('canvas');
canvas.setAttribute('height',res*height);
canvas.setAttribute('width',res*width);
var image = document.createElementNS('http://www.w3.org/2000/svg','image');
image.id = id;
image.style = 'transform: translate('+x+'px,'+y+'px) scale('+1/res+') rotate('+angle+'rad)';
image.setAttribute('height',height*res);
image.setAttribute('width',width*res);
return {
element:image,
canvas:canvas,
context:canvas.getContext("2d"),
c:function(a){return a*res;},
print:function(){
this.element.setAttribute('href',this.canvas.toDataURL("image/png"));
}
};
};
It's kinda clunky, but I think it's what you need. It also handles resolution with the 'res' argument, and the 'c' function (which you can use with any of the canvas drawing functions
canvas.context.fillRect(canvas.c(0), canvas.c(0), canvas.c(width), canvas.c(height));
I think you neither rotate nor transform your canvas. If you want to rotate/transform it you can use CSS style. See the below code.
.transform {
-ms-transform:translateX(200px) rotate(40deg) translateY(130px); /* IE 9 */
-webkit-transform:translateX(200px) rotate(40deg) translateY(130px); /* Chrome, Safari, Opera */
transform:translateX(200px) rotate(40deg) translateY(130px);
background:#FF0000;
}
This css code will work on most of the browsers. You can use this class in your example like the following.
<canvas id="canvas" width="400px" height="300px" class="transform"></canvas>
You can see on https://jsfiddle.net/qjrvxgos/3/ your example with my update.
Canvas and SVG are different libraries, you can't use them together. You can convert one to another to do what you need. see the following link may it helps you http://www.inkfood.com/svg-to-canvas/ also you can do your example like the following example
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g transform='translate(250,10)rotate(40)'>
<rect x="10" y="20" height="50" width="75"
style="stroke: #ffff00; fill: #ff0000"/>
<text x="10" y="90" style="stroke: #660000; fill: #ff0000">
Comment</text>
</g>
I have an svg rect like this:
<svg class="legend-square">
<defs>
<pattern id="pattern1" width="3"
height="3" patternunits="userSpaceOnUse" patterntransform="rotate(-45)">
<rect width="2" height="3" transform="translate(0,0)" fill="purple"></rect>
</pattern>
</defs>
<rect width="12" height="12" fill="url(#pattern1)"></rect>
</svg>
When I inspect the second rect with Chrome it has no width and height. There are no CSS rules applying to it. Why doesn't it get affected by width and height?
One of the reasons why SVG file is rendered on front-end with zero height and width is missing <svg> tag attributes "height" and "width".
Incorrect:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 49 30">
Correct:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 49 30" width="49" height="30">
It works fine.
If the snippet works individually but size of div containing it in you code appears 0x0 then look into : Why is my div's height zero
Its usually caused when float is set.
<div>
<svg class="legend-square">
<defs>
<pattern id="pattern1" width="3" height="3" patternunits="userSpaceOnUse" patterntransform="rotate(-45)">
<rect width="2" height="3" transform="translate(0,0)" fill="purple"></rect>
</pattern>
</defs>
<rect width="12" height="12" fill="url(#pattern1)"></rect>
</svg>
</div>
You really haven't provided enough information. For instance, what is the parent element of your SVG?
Just because your <rect> has a width and hight, it doesn't mean your SVG does. SVG is not like HTML, where elements expand to fit their children. SVG is like the <canvas> element. You have to make sure it either explicitly (or implicitly) has a size.
You have not specified width or height attributes for your <svg> element, so they are both defaulting to "100%". What they are 100% of depends on what size the SVG's parent element is. Hence my first question above.
For an 'inline' SVG element you have to apply the styles, inside the svg element itself.
inside the <defs></defs> tags. Since it has its Own DOM[Document Object Model], Css Apllied to it from Outside, will have no effect.
You have Several different options for embeding an SVG, You can use it inline as in your example where you declare the <svg></svg> inside of your html/php document or you can use one of the many other methods listed below;
embed an: ..............................................<img src="../pathtoyourSvg"></img>
embed as an img with a fallback option: <img src="logo.png" srcset="logo.svg" alt="My logo">
use an: ....................................................<object type="image/svg+xml" data="image.svg">
<!-- Your fall back here -->
<img src="image.svg" />
</object>
use: .........................................................<embed type="image/svg+xml" src="image.svg" />
use an: ....................................................<iframe></iframe>
embed the Svg inside of a canvas element using Javascript:
var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
}
img.src = "path2your.svg";
You may Also Embed the Svg using Css Background image,
ie:
.mySvgContainer{
background-image:url('PathToMySvg');
}
But from the comments & Questions I have read, this need to be either Url or Base64 encoded, which seems a bit Hacky and not very convenient.
each of them have their own advantages and disadvantages.
Because of security reasons, some SVG embedding methods will block access to external resources including CSS, fonts and javascript. Especially when we have multiple images, ideally our embedding method should be able to refer to a single CSS, font or javascript file (to save resources) and be able to manipulate our embedded SVG.
Also worth mentioning that if you have display:flex; attached to an elemnet or its parent then the width and height values will have no effect on the flex items. So It may appear as 0, 0, in the console.
Some Useful Information & related questions:
Svg Coords & Units w3.org
Svg Width - Height
Applying Styles to an embedded Svg
You can wrap your svg element inside the html5 object element.
<style type="text/css">
object{
width:300px;
height:200px;
}
object svg{
width:100%;
height:100%;
}
</style>
<object>
<svg class="legend-square">
<defs>
<pattern id="pattern1" width="3"
height="3" patternunits="userSpaceOnUse" patterntransform="rotate(-45)">
<rect width="2" height="3" transform="translate(0,0)" fill="purple"></rect>
</pattern>
</defs>
<rect width="12" height="12" fill="url(#pattern1)"></rect>
</svg>
</object>
You can set height and width on the object element just link any other html element while you can set height width to 100% for SVG element. It will work. But first, test your SVG by opening the file directly in chrome.
I am trying to make and interactive svg which would react to some actions with javascript functions.
My SVG looks like this (this is example of one of many svg I am generating, I deleted some irrelevant elements to make the code more readable):
<svg contentScriptType="text/ecmascript" onmouseover="myOpacity('msg0', 0.5)"
onclick="svgClick('Some example text')"
width="760" xmlns:xlink="http://www.w3.org/1999/xlink" zoomAndPan="magnify"
onmouseout="myOpacity('msg0', 1)"
contentStyleType="text/css" height="30" preserveAspectRatio="xMidYMid meet"
xmlns="http://www.w3.org/2000/svg" version="1.0">
<text fill="black" x="10" id="msg0" font-size="10" y="20">Some text</text>
<script xlink:href="script.js" xlink:actuate="onLoad"
xlink:type="simple" xlink:show="other" type="text/ecmascript"
xmlns:xlink="http://www.w3.org/1999/xlink"/>
</svg>
This is my script.js file with onClick and opacity functions:
function svgClick(text) {
alert(text);
}
function myOpacity(element_id, op_value)
{
element = document.getElementById(element_id);
element.setAttribute('opacity', op_value);
}
The problem is that myOpacity function does not work and nothing happens when I hover over my objects (despite the id should correspond to the argument of the function).
However, the onCLick function works perfectly, so the problem is probably with identifying the element by id.
I am quite stuck here, could you take a look in the code and tell me where did I go wrong?
EDIT: this is a followup from this answer: Interactive SVG - how to choose element to react on mouseover action?
That code works there but it somehow does not do anything in the code I posted here. So my question is why? I know I could do this via attributes, but in that case, I do not know how to handle scenario, when I want to set opacity to one element when mouseover action is triggered on another one...
I pasted your code into a jsFiddle (making the JavaScript inline), and it works without problems in Firefox and Chrome:
http://jsfiddle.net/wpZs6/
However, the hover part could be considerably easier with just a CSS hover selector:
<svg width="760" height="30" xmlns="http://www.w3.org/2000/svg" version="1.0">
<style type="text/css">
svg:hover #msg0 {opacity:.5}
</style>
<text fill="black" x="10" id="msg0" font-size="10" y="20">Some text</text>
</svg>
See here: http://jsfiddle.net/L58z6/
try this :
var divtmp = document.getElementById(element_id);
var newStyle = "filter:alpha(opacity=85);-moz-opacity:0.85; opacity: 0.85;";
divtmp.setAttribute("style", newStyle );