-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Inline things in defs referenced only once #563
Comments
Yep, but I don't understand your point. What's next? |
Sorry, I don't understand what you don't understand. |
Consider this example @GreLI. This defs/mask/use structure is typical of some graphics programs output like Illustrator or Sketch. With IDs: <svg width="315" height="92" viewBox="0 0 315 92" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<rect id="a" x="7" y="5" width="301" height="31" rx="3"/>
<mask id="m" x="0" y="0" width="301" height="31" fill="#fff">
<use xlink:href="#a"/>
</mask>
</defs>
<g fill="none" fill-rule="evenodd" opacity=".1">
<g stroke="#FFF">
<g>
<g opacity=".8">
<g>
<use mask="url(#m)" stroke-width="4" opacity=".4" xlink:href="#a"/>
</g>
</g>
</g>
</g>
</g>
</svg> Optimized by inlining referenced element: <svg width="315" height="92" viewBox="0 0 315 92" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g fill="none" fill-rule="evenodd" opacity=".1">
<g stroke="#FFF" opacity=".8">
<rect x="7" y="5" width="301" height="31" rx="3" stroke-width="4" opacity=".4"/>
</g>
</g>
</svg> |
@themadcreator I like this idea a lot, but are you sure your example is correct? I believe Sketch uses the shape-with-mask pattern to recreate the stroke-position: inside setting which is not available in SVG. The mask is supposed to hide the part of the stroke that falls outside the shape. By removing the mask you are breaking this and effectively doubling the width stroke. Here's a screenshot of the before and after: |
Though that example was not great, this feature would still be neat for cases where the referenced element is truly only used once within the SVG. Here's a snippet from one of mine:
Can easily become
I'm just starting to use SVGO and SVGs in my workflow, but I'm seeing a number of examples like this exported from Sketch with |
I just found out firefox has some issues with rendering SVGs that use See this codepen for example: http://codepen.io/anon/pen/oWgMov (renders on chrome, but not ff) |
@ProdigySim: Is this related?: #733 |
@GreLI I'd really love a feature like this, currently, I'm working with a designer who uses masks to apply colors to icons in sketch and during the build, I'd like to remove them because they're essentially useless to me and causing display issues in production. |
specifically in this case: <svg
width="60"
height="60"
viewBox="0 0 60 60"
xmlns:link="http://www.w3.org/1999/xlink"
>
<defs>
<path
d="M20.588 26L19.1 22.424h-7.2L10.412 26h-2.52l7.416-17.04h.336L23.084 26h-2.496zm-7.776-5.784l2.688-6.48 2.688 6.48h-5.376zM47.744 34.2v.288L40.64 48.792h6.864V51H37.256v-.336l7.152-14.256h-7.056V34.2h10.392zM27 18.253c0-.14.114-.253.256-.253h2.488a.25.25 0 0 1 .256.253v23.494c0 .14-.114.253-.256.253h-2.488a.25.25 0 0 1-.256-.253V18.253z"
id="a"
/>
</defs>
<use xlink:href="#a" fillRule="evenodd" />
</svg> the xlink:href is causing issues due to all references being named |
@lifeiscontent: What about using the svgo prefixIds plugin? |
@strarsis that works, but it'd be ideal to simplify the SVG. |
@lifeiscontent: You can use a custom hash/prefix callback, |
@strarsis how do you use a callback in SVGO? |
This would be very useful for exported svg from sketch, most |
I dare adding another example : <svg width="32" height="32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path d="M8.18 8H6v13h4.78L8.18 8zm2.04 0l2.6 13H26V8H10.22zM2 24h28l-2 3H4l-2-3zM5 6h22a1 1 0 0 1 1 1v15a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z" id="a"/>
</defs>
<use fill="#0082C3" xlink:href="#a" fill-rule="evenodd"/>
</svg> With svgood (https://github.com/BrentonWheeler/svgood) + svgo <svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path d="M8.18 8H6v13h4.78L8.18 8zm2.04 0l2.6 13H26V8H10.22zM2 24h28l-2 3H4l-2-3zM5 6h22a1 1 0 0 1 1 1v15a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/>
</svg> |
@lifeiscontent the same thing here. Our designer also uses masks to apply colors, and doing so produces a lot of useless and sometimes broken code. I currently clean this up manually, but it takes a lot of time. I think this is not an uncommon problem. |
Hello, everyone! I am writing an SVG image spread across various files, and combining it on Node during build by using a self‐made script (to make ids unique based on file path) that uses jsdom, and then passing the result to SVGO. As such, I have a lot of elements in So, I figured I’d implement it myself! (And that’s what I did.) If anyone thinks it would be useful, feel free to copy and paste: {
type: "full",
fn: item =>
{
let referenced = {}
let referencedOnce = {}
let references = {}
let parents = new Map()
let action = item =>
{
if(item.hasAttr("id"))
referenced[item.attr("id").value] = item
if(item.hasAttr("href") && item.attr("href").value.startsWith("#"))
{
let id = item.attr("href").value.slice(1)
referencedOnce[id] = referencedOnce[id] === undefined
if(referencedOnce[id]) references[id] = item
}
for(let name in item.attrs)
{
let value = item.attrs[name].value
let match = value.match(/\burl\(("|')?#(.+?)\1\)/)
if(match)
referencedOnce[match[2]] = false
}
}
let walk = item =>
{
if(!item.isElem()) return
action(item)
if(!item.content) return
for(let child of item.content)
{
parents.set(child, item)
walk(child)
}
}
walk(item)
for(let id in referenced)
{
let item = referenced[id]
switch(referencedOnce[id])
{
case undefined:
item.removeAttr("id")
break
case true:
let parent = parents.get(item)
if(!parent.isElem("defs")) break
let replaced = references[id]
let replacing = parents.get(replaced)
parents.set(item, replaced)
parent.content = parent.content.filter(child => child !== item)
for(let child of replacing.content)
{
if(child === replaced)
{
item.removeAttr("id")
child.removeAttr("href")
child.renameElem("g")
child.content = [item]
}
}
break
}
}
return item
},
} With this, I was successfully able to reduce my (current) SVG by 1.4kB (from 18.1kB to 16.7kB). Unfortunately, it only works with Note: This replaces the let data = "<svg />" // Your SVG.
for(let i = 0; i < 12; i++)
{
let newdata = (await svgo.optimize(data)).data
if(data === newdata) break
data = newdata
} I set an upper bound of twelve iterations, but in reality, it stops after about five on the I hope someone is able to appreciate this! Cheers〜 Edit: Note that this doesn’t work well when the |
PR ready: #1279 You can already try this feature branch by installing it using this npm command: |
Hi there! Thanks in advance! Edit: Just realized there is an unresolved comment in the PR. |
Hi, Just wanted to ping this PR again. Thanks in advance! |
Since ids are mangled by default, they hardly can be referenced reliably outside.
So it's worth inlining if we detect they're used only once (after removing invisible and other parts).
Such references might happen pretty often even in hand-written code.
The text was updated successfully, but these errors were encountered: