A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="en">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>HTML Includes That Work Today (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Is that even respected? Retrospectively? What a shAItshow…
  28. https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
  29. <meta name="robots" content="noai, noimageai">
  30. <!-- Documented, feel free to shoot an email. -->
  31. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  32. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  36. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  37. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  38. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  39. <script>
  40. function toggleTheme(themeName) {
  41. document.documentElement.classList.toggle(
  42. 'forced-dark',
  43. themeName === 'dark'
  44. )
  45. document.documentElement.classList.toggle(
  46. 'forced-light',
  47. themeName === 'light'
  48. )
  49. }
  50. const selectedTheme = localStorage.getItem('theme')
  51. if (selectedTheme !== 'undefined') {
  52. toggleTheme(selectedTheme)
  53. }
  54. </script>
  55. <meta name="robots" content="noindex, nofollow">
  56. <meta content="origin-when-cross-origin" name="referrer">
  57. <!-- Canonical URL for SEO purposes -->
  58. <link rel="canonical" href="https://www.filamentgroup.com/lab/html-includes/">
  59. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  60. <article>
  61. <header>
  62. <h1>HTML Includes That Work Today</h1>
  63. </header>
  64. <nav>
  65. <p class="center">
  66. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  67. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  68. </svg> Accueil</a> •
  69. <a href="https://www.filamentgroup.com/lab/html-includes/" title="Lien vers le contenu original">Source originale</a>
  70. <br>
  71. Mis en cache le 2024-02-19
  72. </p>
  73. </nav>
  74. <hr>
  75. <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>
  76. <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>
  77. <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>
  78. <p><span class="heading-wrapper"></p>
  79. <h2 id="a-quick-demo%3A-including-svg" tabindex="-1">A Quick Demo: including SVG</h2>
  80. <p></span></p>
  81. <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>
  82. <p>To load and embed the SVG file, I used the following markup:</p>
  83. <pre><code>&lt;iframe src="signal.svg" onload="this.before((this.contentDocument.body || this.contentDocument).children[0]);this.remove()"&gt;&lt;/iframe&gt;
  84. </code></pre>
  85. <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>
  86. <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>
  87. <pre><code>&lt;object data="signal.svg" onload="this.before((this.contentDocument.body || this.contentDocument).children[0]);this.remove()"&gt;&lt;/object&gt;
  88. </code></pre>
  89. <p><span class="heading-wrapper"></p>
  90. <h3 id="another-demo%3A-including-another-html-file" tabindex="-1">Another demo: including another HTML file</h3>
  91. <p></span></p>
  92. <p>Maybe even more useful… here’s an example that uses HTML rather than SVG!</p>
  93. <p>It was loaded using the following markup:</p>
  94. <pre><code>&lt;iframe src="/images/includespost/htmlexample.html" onload="this.before((this.contentDocument.body||this.contentDocument).children[0]);this.remove()"&gt;&lt;/iframe&gt;
  95. </code></pre>
  96. <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>
  97. <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>
  98. <p><span class="heading-wrapper"></p>
  99. <h2 id="benefits!" tabindex="-1">Benefits!</h2>
  100. <p></span></p>
  101. <p>There are some nice benefits to this pattern over others we’ve used in the past:</p>
  102. <ul>
  103. <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>
  104. <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>
  105. <li>It’s asynchronous! The content will load in without blocking page rendering, as is the nature of an <code>iframe</code>.</li>
  106. <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>
  107. <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>
  108. <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>
  109. <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>
  110. <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>
  111. </ul>
  112. <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>
  113. <p><span class="heading-wrapper"></p>
  114. <h2 id="can-it-be-lazy-loaded-too%3F-yes%2C-soon!" tabindex="-1">Can it be lazy loaded too? Yes, soon!</h2>
  115. <p></span></p>
  116. <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>
  117. <pre><code>&lt;iframe src="signal.svg" loading="lazy" onload="this.before((this.contentDocument.body||this.contentDocument).children[0]);this.remove()"&gt;&lt;/iframe&gt;
  118. </code></pre>
  119. <p><span class="heading-wrapper"></p>
  120. <h2 id="possible-drawbacks" tabindex="-1">Possible Drawbacks</h2>
  121. <p></span></p>
  122. <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>
  123. <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>
  124. <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>
  125. <p><span class="heading-wrapper"></p>
  126. <h2 id="feedback%3F" tabindex="-1">Feedback?</h2>
  127. <p></span></p>
  128. <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>
  129. <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>
  130. </article>
  131. <hr>
  132. <footer>
  133. <p>
  134. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  135. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  136. </svg> Accueil</a> •
  137. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  138. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  139. </svg> Suivre</a> •
  140. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  141. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  142. </svg> Pro</a> •
  143. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  144. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  145. </svg> Email</a> •
  146. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  147. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  148. </svg> Légal</abbr>
  149. </p>
  150. <template id="theme-selector">
  151. <form>
  152. <fieldset>
  153. <legend><svg class="icon icon-brightness-contrast">
  154. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  155. </svg> Thème</legend>
  156. <label>
  157. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  158. </label>
  159. <label>
  160. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  161. </label>
  162. <label>
  163. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  164. </label>
  165. </fieldset>
  166. </form>
  167. </template>
  168. </footer>
  169. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  170. <script>
  171. function loadThemeForm(templateName) {
  172. const themeSelectorTemplate = document.querySelector(templateName)
  173. const form = themeSelectorTemplate.content.firstElementChild
  174. themeSelectorTemplate.replaceWith(form)
  175. form.addEventListener('change', (e) => {
  176. const chosenColorScheme = e.target.value
  177. localStorage.setItem('theme', chosenColorScheme)
  178. toggleTheme(chosenColorScheme)
  179. })
  180. const selectedTheme = localStorage.getItem('theme')
  181. if (selectedTheme && selectedTheme !== 'undefined') {
  182. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  183. }
  184. }
  185. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  186. window.addEventListener('load', () => {
  187. let hasDarkRules = false
  188. for (const styleSheet of Array.from(document.styleSheets)) {
  189. let mediaRules = []
  190. for (const cssRule of styleSheet.cssRules) {
  191. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  192. continue
  193. }
  194. // WARNING: Safari does not have/supports `conditionText`.
  195. if (cssRule.conditionText) {
  196. if (cssRule.conditionText !== prefersColorSchemeDark) {
  197. continue
  198. }
  199. } else {
  200. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  201. continue
  202. }
  203. }
  204. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  205. }
  206. // WARNING: do not try to insert a Rule to a styleSheet you are
  207. // currently iterating on, otherwise the browser will be stuck
  208. // in a infinite loop…
  209. for (const mediaRule of mediaRules) {
  210. styleSheet.insertRule(mediaRule.cssText)
  211. hasDarkRules = true
  212. }
  213. }
  214. if (hasDarkRules) {
  215. loadThemeForm('#theme-selector')
  216. }
  217. })
  218. </script>
  219. </body>
  220. </html>