Optimising icon with SVGO
This function is part of icon manipulation functions in Iconify Tools.
Function runSVGO() optimises icon using popular SVG optimisation tool SVGO.
It is meant to be used on icons that have already been processed with cleanupSVG(), which does most of the cleanup.
This function is used to do advanced stuff that SVGO is good at: converting transformations, cleaning up numbers, removing unused SVG elements, and so on.
Plugins
By default, function uses a pre-configured list of SVGO plugins, which excludes some bugged plugins.
If an icon contains SVG animations, plugins that modify shapes are excluded.
Usage
Function has the following parameters:
- svg, SVG. Icon instance.
- options, object. Options (optional).
Options
There are two ways to set options:
- Using a custom list of SVGO plugins.
- Toggle groups of plugins using several options.
Custom plugins list
You can set custom plugins using plugins property of options. Value is array of plugins, passed directly to SVGO (see SVGO documentation).
Example:
runSVGO(svg, {
plugins: ['convertStyleToAttrs', 'inlineStyles'],
multipass: true,
});
Plugin options
You can also pick from a preset list of plugins by setting these options:
- animated, boolean. If true, SVGO plugins that are known to bug out with animated icons are not used.
- keepShapes, boolean. If true, plugins that modify shapes are not used. This is useful if you need to keep shapes as-is, for example, when animating shapes, but it is not as strict as setting animated option.
- cleanupIDs, string|false|function. Custom prefix for rewriting IDs, false to disable plugins that change IDs. Can be a callback that returns new ID based on old ID.
These options cannot be used together with plugins option.
Other options
Options that can be used with any options listed above:
- multipass, boolean. If true, plugins are ran multiple times for better optimisation. Enabled by default.
Example
import { SVG, runSVGO } from '@iconify/tools';
const reallyBadIcon = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="2048"
height="2048"
id="svg3891"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="trash.svg"
inkscape:export-filename="/home/nikku/camunda/projects/bpmn.io/bpmn-font/raw/trash.png"
inkscape:export-xdpi="0.88"
inkscape:export-ydpi="0.88">
<defs
id="defs3893">
<inkscape:path-effect
effect="spiro"
id="path-effect4094"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect4094-0"
is_visible="true" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.175"
inkscape:cx="307.67263"
inkscape:cy="1030.7415"
inkscape:document-units="px"
inkscape:current-layer="layer1-6"
showgrid="false"
inkscape:window-width="1596"
inkscape:window-height="807"
inkscape:window-x="0"
inkscape:window-y="91"
inkscape:window-maximized="0"
inkscape:snap-page="false"
inkscape:snap-object-midpoints="false"
inkscape:snap-nodes="false"
inkscape:snap-to-guides="false"
inkscape:snap-grids="false" />
<metadata
id="metadata3896">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,995.63783)">
<g
transform="matrix(96.752895,0,0,96.752895,55.328158,-100816.34)"
id="layer1-6"
inkscape:label="Layer 1"
style="display:inline">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.343629;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 3.4296875,1038.3672 1.3325877,12.7308 10.5912408,0 1.228186,-12.7284 -13.1520736,0 z m 1.4921875,1.3437 10.185547,0 -0.972656,10.0411 -8.1582035,0 z"
id="rect4089"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccc" />
<g
id="g4275"
transform="matrix(1,0,0,0.90111263,0,103.41515)">
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
inkscape:original-d="m 7.0333918,1040.9794 0.9432241,7.504"
inkscape:path-effect="#path-effect4094"
id="path4092"
d="m 7.0333918,1040.9794 0.9432241,7.504"
style="fill:none;stroke:#000000;stroke-width:1.343629;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
inkscape:original-d="m 12.990235,1040.9794 -0.943224,7.504"
inkscape:path-effect="#path-effect4094-0"
id="path4092-2"
d="m 12.990235,1040.9794 -0.943224,7.504"
style="fill:none;stroke:#000000;stroke-width:1.343629;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<path
style="fill:#000000;fill-opacity:1;stroke:none"
d="m 7.2638322,1035.194 -4.2854023,1.2542 0,0.6276 14.0667651,0 0,-0.6276 -4.337726,-1.2542 z"
id="rect4121"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.72291225;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 7.6269598,1033.8929 4.7697062,0 0,1.737 -4.7697062,0 z"
id="rect4121-6" />
</g>
</g>
</svg>`;
const svg = new SVG(reallyBadIcon);
runSVGO(svg);
console.log(svg.toMinifiedString());
<svg xmlns="http://www.w3.org/2000/svg" width="2048" height="2048" viewBox="0 0 2048 2048"><metadata/><path d="m3.43 1038.367 1.332 12.731h10.592l1.228-12.728H3.43zm1.492 1.344h10.185l-.972 10.041H5.977z" color="#000" enable-background="accumulate" font-family="sans-serif" overflow="visible" style="line-height:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;block-progression:tb;white-space:normal;isolation:auto;mix-blend-mode:normal" transform="translate(55.328 -99820.702) scale(96.7529)"/><path fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.344" d="m7.033 1040.98.944 7.503m5.013-7.503-.943 7.503" transform="matrix(96.7529 0 0 87.185 55.328 -89815)"/><path d="M758.141 337.32 343.458 458.648v60.76h1361.023v-60.76L1284.767 337.32z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="69.952" d="M793.262 211.444h461.512v168.06H793.262z"/></svg>
That example shows running SVGO on icon that has not been cleaned up and validated. Not all useless attributes have been removed, and SVGO doesn't check for some content that should not be in icon, such as text, raster images and events.
Therefore, all icons must be cleaned up after loading.
Same code with clean up:
import { SVG, runSVGO, cleanupSVG } from '@iconify/tools';
const reallyBadIcon = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="2048"
height="2048"
id="svg3891"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="trash.svg"
inkscape:export-filename="/home/nikku/camunda/projects/bpmn.io/bpmn-font/raw/trash.png"
inkscape:export-xdpi="0.88"
inkscape:export-ydpi="0.88">
<defs
id="defs3893">
<inkscape:path-effect
effect="spiro"
id="path-effect4094"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect4094-0"
is_visible="true" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.175"
inkscape:cx="307.67263"
inkscape:cy="1030.7415"
inkscape:document-units="px"
inkscape:current-layer="layer1-6"
showgrid="false"
inkscape:window-width="1596"
inkscape:window-height="807"
inkscape:window-x="0"
inkscape:window-y="91"
inkscape:window-maximized="0"
inkscape:snap-page="false"
inkscape:snap-object-midpoints="false"
inkscape:snap-nodes="false"
inkscape:snap-to-guides="false"
inkscape:snap-grids="false" />
<metadata
id="metadata3896">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,995.63783)">
<g
transform="matrix(96.752895,0,0,96.752895,55.328158,-100816.34)"
id="layer1-6"
inkscape:label="Layer 1"
style="display:inline">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.343629;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 3.4296875,1038.3672 1.3325877,12.7308 10.5912408,0 1.228186,-12.7284 -13.1520736,0 z m 1.4921875,1.3437 10.185547,0 -0.972656,10.0411 -8.1582035,0 z"
id="rect4089"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccc" />
<g
id="g4275"
transform="matrix(1,0,0,0.90111263,0,103.41515)">
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
inkscape:original-d="m 7.0333918,1040.9794 0.9432241,7.504"
inkscape:path-effect="#path-effect4094"
id="path4092"
d="m 7.0333918,1040.9794 0.9432241,7.504"
style="fill:none;stroke:#000000;stroke-width:1.343629;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
inkscape:original-d="m 12.990235,1040.9794 -0.943224,7.504"
inkscape:path-effect="#path-effect4094-0"
id="path4092-2"
d="m 12.990235,1040.9794 -0.943224,7.504"
style="fill:none;stroke:#000000;stroke-width:1.343629;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<path
style="fill:#000000;fill-opacity:1;stroke:none"
d="m 7.2638322,1035.194 -4.2854023,1.2542 0,0.6276 14.0667651,0 0,-0.6276 -4.337726,-1.2542 z"
id="rect4121"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<path
style="display:inline;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.72291225;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 7.6269598,1033.8929 4.7697062,0 0,1.737 -4.7697062,0 z"
id="rect4121-6" />
</g>
</g>
</svg>`;
const svg = new SVG(reallyBadIcon);
// Clean up and validate icon
cleanupSVG(svg);
// Optimise icon
runSVGO(svg);
console.log(svg.toMinifiedString());
<svg xmlns="http://www.w3.org/2000/svg" width="2048" height="2048" viewBox="0 0 2048 2048"><path d="m387.19 644.317 128.875 1231.76h1024.807l118.813-1231.47H387.19zm144.356 130.035h985.428l-94.044 971.496H633.62z" color="#000"/><path fill="none" stroke="#000" stroke-linecap="round" stroke-width="1.344" d="m7.033 1040.98.944 7.503m5.013-7.503-.943 7.503" transform="matrix(96.7529 0 0 87.185 55.328 -89815)"/><path d="M758.141 337.32 343.458 458.648v60.76h1361.023v-60.76L1284.767 337.32z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="69.952" d="M793.262 211.444h461.512v168.06H793.262z"/></svg>