Restrict CSS in .SVG file to that SVG only - javascript

We use Umbraco for our customers, and we want to allow them to change icons for certain content.
It's possible to upload and select a custom SVG.
Because we work on a template base, we want to control the color of the icons.
Using javascript, we transform the <img> with an SVG to a HTML <svg>-tag.
That way, we can alter the fill of stroke in the SVG's using regular CSS.
But we ran into an issue, where a customer had uploaded multiple SVG icons which were all having internal CSS and using the same class-names for paths, circles and what not.
The problem is, the last SVG that's on the page overrules the CSS for all SVG's.
For example:
<svg ...>
<defs>
<style>.a{fill: red;}</style>
</defs>
<circle class="a" cx="50" cy="50" r="50" />
</svg>
<svg ...>
<defs>
<style>.a{fill: blue;}</style>
</defs>
<circle class="a" cx="50" cy="50" r="50" />
</svg>
The customer expects a red and a blue circle, because that's what he selected within the CMS.
But this renders to 2 blue circles.
Is there any way to keep CSS found in an SVG within that same SVG?

With a hint from #Lain, I have changed the to script.
It now add's a random-generated classname to the SVG, and prepends that classname to every class in the tag like this:
let styleTag = $svg.find('style');
if (styleTag.length) {
let svgClass = GenerateClassname(8);
const regex = /\.([a-z]{1})/ig; // Get every . followed by a letter
styleTag[0].innerHTML = styleTag[0].innerHTML.replace(regex, "." + svgClass +" .$1");
}

When you inject the svg into the html document, you can add a unique id to each svg and then use it in the css selector.
<svg id="svg-1">
<defs>
<style>#svg-1 .a{fill: red;}</style>
</defs>
<circle class="a" cx="50" cy="50" r="50" />
</svg>
<svg id="svg-2">
<defs>
<style>#svg-2 .a{fill: blue;}</style>
</defs>
<circle class="a" cx="50" cy="50" r="50" />
</svg>

And then, in your near future, you do want to style those individual elements with (global) styling.
shadowDOM (optional part of the native Web Components spec aka Custom Elements)
was created for use-cases like these
supported in all modern browsers
"outside/global" CSS does not leak in
"inside" CSS does not leak out
and inheritable styles, CSS properties and CSS :part can style shadowDOM
customElements.define("svg-circle", class extends HTMLElement{
connectedCallback(){
this.style.display = "inline-block";
this.style.width = "15vw";
this.attachShadow({mode:"open"})
.innerHTML = `<svg viewBox="0 0 100 100">
<style>.a { fill:${this.getAttribute("color")} }</style>
<circle class="a" cx="50" cy="50" r="45" />
<circle part="inner" class="a" cx="50" cy="50" r="20" />
</svg>`;
}
});
<style>
*::part(inner){ fill:rebeccapurple } /* higher Specificity */
</style>
<h2>Who is afraid of</h2>
<svg-circle color="red" ></svg-circle>
<svg-circle color="yellow" ></svg-circle>
<svg-circle color="blue" ></svg-circle>

Related

Can SVG elements be tagged for later retrieval?

I have many dynamically generated SVGs inside my HTML document. They contain (among other elements) some <text> elements, sort of like this:
<svg>
// bunch of other elements
<text x="50" y="25" text-anchor="middle" dominant-baseline="central" class="myclass">info 1</text>
<text x="50" y="75" text-anchor="middle" dominant-baseline="central" class="myclass">info 2</text>
// more other elements
</svg>
Is there anything like an id or name attribute that I can use for these two pieces of text, so that I can dynamically change them later? id is supposed to be unique in the whole document, and I have many of these SVGs. name appears to mean something different in SVG (as opposed to HTML forms), and in any case might be deprecated. I'm hesitant to use class, since this really isn't about styling and I don't want weird conflicts with other styles that might be present in the document. (I don't have control over the document my code is used in, or its stylesheets.) I also don't want to have to search the whole document for the element I'm looking for, since I'll already have a pointer to its parent. Ideally, I'd do something like this:
mySVG.getChildByType("type1").innerHTML = "here's new text";
(With the caveat that every similar SVG has one of these "type1"` children, that can be grabbed and messed with.)
Thanks.
I'm not sure why you think id is unusable. You say you are dynamically generating the SVGs. I presume that means at run-time. So just generate an id at the same time. Prefix it, if you need to, so that you can be sure it is going to be unique.
textElement.setAttribute("id", "my-prefix-" + nextTextElemIndex);
There's also nothing stopping you from adding your own custom attribute. Then style the elements using attribute selectors.
circle[foo="middle"] {
fill: green;
}
<svg viewBox="0 0 300 100">
<circle cx="50" cy="50" r="50" fill="red" foo="left"/>
<circle cx="150" cy="50" r="50" fill="red" foo="middle"/>
<circle cx="250" cy="50" r="50" fill="red" foo="right"/>
</svg>
It's not exactly clear what you want; it sounds like you want a way to get the text elements inside of a specific SVG?
In that case, use something like the following:
document.querySelectorAll("svg > text")[0].textContent = "I am the new string";
Now querySelectorAll() just uses CSS selectors, so you can replace svg in there with a .class, #id, or just use whatever CSS selector you want.
You could use rather verbose class names to avoid accidentally overridden styles inherited from a global css.
E.g a BEM-like naming scheme as commonly used in scripts/libs like image sliders, media players, map helpers.
If your svg text elements are dynamically compiled you might run a function adding index based IDs/class properties:
const parentSvgs = document.querySelectorAll('.mySvg');
setAutoIds(parentSvgs);
function setAutoIds(parents) {
parents.forEach(function(parent, p) {
let parentIdentifier = 'mySvg__parent--' + p;
parent.id = parentIdentifier;
parent.classList.add(parentIdentifier);
let children = parent.querySelectorAll('.mySvg__text');
children.forEach(function(child, c) {
let childIdentifier = `mySvg__text--${p}-${c}`;
child.id = childIdentifier;
child.classList.add(childIdentifier);
child.classList.add(`mySvg__text--${c}`);
})
})
}
//auto content example
setIndexedContent(parentSvgs);
function setIndexedContent(parents) {
parents.forEach(function(parent, p) {
let children = parent.querySelectorAll('.mySvg__text');
children.forEach(function(child, c) {
child.textContent = `svg nr.${p+1} info nr.${c+1}`;
});
});
}
// single text element selected by ID or class
document.getElementById('mySvg__text--1-3').textContent = 'unique text by ID';
document.getElementById('mySvg__text--1-2').textContent = 'unique text by Class';
.mySvg {
display: inline-block;
width: 20vw;
border: 1px dotted #ccc;
}
.mySvg__text {
font-size: 10px;
}
.mySvg__text--0 {
fill: orange
}
.mySvg__text--1 {
fill: green
}
.mySvg__text--2 {
fill: red
}
<svg class="mySvg" viewBox="0 0 100 100">
<text x="50" y="25" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="50" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="75" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
</svg>
<svg class="mySvg" viewBox="0 0 100 100">
<text x="50" y="10" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="20" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="30" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="40" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
</svg>
This way you could apply shared css styles but also select individual elements according to their global index.

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!

How do I style one of the parameters in a circle inside an SVG?

I have a SVG with circles inside it. And I want them to be increasing and decreasing in radius for ever (like a pulsating circle).
My problem is, can I do it with #keyframes? Or do I need jquery? And if so, how?
Here is my code:
<div class="mapa">
<svg (svg code here......)
<circle opacity="0.3" cx="842" cy="451.814" r="25.582" id="1"/>
<circle opacity="0.3" cx="542" cy="405.814" r="25.582" id="1"/>
</svg>
</div>
How do I style the 'r' parameter?
I read I cannot style the 'r' parameter, but this worked:
<circle cx="168" cy="179" r="59"
fill="white" stroke="black"
onmouseover="evt.target.setAttribute('r', '72');"
onmouseout="evt.target.setAttribute('r', '59');"
/>
However, I want to do it with continuous increase and decrease in radius? And not on mouseover/mouseleave. Something like (r=25, then r=30, then back to 25, and goes on forever). How do I do this?
Thanks for your time, if you can give me any tips I'd apreciate it a lot!
Try to use svg smil animate
<svg width="150" height="150">
<circle opacity="0.3" cx="84%" cy="45%" r="3" id="1">
<animate attributeName="r" values="3; 10; 3" keyTimes="0; 0.5; 1" dur="1s" repeatCount="indefinite" />
</circle>
<circle opacity="0.3" cx="50%" cy="50%" r="10" id="2">
<animate attributeName="r" values="10; 3; 10" keyTimes="0; 0.5; 1" dur="1s" repeatCount="indefinite"/>
</circle>
</svg>
The easiest solution in CSS with a little hack around SVG containers. You change the container, not the svg. The circle element just fills 100% of the container. And the container artificially makes a circle with a border-radius.
svg {
border-radius: 50%;
transition: all 1s;
}
svg:hover {
width: 200px;
height: 200px;
}
<svg width="100" height="100">
<circle cx="50" cy="50" r="100%"
fill="green" />
</svg>
You can figure out how to implement your own keyframes, if this solution works for you.
And just to be clear, JQuery is a framework. You shouldn't bring up JQuery unless this question is about JQuery's framework. The language you're looking for is "Javascript" and it's in all major browsers by default. You can use Javascript to do this.
const grow = function(radius) {
var circle = document.getElementsByTagName("circle")[0];
circle.setAttribute('r', radius);
}
setTimeout(function() {
grow(100);
setTimeout(function() {
grow(40);
}, 2000);
}, 2000);
circle {
transition: all 1s;
}
<svg width="200" height="200">
<circle cx="100" cy="100" r="40"
fill="green" />
</svg>

Easiest way to hide and show svg?

I am using angular 7. I have 2 svgs: one is black and i would like to show the color on the other when it is being hovered.
This is my test snippet:
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less']
})
export class AppComponent {
title = 'menu-svg';
svgCouleur="none";
svgNb="block";
//affiche le svg couleur et cache le noir et blanc
cacheSvg(e){
this.svgCouleur = "block";
this.svgNb = "none";
}
//affiche le svg noir et blanc et on cache la couleur
revientSvg(e){
this.svgCouleur ="none";
this.svgNb = "block";
}
}
/*no at the moment*/
<svg (mouseover)="cacheSvg($event)" [style.display]="svgNb" width="100" height="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="4" fill="gray" />
Sorry, your browser does not support inline SVG.
</svg>
<svg (mouseleave)="revientSvg($event)" [style.display]="svgCouleur" width="100" height="100">
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
Sorry, your browser does not support inline SVG.
</svg>
<svg (mouseover)="cacheSvg($event)" [style.display]="svgNb" width="100" height="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="4" fill="gray" />
Sorry, your browser does not support inline SVG.
</svg>
<svg (mouseleave)="revientSvg($event)" [style.display]="svgCouleur" width="100" height="100">
<circle cx="50" cy="50" r="40" stroke="red" stroke-width="4" fill="yellow" />
Sorry, your browser does not support inline SVG.
</svg>
The effect is applied on all svgs, instead of the current one.
You could try giving your svg an id (or class) and then styling it like so:
#test{
opacity:0;
}
#test:hover{
opacity:1;
}
the id should be inside your svg:
<svg id="test" .............. >
</svg>
Im not sure if this is what you exactly mean but its an easy way to do it
I would suggest taking a look at ngx-svg which allows to create containers and add multiple elements within those containers - in your case circles. It has other elements as well, and there is a documentation, which allows to understand what you have to do as well.

Changing style of SVG elements in a g group using JS

Can I change the css style, such as fill or stroke, for all elements within a g group using JS?
Here's the svg, script and the external css:
<g id="trajectories" class="trajectory">
<circle id="tr1" cx="0" cy="0" r="75" />
<circle id="tr2" cx="0" cy="0" r="110" />
<circle id="tr3" cx="0" cy="0" r="160" />
<circle id="tr4" cx="0" cy="0" r="230" />
<circle id="tr5" cx="0" cy="0" r="350" />
<circle id="tr6" cx="0" cy="0" r="475" />
<circle id="tr7" cx="0" cy="0" r="625" />
<circle id="tr8" cx="0" cy="0" r="750" />
</g>
function mouseDown() {
svgElement = document.getElementById("trajectories");
svgElement.style.stroke = "Black";
}
g.trajectory{
fill: none;
stroke: rgba(30,30,30,1);
}
The circles in the g inherit the fill and stroke of the class g.trajectory when first drawn, but attempting to change the styling of the g element does nothing. I could change the styles of the circles individually, but is it possible to change them all just by targeting the g element?
You tried targetting the circle with straight css?
I.E
g circle {color:#ff0000;}
On the fiddles below i added a little bit in between your circle tags just for demo purpose
Here is a fiddle of this http://jsfiddle.net/vTNrR/
Or with jquery
$('#trajectories circle').css('color','#ff0000');
Here is a fiddle of the JQuery approach http://jsfiddle.net/vTNrR/4/
Or with javascript
var svgElement = document.getElementById("trajectories"); // get the parent node
var circles = svgElement.getElementsByTagName('circle'); // get child nodes
You now have an array of elements in circles so can do something like
for (var i=0;i<circles.length;i++)
{
circles[i].style.color="#ff0000";
}
Here is a fiddle of the pure JS version http://jsfiddle.net/vTNrR/1/
What you are doing should work. It works for me. See here:
http://jsfiddle.net/cSk3f/
However you are changing from dark grey to black, so the change is not very noticeable. So in my demo I have used green (rgb(30,80,30)) instead.

Categories

Resources