I am creating an SVG background and I want to make the file size smaller by reusing paths.
Because the SVG is applied to an HTML element, called using CSS, like so background-image: url("filename.svg") it must be entirely self-contained. In other words, not styled using external CSS, like we could do if it were an inline SVG.
I have created a proof-of-concept demo that works well enough and is practical when the SVG is simple.
You can run the Code Snippet and see that it's functional. No problem with the result. It's background-image compliant.
<!DOCTYPE html>
<html>
<head>
<title>Lightweight SVG Background</title>
<style>
/* just example formatting */
body {
display: flex;
justify-content: center;
padding: 64px;
}
div {
width: 480px;
height: 480px;
border: 1px solid #ccc;
padding: 16px;
box-shadow: 0px 6px 12px 0px rgba(0,0,0,0.3);
background-color: #fffff6;
border-radius: 4px;
}
p {
font-family: sans-serif;
color: #679;
text-align: center;
font-size: 1.2em;
font-weight: 100;
}
/* the relevant stuff */
/* I'm only using base64 for the image URL because I don't see any options to add an SVG file to a Stack Exchange code snippet - sorry that it's awkward */
.svg-bg {
background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxMDAwIDEwMDAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEwMDAgMTAwMDsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIG1lZXQiPgo8ZGVmcz4KCTxsaW5lYXJHcmFkaWVudCBpZD0ibWFpbl9wYXRoX2dyYWQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iNzA4LjE4MDgiIHkxPSIzNDYuOTgxIiB4Mj0iNzU4Ljk5NTUiIHkyPSI0NjIuOTE5MyI+CgkJPHN0b3AgIG9mZnNldD0iMCIgc3R5bGU9InN0b3AtY29sb3I6IzcxQjlGRiIvPgoJCTxzdG9wICBvZmZzZXQ9IjEiIHN0eWxlPSJzdG9wLWNvbG9yOiM3NENDQUYiLz4KCTwvbGluZWFyR3JhZGllbnQ+Cgk8cGF0aCBpZD0ibWFpbl9wYXRoIiBzdHlsZT0iZmlsbDp1cmwoI21haW5fcGF0aF9ncmFkKTsiIGQ9Ik05NzUsNTAwLjUKCQljMC0wLjIsMC0wLjMsMC0wLjVjMC00OC40LTQzLjctNjYuMi01Ny4yLTExMC4zczMyLjktMTM1LjQsOC0xNzIuOWMtMTcuNi0yNi42LTU1LjktMTYtODkuOS01Mi43TDUwMCw1MDBMOTc1LDUwMC41eiIvPgo8L2RlZnM+CjxnIGlkPSJiYWNrZ3JvdW5kX2xheWVyIj4KCTxyZWN0IGlkPSJiZ19yZWN0IiBzdHlsZT0iZmlsbDojRTZFNkU2OyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgcng9IjI0Ii8+CjwvZz4KPGcgaWQ9Im1haW5fbGF5ZXIiPgoJPHVzZSBocmVmPSIjbWFpbl9wYXRoIiB4PSIwIiB5PSIwIi8+Cgk8dXNlIGhyZWY9IiNtYWluX3BhdGgiIHg9IjAiIHk9IjAiIHRyYW5zZm9ybS1vcmlnaW49IjUwJSA1MCUiIHRyYW5zZm9ybT0ic2NhbGUoLTEgMSkgcm90YXRlKC05MCkiLz4KCgk8dXNlIGhyZWY9IiNtYWluX3BhdGgiIHg9IjAiIHk9IjAiIHRyYW5zZm9ybS1vcmlnaW49IjUwJSA1MCUiIHRyYW5zZm9ybT0icm90YXRlKC05MCkiLz4KCTx1c2UgaHJlZj0iI21haW5fcGF0aCIgeD0iMCIgeT0iMCIgdHJhbnNmb3JtLW9yaWdpbj0iNTAlIDUwJSIgdHJhbnNmb3JtPSJzY2FsZSgtMSAxKSIvPgoKCTx1c2UgaHJlZj0iI21haW5fcGF0aCIgeD0iMCIgeT0iMCIgdHJhbnNmb3JtLW9yaWdpbj0iNTAlIDUwJSIgdHJhbnNmb3JtPSJyb3RhdGUoMTgwKSIvPgoJPHVzZSBocmVmPSIjbWFpbl9wYXRoIiB4PSIwIiB5PSIwIiB0cmFuc2Zvcm0tb3JpZ2luPSI1MCUgNTAlIiB0cmFuc2Zvcm09InNjYWxlKC0xIDEpIHJvdGF0ZSg5MCkiLz4KCgk8dXNlIGhyZWY9IiNtYWluX3BhdGgiIHg9IjAiIHk9IjAiIHRyYW5zZm9ybS1vcmlnaW49IjUwJSA1MCUiIHRyYW5zZm9ybT0icm90YXRlKDkwKSIvPgoJPHVzZSBocmVmPSIjbWFpbl9wYXRoIiB4PSIwIiB5PSIwIiB0cmFuc2Zvcm0tb3JpZ2luPSI1MCUgNTAlIiB0cmFuc2Zvcm09InNjYWxlKC0xIDEpIHJvdGF0ZSgxODApIi8+CjwvZz4KPC9zdmc+");
background-position: center;
background-size: 90%;
background-repeat: no-repeat;
}
</style>
</head>
<body>
<div class="svg-bg">
<p>lightweight SVG background</p>
</div>
</body>
</html>
Here is the actual SVG for it, if you view it by itself as a standalone image.
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve" preserveAspectRatio="xMidYMid meet">
<defs>
<linearGradient id="main_path_grad" gradientUnits="userSpaceOnUse" x1="708.1808" y1="346.981" x2="758.9955" y2="462.9193">
<stop offset="0" style="stop-color:#71B9FF"/>
<stop offset="1" style="stop-color:#74CCAF"/>
</linearGradient>
<path id="main_path" style="fill:url(#main_path_grad);" d="M975,500.5
c0-0.2,0-0.3,0-0.5c0-48.4-43.7-66.2-57.2-110.3s32.9-135.4,8-172.9c-17.6-26.6-55.9-16-89.9-52.7L500,500L975,500.5z"/>
</defs>
<g id="background_layer"> <!-- elements within are absolute (defined here and used only once) -->
<rect id="bg_rect" style="fill:#E6E6E6;" width="100%" height="100%" rx="24"/>
</g>
<g id="main_layer"> <!-- elements within are relative (defined in <defs> and reused) -->
<!-- upper right -->
<use href="#main_path" x="0" y="0"/> <!-- original position (not transformed) -->
<use href="#main_path" x="0" y="0" transform-origin="50% 50%" transform="scale(-1 1) rotate(-90)"/>
<!-- upper left -->
<use href="#main_path" x="0" y="0" transform-origin="50% 50%" transform="rotate(-90)"/>
<use href="#main_path" x="0" y="0" transform-origin="50% 50%" transform="scale(-1 1)"/>
<!-- bottom left -->
<use href="#main_path" x="0" y="0" transform-origin="50% 50%" transform="rotate(180)"/>
<use href="#main_path" x="0" y="0" transform-origin="50% 50%" transform="scale(-1 1) rotate(90)"/>
<!-- bottom right -->
<use href="#main_path" x="0" y="0" transform-origin="50% 50%" transform="rotate(90)"/>
<use href="#main_path" x="0" y="0" transform-origin="50% 50%" transform="scale(-1 1) rotate(180)"/>
</g>
</svg>
You're probably thinking...
“it works, so what's the problem?”
Essentially, I want to group the SVG elements in a more intuitive way. This is about workflow, and semantic structure, and code readability.
In order to be able to call the #main_path in this example SVG I used <use>. I'm a use-user.
And I called it using href, which is apparently the right way to do it now.
The core of the issue, it seems, is that <use> can't be nested, and using <svg> within another <svg> is (unless I'm mistaken) not possible when calling the SVG file by background-image in an HTML file. Then there is the <g> (group) element, but since href can't be used on it, that element also won't work for nesting/grouping.
See in the code where I commented upper right, upper left, etc? That's where I wanted to group the elements. Instead of 8 separate <use> elements, it ideally should have been 4 elements (of some kind... I'm not picky about the tag type and am willing to structure in in any way that works). But fiddling with 8 instead of 4 meant additional trial-and-error with the scale/rotate transform combinations, and all of this is doable on a small scale with a simple SVG. But the process doesn't scale up so well when authoring more complex artwork.
If I had just used the unaltered .svg file that Illustrator spit out, #main_path alone would have taken up about eight times more space in the file because it wouldn't have been reused.
In this simplified demo, #main_path is only 188 characters long. But in my actual project's SVG file, that part of the artwork is 64581 characters long. So then multiply that by eight. In this context, hopefully you can see why I care about optimization. If you have to choose a raster image format instead, because the vector artwork's file size is coming out to be larger, that bloat (resulting from redundant instances of what would otherwise be reusable artwork assets) defeats the purpose of the vector-ness.
Utilizing <use> is really helpful for minimizing file size.
It's just the inability to nest such elements in a (non-inline) SVG file that is complicating the authoring process. If somebody knows of a way, please spread the enlightenment.
Update
@Robert Longson re:
“You can point a use at another use, if that's what you need.”
That is something I had tried before posting my question originally, but it didn't work for me. Below I will show what I did. Please let me know if you understand why it fails.
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve" preserveAspectRatio="xMidYMid meet">
<defs>
<linearGradient id="main_path_grad" gradientUnits="userSpaceOnUse" x1="708.1808" y1="346.981" x2="758.9955" y2="462.9193">
<stop offset="0" style="stop-color:#71B9FF"/>
<stop offset="1" style="stop-color:#74CCAF"/>
</linearGradient>
<path id="main_path" style="fill:url(#main_path_grad);" d="M975,500.5
c0-0.2,0-0.3,0-0.5c0-48.4-43.7-66.2-57.2-110.3s32.9-135.4,8-172.9c-17.6-26.6-55.9-16-89.9-52.7L500,500L975,500.5z"/>
<!-- upper right -->
<use id="quadrant">
<use href="#main_path" x="0" y="0"/> <!-- original position (not transformed) -->
<use href="#main_path" x="0" y="0" transform-origin="50% 50%" transform="scale(-1 1) rotate(-90)"/>
</use>
</defs>
<g id="background_layer"> <!-- elements within are absolute (defined here and used only once) -->
<rect id="bg_rect" style="fill:#E6E6E6;" width="100%" height="100%" rx="24"/>
</g>
<g id="main_layer"> <!-- elements within are relative (defined in <defs> and reused) -->
<use href="#quadrant" x="0" y="0"/>
<use href="#quadrant" x="0" y="0" transform-origin="50% 50%" transform="rotate(90)"/>
<use href="#quadrant" x="0" y="0" transform-origin="50% 50%" transform="rotate(180)"/>
<use href="#quadrant" x="0" y="0" transform-origin="50% 50%" transform="rotate(270)"/>
</g>
</svg>
In the above invalid example, I attempted to define the original upper right ¼ of the design as #quadrant within <defs> and call it 4 times with <use>, then rotated 3 of those 4 (those which are not to remain in the default position).
Note that #quadrant itself is comprised of 2 <use> elements that use #main_path - the second being a mirrored and rotated variant of the original.
Furthermore, I tried the <use> tag in both an encapsulating format and in a self-closing format. Neither worked:
- Encapsulating (non-void):
<use>content</use> - Self-closing (void):
<use content />
I was under the impression that any given XML/HTML/SVG tag type had to be either an encapsulating (non-void element), or a self-closing (void element), but could not be both. Since I had only seen <use> as self-closing/void... maybe you meant something else by “point at” another <use>?
As commented –
<use>elements though reducing the markup/file size also introduce rendering slow-downs.So you shouldn't focus too much on the actual markup size.
You can nest use definitions by referencing e.g a
<g>group containing<use>elementsThe above example
<g><use>instances referencing this group IDRule-of-thumb: You can't nest any geometry elements (
path,rect,circle,ellipse,polygon,polyline,line) in another geometry elements. The same applies to<use>elements since they have self contained visual properties like a bounding box.However you can nest "meta" or style related SVG elements like
<title>or<animate>in these elements.Btw. There is an exploit known as SVG Billion Laugh Attack based on nested
<use>references. So you should try to avoid this concept as it slows down your rendering significantly.