Dynamically generated SVG not using clip-path - javascript
I have an SVG file that I import with Vue JS, using vite-svg-loader. I import the file as a raw string, like so:
import DelawareBounds from './assets/images/DelawareOutlineNew.min.svg?raw'
Then, I read the string into an SVG element like so:
const parser = new DOMParser();
const DelawareMask = parser.parseFromString(DelawareBounds, "image/svg+xml").documentElement
I then add some content to the svg and add a clip-path from #DelawareBounds that already existed in the svg
const dataGroup = document.createElementNS('http://www.w3.org/2000/svg','g')
dataGroup.setAttributeNS('http://www.w3.org/2000/svg','id','data')
dataGroup.setAttributeNS('http://www.w3.org/2000/svg','clip-path','url(#DelawareBounds)')
let pathGroup
for (let path of paths) {
pathGroup = document.createElementNS('http://www.w3.org/2000/svg','g')
pathGroup.setAttribute('style','transform: scale('+xScale+','+yScale+')')
pathGroup.setAttributeNS('http://www.w3.org/2000/svg','class','delaware-bounds-scale')
pathGroup.appendChild(path)
dataGroup.appendChild(pathGroup)
}
DelawareMask.appendChild(dataGroup)
The clip-path does not work when I add the content this way. If I take the generated content and paste it into a new file, the clip-path works perfectly.
From my research, the most of these types of issues comes down to making sure to allocating the proper svg namespace. For all my dynamic content, I've made sure to add the namespace.
The only thing I can think of is that when I'm parsing the imported svg string, the data content is not going into the proper namespace. I'm not sure what else to do here? Any suggestions are appreciated.
Before the dynamic content is added, the svg looks like this:
<svg version="1.2" viewBox="0 0 2431 5145" xmlns="http://www.w3.org/2000/svg" fill="none">
<defs>
<clipPath id="DelawareBounds">
<path d="...some content here..."/>
</clipPath>
</defs>
</svg>
after the dynamic content is added, the svg looks like this:
<svg version="1.2" viewBox="0 0 2431 5145" xmlns="http://www.w3.org/2000/svg" fill="none">
<defs>
<clipPath id="DelawareBounds">
<path d="...some content here..."/>
</clipPath>
</defs>
<g id="data" clip-path="url(#DelawareBounds)">
...some data here...
</g>
</svg>
The result is that the data shows appropriately, but the clip-path does not clip the data content.
I found a solution to my problem, but still curious if there are other ways to do this.
My solution was to include:
<g id="data" clip-path="url(#DelawareBounds)"></g>
Then adding content inside the #data group worked. Instead of dynamically generating that line above as well as the content that goes inside.
Generally only elements are created in a specific namespace.
Attributes are usually in the null namespace, so the correct code is.
const dataGroup = document.createElementNS('http://www.w3.org/2000/svg','g')
dataGroup.setAttribute('id','data')
dataGroup.setAttribute('clip-path','url(#DelawareBounds)')
let pathGroup
for (let path of paths) {
pathGroup = document.createElementNS('http://www.w3.org/2000/svg','g')
pathGroup.setAttribute('style','transform: scale('+xScale+','+yScale+')')
pathGroup.setAttribute('class','delaware-bounds-scale')
pathGroup.appendChild(path)
dataGroup.appendChild(pathGroup)
}
DelawareMask.appendChild(dataGroup)
Related
Trying to nest an appendChild in a SVG
based on this: https://dev.to/gavinsykes/appending-a-child-to-an-svg-using-pure-javascript-1h9g I was trying to append a filter inside the defs part of an SVG... But it didn't work. It gave me an error when i tried : Oo_banner.defs.appendChild(filter) Oo_banner being the id of the SVG. So I plowed on thinking I could define my blur filter at the end of the SVG but even that doesn't work because it puts the feGaussianBlur after the filter tag. Anyway here's my code: <svg id="Oo_banner" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 600 300"> <defs> </defs> <rect id="subj" x="30" y="30" width="70" height="70" fill="#f9f"/> </svg> <script> var Oo_banner = document.querySelector("#Oo_banner"), subj=document.querySelector("#subj"), filter = document.createElementNS('http://www.w3.org/2000/svg','filter'); filter.setAttribute('id','blur_subj'); Oo_banner.appendChild(filter); var filterBlur = document.createElementNS('http://www.w3.org/2000/svg','feGaussianBlur'); filterBlur.setAttribute('id','blur_subj_'); filterBlur.setAttribute('stdDeviation','15'); Oo_banner.appendChild(filterBlur); subj.setAttribute('filter','url(#blur_subj)'); </script> I also made a playpen here: https://codepen.io/trufo/pen/dyWbOQp and here's the structure I get when I inspect the page in Chrome: https://i.imgur.com/Et1Lv4h.png so I have three questions: 1.) How to append a tag (filter) in the defs part of an SVG? 2.) How to append a tag (feGaussianBlur) inside the just created tag? 3.) How to make that last tag (feGaussianBlur) without a closing tag (like an img tag for example) Thanks
You're getting your attributes and your DOM id's confused I think. If you want to append a child element to defs, then get a DOM reference to the defs element - which you can do by just moving that id down from the SVG to the defs. Then, if you want to nest the feGaussianBlur inside the filter, append it to the filter DOM element - NOT the svg element. (Also - I'd encourage better naming - having two different ID's distinguished by a trailing underscore is going to get you in trouble.) const Oo_banner = document.querySelector("#Oo_banner"); let subj = document.querySelector("#subj"); filter = document.createElementNS('http://www.w3.org/2000/svg','filter'); filter.setAttribute('id','blurme'); Oo_banner.appendChild(filter); let filterBlur = document.createElementNS('http://www.w3.org/2000/svg','feGaussianBlur'); filterBlur.setAttribute('id','blur_subj_'); filterBlur.setAttribute('stdDeviation','2'); filter.appendChild(filterBlur); subj.setAttribute('filter','url(#blurme)'); <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 600 300"> <defs id="Oo_banner"> </defs> <rect id="subj" x="30" y="30" width="70" height="70" fill="green"/> </svg>
SVG convert <g> tag to an <image> tag containing base64 PNG URI (read more)
I got an interesting problem (I hope!) I have noticed that there are two "types" of SVGs First we have the conventional SVG file with and tags for example: <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 841.89 1190.55" style="enable-background:new 0 0 841.89 1190.55;" xml:space="preserve"> <g id="Background"> <rect id="Color1" class="st3" width="840.94" height="1190.55"/> <g id="Texture" class="st4"> <path class="st5" d="M843.67,410.13c-73.29 ... Secondly we have embedded tags in the image, not sure what to call them, so I've just named them "fake SVG", an example: <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="3718" height="4899" viewBox="0 0 3718 4899"> <image id="Lager_1" data-name="Lager 1" width="185.9" height="244.95000000000002" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUh ... My question is: Is there is any smooth way to convert a conventional SVG into a "fake" SVG? (please tell me if they have a real name). Also keep in mind that I would like to keep the groupings so each <g> should convert to a <image> tag My thoughts: I am thinking about loading the conventional SVG into a <canvas> tag, it seems to be able to understand the <g> groupings in the conventional SVG well and consistently, and from there, somehow, convert those groups individually into base64 PNG URI, and reconstruct it into a fake SVG, perhaps there should be some library out there that can help out, does anyone have any ideas?
I'm not sure if this will exactly answer what you are trying to do, but you can do this: <image id="something" href='data:image/svg+xml;utf8,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" .../>' /> Where you can just embed the original SVG as a string in the href when the encoding is set to data:image/svg+xml;utf8. This will render the SVG in an <image> tag, but not in Base64 (I could not get this working, but there may be a way). Source: https://css-tricks.com/probably-dont-base64-svg/
It is possible to convert an inline SVG with tags in the DOM to an image data-url. You just need to turn it into one like this: imageTag.src = 'data:image/svg+xml,' + window.escape(svgTag.outerHTML); The imageTag version can not have external links embedded in the SVG. It also needs width and height attributes on the SVG tag otherwise it will not show in some browsers. You could also draw this image to a canvas to rasterize the image, but as the comment mentioned that would only make it uglier if scaled. If you want to convert it to a SVG file you should add this as a first line and then add the outerHTML of the svgTag. <?xml version="1.0" encoding="UTF-8"?> You can save the file with the .svg extension
Is it possible to target a specific path in svg that has the fill attribute setted using javascript?
Good day all, I've a page in which I inject some svg elements, they look like this: <svg> <path fill="#FFFFFF" d='....'></path> <path d='....'></path> <path d='....'></path> <path d='....'></path> </svg> I cannot alter the svg files that are injected using a javascript library, and I'd like to change their background color on the fly, is there a way to target the only path that has the fill attribute setted to something?
you can use hasAttribute whether element have a particular or not document.querySelectorAll('.svg path').map( o=> if(!o.hasAttribute('fill')) { o.setAttribute('fill', '#eee') } );
External SVG sprites and .childnodes
I have a site with several svg icons and svg logo that I have to animate. For a better readability, I use a sprite system on an external file. I use XMLHttpRequests to load sprites at the beginning of the request. function getSprites(url){ var ajax = new XMLHttpRequest(); ajax.open("GET", url, true); ajax.send(); ajax.onload = function() { var div = document.createElement("div"); div.innerHTML = ajax.responseText; document.body.insertBefore(div, document.body.childNodes[0]); } } getSprites("./assets/images/sprites-icon.svg"); getSprites("./assets/images/sprites-logo.svg"); as soon as I use queryselector to select an element and animate it, this one is undefined for example let shapeLogoRoadmap = document.querySelector('.js-shape-roadmap-logo').childNodes; and here an example of svg symbol from sprite <symbol id="roadmap-small-logo" viewBox="0 0 250 250"> <g class="js-shape-roadmap-logo"> <path fill="#93718d" style="fill: var(--first-color, #93718d)" filter="url(#AI_Shadow_2)" d="M219.4366,111.5074l-80.944-80.944a19.1431,19.1431,0,0,0-26.9852,0l-80.944,80.944a19.1431,19.1431,0,0,0,0,26.9852l80.944,80.944a19.1431,19.1431,0,0,0,26.9852,0l80.944-80.944A19.1431,19.1431,0,0,0,219.4366,111.5074ZM200.5485,135.791,135.791,200.5485a15.3013,15.3013,0,0,1-21.582,0L49.4515,135.791a15.3013,15.3013,0,0,1,0-21.582L114.209,49.4515a15.3013,15.3013,0,0,1,21.582,0l64.7575,64.7575A15.3013,15.3013,0,0,1,200.5485,135.791Z"/> <path fill="#977792" style="fill: var(--second-color, #977792)" filter="url(#AI_Shadow_2)" d="M201.15,114.1231,135.8769,48.85a15.4233,15.4233,0,0,0-21.7538,0L48.85,114.1231a15.4233,15.4233,0,0,0,0,21.7538L114.1231,201.15a15.4233,15.4233,0,0,0,21.7538,0L201.15,135.8769A15.4233,15.4233,0,0,0,201.15,114.1231Zm-15.2308,19.5807-52.2154,52.2154a12.3428,12.3428,0,0,1-17.4077,0L64.0808,133.7038a12.3428,12.3428,0,0,1,0-17.4077l52.2153-52.2153a12.3428,12.3428,0,0,1,17.4077,0l52.2154,52.2153A12.3428,12.3428,0,0,1,185.9192,133.7038Z"/> <path fill="#9c7d96" style="fill: var(--third-color, #9c7d96)" filter="url(#AI_Shadow_2)" d="M185.9192,116.2961,133.7038,64.0808a12.3428,12.3428,0,0,0-17.4077,0L64.0808,116.2961a12.3428,12.3428,0,0,0,0,17.4077l52.2153,52.2154a12.3428,12.3428,0,0,0,17.4077,0l52.2154-52.2154A12.3428,12.3428,0,0,0,185.9192,116.2961Zm-12.1846,15.6654-41.7731,41.7731a9.87,9.87,0,0,1-13.923,0L76.2654,131.9615a9.87,9.87,0,0,1,0-13.923l41.7731-41.7731a9.87,9.87,0,0,1,13.923,0l41.7731,41.7731A9.87,9.87,0,0,1,173.7346,131.9615Z"/> <path fill="#a0829b" style="fill: var(--fourth-color, #a0829b)" filter="url(#AI_Shadow_2)" d="M173.7346,118.0385,131.9615,76.2654a9.87,9.87,0,0,0-13.923,0L76.2654,118.0385a9.87,9.87,0,0,0,0,13.923l41.7731,41.7731a9.87,9.87,0,0,0,13.923,0l41.7731-41.7731A9.87,9.87,0,0,0,173.7346,118.0385Zm-9.7461,12.5307-33.4193,33.4193a7.8951,7.8951,0,0,1-11.1384,0L86.0115,130.5692a7.8951,7.8951,0,0,1,0-11.1384l33.4193-33.4193a7.8949,7.8949,0,0,1,11.1384,0l33.4193,33.4193A7.8951,7.8951,0,0,1,163.9885,130.5692Z"/> <path fill="#a98ea4" style="fill: var(--fifth-color, #a98ea4)" filter="url(#AI_Shadow_2)" d="M163.9885,130.5692l-33.4193,33.4193a7.8951,7.8951,0,0,1-11.1384,0L86.0115,130.5692a7.8951,7.8951,0,0,1,0-11.1384l33.4193-33.4193a7.8949,7.8949,0,0,1,11.1384,0l33.4193,33.4193A7.8951,7.8951,0,0,1,163.9885,130.5692Z"/> </g> </symbol> I tried setinterval on animation but without success In this situation, I want to have access to each path because I animate them one after the other. Should I give them the same class name? or? svg are as clear as possible, but I prefer to avoid inline svg but if there is no other better solution.... I need to have access to each path of the svg, the animations are not just transferring an image from right to left but a little more complex I use gsap to animate just for information I read that you can have problems with childnodes in this kind of situation? To summarize, I would like to be able to manage each path of my svg, keeping a good organization. Thx ! Once the page is loaded, in the console, I have access to the paths When I refresh the page mutltiple time, sometimes it works !! :/
I found a solution with the help of the GSAP forum. Clean! Thx Blake from GSAP window.mySvg = window.mySvg || {}; window.mySvg.logo = ` <svg> ... </svg> `; document.body.insertAdjacentHTML("afterbegin", mySvg.logo);
Different behaviour updating SVG elements created with <use> tags in Chrome and Firefox
I've made a cut down version of the issue I'm having in the JSFiddle here. I use d3.js to dynamically add SVG path elements with class symbol to the group with id grouped-shapes in the following defs tag: <defs> <g id="grouped-shapes"> ... </g> </defs> I then use this definition twice, once directly and once via a reference to the first use tag: <g id="first-triangle"> <use xlink:href="#grouped-shapes" transform="translate(100, 100)"/> </g> <g id="second-triangle"> <use xlink:href="#first-triangle" transform="translate(200, 200) rotate(30)"/> </g> (In this example case I could easily avoid the problem by working out everything as a transformation of the original grouped-shapes instead of doing this indirect transformation, but in my real use case this would be more complicated.) I then set up an event listener to remove paths with class symbol on clicking the container div: document.getElementById("kaleidoscope-container") .addEventListener("click", function( event ) { d3.selectAll(".symbol").remove(); }, false); In Chrome, both groups update on clicking, but in Firefox only the first one does. Which of these behaviours is correct? I'm looking to be able to update in both instances, as in Chrome - what is the correct way of going about this?