Throughout this site, I use SVG icons by inlining them directly into the page, ensuring immediate availability without unnecessary network calls. Basic inlining would embed data for each SVG separately, even if the same icon is used multiple times, quickly bloating the page with duplicate data. But there’s a solution!
SVGs support the <use> element, which references and duplicates content from another SVG document. This process effectively clones nodes into a hidden DOM and pastes them where the <use> element is, similar to how template elements are cloned.
CSS-Tricks offers two insightful articles on using SVGs with an external Source or Reference. These are worth a read. Here, I’ll be focusing on updating the Font Awesome shortcodes from my earlier article, Hugo with Font Awesome, to work with the <use> element.
What’s Covered Here
Updating the Font Awesome Partial: Revising the file used by the shortcodes from my earlier article to place an SVG stub on the page, containing the <use> element.
Tracking Usage: Keeping track of every SVG stub to ensure we know which styles and icons are used.
Adding Source SVGs: At the end of the page, adding a single source SVG for each style/icon pair used earlier.
{{-$settings:=partial"func/getFontAwesomeSettings.html"(dict"style".style"arg1".arg1"arg2".arg2)-}}<spanstyle="line-height:1em; vertical-align:middle;">{{-$fname:=print"/assets/svg/".style"/".icon".svg"-}}{{-if(fileExists$fname)-}}{{-with.page-}}{{-/* The SVG will utilize the USE tag to reference SVG shapes defined later.
This allows multiple SVG instances to share the same shape data. */-}}{{-$.page.Scratch.Add"used-svg"(slice(print$.style"|"$.icon))-}}{{-/* The SVG's original width and height will be retained here, and only the inner
content will be replaced with the USE tag. Any customizations, such as size and
color should be definined here. Only shape data will be shared. */-}}{{-$svg:=readFile$fname-}}{{-$svg=replace$svg" 512\"><!--"(print" 512\" style=\"height:"$settings.size"; width:"$settings.size"\"><!--")-}}{{-$svgStyle:=print"--fa-pc:"$settings.primaryColor";--fa-po:"$settings.primaryOpacity";"}}{{-ifor(eq$.style"duotone")(eq$.style"custom")-}}{{-$svgStyle=print$svgStyle"--fa-sc:"$settings.secondaryColor";--fa-so:"$settings.secondaryOpacity";"}}{{-end-}}{{-$svg=replaceRE"(<svg.*>)(<!--.*)(<\\/svg>)"(print"$1<use xlink:href=\"#"$.style"-"$.icon"\" style=\""$svgStyle"\"/>$3")$svg-}}{{-$svg|safeHTML-}}{{-else-}}{{-$svg:=readFile$fname-}}{{-$svg=replace$svg" 512\"><!--"(print" 512\" style=\"height:"$settings.size"; width:"$settings.size"\"><!--")-}}{{-$svg=replaceRE"<!--[^>]*-->"""$svg-}}{{-ifeq.style"duotone"-}}{{-$svg=replace$svg"class=\"fa-primary\""(print"class=\"fa-primary\" style=\"fill:"$settings.primaryColor"; opacity:"$settings.primaryOpacity"\"")-}}{{-$svg=replace$svg"class=\"fa-secondary\""(print"class=\"fa-secondary\" style=\"fill:"$settings.secondaryColor"; opacity:"$settings.secondaryOpacity"\"")-}}{{-$svg=replace$svg"<defs><style>.fa-secondary{opacity:.4}</style></defs>"""-}}{{-else-}}{{-$svg=replace$svg"<path"(print"<path fill=\""$settings.primaryColor"\" opacity=\""$settings.primaryOpacity"\"")-}}{{-end-}}{{-$svg|safeHTML-}}{{-end}}{{-else-}}<spanclass="sc-fontawesome-missing"title="Could not find "{{.icon}}" icon with "{{.style}}" style">�</span>{{-warnf"Could not find \"%s\" icon with \"%s\" style.".icon.style-}}{{-end-}}</span>{{-""-}}
Lines 5-32 changed, however lines 21-31 were just indented, so we can skip those.
First, we check if the partial is being called from a page. If so, we insert the new <use> version; otherwise, we insert the full version with shape data.
When inserting the <use> version, we also need to update a scratch with a reference to the style and icon being used, so that a source copy can be added to the page later in the build process.
Finally, the SVG file is read and modified with the desired color and opacity settings. Most importantly, all shape data is replaced with the <use> element, resulting in significantly less data being written to the page for this instance compared to including the entire SVG.
On a generated page, the above template will result in an <svg> element appearing in the HTML source code. This element will contain only a <use> child element that references the source SVG to obtain its shape data.
Create a new partial file that will insert source copies of all SVG icons previously added to the page with the <use> element. These source copies contain the actual shape data.
{{-$all:=(.Scratch.Get"used-svg")|default(slice)}}{{-with$all}}{{-$unique:=.|uniq}}<divid="svg-use-src"style="display:none">{{-range$unique}}{{-$tokens:=split."|"}}{{-ifeq(len$tokens)2}}{{-$style:=index$tokens0}}{{-$icon:=index$tokens1}}{{-$fname:=print"/assets/svg/"$style"/"$icon".svg"-}}{{-if(fileExists$fname)-}}{{-$svg:=readFile$fname-}}{{-$svg=replaceRE"(<svg.*>)(<!--.*)(<\\/svg>)"(print"$1<g id=\""$style"-"$icon"\">$2</g>$3")$svg-}}{{-$svg=replaceRE"<!--[^>]*-->"""$svg-}}{{-ifor(eq$style"duotone")(eq$style"custom")-}}{{-$svg=replace$svg"<defs><style>.fa-secondary{opacity:.4}</style></defs>"""-}}{{-$svg=replace$svg"class=\"fa-primary\""(print"class=\"fa-primary\" style=\"fill:var(--fa-pc); opacity:var(--fa-po)\"")-}}{{-$svg=replace$svg"class=\"fa-secondary\""(print"class=\"fa-secondary\" style=\"fill:var(--fa-sc); opacity:var(--fa-so)\"")-}}{{-else-}}{{-$svg=replace$svg"<path"(print"<path fill=\"var(--fa-pc)\" opacity=\"var(--fa-po)\"")-}}{{-end}}{{$svg|safeHTML}}{{-else-}}{{-errorf"Could not find \"%s\" icon with \"%s\" style.".icon.style-}}<spanclass="sc-fontawesome-missing"title="Could not find "{{.icon}}" icon with "{{.style}}" style">�</span>{{-end-}}{{-else}}{{-warnf"Not a Font Awesome SVG: \"%s\".".-}}{{.}}{{-end}}{{-end}}</div>{{-end-}}
What’s going on here?
All items are retrieved from the “used-svg” scratch, which was populated by the fontawesome partial.
The collection is filtered so that it only contains unique items. We just want to insert one SVG image per style/icon pair.
A hidden <div> is used to contain the SVGs, however it won’t be rendered to the page itself. It’s only for reference.
Finally, a complete SVG icon is inserted for each collection item.
Add Source SVGs to the Site
The final step is to load the svg-source partial into the site. I do this by adding it to the end of my baseof template, just before the closing </body> tag.
Your baseof file may look different. Here’s an example:
<root>/layouts/_default/baseof.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPEhtml><htmllang="{{ or .Site.LanguageCode .Site.Language.Lang }}"><head><metacharset="utf-8"><metaname="viewport"content="width=device-width, minimum-scale=1.0, initial-scale=1.0"><title>{{with.Title}}{{printf"%s | ".}}{{end}}{{site.Title}}</title></head><body>{{block"main".}}{{end}}{{-partial"body/svg-source"$.Page}}</body></html>
Once you compile the site, a page featuring multiple Font Awesome SVG icons will have something like this at the bottom:
The above <svg> elements were truncated for brevity. However, you can still see the id attribute of the g child element, which indicates the style and icon the SVG refers to. For example, <g id="duotone-folder">.
By leveraging the <use> element, you can efficiently add SVG icons to your site while minimizing duplicate data. This approach ensures a clean, optimized, and visually appealing web experience.