Possible work-around for Chrome clip-path bug with SVG - javascript

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?

Related

SVG animation breaks in img tag on different zoom levels in macOS Safari

I have an SVG animation which runs well on every browser. But, when zoomed in on Safari, the animation breaks. It looks like the animation is bleeding through the right and bottom edges.
And, this only happens when the SVG is in an img tag. If the SVG is used inline, there is no issue as such.
When the page is zoomed in to 125% on macOS Safari, you'll get,
At first, it looked like an issue with my screen or with my personal preferences set in my browser. But, this is a real issue that I could reproduce on other machines too and also with other animated SVGs. For example, open up https://loading.io/spinner or http://samherbert.net/svg-loaders/ in macOS Safari with a zoom level other than 100% as you'll see the same behavior.
Snippet (save this SVG as a .svg image and use it in an img tag to reproduce this behavior)
<svg width="36" height="36" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25" />
<path d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z">
<animateTransform
attributeName="transform"
type="rotate"
dur="0.75s"
values="0 12 12;360 12 12"
repeatCount="indefinite"
/>
</path>
</svg>
I could reproduce this rendering bug on Otter Browser on windows (also webkit based) and found a kind of workaround:
Once the html body contains an inlined svg element with an <animateTransform> definition – SMIL animations in <img> tags will be rendered correctly.
In my tests you can append a hidden pseudo animation (not containing any animated element) like this:
<svg style="position:absolute; width:0; height:0; overflow:hidden;" >
<animateTransform
repeatCount="indefinite"
dur="1s"
attributeName="transform" />
</svg>
So it doesn't need to be the exact animation as used in the svg file.
Example: add animation fix via js:
function addAnimateFix() {
let svgFix =
`<svg style="position:absolute; width:0; height:0; overflow:hidden;" >
<animateTransform
repeatCount="indefinite"
dur="1s"
attributeName="transform" />
</svg>`;
document.body.insertAdjacentHTML('beforeend', svgFix)
}
<p><button type="button" onclick="addAnimateFix()">addAnimateFix</button></p>
<img height="200px" width="200px" src="https://svgshare.com/i/ia5.svg" alt="">
<img height="200px" width="200px" src="https://svgshare.com/i/i_v.svg" alt="">
Alternative workaround
background-image is apparently not affected by this bug.
So setting a background image on your image or wrapping parent element (like <figure>) could be a viable option.

foreignObject display:none in Firefox and Chrome

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.

D3.js Text on path not rendered (no height / width)

I am trying to render circles which have a text inside, that runs along a given path.
The markup d3 produces looks fine, but Chrome is not showing the texts.
Upon inspection it says text elements have 0 width and 0 height.
This is sample markup including only two circles:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 950 600">
<g>
<g transform="translate(334.14792673070184,58.96385042366173)">
<defs>
<path id="path-1" d="m5,50 a45,45 0 0 0 90,0"></path>
</defs>
<circle class="circle" fill="#ccc" cx="50" cy="50" r="50"></circle>
<text fill="#333" font-size="15px">
<textpath xlink:href="#path-1" start-offset="0%">123456</textpath>
</text>
<use xlink:href="#path-1" fill="#666" opacity="0.1"></use>
</g>
</g>
<g transform="translate(374.66047394649974,371.7948729806046)">
<defs>
<path id="path-2" d="m5,50 a45,45 0 0 0 90,0"></path>
</defs>
<circle class="circle" fill="#ccc" cx="50" cy="50" r="50"></circle>
<text fill="#333" font-size="15px">
<textpath xlink:href="#path-2" start-offset="0%">123456</textpath>
</text>
<use xlink:href="#path-2" fill="#666" opacity="0.1"></use>
</g>
</svg>
When I inspect the markup in Chrome console and click "Edit as HTML" on the SVG element, make a random change, save & exit - the SVG suddenly renders correctly.
The exact same thing happens in Firefox and Opera.
Copy pasting the generated markup into a jsfiddle renders everything as expected.
I have tried pulling the < defs > tags out of each individual group into a single global < defs > but it did not solve the problem.
I have also looked at user-agent-stylesheet and other CSS rules that might interfere with rendering.
Is this a problem with how the SVG tag is included and/or the container element's width/height properties? I have been trying different things to fix this for a couple of hours now...
Here is the full SVG markup http://pastebin.com/J2Lz8p23
Here are the relevant parts in my code http://pastebin.com/Bym8kJVN

Stretch and rotate particular part of SVG image

I'm making a web interactive test for musicians, and got stuck up on, well, interactiveness.
Users are supposed to move notes on a stave and adjust them.
SVG pathes are rendered as notes, with a separate parent div container for each note. Parent div is dragged on to the stave, and then can be resized ( I'm using jQuery UI for this, with option "handles" set to "e", so div can be resized only to the right). After this point I can not figure out how to correctly resize the SVG note inside it, because what needed to be resized, is not the whole SVG, but just one part.
SVGs look like this:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="32" viewBox="0 0 24 32" class="svgnote" preserveAspectRatio="none">
<path d="M0 23.703q0-0.922 0.672-1.797 0.313-0.344 0.766-0.711t1-0.727q0.531-0.25 1.031-0.359t0.953-0.109q0.953 0 1.688 0.531v-17.344h13.578v18.797q0 0.953-0.641 1.781-0.641 0.875-1.703 1.352t-2.047 0.477q-0.859 0-1.563-0.516t-0.703-1.375q0-0.984 0.703-1.797 0.609-0.797 1.719-1.438 0.578-0.25 1.055-0.359t0.93-0.109q0.969 0 1.703 0.531v-15.188h-12.484v16.641q0 0.953-0.641 1.781-0.641 0.875-1.695 1.352t-2.055 0.477q-0.891 0-1.594-0.516-0.672-0.516-0.672-1.375z"></path>
</svg>
Picture example
I added a picture, where above red figure
it's an initial state of a note;
upper red arrow is pointing out on a line, which is the only part of svg that needs to stretch out to the right.
lower red arrows aside -- the note should (somehow?) be rotated up and down, to make 3rd state possible
is what final state of note I'm looking for.
What I have done:
except excessive googling, not much. I tried to set "width" of SVG image to "100%" of the parent div, and add preserveAspectRatio="none", but all I got is ugly stretched note.
As for rotation I do not have even a single idea from what should I start.
I'd be eternally grateful if someone point out for what should I google, or maybe a library should I use. I'm sensing a solution to this is close somewhere, I'm just missing it because of my very limited experience in the field.
Thank you all.
Here's a 10mins hack. Connecting bar needs more work but the gist is use a collection of <symbol>for your musical notation and a better path (eg: polygon?) for the connecting bar.
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="note-1" viewBox="0 0 313 340">
<g xmlns="http://www.w3.org/2000/svg" transform="matrix(-1,0,0,-1,313,340)" id="g11029">
<path d="M 303.13715,299.65106 C 299.74131,301.47103 297.93187,304.76561 299.04493,307.24402 C 300.23219,309.88766 304.31194,310.63374 308.15151,308.90939 C 311.99107,307.18503 314.14367,303.63999 312.95641,300.99636 C 311.76914,298.35272 307.6894,297.60664 303.84983,299.33099 C 303.60986,299.43876 303.36355,299.52973 303.13715,299.65106 z " style="opacity:0.9;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.2;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" id="path11031"/>
<path d="M 299.50465,305.98445 L 299.50465,339.57202" style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" id="path11033"/>
</g>
</symbol>
<g class="first" transform="translate(0,20)">
<use xlink:href="#note-1" />
</g>
<g class="second" transform="translate(30,10)">
<use xlink:href="#note-1" />
</g>
<path stroke-width="4" stroke="black" d="M87,20 L117,10"></path>
</svg>

Changing Clickable Area of an Image without Resizing Image

I am currently working on a PHP/Javascript project where an action occurs when an image is clicked. The image is small, so I would like to expand the clickable area to further around the image without enlarging the image itself. Is this possible? Below is a general idea of the structure of what I'm working on.
<g id="pictures">
<image id="marker_image" cx="145" cy="460" r="1" preserveAspectRatio="none"
x="136" y="451" width="18" height="18"
xmlns:xlink="http://www.w3.org/1999/xlink"
xlink:href="//link.thislink.com/image_assets/markers/pool.png"
style="opacity: 1" fill="#000000" fill-opacity="1" stroke="#000000"
stroke-opacity="1" stroke-width="2" stroke-linecap="round" stroke-
linejoin="round"></image>
</g>
I'm new to working with SVGs, but from what I've read, I think I can use the <g> to add padding?
Wrap you image in a div, make the div as big as you want using padding, and bind your action on the div instead of the img

Categories