SVG - Draw scrolling issue with multiple instances - svg

I am trying to create a page (in Wordpress), which is essentially a timeline. As the user scrolls to the next sections, I have a vertical line that "connects" to the next content section. After a LOT of trial and error, I was able to create a line that "draws" itself on scroll, and reverses when scrolling back up. My issue is, when I try to use the code again in the same page, it is already drawn, -- in other words, I *think there is an issue with the code not knowing that is is not supposed to trigger yet. I do not know enough about this to know why it is not working. ideally, I want each line to start drawing as the view-box/browser window is in view.
I have tried creating unique ID's, unique div's and ID's, etc. I originally thought it may be an issue with needing unique containers/ID's. Now, I am *thinking it might be because I do not know how to tell the "line" to not be visible until it is pulled into view.
Here is my pen:
// Get the id of the <path> element and the length of <path>
var triangle = document.getElementById("triangle");
var length = triangle.getTotalLength();
// The start position of the drawing
triangle.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
triangle.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
function myFunction() {
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
triangle.style.strokeDashoffset = length - draw;
}
body {
height: 200vh;
}
#mySVG {
position: relative;
top: 15%;
left: 50%;
width: 50px;
height: 710px;
}
<svg id="mySVG" preserveAspectRatio="none" viewBox="0 0 4 100">
<path fill="none" stroke="#000000" stroke-width="1"
id="triangle" d="M 0 0 V 100 0"/>
</svg>

Whenever you need to manipulate multiple elements, you need to query these elements to get an array/list and then loop through all nodes in this array.
Usually it's a better approach to use class names to avoid non-unique IDs like so
let triangles = document.querySelectorAll(".triangle");
triangles.forEach( (triangle) => {
// do something
});
So add class names (or just replace id attributes) to your <path> elements and add a scroll EventListener
let triangles = document.querySelectorAll(".triangle");
// calculate path length and init
triangles.forEach((triangle) => {
let pathLength = triangle.getTotalLength();
triangle.setAttribute("stroke-dasharray", `0 ${pathLength}`);
});
// Find scroll percentage on scroll
window.addEventListener("scroll", (e) => {
drawLines(triangles);
});
function drawLines(triangle, pathLength) {
var scrollpercent =
(document.body.scrollTop + document.documentElement.scrollTop) /
(document.documentElement.scrollHeight -
document.documentElement.clientHeight);
triangles.forEach((triangle) => {
let pathLength = triangle.getAttribute("stroke-dasharray").split(" ")[1];
var dashLength = pathLength * scrollpercent;
triangle.setAttribute("stroke-dasharray", `${dashLength} ${pathLength}`);
});
}
body {
padding:0 5em;
height: 200vh;
margin:0;
}
svg {
position: relative;
top: 15%;
left: 50%;
width: 50px;
height: 710px;
}
path{
transition:0.4s 0.4s;
}
<svg preserveAspectRatio="none" viewBox="0 0 4 100">
<path fill="none" stroke="#000000" stroke-width="1" class="triangle" d="M 0 0 V 100 0" />
</svg>
<svg preserveAspectRatio="none" viewBox="0 0 4 100">
<path fill="none" stroke="#000000" stroke-width="1" class="triangle" d="M 0 0 V 100 0"/>
</svg>
You can also simplify your stroke animation by using the stroke-dasharray attributes specifying 2 arguments:
dashlength
dash gap
stroke-dasharray="50 100"
You can even skip the length calculation getTotalLength() by applying these fixed initial attributes pathLength="100" and stroke-dasharray="0 100".
This way you can work with percentages without the need to calculate the exact length of your element.
let triangles = document.querySelectorAll(".triangle");
// Find scroll percentage on scroll
window.addEventListener("scroll", (e) => {
drawLines(triangles);
});
function drawLines(triangle, pathLength) {
var scrollpercent =
(document.body.scrollTop + document.documentElement.scrollTop) /
(document.documentElement.scrollHeight -
document.documentElement.clientHeight);
triangles.forEach((triangle) => {
var dashLength = 100 * scrollpercent;
triangle.setAttribute("stroke-dasharray", `${dashLength} 100`);
});
}
body {
padding:0 5em;
height: 200vh;
margin:0;
}
svg {
position: relative;
top: 15%;
left: 50%;
width: 50px;
height: 710px;
}
path{
transition:0.4s 0.4s;
}
<svg preserveAspectRatio="none" viewBox="0 0 4 100">
<path fill="none" stroke="#000000" stroke-width="1" class="triangle" d="M 0 0 V 100 0" pathLength="100" stroke-dasharray="0 100"/>
</svg>
<svg preserveAspectRatio="none" viewBox="0 0 4 100">
<path fill="none" stroke="#000000" stroke-width="1" class="triangle" d="M 0 0 V 100 0" pathLength="100" stroke-dasharray="0 100"/>
</svg>

Related

ExtrudeGeometry issue for SVG Containing Hole

I am currently looking for extruding the SVG below:
But I get the following result:
I would extrude only the two hexagon wall space. How can I do it ?
I tried both SVGLoader.createShapes(path); and //path.toShapes(true);
Here is my code:
const loader = new SVGLoader();
// load a SVG resource
loader.load(
// resource URL
'./svg/hexagone.svg',
// called when the resource is loaded
function (data) {
const paths = data.paths;
const svgGroup = new THREE.Group();
svgGroup.name = "svgGroup"
svgGroup.scale.y *= -1;
let mesh;
for (let i = 0; i < paths.length; i++) {
const path = paths[i];
const material = new THREE.MeshNormalMaterial();
const shapes = SVGLoader.createShapes(path); //path.toShapes(true);
shapes.forEach((shape, i) => {
const geometry = new THREE.ExtrudeGeometry(shape, {
depth: 10,
bevelEnabled: false
});
geometry.computeVertexNormals();
mesh = new THREE.Mesh(geometry, material);
svgGroup.add(mesh);
});
}
// Reshape items
const box = new THREE.Box3().setFromObject(svgGroup);
const size = new THREE.Vector3();
box.getSize(size);
const zOffset = size.z / -2;
const yOffset = size.y / -2;
const xOffset = size.x / -2;
svgGroup.children.forEach(item => {
item.position.z = zOffset;
item.position.x = xOffset;
item.position.y = yOffset;
});
scene.add(new THREE.AxesHelper(5))
scene.add(svgGroup)
// Rotate
svgGroup.rotateX(90 * Math.PI / 180);
}
My add my SVG below. When I tried to debug, I get 4 meshes. 1 for each line, 1 for the outside hexagon (overlapping the inside hexagon), 1 for the inside hexagon.
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 53.86 53.86">
<defs>
<style>
.cls-1 {
fill: #fff;
}
</style>
</defs>
<g>
<polygon class="cls-1" points="15.98 53.36 .5 37.88 .5 15.98 15.98 .5 37.88 .5 53.36 15.98 53.36 37.88 37.88 53.36 15.98 53.36"/>
<path d="M37.67,1l15.19,15.19v21.48l-15.19,15.19H16.19L1,37.67V16.19L16.19,1h21.48m.41-1H15.77L0,15.77v22.31l15.77,15.77h22.31l15.77-15.77V15.77L38.08,0h0Z"/>
</g>
<g>
<polygon class="cls-1" points="20.63 44.31 10.96 34.64 10.96 20.96 20.63 11.29 34.31 11.29 43.98 20.96 43.98 34.64 34.31 44.31 20.63 44.31"/>
<path d="M34.1,11.79l9.38,9.38v13.26l-9.38,9.38h-13.26l-9.38-9.38v-13.26l9.38-9.38h13.26m.41-1h-14.09l-9.96,9.96v14.09l9.96,9.96h14.09l9.96-9.96v-14.09l-9.96-9.96h0Z"/>
</g>
</svg>
With another SVG (see below) the result is the same even if in this scenario I have 2 paths in the SVG.
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Fusion 360, Shaper Origin Export Add-In, Version 1.6.10 -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:shaper="http://www.shapertools.com/namespaces/shaper" shaper:fusionaddin="version:1.6.10" width="3.1176915cm" height="3.6cm" version="1.1" x="0cm" y="0cm" viewBox="0 0 3.1176915 3.6" enable-background="new 0 0 3.1176915 3.6" xml:space="preserve">
<path d="M-0,-1.8 L1.5588457,-0.9 1.5588457,0.9 -0,1.8 -1.5588457,0.9 -1.5588457,-0.9 -0,-1.8z" transform="matrix(1,0,0,-1,1.5588457,1.8)" fill="rgb(0,0,0)" stroke-linecap="round" stroke-linejoin="round" />
<path d="M-0.9588457,0.5535898 L-0.9588457,-0.5535898 -0,-1.1071797 0.9588457,-0.5535898 0.9588457,0.5535898 0,1.1071797 -0.9588457,0.5535898z" transform="matrix(1,0,0,-1,1.5588457,1.8)" fill="rgb(255,255,255)" stroke="rgb(0,0,0)" stroke-width="0.0010cm" stroke-linecap="round" stroke-linejoin="round" />
</svg>
Hexagon inside is also recovered by the biggest.
I finally chose to use lnkscape to generate my SVG. I have mush more options and I was able to generate the hole by combining all pathes in 1 path.

change color of button with text and svg without using inline svg [duplicate]

I want to use this technique and change the SVG color, but so far I haven't been able to do so. I use this in the CSS, but my image is always black, no matter what.
My code:
.change-my-color {
fill: green;
}
<svg>
<image class="change-my-color" xlink:href="https://svgur.com/i/AFM.svg" width="96" height="96" src="ppngfallback.png" />
</svg>
2020 answer
CSS Filter works on all current browsers
To change any SVGs color
Add the SVG image using an <img> tag.
<img src="dotted-arrow.svg" class="filter-green"/>
To filter to a specific color, use the following Codepen (click here to open the codepen) to convert a hexadecimal color code to a CSS filter:
For example, output for #00EE00 is
filter: invert(42%) sepia(93%) saturate(1352%) hue-rotate(87deg) brightness(119%) contrast(119%);
Add the CSS filter into this class.
.filter-green{
filter: invert(48%) sepia(79%) saturate(2476%) hue-rotate(86deg) brightness(118%) contrast(119%);
}
To change the color of any SVG, you can directly change the SVG code by opening the SVG file in any text editor. The code may look like the below code:
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="500px" height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g>
<path d="M114.26,436.584L99.023,483h301.953l-15.237-46.416H114.26z M161.629,474.404h-49.592l9.594-29.225h69.223
C181.113,454.921,171.371,464.663,161.629,474.404z"/>
/* Some more code goes on */
</g>
</svg>
You can observe that there are some XML tags like path, circle, polygon, etc.. There you can add your own color with help of the style attribute. Look at the below example
<path fill="#AB7C94" d="M114.26,436.584L99.023,483h301.953l-15.237-46.416H114.26z M161.629,474.404h-49.592l9.594-29.225h69.223
C181.113,454.921,171.371,464.663,161.629,474.404z"/>
Add the style attribute to all the tags so that you can get your SVG of your required color.
As per Daniel's comment, we can use fill attribute directly instead of fill element inside style attribute.
You can't change the color of an image that way. If you load SVG as an image, you can't change how it is displayed using CSS or JavaScript in the browser.
If you want to change your SVG image, you have to load it using <object>, <iframe> or using <svg> inline.
If you want to use the techniques in the page, you need the Modernizr library, where you can check for SVG support and conditionally display or not a fallback image. You can then inline your SVG and apply the styles you need.
See:
#time-3-icon {
fill: green;
}
.my-svg-alternate {
display: none;
}
.no-svg .my-svg-alternate {
display: block;
width: 100px;
height: 100px;
background-image: url(image.png);
}
<svg width="96px" height="96px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<path id="time-3-icon" d="M256,50C142.229,50,50,142.229,50,256c0,113.77,92.229,206,206,206c113.77,0,206-92.23,206-206
C462,142.229,369.77,50,256,50z M256,417c-88.977,0-161-72.008-161-161c0-88.979,72.008-161,161-161c88.977,0,161,72.007,161,161
C417,344.977,344.992,417,256,417z M382.816,265.785c1.711,0.297,2.961,1.781,2.961,3.518v0.093c0,1.72-1.223,3.188-2.914,3.505
c-37.093,6.938-124.97,21.35-134.613,21.35c-13.808,0-25-11.192-25-25c0-9.832,14.79-104.675,21.618-143.081
c0.274-1.542,1.615-2.669,3.181-2.669h0.008c1.709,0,3.164,1.243,3.431,2.932l18.933,119.904L382.816,265.785z"/>
</svg>
<image class="my-svg-alternate" width="96" height="96" src="ppngfallback.png" />
You can inline your SVG. Tag your fallback image with a class name (my-svg-alternate):
<svg width="96px" height="96px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<path id="time-3-icon" .../>
</svg>
<image class="my-svg-alternate" width="96" height="96" src="ppngfallback.png" />
And in CSS use the no-svg class from Modernizr (CDN: http://ajax.aspnetcdn.com/ajax/modernizr/modernizr-2.7.2.js ) to check for SVG support. If there isn't any SVG support, the SVG block will be ignored and the image will be displayed, otherwise the image will be removed from the DOM tree (display: none):
.my-svg-alternate {
display: none;
}
.no-svg .my-svg-alternate {
display: block;
width: 100px;
height: 100px;
background-image: url(image.png);
}
Then you can change the color of your inlined element:
#time-3-icon {
fill: green;
}
If you want to change the color dynamically:
Open the SVG in a code editor
Add or rewrite the attribute of fill of every path to fill="currentColor"
Now, that svg will take the color of your font color, so you can do something like:
svg {
color : "red";
}
Only SVG with path information. You can't do that to the image... as the path you can change stroke and fill information and you are done. like Adobe Illustrator
So, via CSS you can overwrite the path fill value:
path { fill: orange; }
But if you want a more flexible way as you want to change it with a text when having some hovering effect going on, use:
path { fill: currentColor; }
body {
background: #ddd;
text-align: center;
padding-top: 2em;
}
.parent {
width: 320px;
height: 50px;
display: block;
transition: all 0.3s;
cursor: pointer;
padding: 12px;
box-sizing: border-box;
}
/*** desired colors for children ***/
.parent{
color: #000;
background: #def;
}
.parent:hover{
color: #fff;
background: #85c1fc;
}
.parent span{
font-size: 18px;
margin-right: 8px;
font-weight: bold;
font-family: 'Helvetica';
line-height: 26px;
vertical-align: top;
}
.parent svg{
max-height: 26px;
width: auto;
display: inline;
}
/**** magic trick *****/
.parent svg path{
fill: currentcolor;
}
<div class='parent'>
<span>TEXT WITH SVG</span>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 32 32">
<path d="M30.148 5.588c-2.934-3.42-7.288-5.588-12.148-5.588-8.837 0-16 7.163-16 16s7.163 16 16 16c4.86 0 9.213-2.167 12.148-5.588l-10.148-10.412 10.148-10.412zM22 3.769c1.232 0 2.231 0.999 2.231 2.231s-0.999 2.231-2.231 2.231-2.231-0.999-2.231-2.231c0-1.232 0.999-2.231 2.231-2.231z"></path>
</svg>
</div>
I added a test page - to color SVG via Filter settings:
For example,
filter: invert(0.5) sepia(1) saturate(5) hue-rotate(175deg)
Upload & Color your SVG - Jsfiddle
I took the idea from: Swapping Fill Color on Image Tag SVGs
Solution 1 - Edit SVG to point to the currentColor
<svg>... fill: currentColor stroke: currentColor ...</svg>
Then you can control the color of the stroke and the fill from your CSS content:
svg {
color: blue; /* Or any color of your choice. */
}
Pros and cons:
Simple and uses conventional supported CSS.
Suitable if:
You control the SVG
SVG can be included inline in the HTML.
Solution 2 - CSS mask property
<i class="icon"></i>
.icon {
-webkit-mask-size: cover;
mask-size: cover;
-webkit-mask-image: url(https://url.of.svg/....svg);
mask-image: url(https://url.of.svg/....svg);
background-color: blue; /* Or any color of your choice. */
width: 20px;
height: 20px;
}
}
Pros and cons
Relatively easy to use
Browser support for the mask CSS property is partial.
Suitable if:
SVG is external, and included via URL
Meant to be used on modern known browsers.
Solution 3 - CSS Filter property - static color
If the color is known in advance, you can use https://codepen.io/sosuke/pen/Pjoqqp to find the filter needed to change your SVG to the desired color. For example, to convert the svg to #00f:
<img src="https://url.of.svg/....svg" class="icon">
.icon {
filter: invert(8%) sepia(100%) saturate(6481%) hue-rotate(246deg) brightness(102%) contrast(143%);
}
If your original color isn't black, prefix the list of filters with brightness(0) saturate(100%) to convert it first to black.
Pros and cons:
There might be a small, nonsignificant difference between the result and the desired color.
Suitable if:
Desired color is known in advance.
External image
SVG mask on a box element with a background color will result:
body{ overflow:hidden; }
.icon {
--size: 70px;
display: inline-block;
width: var(--size);
height: var(--size);
transition: .12s;
-webkit-mask-size: cover;
mask-size: cover;
}
.icon-bike {
background: black;
animation: 4s frames infinite linear;
-webkit-mask-image: url(https://image.flaticon.com/icons/svg/89/89139.svg);
mask-image: url(https://image.flaticon.com/icons/svg/89/89139.svg);
}
#keyframes frames {
0% { transform:translatex(100vw) }
25% { background: red; }
75% { background: lime; }
100% { transform:translatex(-100%) }
}
<i class="icon icon-bike" style="--size:150px"></i>
Note - SVG masks are not supported in Internet Explorer browsers
The easiest way would be to create a font out of the SVG using a service like https://icomoon.io/app/#/select or such. Upload your SVG, click "generate font", include font files and CSS content into your side and just use and style it like any other text. I always use it like this because it makes styling much easier.
But as mentioned in the article commented by #CodeMouse92, icon fonts mess up screen readers (and are possibly bad for SEO). So rather stick to the SVGs.
You can try to color it with this css filter hack:
.colorize-pink {
filter: brightness(0.5) sepia(1) hue-rotate(-70deg) saturate(5);
}
.colorize-navy {
filter: brightness(0.2) sepia(1) hue-rotate(180deg) saturate(5);
}
.colorize-blue {
filter: brightness(0.5) sepia(1) hue-rotate(140deg) saturate(6);
}
To simply change the color of the SVG file:
Go to the SVG file and under styles, mention the color in fill:
<style>.cls-1{fill: #FFFFFF;}</style>
Target the path within the 'svg' tag:
<svg>
<path>....
</svg>
You can do it inline, like:
<path fill="#ccc">
Or
svg{
path{
fill: #ccc
To change the color of an SVG element, I have found out a way while inspecting the Google search box search icon below:
.search_icon {
color: red;
fill: currentColor;
display: inline-block;
width: 100px;
height: 100px;
}
<span class="search_icon">
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path></svg>
</span>
I have used a span element with "display:inline-block", height, width and setting a particular style "color: red; fill: currentColor;" to that span tag which is inherited by the child svg element.
You can change SVG coloring with CSS if you use some tricks.
I wrote a small script for that.
go through a list of elements which do have an SVG image
load the SVG file as XML
fetch only the SVG part
change color of path
replace src with the modified SVG image as an inline image
$('img.svg-changeable').each(function () {
var $e = $(this);
var imgURL = $e.prop('src');
$.get(imgURL, function (data) {
// Get the SVG tag, ignore the rest
var $svg = $(data).find('svg');
// Change the color
$svg.find('path').attr('fill', '#000');
$e.prop('src', "data:image/svg+xml;base64," + window.btoa($svg.prop('outerHTML')));
});
});
The code above might not be working correctly. I've implemented this for elements with an SVG background image which works nearly similar to this.
But anyway, you have to modify this script to fit your case.
Method 1
The easy and effect way:
Open your .svg file with any text editor
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 477.526 477.526" style="enable-background:new 0 0 477.526 477.526;
fill: rgb(109, 248, 248);" xml:space="preserve">
<svg />
Give an style attribute and fill that with color.
Another way
Fill with color in your shape. Here i have rect shape fill="white".
<svg width="800" height="600" xmlns="http://www.w3.org/2000/svg">
<g>
<title>background</title>
<rect fill="#fff" id="canvas_background" height="602" width="802" y="-1"
x="-1"/>
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%"
id="canvasGrid">
<rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%"
width="100%"/>
</g>
</g>
</svg>
2022 Web Component <load-file> answer
This (8 line) native Web Component loads external content, and injects it into the DOM.
It is explained and documented in a DEV blog post: <load-file> Web Component.
Full source code:
customElements.define("load-file", class extends HTMLElement {
// declare default connectedCallback as sync so await can be used
async connectedCallback(
// call connectedCallback with parameter to *replace* SVG (of <load-file> persists)
src = this.getAttribute("src"),
// attach a shadowRoot if none exists (prevents displaying error when moving Nodes)
// declare as parameter to save 4 Bytes: 'let '
shadowRoot = this.shadowRoot || this.attachShadow({mode:"open"})
) {
// load SVG file from src="" async, parse to text, add to shadowRoot.innerHTML
shadowRoot.innerHTML = await (await fetch(src)).text()
// append optional <tag [shadowRoot]> Elements from inside <load-svg> after parsed <svg>
shadowRoot.append(...this.querySelectorAll("[shadowRoot]"))
// if "replaceWith" attribute
// then replace <load-svg> with loaded content <load-svg>
// childNodes instead of children to include #textNodes also
this.hasAttribute("replaceWith") && this.replaceWith(...shadowRoot.childNodes)
}
})
<load-file src="//load-file.github.io/heart.svg">
<!-- elements inside load-file are MOVED to shadowDOM -->
<style shadowRoot>
svg {
height: 180px; /* Stack Overflow subwindow height */
}
path:nth-child(2n+2) {
fill: GREEN; /* shadowDOM style does NOT style global DOM */
}
</style>
</load-file>
If the same SVG must be used multiple times with different colors, define the set of paths within a hidden SVG which serves as the master copy. Then place new instances which refer to the master path with their individual fills.
Note: This approach only works with inline <svg> tags. It will not work with <img> tags loading .svg files.
:root {
fill: gray;
}
.hidden {
display: none;
}
svg {
width: 1em;
height: 1em;
}
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" class="hidden">
<path id="s_fave" d="m379 21c-57 0-104 53-123 78-19-25-66-78-123-78-74 0-133 68-133 151 0 45 18 88 49 116 0.5 0.8 1 2 2 2l197 197c2 2 5 3 8 3s5-1 8-3l206-206c2-2 3-3 5-5 0.8-0.8 1-2 2-3 23-28 35-64 35-102 0-83-60-151-133-151z"/>
<path id="s_star" d="m511 196c-3-10-13-18-23-19l-148-13-58-137c-4-10-14-17-25-17-11 0-21 6-25 17l-58 137-148 13c-11 1-20 8-23 19-3 10-0.3 22 8 29l112 98-33 145c-2 11 2 22 11 28 5 3 10 5 16 5 5 0 10-1 14-4l127-76 127 76c9 6 21 5 30-1 9-6 13-17 11-28l-33-145 112-98c8-7 11-19 8-29z"/>
</svg>
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><use href="#s_fave"></use></svg>
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><use href="#s_star"></use></svg>
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><use href="#s_fave" fill="red"></use></svg>
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><use href="#s_star" fill="gold"></use></svg>
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><use href="#s_fave" fill="purple"></use></svg>
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><use href="#s_star" fill="silver"></use></svg>
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><use href="#s_fave" fill="pink"></use></svg>
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><use href="#s_star" fill="blue"></use></svg>
Here the fast and furious way :)
body {
background-color: #DEFF05;
}
svg {
width: 30%;
height: auto;
}
svg path {
color: red;
fill: currentcolor;
}
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 514.666 514.666"><path d="M514.666,210.489L257.333,99.353L0,210.489l45.933,19.837v123.939h30V243.282l33.052,14.274v107.678l4.807,4.453 c2.011,1.862,50.328,45.625,143.542,45.625c93.213,0,141.53-43.763,143.541-45.626l4.807-4.452V257.557L514.666,210.489z M257.333,132.031L439,210.489l-181.667,78.458L75.666,210.489L257.333,132.031z M375.681,351.432 c-13.205,9.572-53.167,33.881-118.348,33.881c-65.23,0-105.203-24.345-118.348-33.875v-80.925l118.348,51.112l118.348-51.111 V351.432z"></path></svg>
For example, in your HTML:
<body>
<svg viewBox="" width="" height="">
<path id="struct1" fill="#xxxxxx" d="M203.3,71.6c-.........."></path>
</svg>
</body>
Use jQuery:
$("#struct1").css("fill", "<desired colour>");
Check out this code. It works.
<div>
<!-- YouTube -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path fill="white"
d="M549.655 124.083c-6.281-23.65-24.787-42.276-48.284-48.597C458.781 64 288 64 288 64S117.22 64 74.629 75.486c-23.497 6.322-42.003 24.947-48.284 48.597-11.412 42.867-11.412 132.305-11.412 132.305s0 89.438 11.412 132.305c6.281 23.65 24.787 41.5 48.284 47.821C117.22 448 288 448 288 448s170.78 0 213.371-11.486c23.497-6.321 42.003-24.171 48.284-47.821 11.412-42.867 11.412-132.305 11.412-132.305s0-89.438-11.412-132.305zm-317.51 213.508V175.185l142.739 81.205-142.739 81.201z" />
</svg>
<!-- Instagram -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path fill="white"
d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z" />
</svg>
</div>
CSS
svg {
fill: white;
}
For a better resolution about Manish Menaria's (thank you so much for your help) response, use this filter generator instead a purposed generator: https://angel-rs.github.io/css-color-filter-generator/
.filter-green{
filter: invert(48%) sepia(79%) saturate(2476%) hue-rotate(86deg) brightness(118%) contrast(119%);
}
Use an svg <mask> element.
This is better than other solutions because:
Closely matches your original code.
Works in IE!
The embedded image can still be an external, unmodified file.
The image does not even have to be an SVG.
Color is inherited from font-color, so easy to use alongside text.
Color is a normal CSS color, not a strange combination of filters.
<svg style="color: green; width: 96px; height: 96px" viewBox="0 0 100 100" preserveAspectRatio="none">
<defs>
<mask id="fillMask" x="0" y="0" width="100" height="100">
<image xlink:href="https://svgur.com/i/AFM.svg" x="0" y="0" width="100" height="100" src="ppngfallback.png" />
</mask>
</defs>
<rect x="0" y="0" width="100" height="100" style="stroke: none; fill: currentColor" mask="url("#fillMask")" />
</svg>
https://jsfiddle.net/jamiegl/5jaL0s1t/19/
If you want to do this to an inline SVG file, that is, for example, a background image in your CSS content:
background: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='rgba(31,159,215,1)' viewBox='...'/%3E%3C/svg%3E");
Of course, replace the ... with your inline image code.
There are some problems with Manish Menaria's answer, if we convert white color it shows gray.
So I added some tweaks, and the below example specifically shows how to change the color in the material icon:
<mat-icon class="draft-white" svgIcon="draft" aria-hidden="false"></mat-icon>
.draft-white{
filter: brightness(0) invert(1);
}
You can use a font icon to use any CSS option in SVG
I was searching for a way to have any CSS options, like animation for SVG, and I ended up to generate a font icon with my SVG(s) and then used it inside a span (like Font Awesome), so any CSS option, like coloring, was available on it.
I used https://icomoon.io to convert my SVG image to a font icon. Then you can use it like Font Awesome or MaterialIcon inside HTML elements.
I found it a bit clumsy, but it is definitely a working way to dynamically change the color of an SVG included with <img> tag.
In the SVG file, you can add CSS content the following way:
<svg ...>
<defs>
<style>
...
<style>
<defs>
There you can use #media rules, with which the SVG can look outside itself for contextual circumstances. There's an aspect-ratio media feature that applies to the SVG box (e.g., the <img> tag). You can create different contexts for the SVG by stretching the SVG box a little bit.
This way you can also make the favicon the same SVG that appears on the website, but with a different color. (In this case, no other SVG boxes should be square-shaped.)
/* img stretched horizontally (if SVG is square-shaped) */
#media (min-aspect-ratio: 1000/999) {
path {
fill: blue;
}
}
/* img stretched vertically (if SVG is square-shaped) */
#media (max-aspect-ratio: 999/1000) {
path {
fill: green;
}
}
/* img with exact sizes */
#media (aspect-ratio: 86/74) {
path {
fill: red;
}
}
/* favicon with light browser theme */
#media (aspect-ratio: 1/1) and (prefers-color-scheme: light) {
path {
fill: black;
}
}
/* favicon with dark browser theme */
#media (aspect-ratio: 1/1) and (prefers-color-scheme: dark) {
path {
fill: white;
}
}
One very important thing
The SVG must contain viewBox information, so that the stretching does not affect the graphics. Example:
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">
Actually, there is a quite more flexible solution to this problem: writing a Web Component which will patch SVG as text at runtime. I also published in a gist with a link to JSFiddle.
👍 filter: invert(42%) sepia(93%) saturate(1352%) hue-rotate(87deg) brightness(119%) contrast(119%);
<html>
<head>
<title>SVG with color</title>
</head>
<body>
<script>
(function () {
const createSvg = (color = '#ff9933') => `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="76px" height="22px" viewBox="-0.5 -0.5 76 22">
<defs/>
<g>
<ellipse cx="5" cy="10" rx="5" ry="5" fill="#ff9933" stroke="none" pointer-events="all"/>
<ellipse cx="70" cy="10" rx="5" ry="5" fill="#ff9933" stroke="none" pointer-events="all"/>
<path d="M 9.47 12.24 L 17.24 16.12 Q 25 20 30 13 L 32.5 9.5 Q 35 6 40 9 L 42.5 10.5 Q 45 12 50 6 L 52.5 3 Q 55 0 60.73 3.23 L 66.46 6.46" fill="none" stroke="#ff9933" stroke-miterlimit="10" pointer-events="stroke"/>
</g>
</svg>`.split('#ff9933').join(color);
function SvgWithColor() {
const div = Reflect.construct(HTMLElement, [], SvgWithColor);
const color = div.hasAttribute('color') ? div.getAttribute('color') : 'cyan';
div.innerHTML = createSvg(color);
return div;
}
SvgWithColor.prototype = Object.create(HTMLElement.prototype);
customElements.define('svg-with-color', SvgWithColor);
document.body.innerHTML += `<svg-with-color
color='magenta'
></svg-with-color>`;
})();
</script>
</body>
</html>
My answer would be this. But I’m not 100% sure if it works for everyone:
Select 'svg' and then 'path'. And you can change 'fill' then.
.eye-icon-container {
width: 33px;
height: 33px;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
:hover {
background-color: #ddf0ff;
}
:active {
background-color: #1d398d;
svg {
path {
fill: #fff;
}
}
}
}
If you have a single-colour SVG with varying opacities that you simply want to tint to a different colour then there is another approach that can be used: the feFlood SVG filter.
This solution is not as straightforward as a single-line CSS, however:
It works on SVGs inside of an img element.
This doesn't require editing the source SVG at all.
It allows you to simply choose a target colour for the SVG and not worry about complex colour transforms, like hue-rotate.
Here is an example:
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
<defs>
<filter id="recolourFilter" filterUnits="userSpaceOnUse">
<feFlood flood-color="aquamarine" result="flood" />
<feComposite in="flood" in2="SourceAlpha" operator="in" />
</filter>
</defs>
</svg>
<img style="filter: url(#recolourFilter);" width="300" src="https://upload.wikimedia.org/wikipedia/commons/6/6b/Bitmap_VS_SVG.svg" />
In the above example, we create an inline SVG to define the filters and then we apply it to the image. Inside of the <filter> block we first define the fill colour that we want via <feFlood> and then we create a composite image using the alpha channel of the source plus the flood colour. Finally, the filter is applied to the whole image via the filter CSS property on the img element.
I learned about this technique from this Smashing Magasine article. It's a highly recommended read if you want to learn more about SVG filters.
A few additional things to note:
This filter can be applied to any HTML element via the CSS filter property.
The same filter can be reused multiple times on the same page.
If you are using an inline SVG then the <defs> block can form part of the svg element and the filter can still be applied to the whole SVG or on selective elements. This avoids needing a separate SVG element for the filters.
A good approach is to use a mixin to control stroke colour and fill colour. My 'svg's are used as icons.
#mixin icon($color, $hoverColor) {
svg {
fill: $color;
circle, line, path {
fill: $color
}
&:hover {
fill: $hoverColor;
circle, line, path {
fill: $hoverColor;
}
}
}
}
You can then do the following in your SCSS file:
.container {
#include icon(white, blue);
}

SVG elements to zoom whole SVG group on click or mouseover

I would like to use the circles within my SVG file to trigger a zoom in centred on the circle. I have got it working with a div acting as the trigger for the zoom but if I instead apply id="pin" to one of the circle elements within the SVG it no longer zooms in. Can anyone tell me why this is?
Is there a better way for me to achieve what I am trying to do? Ideally, I would like it to be possible to click to zoom and then to access other interactivity within the SVG while zoomed in.
If this is not possible is there a simple way to zoom and pan an SVG and to be able to access SVG interactivity while zoomed?
If I have missed something obvious please forgive me, I’m very much still learning the basics!
Rough example:
CodePen link
<div id="pin">click to trigger zoom</div>
<div class="map" id="mapFrame">
<svg class="image" id="mapSVG" 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 1920 1442.5"" style="
enable-background:new 0 0 1920 924.9;" xml:space="preserve">
<g id="Layer_7" data-name="Layer 7">
<image width="1800" height="1350" transform="translate(0) scale(1.069)" opacity="0.3"
xlink:href="https://media.npr.org/assets/img/2020/07/04/seamus-coronavirus-d3-world-map-20200323_wide-a3888a851b91a905e9ad054ea03e177e23620015.png" />
</g>
<g id="one">
<circle cx="929.664" cy="944.287" r="81.191"/>
</g>
<g id="two">
<circle cx="638.164" cy="456.863" r="81.191" />
</g>
<g id="three">
<circle cx="1266.164" cy="498.868" r="81.191" />
</g>
</svg>
</div>
<script src="app.js"></script>
svg {
width: 100%;
height: auto;
}
#pin {
position: absolute;
height: 65px;
width: 75px;
top: 300px;
left: 550px;
padding: 10px;
background-color: yellow;
}
let imgElement = document.querySelector('#mapFrame');
let pinElement = document.querySelector('#pin');
pinElement.addEventListener('click', function (e) {
imgElement.style.transform = 'translate(-' + 0 + 'px,-' + 0 + 'px) scale(2)';
pinElement.style.display = 'none';
});
imgElement.addEventListener('click', function (e) {
imgElement.style.transform = null;
pinElement.style.display = 'block';
});
When you click on the circle, you are also clicking on the background image as well, triggering two events which is essentially cancelling the zoom. You can see this if you place alert('click 1'); and alert('click 2'); in your listeners.
This doesn't happen on the #pin element because it's outside background div and avoids the event bubbling up. This is solved by adding event.stopPropagation();
Code from your CodePen:
let imgElement = document.querySelector('#mapFrame');
let pinElement = document.querySelector('#one'); //changed to #one
pinElement.addEventListener('click', function (e) {
imgElement.style.transform = 'translate(-' + 0 + 'px,-' + 0 + 'px) scale(2)';
pinElement.style.display = 'none';
event.stopPropagation(); //added to prevent bubbling
});
imgElement.addEventListener('click', function (e) {
imgElement.style.transform = null;
pinElement.style.display = 'block';
});

Maintain orientation of some elements in dynamic rotation?

I'm animating rotations of groups of SVG elements using d3.js. However, I want to preserve the orientation of some elements. For example, in this fiddle (code below), the blue dot is the center of the blue circle. The blue dot is displaced vertically from the black dot, which rotates around the yellow center. I want to maintain the vertical relationship between these two dots.
In the fiddle, I maintain the vertical orientation by rotating the "shift" <g> group backwards the same amount that its enclosing group is rotating forwards. This is the same method given in cmonkey's answer here. That works, but I'm wondering whether there are other methods. Is there any way to preserve orientation without an inverse rotation?
Why?
The inverse rotation strategy means that one has to carefully keep the inverse rotations in sync with changes to rotations of outer groups. In full-fledged versions of this code, I use rotations within rotations (within rotations), as in this example. That means summing up all of the outer groups' rotations in order to determine what the inverse rotation should be. I also want to add text labels to SVG elements. Since different text labels will fall within different numbers of rotation groups, each text label will need its own customized rotation if I want to keep the text upright.
Feel free to suggest more D3. I only hand-coded the SVG in these versions in order to get clarity about how I would dynamically generate the SVG with D3 in a later version.
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js"></script>
<style>
.cycle {
stroke : #000000;
fill : none;
}
.movingPointOnCycle {
stroke : #000000;
fill : #000000;
}
#pointB {
stroke : #0000FF;
fill : #0000FF;
}
#epicycle2 {
stroke : #0000FF;
}
.centerOfUniverse {
stroke : #000000;
fill : #000000;
}
.sun {
stroke : #000000;
fill : #F0F000;
}
.mars {
stroke : #000000;
fill : #A00000;
}
.earth {
stroke : #000000;
fill : #00A000;
}
</style>
</head>
<body>
<svg width="400" height="400">
<g transform="translate(200,200) scale(1.3)">
<circle class="sun" r="10"></circle>
<g class="cycle"speed="0.01">
<circle id="deferent" class="cycle" r="60"></circle>
<g class="epicycleCenter" transform="translate(60,0)">
<circle id="pointD" class="movingPointOnCycle" r="2"></circle>
<g class="shift" speed="-0.01" displacement="-25">
<circle id="pointB" class="movingPointOnCycle" r="2"></circle>
<line x1="0" y1="0" x2="0" y2="25" stroke-dasharray="1,2"></line>
<g class="cycle"speed="0.01">
<circle id="epicycle2" class="cycle" r="75"></circle>
</g>
</g>
</g>
</g>
</g>
</svg>
<script type="text/javascript">
var t0 = Date.now();
var svg = d3.select("svg");
d3.timer(function() {
var delta = (Date.now() - t0);
svg.selectAll(".cycle").attr("transform", function(d) {
return "rotate(" + delta * d3.select(this).attr("speed") + ")";
});
svg.selectAll(".shift").attr("transform", function(d) {
return "rotate(" + delta * d3.select(this).attr("speed") + ")"
+
"translate(0," + d3.select(this).attr("displacement") + ")"
;
});
});
</script>
</body>
</html>

Zoom on multiple areas in d3.js

I'm planning to have a geoJSON map inside my svg alongside other svg elements. I would like to be able to zoom (zoom+pan) in the map and keep the map in the same location with a bounding box. I can accomplish this by using a clipPath to keep the map within a rectangular area. The problem is that I also want to enable zooming and panning on my entire svg. If I do d3.select("svg").call(myzoom); this overrides any zoom I applied to my map. How can I apply zoom to both my entire svg and to my map? That is, I want to be able to zoom+pan on my map when my mouse is in the map's bounding box, and when the mouse is outside the bounding box, zoom+pan on the entire svg.
Here's example code: http://bl.ocks.org/nuernber/aeaac0e8edcf7ca93ade.
<svg id="svg" width="640" height="480" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<clipPath id="rectClip">
<rect x="150" y="25" width="400" height="400" style="stroke: gray; fill: none;"/>
</clipPath>
</defs>
<g id="outer_group">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
<g id="svg_map" style="clip-path: url(#rectClip);">
</g>
</g>
</svg><br/>
<script type="text/javascript">
var svg = d3.select("#svg_map");
var mapGroup = svg.append("g");
var projection = d3.geo.mercator();
var path = d3.geo.path().projection(projection);
var zoom = d3.behavior.zoom()
.translate(projection.translate())
.scale(projection.scale())
.on("zoom", zoomed);
mapGroup.call(zoom);
var pan = d3.behavior.zoom()
.on("zoom", panned);
d3.select("svg").call(pan);
mapGroup.attr("transform", "translate(200,0) scale(2,2)");
d3.json("ne_110m_admin_0_countries/ne_110m_admin_0_countries.geojson", function(collection) {
mapGroup.selectAll("path").data(collection.features)
.enter().append("path")
.attr("d", path)
.attr("id", function(d) { return d.properties.name.replace(/\s+/g, "")})
.style("fill", "gray").style("stroke", "white").style("stroke-width",1);
}
);
function panned() {
var x = d3.event.translate[0];
var y = d3.event.translate[1];
d3.select("#outer_group").attr("transform", "translate("+x+","+y+") scale(" + d3.event.scale + ")");
}
function zoomed() {
previousScale = d3.event.scale;
projection.translate(d3.event.translate).scale(d3.event.scale);
translationOffset = d3.event.translate;
mapGroup.selectAll("path").attr("d", path);
}
</script>
You need two zoom behaviours for that. The first one would be attached to the SVG and the second one to the map. In the zoom handlers you would have to take care of taking the appropriate action for each.

Resources