Skip to content

Instantly share code, notes, and snippets.

@MoOx
Last active December 3, 2018 08:50
Show Gist options
  • Save MoOx/1eb30eac43b2114de73a to your computer and use it in GitHub Desktop.
Save MoOx/1eb30eac43b2114de73a to your computer and use it in GitHub Desktop.
Svg icons with React.js with webpack loader (svg: raw-loader)
.SVGIcon {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* fix webkit/blink poor rendering issues */
transform: translate3d(0,0,0);
/* it's better defined directly because of the cascade shit
width: inherit;
height: inherit;
line-height: inherit;
*/
}
.SVGIcon-svg {
width: inherit;
height: inherit;
line-height: inherit;
color: inherit;
fill: currentColor;
}
import React, {Component, PropTypes} from "react"
// import styled from "bloody-react-styled"
import cx from "classnames"
// import styles from "./styles"
const cleanups = {
// some useless stuff for us
// that svgo doesn't remove
title: /<title>.*<\/title>/gi,
desc: /<desc>.*<\/desc>/gi,
comment: /<!--.*-->/gi,
defs: /<defs>.*<\/defs>/gi,
// remove hardcoded dimensions
width: / +width="\d+(\.\d+)?(px)?"/gi,
height: / +height="\d+(\.\d+)?(px)?"/gi,
// remove fill
fill: / +fill=\"(none|#[0-9a-f]+)\"/gi,
// Sketch.app shit
sketchMSShapeGroup: / +sketch:type=\"MSShapeGroup\"/gi,
sketchMSPage: / +sketch:type=\"MSPage\"/gi,
sketchMSLayerGroup: / +sketch:type=\"MSLayerGroup\"/gi,
}
// @styled(styles)
export default class SVGIcon extends Component {
static defaultProps = {
component: "span",
classSuffix: "-svg",
cleanup: [],
cleanupExceptions: [],
}
static propTypes = {
component: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
]),
svg: PropTypes.string.isRequired,
fill: PropTypes.string,
cleanup: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.array,
]),
width: PropTypes.string,
height: PropTypes.string,
}
static cleanupSvg(svg, cleanup = []) {
return Object.keys(cleanups)
.filter(key => cleanup.includes(key))
.reduce((acc, key) => {
return acc.replace(cleanups[key], "")
}, svg)
.trim()
}
render() {
const {
className,
component,
svg,
fill
} = this.props
let cleanup = this.props.cleanup
if (
// simple way to enable entire cleanup
cleanup === true ||
// passing cleanupExceptions enable cleanup as well
(
this.props.cleanup.length === 0 &&
this.props.cleanupExceptions.length > 0
)
) {
cleanup = Object.keys(cleanups)
}
cleanup = cleanup.filter(
key => {
return !this.props.cleanupExceptions.includes(key)
}
)
let {
width,
height
} = this.props
if (width && height === undefined) {
height = width
}
const props = {...this.props, svg:null, fill: null, width: null, height:null}
const classes = cx({
"SVGIcon": true,
"SVGIcon--cleaned": cleanup.length,
[className]: className,
})
const svgClasses = classes
.split(" ")
.join(this.props.classSuffix + " ") + this.props.classSuffix
return (
React.createElement(
component,
{
...props, // take most props
className: classes,
dangerouslySetInnerHTML: {
__html: SVGIcon.cleanupSvg(svg, cleanup).replace(
/<svg/,
`<svg class="${ svgClasses }"` +
(
fill
? ` fill="${ fill }"`
: ``
) +
(
width || height
? (
` style="` +
(width ? `width: ${width};` : ``) +
(height ? `height: ${height};` : ``) +
`"`
)
: ""
)
),
},
}
)
)
}
}
import tape from "tape-catch"
import React, {Component} from "react"
import SVGIcon from ".."
tape("SVGIcon", (test) => {
test.equal(
React.renderToStaticMarkup(
<SVGIcon className="TestSVG" svg="<svg><g></g></svg>" />
),
`<span class="SVGIcon TestSVG"><svg class="SVGIcon-svg TestSVG-svg"` +
`><g></g></svg></span>`,
"passes & merges className"
)
test.equal(
React.renderToStaticMarkup(
<SVGIcon
component="div"
className="TestSVG"
svg="<svg><g></g></svg>"
/>
),
`<div class="SVGIcon TestSVG"><svg class="SVGIcon-svg TestSVG-svg"` +
`><g></g></svg></div>`,
"parent component can be chosen by tagName"
)
class TestComponent extends Component {
render() {
return (
<div {...this.props} className="foo" />
)
}
}
test.equal(
React.renderToStaticMarkup(
<SVGIcon
component={TestComponent}
className="TestSVG"
svg="<svg><g></g></svg>" />
),
`<div class="foo"><svg class="SVGIcon-svg TestSVG-svg"><g></g></svg>` +
`</div>`,
"parent composite component can be chosen"
)
const svgPiece = `width="24" height="16px"><g fill="none"><path ` +
`fill="#ab234f"></path></g></svg>`
const SVGIconStart = `<span class="SVGIcon"><svg class="SVGIcon-svg"`
const SVGIconCleanedStart = `<span class="SVGIcon SVGIcon--cleaned">` +
`<svg class="SVGIcon-svg SVGIcon--cleaned-svg"`
test.equal(
React.renderToStaticMarkup(
<SVGIcon svg={`<svg ${svgPiece}`} />
),
`${SVGIconStart} ${svgPiece}</span>`,
"doesn't cleanup the svg by default"
)
test.equal(
React.renderToStaticMarkup(
<SVGIcon cleanup svg={`<svg ${svgPiece}`} />
),
`${SVGIconCleanedStart}><g><path></path></g></svg></span>`,
"can cleanup the svg"
)
test.equal(
React.renderToStaticMarkup(
<SVGIcon cleanupExceptions={["fill"]} svg={`<svg ${svgPiece}`} />
),
`${SVGIconCleanedStart}><g fill="none"><path fill="#ab234f"></path></g>` +
`</svg></span>`,
"cleanup the svg with exceptions"
)
test.equal(
React.renderToStaticMarkup(
<SVGIcon svg={`<svg><g></g></svg>`} width="1rem" />
),
`${SVGIconStart} style="width: 1rem;height: 1rem;"><g></g></svg></span>`,
"should add width (and height automatically)"
)
test.equal(
React.renderToStaticMarkup(
<SVGIcon svg={`<svg><g></g></svg>`} width="1rem" height="auto" />
),
`${SVGIconStart} style="width: 1rem;height: auto;"><g></g></svg></span>`,
"should add width & height"
)
test.equal(
React.renderToStaticMarkup(
<SVGIcon svg={`<svg><g></g></svg>`} height="1rem" />
),
`${SVGIconStart} style="height: 1rem;"><g></g></svg></span>`,
"should add height"
)
test.end()
})
@gaearon
Copy link

gaearon commented Jun 5, 2014

We use the same convention for modifiers in React. :-)

@wmertens
Copy link

@MoOx do you have a solution for using the same icon multiple times in the same page without having the same markup multiple times, causing more work for the browser?

@wmertens
Copy link

Ooh found a performance comparison (font files win) and apparently there's a symbol mode in svg.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment