{"version":"https://jsonfeed.org/version/1","title":"Aaron Gustafson: Content tagged performance","description":"The latest 20 posts and links tagged performance.","home_page_url":"https://www.aaron-gustafson.com","feed_url":"https://www.aaron-gustafson.com/feeds/performance.json","author":{"name":"Aaron Gustafson","url":"https://www.aaron-gustafson.com"},"icon":"https://www.aaron-gustafson.com/i/og-logo.png","favicon":"https://www.aaron-gustafson.com/favicon.png","expired":false,"items":[{"id":"https://www.aaron-gustafson.com/notebook/lazy-loading-images-based-on-screen-size/","title":"✍🏻 Lazy Loading Images Based on Screen Size","summary":"The lazy-img web component goes beyond native lazy loading and srcset—it can completely skip loading images on small screens, saving bandwidth where it matters most.","content_html":"<p>Native lazy loading and <code>srcset</code> are great, but they have a limitation: they always load <em>some</em> variant of the image. The <code>lazy-img</code> web component takes a different approach—it can completely skip loading images when they don’t meet your criteria, whether that’s screen size, container size, or visibility in the viewport.</p>\n<p>This is particularly valuable for mobile users on slow connections or limited data plans. If an image is only meaningful on larger screens, why waste their bandwidth loading it at all?</p>\n<h2 id=\"the-performance-benefit\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#the-performance-benefit\" aria-hidden=\"true\">#</a> The performance benefit</h2>\n<p>Unlike <code>picture</code> or <code>srcset</code>, which always load some image variant, <code>lazy-img</code> can <strong>completely skip loading images</strong> on screens or containers below your specified threshold. Set <code>min-inline-size=&quot;768px&quot;</code> and mobile users will never download that image at all—saving data and speeding up page loads.</p>\n<p>Once an image is loaded, however, it remains loaded even if the viewport or container is resized below the threshold. This is intentional—the component prevents unnecessary downloads but doesn’t unload images already in memory. You can control visibility with CSS if needed using the <code>loaded</code> and <code>qualifies</code> attributes (which we’ll get to shortly).</p>\n<h2 id=\"basic-usage\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#basic-usage\" aria-hidden=\"true\">#</a> Basic usage</h2>\n<p>The <code>lazy-img</code> works pretty much identically to a regular <code>img</code> element, with all the attributes you know and love:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image.jpg<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>A beautiful image<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span> <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>But that’s not very interesting. The real power comes from conditional loading.</p>\n<h2 id=\"container-queries-(default)\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#container-queries-(default)\" aria-hidden=\"true\">#</a> Container queries (default)</h2>\n<p>Load an image only when its container reaches a minimum width:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>large-image.jpg<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Large image<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">min-inline-size</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>500px<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>The image loads when the <code>lazy-img</code> element’s container reaches 500px wide. This is the default query mode—it uses <code>ResizeObserver</code> to watch the container size.</p>\n<h2 id=\"media-queries\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#media-queries\" aria-hidden=\"true\">#</a> Media queries</h2>\n<p>You can lazy load images based on viewport width instead by switching to media query mode:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>desktop-image.jpg<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Desktop image<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">min-inline-size</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>768px<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">query</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>media<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>With this configuration, the image loads when the browser window is at least 768px wide.</p>\n<h2 id=\"view-mode-(scroll-based-loading)\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#view-mode-(scroll-based-loading)\" aria-hidden=\"true\">#</a> View mode (scroll-based loading)</h2>\n<p>Load images when they scroll into view using <code>IntersectionObserver</code> by switching to the “view” query type:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image.jpg<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Loads when scrolled into view<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">query</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>view<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>The default behavior (<code>view-range-start=&quot;entry 0%&quot;</code>) loads as soon as any part of the image enters the viewport.</p>\n<p>Control when images load with the <code>view-range-start</code> attribute:</p>\n<p><strong>Load when 50% visible:</strong></p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image.jpg<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Loads when half visible<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">query</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>view<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">view-range-start</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>entry 50%<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p><strong>Preload before entering viewport:</strong></p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image.jpg<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Preloads 200px before visible<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">query</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>view<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">view-range-start</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>entry -200px<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>This creates a smooth user experience—images are already loaded by the time users scroll to them.</p>\n<h2 id=\"responsive-images\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#responsive-images\" aria-hidden=\"true\">#</a> Responsive images</h2>\n<p>As with regular images, you can use <code>srcset</code> and <code>sizes</code> for responsive images:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image-800.jpg<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">srcset</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image-400.jpg 400w,\n          image-800.jpg 800w,\n          image-1200.jpg 1200w<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">sizes</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>(max-width: 600px) 400px,\n         (max-width: 1000px) 800px,\n         1200px<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Responsive image<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">min-inline-size</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>400px<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>The component waits until the conditions are met before loading a real image and the browser takes over from there.</p>\n<h2 id=\"named-breakpoints\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#named-breakpoints\" aria-hidden=\"true\">#</a> Named breakpoints</h2>\n<p>You can also define named breakpoints using CSS custom properties:</p>\n<pre class=\"language-css\" tabindex=\"0\"><code class=\"language-css\"><span class=\"token selector\">:root</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">--lazy-img-mq</span><span class=\"token punctuation\">:</span> small<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token atrule\"><span class=\"token rule\">@media</span> <span class=\"token punctuation\">(</span><span class=\"token property\">min-width</span><span class=\"token punctuation\">:</span> 768px<span class=\"token punctuation\">)</span></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token selector\">:root</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">--lazy-img-mq</span><span class=\"token punctuation\">:</span> medium<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token atrule\"><span class=\"token rule\">@media</span> <span class=\"token punctuation\">(</span><span class=\"token property\">min-width</span><span class=\"token punctuation\">:</span> 1024px<span class=\"token punctuation\">)</span></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token selector\">:root</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">--lazy-img-mq</span><span class=\"token punctuation\">:</span> large<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>Then reference them in your markup:</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image.jpg<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Image with named breakpoints<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">named-breakpoints</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>medium, large<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">query</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>media<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>The image loads when <code>--lazy-img-mq</code> matches “medium” or “large”.</p>\n<h2 id=\"preventing-layout-shift\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#preventing-layout-shift\" aria-hidden=\"true\">#</a> Preventing layout shift</h2>\n<p>As with regular images, don’t forget to use <code>width</code> and <code>height</code> attributes to prevent Cumulative Layout Shift (CLS):</p>\n<pre class=\"language-html\" tabindex=\"0\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>lazy-img</span>\n  <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>image.jpg<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">alt</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>A beautiful image<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">width</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>800<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">height</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>600<span class=\"token punctuation\">\"</span></span>\n  <span class=\"token attr-name\">min-inline-size</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>768px<span class=\"token punctuation\">\"</span></span>\n<span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>lazy-img</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>The browser reserves the correct space while the image loads, preventing content from jumping around.</p>\n<h2 id=\"state-attributes-for-styling\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#state-attributes-for-styling\" aria-hidden=\"true\">#</a> State attributes for styling</h2>\n<p>The component provides <code>loaded</code> and <code>qualifies</code> attributes you can use in CSS:</p>\n<pre class=\"language-css\" tabindex=\"0\"><code class=\"language-css\"><span class=\"token comment\">/* Hide images that loaded but no longer meet conditions */</span>\n<span class=\"token selector\">lazy-img[loaded]:not([qualifies])</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">display</span><span class=\"token punctuation\">:</span> none<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">/* Show a placeholder for images that qualify but haven't loaded */</span>\n<span class=\"token selector\">lazy-img[qualifies]:not([loaded])::before</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">content</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"Loading…\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token property\">display</span><span class=\"token punctuation\">:</span> block<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">padding</span><span class=\"token punctuation\">:</span> 2em<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">background</span><span class=\"token punctuation\">:</span> #f0f0f0<span class=\"token punctuation\">;</span>\n  <span class=\"token property\">text-align</span><span class=\"token punctuation\">:</span> center<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"events\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#events\" aria-hidden=\"true\">#</a> Events</h2>\n<p>If you crave control, you can add your own functionality by listening for when images load:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> lazyImg <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"lazy-img\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nlazyImg<span class=\"token punctuation\">.</span><span class=\"token function\">addEventListener</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"lazy-img:loaded\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">event</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Image loaded:\"</span><span class=\"token punctuation\">,</span> event<span class=\"token punctuation\">.</span>detail<span class=\"token punctuation\">.</span>src<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"performance\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#performance\" aria-hidden=\"true\">#</a> Performance</h2>\n<p>The component is highly optimized:</p>\n<ul>\n<li><strong>Throttled resize</strong>: Resize events are throttled to prevent excessive checks</li>\n<li><strong>Shared <code>ResizeObserver</code></strong>: Multiple images observing the same container share a single ResizeObserver</li>\n<li><strong>Shared window resize listener</strong>: Media query mode shares a single window resize listener</li>\n<li><strong>Shared <code>IntersectionObserver</code></strong>: View mode with the same <code>view-range-start</code> shares an <code>IntersectionObserver</code></li>\n<li><strong>Clean disconnection</strong>: Properly cleans up observers when elements are removed</li>\n</ul>\n<p>Even with hundreds of <code>lazy-img</code> elements on a page, performance remains excellent.</p>\n<h2 id=\"progressive-enhancement\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#progressive-enhancement\" aria-hidden=\"true\">#</a> Progressive enhancement</h2>\n<p>If JavaScript fails to load, images simply don’t appear (unless using immediate loading mode). This might sound problematic, but for non-critical images—decorative graphics, supplementary screenshots, marketing imagery—it’s often exactly what you want. Your content remains accessible; you just lose the enhancements.</p>\n<p>For critical images that are part of your content, use standard <code>img</code> tags. Use <code>lazy-img</code> for conditional enhancements.</p>\n<h2 id=\"demo\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#demo\" aria-hidden=\"true\">#</a> Demo</h2>\n<p>Explore <a href=\"https://aarongustafson.github.io/lazy-img/demo/\">the demo</a> to see container queries, media queries, scroll-based loading, and more in action:</p>\n<figure id=\"fig-2025-12-06-04\" class=\"media-container\">\n<fullscreen-control class=\"talk__slides__embed video-embed__video\">\n<iframe src=\"https://aarongustafson.github.io/lazy-img/demo/\" class=\"talk__slides__embed video-embed__video\" frameborder=\"0\"></iframe>\n</fullscreen-control>\n</figure>\n<h2 id=\"grab-it\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"#grab-it\" aria-hidden=\"true\">#</a> Grab it</h2>\n<p>Check out the project on <a href=\"https://github.com/aarongustafson/lazy-img\">GitHub</a>. Install via npm:</p>\n<pre class=\"language-bash\" tabindex=\"0\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> @aarongustafson/lazy-img</code></pre>\n<p>Import and use:</p>\n<pre class=\"language-javascript\" tabindex=\"0\"><code class=\"language-javascript\"><span class=\"token keyword\">import</span> <span class=\"token string\">\"@aarongustafson/lazy-img\"</span><span class=\"token punctuation\">;</span></code></pre>\n<p>Based on my original <a href=\"https://github.com/easy-designs/easy-lazy-images.js\">Easy Lazy Images</a> concept, reimagined as a modern custom element.</p>\n","social_text":"Want to skip loading images entirely on mobile? Here’s a web component that does just that.","url":"https://www.aaron-gustafson.com/notebook/lazy-loading-images-based-on-screen-size/","tags":["web components","progressive enhancement","HTML","performance","images","responsive design"],"date_published":"2025-12-10T17:15:54Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/why-i-care-deeply-about-web-accessibility-and-you-should-too/","title":"🔗 Why I Care Deeply About Web Accessibility And You Should Too","content_html":"<p>I agree with so much of this piece… especially <a href=\"https://www.aaron-gustafson.com/speaking-engagements/delivering-critical-information-services/\">the expansive view of accessibility that is inclusive of both the disability divide and the digital divide</a>.</p>\n<p>Great summary here:</p>\n<blockquote>\n<p>[M]y passion for accessibility stems from experiencing accessibility barriers personally, observing their impact on others, and holding the conviction that technology should tear down divides - not erect new ones. I want to fulfill, and help you fulfill, the web’s promise of equal access and opportunity for everyone, regardless of circumstances. Digital accessibility should not be an accommodation but a fundamental right and prerequisite for technology to truly better humanity.</p>\n</blockquote>\n","social_text":"I agree with so much of this piece, especially the expansive view of accessibility that is inclusive of both the disability divide and the digital divide.","url":"https://www.aaron-gustafson.com/notebook/links/why-i-care-deeply-about-web-accessibility-and-you-should-too/","external_url":"https://dev.to/schalkneethling/why-i-care-deeply-about-web-accessibility-and-you-should-too-274a","tags":["accessibility","inclusive design","performance"],"date_published":"2024-05-09T17:36:18Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/save-our-world/","title":"🔗 Save Our World","content_html":"<p>I wish I could have been at CSUN to see Jen Strickland deliver this talk. It’s about the potential and actual harms inherent in web design and how to address them in our work.</p>\n<p>Lots of juicy stats to share in your team discussions!</p>\n","social_text":"I wish I could have been at #CSUN to see @jenstrickland deliver this talk: “Save Our World.”\nIt’s about the potential and actual harms inherent in web design and how to address them in our work.","url":"https://www.aaron-gustafson.com/notebook/links/save-our-world/","external_url":"https://github.com/jenstrickland/CSUN2023/tree/main","tags":["accessibility","progressive enhancement","performance"],"image":"https://opengraph.githubassets.com/1859bbad57466a3830d682be630a63f22fa117502e1d3ef98561b36400c14615/jenstrickland/CSUN2023","date_published":"2023-04-05T22:03:37Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/building-a-resilient-frontend-using-progressive-enhancement/","title":"🔗 Building a resilient frontend using progressive enhancement","summary":"How did I miss this? The <a href=\"http://gov.uk\">gov.uk</a> Service Manual recommends progressive enhancement for all their websites.","content_html":"<p>How did I miss this? The <a href=\"http://gov.uk\">gov.uk</a> Service Manual recommends progressive enhancement for all their websites.</p>\n<blockquote>\n<p>Using progressive enhancement means your users will be able to do what they need to do if any part of the stack fails. Building your service using progressive enhancement will:</p>\n<ul>\n<li>make your service more resilient</li>\n<li>mean your service’s most basic functionality will work and meet the core needs of the user</li>\n<li>improve accessibility by encouraging best practices like writing semantic markup</li>\n<li>help users with device or connectivity limitations to use your service</li>\n</ul>\n</blockquote>\n<p>🥰</p>\n","social_text":"How did I miss that gov.uk was requiring progressive enhancement‽","url":"https://www.aaron-gustafson.com/notebook/links/building-a-resilient-frontend-using-progressive-enhancement/","external_url":"https://www.gov.uk/service-manual/technology/using-progressive-enhancement","tags":["accessibility","performance","progressive enhancement","web design","web development"],"image":"https://www.gov.uk/assets/static/govuk-opengraph-image-dade2dad5775023b0568381c4c074b86318194edb36d3d68df721eea7deeac4b.png","date_published":"2022-11-03T16:58:46Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/real-world-performance-budgets-perfnow-2022/","title":"🔗 Real-World Performance Budgets [PerfNow 2022]","summary":"A clear and focused walkthrough of how to get started with performance budgets and then how to ramp up.","content_html":"<p>A clear and focused walkthrough of how to get started with performance budgets and then how to ramp up.</p>\n","social_text":"An awesome overview of the whys and hows of performance budgets, from @tameverts","url":"https://www.aaron-gustafson.com/notebook/links/real-world-performance-budgets-perfnow-2022/","external_url":"https://www.slideshare.net/tammyeverts/realworld-performance-budgets-perfnow-2022","tags":["performance"],"image":"https://cdn.slidesharecdn.com/ss_thumbnails/2022-perfnow-performance-budgets-221027130426-9aa85c3a-thumbnail.jpg?width=640&amp;height=640&amp;fit=bounds","date_published":"2022-11-03T16:45:16Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/will-serving-real-html-content-make-a-website-faster-lets-experiment/","title":"🔗 Will Serving Real HTML Content Make A Website Faster? Let&#39;s Experiment!","content_html":"<p>When you’ve worked on the web for as long as I have, you see trends come and go. I’ve witnessed at least three different eras where folks began to put all their eggs in the JavaScript basket, only to realize the massive hits they were taking to performance.</p>\n<p>In this piece for WebPageTest, Scott Jehl uses a new “experiments” feature to demonstrate how serving HTML would make a ton of popular websites much, much faster. Some of these companies learned this lesson previously (and even <a href=\"https://medium.com/airbnb-engineering/isomorphic-javascript-the-future-of-web-apps-10882b7a2ebc\">wrote about it</a>) only to have thrown their own advice out the window, which is disappointing.</p>\n<p>Serving HTML will always result in faster page loads. There is no way around that. Sending your JavaScript framework to the client and having it render the HTML adds a ton of extra steps:</p>\n<ol>\n<li>Download skeleton markup</li>\n<li>Download high priority JavaScript file(s) and CSS</li>\n<li>Load JavaScript program into memory</li>\n<li>Execute JavaScript</li>\n<li>Generate actual markup (and replace skeleton)</li>\n<li>Request assets referenced in markup (e.g., images, videos)</li>\n<li><strong>Render page</strong></li>\n<li>Load deferred assets</li>\n</ol>\n<p>Compare that to the HTML-first route:</p>\n<ol>\n<li>Download markup</li>\n<li>Download high priority JavaScript file(s) and CSS</li>\n<li>Request assets referenced in markup (e.g., images, videos)</li>\n<li><strong>Render page</strong></li>\n<li>Load deferred assets</li>\n</ol>\n<p>Sure, on subsequent navigations having JavaScript request only the bits and pieces you need is a performance win, but that first render is a beast. And you can totally load that JavaScript later, after the page is rendered.</p>\n","social_text":"Loving @scottjehl’s in-depth performance comparison between serving HTML and JavaScript that builds HTML. No surprise: HTML wins every time.","url":"https://www.aaron-gustafson.com/notebook/links/will-serving-real-html-content-make-a-website-faster-lets-experiment/","external_url":"https://blog.webpagetest.org/posts/will-html-content-make-site-faster/","tags":["JavaScript","performance"],"image":"https://blog.webpagetest.org/cloudinary/webpagetest/image/upload/f_auto,q_auto,c_fill,g_xy_center,h_630,w_1200,x_0/Mimic_Post_Cover_sedsq6.png","date_published":"2022-10-07T17:31:05Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/making-the-world-s-fastest-website-and-other-mistakes-series-/","title":"🔗 Making the world’s fastest website, and other mistakes (Series)","content_html":"<p>This 4 part series walks through the various levels of Hell you must traverse to actually achieve solid web performance on a large e-commerce platform. What is most amazing to me is how things continue to align pretty directly with the philosophy of progressive enhancement.</p>\n","social_text":"This series on web performance from @tigt_ is really dense but full of so many excellent real-world learnings.","url":"https://www.aaron-gustafson.com/notebook/links/making-the-world-s-fastest-website-and-other-mistakes-series-/","external_url":"https://dev.to/tigt/making-the-worlds-fastest-website-and-other-mistakes-56na","tags":["performance","web development","progressive enhancement"],"image":"https://res.cloudinary.com/practicaldev/image/fetch/s--SeomjpZ---/c_imagga_scale,f_auto,fl_progressive,h_500,q_auto,w_1000/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rix9oblwajfxxrtwc9xk.jpg","date_published":"2022-04-28T16:37:38Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/benchmarking-javascript-memory-usage/","title":"🔗 Benchmarking JavaScript Memory Usage","content_html":"<p>Testing JavaScript memory usage is not done often enough, but it’s incredibly important. This is an awesome guide from Tim Kadlec.</p>\n","social_text":"Testing JavaScript memory usage is not done often enough, but it’s incredibly important. This is an awesome guide from @tkadlec.","url":"https://www.aaron-gustafson.com/notebook/links/benchmarking-javascript-memory-usage/","external_url":"https://blog.webpagetest.org/posts/benchmarking-javascript-memory-usage/","tags":["JavaScript","performance"],"image":"https://blog.webpagetest.org/cloudinary/webpagetest/image/upload/f_auto,q_auto,c_limit,w_1200,h_630/MemoryBlogImage_uflo0b.psd","date_published":"2021-07-23T21:41:30Z"},{"id":"https://www.aaron-gustafson.com/speaking-engagements/delivering-critical-information-services/","title":"📢 Delivering Critical Information &amp;amp; Services","summary":"In this session, Aaron discusses the many challenges to delivering critical information and services as well as the steps you can take to overcome those challenges.","content_html":"<p>Early on, Internet access was considered a luxury. Those times have passed and the Internet, especially the Web, has become a necessity. Whether your users are trying to access their money, gather health information, attend class, apply for assistance, or any of the other hundreds (if not thousands) of critical tasks people do on the web, your site needs to be prepared to meet their needs. And it needs to work, no matter what.</p>\n<p>In this session, Aaron discusses the many challenges to delivering critical information and services as well as the steps you can take to overcome those challenges. He’ll explore ways to make sure you can meet users on a variety of devices—and not the just the latest and greatest high end ones folks are talking about; how to make it accessible to people with disabilities; and how to load—and load quickly—on limited-bandwidth connections.</p>\n","social_text":"In this session, Aaron discusses the many challenges to delivering critical information and services as well as the steps you can take to overcome those challenges.","url":"https://www.aaron-gustafson.com/speaking-engagements/delivering-critical-information-services/","tags":["accessibility","inclusive design","performance","web design","web development"],"image":"https://www.aaron-gustafson.com/undefined","date_published":"2020-08-17T05:09:00Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/2020-04-21-the-cost-of-javascript-frameworks/","title":"🔗 The Cost of Javascript Frameworks","content_html":"<p>Excellent analysis by Tim here:</p>\n<blockquote>\n<p>Good frameworks should provide a better starting point on the essentials (security, accessibility, performance) or have built-in constraints that make it harder to ship something that violates those.</p>\n<p>That doesn’t appear to be happening with performance (nor with accessibility, apparently).</p>\n<p>…</p>\n<p>What is clear: right now, if you’re using a framework to build your site, you’re making a trade-off in terms of initial performance—even in the best of scenarios.</p>\n<p>Some trade-off may be acceptable in the right situations, but it’s important that we make that exchange consciously.</p>\n</blockquote>\n<p>Do yourself a favor and tuck into the numbers here. He presents a substantial amount of very useful information.</p>\n","social_text":"The Cost of Javascript Frameworks","url":"https://www.aaron-gustafson.com/notebook/links/2020-04-21-the-cost-of-javascript-frameworks/","external_url":"https://timkadlec.com/remembers/2020-04-21-the-cost-of-javascript-frameworks/","tags":["performance","JavaScript"],"image":"https://timkadlec.com/images/cost-of-frameworks-bytes-mobile-sm.png","date_published":"2020-04-27T19:39:10Z"},{"id":"https://www.aaron-gustafson.com/publications/articles/request-with-intent-caching-strategies-in-the-age-of-pwas/","title":"📄 Request with Intent: Caching Strategies in the Age of PWAs","content_html":"<p>Caching media files, especially images, seems like an obvious way to improve performance, but should we? To provide a more performant UX without abusing users’ network connections or hard drives, I put a spin on classic best practices, experiment with media caching strategies, and share smart Cache API tricks.</p>\n","url":"https://alistapart.com/article/request-with-intent-caching-strategies-in-the-age-of-pwas/","tags":["progressive web apps","responsive web design","performance"],"date_published":"2019-11-21T00:00:00Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/5g-will-definitely-make-the-web-slower-maybe/","title":"🔗 5G Will Definitely Make the Web Slower, Maybe","content_html":"<p>The bigger the pipe, the more we’ll shove into it. This is an important piece from Scott Jehl. You should give it a read.</p>\n<blockquote>\n<p>This problem is on us. Yes, we need to better prioritize our asset delivery, but most importantly, we need to stop delivering so much JavaScript. We need to audit our script inventory, and scrutinize our 3rd party integrations regularly, as many of these packages are abandoned or meant to be short-lived. … We should do whatever we can to keep our team members aware of their own impact, across all roles.</p>\n</blockquote>\n","social_text":"The bigger the pipe, the more we’ll shove into it.","url":"https://www.aaron-gustafson.com/notebook/links/5g-will-definitely-make-the-web-slower-maybe/","external_url":"https://www.filamentgroup.com/lab/5g/","tags":["performance","JavaScript","web development"],"image":"https://www.filamentgroup.com/images/icons/twittercard.png","date_published":"2019-09-25T21:56:56Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/the-weight-of-the-wwworld-is-up-to-us-by-patty-toland/","title":"🔗 The Weight of the WWWorld is Up to Us by Patty Toland","content_html":"<p>So much awesome content in Patty’s talk. In particular…</p>\n<blockquote>\n<p>There was a common assertion that slow networks were a third-world challenge. Remember Facebook’s network challenges? They always talked about new markets in India and Africa. The implication is that this isn’t our problem in, say, Omaha or New York.</p>\n<p>Pew Research provided a lot of data back then that showed that this thinking was wrong. Use of cell phones, especially smartphones and tablets, escalated dramatically in the United States. There was a trend towards mobile-only usage. This was in low-income households—about one third of the population. Among 5,400 panelists, 15% did not have a JavaScript-enabled device.</p>\n</blockquote>\n","social_text":"The brilliant @pattytoland’s “The Weight of the WWWorld is Up to Us” talk as captured by @adactio’s furiously-typing fingers. #AEACHI","url":"https://www.aaron-gustafson.com/notebook/links/the-weight-of-the-wwworld-is-up-to-us-by-patty-toland/","external_url":"https://adactio.com/journal/15740","tags":["performance","progressive enhancement","web development"],"image":"https://adactio.com/images/photo-300.jpg","date_published":"2019-08-29T23:29:54Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/-how-web-content-can-affect-power-usage/","title":"🔗 How Web Content Can Affect Power Usage","content_html":"<p>This is a great overview of the many ways in which our designs (and code) affect power usage and battery life.</p>\n","url":"https://www.aaron-gustafson.com/notebook/links/-how-web-content-can-affect-power-usage/","external_url":"https://webkit.org/blog/8970/how-web-content-can-affect-power-usage/","tags":["performance"],"image":"https://webkit.org/wp-content/uploads/Web-Inspector-CPU-Timeline-Overview-Light.png","date_published":"2019-08-29T22:29:17Z"},{"id":"https://www.aaron-gustafson.com/speaking-engagements/media-in-the-age-of-pwas/","title":"📢 Media in the Age of PWAs","summary":"In this session I discuss some of the potential pitfalls in implementing Service Workers, especially when it comes to managing heavy files like images and video.","content_html":"<p>Our industry is abuzz with talk about Progressive Web Apps (PWAs) and with good reason: they are a great way to improve the experiences our users have on our sites, especially when it comes to performance. Using Service Workers—a key component of PWAs—we can manage network requests and the cache to an incredibly granular degree. We can also totally abuse the privilege Service Workers grant us when it comes to writing files to disk.</p>\n<p>In this session, I discuss some of the potential pitfalls in implementing Service Workers, especially when it comes to managing heavy files like images and video. He’ll provide guidance on current best practices in cache management. And he’ll offer a few simple recipes you can put to use right away to deliver amazing experiences for your users that respect their data usage and disk space.</p>\n","social_text":"In this session I discuss some of the potential pitfalls in implementing Service Workers, especially when it comes to managing heavy files like images and video.","url":"https://www.aaron-gustafson.com/speaking-engagements/media-in-the-age-of-pwas/","tags":["performance","progressive web apps"],"image":"https://www.aaron-gustafson.com/undefined","date_published":"2019-05-01T00:09:00Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/native-image-lazy-loading-for-the-web-/","title":"🔗 Native image lazy-loading for the web!","content_html":"<p>I’m incredibly excited that this feature is shipping in Chromium. It was one of my favorite IE features.</p>\n<p><em>Note: I no longer use “native” in this context, but it remains in quoted material.</em></p>\n","url":"https://www.aaron-gustafson.com/notebook/links/native-image-lazy-loading-for-the-web-/","external_url":"https://addyosmani.com/blog/lazy-loading/","tags":["HTML","performance"],"image":"https://addyosmani.com/assets/images/loading-attribute.png","date_published":"2019-04-08T18:30:23Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/hard-costs-of-third-party-scripts/","title":"🔗 Hard Costs of Third-Party Scripts","content_html":"<p>Dave has an excellent round-up of considerations when looking at your reliance on 3rd party scripts (or any 3rd party resources, for that matter). Most are hidden and all have a serious effect on download performance, UI responsiveness, and, ultimately, user experience.</p>\n<blockquote>\n<p>The Web is an undependable place, so this shouldn’t be very surprising.</p>\n</blockquote>\n","social_text":"All 3rd party resources have hidden costs that cause serious problems for users in terms of download performance, UI responsiveness, and, ultimately, user experience","url":"https://www.aaron-gustafson.com/notebook/links/hard-costs-of-third-party-scripts/","external_url":"https://daverupert.com/2018/10/hard-costs-of-third-party-scripts/","tags":["performance"],"image":"https://daverupert.com/images/global/newshammericon.png","date_published":"2018-10-29T23:31:47Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/performance-budgets/","title":"🔗 Start Performance Budgeting","content_html":"<p>More thoughtful (and actionable) advice about performance budgets. Would that more companies would do this!</p>\n","url":"https://www.aaron-gustafson.com/notebook/links/performance-budgets/","external_url":"https://addyosmani.com/blog/performance-budgets/","tags":["performance"],"image":"https://addyosmani.com/assets/images/perf-budget-metrics.png","date_published":"2018-10-19T22:16:29Z"},{"id":"https://www.aaron-gustafson.com/speaking-engagements/better-performance-greater-accessibility/","title":"📢 Better Performance == Greater Accessibility","summary":"In this intensely practical session, Aaron will explore the ins and outs of page load performance by showing how he made the web site of the 10K Apart meet its own contest rules, by having a site that was functional and attractive even without JavaScript, and was less than ten kilobytes at initial load.","content_html":"<p>Design is problem solving. Each and every day, we are tasked with finding ways to reduce the friction our users experience on the Web. That means streamlining flows, reducing cognitive load, writing more appropriate copy, and (of course) building accessible experience. But experience is about more than just interface. Our users’ experiences begin with their first request to our servers.</p>\n<p>In this intensely practical session, Aaron will explore the ins and outs of page load performance by showing how he made the web site of the 10K Apart meet its own contest rules, by having a site that was functional and attractive even without JavaScript, and was less than ten kilobytes at initial load. You’ll walk away with a better understanding of the page load process as well as numerous ways you can improve the projects you are working on right now.</p>\n","social_text":"In this intensely practical session, Aaron will explore the ins and outs of page load performance by showing how he made the web site of the 10K Apart meet its own contest rules, by having a site that was functional and attractive even without JavaScript, and was less than ten kilobytes at initial load.","url":"https://www.aaron-gustafson.com/speaking-engagements/better-performance-greater-accessibility/","tags":["performance","accessibility"],"image":"https://www.aaron-gustafson.com/undefined","date_published":"2018-10-11T00:09:00Z"},{"id":"https://www.aaron-gustafson.com/notebook/links/how-to-build-a-lowtech-website/","title":"🔗 How to Build a Low-tech Website?","content_html":"<p>I <em>love</em> this project. It ticks so many boxes for me as it’s all about doing more with less. I love the ways they’ve found to reduce consumption on the server side and throughput to their users as well. So much to unpack!</p>\n","social_text":"Building a Low Tech Website… I *love* this project. It ticks so many boxes for me as it’s all about doing more with less.","url":"https://www.aaron-gustafson.com/notebook/links/how-to-build-a-lowtech-website/","external_url":"https://solar.lowtechmagazine.com/2018/09/how-to-build-a-lowtech-website/","tags":["web design","web development","performance"],"image":"https://solar.lowtechmagazine.com//dithers/sps_close.png","date_published":"2018-10-03T22:38:05Z"}]}