<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/css" href="https://www.aaron-gustafson.com/c/feed.min.css" ?><feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:amg="https://www.aaron-gustafson.com.com/amg-dtd/"><title>Aaron Gustafson: Content tagged experiments</title><subtitle>The latest 20 posts and links tagged experiments.</subtitle><id>https://www.aaron-gustafson.com</id><link href="https://www.aaron-gustafson.com/feeds/experiments.xml" rel="self"/><link href="https://www.aaron-gustafson.com"/><author><name>Aaron Gustafson</name><uri>https://www.aaron-gustafson.com</uri></author><updated>2023-10-09T22:38:54Z</updated><entry><id>https://www.aaron-gustafson.com/notebook/widgets/</id><title type="html"><![CDATA[✍🏻 Widgets!]]></title><link href="https://www.aaron-gustafson.com/notebook/widgets/" rel="alternate" type="text/html" /><published>2023-10-09T22:38:54Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>It was a long time coming, but I finally had a chance to put the work I did on <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/tree/main/PWAWidgets">a widgets proposal for PWAs</a> into practice on my own site. I’m pretty excited about it!</p><h2 id="where-it-all-started" tabindex="-1"><a class="header-anchor" href="#where-it-all-started" aria-hidden="true">#</a> Where it all started</h2><p>I had <a href="https://web.archive.org/web/20200929174844/https://discourse.wicg.io/t/noodling-on-an-idea-projections-for-web-apps/3900">the original idea for “projections”</a> way back in 2019. Inspired by <a href="https://en.wikipedia.org/wiki/Dashboard_(macOS)#Widget_functions_and_capabilities">OS X’s Dashboard Widgets</a> and <a href="https://en.wikipedia.org/wiki/Adobe_AIR">Adobe AIR</a>, I’d begun to wonder if it might be possible to <em>project</em> a component from a website into those kinds of surfaces. Rather than building a bespoke widget that connected to an API, I thought it made sense to leverage an installed PWA to manage those “projections.” I shared the idea at TPAC that year and got some interest from a broad range of folks, but didn’t have much time to work on the details until a few years later.</p><p>In the intervening time, I kept working through the concept in my head. I mean in an ideal world, the widget would just be a responsive web page, right? But if that were the case, what happens when every widget loads the entirety of React to render their stock ticker? That seemed like a performance nightmare.</p><p>In my gut, I felt like the right way to build things would be to have a standard library of widget templates and to enable devs to flow data into them via a Service Worker. Alex Russell suggested I model the APIs on how Notifications are handled (since they serve a similar function) and I was off to the races.</p><p>I drafted <a href="https://github.com/aarongustafson/pwa-widgets">a substantial proposal for my vision of how PWA widgets should work</a>. Key aspects included:</p><ul><li>A declarative way to define and configure a widget from within the Web App Manifest;</li><li>A progressively enhanced pathway for devs to design a widget that adapts to its host environment, from using predefined templates to using custom templates to full-blown web-based widgets (with rendering akin to an <code>iframe</code>);</li><li>A collection of recommended stock templates that implementors should offer to support most widget types;</li><li>Extensibility to support custom templates using any of a variety of templating languages; and</li><li>A complete suite of tools for managing widgets and any associated business logic within a Service Worker.</li></ul><h2 id="widgets-became-a-reality" tabindex="-1"><a class="header-anchor" href="#widgets-became-a-reality" aria-hidden="true">#</a> Widgets became a reality</h2><p>After continuing to gently push on this idea with colleagues across Microsoft (and beyond), I discovered that the Windows 11 team was looking to open up the new Widget Dashboard to third-party applications. I saw this as an opportunity to turn my idea into a reality. After working my way into the conversation, I made a solid case for why PWAs needed to be a part of that story and… it worked! (It no doubt helped that companies including Meta, Twitter, and Hulu were all invested in PWA as a means of delivering apps for Windows.)</p><p>While the timeline for implementation didn’t allow us to tackle the entirety of my proposal, we did carve out the pieces that made for a compelling MVP. This allowed us to show what’s possible, see how folks use it, and plan for future investment in the space.</p><p>Sadly, it meant tabling two features I really loved:</p><ul><li><strong>Stock/predefined templates</strong>. A library of lightly theme-able, consistent, cross-platform templates based on common data structures (e.g., RSS/Atom, iCal) would make it incredibly simple for devs to build a widget. If implemented well, devs might not even need to write a single line of business logic in their Service Worker as the browser could pick up all of the configuration details from the Manifest.</li><li><strong>Configurable widget instances.</strong> Instead of singleton widgets, these would allow you to define a single widget type and replicate it for different use cases. For example, a widget to follow a social media user’s profile could be defined once and the individual instances could be configured with the specific account to be followed.</li></ul><p>I’m sincerely hopeful these two features eventually make their way to us as I think they truly unlock the power of the widget platform. Perhaps, with enough uptake on the current implementation, we can revisit these in the not-too-distant future.</p><hr><p>To test things out, I decided to build two widgets for this site:</p><ol><li>Latest posts</li><li>Latest links</li></ol><p>Both are largely the same in terms of their setup: They display a list of linked titles from this site.</p><h2 id="designing-my-widget-templates" tabindex="-1"><a class="header-anchor" href="#designing-my-widget-templates" aria-hidden="true">#</a> Designing my widget templates</h2><p>Given that they were going to be largely identical, I made <a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/static/w/feed.ac.json">a single “feed” template for use in both widgets</a>. The templating tech I used is called <a href="https://adaptivecards.io">Adaptive Cards</a>, which is what Windows 11 uses for rendering.</p><p>Adaptive Card templates are relatively straightforward JSON:</p><pre class="language-json" tabindex="0"><code class="language-json">&amp;#<span class="token number">123</span>;<span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“AdaptiveCard”</span><span class="token punctuation">,</span><span class="token property">“$schema”</span><span class="token operator">:</span><span class="token string">“<a href="http://adaptivecards.io/schemas/adaptive-card.json">http://adaptivecards.io/schemas/adaptive-card.json</a>”</span><span class="token punctuation">,</span><span class="token property">“version”</span><span class="token operator">:</span><span class="token string">“1.6”</span><span class="token punctuation">,</span><span class="token property">“body”</span><span class="token operator">:</span> &amp;#<span class="token number">91</span>;&amp;#<span class="token number">123</span>;<span class="token property">“$data”</span><span class="token operator">:</span><span class="token string">“$&amp;#123;take(items,5)&amp;#125;”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“Container”</span><span class="token punctuation">,</span><span class="token property">“items”</span><span class="token operator">:</span> &amp;#<span class="token number">91</span>;&amp;#<span class="token number">123</span>;<span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“TextBlock”</span><span class="token punctuation">,</span><span class="token property">“text”</span><span class="token operator">:</span><span class="token string">“&amp;#91;$&amp;#123;title&amp;#125;&amp;#93;($&amp;#123;url&amp;#125;)”</span><span class="token punctuation">,</span><span class="token property">“wrap”</span><span class="token operator">:</span><span class="token boolean">true</span><span class="token punctuation">,</span><span class="token property">“weight”</span><span class="token operator">:</span><span class="token string">“Bolder”</span><span class="token punctuation">,</span><span class="token property">“spacing”</span><span class="token operator">:</span><span class="token string">“Padding”</span><span class="token punctuation">,</span><span class="token property">“height”</span><span class="token operator">:</span><span class="token string">“stretch”</span>&amp;#<span class="token number">125</span>;&amp;#<span class="token number">93</span>;<span class="token punctuation">,</span><span class="token property">“height”</span><span class="token operator">:</span><span class="token string">“stretch”</span>&amp;#<span class="token number">125</span>;&amp;#<span class="token number">93</span>;<span class="token punctuation">,</span><span class="token property">“backgroundImage”</span><span class="token operator">:</span> &amp;#<span class="token number">123</span>;<span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“<a href="https://www.aaron-gustafson.com/i/background-logo.png">https://www.aaron-gustafson.com/i/background-logo.png</a>”</span><span class="token punctuation">,</span><span class="token property">“verticalAlignment”</span><span class="token operator">:</span><span class="token string">“Bottom”</span><span class="token punctuation">,</span><span class="token property">“horizontalAlignment”</span><span class="token operator">:</span><span class="token string">“Center”</span>&amp;#<span class="token number">125</span>;&amp;#<span class="token number">125</span>;</code></pre><p>What this structure does is:</p><ol><li>Create a container into which I will place the content;</li><li>Extract the first five <code>items</code> from the data being fed into the template (more on that in a moment);</li><li>Loop through each <code>item</code> and<ul><li>create a text block,</li><li>populate its content with Markdown to generate a linked title (using the <code>title</code> and <code>url</code> keys from the <code>item</code> object)</li><li>Set some basic styles to make the text bold, separate the titles a little and make them grow to fill the container; then, finally</li></ul></li><li>Set a background on the widget.</li></ol><p>The way Adaptive Cards work is that they flow JSON data into a template and render that. The variable names in the template map directly to the incoming data structure, so are totally up to you to define. As these particular widgets are feed-driven and <a href="https://www.aaron-gustafson.com/feeds/">this site already supports JSONFeed</a>, I set up the widgets to flow the appropriate feed into each and used the keys that were already there. For reference, here’s a sample JSONFeed <code>item</code>:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span><span class="token property">“id”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“title”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“summary”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“content_html”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“tags”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token property">“date_published”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">}</span></code></pre><p>If you want to tinker with Adaptive Cards and make your own, you can do so with <a href="https://adaptivecards.io/designer/">their Designer tool</a>.</p><h3 id="defining-the-widgets-in-the-manifest" tabindex="-1"><a class="header-anchor" href="#defining-the-widgets-in-the-manifest" aria-hidden="true">#</a> Defining the widgets in the Manifest</h3><p>With a basic template created, the next step was to set up the two widgets in my Manifest. As they both function largely the same, I’ll just focus on the definition for one of them.</p><p>First off, defining widgets in the Manifest is done via the <code>widgets</code> member, which is an array (much like <code>icons</code> and <code>shortcuts</code>). Each widget is represented as an object in that array. Here is the definition for the “latest posts” widget:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Latest Posts”</span><span class="token punctuation">,</span><span class="token property">“short_name”</span><span class="token operator">:</span><span class="token string">“Posts”</span><span class="token punctuation">,</span><span class="token property">“tag”</span><span class="token operator">:</span><span class="token string">“feed-posts”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“The latest posts from Aaron Gustafson’s blog”</span><span class="token punctuation">,</span><span class="token property">“template”</span><span class="token operator">:</span><span class="token string">“feed”</span><span class="token punctuation">,</span><span class="token property">“ms_ac_template”</span><span class="token operator">:</span><span class="token string">“/w/feed.ac.json”</span><span class="token punctuation">,</span><span class="token property">“data”</span><span class="token operator">:</span><span class="token string">“/feeds/latest-posts.json”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“application/json”</span><span class="token punctuation">,</span><span class="token property">“auth”</span><span class="token operator">:</span><span class="token boolean">false</span><span class="token punctuation">,</span><span class="token property">“update”</span><span class="token operator">:</span><span class="token number">21600</span><span class="token punctuation">,</span><span class="token property">“icons”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/i/icons/webicon-rss.png”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“image/png”</span><span class="token punctuation">,</span><span class="token property">“sizes”</span><span class="token operator">:</span><span class="token string">“120x120”</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token property">“screenshots”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/i/screenshots/widget-posts.png”</span><span class="token punctuation">,</span><span class="token property">“sizes”</span><span class="token operator">:</span><span class="token string">“387x387”</span><span class="token punctuation">,</span><span class="token property">“label”</span><span class="token operator">:</span><span class="token string">“The latest posts widget”</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">}</span></code></pre><p>Breaking this down:</p><ol><li><code>name</code> and <code>short_name</code> act much like these keys in the root of the Manifest as well as in <code>shortcuts</code>: The <code>name</code> value is used as the name for the widget unless there’s not enough room, in which case <code>short_name</code> is used.</li><li>You can think of <code>tag</code> as analogous to <code>class</code> in HTML sense. It’s a way of labeling a widget so you can easily reference it later. Each widget instance will have a unique id created by the widget service, but that instance (or all instances, if the widget supports multiple instances) can be accessed via the <code>tag</code>. But more on that later.</li><li>The <code>description</code> key is used for marketing the widget within a host OS or digital storefront. It should accurately (and briefly) describe what the widget does.</li><li>The <code>template</code> key is not currently used in the Windows 11 implementation but refers to the expected standard library widget template provided by the system. As a template library is not currently available, the <code>ms_ac_template</code> value is used to provide a URL to get the custom Adaptive Card (hence “ac”) template. The “ms_” prefix is there because it’s expected that this would be a Microsoft-proprietary property. It follows <a href="https://www.w3.org/TR/appmanifest/#proprietary-extensions">the guidance for extending the Manifest</a>.</li><li>The <code>data</code> and <code>type</code> keys define the path to the data that should be fed into the template for rendering by the widget host and the MIME of the data format it’s in. The Windows 11 implementation currently only accepts JSON data, but the design of widgets is set up to allow for this to eventually extend to other standardized formats like RSS, iCal, vCard, and such.</li><li><code>update</code> is an optional configuration member allowing you to set how often you’d like the widget to update, in seconds. Developers currently need to add the logic for implementing this into their Service Worker, but this setup allows the configuration to remain independent of the JavaScript code, making it easier to maintain.</li><li>Finally, <code>icons</code> and <code>screenshots</code> allow us to define how the widget shows up in the widget host and how it is promoted for install.</li></ol><p>When someone installs my site as a PWA, the information about the available widgets gets ingested by the browser. The browser then determines, based on the provided values and its knowledge of the available widget service(s) on the device, which widgets should be offered. On Windows 11, this information is <a href="https://learn.microsoft.com/en-us/windows/apps/develop/widgets/implement-widget-provider-cs#update-the-package-manifest">routed into the AppXManifest that governs how apps are represented in Windows</a>. The Windows 11 widget service can then read in the details about the available widgets and offer them for users to install.</p><figure id="2023-10-09-01"><p><img src="https://www.aaron-gustafson.com/i/posts/2023-10-09/widgets-promotion.gif" alt=""></p><figcaption>An animated capture of Windows 11’s widget promotion surface, showing 2 widgets available from this site’s PWA.</figcaption></figure><h2 id="adding-widget-support-to-my-service-worker" tabindex="-1"><a class="header-anchor" href="#adding-widget-support-to-my-service-worker" aria-hidden="true">#</a> Adding widget support to my Service Worker</h2><p>As I mentioned earlier, all of the plumbing for widgets is done within a Service Worker and is modeled on the Notifications API. I’m not going to exhaustively detail how it all works, but I’ll give you enough detail to get you started.</p><p>First off, widgets are exposed via the <code>self.widgets</code> interface. Most importantly, this interface lets you access and update any instances of a widget connected to your PWA.</p><h3 id="installing-a-widget" tabindex="-1"><a class="header-anchor" href="#installing-a-widget" aria-hidden="true">#</a> Installing a widget</h3><p>When a user chooses to install a widget, that emits a “widgetinstall” event in your Service Worker. You use that to kickoff the widget lifecycle by gathering the template and data needed to instantiate the widget:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetinstall”</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">=&gt;</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Installing &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">initializeWidget</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>The event argument comes in with details of the specific widget being instantiated (as <code>event.widget</code>). In the code above, you can see I’ve logged the widget’s <code>tag</code> value to the console. I pass the widget information over to my <code>initializeWidget()</code> function and it updates the widget with the latest data and, if necessary, sets up a <a href="https://developer.mozilla.org/docs/Web/API/Web_Periodic_Background_Synchronization_API">Periodic Background Sync</a>:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">initializeWidget</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">await</span><span class="token function">updateWidget</span><span class="token punctuation">(</span>widget<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">await</span><span class="token function">registerPeriodicSync</span><span class="token punctuation">(</span>widget<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>The code for my <code>updateWidget()</code> function is as follows:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">updateWidget</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">const</span> template <span class="token operator">=</span><span class="token keyword">await</span><span class="token punctuation">(</span><span class="token keyword">await</span><span class="token function">fetch</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>msAcTemplate<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> data <span class="token operator">=</span><span class="token keyword">await</span><span class="token punctuation">(</span><span class="token keyword">await</span><span class="token function">fetch</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">try</span><span class="token punctuation">{</span><span class="token keyword">await</span> self<span class="token punctuation">.</span>widgets<span class="token punctuation">.</span><span class="token function">updateByTag</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>tag<span class="token punctuation">,</span><span class="token punctuation">{</span> template<span class="token punctuation">,</span> data <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Couldn’t update the widget &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>This function does the following:</p><ol><li>Get the template for this widget</li><li>Get the data to flow into the template</li><li>Use the <code>self.widgets.updateByTag()</code> method to push the <var>template</var> and <var>data</var> to the widget service to update any widget instances connected to the widget’s <code>tag</code>.</li></ol><p>As I mentioned, I also have code in place to take advantage of Periodic Background Sync if/when it’s available and the browser allows my site to do it:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">registerPeriodicSync</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">let</span> tag <span class="token operator">=</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>tag<span class="token punctuation">;</span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token string">“update”</span><span class="token keyword">in</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">)</span><span class="token punctuation">{</span>registration<span class="token punctuation">.</span>periodicSync<span class="token punctuation">.</span><span class="token function">getTags</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">tags</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span><span class="token comment">// only one registration per tag</span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>tags<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>periodicSync<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>tag<span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token literal-property property">minInterval</span><span class="token operator">:</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>update<span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>This function also receives the widget details and:</p><ol><li>Looks to see if the widget <code>definition</code> (from the Manifest) includes an <code>update</code> member. If it has one, it…</li><li>Checks to see if there’s already a Periodic Background Sync that is registered for this tag. If none exists, it…</li><li>Registers a new Periodic Background Sync using the <code>tag</code> value and a minimum interval equal to the <code>update</code> requested.</li></ol><p>The <code>update</code> member, as you may recall, is the frequency (in seconds) you’d ideally like the widget to be updated. In reality, you’re at the mercy of the browser as to when (or even if) your sync will run, but that’s totally cool as there are other ways to update widgets as well.<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup></p><h3 id="uninstalling-a-widget" tabindex="-1"><a class="header-anchor" href="#uninstalling-a-widget" aria-hidden="true">#</a> Uninstalling a widget</h3><p>When a user uninstalls a widget, your Service Worker will receive a “widgetuninstall” event. Much like the “widgetinstall” event, the argument contains details about that widget which you can use to clean up after yourself:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetuninstall”</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">=&gt;</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Uninstalling &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">uninstallWidget</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>Your application may have different cleanup needs, but this is a great time to clean up any unneeded Periodic Sync registrations. Just be sure to check the length of the widget’s <code>instances</code> array (<code>widget.instances</code>) to make sure you’re dealing with the last instance of a given widget <em>before</em> you unregister the sync:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">uninstallWidget</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">if</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>instances<span class="token punctuation">.</span>length <span class="token operator">===</span><span class="token number">1</span><span class="token operator">&amp;&amp;</span><span class="token string">“update”</span><span class="token keyword">in</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">await</span> self<span class="token punctuation">.</span>registration<span class="token punctuation">.</span>periodicSync<span class="token punctuation">.</span><span class="token function">unregister</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>tag<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><h3 id="refreshing-your-widgets" tabindex="-1"><a class="header-anchor" href="#refreshing-your-widgets" aria-hidden="true">#</a> Refreshing your widgets</h3><p>Widget platforms may periodically freeze your widget(s) to save resources. For example, they may do this when widgets are not visible. To keep your widgets up to date, they will periodically issue a “widgetresume” event. If you’ve modeled your approach on the one I’ve outlined above, you can route this event right through to your <code>updateWidget()</code> function:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetresume”</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">=&gt;</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Resuming &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">updateWidget</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h3 id="actions" tabindex="-1"><a class="header-anchor" href="#actions" aria-hidden="true">#</a> Actions</h3><p>While I don’t want to get too into the weeds here, I do want to mention that widgets can have predefined user actions as well. These actions result in “widget click” events being sent back to the Service Worker so you can respond to them:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetclick”</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">=&gt;</span><span class="token punctuation">{</span><span class="token keyword">const</span> widget <span class="token operator">=</span> event<span class="token punctuation">.</span>widget<span class="token punctuation">;</span><span class="token keyword">const</span> action <span class="token operator">=</span> event<span class="token punctuation">.</span>action<span class="token punctuation">;</span><span class="token keyword">switch</span><span class="token punctuation">(</span>action<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment">// Custom Actions</span><span class="token keyword">case</span><span class="token string">“refresh”</span><span class="token operator">:</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">updateWidget</span><span class="token punctuation">(</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">break</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>For a great example of how a widget can integrate actions, you should check out <a href="https://microsoftedge.github.io/Demos/pwamp/">the demo PWAmp project</a>. <a href="https://github.com/MicrosoftEdge/Demos/blob/main/pwamp/sw-widgets.js">Their Service Worker widget code</a> is worth a read.</p><h2 id="result!" tabindex="-1"><a class="header-anchor" href="#result!" aria-hidden="true">#</a> Result!</h2><p>With all of these pieces in place, I was excited to see my site showing up in the Widget Dashboard in Windows 11.</p><figure id="2023-10-09-02"><p><img src="https://www.aaron-gustafson.com/i/posts/2023-10-09/widgets-in-windows.jpg" alt=""></p><figcaption>A screenshot of Windows 11 showing the Widget Dashboard overlaying the desktop with this site installed as a PWA to the right. The “latest posts” and “latest links” widgets are shown.</figcaption></figure><p>You can view the full source code on GitHub:</p><ul><li><a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/static/w/feed.ac.json">“Feed” Adaptive Card Template</a></li><li><a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/static/manifest.json#L158-L237">Widget definitions in the Manifest</a></li><li><a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/_javascript/serviceworker/widgets.js">Widgets code in my Service Worker</a></li></ul><hr><p>I’m quite hopeful this will be the first of many places PWA-driven widgets will appear. If you’s like to see them supported elsewhere, be sure to tell your browser and OS vendor(s) of choice. The more they hear from their user base that this feature is needed, the more likely we are to see it get implemented in more places.</p><h2 id="addendum%3A-gotchas" tabindex="-1"><a class="header-anchor" href="#addendum%3A-gotchas" aria-hidden="true">#</a> Addendum: Gotchas</h2><p>In wiring this all up, I ran into a few current bugs I wanted to flag so you can avoid them:</p><ul><li>The <code>icons</code> member won’t accept SVG images. This should eventually be fixed, but it was keeping my widgets from appearing as installable.</li><li>The <code>screenshots</code> members can’t be incredibly large. I’m told you should provide square screenshots no larger than 500px ×500px.</li></ul><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>Have you checked out <a href="https://developer.mozilla.org/docs/Web/API/Server-sent_events/Using_server-sent_events">Server Events</a>? <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content><amg:twitter><![CDATA[I finally had a chance to put the work I did on a widgets proposal for PWAs into practice on my own site. It’s pretty exciting!]]></amg:twitter><amg:summary><![CDATA[I finally had a chance to put the work I did on a widgets proposal for PWAs into practice on my own site. It’s pretty exciting!]]></amg:summary><summary type="html"><![CDATA[<p>I finally had a chance to put the work I did on a widgets proposal for PWAs into practice on my own site. It’s pretty exciting!</p>]]></summary><category term="progressive web apps" /><category term="experiments" /><category term="JavaScript" /><category term="Microsoft" /><category term="this site" /><category term="user experience" /><category term="web development" /><category term="web standards" /><category term="Windows" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/posts/2023-10-09/hero1.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/speeding-things-up-with-service-worker-resource-hints-and-more/</id><title type="html"><![CDATA[✍🏻 Speeding Things Up with Service Worker, Resource Hints, and More]]></title><link href="https://www.aaron-gustafson.com/notebook/speeding-things-up-with-service-worker-resource-hints-and-more/" rel="alternate" type="text/html" /><published>2015-11-20T21:39:09Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>User experience encompasses more than just the interface. Download speed, render performance, and the cost of accessing a site are often overlooked areas when it comes to the practice of UX, but they all affect how users experience what we build on the Web.</p><p>I’m always looking for ways to improve these aspects of my own site. And, since it’s my own personal playground, I often use it as a test-bed for new technologies, ideas, and techniques. My latest adventure was inspired by a bunch of <a href="https://www.aaron-gustafson.com/notebook/links/">articles and posts I’ve linked to recently</a>, especially</p><ul><li><a href="https://adactio.com/journal/9775">Jeremy Keith’s “My First Service Worker”</a>,</li><li><a href="https://css-tricks.com/serviceworker-for-offline/">Nicolas Bevacqua’s “Making a Simple Site Work Offline with ServiceWorker”</a>,</li><li><a href="http://deanhume.com/Home/BlogPost/service-workers--dynamic-responsive-images-using-webp-images/10132/">Dean Hume’s “Service Workers: Dynamic Responsive Images Using Webp Images”</a>, and</li><li><a href="https://medium.com/@cramforce/not-so-micro-optimizations-f867c47b832d#.satdv0fap">Malte Ubl’s “Not so micro optimizations”</a></li></ul><p>After reading these pieces, I decided to see how much I could do to improve the performance of this site, especially on posts with a lot of images and embedded code samples, like <a href="https://www.aaron-gustafson.com/notebook/labeled-with-love/">my recent post on form labels</a>.</p><h2 id="using-resource-hints" tabindex="-1"><a class="header-anchor" href="#using-resource-hints" aria-hidden="true">#</a> Using Resource Hints</h2><p>To kick things off, I followed Malte’s advice and used <a href="https://w3c.github.io/resource-hints/">Resource Hints</a> to <em>prime the pump</em> for any third-party servers hosting assets I use frequently (e.g. Disqus, Twitter, etc.). I used the code Malte references in <a href="https://github.com/ampproject/amphtml">the AMP Project</a> as my starting point and <a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/_javascript/main/resource-hints.js">added two new methods (<code>preconnect()</code> and <code>prefetch()</code>) to my global <code>AG</code> object</a>. With that library code in place, I can call those methods as necessary from within my other JavaScript files. Here’s a simplified extract from <a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/old-jekyll/_javascript/post/disqus.js.removed">my Disqus integration script</a>:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token string">“AG”</span><span class="token keyword">in</span> window <span class="token operator">&amp;&amp;</span><span class="token string">“preconnect”</span><span class="token keyword">in</span> window<span class="token punctuation">.</span><span class="token constant">AG</span><span class="token punctuation">)</span><span class="token punctuation">{</span>window<span class="token punctuation">.</span><span class="token constant">AG</span><span class="token punctuation">.</span><span class="token function">preconnect</span><span class="token punctuation">(</span><span class="token string">“<a href="//disqus.com/">//disqus.com/</a>”</span><span class="token punctuation">)</span><span class="token punctuation">;</span>window<span class="token punctuation">.</span><span class="token constant">AG</span><span class="token punctuation">.</span><span class="token function">prefetch</span><span class="token punctuation">(</span><span class="token string">“//”</span><span class="token operator">+</span> disqus_shortname <span class="token operator">+</span><span class="token string">“.disqus.com/count.js”</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>While a minor addition, the speed improvement in <a href="http://caniuse.com/#search=resource%20hints">supporting browsers</a> was noticeable.<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup></p><h2 id="integrating-service-worker" tabindex="-1"><a class="header-anchor" href="#integrating-service-worker" aria-hidden="true">#</a> Integrating Service Worker</h2><p>With that in the bag, I set about making my first <a href="http://www.w3.org/TR/service-workers/">Service Worker</a>. I started off gently, using Dean’s piece as a guide. I added a WebP conversion bit to <a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/old-jekyll/_tasks/gulp/images.js">my image processing Gulp task</a> to get the files in place and then I created the Service Worker. By default, <a href="https://gist.github.com/deanhume/c04478df744ce833925c#file-client-hints-service-worker-js">Dean’s code</a> converts <em>all</em> JPG and PNG requests to WebP responses, so I set it up to limit the requests to only those files being requested directly from my server. I have no way of knowing if WebP equivalents of every JPG and PNG exist on the open web (probably not), but I know they exist on my server. Here’s the updated code:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token string">“use strict”</span><span class="token punctuation">;</span>self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“fetch”</span><span class="token punctuation">,</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">var</span> request <span class="token operator">=</span> event<span class="token punctuation">.</span>request<span class="token punctuation">,</span>url <span class="token operator">=</span> request<span class="token punctuation">.</span>url<span class="token punctuation">,</span>url_object <span class="token operator">=</span><span class="token keyword">new</span><span class="token class-name">URL</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">,</span>re_jpg_or_png <span class="token operator">=</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">.(?:jpg|png)$</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">,</span>supports_webp <span class="token operator">=</span><span class="token boolean">false</span><span class="token punctuation">,</span><span class="token comment">// pessimism</span>webp_url<span class="token punctuation">;</span><span class="token comment">// Check if the image is a local jpg or png</span><span class="token keyword">if</span><span class="token punctuation">(</span>re_jpg_or_png<span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>url<span class="token punctuation">)</span><span class="token operator">&amp;&amp;</span> url_object<span class="token punctuation">.</span>origin <span class="token operator">==</span> location<span class="token punctuation">.</span>origin<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment">// console.log(‘WORKER: caught a request for a local PNG or JPG’);</span><span class="token comment">// Inspect the accept header for WebP support</span><span class="token keyword">if</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>headers<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span><span class="token string">“accept”</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>supports_webp <span class="token operator">=</span> request<span class="token punctuation">.</span>headers<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">“accept”</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">“webp”</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment">// Browser supports WebP</span><span class="token keyword">if</span><span class="token punctuation">(</span>supports_webp<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment">// Make the new URL</span>webp_url <span class="token operator">=</span> url<span class="token punctuation">.</span><span class="token function">substr</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> url<span class="token punctuation">.</span><span class="token function">lastIndexOf</span><span class="token punctuation">(</span><span class="token string">“.”</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">“.webp”</span><span class="token punctuation">;</span>event<span class="token punctuation">.</span><span class="token function">respondWith</span><span class="token punctuation">(</span><span class="token function">fetch</span><span class="token punctuation">(</span>webp_url<span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token literal-property property">mode</span><span class="token operator">:</span><span class="token string">“no-cors”</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>When I began tucking to the caching possibilities of Service Workers, following Nicolas’ and Jeremy’s posts, I <a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/old-jekyll/_javascript/serviceworker/fetch.js">opted to tweak Nicholas’ caching setup a bit</a>. I’m still not completely thrilled with it, but it’s a work in progress. I’m sure I will tweak as I get more familiar with the technology.</p><p>To keep my Service Worker code modularized (like my other JavaScript code), I opted to <a href="https://github.com/aarongustafson/aaron-gustafson.com/tree/main/src/_javascript/serviceworker">break it up into separate files</a> and am using Gulp to merge them all together and move the combined file into the root of the site. If you’d like to follow a similar path, feel free to adapt this Gulp task (which builds all of my JavaScript):</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">var</span> gulp <span class="token operator">=</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">“gulp”</span><span class="token punctuation">)</span><span class="token punctuation">,</span>path <span class="token operator">=</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">“path”</span><span class="token punctuation">)</span><span class="token punctuation">,</span>folder <span class="token operator">=</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">“gulp-folders”</span><span class="token punctuation">)</span><span class="token punctuation">,</span>gulpIf <span class="token operator">=</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">“gulp-if”</span><span class="token punctuation">)</span><span class="token punctuation">,</span>insert <span class="token operator">=</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">“gulp-insert”</span><span class="token punctuation">)</span><span class="token punctuation">,</span>concat <span class="token operator">=</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">“gulp-concat”</span><span class="token punctuation">)</span><span class="token punctuation">,</span>uglify <span class="token operator">=</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">“gulp-uglify”</span><span class="token punctuation">)</span><span class="token punctuation">,</span>notify <span class="token operator">=</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">“gulp-notify”</span><span class="token punctuation">)</span><span class="token punctuation">,</span>rename <span class="token operator">=</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">“gulp-rename”</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token comment">//handleErrors = require(‘handleErrors’),</span>source_folder <span class="token operator">=</span><span class="token string">“source/_javascript”</span><span class="token punctuation">,</span>destination_root <span class="token operator">=</span><span class="token string">“source”</span><span class="token punctuation">,</span>destination_folder <span class="token operator">=</span> destination_root <span class="token operator">+</span><span class="token string">“/j”</span><span class="token punctuation">,</span>public_root <span class="token operator">=</span><span class="token string">“public”</span><span class="token punctuation">;</span><span class="token punctuation">(</span><span class="token punctuation">(</span>public_folder <span class="token operator">=</span> public_root <span class="token operator">+</span><span class="token string">“/j”</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token punctuation">(</span>rename_serviceworker <span class="token operator">=</span><span class="token function">rename</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token literal-property property">dirname</span><span class="token operator">:</span><span class="token string">“…/”</span><span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>gulp<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token string">“scripts”</span><span class="token punctuation">,</span><span class="token function">folder</span><span class="token punctuation">(</span>source_folder<span class="token punctuation">,</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">folder</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">return</span> gulp<span class="token punctuation">.</span><span class="token function">src</span><span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span>source_folder<span class="token punctuation">,</span> folder<span class="token punctuation">,</span><span class="token string">“*.js”</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">concat</span><span class="token punctuation">(</span>folder <span class="token operator">+</span><span class="token string">“.js”</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>insert<span class="token punctuation">.</span><span class="token function">transform</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">contents<span class="token punctuation">,</span> file</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment">// insert a build time variable</span><span class="token keyword">var</span> build_time <span class="token operator">=</span><span class="token keyword">new</span><span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">“”</span><span class="token punctuation">;</span><span class="token keyword">return</span> contents<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">“”</span><span class="token punctuation">,</span> build_time<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span>destination_folder<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span>public_folder<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">rename</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token literal-property property">suffix</span><span class="token operator">:</span><span class="token string">“.min”</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">uglify</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">gulpIf</span><span class="token punctuation">(</span>folder <span class="token operator">==</span><span class="token string">“serviceworker”</span><span class="token punctuation">,</span> rename_serviceworker<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span>destination_folder<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>gulp<span class="token punctuation">.</span><span class="token function">dest</span><span class="token punctuation">(</span>public_folder<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span><span class="token function">notify</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token literal-property property">message</span><span class="token operator">:</span><span class="token string">“Scripts task complete”</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//.on(‘error’, handleErrors);</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>As most of the walkthroughs recommended that you version your Service Worker if you’re doing any caching, I set mine up to be auto-versioned by inserting a timestamp (lines 23-27, above) into my Service Worker header file (line 3, below):</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token string">“use strict”</span><span class="token punctuation">;</span><span class="token keyword">var</span> version <span class="token operator">=</span><span class="token string">“v:”</span><span class="token punctuation">,</span>default_avatar <span class="token operator">=</span><span class="token string">“<a href="https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mm&amp;amp;f=y">https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mm&amp;amp;f=y</a>”</span><span class="token punctuation">,</span>missing_image <span class="token operator">=</span><span class="token string">“<a href="https://i.imgur.com/oWLuFAa.gif">https://i.imgur.com/oWLuFAa.gif</a>”</span><span class="token punctuation">;</span></code></pre><p>Service Workers are still pretty new (and <a href="http://caniuse.com/#feat=serviceworkers">modestly supported</a>), but it’s definitely interesting to see what’s possible using them. <a href="https://adactio.com/journal/9844">Like Jeremy</a>, I want to do a bit more exploration into caching and how it may actually <em>increase</em> the monetary cost of accessing a website if not used properly. Like any powerful tool, we need to wield it wisely.</p><figure id="fig-2015-11-20-01"><img src="https://media.giphy.com/media/dlmcYrvalMmAw/giphy.gif" alt="Animated GIF of a guy accidentally launching a board into his helper while power sanding." loading="lazy"></figure><h2 id="making-gists-static" tabindex="-1"><a class="header-anchor" href="#making-gists-static" aria-hidden="true">#</a> Making Gists Static</h2><p>On particularly code-heavy posts (yes, like this one), I make liberal use of Gists. They’re quite useful, but <a href="https://gist.github.com/BinaryMuse/803483">the Gist plugin for Jekyll</a>, while good, still requests a script from Github in order to load the pretty printed version of the Gist. On some posts, that can mean 5 or more additional network requests, not to mention execution time for the JavaScript. It’s yet another dependency that could prohibit you from quickly getting to the content you’re looking for. Additionally, <a href="https://gds.blog.gov.uk/2013/10/21/how-many-people-are-missing-out-on-javascript-enhancement/">if JavaScript should be available, but isn’t</a>, you get nothing (since the <code>noscript</code> content is only evaluated if JavaScript support isn’t available or if a user turns it off).</p><p>With all of this in mind, I decided to revise the plugin and make it capable of downloading the JavaScript code directly. It then extracts the HTML markup that the JavaScript would be writing into the page and just embeds it directly. It also caches the result, which is handy for speeding up the build process.</p><p>You can grab <a href="https://gist.github.com/aarongustafson/b98add8f3580f6707cf5">my fork of the Gist Jekyll Plugin as, well, a Gist</a>. It’s also <a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/old-jekyll/_plugins/gist_tag.rb">in the source of this site on Github</a>.</p><h2 id="(hopefully)-a-little-faster" tabindex="-1"><a class="header-anchor" href="#(hopefully)-a-little-faster" aria-hidden="true">#</a> (Hopefully) A Little Faster</h2><p>All told, these changes have gotten the render time of this site down significantly across the board.<sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup> Even more so on browsers that support Service Workers and Resource Hints. I’ll likely continue tweaking as I go, but I wanted to share my process, code, and thoughts in case any of it might be useful to you in your own work. In the end, it’s all about creating better experiences for our users. How our sites perform is a big part of that.</p><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>Sadly I forgot to run some speed tests prior to rolling out this change and I didn’t feel like rolling back the site, so I don’t have solid numbers for you. That said, it seemed to shave nearly 2 seconds off of the load time on heavy pages like the post I mentioned. <a href="#fnref1" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p>Again, I don’t have the numbers, but I am routinely seeing <code>DOMContentLoaded</code> reached between 400-600ms with Service Worker caching in play. <a href="#fnref2" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content><amg:summary><![CDATA[User experience encompasses more than just the interface. Download speed, render performance, and the cost of accessing a site are often overlooked areas when it comes to the practice of UX, but they all affect how users experience what we build on the Web.]]></amg:summary><summary type="html"><![CDATA[<p>User experience encompasses more than just the interface. Download speed, render performance, and the cost of accessing a site are often overlooked areas when it comes to the practice of UX, but they all affect how users experience what we build on the Web.</p>]]></summary><category term="web design" /><category term="progressive enhancement" /><category term="experiments" /><category term="web development" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/playing-with-flexbox-and-quantity-queries/</id><title type="html"><![CDATA[✍🏻 Playing with Flexbox and Quantity Queries]]></title><link href="https://www.aaron-gustafson.com/notebook/playing-with-flexbox-and-quantity-queries/" rel="alternate" type="text/html" /><published>2015-03-26T18:44:24Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Ever since reading <a href="http://twitter.com/heydonworks">Haydon Pickering</a>’s <a href="http://alistapart.com/article/quantity-queries-for-css">piece on quantity queries</a>, I’ve been musing over the possibilities for layout. I decided I to play around with them a bit on this site as it’s been a while since I’ve tweaked the design. Being that I wanted to experiment, I thought this would be a fun time to tuck into <a href="http://www.w3.org/TR/css-flexbox-1/">Flexbox</a> a bit more as well.</p><p>Here’s a brief overview of the project:</p><p><strong>The Candidate</strong>: My <a href="/speaking-engagements/">speaking engagements</a> page.<br><strong>The Challenge</strong>: The “Future” list will grow and shrink as I book things, so I never know how many will be there. The “Past” list will also grow, but I am less interested in getting crazy with that.<br><strong>The Idea</strong>: A grid layout that flexes to visually highlight 1-2 upcoming future events and allows the others to flow in at the default grid size. It should be set up to handle everything from a single future event to a dozen or more.</p><figure id="2015-03-26-1" class="media-container"><p><img src="https://www.aaron-gustafson.com/i/posts/2015-03-26/the-idea-lg.jpg" alt=""></p><figcaption>My sketch of the idea.</figcaption></figure><p>The markup pattern was pretty simple. It’s just a list of events:</p><pre class="language-html" tabindex="0"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>listing listing–events<span class="token punctuation">”</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>listing__item listing__item–1 event event–future<span class="token punctuation">”</span></span><span class="token punctuation">&gt;</span></span><span class="token comment">&lt;!-- content --&gt;</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span><span class="token comment">&lt;!-- lis continue --&gt;</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span></code></pre><p>With that in place, I got to work.</p><h2 id="single-file" tabindex="-1"><a class="header-anchor" href="#single-file" aria-hidden="true">#</a> Single File</h2><p>To set the stage, I started with some basic Flexbox syntax<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup> by handling the container and the basic full-width small screen view:</p><pre class="language-css" tabindex="0"><code class="language-css"><span class="token selector">.listing–events</span><span class="token punctuation">{</span><span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span><span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token selector">.event</span><span class="token punctuation">{</span><span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span><span class="token property">padding</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span><span class="token comment">/* 20px padding <em>/</span><span class="token property">margin</span><span class="token punctuation">:</span> 0 0 1.25rem 0<span class="token punctuation">;</span><span class="token comment">/</em> 20px bottom margin <em>/</span><span class="token property">flex</span><span class="token punctuation">:</span> 0 0 100%<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>You may be wondering where all of the experimental style rules are. I use <a href="https://github.com/postcss/autoprefixer">Autoprefixer</a> to handle the experimental property inclusion/trans-compilation so I can keep my CSS clean and standards-based.</p><p>This simple CSS gives you exactly what you’d expect: a vertical list of events, separated by 20px worth of space.</p><figure id="2015-03-26-2" class="media-container"><p><img src="https://www.aaron-gustafson.com/i/posts/2015-03-26/first-pass-lg.jpg" alt=""></p></figure><h2 id="two-by-two" tabindex="-1"><a class="header-anchor" href="#two-by-two" aria-hidden="true">#</a> Two by Two</h2><p>Next up, I tackled the first breakpoint at 28.75em:</p><pre class="language-css" tabindex="0"><code class="language-css"><span class="token selector">.listing–events</span><span class="token punctuation">{</span><span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span><span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span><span class="token property">align-items</span><span class="token punctuation">:</span> stretch<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token selector">.event</span><span class="token punctuation">{</span><span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span><span class="token property">padding</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span><span class="token comment">/</em> 20px <em>/</span><span class="token property">margin</span><span class="token punctuation">:</span> 0 0 1.25rem 0<span class="token punctuation">;</span><span class="token comment">/</em> 20px v <em>/</span><span class="token property">flex</span><span class="token punctuation">:</span> 0 0 100%<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token atrule"><span class="token rule">@media</span><span class="token keyword">only</span> screen <span class="token keyword">and</span><span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 28.75em<span class="token punctuation">)</span></span><span class="token punctuation">{</span><span class="token comment">/</em> 20px gap divided by 2 events per row <em>/</span><span class="token selector">.event</span><span class="token punctuation">{</span><span class="token property">flex</span><span class="token punctuation">:</span> 0 0 <span class="token function">calc</span><span class="token punctuation">(</span>50% - 1.25rem / 2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span><span class="token comment">/</em> 20px &lt; <em>/</span><span class="token punctuation">}</span><span class="token comment">/</em> Remove left margin for row starters <em>/</span><span class="token selector">.event:nth-child(odd)</span><span class="token punctuation">{</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment">/</em> Reset margins on “future” events&amp; remove the correct one <em>/</span><span class="token selector">.event–future:nth-child(odd)</span><span class="token punctuation">{</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token selector">.event–future:nth-child(even)</span><span class="token punctuation">{</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment">/</em> Quantity Query - when more than 1,make the first span both columns  <em>/</span><span class="token selector">.event–future:nth-last-child(n + 1):first-child</span><span class="token punctuation">{</span><span class="token property">flex</span><span class="token punctuation">:</span> 0 0 100%<span class="token punctuation">;</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1.5em<span class="token punctuation">;</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>In this pass, I set up the event blocks to fill 50% of the parent container (well, 50% minus the 1.25rem gutter between them, using <a href="http://www.w3.org/TR/css3-values/#calc"><code>calc()</code></a>).<sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup> In order to make the children wrap to form rows, I set <code>flex-wrap: wrap</code> on the list (<code>.listing–events</code>). Then, to make the children all equal heights across each row, I set <code>align-items: stretch</code>. The gutter space was achieved via left margins on all events save the row starters (<code>.event:nth-child(odd)</code>).</p><p>It’s worth noting that in the full page I have two sets of event listings: one past and one future. The “event” <code>class</code> is used in all instances. The modified “future” <code>class</code> is added to events that have not happened yet.</p><p>Then I used a quantity query to select the first future event when there is more than one in the list (line 38) and set it span 100% of the parent width. To keep the gutters accurate, I also swapped where the margins were applied, adding the margin back to <code>.event–future:nth-child(odd)</code> and removing it from <code>.event–future:nth-child(even)</code>.</p><figure id="2015-03-26-3" class="media-container"><p><img src="https://www.aaron-gustafson.com/i/posts/2015-03-26/second-pass-lg.jpg" alt=""></p></figure><h2 id="three%E2%80%99s-a-crowd" tabindex="-1"><a class="header-anchor" href="#three%E2%80%99s-a-crowd" aria-hidden="true">#</a> Three’s a Crowd</h2><p>Finally, I could tackle the third and most complicated layout. Things seemed to get a little wide around 690px, so I set the breakpoint to 43.125em.</p><pre class="language-css" tabindex="0"><code class="language-css"><span class="token selector">.listing–events</span><span class="token punctuation">{</span><span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span><span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span><span class="token property">align-items</span><span class="token punctuation">:</span> stretch<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token selector">.event</span><span class="token punctuation">{</span><span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span><span class="token property">padding</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span><span class="token comment">/</em> 20px <em>/</span><span class="token property">margin</span><span class="token punctuation">:</span> 0 0 1.25rem 0<span class="token punctuation">;</span><span class="token comment">/</em> 20px v <em>/</span><span class="token property">flex</span><span class="token punctuation">:</span> 0 0 100%<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token atrule"><span class="token rule">@media</span><span class="token keyword">only</span> screen <span class="token keyword">and</span><span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 28.75em<span class="token punctuation">)</span></span><span class="token punctuation">{</span><span class="token comment">/</em> 20px gap divided by 2 events per row <em>/</span><span class="token selector">.event</span><span class="token punctuation">{</span><span class="token property">flex</span><span class="token punctuation">:</span> 0 0 <span class="token function">calc</span><span class="token punctuation">(</span>50% - 1.25rem / 2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span><span class="token comment">/</em> 20px &lt; <em>/</span><span class="token punctuation">}</span><span class="token comment">/</em> Remove left margin for row starters <em>/</span><span class="token selector">.event:nth-child(odd)</span><span class="token punctuation">{</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment">/</em> Reset margins on “future” events&amp; remove the correct one <em>/</span><span class="token selector">.event–future:nth-child(odd)</span><span class="token punctuation">{</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token selector">.event–future:nth-child(even)</span><span class="token punctuation">{</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment">/</em> Quantity Query - when more than 1,make the first span both columns  <em>/</span><span class="token selector">.event–future:nth-last-child(n + 1):first-child</span><span class="token punctuation">{</span><span class="token property">flex</span><span class="token punctuation">:</span> 0 0 100%<span class="token punctuation">;</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1.5em<span class="token punctuation">;</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token atrule"><span class="token rule">@media</span><span class="token keyword">only</span> screen <span class="token keyword">and</span><span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 43.125em<span class="token punctuation">)</span></span><span class="token punctuation">{</span><span class="token comment">/</em> 1/3 width with 20px gutter <em>/</span><span class="token selector">.event</span><span class="token punctuation">{</span><span class="token property">flex</span><span class="token punctuation">:</span> 0 0 <span class="token function">calc</span><span class="token punctuation">(</span>100% / 3 - 0.875rem<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment">/</em> Reset margins <em>/</span><span class="token selector">.event:nth-child(even),.event:nth-child(odd)</span><span class="token punctuation">{</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment">/</em> Normal Grid margin removal <em>/</span><span class="token selector">.event:nth-child(3n + 1)</span><span class="token punctuation">{</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment">/</em> Correct margins for the future events <em>/</span><span class="token selector">.event–future:nth-child(3n + 1)</span><span class="token punctuation">{</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment">/</em> Quantity Query - when more than 2,make the first 2 go 50%  <em>/</span><span class="token selector">.event–future:nth-last-child(n + 2):first-child,.event–future:nth-last-child(n + 2):first-child + .event–future</span><span class="token punctuation">{</span><span class="token property">flex</span><span class="token punctuation">:</span> 0 0 <span class="token function">calc</span><span class="token punctuation">(</span>50% - 1.25rem / 2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1.5em<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment">/</em> Quantity + nth for margin removal */</span><span class="token selector">.event–future:nth-last-child(n + 2):first-child~ .event–future:nth-child(3n)</span><span class="token punctuation">{</span><span class="token property">margin-left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>In this final pass, I used a slightly more complicated calculation to set the width of each child to 1/3 of the parent minus the gutters between them (100% / 3 - 0.875rem).</p><p>If you’re paying close attention, you might wonder why the gutter being used in the calculation is 0.875rem rather than the full 1.25rem. Well, the reason is (as best I can surmise) rounding. In order to get the flex width to fill the parent without causing a wrap, 14px (0.875rem) seemed to be the magic number.</p><p>It’s worth noting that if I allowed the event to grow (using <code>flex-grow: 1</code> or its equivalent in the shorthand), the column would fill in perfectly, but the last row would always be filled completely too. You could end up with two events in the last row being 50% wide each or a single event being 100% wide, which I didn’t want. I wanted them all to be equal width with the exception of the first 2. Setting <code>flex</code> as I did allowed that to happen.</p><p>I went ahead and reset the standard margins for events as well (on both <code>.event:nth-child(even)</code> and <code>.event:nth-child(odd)</code>). And then I turned off the margins on the first of every group of three events using <code>.event:nth-child(3n+1)</code>.</p><p>With that in place, I went to work on the future events, resetting the margins there as well. Then I used a quantity query (lines 70-71) to select the first two members when the list is more than 2 and set them to be 50% of the parent width minus the gutter.</p><p>To handle the margins in the quantity query instance, I added all the margins back (line 64) and then removed the left margins from the new row starters (line 77).</p><figure id="2015-03-26-4" class="media-container"><p><img src="https://www.aaron-gustafson.com/i/posts/2015-03-26/third-pass-lg.jpg" alt=""></p></figure><h2 id="ta-da!" tabindex="-1"><a class="header-anchor" href="#ta-da!" aria-hidden="true">#</a> Ta-da!</h2><p>And there you have it. In about 80 lines of very generously spaced and commented CSS, we’ve got a flexible grid-based Flexbox layout with visual enhancements injected via quantity queries. I’m sure I’ll continue to tinker, but I’m pretty happy with the results so far.</p><p>You can view <a href="/speaking-engagements/">the final page of course</a> (or <a href="https://www.youtube.com/watch?v=V20wuGM2tzU">watch a video of the interaction</a>), but I also created a <a href="http://codepen.io/aarongustafson/pen/VYRZBP">distilled demo on Codepen for you to play with</a>.</p><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>If you aren’t familiar with Flexbox, <a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/">CSS Tricks has a great cheatsheet</a>. <a href="#fnref1" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p>Interestingly, the support matrices for <a href="http://caniuse.com/#feat=calc"><code>calc()</code></a> and <a href="http://caniuse.com/#feat=flexbox">Flexbox</a> are pretty well aligned. <a href="#fnref2" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content><amg:summary><![CDATA[While flying across the U.S. this week, I decided to play around with Flexbox and quantity queries in hopes of coming up with something interesting.]]></amg:summary><summary type="html"><![CDATA[<p>While flying across the U.S. this week, I decided to play around with Flexbox and quantity queries in hopes of coming up with something interesting.</p>]]></summary><category term="web design" /><category term="experiments" /><category term="CSS" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/browse-flickr-with-the-stroke-of-a-pen/</id><title type="html"><![CDATA[✍🏻 Browse Flickr with the stroke of a pen]]></title><link href="https://www.aaron-gustafson.com/notebook/browse-flickr-with-the-stroke-of-a-pen/" rel="alternate" type="text/html" /><published>2006-01-11T18:10:11Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>This is perhaps the coolest (albeit experimental) way to browse <a href="http://www.flickr.com">Flickr</a>: <a href="http://labs.systemone.at/retrievr/">retrievr</a> from <a href="http://labs.systemone.at/">System One Labs</a>.</p>]]></content><amg:summary><![CDATA[This is perhaps the coolest (albeit experimental) way to browse Flickr : retrievr from System One Labs .]]></amg:summary><summary type="html"><![CDATA[<p>This is perhaps the coolest (albeit experimental) way to browse Flickr : retrievr from System One Labs .</p>]]></summary><category term="experiments" /><category term="images" /></entry></feed>