Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save msand/4b37d3ce04246f83cb28fdbfe4716ecc to your computer and use it in GitHub Desktop.
Save msand/4b37d3ce04246f83cb28fdbfe4716ecc to your computer and use it in GitHub Desktop.
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