Convert Illustrator SVG export into cross-platform CSS independent mode. CSS rule and style inlining, group gradients into a definitions tag, remove elements with display set to none, make colors hex. Works with react-native-web react-native-svg and svgs to give cross-platform web and native mobile rendering of any SVG produced in Illustrator.
/* | |
Open console. | |
(Import https://raw.githubusercontent.com/MikeMcl/decimal.js/master/decimal.js first if you want/need number formatting) | |
Then copy & paste this + enter, to run this. | |
Copy console output to a NewFile.js or NewFile.jsx file. | |
prettier --write NewFile.js | |
*/ | |
/* eslint no-console: ["error", { allow: ["log"] }] */ | |
/* global document, Decimal*/ | |
(() => { | |
function rgb2hex(input) { | |
const rgb = input.match( | |
/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i | |
); | |
return rgb && rgb.length === 4 | |
? `#${`0${parseInt(rgb[1], 10).toString(16)}`.slice(-2)}${`0${parseInt( | |
rgb[2], | |
10 | |
).toString(16)}`.slice(-2)}${`0${parseInt(rgb[3], 10).toString( | |
16 | |
)}`.slice(-2)}` | |
: input; | |
} | |
/* Inline all rules from all CSS stylesheets as attributes | |
on all matching elements and remove class attribute */ | |
const styles = Array.from(document.querySelectorAll("style")); | |
styles.forEach(styleSheet => { | |
Array.from(styleSheet.sheet.cssRules).forEach(rule => { | |
if (rule.style.display === "none") { | |
// Remove hidden elements | |
Array.from(document.querySelectorAll(rule.selectorText)).forEach(el => { | |
el.parentElement.removeChild(el); | |
}); | |
} else { | |
const styles = []; | |
Object.entries(rule.style).forEach(([key, value]) => { | |
if (key !== "cssText" && value) { | |
/* Convert rgba? values to hex */ | |
styles.push([key, rgb2hex(value.replace(/"/g, ""))]); | |
} | |
}); | |
Array.from(document.querySelectorAll(rule.selectorText)).forEach(el => { | |
styles.forEach(([key, value]) => { | |
el.setAttribute(key, value); | |
}); | |
}); | |
} | |
}); | |
}); | |
styles.forEach(styleSheet => { | |
Array.from(styleSheet.sheet.cssRules).forEach(rule => { | |
Array.from(document.querySelectorAll(rule.selectorText)).forEach(el => { | |
el.removeAttribute("class"); | |
}); | |
}); | |
styleSheet.parentElement.removeChild(styleSheet); | |
}); | |
/* Move inline CSS style values into element attributes */ | |
Array.from(document.querySelectorAll("[style]")).forEach(el => { | |
Object.entries(el.style).forEach(([key, value]) => { | |
if (key !== "cssText" && value) { | |
el.setAttribute(key, rgb2hex(value)); | |
} | |
}); | |
el.removeAttribute("style"); | |
}); | |
Array.from(document.querySelectorAll("g")).forEach(el => { | |
if (el.innerHTML.trim() === "") { | |
el.parentElement.removeChild(el); | |
} | |
}); | |
const gradients = Array.from( | |
document.querySelectorAll("linearGradient, radialGradient") | |
); | |
const svg = document.querySelector("svg"); | |
Array.from(svg.attributes).forEach(attr => { | |
if (attr.name === "xmlns:xlink") { | |
svg.removeAttributeNode(attr); | |
} | |
}); | |
/* Move gradients inside of a defs tag, | |
convert offsets from scientific notation to decimal notation */ | |
if (gradients.length) { | |
if (typeof Decimal !== "undefined") { | |
Array.from(document.querySelectorAll("stop")).forEach(el => { | |
const offset = el.getAttribute("offset"); | |
const newOffset = new Decimal(offset).toFixed(); | |
el.setAttribute("offset", newOffset); | |
}); | |
} | |
const def = document.createElement("defs"); | |
gradients.forEach(el => def.appendChild(el)); | |
svg.insertBefore(def, svg.firstChild); | |
svg.innerHTML = svg.innerHTML | |
.replace(/stopColor/g, "stop-color") | |
.replace(/stopOpacity/g, "stop-opacity"); | |
} | |
const map = { | |
circle: "Circle", | |
clippath: "ClipPath", | |
defs: "Defs", | |
ellipse: "Ellipse", | |
g: "G", | |
image: "Image", | |
line: "Line", | |
lineargradient: "LinearGradient", | |
path: "Path", | |
polygon: "Polygon", | |
polyline: "Polyline", | |
radialgradient: "RadialGradient", | |
rect: "Rect", | |
stop: "Stop", | |
svg: "Svg", | |
symbol: "Symbol", | |
text: "Text", | |
tspan: "TSpan", | |
textpath: "TextPath", | |
use: "Use", | |
"stop-color": "stopColor", | |
"stop-opacity": "stopOpacity" | |
}; | |
const keys = Object.keys(map).map(key => `<${key}[ >]|<\\/${key}| ${key}=`); | |
const match = keys.join("|"); | |
const regex = new RegExp(match, "ig"); | |
const markup = svg.outerHTML; | |
const pre = `import React from "react"; | |
import { | |
Circle, | |
ClipPath, | |
Defs, | |
Ellipse, | |
G, | |
Image, | |
Line, | |
LinearGradient, | |
Path, | |
Polygon, | |
Polyline, | |
RadialGradient, | |
Rect, | |
Stop, | |
Svg, | |
Symbol, | |
Text, | |
TSpan, | |
TextPath, | |
Use | |
} from "svgs"; | |
export default ({ width, height, native }) => (`; | |
const body = markup | |
.replace(regex, key => { | |
const isEnd = key[1] === "/"; | |
const tag = (isEnd | |
? key.slice(2) | |
: key.slice(1, key.length - 1)).toLowerCase(); | |
const replaced = map[tag] || tag; | |
return isEnd | |
? `</${replaced}` | |
: `${key[0]}${replaced}${key[key.length - 1]}`; | |
}) | |
.replace(new RegExp(' xmlns="http://www.w3.org/2000/svg"', "ig"), "") | |
.replace(new RegExp("xml:space", "ig"), "xmlSpace"); | |
const post = ` | |
); | |
`; | |
const start = `<Svg | |
width={width} | |
height={height}`; | |
const output = pre + body.replace("<Svg", start) + post; | |
console.log(output); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment