I have two SVG paths that have a gap between them.
From reading through other questions (in particular this one) I understand this is because of the native anti-aliasing properties of SVGs.
So I added shapeRendering="crispEdges"
This does remove the gap. However it results in jagged edges because of the removal of anti-aliasing.
<svg height="300" width="300" shapeRendering="crispEdges">
<path
d="M150 10 a120 120 0 0 1 103.9230 60"
fill="none"
stroke="green"
stroke-width="20"
/>
<path
d="M253.9230 70 a120 120 0 0 1 0 120"
fill="none"
stroke="green"
stroke-width="20"
/>
</svg>
I've also tried the suggestion in this question to add crispEdges to the parent svg of the path and add shapeRendering="optimizeQuality" to the path but that didn't work.
How can I remove the gap AND keep the smooth edges of my svg path?
You could also mitigate this gap rendering effect by applying a subtle svg feMorphology dilate filter – resulting in slightly expanded strokes closing thin gaps between paths:
SVG feMorphology filter
svg {
overflow: visible;
}
.chart path {
filter: url(#outline);
}
path:hover {
stroke: red;
}
<svg height="300" width="300" shape-rendering="geometricPrecision">
<g class="original">
<path d="M150 10 a120 120 0 0 1 103.9230 60" fill="none" stroke="green" stroke-width="20" />
<path d="M253.9230 70 a120 120 0 0 1 0 120" fill="none" stroke="green" stroke-width="20" />
</g>
<text x="50%" y="50%">Original</text>
</svg>
<svg height="300" width="300">
<filter filterUnits="userSpaceOnUse" id="outline" >
<feMorphology in="SourceGraphic" result="DILATED" operator="dilate" radius="0.5" />
</filter>
<g class="chart">
<path d="M150 10 a120 120 0 0 1 103.9230 60" fill="none" stroke="green" stroke-width="20" />
<path d="M253.9230 70 a120 120 0 0 1 0 120" fill="none" stroke="green" stroke-width="20" />
</g>
<text x="50%" y="50%">Dilate filter</text>
</svg>
But this approach will also introduce slightly rounded edges (you can see this effect on hover).
More importantly, svg filters are quite expensive with regards to rendering performance – rather negligible if you only display a few elements per page view.
Add concatenated background path
As suggested by #Robert Longson: you could also prepend a background path based on concatenated d path data.
This task could be achieved with a javaScript helper method cloning the first path and displaying the concatenated paths.
addBgPaths(".addBGPath");
function addBgPaths(selector) {
let addPathSvgs = document.querySelectorAll(selector);
addPathSvgs.forEach(function(svg) {
let paths = document.querySelectorAll(".addBGPath path");
let firstPath = paths[0];
let firstPathCloned = firstPath.cloneNode();
//cloned elements shouldn't have ids to avoid non unique ids
firstPathCloned.removeAttribute("id");
let dArr = [firstPath.getAttribute("d")];
for (let i = 1; i < paths.length; i++) {
let path = paths[i];
let d = path.getAttribute("d");
dArr.push(d);
}
firstPathCloned.setAttribute("d", dArr.join(" "));
svg.insertBefore(firstPathCloned, svg.children[0]);
});
}
<svg height="300" width="300" shape-rendering="geometricPrecision">
<path d="M150 10 a120 120 0 0 1 103.9230 60" fill="none" stroke="green" stroke-width="20" />
<path d="M253.9230 70 a120 120 0 0 1 0 120" fill="none" stroke="green" stroke-width="20" />
<text x="0" y="50%">Original</text>
</svg>
<svg class="addBGPath" height="300" width="300" shape-rendering="geometricPrecision">
<path id="first2" d="M150 10 a120 120 0 0 1 103.9230 60" fill="none" stroke="green" stroke-width="20" />
<path d="M253.9230 70 a120 120 0 0 1 0 120" fill="none" stroke="green" stroke-width="20" />
<text x="0" y="50%">Add bg path</text>
</svg>
<svg class="addBGPath" height="300" width="300" shape-rendering="geometricPrecision">
<path id="first" d="M150 10 a120 120 0 0 1 103.9230 60" fill="none" stroke="green" stroke-width="20" />
<path d="M253.9230 70 a120 120 0 0 1 0 120" fill="none" stroke="red" stroke-width="20" />
<text x="0" y="50%">Add bg path (red)</text>
</svg>
<svg height="300" width="300" shape-rendering="geometricPrecision">
<path d="M150 10 a120 120 0 0 1 103.9230 60" fill="none" stroke="green" stroke-width="20" />
<path d="M253.9230 70 a120 120 0 0 1 0 120" fill="none" stroke="red" stroke-width="20" />
<text x="0" y="50%">Original (red)</text>
</svg>
If all your path segments have the same color, this is probably the most elegant solution.
But this approach will also introduce colored "halos" when segments use different stroke colors (example #3 compared to #4).
If you able to edit the svg in editor, you can overlap like this. The darker green is the intersection between two paths.
As a quick fix, you can make the ends overlap with stroke-linecap="square"
But ideally, you need to create a single path instead of two separate paths.
<svg height="300" width="300" shapeRendering="crispEdges">
<path
d="M150 10 a120 120 0 0 1 103.9230 60"
fill="none"
stroke="green"
stroke-width="20"
stroke-linecap="square"
/>
<path
d="M253.9230 70 a120 120 0 0 1 0 120"
fill="none"
stroke="green"
stroke-width="20"
stroke-linecap="square"
/>
</svg>
I'm creating a 5-star-review using SVG, but need to be able to display a value to 1-decimal-point (e.g. 2.5/5 or 4.2/5).
I also need to be able to display multiple 5-star-reviews on the same page.
I've been able to create the following (which displays 2.5) but the 50% on the second <rect> in the mask (which is required for the 0.5 on the 3rd star) is hardcoded...
<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<mask id="partial">
<rect x="0" y="0" width="64" height="64" fill="white" />
<rect x="50%" y="0" width="64" height="64" fill="black" />
</mask>
<symbol xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" id="star">
<path d="M32 2l-10 22l-20 2l15 14l-5 22l20-12l20 12l-5-22l15-14l-20-2Z"
stroke-width="3" stroke="#FFC800" />
</symbol>
</defs>
</svg>
<svg width="32" height="32" viewBox="0 0 64 64">
<use href="#star" fill="#FFC800"></use>
<use href="#star" fill="none"></use>
</svg>
<svg width="32" height="32" viewBox="0 0 64 64">
<use href="#star" fill="#FFC800"></use>
<use href="#star" fill="none"></use>
</svg>
<svg width="32" height="32" viewBox="0 0 64 64">
<use href="#star" fill="#FFC800" mask="url(#partial)"></use>
<use href="#star" fill="none"></use>
</svg>
<svg width="32" height="32" viewBox="0 0 64 64">
<use href="#star" fill="none"></use>
</svg>
<svg width="32" height="32" viewBox="0 0 64 64">
<use href="#star" fill="none"></use>
</svg>
Is there any way (preferably without javascript/jquery) that I can set the x="50%" on the <use> element?
Please remember that I will have multiple instances of the 5-star-review on the page at the same time, so it's not possible for me to dynamic set the x=50% on the <mask> because it will effect all instances.
What I don't want is to have to have 9 different id="partial10", id="partial20" <mask> elements.
I would like to do something like...
<use href="#star" fill="#FFC800" maskwidth="30%"></use>
If you want multiple instances on one page, then go for a native Web Component (supported in all browsers)
All HTML required:
<star-rating stars=5 rating="3.5"
bgcolor="green" nocolor="grey" color="gold"></star-rating>
<star-rating stars=7 rating="50%"
bgcolor="rebeccapurple" nocolor="beige" color="goldenrod"></star-rating>
to create:
But it does require JavaScript to create the Web Component
Key is to draw the rating color behind cutouts of all stars
Needs minor work to make 51% notation possible,
add more <rect> elements, it now does 2 for each star, and set the correct width for each mouseover capturing <rect>
The beauty of Web Components is: It totally does NOT matter when the <star-rating> is defined.
customElements.define("star-rating", class extends HTMLElement {
set rating(rate) {
if (!String(rate).includes("%")) rate = Number(rate) / this.stars * 100 + "%";
this.querySelector(":nth-child(2)").setAttribute("width", rate); //2nd rect
}
connectedCallback() {
let {bgcolor,stars,nocolor,color,rating} = this.attributes;
let repeat = (count, func) => Array(count).fill().map(func);
this.stars = ~~stars.value || 5;
this.innerHTML = `<svg viewBox="0 0 ${this.stars*100} 100" style=cursor:pointer>` +
`<rect height=100 fill=${nocolor.value} width=100% />` +
`<rect height=100 fill=${color.value} />` +
repeat(this.stars , (i, n) => `<path fill=${bgcolor.value} d="m${ n*100 } 0h102v100h-102v-100m91 42a6 6 90 00-4-10l-22-1a1 1 90 01-1 0l-8-21a6 6 90 00-11 0l-8 21a1 1 90 01-1 1l-22 1a6 6 90 00-4 10l18 14a1 1 90 010 1l-6 22a6 6 90 008 6l19-13a1 1 90 011 0l19 13a6 6 90 006 0a6 6 90 002-6l-6-22a1 1 90 010-1z"/>`) +
repeat(this.stars * 2, (i, n) => `<rect x=${ n*50 } n=${n} opacity=0 width=50 height=100 ` +
` onclick="this.closest('star-rating').dispatchEvent(new Event('click'))" ` +
` onmouseover="this.closest('star-rating').rating=${(n+1)/2}"/>`) +
"</svg>";
this.rating = rating.value;
}
});
<star-rating stars=5 rating="3.5"
bgcolor="green" nocolor="grey" color="gold"></star-rating>
<br>
<star-rating stars=7 rating="50%"
bgcolor="rebeccapurple" nocolor="beige" color="goldenrod"></star-rating>
I'm trying to toggle between two classes dark mode and normal mode.
THis is the vanilla js eventlistener
const modeSwitch = document.querySelector('.mode-switch');
modeSwitch.addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
modeSwitch.classList.toggle('active');
});
This is the button that when clicked on, switches between the two modes. how can I achieve this with react
const [active, setActive] = useState(false)
const handleToggle = () => {
setActive(!active)
}
return (
<button className="mode-switch" title="Switch Theme" onClick={handleToggle}>
<svg className="moon" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" width="24" height="24" viewBox="0 0 24 24">
<defs></defs>
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"></path>
</svg>
</button>
)
In this case you could use useRef:
const [active, setActive] = useState(false)
const modeRef = useRef();
const handleToggle = () => {
modeRef.current.classList.toggle("dark")
}
return (
<button ref={modeRef} className="mode-switch" title="Switch Theme" onClick={handleToggle}>
<svg className="moon" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" width="24" height="24" viewBox="0 0 24 24">
<defs></defs>
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"></path>
</svg>
</button>
)
I have two SVG icons I'm importing within my react application using "react-svg-loader"
The icons are imported as components with their own paths yet the output is the same for which ever icon is first in the code.
Why is this happening and how can I fix this?
Here is my code:
import React, { Component } from 'react';
import Navigation from '../Navigation/index.js';
import MainLogo from '../MainLogo/index.js';
import Search from '../Search/index.js';
import './index.css'; // styles from
import Logo from '../../assets/svg/logos/Voo_Main.svg';
import SearchIcon from '../../assets/svg/icons/search.svg';
export default class Header extends Component {
render() {
const { navItems } = this.props;
return (
<header className="header">
<SearchIcon />
<Logo />
</header>
);
}
};
Here is the output:
Here is my loader within my webpack config:
// react-svg-loader
// https://www.npmjs.com/package/react-svg-loader
{
test: /\.svg$/,
use: [
{
loader: "babel-loader",
},
{
loader: "react-svg-loader",
options: {
jsx: true, // true outputs JSX tags
},
},
],
},
Here is the code for the SVG icons from the DOM:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
<defs>
<path id="a" d="M14.5 16A6.508 6.508 0 0 1 8 9.5C8 5.916 10.916 3 14.5 3S21 5.916 21 9.5 18.084 16 14.5 16m0-15C9.813 1 6 4.813 6 9.5c0 1.983.688 3.807 1.832 5.254l-6.539 6.539a.999.999 0 1 0 1.414 1.414l6.539-6.539A8.443 8.443 0 0 0 14.5 18c4.687 0 8.5-3.813 8.5-8.5C23 4.813 19.187 1 14.5 1"></path>
</defs>
<g fill="none" fill-rule="evenodd">
<mask id="b" fill="#fff">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#a"></use>
</mask>
<use fill="#D3D3D3" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#a"></use>
<g fill="#121A28" mask="url(#b)">
<path d="M0 24h24V0H0z"></path>
</g>
</g>
</svg>
Second icon:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="120" height="50" viewBox="0 0 120 50">
<defs>
<path id="a" d="M95.448 0c-6.967 0-13.25 2.905-17.717 7.567C73.262 2.905 66.979 0 60.01 0c-6.974 0-13.263 2.912-17.73 7.578C37.81 2.912 31.523 0 24.548 0 10.992 0 0 10.989 0 24.546c0 13.557 10.992 24.548 24.548 24.548 6.975 0 13.263-2.91 17.732-7.579 4.467 4.668 10.756 7.579 17.73 7.579 6.969 0 13.252-2.908 17.72-7.568 4.469 4.66 10.751 7.568 17.718 7.568 13.56 0 24.552-10.99 24.552-24.548C120 10.989 109.008 0 95.448 0M37.533 19.734l-9.664 14.84a3.968 3.968 0 0 1-3.327 1.8 3.967 3.967 0 0 1-3.33-1.8l-9.661-14.84a3.937 3.937 0 0 1 1.17-5.462 3.971 3.971 0 0 1 5.484 1.167l6.337 9.728 6.332-9.728a3.974 3.974 0 0 1 5.485-1.167 3.94 3.94 0 0 1 1.174 5.462m22.465 15.7c-6.018 0-10.898-4.878-10.898-10.898 0-6.018 4.88-10.899 10.898-10.899 6.021 0 10.903 4.88 10.903 10.9 0 6.02-4.88 10.897-10.903 10.897m35.438 0c-6.018 0-10.898-4.878-10.898-10.898 0-6.018 4.88-10.899 10.898-10.899 6.021 0 10.902 4.88 10.902 10.9 0 6.02-4.88 10.897-10.902 10.897"></path>
</defs>
<g fill="none" fill-rule="evenodd">
<mask id="b" fill="#fff">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#a"></use>
</mask>
<use fill="#FEFEFE" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#a"></use>
<g fill="#D4107A" mask="url(#b)">
<path d="M0 0h120v50H0z"></path>
</g>
</g>
</svg>
If you convert the SVG to a .jsx file and modify the attributes so it's .jsx-friendly, it will render the SVGs as if it were a .svg file.
import * as React from 'react';
const svg = (
<div>
<svg
width="120"
height="50"
viewBox="0 0 120 50"
>
<defs>
<path
id="a"
d="M95.448 0c-6.967 0-13.25
2.905-17.717 7.567C73.262 2.905 66.979 0
60.01 0c-6.974 0-13.263 2.912-17.73 7.578C37.81
2.912 31.523 0 24.548 0 10.992 0 0 10.989 0 24.546c0
13.557 10.992 24.548 24.548 24.548 6.975 0 13.263-2.91
17.732-7.579 4.467 4.668 10.756 7.579 17.73 7.579 6.969
0 13.252-2.908 17.72-7.568 4.469 4.66 10.751 7.568 17.718
7.568 13.56 0 24.552-10.99 24.552-24.548C120 10.989 109.008
0 95.448 0M37.533 19.734l-9.664 14.84a3.968 3.968 0 0 1-3.327
1.8 3.967 3.967 0 0 1-3.33-1.8l-9.661-14.84a3.937 3.937 0 0 1
1.17-5.462 3.971 3.971 0 0 1 5.484 1.167l6.337
9.728 6.332-9.728a3.974 3.974 0 0 1 5.485-1.167 3.94 3.94 0
0 1 1.174 5.462m22.465 15.7c-6.018 0-10.898-4.878-10.898-10.898
0-6.018 4.88-10.899 10.898-10.899 6.021 0 10.903 4.88 10.903
10.9 0 6.02-4.88 10.897-10.903 10.897m35.438 0c-6.018
0-10.898-4.878-10.898-10.898 0-6.018 4.88-10.899 10.898-10.899
6.021 0 10.902 4.88 10.902 10.9 0 6.02-4.88 10.897-10.902 10.897"
/>
</defs>
<g fill="none" fillRule="evenodd">
<mask id="b" fill="#fff">
<use xmlns="http://www.w3.org/1999/xlink" xlinkHref="#a"/>
</mask>
<use fill="#FEFEFE"/>
<g fill="#D4107A" mask="url(#b)">
<path d="M0 0h120v50H0z"/>
</g>
</g >
</svg >
</div>
);
Excuse the blue background. That's the background color I have in my test dev environment.
Encountered the same issue - it seems that different SVGs used the same path id.
This is how I solved it - changed each SVG file to use different ID:
In your case - you should change the second SVG:
<path id="a" to <path id="b"
AND
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#a"></use>
to
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#b"></use>
AND
<use fill="#D3D3D3" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#a"></use>
to
<use fill="#D3D3D3" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#b"></use>
I'm trying to render an svg within a react component.
here's the svg that is rendered correctly
<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 427.3 444.5" style="enable-background:new 0 0 427.3 444.5;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#7FD093;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st1{display:none;fill:none;}
</style>
<symbol id="Stylish" viewBox="-154 -190.2 154 380.4">
<path id="XMLID_12_" class="st0" d="M-46.5,165c-6.7-3.3-15.6,2.9-15.8,10.4c-0.2,7.5,7.2,13.9,14.7,13.8s14.2-5.8,16.7-12.9
c4.5-12.7-3.7-26.7-14.4-34.9s-23.8-12.6-35.1-19.9c-9-5.8-16.7-13.4-22.7-22.2c-1.9-2.8-3.8-6.1-3.6-9.5c0.4-7.6,11.7-11,17.3-5.8
c5.6,5.2,4.6,15-0.7,20.4c-5.4,5.4-13.7,6.9-21.2,5.7c-9.5-1.5-18.5-7-23.2-15.3c-4.8-8.3-4.7-19.5,0.9-27.2
c7.6-10.5,23.2-13.3,29.7-24.5c5.2-8.9,2.7-20.8-3.5-29s-21.1-26.8-38.4-20.1c-7.4,2.8-7.8,15.3-5,20.2c7.7,13.3,21.9,5.7,24-5
c2.6-12.9-12.1-51-0.2-66.7c16-21,45.3-22,49.8-8.6c2.9,8.6-1.9,19.6-12.1,21.1c-9.1,1.3-20-8.1-18.6-41.2
c2.7-64.1,62.8-62.8,72.2-64.1c9.4-1.3,33.4-2.7,34.7-21.4S-22.2-195-24-181.1c-1.3,10.3,9.6,8,9.6,8"/>
<polygon id="XMLID_13_" class="st1" points="-154,190 0,190 0,-190 -154,-190 "/>
</symbol>
<use xlink:href="#Stylish" width="154" height="380.4" id="XMLID_1_" x="-154" y="-190.2" transform="matrix(1 0 0 -1 190.6723 229.2729)" style="overflow:visible;"/>
<use xlink:href="#Stylish" width="154" height="380.4" id="XMLID_5_" x="-154" y="-190.2" transform="matrix(-1 0 0 -1 236.6723 229.2729)" style="overflow:visible;"/>
</svg>
and here's my react component, i converted all the attributes but nothing happens/ or, if I play with the viewBox a black rectangle appears.
import React from 'react';
var _ = require('lodash');
export class SVGStylish extends React.Component{
render(){
return(<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
x={0} y={0}
viewBox="0 0 427.3 444.5" style={{enableBackground:'new 0 0 427.3 444.5'}}
xmlSpace="preserve">
<symbol id="Stylish" viewBox="-154 -190.2 154 380.4">
<path id="XMLID_12_"
style = {{
fill:'none',
stroke:'#7FD093',
strokeWidth:2,
strokeLinecap:'round',
strokeLinejoin:'round',
strokeMiterlimit:10
}}
d="M-46.5,165c-6.7-3.3-15.6,2.9-15.8,10.4c-0.2,7.5,7.2,13.9,14.7,13.8s14.2-5.8,16.7-12.9
c4.5-12.7-3.7-26.7-14.4-34.9s-23.8-12.6-35.1-19.9c-9-5.8-16.7-13.4-22.7-22.2c-1.9-2.8-3.8-6.1-3.6-9.5c0.4-7.6,11.7-11,17.3-5.8
c5.6,5.2,4.6,15-0.7,20.4c-5.4,5.4-13.7,6.9-21.2,5.7c-9.5-1.5-18.5-7-23.2-15.3c-4.8-8.3-4.7-19.5,0.9-27.2
c7.6-10.5,23.2-13.3,29.7-24.5c5.2-8.9,2.7-20.8-3.5-29s-21.1-26.8-38.4-20.1c-7.4,2.8-7.8,15.3-5,20.2c7.7,13.3,21.9,5.7,24-5
c2.6-12.9-12.1-51-0.2-66.7c16-21,45.3-22,49.8-8.6c2.9,8.6-1.9,19.6-12.1,21.1c-9.1,1.3-20-8.1-18.6-41.2
c2.7-64.1,62.8-62.8,72.2-64.1c9.4-1.3,33.4-2.7,34.7-21.4S-22.2-195-24-181.1c-1.3,10.3,9.6,8,9.6,8"/>
<polygon id="XMLID_13_" points="-154,190 0,190 0,-190 -154,-190 "/>
</symbol>
<use xlinkHref="#Stylish" width="154" height="380.4"
id="XMLID_1_" x="-154" y="-190.2"
style={{transform:"matrix(1 0 0 -1 190.6723 229.2729)", overflow:'visible'}}
/>
<use xlinkHref="#Stylish" width="154" height="380.4"
id="XMLID_5_" x="-154" y="-190.2"
style={{transform:"matrix(-1 0 0 -1 236.6723 229.2729)", overflow:'visible'}}
/>
</svg>
);
}
}
the react component seems to render correct elements, attribute and styles.
any ideas? I'm sure i'm missing something..
Thanks in advance
Hi the problem is that you were missing the style of the polygon st1.
I have created a css and added the class name.
I have refactor the code and you can see it working here