|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566 |
- title: HTML Includes That Work Today
- url: https://www.filamentgroup.com/lab/html-includes/
- hash_url: 88df28660094efbc5a13bb09d70dfea6
- archive_date: 2024-02-19
- og_image: https://www.filamentgroup.com/images/icons/twittercard.png
- description: Read this page on the Filament Group website
- favicon: https://www.filamentgroup.com/images/icons/favicon-32x32.png
- language: en_US
-
- <p class="note">Note: this post describes an experimental technique that we still need to test for performance implications. It may end up being a useful tool, and it may end up being a practice we won't recommend. Either way, it's fascinating to us that it works!</p>
- <p>As long as I have been working on the web, I’ve desired a simple HTML-driven means of including the contents of another file directly into the page. For example, I often want to append additional HTML to a page after it is delivered, or embed the contents of an SVG file so that we can animate and style its elements. Typically here at Filament, we have achieved this embedding by either <a href="https://www.filamentgroup.com/lab/ajax-includes-modular-content">using JavaScript to fetch a file and append its contents</a> to a particular element, or by <a href="https://www.filamentgroup.com/lab/inlining-cache">including the file on the server side</a>, but in many cases, neither of those approaches is quite what we want.</p>
- <p>This week I was thinking about ways I might be able to achieve this using some of the new <code>fetch</code>-related markup patterns, like <code>rel="preload"</code>, or HTML imports, but I kept coming back to the same conclusion that none of these give you easy access to the contents of the fetched file. Then I thought, perhaps a good old <code>iframe</code> could be a nice primitive for the pattern, assuming the browser would allow me to retrieve the <code>iframe</code>’s contents in the parent document. As it turns out, it sure would!</p>
- <span class="heading-wrapper">
- <h2 id="a-quick-demo%3A-including-svg" tabindex="-1">A Quick Demo: including SVG</h2>
- </span>
- <p>Below is an inline (embedded) SVG graphic. It was loaded from an external file called <a href="/images/includespost/signal.svg"><code>signal.svg</code></a>.</p>
-
- <p>To load and embed the SVG file, I used the following markup:</p>
- <pre><code><iframe src="signal.svg" onload="this.before((this.contentDocument.body || this.contentDocument).children[0]);this.remove()"></iframe>
- </code></pre>
- <p>Despite the fact that this markup starts out as an <code>iframe</code>, if you inspect the graphic above using developer tools, you’ll see the SVG markup for the icon, inlined right in the HTML DOM, with no <code>iframe</code> element to be found. This is because the code uses an <code>iframe</code> to load the file, and an <code>onload</code> event to inject the <code>iframe</code>’s content just before the <code>iframe</code> in the HTML, before deleting the <code>iframe</code> itself.</p>
- <p>The approach also works with an <code>object</code> element, which is commonly used to reference SVG anyway, so I think that’s particularly nice. With an <code>object</code>, the <code>src</code> attribute needs to be <code>data</code> instead:</p>
- <pre><code><object data="signal.svg" onload="this.before((this.contentDocument.body || this.contentDocument).children[0]);this.remove()"></object>
- </code></pre>
- <span class="heading-wrapper">
- <h3 id="another-demo%3A-including-another-html-file" tabindex="-1">Another demo: including another HTML file</h3>
- </span>
- <p>Maybe even more useful… here’s an example that uses HTML rather than SVG!</p>
-
- <p>It was loaded using the following markup:</p>
- <pre><code><iframe src="/images/includespost/htmlexample.html" onload="this.before((this.contentDocument.body||this.contentDocument).children[0]);this.remove()"></iframe>
- </code></pre>
- <p>One note about this one: you might have noticed that the markup snippet checks for either the <code>contentDocument.body</code> or just the <code>contentDocument</code>. This is a check to normalize between HTML and SVG includes. It’s necessary because even though the HTML file itself contains nothing more than a paragraph element, the browser creates a full HTML document to surround that paragraph, complete with an HTML element, a head, a body, etc. So the snippet tries to get the <code>iframe</code>’s <code>body</code> element if it exists, and if not, it goes for the entire document.</p>
- <p>Notably, if you’re importing an HTML file that contains more than one element in it, I’d recommend wrapping it all in a <code>div</code> to keep the <code>iframe</code> markup able to simply look for the first childnode in the <code>body</code>.</p>
- <span class="heading-wrapper">
- <h2 id="benefits!" tabindex="-1">Benefits!</h2>
- </span>
- <p>There are some nice benefits to this pattern over others we’ve used in the past:</p>
- <ul>
- <li>It’s declarative. Unlike most custom JavaScript approaches, this one is HTML-driven and its purpose in the markup is pretty clear at a glance.</li>
- <li>It works for HTML or SVG. I’m not sure what else you’d want to include like this client-side, but this covers my own wishlist at least.</li>
- <li>It’s asynchronous! The content will load in without blocking page rendering, as is the nature of an <code>iframe</code>.</li>
- <li>It’s cache-friendly. Unlike server-side embedding, this pattern lets us include an external file while allowing the file to be cached naturally for subsequent reuse. (With server-side included content, <a href="https://www.filamentgroup.com/lab/inlining-cache">client-side caching is possible, but harder to do</a>).</li>
- <li>It will show the content regardless of whether JavaScript runs at all, since that’s what an <code>iframe</code> is designed to do. The JavaScript is there to move the <code>iframe</code>’s content into the parent document, but if anythign fails, you’ll still see the included content.</li>
- <li>It leaves no trace: the <code>iframe</code> is deleted after it imports the content into the page. Note: you may want to give the <code>iframe</code> a <code>border:0;</code> or even safely hide it while loading (perhaps showing it again via an onerror event?).</li>
- <li>It works across browsers: in my brief testing so far, this works in Chrome, Firefox, Safari, and Edge. IE will show the fallback content in the <code>iframe</code>, but I think I can probably get IE support to work by adjusting the JS in the <code>onload</code> handler, as it’s currently using syntax that IE doesn’t like. With a little tweaking, I expect IE support will be possible.</li>
- <li>It could even be wrapped in a web component if you’d like, as <a href="https://codepen.io/andybelldesign/project/full/DyVyPG">Andy Bell has cleverly demonstrated here</a> (which is a little cleaner markup, but a little more fragile as far as JS dependence goes).</li>
- </ul>
- <p>It’s fun to think of other possible uses… Perhaps you could pull in HTML modules along with their relevant CSS link. Or embed a tweet or code examples in documentation or a blog post. It could probably even be used to load and apply a regular <code>rel=stylesheet</code> link asynchronously, and at a low priority, which is otherwise surprisingly hard to do (note: I didn’t test this idea much to say for sure).</p>
- <span class="heading-wrapper">
- <h2 id="can-it-be-lazy-loaded-too%3F-yes%2C-soon!" tabindex="-1">Can it be lazy loaded too? Yes, soon!</h2>
- </span>
- <p>Another nice thing about using an <code>iframe</code> for this pattern is that <code>iframe</code>s will soon gain the native ability to lazy-load when they enter the viewport. This will be achieved using the <code>loading="lazy"</code> attribute, which also will also work for <code>img</code> elements. Here’s how that markup will look:</p>
- <pre><code><iframe src="signal.svg" loading="lazy" onload="this.before((this.contentDocument.body||this.contentDocument).children[0]);this.remove()"></iframe>
- </code></pre>
- <span class="heading-wrapper">
- <h2 id="possible-drawbacks" tabindex="-1">Possible Drawbacks</h2>
- </span>
- <p><code>Iframe</code>s are very commonly used on the web, but it’s possible that overusing them in a page could lead to a performance or memory consumption problem. It’s possible that using this for all the icons on a page would be too heavy, for example, but it could be nice to use for just the particular icons that need to be animated and styled. I’ll have to do more testing before I can say.</p>
- <p>There’s also the possibility of XSS problems, though I’m not sure this is any different than other situations where you’d want to be careful with external content. The usual safechecks apply, and it’s probably best to consider it a same-domain technique, though again I’m not sure on that either.</p>
- <p>For now, this approach feels promising as an improvement over prior methods we’ve used for including another file directly into a page.</p>
- <span class="heading-wrapper">
- <h2 id="feedback%3F" tabindex="-1">Feedback?</h2>
- </span>
- <p>We’ll be continuing to test this pattern and may post a followup soon if we find anything interesting. If you have any feedback or ideas, feel free to <a href="https://twitter.com/filamentgroup">hit us up on twitter</a>. Thanks for reading!</p>
- <p><small><b>Note:</b> This post is a followup and improvement on <a href="https://twitter.com/scottjehl/status/1116761767617519617">an earlier experiment I posted on Codepen</a>.</small></p>
|