Prevent SVG CSS bleeding - javascript

So basically I have a webpage with two SVGs. On click the chosen SVG will be visible.
Problem: If a SVG with viewport 0 0 20 20 is loaded first and has a stroke-width of 2 and then you load the other SVG with viewport 0 0 2000 2000, the stroke-width of the first one is inherited to the second one. The 2nd one has now a stroke-width of 2 instead of 200.
This is how the containers are:
<div class="clearView-container">
// svg 2
</div>
<div class="techView-container" style="display: none;">
// svg 1
</div>
svg1:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="273.263mm" height="210.784mm" viewBox="-49.35 -56.0421 136.632 105.392">
<defs>
<style type="text/css">
.pen_style1 {stroke: #000000;stroke-width: 0.25;}
.pen_style3 {stroke: #c6c6c6;stroke-width: 0.125;stroke-dasharray: 1, 0.5}
.pen_style4 {stroke: #ff0000;stroke-width: 0.125;stroke-dasharray: 0.2, 0.5, 1, 0.5}
.cos {stroke: #0037a0;}
.hiddenLine { display: none; }
</style>
</defs>
svg2:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="127.46mm" height="104.6mm" viewBox="-3214 -2698 6373 5230">
<defs>
<style type="text/css">
.pen_style1 {stroke: #000000;stroke-width: 25;}
.pen_style3 {stroke: #c6c6c6;stroke-width: 12.5;stroke-dasharray: 100, 50}
.pen_style4 {stroke: #ff0000;stroke-width: 12.5;stroke-dasharray: 20, 50, 100, 50}
.cos {stroke: #0037a0;}
.hiddenLine { display: none; }
</style>
</defs>
And now the SVG in the clearView-container gets the properties of the one from the techView-container if this one (svg1) is loaded first.
Is there a way to prevent the <defs> of the two SVGs to "bleed through"?

In case an answer is needed by someone. This is how I ended up doing it.
I get the svg from a server as string. Than I used #DBSs solution with the IDs (can't do it server-side so here it is:)
function _injectCustomCSS (svg: string): string {
const newID = 'id' + MathLib.hashCode(svg);
const replaceStr = /.pen/g;
const svgDoc = new DOMParser().parseFromString(svg, 'image/svg+xml');
svgDoc.getElementsByTagName('svg')[0].setAttribute('id', newID);
svgDoc.getElementsByTagName('style')[0].textContent = svgDoc.getElementsByTagName('style')[0].textContent.replace(replaceStr, '#' + newID + '>.pen');
/* ... */
return new XMLSerializer().serializeToString(svgDoc);
};

Related

Any workaround to this Firefox bug? SVGElement.getScreenCTM incorrect when parent element has a transform

The Firefox bug in question is https://bugzilla.mozilla.org/show_bug.cgi?id=1610093
It's a long-standing issue whereby the getScreenCTM method of an SVG element returns incorrect values when a parent above the root SVG element has a transform applied.
The following snippet should show the black circle directly under the mouse pointer inside the red box. This works correctly in webkit/blink where the css transform on the parent is properly calculated. In Firefox this bug causes the black circle to be offset by the same values as the css transform on the parent div element.
I've avoided the issue up until now by ensuring there are no transforms further up the dom tree, but I'm now developing this as a component where I won't have control over the parent dom.
Does anyone know an easy workaround for getting a correct transform matrix?
const svgEl = document.querySelector(`svg#test-1`);
const circleEl = document.querySelector(`svg#test-1 circle`);
const handleMouseMove = (e) => {
// transform screen to SVG coordinate space
const mousePoint = svgEl.createSVGPoint();
mousePoint.x = e.clientX;
mousePoint.y = e.clientY;
const mouseCoordsInCanvasSpace = mousePoint.matrixTransform(svgEl.getScreenCTM().inverse())
// set circle to coords, should be mouse center in SVG coord space
circleEl.setAttributeNS(null, 'cx', mouseCoordsInCanvasSpace.x);
circleEl.setAttributeNS(null, 'cy', mouseCoordsInCanvasSpace.y);
}
window.addEventListener('mousemove', handleMouseMove);
body{
padding: 0px;
margin: 0px;
}
svg {
border: 2px solid red;
margin-left: 0px;
}
<!DOCTYPE html>
<html>
<body>
<div style="transform: translate(50px, 50px);">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100" id="test-1">
<circle r="10" />
</svg>
</div>
</body>
</html>
That is an annoying bug! My suggestion is to use an <object> element. The source/data for the object should be the SVG.
In the following example I'm using a data URL as a data source for the <object>, so that we at least can see something here on ST. But I imagine that the SVG should be dynamic somehow. So, to make this work you will need to load the SVG as a source of a file (data="/file.svg"). If you do that you have access to the contentDocument property on <object> and in that way you have direct access to the DOM of the SVG from the "outside". It needs to run from a web server and on the same domain as well.
body{
padding: 0px;
margin: 0px;
}
object {
border: 2px solid red;
margin-left: 0px;
}
<div style="transform: translate(50px, 50px);">
<object width="100" height="100" type="image/svg+xml" data="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgaWQ9InRlc3QtMSI+CjxzY3JpcHQgdHlwZT0idGV4dC9qYXZhc2NyaXB0Ij4KPCFbQ0RBVEFbCnZhciBjaXJjbGVFbDsKY29uc3QgaGFuZGxlTW91c2VNb3ZlID0gZSA9PiB7CiAgY29uc3QgbW91c2VQb2ludCA9IG5ldyBET01Qb2ludChlLmNsaWVudFgsIGUuY2xpZW50WSk7ICAKICBjb25zdCBtb3VzZUNvb3Jkc0luQ2FudmFzU3BhY2UgPSBtb3VzZVBvaW50Lm1hdHJpeFRyYW5zZm9ybShlLnRhcmdldC5nZXRTY3JlZW5DVE0oKS5pbnZlcnNlKCkpOwogIC8vIHNldCBjaXJjbGUgdG8gY29vcmRzLCBzaG91bGQgYmUgbW91c2UgY2VudGVyIGluIFNWRyBjb29yZCBzcGFjZSAKICBjaXJjbGVFbC5zZXRBdHRyaWJ1dGVOUyhudWxsLCAnY3gnLCBtb3VzZUNvb3Jkc0luQ2FudmFzU3BhY2UueCk7CiAgY2lyY2xlRWwuc2V0QXR0cmlidXRlTlMobnVsbCwgJ2N5JywgbW91c2VDb29yZHNJbkNhbnZhc1NwYWNlLnkpOwp9OwoKZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGUgPT4gewogIGNpcmNsZUVsID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2MxJyk7CiAgZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignbW91c2Vtb3ZlJywgaGFuZGxlTW91c2VNb3ZlKTsKfSk7Cl1dPgo8L3NjcmlwdD4KPGNpcmNsZSBpZD0iYzEiIHI9IjEwIiAvPgo8L3N2Zz4="></object>
</div>
Here is the SVG I use in the <object>:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
<script type="text/javascript">
<![CDATA[
var circleEl;
const handleMouseMove = e => {
const mousePoint = new DOMPoint(e.clientX, e.clientY);
const mouseCoordsInCanvasSpace = mousePoint.matrixTransform(e.target.getScreenCTM().inverse());
circleEl.setAttributeNS(null, 'cx', mouseCoordsInCanvasSpace.x);
circleEl.setAttributeNS(null, 'cy', mouseCoordsInCanvasSpace.y);
};
document.addEventListener('DOMContentLoaded', e => {
circleEl = document.getElementById('c1');
document.addEventListener('mousemove', handleMouseMove);
});
]]>
</script>
<circle id="c1" r="10" />
</svg>

SVG access to path when instantiated with a use (shadowDOM) (vanilla javascript)

I'd like to manipulate the internal path of a SVG which is in a separate file and instantiated with a "use"
here is my code
<!DOCTYPE html><meta charset="utf8"/>
<script src="https://ipfs.blockringtm.ml/ipfs/QmZU12UqysToVJjNmWo3E31i4tmmMbWyxZ82Vyse9CQp49/path-data-polyfill.js"></script>
<svg data="logo.svg" type="image/svg+xml" id="logo"
width="120px"
preserveAspectRatio="xMidYMin slice" viewBox="0 0 512 512"
>
<!-- https://stackoverflow.com/questions/65526455/trying-to-access-svg-elements-generated-with-use-with-javascript -->
<!--
SVG use elements are like shadow DOM elements in HTML. Only the attributes
of the USE element itself is exposed via the SVG DOM - you cannot alter
any properties on a single USE element instance that are being cloned
from the original symbol. It's not like a macro.
-->
<use id="logo1" xlink:href="logo.svg#logo"></use>
</svg>
<script>
var svg = document.querySelector('#logo');
var use = svg.querySelector('#logo1');
console.log('use:',use);
var paths = use.querySelectorAll('path'); // FAILED HERE : empty NodeList: []
console.log('paths:',paths);
for (let path of paths) {
var pathdata = path.getPathData();
console.log(pathdata);
}
</script>
The svg file is :
<svg id="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.1 236.8">
<defs>
<linearGradient id="a">
<stop offset="0" stop-color="#606"/>
<stop offset="1"/>
</linearGradient>
</defs>
<path id="pin" fill="#01a49b" d="M30.3 168a94.8 94.8 0 0063.2 27.2 95 95 0 0065.6-21.8c.8-.8 17.6-16 1.2.6l-28 28.2L98 236.8l-68.1-68c-6.5-6.5-12.6-15.5-12.6-15.5a88.1 88.1 0 0013 14.7z"/>
<path if="wrench" fill="#660266" d="M13.5 47.8C36.2 8.5 97-15.8 145 11.9c41.1 23.7 69.1 81.4 37.6 135-17.6 30-49.3 44-65 46.7-4 .7-18.3 1.9-22.5 1.9-1.3-.1-1.8-1.2-1-2.6l17.5-30.8c14.7-4.5 32-11.3 44.2-32.4a66.7 66.7 0 00-26.5-90.8C93.8 18.7 55 37.9 40 63.7A64.5 64.5 0 0051 144l8.2-14.5c2.2-3.8 2.4-4.5-.2-9A46.6 46.6 0 0158 73.3a44.8 44.8 0 0134.5-21.7c5.4-.3 4.8 1 3.7 2.7L80 82.3l9.4 16.3c1.7 3 5 6 8.5 5.9l22-2 15.3-26c1.3-1.9 2.2 0 3 2.2.7 2.2 10.4 16.9-2.5 39.3a45.3 45.3 0 01-37.9 23.4c-4.8-.3-6.1 2.5-8.7 7L65 190A99.7 99.7 0 012.7 121c-6.4-25.7-.8-53.3 10.8-73.3z"/>
</svg>
if I use an <object> tag instead of the <use> I can access the internal path with a path.pathSegList
however it doesn't work with path.getPathData pollyfill as the path is a plain object and not a SVGPathElement.
Questions :
how to I get a SVGPathElement from a path within an Object tag ?
i.e. how to I cast a DOMobject into a SVGPathElement ?
alternatively is there a way to I get and "open" shadow-root with the use tag, do I need to create the tag within the script to keep it open ?
The code (that works) with the object tag instance is :
<!DOCTYPE html>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf8">
<script src="http://ipfs.blockringtm.ml/ipfs/QmU3jKAewrHV8Wryx2WzT5v72PsgT7wt8FgnL8mqmWk259/path-data-polyfill.js"></script>
<style>
body { border:0; margin:0; padding: 0; height: 100vh; top: 0; }
svg { border: 1px dashed #555 }
</style>
<object id="sotest1" style="width:100px" data="logo.svg" type="image/svg+xml"></object>
<script>
let obj = document.querySelector('#sotest1');
obj.addEventListener("load",function(){
console.log('obj.loaded !');
var svgDoc = obj.contentDocument;
console.log('svgDoc:',svgDoc);
let path1 = svgDoc.querySelector("#pin");
console.log('typeof:',typeof(path1))
console.log('#path.d:',path1.getAttribute('d'));
let paths = svgDoc.getElementsByTagName('path');
for (let p of paths) {
let d = p.getAttribute('d'); // does work but can get p.getPathData() to work
console.log('p.d:',d);
}
},false);
</script>
if you want to get the file I have prepared a gist for it :
https://gist.github.com/DrI-T/145db3eed5905f83333d8127cbf2c6b8

How to make svg path as moveable?

I have created an SVG image. You can see it here:
jsfiddle.net/o2n56fyb/5/
In this SVG image, there is few dot line. You can see it here:
http://creativeartbd.com/demo/blockchain/
Now, I want to make those dot lines as moveable like it's running continuously. Is that possible? If so, can you tell me how?
You need to use stroke-dasharray and stroke-dashoffset for example like this:
var polygon = document.querySelector("polygon");
var dashoffset = 0;
polygon.style.strokeDashoffset = dashoffset;
function Animate() {
window.requestAnimationFrame(Animate);
dashoffset += 2;
polygon.style.strokeDashoffset = dashoffset;
}
Animate();
svg{border:1px solid #d9d9d9; display:block; margin:0 auto;max-height:100vh}
<svg width="250" height="250" viewBox="0 0 250 250">
<polygon points="50,50 200,50 200,200 50 200"
style="fill:none;
stroke:#000;
stroke-width:3;
stroke-dasharray: 30,15;"
></polygon>
</svg>
I hope this is what you need.

Can't seem to get a mask working in Snap.svg

I'm trying to create an SVG animation using Snap.svg.
Everytime I try to apply a mask, I just see nothing.
The code is relatively straightforward (and works perfectly in the examples):
var open = s.select('.open');
var circle = s.select('.circle').attr({mask:open});
Here's the whole example. I've commented out the code that should apply the mask so you can get an idea of what I'm going for.
Any help would be very appreciated!
The problem you are having is caused by the fact that the mask is affected by both the transform on your iris circle (.circle), and its own transform. In other words, the transform is being applied twice.
There are a number of solutions. One would be to remove the transform attribute from your mask path (.open).
You also need to give your mask element a fill. If it doesn't have a fill, nothing will show up. The reason is that in masks, black (or none/transparent) corresponds to transparency in the mask, and white corresponds to opaque. Colours in between result in translucent areas.
var s = Snap('#eye');
var open = s.select('.open');
var circle = s.select('.circle')
.attr({mask:open});
function closeEye() {
open.animate({ d: "M317.44,135.56s-3,7.41-14,7.41c-10.23,0-13.39-7.44-13.39-7.44s3.16,7.44,13.39,7.44C314.44,143,317.44,135.56,317.44,135.56Z" }, 200, mina.easeinout,openEye);
}
function openEye() {
console.log('callback fired');
open.animate({ d: "M317.44,135.56s-3-7.59-14-7.59c-10.23,0-13.39,7.56-13.39,7.56s3.16,7.44,13.39,7.44C314.44,143,317.44,135.56,317.44,135.56Z" }, 200, mina.easeinout);
}
$(document).ready(function() {
closeEye();
$('#eye').mouseenter( function() {
closeEye();
console.log('hovered');
});
});
.circle, .open {
stroke-width: 1px;
stroke-linecap: rounded;
stroke:black;
fill: none;
}
.open {
fill: white;
}
.svg-wrapper {
width:100px;
height:auto;
position:relative;
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.3.0/snap.svg-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="svg-wrapper">
<svg id="eye" viewBox="0 0 28.5 16">
<defs></defs>
<path class="circle" d="M309.56,135.47a5.82,5.82,0,1,1-5.82-5.82A5.82,5.82,0,0,1,309.56,135.47Z" transform="translate(-289.48 -127.47)"/>
<path class="open" d="M317.44,135.56s-3-7.59-14-7.59c-10.23,0-13.39,7.56-13.39,7.56s3.16,7.44,13.39,7.44C314.44,143,317.44,135.56,317.44,135.56Z"/>
</svg>
</div>

svg object pattern, how to multiply it

So I have a svg pattern which has 9 dots (3x3) inside with a transparent background. I have brought this in my html as an object, because I also have to change the color of the dots, I dont think I can do it with just CSS.
Now I need to repeat this object so I would have fullscreen worth of dots? How would I accomplish that?
So, it is possible to use CSS to change dots color. For example:
<object id="object" type="image/svg+xml" data="/paht/to/mysvg.svg"></object>
var a = document.getElementById("object");
var svgDoc = a.contentDocument;
var styleElement = svgDoc.createElementNS("http://www.w3.org/2000/svg", "style");
styleElement.textContent = "* { fill: #000 }";
svgDoc.documentElement.appendChild(styleElement);
And you can access to each childs(dots) in your root(svgDoc.documentElement) and manupulate it as you want, for example to multiply dots and so on via svgDoc.documentElement.childNodes.
I personally like the SVG use tag:
<svg class="icon">
<use xlink:href="#svg-icon" />
</svg>
<svg id="defs" style="display:none">
<defs>
<symbol id="svg-icon" viewBox="0 0 13 13">
<path d="M2,2 L11.1923882,11.1923882"></path>
<path d="M11.1923882,2 L2,11.1923882"></path>
</symbol>
</defs>
</svg>
This example draws you an 'X'. I can style it with CSS by targeting the <svg>:
.icon {
fill: none;
stroke-width: 2;
stroke: red;
}
Props to CSS-Tricks, where I originally picked this up.

Categories

Resources