Congratulations!

[Valid RSS] This is a valid RSS feed.

Recommendations

This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

Source: http://meyerweb.com/eric/thoughts/rss2/full

  1. <?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
  2. xmlns:content="http://purl.org/rss/1.0/modules/content/"
  3. xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  4. xmlns:dc="http://purl.org/dc/elements/1.1/"
  5. xmlns:atom="http://www.w3.org/2005/Atom"
  6. xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
  7. xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
  8. >
  9.  
  10. <channel>
  11. <title>Thoughts From Eric</title>
  12. <atom:link href="http://meyerweb.com/eric/thoughts/feed/?scope=full" rel="self" type="application/rss+xml" />
  13. <link>https://meyerweb.com/eric/thoughts</link>
  14. <description>Things that Eric A. Meyer, CSS expert, writes about on his personal Web site; it&#039;s largely Web standards and Web technology, but also various bits of culture, politics, personal observations, and other miscellaneous stuff</description>
  15. <lastBuildDate>Mon, 05 Feb 2024 21:33:43 +0000</lastBuildDate>
  16. <language>en-US</language>
  17. <sy:updatePeriod>
  18. hourly </sy:updatePeriod>
  19. <sy:updateFrequency>
  20. 1 </sy:updateFrequency>
  21. <item>
  22. <title>Bookmarklet: Load All GitHub Comments</title>
  23. <link>https://meyerweb.com/eric/thoughts/2024/02/05/bookmarklet-load-all-github-comments/</link>
  24. <comments>https://meyerweb.com/eric/thoughts/2024/02/05/bookmarklet-load-all-github-comments/#comments</comments>
  25. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  26. <pubDate>Mon, 05 Feb 2024 14:49:46 +0000</pubDate>
  27. <category><![CDATA[Hacks]]></category>
  28. <category><![CDATA[JavaScript]]></category>
  29. <category><![CDATA[Tools]]></category>
  30. <category><![CDATA[Web]]></category>
  31. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5497</guid>
  32.  
  33. <description><![CDATA[A quick way to load all the comments on a GitHub issue.]]></description>
  34. <content:encoded><![CDATA[<p>What happened was, <a href="https://bkardell.com/" rel="acquaintance colleague co-worker met">Brian</a> and I were chatting about W3C GitHub issues and Brian mentioned how really long issues are annoying to search and read, because GitHub has this thing where if there are too many comments on an issue, it snips out the middle with a “Load more…” button that’s very tastefully designed and pretty easy to miss if you’re quick-scrolling to try to catch up.  The squiggle-line would be a good marker, if it weren’t so tasteful as to blend into the background in a way that makes the Baby WCAG cry.</p>
  35.  
  36. <p>And what’s worse, from this perspective, is that if the issue has been discussed to a very particular kind of death, the “Load more…”&#160;button can have more “Load more…” buttons hiding within.  So even if you know there was an interesting comment, and you remember a word or two of it, page-searching in your browser will do no good if the comment in question is buried one or more XMLHTTPRequest calls deep.</p>
  37.  
  38. <p>“I really wish GitHub had an ‘expand all comments’ button at the top or something,” Brian said (or words to that effect).</p>
  39.  
  40. <p>Well, it was a Friday afternoon and I was feeling code-hacky, so I wrote a bookmarklet.  Here it is in easy-to-save hyperlink form:</p>
  41.  
  42. <p>
  43. <a id="ghload" href="javascript:function start(){let buttons=document.querySelectorAll('button');let loaders=[];for(let i=0;i&lt;buttons.length;i+=1){if(buttons[i].textContent.trim()=='Load more%E2%80%A6'){loaders.push(buttons[i]);buttons[i].dispatchEvent(new MouseEvent('click',{view:window,bubbles:false}))}}if(loaders.length&gt;0){setTimeout(start,5000)}}setTimeout(start,500);void(20240130);">GitHub issue loader</a>
  44. </p>
  45.  
  46. <p>It waits half a second after you activate it to find all the buttons on the page (in my test runs, usually <em>six hundred</em> of them).  Then it looks through all the buttons to find the ones that have a <code>textContent</code> of “Load more…” and dispatches a click event to each one.  With that done, it waits five seconds and does it all again, waits five seconds to do it again, and so on.  Once it finds there are zero buttons with the “Load more…” <code>textContent</code>, it exits.  And, if five seconds is too quick due to slow loading times, you can always invoke the bookmarklet again should you come across a “Load more…” button.</p>
  47.  
  48. <p>If you want this ability for yourself, just drag the link above into your bookmark toolbar or bookmarks menu, and whenever you load up a mega-thread GitHub issue, fire the bookmarklet to load all the comments.  I imagine there may be cleaner ways to do this, but I was able to codeslam this in about 15 minutes using <a href="https://violentmonkey.github.io/">ViolentMonkey</a> on live GitHub pages, and it does the thing.</p>
  49.  
  50. <p>I did consider complexifying the ViolentMonkey script so that any GitHub page is scanned for the “Load more…” button, and if one is present, then a “Load all comments” button is plopped into the top of the page, but I knew that would take at least another 15 minutes and my codeslam window was closing.  Also, it would require anyone using it to run ViolentMonkey (or equivalent) all the time, whereas the bookmarlet has zero impact unless the user invokes it.  If you want to extend this into something more than it is and share your solution with the world, by all means feel free.</p>
  51.  
  52. <p>The point of all this being, if you too wish GitHub had an easy way to load all the comments without you having to search for the “Load more…” button yourself, now there’s a bookmarklet made just for you.  Enjoy!</p>
  53.  
  54. <style>#ghload {border: 1px solid; padding: 0.75em; background: #FED3; border-radius: 0.75em; display: block; margin-inline: auto; max-width: max-content; margin-block: 1.5em; box-shadow: 0.25em 0.33em 0.67em #0003; text-indent: 0;}</style>
  55. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2024/02/05/bookmarklet-load-all-github-comments/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Bookmarklet:%20Load%20All%20GitHub%20Comments%22">email Eric directly</a>.</p>]]></content:encoded>
  56. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2024/02/05/bookmarklet-load-all-github-comments/feed/</wfw:commentRss>
  57. <slash:comments>1</slash:comments>
  58. </item>
  59. <item>
  60. <title>Once Upon a Browser</title>
  61. <link>https://meyerweb.com/eric/thoughts/2024/01/02/once-upon-a-browser/</link>
  62. <comments>https://meyerweb.com/eric/thoughts/2024/01/02/once-upon-a-browser/#comments</comments>
  63. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  64. <pubDate>Tue, 02 Jan 2024 16:22:01 +0000</pubDate>
  65. <category><![CDATA[CSS]]></category>
  66. <category><![CDATA[Design]]></category>
  67. <category><![CDATA[Projects]]></category>
  68. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5475</guid>
  69.  
  70. <description><![CDATA[I am pleased to inform you that I’m back on my generative art BS again.]]></description>
  71. <content:encoded><![CDATA[<p>Once upon a time, there was <a href="https://en.wikipedia.org/wiki/Once_Upon_a_Forest">a movie called <cite>Once Upon a Forest</cite></a>.  I’ve never seen it.  In fact, the only reason I know it exists is because a few years after it was released, <a href="https://joshuadavis.com/"> Joshua Davis</a> created a site called Once Upon a Forest, which I was doing searches to find again.  The movie came up in my search results; the site, long dead, did not.  Instead, I found its original URL on <a href="https://en.wikipedia.org/wiki/Joshua_Davis_(designer)">Joshua’s Wikipedia page</a>, and the Wayback Machine coughed up snapshots of it, <a href="https://web.archive.org/web/20110902144221/http://www.once-upon-a-forest.com/"> such as this one</a>.  You can also find static shots of it on Joshua’s personal web site, if you scroll far enough.</p>
  72.  
  73. <p>That site has long stayed with me, not so much for its artistic expression (which is pleasant enough) as for how the pieces were produced.  Joshua explained in a talk that he wrote code to create <a href="https://en.wikipedia.org/wiki/Generative_art">generative art</a>, where it took visual elements and arranged them randomly, then waited for him to either save the result or hit a key to try again.  He created the elements that were used, and put constraints on how they might be arranged, but allowed randomness to determine the outcome.</p>
  74.  
  75. <p>That appealed to me deeply.  I eventually came to realize that the appeal was rooted in my love of the web, where we create content elements and visual styles and scripted behavior, and then we send our work into a medium that definitely has constraints, but something very much like the random component of generative art: viewport size, device capabilities, browser, and personal preference settings can combine in essentially infinite ways.  The user is the seed in the RNG of our work’s output.</p>
  76.  
  77. <p>Normally, we try very hard to minimize the variation our work can express.  Even when crossing from one experiential stratum to another&#160;&#x202F;—&#x2009; that is to say, when changing media breakpoints&#160;&#x202F;—&#x2009; we try to keep things visually consistent, orderly, and understandable.  That drive to be boring for the sake of user comprehension and convenience is often at war with our desire to be visually striking for the sake of expression and enticement.</p>
  78.  
  79. <p>There is a lot, and I mean a <strong>lot</strong>, of room for variability in web technologies.  We work very hard to tame it, to deny it, to shun it.  Too much, if you ask me.</p>
  80.  
  81. <p>About twelve and half years ago, I took a first stab at pushing back on that denial with <a href="https://meyerweb.com/eric/thoughts/2011/06/03/spinning-the-web/">a series posted to Flickr called “Spinning the Web”</a>, where I used CSS rotation transforms to take consistent, orderly, understandable web sites and shake them up hard.  I enjoyed the process, and a number of people enjoyed the results.</p>
  82.  
  83. <figure>
  84. <img decoding="async" src="https://meyerweb.com/once-upon-a-browser/2023-12/google.com-2023-11-25.jpg" class="border" alt="" />
  85. <figcaption>google.com, late November 2023</figcaption>
  86. </figure>
  87.  
  88. <p>In the past few months, I’ve come back to the concept for no truly clear reason and have been exploring new approaches and visual styles.  The first collection launched a few days ago: <a href="https://meyerweb.com/once-upon-a-browser/2023-12/">Spinning the Web 2023</a>, a collection of 26 web sites remixed with a combination of CSS and JS.</p>
  89.  
  90. <p>I’m announcing them now in part because this month <a href="https://genuary.art/">has been dubbed “Genuary”</a>, a month for experimenting with generative art, with daily prompts to get people generating.  I don’t know if I’ll be following any of the prompts, but we’ll see.  And now I have a place to do it.</p>
  91.  
  92. <p>You see, back in 2011, I mentioned that my working title for the “Spinning the Web” series was “Once Upon a Browser”.  That title has never left me, so I’ve decided to claim it and created <a href="https://once-upon-a-browser.com/">an umbrella site with that name</a>.  At launch, it’s sporting a design that owes quite a bit to Once Upon a Forest&#160;&#x202F;—&#x2009; albeit with its own SVG-based generative background, one I plan to mess around with whenever the mood strikes.  New works will go up there from time to time, and I plan to migrate the 2011 efforts there as well.  For now, there are pointers to the Flickr albums for the old works.</p>
  93.  
  94. <p>I said this back in 2011, and I mean it just as much in 2023: I hope you enjoy these works even half as much as I enjoyed creating them.</p>
  95.  
  96. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2024/01/02/once-upon-a-browser/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Once%20Upon%20a%20Browser%22">email Eric directly</a>.</p>]]></content:encoded>
  97. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2024/01/02/once-upon-a-browser/feed/</wfw:commentRss>
  98. <slash:comments>2</slash:comments>
  99. </item>
  100. <item>
  101. <title>2023 in (Brief) Review</title>
  102. <link>https://meyerweb.com/eric/thoughts/2023/12/31/2023-in-brief-review/</link>
  103. <comments>https://meyerweb.com/eric/thoughts/2023/12/31/2023-in-brief-review/#comments</comments>
  104. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  105. <pubDate>Mon, 01 Jan 2024 04:32:13 +0000</pubDate>
  106. <category><![CDATA[Books]]></category>
  107. <category><![CDATA[Carolyn]]></category>
  108. <category><![CDATA[Parenting]]></category>
  109. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5488</guid>
  110.  
  111. <description><![CDATA[That was the year that was… and it had three big turning points.]]></description>
  112. <content:encoded><![CDATA[<p>I haven’t generally been one to survey years as they end, but I’m going to make an exception for 2023, because there were three pretty big milestones I’d like to mark.</p>
  113.  
  114. <figure>
  115. <a href="https://meyerweb.com/eric/books/css-tdg/"><img decoding="async" src="https://meyerweb.com/eric/books/css-tdg/cover-sm.jpg" alt="" class="book cover" /></a>
  116. </figure>
  117.  
  118. <p>The first is that toward the end of May, the fifth edition of <a href="https://meyerweb.com/eric/books/css-tdg/"><cite>CSS: The Definitive Guide</cite></a> was published.  This edition weighs in at a mere 1,126 pages, and covers just about everything in CSS that was widely supported by the end of the 2022, and a bit from the first couple of months in 2023.  It’s about 5% longer by page count than <a href="https://meyerweb.com/eric/books/css-tdg/ed-fourth/">the previous edition</a>, but it has maybe 20% more material.  <a href="http://standardista.com/" rel="friend colleague met">Estelle</a> and I pulled that off by optimizing some of the older material, dropping some “intro to web” stuff that was still hanging about in the first chapter, and replacing all the appendices from the fourth edition with a single appendix that lists the URLs of useful CSS resources.  As with the previous edition, the files used to produce the figures for the book are all available online as <a href="https://meyerweb.github.io/csstdg5figs/"> a website</a> and <a href="https://github.com/meyerweb/csstdg5figs">a repository</a>.</p>
  119.  
  120. <p>The second is that Kat and I went away for a week in the summer to celebrate our 25th wedding anniversary.  As befits our inclinations, we went somewhere we’d never been but always wanted to visit, the <a href="https://www.wisdells.com/">Wisconsin Dells</a> and surrounding environs.  We got to tour <a href="https://www.caveofthemounds.com/">The Cave of the Mounds</a> (<em>wow</em>), <a href="https://www.thehouseontherock.com/">The House on the Rock</a> (<em><strong>double</strong> wow</em>), <a href="https://www.worldofdrevermor.com/gallery/">The World of Doctor Evermore</a> (<em>wowee</em>), and the Dells themselves.  We took a river tour, indulged in cheesy tourist traps, had some fantastic meals, and generally enjoyed our time together.  I did a freefall loop-de-loop waterslide <em>twice</em>, so take <em> that</em>, <a href="https://en.wikipedia.org/wiki/Action_Park">Action Park</a>.</p>
  121.  
  122. <p>The third is that toward the end of the year, Kat and I became grandparents to the beautiful, healthy baby of our daughter Carolyn.  A thing that people who know us personally know is that we love babies and kids, so it’s been a real treat to have a baby in our lives again.  It’s also been, and will continue to be, a new and deeper phase of parenthood, as we help our child learn how to be a parent to her child.  We eagerly look forward to seeing them both grow through the coming years.</p>
  123.  
  124. <p>So here’s to a year that contained some big turning points, and to the turning points of the coming year.  May we all find fulfillment and joy wherever we can.</p>
  125.  
  126. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2023/12/31/2023-in-brief-review/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%222023%20in%20(Brief)%20Review%22">email Eric directly</a>.</p>]]></content:encoded>
  127. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2023/12/31/2023-in-brief-review/feed/</wfw:commentRss>
  128. <slash:comments>1</slash:comments>
  129. </item>
  130. <item>
  131. <title>Pixelating Live with SVG</title>
  132. <link>https://meyerweb.com/eric/thoughts/2023/12/21/pixelating-live-with-svg/</link>
  133. <comments>https://meyerweb.com/eric/thoughts/2023/12/21/pixelating-live-with-svg/#comments</comments>
  134. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  135. <pubDate>Thu, 21 Dec 2023 15:35:28 +0000</pubDate>
  136. <category><![CDATA[SVG]]></category>
  137. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5455</guid>
  138.  
  139. <description><![CDATA[In which I ask for SVG filter assistance, because I can’t figure this out.]]></description>
  140. <content:encoded><![CDATA[<p>For reasons I’m not going to get into here, I want be able to pixelate web pages, or even parts of web pages, entirely from the client side.  I’m using <a href="https://violentmonkey.github.io/">ViolentMonkey</a> to inject scripts into pages, since it lets me easily open the ViolentMonkey browser-toolbar menu and toggle scripts on or off at will.</p>
  141.  
  142. <p>I’m aware I could take raster screenshots of pages and then manipulate them in an image editor.  I don’t <em>want</em> to do that, though&#160;&#x202F;—&#x2009; I want to pixelate <em>live</em>.  For reasons.</p>
  143.  
  144. <p>So far as I’m aware, my only option here is to apply SVG filters by way of CSS.  The problem I’m running into is that I can’t figure out how to construct an SVG filter that will exactly:</p>
  145.  
  146. <ul>
  147. <li>Divide the element into cells; for example, a grid of 4&#215;4 cells</li>
  148. <li>Find the average color of the pixels in each cell</li>
  149. <li>Flood-fill each cell with the average color of its pixels</li>
  150. </ul>
  151.  
  152. <p>As a way of understanding the intended result, see the following screenshot of Wikipedia’s home page, and then the corresponding pixelated version, which I generated using the Pixelate filter in <a href="https://flyingmeat.com/acorn/">Acorn</a>.</p>
  153.  
  154. <figure class="standalone">
  155. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/wikipedia-home.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/wikipedia-home.png" alt="" class="border" /></a>
  156. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/wikipedia-home-pixelated.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/wikipedia-home-pixelated.png" alt="" class="border" /></a>
  157. <figcaption>Wikipedia in the raw, and blockified.</figcaption>
  158. </figure>
  159.  
  160. <p>See how the text is rendered out?  That’s key here.</p>
  161.  
  162. <p>I found a couple of SVG pixelators in <a href="https://stackoverflow.com/questions/37451189/can-one-pixelate-images-with-an-svg-filter">a StackOverflow post</a>, but what they both appear to do is sample pixels at regularly-spaced intervals, then dilate them.  This works pretty okay for things like photographs, but it falls down hard when it comes to text, or even images of diagrams.  Text is almost entirely vanished, as shown here.</p>
  163.  
  164. <figure class="standalone">
  165. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/wikipedia-home-svged.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/wikipedia-home-svged.png" alt="" class="border" /></a>
  166. <figcaption>The text was there a minute ago, I swear it.</figcaption>
  167. </figure>
  168.  
  169. <p>I tried Gaussian blurring at the beginning of my filters in an attempt to overcome this, but that mostly washed the colors out, and didn’t make the text more obviously text, so it was a net loss.  I messed around with dilation radii, and there was no joy there.  I did find some interesting effects along the way, but none of them were what I was after.</p>
  170.  
  171. <p>I’ve been reading through various tutorials and MDN pages about SVG filters, and I’m unable to figure this out.  Though I may be wrong, I feel like the color-averaging step is the sticking point here, since it seems like <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTile"><code>&lt;feTile&gt;</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFlood"><code>&lt;feFlood&gt;</code></a> should be able to handle the first and last steps.  I’ve wondered if there’s a way to get a <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feConvolveMatrix">convolve matrix</a> to do the color-averaging part, but I have no idea&#160;&#x202F;—&#x2009; I never learned matrix math, and later-life attempts to figure it out have only gotten me as far as grasping the most general of principles.  I’ve also tried to work out if <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDisplacementMap">a displacement map</a> could be of help here, but so far as I can tell, no.  But maybe I just don’t understand them well enough to tell?</p>
  172.  
  173. <p>It also occurred to me, as I was prepared to publish this, that maybe a solution would be to use some kind of operation (a matrix, maybe?) to downsize the image and then use another operation to upsize it to the original size.  So to pixelfy a 1200&#215;1200 image into 10&#215;10 blocks, smoothly downsize it to 120&#215;120 and then nearest-neighbor it back up to 1200&#215;1200.  That feels like it would make sense as a technique, but once again, even if it does make sense I can’t figure out how to do it.  I searched for terms like <kbd>image scale transform matrix</kbd> but I either didn’t get good results, or didn’t understand them when I did.  Probably the latter, if we’re being honest.</p>
  174.  
  175. <p>So, if you have any ideas for how to make this work, I’m all ears&#160;&#x202F;—&#x2009; either here in the comments, on your own site, or as forks of <a href="https://codepen.io/meyerweb/pen/mdoboMq">the Codepen I set up for exactly that purpose</a>.  My thanks for any help!</p>
  176.  
  177. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2023/12/21/pixelating-live-with-svg/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Pixelating%20Live%20with%20SVG%22">email Eric directly</a>.</p>]]></content:encoded>
  178. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2023/12/21/pixelating-live-with-svg/feed/</wfw:commentRss>
  179. <slash:comments>18</slash:comments>
  180. </item>
  181. <item>
  182. <title>Three Decades of HTML</title>
  183. <link>https://meyerweb.com/eric/thoughts/2023/12/06/three-decades-of-html/</link>
  184. <comments>https://meyerweb.com/eric/thoughts/2023/12/06/three-decades-of-html/#comments</comments>
  185. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  186. <pubDate>Thu, 07 Dec 2023 03:40:15 +0000</pubDate>
  187. <category><![CDATA[(X)HTML]]></category>
  188. <category><![CDATA[History]]></category>
  189. <category><![CDATA[Web]]></category>
  190. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5449</guid>
  191.  
  192. <description><![CDATA[A few days ago was the 30th anniversary of the first time I wrote an HTML document.]]></description>
  193. <content:encoded><![CDATA[<p>A few days ago was the 30th anniversary of the first time I wrote an HTML document.  Back in 1993, I took a <a href="https://en.wikipedia.org/wiki/Usenet">Usenet</a> posting of the “Incomplete Mystery Science Theater 3000 Episode Guide” and marked it up.  You can see <a href="https://meyerweb.com/eric/cwru/mst3keg/mst3kf.html">the archived copy</a> here on meyerweb.  At some point, the markup got updated for reasons I don’t remember, but I can guarantee you the original had uppercase tag names and I didn’t close any paragraphs.  That’s because I was using <code>&lt;P&gt;</code> as a shorthand for <code>&lt;BR&gt;&lt;BR&gt;</code>, which was the style at the time.</p>
  194.  
  195. <p>Its last-updated date of December 3, 1993, is also the date I created it.  I was on lobby duty with the <a href="https://films.cwru.edu/">CWRU Film Society</a>, and had lugged a laptop (I think it was an Apple PowerBook of some variety, something like <a href="https://en.wikipedia.org/wiki/PowerBook_180">a 180</a>, borrowed from my workplace) and a printout of <a href="https://www.w3.org/MarkUp/draft-ietf-iiir-html-01.txt"> the HTML specification</a> (or maybe it was “<a href="http://info.cern.ch/hypertext/WWW/MarkUp/Tags.html">Tags in HTML</a>”?) along with me.</p>
  196.  
  197. <p>I spent most of that evening in the lobby of <a href="https://commons.wikimedia.org/wiki/File:Strosacker_Auditorium,_Case_Western_Reserve_University,_Cleveland,_OH_(28593864547).jpg">Strosacker Auditorium</a>, typing tags and doing find-and-replace operations in Microsoft Word, and then saving as text to a file that ended in <code>.html</code>, which was the style at the time.  By the end of the night, I had more or less what you see in the archived copy.</p>
  198.  
  199. <p>The only visual change between then and now is that a year or two later, when I put the file up in my home directory, I added the toolbars at the top and bottom of the page&#160;&#x202F;—&#x2009; toolbars I’d designed and made a layout standard as <a href="https://en.wikipedia.org/wiki/Case_Western_Reserve_University">CWRU</a>’s webmaster.  Which itself only happened because I learned HTML.</p>
  200.  
  201. <p>A couple of years ago, I was fortunate enough to be able to relate some of this story to <a href="https://en.wikipedia.org/wiki/Joel_Hodgson">Joel Hodgson</a> himself.  The story delighted him, which delighted me, because delighting someone who has been a longtime hero really is one of life’s great joys.  And the fact that I got to have that conversation, to feel that joy, is inextricably rooted in my sitting in that lobby with that laptop and that printout and that Usenet post, adding tags and saving as text and hitting reload in <a href="https://en.wikipedia.org/wiki/Mosaic_(web_browser)">Mosaic</a> to instantly see the web page take shape, thirty years ago this week.</p>
  202.  
  203. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2023/12/06/three-decades-of-html/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Three%20Decades%20of%20HTML%22">email Eric directly</a>.</p>]]></content:encoded>
  204. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2023/12/06/three-decades-of-html/feed/</wfw:commentRss>
  205. <slash:comments>4</slash:comments>
  206. </item>
  207. <item>
  208. <title>Blinded By the Light DOM</title>
  209. <link>https://meyerweb.com/eric/thoughts/2023/11/01/blinded-by-the-light-dom/</link>
  210. <comments>https://meyerweb.com/eric/thoughts/2023/11/01/blinded-by-the-light-dom/#comments</comments>
  211. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  212. <pubDate>Wed, 01 Nov 2023 18:09:17 +0000</pubDate>
  213. <category><![CDATA[DOM]]></category>
  214. <category><![CDATA[JavaScript]]></category>
  215. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5435</guid>
  216.  
  217. <description><![CDATA[I only recently had a breakthrough about using web components, and now I quite like them.  But not the shadow kind.]]></description>
  218. <content:encoded><![CDATA[<p>For a while now, Web Components (which I’m not going to capitalize again, you’re welcome) have been one of those things that pop up in the general web conversation, seem intriguing, and then fade into the background again.</p>
  219.  
  220. <p>I freely admit a lot of this experience is due to me, who is not all that thrilled with the Shadow DOM in general and all the shenanigans required to cross from the Light Side to the Dark Side in particular.  I <em>like</em> the Light DOM.  It’s designed to work together pretty well.  This whole high-fantasy-flavored Shadowlands of the DOM thing just doesn’t sit right with me.</p>
  221.  
  222. <p>If they do for you, that’s great!  Rock on with your bad self.  I say all this mostly to set the stage for why I only recently had a breakthrough using web components, and now I quite like them.  But not the shadow kind.  I’m talking about Fully Light-DOM Components here.</p>
  223.  
  224. <p>It started with a one-two punch: first, I read Jim Nielsen’s “<a href="https://blog.jim-nielsen.com/2023/web-components-icon-galleries/">Using Web Components on My Icon Galleries Websites</a>”, which I didn’t really get the first few times I read it, but I could tell there was something new (to me) there.  Very shortly thereafter, I saw Dave Rupert’s <a href="https://codepen.io/davatron5000/pen/rNoRdex?editors=1010"><code>&lt;fit-vids&gt;</code> CodePen</a>, and that’s when the Light DOM Bulb went off in my head.  You just take some normal HTML markup, wrap it with a custom element, and then write some JS to add capabilities which you can then style with regular CSS!  Everything’s of the Light Side of the Web.  No need to pierce the Vale of Shadows or whatever.</p>
  225.  
  226. <p>Kindly permit me to illustrate at great length and in some depth, using a thing I created while developing a tool for internal use at <a href="https://igalia.com/">Igalia</a> as the basis.  Suppose you have some range inputs, just some happy little slider controls on your page, ready to change some values, like this:</p>
  227.  
  228. <pre class="html"><code>&lt;label for="title-size"&gt;Title font size&lt;/label&gt;
  229. &lt;input id="title-size" type="range" min="0.5" max="4" step="0.1" value="2" /&gt;</code>
  230. </pre>
  231.  
  232. <p>The idea here is that you use the slider to change the font size of an element of some kind.  Using HTML’s built-in attributes for range inputs, I set a minimum, maximum, and initial value, the step size permitted for value changes, and an ID so a <code>&lt;label&gt;</code> can be associated with it.  Dirt-standard HTML stuff, in other words.  Given that this markup exists in the page, then, it needs to be hooked up to the thing it’s supposed to change.</p>
  233.  
  234. <p>In Ye Olden Days, you’d need to write a function to go through the entire DOM looking for these controls (maybe you’d add a specific <code>class</code> to the ones you need to find), figure out how to associate them with the element they’re supposed to affect (a title, in this case), add listeners, and so on.  It might go something like:</p>
  235.  
  236. <pre class="js"><code>let sliders = document.querySelectorAll('input[id]');
  237. for (i = 0; i &lt; sliders.length; i++) {
  238. let slider = sliders[i];
  239. // …add event listeners
  240. // …target element to control
  241. // …set behaviors, maybe call external functions
  242. // …etc., etc., etc.
  243. }</code>
  244. </pre>
  245.  
  246. <p>Then you’d have to stuff all that into a <code>window.onload</code> observer or otherwise defer the script until the document is finished loading.</p>
  247.  
  248. <p>To be clear, you can <em>absolutely</em> still do it that way.  Sometimes, it’s even the most sensible choice!  But fully-light-DOM components can make a lot of this easier, more reusable, and robust.  We can add some custom elements to the page and use those as a foundation for scripting advanced behavior.</p>
  249.  
  250. <p>Now, if you’re like me (and I know I am), you might think of converting everything into a completely bespoke element and then forcing all the things you want to do with it into its attributes, like this:</p>
  251.  
  252. <pre class="html"><code>&lt;super-slider type="range" min="0.5" max="4" step="0.1" value="2"
  253.           unit="em" target=".preview h1"&gt;
  254. Title font size
  255. &lt;/super-slider&gt;</code>
  256. </pre>
  257.  
  258. <p>Don’t do this.  If you do, then you end up having to reconstruct the HTML you want to exist out of the data you stuck on the custom element.  As in, you have to read off the <code>type</code>, <code> min</code>, <code> max</code>, <code> step</code>, and <code> value</code> attributes of the <code>&lt;super-slider&gt;</code> element, then create an <code>&lt;input&gt;</code> element and add the attributes and their values you just read off <code>&lt;super-slider&gt;</code>, create a <code>&lt;label&gt;</code> and insert the <code>&lt;super-slider&gt;</code>’s text content into the label’s text content, and why?  Why did I do this to myse&#x202F;—&#x2009; uh, I mean, why do this to yourself?</p>
  259.  
  260. <p>Do this instead:</p>
  261.  
  262. <pre class="html"><code>&lt;super-slider unit="em" target=".preview h1"&gt;
  263. &lt;label for="title-size"&gt;Title font size&lt;/label&gt;
  264. &lt;input id="title-size" type="range" min="0.5" max="4" step="0.1" value="2" /&gt;
  265. &lt;/super-slider&gt;</code>
  266. </pre>
  267.  
  268. <p>This is the pattern I got from <code>&lt;fit-vids&gt;</code>, and the moment that really broke down the barrier I’d had to understanding what makes web components so valuable.  By taking this approach, you get everything HTML gives you with the <code>&lt;label&gt;</code> and <code>&lt;input&gt;</code> elements for free, and you can add things on top of it.  It’s pure progressive enhancement.</p>
  269.  
  270. <aside>
  271. <p>Side note: keep in mind that the name <code>&lt;super-slider&gt;</code> was chosen, just as with <code>&lt;fit-vids&gt;</code>, because custom elements <strong>must</strong> (in the <a href="https://www.rfc-editor.org/rfc/rfc2119"> RFC 2119</a> sense of that word) have a hyphen within their name.  You can’t call your element <code>&lt;betterH1&gt;</code> or <code>&lt;fun_textarea&gt;</code> and have it work; you’d have to call them <code>&lt;better-H1&gt;</code> and <code>&lt;fun-textarea&gt;</code> instead.</p>
  272. <p>Also, don’t @ me about the <code>H1</code> being uppercase.  Element names can be capitalized however you want; we lowercase them out of historical inertia and unexamined habit.</p>
  273. </aside>
  274.  
  275. <p>To figure out how all this goes together, I found MDN’s page “<a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Components/Using_custom_elements">Using custom elements</a>” really quite valuable.  That’s where I internalized the reality that instead of having to scrape the DOM for custom elements and then run through a loop, I could extend HTML itself:</p>
  276.  
  277. <pre class="js"><code>class superSlider extends HTMLElement {
  278. connectedCallback() {
  279. //
  280. // the magic happens here!
  281. //
  282. }
  283. }
  284.  
  285. customElements.define("super-slider",superSlider);</code>
  286. </pre>
  287.  
  288. <p>What that last line does is tell the browser, “any <code>&lt;super-slider&gt;</code> element is of the <code>superSlider</code> JavaScript class”.  Which means, any time the browser sees <code>&lt;super-slider&gt;</code>, it does the stuff that’s defined by <code> class superSlider</code> in the script.  Which is the thing in the previous code block!  So let’s talk about how it works, with concrete examples.</p>
  289.  
  290. <p>It’s the <code>class</code> structure that holds the real power.  Inside there, <code>connectedCallback()</code> is invoked whenever a <code>&lt;super-slider&gt;</code> is connected; that is, whenever one is encountered in the page by the browser as it parses the markup, or when one is added to the page later on.  It’s an auto-startup <a href="https://developer.mozilla.org/en-US/docs/Glossary/Callback_function">callback</a>.  (What’s a callback? I’ve never truly understood that, but it turns out I don’t have to!)  So in there, I write something like:</p>
  291.  
  292. <pre class="js"><code>connectedCallback() {
  293. let targetEl = document.querySelector(this.getAttribute('target'));
  294. let unit = this.getAttribute('unit');
  295. let slider = this.querySelector('input[type="range"]');
  296. }</code>
  297. </pre>
  298.  
  299. <p>So far, all I’ve done here is:</p>
  300.  
  301. <ul>
  302. <li>Used the value of the <code>target</code> attribute on <code>&lt;super-slider&gt;</code> to find the element that the range slider should affect using a CSS-esque query.</li>
  303. <li>The <code>unit</code> attribute’s value to know what CSS unit I’ll be using later in the code.</li>
  304. <li>Grabbed the range input itself by running a <code>querySelector()</code> within the <code>&lt;super-slider&gt;</code> element.</li>
  305. </ul>
  306.  
  307. <p>With all those things defined, I can add an event listener to the range input:</p>
  308.  
  309. <pre class="js"><code>slider.addEventListener("input",(e) =&gt; {
  310. let value = slider.value + unit;
  311. targetEl.style.setProperty('font-size',value);
  312. });</code>
  313. </pre>
  314.  
  315. <p>…and really, that’s it.  Put all together:</p>
  316.  
  317. <pre class="js"><code>class superSlider extends HTMLElement {
  318. connectedCallback() {
  319. let targetEl = document.querySelector(this.getAttribute('target'));
  320. let unit = this.getAttribute('unit');
  321. let slider = this.querySelector('input[type="range"]');
  322. slider.addEventListener("input",(e) =&gt; {
  323. targetEl.style.setProperty('font-size',slider.value + unit);
  324. });
  325. }
  326. }
  327.  
  328. customElements.define("super-slider",superSlider);</code>
  329. </pre>
  330.  
  331. <p>You can see it in action with <a href="https://codepen.io/meyerweb/pen/oNmXJRX">this CodePen</a>.</p>
  332.  
  333. <div class="codepen" data-height="350" data-default-tab="js,result" data-slug-hash="oNmXJRX" data-user="meyerweb" style="height: 350px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; padding: 1em;">
  334. <pre><code> &lt;span&gt;See the Pen &lt;a href="https://codepen.io/meyerweb/pen/oNmXJRX"&gt;
  335. WebCOLD 01&lt;/a&gt; by Eric A.  Meyer (&lt;a href="https://codepen.io/meyerweb"&gt;@meyerweb&lt;/a&gt;)
  336. on &lt;a href="https://codepen.io"&gt;CodePen&lt;/a&gt;.&lt;/span&gt;</code>
  337. </pre>
  338. </div>
  339.  
  340. <script async="async" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
  341.  
  342. <p>As I said earlier, you can get to essentially the same result by running <code>document.querySelectorAll('super-slider')</code> and then looping through the collection to find all the bits and bobs and add the event listeners and so on.  In a sense, that’s what I’ve done above, except I didn’t have to do the scraping and looping and waiting until the document has loaded &#x202F;—&#x2009;&#160;using web components abstracts all of that away.  I’m also registering all the components with the browser via <code>customElements.define()</code>, so there’s that too.  Overall, somehow, it just feels cleaner.</p>
  343.  
  344. <p>One thing that sets <code>customElements.define()</code> apart from the collect-and-loop-after-page-load approach is that custom elements fire all that connection callback code on themselves whenever they’re added to the document, all nice and encapsulated.  Imagine for a moment an application where custom elements are added well after page load, perhaps as the result of user input.  No problem!  There isn’t the need to repeat the collect-and-loop code, which would likely have to have special handling to figure out which are the new elements and which already existed.  It’s incredibly handy and much easier to work with.</p>
  345.  
  346. <p>But that’s not all!  Suppose we want to add a “reset” button &#x202F;—&#x2009;&#160;a control that lets you set the slider back to its starting value.  Adding some code to the <code>connectedCallback()</code> can make that happen.  There’s probably a bunch of different ways to do this, so what follows likely isn’t the most clever or re-usable way.  It is, instead, the way that made sense to me at the time.</p>
  347.  
  348. <pre class="js"><code>let reset = slider.getAttribute('value');
  349. let resetter = document.createElement('button');
  350. resetter.textContent = '↺';
  351. resetter.setAttribute('title', reset + unit);
  352. resetter.addEventListener("click",(e) =&gt; {
  353. slider.value = reset;
  354. slider.dispatchEvent(
  355.     new MouseEvent('input', {view: window, bubbles: false})
  356. );
  357. });
  358. slider.after(resetter);</code>
  359. </pre>
  360.  
  361. <p>With that code added into the connection callback, a button gets added right after the slider, and it shows a little circle-arrow to convey the concept of resetting.  You could just as easily make its text “Reset”.  When said button is clicked or keyboard-activated (<code>"click"</code> handles both, it seems), the slider is reset to the stored initial value, and then an input event is fired at the slider so the target element’s style will also be updated.  This is probably an ugly, ugly way to do this!  I did it anyway.</p>
  362.  
  363. <div class="codepen" data-height="350" data-default-tab="js,result" data-slug-hash="jOdPdyQ" data-user="meyerweb" style="height: 350px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; padding: 1em;">
  364. <pre><code> &lt;span&gt;See the Pen &lt;a href="https://codepen.io/meyerweb/pen/jOdPdyQ"&gt;
  365. WebCOLD 02&lt;/a&gt; by Eric A.  Meyer (&lt;a href="https://codepen.io/meyerweb"&gt;@meyerweb&lt;/a&gt;)
  366. on &lt;a href="https://codepen.io"&gt;CodePen&lt;/a&gt;.&lt;/span&gt;</code>
  367. </pre>
  368. </div>
  369.  
  370. <p>Okay, so now that I can reset the value, maybe I’d also like to see what the value is, at any given moment in time?  Say, by inserting a classed <code>&lt;span&gt;</code> right after the label and making its text content show the current combination of value and unit?</p>
  371.  
  372. <pre class="js"><code>let label = this.querySelector('label');
  373. let readout = document.createElement('span');
  374. readout.classList.add('readout');
  375. readout.textContent = slider.value + unit;
  376. label.after(readout);</code>
  377. </pre>
  378.  
  379. <p>Plus, I’ll need to add the same text content update thing to the slider’s handling of <code>input</code> events:</p>
  380.  
  381. <pre class="js"><code>slider.addEventListener("input", (e) =&gt; {
  382. targetEl.style.setProperty("font-size", slider.value + unit);
  383. readout.textContent = slider.value + unit;
  384. });</code>
  385. </pre>
  386.  
  387. <p>I imagine I could have made this readout-updating thing a little more generic (less <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>, if you like) by creating some kind of getter/setter things on the JS class, which is totally possible to do, but that felt like a little much for this particular situation.  Or I could have broken the readout update into its own function, either within the class or external to it, and passed in the readout and slider and reset value and unit to cause the update.  That seems awfully clumsy, though.  Maybe figuring out how to make the span a thing that observes slider changes and updates automatically?  I dunno, just writing the same thing in two places seemed a lot easier, so that’s how I did it.</p>
  388.  
  389. <p>So, at this point, here’s the entirety of the script, with a CodePen example of the same thing immediately after.</p>
  390.  
  391. <pre class="js"><code>class superSlider extends HTMLElement {
  392. connectedCallback() {
  393. let targetEl = document.querySelector(this.getAttribute("target"));
  394. let unit = this.getAttribute("unit");
  395.  
  396. let slider = this.querySelector('input[type="range"]');
  397. slider.addEventListener("input", (e) =&gt; {
  398. targetEl.style.setProperty("font-size", slider.value + unit);
  399. readout.textContent = slider.value + unit;
  400. });
  401.  
  402. let reset = slider.getAttribute("value");
  403. let resetter = document.createElement("button");
  404. resetter.textContent = "↺";
  405. resetter.setAttribute("title", reset + unit);
  406. resetter.addEventListener("click", (e) =&gt; {
  407. slider.value = reset;
  408. slider.dispatchEvent(
  409. new MouseEvent("input", { view: window, bubbles: false })
  410. );
  411. });
  412. slider.after(resetter);
  413.  
  414. let label = this.querySelector("label");
  415. let readout = document.createElement("span");
  416. readout.classList.add("readout");
  417. readout.textContent = slider.value + unit;
  418. label.after(readout);
  419. }
  420. }
  421.  
  422. customElements.define("super-slider", superSlider);</code>
  423. </pre>
  424.  
  425. <div class="codepen" data-height="350" data-default-tab="js,result" data-slug-hash="NWoGbWX" data-user="meyerweb" style="height: 350px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; padding: 1em;">
  426. <pre><code> &lt;span&gt;See the Pen &lt;a href="https://codepen.io/meyerweb/pen/NWoGbWX"&gt;
  427. WebCOLD 03&lt;/a&gt; by Eric A.  Meyer (&lt;a href="https://codepen.io/meyerweb"&gt;@meyerweb&lt;/a&gt;)
  428. on &lt;a href="https://codepen.io"&gt;CodePen&lt;/a&gt;.&lt;/span&gt;</code>
  429. </pre>
  430. </div>
  431.  
  432. <p>Anything you can imagine JS would let you do to the HTML and CSS, you can do in here.  Add a class to the slider when it has a value other than its default value so you can style the reset button to fade in or be given a red outline, for example.</p>
  433.  
  434. <p>Or maybe do what I did, and add some structural-fix-up code.  For example, suppose I were to write:</p>
  435.  
  436. <pre class="html"><code>&lt;super-slider unit="em" target=".preview h2"&gt;
  437. &lt;label&gt;Subtitle font size&lt;/label&gt;
  438. &lt;input type="range" min="0.5" max="2.5" step="0.1" value="1.5" /&gt;
  439. &lt;/super-slider&gt;</code>
  440. </pre>
  441.  
  442. <p>In that bit of markup, I left off the <code>id</code> on the <code>&lt;input&gt;</code> and the <code>for</code> on the <code>&lt;label&gt;</code>, which means they have no structural association with each other.  (You should never do this, but sometimes it happens.)  To handle this sort of failing, I threw some code into the connection callback to detect and fix those kinds of authoring errors, because why not?  It goes a little something like this:</p>
  443.  
  444. <pre class="js"><code>if (!label.getAttribute('for') &amp;&amp; slider.getAttribute('id')) {
  445. label.setAttribute('for',slider.getAttribute('id'));
  446. }
  447. if (label.getAttribute('for') &amp;&amp; !slider.getAttribute('id')) {
  448. slider.setAttribute('id',label.getAttribute('for'));
  449. }
  450. if (!label.getAttribute('for') &amp;&amp; !slider.getAttribute('id')) {
  451. let connector = label.textContent.replace(' ','_');
  452. label.setAttribute('for',connector);
  453. slider.setAttribute('id',connector);
  454. }</code>
  455. </pre>
  456.  
  457. <p>Once more, this is probably the ugliest way to do this in JS, but also again, it works.  Now I’m making sure labels and inputs have association even when the author forgot to explicitly define it, which I count as a win.  If I were feeling particularly spicy, I’d have the code pop an alert chastising me for screwing up, so that I’d fix it instead of being a lazy author.</p>
  458.  
  459. <p>It also occurs to me, as I review this for publication, that I didn’t try to do anything in situations where both the <code>for</code> and <code>id</code> attributes are present, but their values don’t match.  That feels like something I should auto-fix, since I can’t imagine a scenario where they would need to intentionally be different.  It’s possible my imagination is lacking, of course.</p>
  460.  
  461. <p>So now, here’s all just-over-40 lines of the script that makes all this work, followed by a CodePen demonstrating it.</p>
  462.  
  463. <pre class="js"><code>class superSlider extends HTMLElement {
  464. connectedCallback() {
  465. let targetEl = document.querySelector(this.getAttribute("target"));
  466. let unit = this.getAttribute("unit");
  467.  
  468. let slider = this.querySelector('input[type="range"]');
  469. slider.addEventListener("input", (e) =&gt; {
  470. targetEl.style.setProperty("font-size", slider.value + unit);
  471. readout.textContent = slider.value + unit;
  472. });
  473.  
  474. let reset = slider.getAttribute("value");
  475. let resetter = document.createElement("button");
  476. resetter.textContent = "↺";
  477. resetter.setAttribute("title", reset + unit);
  478. resetter.addEventListener("click", (e) =&gt; {
  479. slider.value = reset;
  480. slider.dispatchEvent(
  481. new MouseEvent("input", { view: window, bubbles: false })
  482. );
  483. });
  484. slider.after(resetter);
  485.  
  486. let label = this.querySelector("label");
  487. let readout = document.createElement("span");
  488. readout.classList.add("readout");
  489. readout.textContent = slider.value + unit;
  490. label.after(readout);
  491.  
  492. if (!label.getAttribute("for") &amp;&amp; slider.getAttribute("id")) {
  493. label.setAttribute("for", slider.getAttribute("id"));
  494. }
  495. if (label.getAttribute("for") &amp;&amp; !slider.getAttribute("id")) {
  496. slider.setAttribute("id", label.getAttribute("for"));
  497. }
  498. if (!label.getAttribute("for") &amp;&amp; !slider.getAttribute("id")) {
  499. let connector = label.textContent.replace(" ", "_");
  500. label.setAttribute("for", connector);
  501. slider.setAttribute("id", connector);
  502. }
  503. }
  504. }
  505.  
  506. customElements.define("super-slider", superSlider);</code>
  507. </pre>
  508.  
  509. <div class="codepen" data-height="350" data-default-tab="result" data-slug-hash="PoVPbzK" data-user="meyerweb" style="height: 350px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; padding: 1em;">
  510. <pre><code> &lt;span&gt;See the Pen &lt;a href="https://codepen.io/meyerweb/pen/PoVPbzK"&gt;
  511. WebCOLD 04&lt;/a&gt; by Eric A.  Meyer (&lt;a href="https://codepen.io/meyerweb"&gt;@meyerweb&lt;/a&gt;)
  512. on &lt;a href="https://codepen.io"&gt;CodePen&lt;/a&gt;.&lt;/span&gt;</code>
  513. </pre>
  514. </div>
  515.  
  516. <p>There are doubtless cleaner/more elegant/more clever ways to do pretty much everything I did above, considering I’m not much better than an experienced amateur when it comes to JavaScript.  Don’t focus so much on the specifics of what I wrote, and more on the overall concepts at play.</p>
  517.  
  518. <p>I will say that I ended up using this custom element to affect more than just font sizes.  In some places I wanted to alter margins; in others, the hue angle of colors.  There are a couple of ways to do this.  The first is what I did, which is to use a bunch of CSS variables and change their values.  So the markup and relevant bits of the JS looked more like this:</p>
  519.  
  520. <pre class="html"><code>&lt;super-slider unit="em" variable="titleSize"&gt;
  521. &lt;label for="title-size"&gt;Title font size&lt;/label&gt;
  522. &lt;input id="title-size" type="range" min="0.5" max="4" step="0.1" value="2" /&gt;
  523. &lt;/super-slider&gt;</code>
  524. </pre>
  525.  
  526. <pre class="js"><code>let cssvar = this.getAttribute("variable");
  527. let section = this.closest('section');
  528.  
  529. slider.addEventListener("input", (e) =&gt; {
  530. section.style.setProperty(`--${cssvar}`, slider.value + unit);
  531. readout.textContent = slider.value + unit;
  532. });</code>
  533. </pre>
  534.  
  535. <p>The other way (that I can think of) would be to declare the target element’s selector and the property you want to alter, like this:</p>
  536.  
  537. <pre class="html"><code>&lt;super-slider unit="em" target=".preview h1" property="font-size"&gt;
  538. &lt;label for="title-size"&gt;Title font size&lt;/label&gt;
  539. &lt;input id="title-size" type="range" min="0.5" max="4" step="0.1" value="2" /&gt;
  540. &lt;/super-slider&gt;</code>
  541. </pre>
  542.  
  543. <p>I’ll leave the associated JS as an exercise for the reader.  I can think of reasons to do either of those approaches.</p>
  544.  
  545. <p><strong>But wait!  There’s more!</strong> Not more in-depth JS coding (even though we could absolutely keep going, and in the tool I built, I absolutely did), but there are some things to talk about before wrapping up.</p>
  546.  
  547. <p>First, if you need to invoke the class’s constructor for whatever reason&#x202F;—&#x2009;I’m sure there <em>are</em> reasons, whatever they may be&#x202F;—&#x2009;you have to do it with a <code>super()</code> up top.  Why?  I don’t know.  Why would you need to?  I don’t know.  If I read the intro to the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super">super page</a> correctly, I think it has something to do with class prototypes, but the rest went so far over my head the <a href="https://www.faa.gov/">FAA</a> issued a <abbr title="Notice to Airmen/Notice to Air Missions">NOTAM</abbr>.  Apparently I didn’t do anything that depends on the constructor in this article, so I didn’t bother including it.</p>
  548.  
  549. <p>Second, basically all the JS I wrote in this article went into the <code>connectedCallback()</code> structure.  This is only one of four built-in callbacks!  The others are:</p>
  550.  
  551. <ul>
  552. <li>
  553. <code>disconnectedCallback()</code>, which is fired whenever a custom element of this type is removed from the page.  This seems useful if you have things that can be added or subtracted dynamically, and you want to update other parts of the DOM when they’re subtracted.</li>
  554. <li>
  555. <code>adoptedCallback()</code>, which is (to quote MDN) “called each time the element is moved to a new document.” I have
  556. <em>no idea</em> what that means.  I understand all the words; it’s just that particular combination of them that confuses me.</li>
  557. <li>
  558. <code>attributeChangedCallback()</code>, which is fired when attributes of the custom element change.  I thought about trying to use this for my super-sliders, but in the end, nothing I was doing made sense (to me) to bubble up to the custom element just to monitor and act upon.  A use case that does suggest itself: if I allowed users to change the sizing unit, say from
  559. <code>em</code> to
  560. <code>vh</code>, I’d want to change other things, like the
  561. <code>min</code>,
  562. <code>max</code>,
  563. <code>step</code>, and default
  564. <code>value</code> attributes of the sliders.  So, since I’d have to change the value of the
  565. <code>unit</code> attribute anyway, it might make sense to use
  566. <code>attributeChangedCallback()</code> to watch for that sort of thing and then take action.  Maybe!</li>
  567. </ul>
  568.  
  569. <p>Third, I didn’t really talk about styling any of this.  Well, because all of this stuff is in the Light DOM, I don’t have to worry about Shadow Walls or whatever, I can style everything the normal way.  Here’s a part of the CSS I use in the CodePens, just to make things look a little nicer:</p>
  570.  
  571. <pre class="css"><code>super-slider {
  572. display: flex;
  573. align-items: center;
  574. margin-block: 1em;
  575. }
  576. super-slider input[type="range"] {
  577. margin-inline: 0.25em 1px;
  578. }
  579. super-slider .readout {
  580. width: 3em;
  581. margin-inline: 0.25em;
  582. padding-inline: 0.5em;
  583. border: 1px solid #0003;
  584. background: #EEE;
  585. font: 1em monospace;
  586. text-align: center;
  587. }</code>
  588. </pre>
  589.  
  590. <p>Hopefully that all makes sense, but if not, let me know in the comments and I’ll clarify.</p>
  591.  
  592. <p>A thing I didn’t do was use the <code>:defined</code> pseudo-class to style custom elements that are defined, or rather, to style those that are <em>not</em> defined.  Remember the last line of the script, where <code>customElements.define()</code> is called to define the custom elements?  Because they are defined that way, I could add some CSS like this:</p>
  593.  
  594. <pre class="css"><code>super-slider:not(:defined) {
  595. display: none;
  596. }</code>
  597. </pre>
  598.  
  599. <p>In other words, if a <code>&lt;super-slider&gt;</code> for some reason <em>isn’t</em> defined, make it and everything inside it just… go away.  Once it becomes defined, the selector will no longer match, and the <code>display: none</code> will be peeled away.  You could use <code>visibility</code> or <code>opacity</code> instead of <code>display</code>; really, it’s up to you.  Heck, you could tile red warning icons in the whole background of the custom element if it hasn’t been defined yet, just to drive the point home.</p>
  600.  
  601. <p>The beauty of all this is, you don’t have to mess with Shadow DOM selectors like <code>::part()</code> or <code>::slotted()</code>.  You can just style elements the way you always style them, whether they’re built into HTML or special hyphenated elements you made up for your situation and then, like the Boiling Isles’ most powerful witch, called into being.</p>
  602.  
  603. <p>That said, there’s a “fourth” here, which is that Shadow DOM does offer one very powerful capability that fully Light DOM custom elements lack: the ability to create a structural template with <code>&lt;slot&gt;</code> elements, and then drop your Light-DOM elements into those slots.  This slotting ability does make Shadowy web components a lot more robust and easier to share around, because as long as the slot names stay the same, the template can be changed without breaking anything.  This is a level of robustness that the approach I explored above lacks, and it’s built in.  It’s the one thing I actually do like about Shadow DOM.</p>
  604.  
  605. <p>It’s true that in a case like I’ve written about here, that’s not a huge issue: I was quickly building a web component for a single tool that I could re-use within the context of that tool.  It works fine in that context.  It isn’t portable, in the sense of being a thing I could turn into an npm package for others to use, or probably even share around my organization for other teams to use.  But then, I only put 40-50 lines worth of coding into it, and was able to rapidly iterate to create something that met my needs perfectly.  I’m a lot more inclined to take this approach in the future, when the need arises, which will be a very powerful addition to my web development toolbox.</p>
  606.  
  607. <p>I’d love to see the templating/slotting capabilities of Shadow DOM brought into the fully Light-DOM component world.  Maybe that’s what Declarative Shadow DOM is?  Or maybe not!  My eyes still go cross-glazed whenever I try to read articles about Shadow DOM, almost like a trickster demon lurking in the shadows casts a Spell of Confusion at me.</p>
  608.  
  609. <p>So there you have it: a few thousand words on my journey through coming to understand and work with these fully-Light-DOM web components, otherwise known as custom elements.  Now all they need is a catchy name, so we can draw more people to the Light Side of the Web.  If you have any ideas, please drop ’em in the comments!</p>
  610.  
  611. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2023/11/01/blinded-by-the-light-dom/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Blinded%20By%20the%20Light%20DOM%22">email Eric directly</a>.</p>]]></content:encoded>
  612. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2023/11/01/blinded-by-the-light-dom/feed/</wfw:commentRss>
  613. <slash:comments>13</slash:comments>
  614. </item>
  615. <item>
  616. <title>Mistakes Were Made</title>
  617. <link>https://meyerweb.com/eric/thoughts/2023/10/24/mistakes-were-made/</link>
  618. <comments>https://meyerweb.com/eric/thoughts/2023/10/24/mistakes-were-made/#comments</comments>
  619. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  620. <pubDate>Tue, 24 Oct 2023 20:50:27 +0000</pubDate>
  621. <category><![CDATA[Browsers]]></category>
  622. <category><![CDATA[Standards]]></category>
  623. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5430</guid>
  624.  
  625. <description><![CDATA[Late last week, I posted a tiny hack related to :has() and Firefox.  This was, in some ways, a mistake. ]]></description>
  626. <content:encoded><![CDATA[<p>Late last week, I posted a tiny hack related to <code>:has()</code> and Firefox.  This was, in some ways, a mistake.  Let me explain how.</p>
  627.  
  628. <p>Primarily, I should have filed a bug about it.  Someone else <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1860136">did so</a>, and it’s already been fixed.  This is all great in the wider view, but I shouldn’t be offloading the work of reporting browser bugs when I know perfectly well how to do that.  I got too caught up in the fun of documenting a tiny hack (my favorite kind!) to remember that, which is no excuse.</p>
  629.  
  630. <p>Not far behind that, I should have remembered that Firefox only supports <code>:has()</code> at the moment if you’ve enabled the <code>layout.css.has-selector.enabled</code> flag in <kbd>about:config</kbd>.  Although this may be the default now in Nightly builds, given that my copy of Firefox Nightly (121.0a1) shows the flag as <code> true</code> without the Boldfacing of Change.  At any rate, I should have been clear about the support status.</p>
  631.  
  632. <p>Thus, I offer my apologies to the person who did the reporting work I should have done, who also has my gratitude, and to anyone who I misled about the state of support in Firefox by not being clear about it.  Neither was my intent, but impact outweighs intent.  I’ll add a note to the top of the previous article that points here, and resolve to do better.</p>
  633.  
  634. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2023/10/24/mistakes-were-made/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Mistakes%20Were%20Made%22">email Eric directly</a>.</p>]]></content:encoded>
  635. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2023/10/24/mistakes-were-made/feed/</wfw:commentRss>
  636. <slash:comments>1</slash:comments>
  637. </item>
  638. <item>
  639. <title>Prodding Firefox to Update :has() Selection</title>
  640. <link>https://meyerweb.com/eric/thoughts/2023/10/19/ffhack-has/</link>
  641. <comments>https://meyerweb.com/eric/thoughts/2023/10/19/ffhack-has/#comments</comments>
  642. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  643. <pubDate>Thu, 19 Oct 2023 16:29:24 +0000</pubDate>
  644. <category><![CDATA[Browsers]]></category>
  645. <category><![CDATA[CSS]]></category>
  646. <category><![CDATA[DOM]]></category>
  647. <category><![CDATA[Hacks]]></category>
  648. <category><![CDATA[JavaScript]]></category>
  649. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5424</guid>
  650.  
  651. <description><![CDATA[Wanted to share a little hack I developed to make Firefox a tiny bit more capable with :has().]]></description>
  652. <content:encoded><![CDATA[<p>
  653. I’ve posted <a href="https://meyerweb.com/eric/thoughts/2023/10/24/mistakes-were-made/">a followup to this post</a> which you should read before you read this post, because you might decide there’s no need to read this one.  If not, <strong>please note</strong> that what’s documented below was a hack to overcome a bug that was <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1860136">quickly fixed</a>, in a part of CSS that wasn’t enabled in stable Firefox at the time I wrote the post.  Thus, what follows isn’t really useful, and leaves more than one wrong impression.  I apologize for this.  For a more detailed breakdown of my errors, please see <a href="https://meyerweb.com/eric/thoughts/2023/10/24/mistakes-were-made/">the followup post</a>.
  654. </p>
  655.  
  656. <hr />
  657.  
  658. <p>I’ve been doing some development recently on a tool that lets me quickly produce social-media banners for my work at <a href="https://igalia.com">Igalia</a>.  It started out using a vanilla JS script to snarfle up collections of HTML elements like all the range inputs, stick listeners and stuff on them, and then alter CSS variables when the inputs change.  Then I had a conceptual breakthrough and refactored the entire thing to use fully light-DOM web components (FLDWCs), which let me rapidly and radically increase the tool’s capabilities, and I kind of love the FLDWCs even as I struggle to figure out the best practices.</p>
  659.  
  660. <p>With luck, I’ll write about all that soon, but for today, I wanted to share a little hack I developed to make Firefox a tiny bit more capable.</p>
  661.  
  662. <p>One of the things I do in the tool’s CSS is check to see if an element (represented here by a <code>&lt;div&gt;</code> for simplicity’s sake) has an image whose <code>src</code> attribute is a <code>base64</code> string instead of a URI, and when it is, add some generated content. (It makes sense in context.  Or at least it makes sense to me.) The CSS rule looks very much like this:</p>
  663.  
  664. <pre class="css"><code>div:has(img[src*=";data64,"])::before {
  665. […generated content styles go here…]
  666. }</code>
  667. </pre>
  668.  
  669. <p>This works fine in WebKit and Chromium.  Firefox, at least as of the day I’m writing this, often fails to notice the change, which means the selector doesn’t match, even in the Nightly builds, and so the generated content isn’t generated.  It has problems correlating DOM updates and <code>:has()</code>, is what it comes down to.</p>
  670.  
  671. <p>There is a way to prod it into awareness, though!  What I found during my development was that if I clicked or tabbed into a <code>contenteditable</code> element, the <code>:has()</code> would suddenly match and the generated content would appear.  The editable element didn’t even have to be a child of the <code>div</code> bearing the <code>:has()</code>, which seemed weird to me for no distinct reason, but it made me think that maybe any content editing would work.</p>
  672.  
  673. <p>I tried adding <code>contenteditable</code> to a nearby element and then immediately removing it via JS, and that didn’t work.  But then I added a tiny delay to removing the <code>contenteditable</code>, and that worked!  I feel like I might have seen a similar tactic proposed by someone on social media or a blog or something, but if so, I can’t find it now, so my apologies if I ganked your idea without attribution.</p>
  674.  
  675. <p>My one concern was that if I wasn’t careful, I might accidentally pick an element that was <em>supposed</em> to be editable, and then remove the editing state it’s supposed to have.  Instead of doing detection of the attribute during selection, I asked myself, “Self, what’s an element that is assured to be present but almost certainly not ever set to be editable?”</p>
  676.  
  677. <p>Well, there will always be a root element.  Usually that will be <code>&lt;html&gt;</code> but you never know, maybe it will be something else, what with web components and all that.  Or you could be styling your RSS feed, which is in fact <a href="https://stackoverflow.com/questions/118685/how-can-i-apply-my-css-stylesheet-to-an-rss-feed">a thing one can do</a>.  At any rate, where I landed was to add the following right after the part of my script where I set an image’s <code> src</code> to use a <code>base64</code> URI:</p>
  678.  
  679. <pre class="js"><code>let ffHack = document.querySelector(':root');
  680. ffHack.setAttribute('contenteditable','true');
  681. setTimeout(function(){
  682. ffHack.removeAttribute('contenteditable');
  683. },7);</code>
  684. </pre>
  685.  
  686. <p>Literally all this does is grab the page’s root element, set it to be <code>contenteditable</code>, and then seven milliseconds later, remove the <code> contenteditable</code>.  That’s about a millisecond less than the lifetime of a rendering frame at 120fps, so ideally, the browser won’t draw a frame where the root element is actually editable… or, if there is such a frame, it will be replaced by the next frame so quickly that the odds of accidentally editing the root are very, very, very small.</p>
  687.  
  688. <p>At the moment, I’m not doing any browser sniffing to figure out if the hack needs to be applied, so every browser gets to do this shuffle on Firefox’s behalf.  Lazy, I suppose, but I’m going to wave my hands and intone “browsers are very fast now” while studiously ignoring all the inner voices complaining about inefficiency and inelegance.  I feel like using this hack means it’s too late for all those concerns anyway.</p>
  689.  
  690. <p>I don’t know how many people out there will need to prod Firefox like this, but for however many there are, I hope this helps.  And if you have an even better approach, please let us know in the comments!</p>
  691.  
  692. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2023/10/19/ffhack-has/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Prodding%20Firefox%20to%20Update%20:has()%20Selection%22">email Eric directly</a>.</p>]]></content:encoded>
  693. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2023/10/19/ffhack-has/feed/</wfw:commentRss>
  694. <slash:comments>4</slash:comments>
  695. </item>
  696. <item>
  697. <title>An Anchored Navbar Solution</title>
  698. <link>https://meyerweb.com/eric/thoughts/2023/10/05/an-anchored-navbar-solution/</link>
  699. <comments>https://meyerweb.com/eric/thoughts/2023/10/05/an-anchored-navbar-solution/#comments</comments>
  700. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  701. <pubDate>Thu, 05 Oct 2023 13:23:58 +0000</pubDate>
  702. <category><![CDATA[CSS]]></category>
  703. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5386</guid>
  704.  
  705. <description><![CDATA[Revisiting a CSS trick I wrote about in 2022 and re-doing it using a new CSS feature.]]></description>
  706. <content:encoded><![CDATA[<p>Not quite a year ago, I published <a href="https://meyerweb.com/eric/thoughts/2022/10/19/a-dashing-navbar-solution/">an exploration of how I used layered backgrounds</a> to create the appearance of a single bent line that connected one edge of the design to whichever navbar link corresponded to the current page.  It was fairly creative, if I do say so myself, but even then I knew&#160;&#x202F;—&#x2009; and said explicitly!&#160;&#x202F;—&#x2009; that it was a hack, and that I really wanted to use anchor positioning to do it cleanly.</p>
  707.  
  708. <p>Now that anchor positioning is supported behind a developer flag in Chrome, we can experiment with it, as I did in the recent post “<a href="https://meyerweb.com/eric/thoughts/2023/09/12/nuclear-anchored-sidenotes/">Nuclear Anchored Sidenotes</a>”.  Well, today, I’m back on my anchor BS with a return to that dashed navbar connector as seen on <a href="https://wpewebkit.org"> wpewebkit.org</a>, and how it can be done more cleanly and simply, just as I’d hoped last year.</p>
  709.  
  710. <p>First, let’s look at the thing we’re trying to recreate.</p>
  711.  
  712. <figure class="standalone">
  713. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-00.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-00.png" alt="" /></a>
  714. <figcaption>The connecting line, as done with a bunch of forcibly-sized and creatively overlapped background gradient images.</figcaption>
  715. </figure>
  716.  
  717. <p>To understand the ground on which we stand, let’s make a quick perusal of the simple HTML structure at play here.  At least, the relevant parts of it, with some bits elided by ellipses for clarity.</p>
  718.  
  719. <pre class="html">
  720. <code>&lt;nav class="global"&gt;
  721. &lt;div&gt;
  722. &lt;a href="…"&gt;&lt;img src="…" alt="WPE"&gt;&lt;/a&gt;
  723. &lt;ul&gt;…&lt;/ul&gt;
  724. &lt;/div&gt;
  725. &lt;/nav&gt;</code>
  726. </pre>
  727.  
  728. <p>Inside that (unclassed! on purpose!) <code>&lt;ul&gt;</code>, there are a number of list items, each of which holds a hyperlink.  Whichever list item contains the hyperlink that corresponds to the current page gets a class of <code> currentPage</code>, because class naming is a deep and mysterious art.</p>
  729.  
  730. <p>To that HTML structure, the following bits of CSS trickery were applied in the work I did last year, brought together in this code block for the sake of brevity (note this is the old thing, not the new anchoring hotness):</p>
  731.  
  732. <pre class="css">
  733. <code>nav.global div {
  734. display: flex;
  735. justify-content: space-between;
  736. gap: 1em;
  737. max-width: var(--mainColMax);
  738. margin: 0 auto;
  739. height: 100%;
  740. background: var(--dashH);
  741. }
  742.  
  743. @media (min-width: 720px) {
  744. nav.global ul li.currentPage::before {
  745. content: '';
  746. position: absolute;
  747. z-index: 1;
  748. top: 50%;
  749. bottom: 0;
  750. left: 50%;
  751. right: 0;
  752. background:
  753. var(--dashV),
  754. linear-gradient(0deg, #FFFF 2px, transparent 2px)
  755. ;
  756. background-size: 1px 0.5em, auto;
  757. }
  758. nav.global ul li.currentPage {
  759. position: relative;
  760. }
  761. nav.global ul li.currentPage a {
  762. position: relative;
  763. z-index: 2;
  764. padding: 0;
  765. padding-block: 0.25em;
  766. margin: 1em;
  767. background: var(--dashH);
  768. background-size: 0.5em 1px;
  769. background-position: 50% 100%;
  770. background-color: #FFF;
  771. color: inherit;
  772. }
  773. }</code>
  774. </pre>
  775.  
  776. <p>If you’re wondering what the heck is going on there, please feel free to read <a href="https://meyerweb.com/eric/thoughts/2022/10/19/a-dashing-navbar-solution/">the post from last year</a>.  You can even go read it now, if you want, even though I’m about to flip most of that apple cart and stomp on the apples to make ground cider.  Your life is your own; steer it as best suits you.</p>
  777.  
  778. <p>Anyway, here are the bits I’m tearing out to make way for an anchor-positioning solution.  The positioning-edge properties (<code>top</code>, etc.) removed from the second rule will return shortly in a more logical form.</p>
  779.  
  780. <pre class="css">
  781. <code>nav.global div {
  782. display: flex;
  783. justify-content: space-between;
  784. gap: 1em;
  785. max-width: var(--mainColMax);
  786. margin: 0 auto;
  787. height: 100%;
  788. <del>   background: var(--dashH);</del>
  789. }
  790. @media (min-width: 720px) {
  791. nav.global ul li.currentPage::before {
  792. content: '';
  793. position: absolute;
  794. <del>    z-index: 1;
  795. top: 50%;
  796. bottom: 0;
  797. left: 50%;
  798. right: 0;
  799. background:
  800. var(--dashV),
  801. linear-gradient(0deg, #FFFF 2px, transparent 2px)
  802. ;
  803. background-size: 1px 0.5em, auto;</del>
  804. }
  805. <del>   nav.global ul li.currentPage {
  806. position: relative;
  807. }</del>
  808. nav.global ul li.currentPage a {
  809. <del>    position: relative;
  810. z-index: 2;</del>
  811. padding: 0;
  812. padding-block: 0.25em;
  813. margin: 1em;
  814. <del>    background: var(--dashH);
  815. background-size: 0.5em 1px;
  816. background-position: 50% 100%;
  817. background-color: #FFF;</del>
  818. color: inherit;
  819. }
  820. }</code>
  821. </pre>
  822.  
  823. <p>That pulls out not only the positioning edge properties, but also the background dash variables and related properties.  And a whole rule to relatively position the <code>currentPage</code> list item, gone.  The resulting lack of any connecting line being drawn is perhaps predictable, but here it is anyway.</p>
  824.  
  825. <figure class="standalone">
  826. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-01.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-01.png" /></a>
  827. <figcaption>The connecting line disappears as all its support structures and party tricks are swept away.</figcaption>
  828. </figure>
  829.  
  830. <p>With the field cleared of last year’s detritus, let’s get ready to anchor!</p>
  831.  
  832. <p>Step one is to add in positioning edges, for which I’ll use logical positioning properties instead of the old physical properties.  Along with those, a negative Z index to drop the generated decorator (that is, a decorative component based on generated content, which is what this <code>::before</code> rule is creating) behind the entire set of links, dashed borders along the block and inline ends of the generated decorator, and a light-red background color so we can see the decorator’s placement more clearly.</p>
  833.  
  834. <pre class="css">
  835. <code> nav.global ul li.currentPage::before {
  836. content: '';
  837. position: absolute;
  838. <ins>    inset-block-start: 0;
  839. inset-block-end: 0;
  840. inset-inline-start: 0;
  841. inset-block-end: 0;
  842. z-index: -1;
  843. border: 1px dashed;
  844. border-block-width: 0 1px;
  845. border-inline-width: 0 1px;
  846. background-color: #FCC;</ins>
  847. }</code>
  848. </pre>
  849.  
  850. <p>I’ll also give the <code>&lt;a&gt;</code> element inside the <code>currentPage</code> list item a dashed border along its block-end edge, since the design calls for one.</p>
  851.  
  852. <pre class="css">
  853. <code> nav.global ul li.currentPage a {
  854. padding: 0;
  855. padding-block: 0.25em;
  856. margin: 1em;
  857. color: inherit;
  858. <ins>    border-block-end: 1px dashed;</ins>
  859. }</code>
  860. </pre>
  861.  
  862. <p>And those changes give us the result shown here.</p>
  863.  
  864. <figure class="standalone">
  865. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-02.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-02.png" alt="" /></a>
  866. <figcaption>The generated decorator, decorating the entirety of its containing block.</figcaption>
  867. </figure>
  868.  
  869. <p>Well, I <em>did</em> set all the positioning edge values to be <code>0</code>, so it makes sense that the generated decorator fills out the relatively-positioned <code>&lt;div&gt;</code> acting as its containing block.  Time to fix that.</p>
  870.  
  871. <p>What we need to do give the top and right&#160;&#x202F;—&#x2009; excuse me, the block-start and inline-end&#160;&#x202F;—&#x2009; edges of the decorator a positioning anchor.  Since the thing we want to connect the decorator’s visible edges to is the <code>&lt;a&gt;</code> inside the <code>currentPage</code> list item, I’ll make it the positioning anchor:</p>
  872.  
  873. <pre class="css">
  874. <code> nav.global ul li.currentPage a {
  875. padding: 0;
  876. padding-block: 0.25em;
  877. margin: 1em;
  878. color: inherit;
  879. border-block-end: 1px dashed;
  880. <ins>    anchor-name: --currentPageLink;</ins>
  881. }</code>
  882. </pre>
  883.  
  884. <p>Yes, you’re reading that correctly: I made an anchor be an anchor.</p>
  885.  
  886. <p>(That’s an HTML anchor element being designated as a CSS positioning anchor, to be clear.  Sorry to pedantically explain the joke and thus ruin it, but I fear confusion more than banality.)</p>
  887.  
  888. <p>Now that we have a positioning anchor, the first thing to do, because it’s more clear to do it in this order, is to pin the inline-end edge of the generated decorator to its anchor.  Specifically, to pin it to the <em>center</em> of the anchor, since that’s what the design calls for.</p>
  889.  
  890. <pre class="css">
  891. <code> nav.global ul li.currentPage::before {
  892. content: '';
  893. position: absolute;
  894. inset-block-start: 0;
  895. inset-block-end: 0;
  896. inset-inline-start: 0;
  897. inset-inline-end: <ins>anchor(--currentPageLink center);</ins>
  898. z-index: -1;
  899. border: 1px dashed;
  900. border-block-width: 0 1px;
  901. border-inline-width: 0 1px;
  902. background-color: #FCC;
  903. }</code>
  904. </pre>
  905.  
  906. <p>Because this <code>anchor()</code> function is being used with an inline inset property, the <code>center</code> here refers to the inline center of the referenced anchor (in both the HTML and CSS senses of that word) <code>--currentPageLink</code>, which in this particular case is its horizontal center.  That gives us the following.</p>
  907.  
  908. <figure class="standalone">
  909. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-03a.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-03a.png" alt="" /></a>
  910. <figcaption>The generated decorator with its inline-end edge aligned with the inline center of the anchoring anchor.</figcaption>
  911. </figure>
  912.  
  913. <p>The next step is to pin the top block edge of the generated decorator with respect to its positioning anchor.  Since we want the line to come up and touch the block-end edge of the anchor, the <code>end</code> keyword is used to pin to the block end of the anchor (in this situation, its bottom edge).</p>
  914.  
  915. <pre class="css">
  916. <code> nav.global ul li.currentPage::before {
  917. content: '';
  918. position: absolute;
  919. inset-block-start: <ins>anchor(--currentPageLink end);</ins>
  920. inset-block-end: 0;
  921. inset-inline-start: 0;
  922. inset-inline-end: anchor(--currentPageLink center);
  923. z-index: -1;
  924. border: 1px dashed;
  925. border-block-width: 0 1px;
  926. border-inline-width: 0 1px;
  927. background-color: #FCC;
  928. }</code>
  929. </pre>
  930.  
  931. <p>Since the inset property in this case is block-related, the <code>end</code> keyword here means the block end of the anchor (again, in both senses).  And thus, the job is done, except for removing the light-red diagnostic background.</p>
  932.  
  933. <figure class="standalone">
  934. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-03b.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-03b.png" alt="" /></a>
  935. <figcaption>The generated decorator with its block-start edge aligned with the block-end edge of the anchoring anchor.</figcaption>
  936. </figure>
  937.  
  938. <p>Once that red background is taken out, we end up with the following rules inside the media query:</p>
  939.  
  940. <pre class="css">
  941. <code> nav.global ul li.currentPage::before {
  942. content: '';
  943. position: absolute;
  944. inset-block-start: anchor(--currentPageLink bottom);
  945. inset-block-end: 0;
  946. inset-inline-start: 0;
  947. inset-inline-end: anchor(--currentPageLink center);
  948. z-index: -1;
  949. border: 1px dashed;
  950. border-block-width: 0 1px;
  951. border-inline-width: 0 1px;
  952. }
  953. nav.global ul li.currentPage a {
  954. padding: 0;
  955. padding-block: 0.25em;
  956. margin: 1em;
  957. color: inherit;
  958. border-block-end: 1px dashed;
  959. anchor-name: --currentPageLink;
  960. }</code>
  961. </pre>
  962.  
  963. <p>The inline-start and block-end edges of the generated decorator still have position values of <code>0</code>, so they stick to the edges of the containing block (the <code>&lt;div&gt;</code>).  The block-start and inline-end edges have values that are set with respect to their anchor.  That’s it, done and dusted.</p>
  964.  
  965. <figure class="standalone">
  966. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-04.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-04.png" alt="" /></a>
  967. <figcaption>The connecting line is restored, but is now a lot easier to manage from the CSS side.</figcaption>
  968. </figure>
  969.  
  970. <p>…okay, okay, there are a couple more things to talk about before we go.</p>
  971.  
  972. <p>First, the dashed borders I used here don’t look fully consistent with the other dashed “borders” in the design.  I used actual borders for the CSS in this article because they’re fairly simple, as CSS goes, allowing me to focus on the topic at hand.  To make these borders fully consistent with the rest of the design, I have two choices:</p>
  973.  
  974. <ol>
  975. <li>Remove the borders from the generated decorator and put the background-trick “borders” back into it.  This would be relatively straightforward to do, at the cost of inflating the rules a little bit with background sizing and positioning and all that.</li>
  976. <li>Convert all the other background-trick “borders” to be actual dashed borders.  This would also be pretty straightforward, and would reduce the overall complexity of the CSS.</li>
  977. </ol>
  978.  
  979. <p>On balance, I’d probably go with the first option, because dashed borders still aren’t fully visually consistent from browser to browser, and people get cranky about those kinds of inconsistencies.  Background gradient tricks give you more control in exchange for you writing more declarations.  Still, either choice is completely defensible.</p>
  980.  
  981. <p>Second, you might be wondering if that <code>&lt;div&gt;</code> was even necessary.  Not technically, no.  At first, I kept using it because it was already there, and removing it seemed like it would require refactoring a bunch of other code not directly related to this post.  So I didn’t.</p>
  982.  
  983. <p>But it tasked me.  It <em>tasked</em> me.  So I decided to take it out after all, and see what I’d have to do to make it work.  Once I realized doing this illuminated an important restriction on what you can do with anchor positioning, I decided to explore it here.</p>
  984.  
  985. <p>As a reminder, here’s the HTML as it stood before I started removing bits:</p>
  986.  
  987. <pre class="html">
  988. <code>&lt;nav class="global"&gt;
  989. &lt;div&gt;
  990. &lt;a href="…"&gt;&lt;img src="…" alt="WPE"&gt;&lt;/a&gt;
  991. &lt;ul&gt;…&lt;/ul&gt;
  992. &lt;/div&gt;
  993. &lt;/nav&gt;</code>
  994. </pre>
  995.  
  996. <p>Originally, the <code>&lt;div&gt;</code> was put there to provide a layout container for the logo and navbar links, so they’d be laid out to line up with the right and left sides of the page content.  The <code>&lt;nav&gt;</code> was allowed to span the entire page, and the <code>&lt;div&gt;</code> was set to the same width as the content, with <code>auto</code> side margins to center it.</p>
  997.  
  998. <p>So, after pulling out the <code>&lt;div&gt;</code>, I needed an anchor for the navbar to size itself against.  I couldn’t use the <code>&lt;main&gt;</code> element that follows the <code>&lt;nav&gt;</code> and contains the page content, because it’s a page-spanning Grid container.  Just inside it, though, are <code>&lt;section&gt;</code> elements, and some (not all!) of them are the requisite width.  So I added:</p>
  999.  
  1000. <pre class="css">
  1001. <code>main > section:not(.full-width) {
  1002. anchor-name: --mainCol;
  1003. }</code>
  1004. </pre>
  1005.  
  1006. <p>The <code>full-width</code> class makes some sections page-spanning, so I needed to avoid those; thus the negative selection there.  Now I could reference the <code>&lt;nav&gt;</code>’s edges against the named anchor I just defined.  (Which is probably actually multiple anchors, but they all have the same width, so it comes to the same thing.)  So I dropped those anchor references into the CSS:</p>
  1007.  
  1008. <pre class="css">
  1009. <code>nav.global {
  1010. display: flex;
  1011. justify-content: space-between;
  1012. height: 5rem;
  1013. gap: 1em;
  1014. position: fixed;
  1015. top: 0;
  1016. <ins> inset-inline-start: anchor(--mainCol left);
  1017. inset-inline-end: anchor(--mainCol right);</ins>
  1018. z-index: 12345;
  1019. backdrop-filter: blur(10px);
  1020. background: hsla(0deg,0%,100%,0.9);
  1021. }</code>
  1022. </pre>
  1023.  
  1024. <p>And that worked!  The inline start and end edges, which in this case are the left and right edges, lined up with the edges of the content column.</p>
  1025.  
  1026. <figure class="standalone">
  1027. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-05.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-05.png" alt="" /></a>
  1028. <figcaption>Positioning the <code>&lt;nav&gt;</code> with respect to the anchoring section(s).</figcaption>
  1029. </figure>
  1030.  
  1031. <p>…except it <em>didn’t</em> work on any page that had any content that overflowed the main column, which is most of them.</p>
  1032.  
  1033. <figure class="standalone">
  1034. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-05.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-06.png" alt="" /></a>
  1035. <figcaption>See, this is why I embedded a <code>&lt;div&gt;</code> inside the <code>&lt;nav&gt;</code> in the first place.</figcaption>
  1036. </figure>
  1037.  
  1038. <p>But wait.  Why couldn’t I just position the logo and list of navigation links against the <code>--mainCol</code> anchor?  Because in anchored positioning, just like nearly every other form of positioning, containing blocks are barriers.  Recall that the <code>&lt;nav&gt;</code> is a fixed-position box, so it can stick to the top of the viewport.  That means any elements inside it can only be positioned with respect to anchors that also have the <code>&lt;nav&gt;</code> as their containing block.</p>
  1039.  
  1040. <p>That’s fine for the generated decorator, since it and the <code>currentPageLink</code> anchor both have the <code>&lt;nav&gt;</code> as their containing block.  To try to align the logo and navlinks, though, I can’t look outside the <code>&lt;nav&gt;</code> at anything else, and that includes the sections inside the <code>&lt;main&gt;</code> element, because the <code>&lt;nav&gt;</code> is not their containing block.  The <code>&lt;nav&gt;</code> element itself, on the other hand, shares a containing block with those sections: the initial containing block.  So I can anchor the <code>&lt;nav&gt;</code> itself to <code>--mainCol</code>.</p>
  1041.  
  1042. <p>I fiddled with various hacks to extend the background of the <code>&lt;nav&gt;</code> without shifting its content edges, padding and negative margins and stuff like that, but in end, I fell back on a border-image hack, which required I remove the background.</p>
  1043.  
  1044. <pre class="css">
  1045. <code>nav.global {
  1046. display: flex;
  1047. justify-content: space-between;
  1048. height: 5rem;
  1049. gap: 1em;
  1050. position: fixed;
  1051. top: 0;
  1052. inset-inline-start: anchor(--mainCol left);
  1053. inset-inline-end: anchor(--mainCol right)
  1054. z-index: 12345;
  1055. backdrop-filter: blur(10px);
  1056. <del> background: hsla(0deg,0%,100%,0.9);</del>
  1057. <ins> border-image-outset: 0 100vw;
  1058. border-image-slice: 0 fill;
  1059. border-image-width: 0;
  1060. border-image-repeat: stretch;
  1061. border-image-source: linear-gradient(0deg,hsla(0deg,0%,100%,0.9),hsla(0deg,0%,100%,0.9));</ins>
  1062. }</code>
  1063. </pre>
  1064.  
  1065. <p>And that solved the visual problem.</p>
  1066.  
  1067. <figure class="standalone">
  1068. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-05.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchor-07.png" alt="" /></a>
  1069. <figcaption>The appearance of a full-width navbar, although it’s mostly border image fakery.</figcaption>
  1070. </figure>
  1071.  
  1072. <p>Was it worth it?  I have mixed feelings about that.  On the one hand, putting all of the layout hackery into the CSS and removing it all from the HTML feels like the proper approach.  On the other hand, it’s one measly <code>&lt;div&gt;</code>, and taking that approach means better support for older browsers.  On the gripping hand, if I’m going to use anchor positioning, older browsers are already being left out of the fun.  So I probably wouldn’t have even gone down this road, except it was a useful example of how anchor positioning can be stifled.</p>
  1073.  
  1074. <p>At any rate, there you have it, another way to use anchor positioning to create previously difficult design effects with relative ease.  Just remember that all this is still in the realm of experiments, and production use will be limited to progressive enhancements until this comes out from behind the developer flags and more browsers add support.  That makes now a good time to play around, get familiar with the technology, that sort of thing.  Have fun with it!</p>
  1075.  
  1076. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2023/10/05/an-anchored-navbar-solution/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22An%20Anchored%20Navbar%20Solution%22">email Eric directly</a>.</p>]]></content:encoded>
  1077. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2023/10/05/an-anchored-navbar-solution/feed/</wfw:commentRss>
  1078. <slash:comments>1</slash:comments>
  1079. </item>
  1080. <item>
  1081. <title>Nuclear Anchored Sidenotes</title>
  1082. <link>https://meyerweb.com/eric/thoughts/2023/09/12/nuclear-anchored-sidenotes/</link>
  1083. <comments>https://meyerweb.com/eric/thoughts/2023/09/12/nuclear-anchored-sidenotes/#comments</comments>
  1084. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  1085. <pubDate>Tue, 12 Sep 2023 15:16:21 +0000</pubDate>
  1086. <category><![CDATA[CSS]]></category>
  1087. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5350</guid>
  1088.  
  1089. <description><![CDATA[Sidenotes are hard.  Anchor positioning makes them easy.]]></description>
  1090. <content:encoded><![CDATA[<p>Exactly one year ago today, which I swear is a coincidence I only noticed as I prepared to publish this, I posted <a href="https://meyerweb.com/eric/thoughts/2022/09/12/nuclear-footnotes/">an article on how I coded the footnotes</a> for <a href="https://atomicarchive.com/resources/documents/effects/glasstone-dolan/"><cite>The Effects of Nuclear Weapons</cite></a>.  In that piece, I mentioned that the footnotes I ended up using weren’t what I had hoped to create when the project first started.  As I said in the original post:</p>
  1091.  
  1092. <blockquote>
  1093. <p>Originally I had thought about putting footnotes off to one side in desktop views, such as in the right-hand grid gutter.  After playing with some rough prototypes, I realized this wasn’t going to go the way I wanted it to…</p>
  1094. </blockquote>
  1095.  
  1096. <p>I came back to this in my post “CSS Wish List 2023”, when I <a href="https://meyerweb.com/eric/thoughts/2023/02/08/css-wish-list-2023/#anchored-positioning">talked about anchor(ed) positioning</a>.  The ideal, which wasn’t really possible a year ago without a bunch of scripting, was to have the footnotes arranged structurally as endnotes, which we did, but in a way that I could place the notes as sidenotes, next to the footnote reference, when there was enough space to show them.</p>
  1097.  
  1098. <p>As it happens, that’s still not really possible without a lot of scripting today, unless you have:</p>
  1099.  
  1100. <ol type="1">
  1101. <li>A recent (as of late 2023) version of Chrome</li>
  1102. <li>With the “Experimental web features”&#160;flag enabled</li>
  1103. </ol>
  1104.  
  1105. <p>With those things in place, you get experimental support for <a href="https://www.w3.org/TR/css-anchor-position-1/">CSS anchor positioning</a>, which lets you absolutely position an element in relation to any other element, anywhere in the DOM, essentially regardless of their markup relationship to each other, as long as they <a href="https://www.w3.org/TR/2023/WD-css-anchor-position-1-20230629/#acceptable-anchor-element">conform to a short set of constraints</a> related to their containing blocks.  You could reveal an embedded stylesheet and then position it next to the bit of markup it styles!</p>
  1106.  
  1107. <h3 id="anchoring-sidenotes">Anchoring Sidenotes</h3>
  1108.  
  1109. <p>More relevantly to <cite>The Effects of Nuclear Weapons</cite>, I can enhance the desktop browsing experience by turning the popup footnotes into <a href="https://edwardtufte.github.io/tufte-css/#sidenotes"> Tufte-style static sidenotes</a>.  So, for example, I can style the list items that contain the footnotes like this:</p>
  1110.  
  1111. <pre class="css">
  1112. <code>.endnotes li {
  1113. position: absolute;
  1114. top: anchor(top);
  1115. bottom: auto;
  1116. left: calc(anchor(--main right) + 0.5em);
  1117. max-width: 23em;
  1118. }</code>
  1119. </pre>
  1120.  
  1121. <figure class="standalone">
  1122. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchored-sidenotes-01.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchored-sidenotes-01.png" alt="" /></a>
  1123. <figcaption>A sidenote next to the main text column, with its number aligned with the referencing number found in the main text column.</figcaption>
  1124. </figure>
  1125.  
  1126. <p>Let me break that down.  The <code>position</code> is <code>absolute</code>, and <code> bottom</code> is set to <code>auto</code> to override a previous bit of styling that’s needed in cases where a footnote isn’t being anchored.  I also decided to restrain the maximum width of a sidenote to <code>23em</code>, for no other reason than it looked right to me.</p>
  1127.  
  1128. <p>(A brief side note, <a href="https://meyerweb.com/bkkt/intend-your-puns.jpg">pun absolutely intended</a>: I’m using the physical-direction property <code>top</code> because the logical-direction equivalent in this context, <code>inset-block-start</code>, only gained full desktop cross-browser support a couple of years ago, and that’s only true if you ignore IE11’s existence, plus it arrived in several mobile browsers only this year, and I still fret about those kinds of things.  Since this is desktop-centric styling, I should probably set a calendar reminder to fix these at some point in the future.  Anyway, see <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/inset-block-start">MDN’s entry</a> for more.)</p>
  1129.  
  1130. <p>Now for the new and unfamiliar parts.</p>
  1131.  
  1132. <pre class="css">
  1133. <code> top: anchor(top);</code>
  1134. </pre>
  1135.  
  1136. <p>This sets the position of the top edge of the list item to be aligned with the top edge of its anchor’s box.  What is a footnote’s anchor?  It’s the corresponding superscripted footnote mark embedded in the text.  How does the CSS know that?  Well, the way I set things up&#160;&#x202F;—&#x2009; and this is not the only option for defining an anchor, but it’s the option that worked in this use case&#160;&#x202F;—&#x2009; the anchor is defined in the markup itself.  Here’s what a footnote mark and its associated footnote look like, markup-wise.</p>
  1137.  
  1138. <pre class="html">
  1139. <code>explosion,&lt;sup&gt;&lt;a href="#fnote01" id="fn01"&gt;1&lt;/a&gt;&lt;/sup&gt; although</code>
  1140. </pre>
  1141.  
  1142. <pre class="html">
  1143. <code>&lt;li id="fnote01" anchor="fn01"&gt;&lt;sup&gt;1&lt;/sup&gt; … &lt;/li&gt;</code>
  1144. </pre>
  1145.  
  1146. <p>The important bits for anchor positioning are the <code>id="fn01"</code> on the superscripted link, and the <code>anchor="fn01"</code> on the list item: the latter establishes the element with an <code>id</code> of <code>fn01</code> as the anchor for the list item.  Any element can have an <code>anchor</code> attribute, thus creating what the CSS Anchor Positioning specification <a href="https://www.w3.org/TR/css-anchor-position-1/#implicit">calls an implicit anchor</a>.  It’s explicit in the HTML, yes, but that makes it implicit to CSS, I guess.  There’s even an <code>implicit</code> keyword, so I could have written this in my CSS instead:</p>
  1147.  
  1148. <pre class="css">
  1149. <code> top: anchor(implicit top);</code>
  1150. </pre>
  1151.  
  1152. <p>(There are ways to mark an element as an anchor and associate other elements with that anchor, without the need for any HTML.  You don’t even need to have IDs in the HTML.  I’ll get to that in a bit.)</p>
  1153.  
  1154. <p>Note that the superscripted link and the list item are just barely related, structurally speaking.  Their closest ancestor element is the page’s single <code>&lt;main&gt;</code> element, which is the link’s fourth-great-grandparent, and the list item’s third-great-grandparent.  That’s okay!  Much as a <code>&lt;label&gt;</code> can be associated with an input element across DOM structures via its <code>for</code> attribute, any element can be associated with an anchoring element via its <code>anchor</code> attribute.  In both cases, the value is an ID.</p>
  1155.  
  1156. <p>So anyway, that means the top edge of the endnote will be absolutely positioned to line up with the top edge of its anchor.  Had I wanted the top of the endnote to line up with the bottom edge of the anchor, I would have said:</p>
  1157.  
  1158. <pre class="css">
  1159. <code> top: anchor(bottom);</code>
  1160. </pre>
  1161.  
  1162. <p>But I didn’t.  With the top edges aligned, I now needed to drop the endnote into the space outside the main content column, off to its right.  At first, I did it like this:</p>
  1163.  
  1164. <pre class="css">
  1165. <code> left: anchor(--main right);</code>
  1166. </pre>
  1167.  
  1168. <p>Wait.  Before you think you can just automatically use HTML element names as anchor references, well, you can’t.  That <code>--main</code> is what CSS calls a <em>dashed-ident</em>, as in a dashed identifier, and I declared it elsewhere in my CSS.  To wit:</p>
  1169.  
  1170. <pre class="css">
  1171. <code>main {
  1172. anchor-name: --main;
  1173. }</code>
  1174. </pre>
  1175.  
  1176. <p>That assigns the anchor name <code>--main</code> to the <code>&lt;main&gt;</code> element in the CSS, no HTML attributes required.  Using the name <code>--main</code> to identify the <code>&lt;main&gt;</code> element was me following the common practice of naming things for what they are.  I <em>could</em> have called it <code>--mainElement</code> or <code>--elMain</code> or <code>--main-column</code> or <code>--content</code> or <code>--josephine</code> or <code>--📕😉</code> or whatever I wanted.  It made the most sense to <em>me</em> to call it <code>--main</code>, so that’s what I picked.</p>
  1177.  
  1178. <p>Having done that, I can use the edges of the <code>&lt;main&gt;</code> element as positioning referents for any absolutely (or fixed) positioned element.  Since I wanted the left side of sidenotes to be placed with respect to the right edge of the <code>&lt;main&gt;</code>, I set their <code> left</code> to be <code>anchor(--main right)</code>.</p>
  1179.  
  1180. <p>Thus, taking these two declarations together, the top edge of a sidenote is positioned with respect to the top edge of its implicit anchor, and its left edge is positioned with respect to the right edge of the anchor named <code>--main</code>.</p>
  1181.  
  1182. <pre class="css">
  1183. <code> top: anchor(top);
  1184. left: anchor(--main right);</code>
  1185. </pre>
  1186.  
  1187. <p>Yes, I’m anchoring the sidenotes with respect to two completely different anchors, one of which is a descendant of the other.  That’s okay!  You can do that!  Literally, you could position each edge of an anchored element to a separate anchor, regardless of how they relate to each other structurally.</p>
  1188.  
  1189. <p>Once I previewed the result of those declarations, I saw I the sidenotes were too close to the main content, which makes sense: I had made the edges adjacent to each other.</p>
  1190.  
  1191. <figure class="standalone">
  1192. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchored-sidenotes-04.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchored-sidenotes-04.png" alt=""  /></a>
  1193. <figcaption>Red borders showing the edges of the sidenote and the main column touching.</figcaption>
  1194. </figure>
  1195.  
  1196. <p>I thought about using a left margin on the sidenotes to push them over, and that would work fine, but I figured what the heck, CSS has calculation functions and anchor functions can go inside them, and any engine supporting anchor positioning will also support <code>calc()</code>, so why not?  Thus:</p>
  1197.  
  1198. <pre class="css">
  1199. <code> left: calc(anchor(--main right) + 0.5em);</code>
  1200. </pre>
  1201.  
  1202. <p>I wrapped those in a media query that only turned the footnotes into sidenotes at or above a certain viewport width, and wrapped that in a feature query so as to keep the styles away from non-anchor-position-understanding browsers, and I had the solution I’d envisioned at the beginning of the project!</p>
  1203.  
  1204. <p>Except I didn’t.</p>
  1205.  
  1206. <h3 id="fixing-proximate-overlap">Fixing Proximate Overlap</h3>
  1207.  
  1208. <p>What I’d done was fine as long as the footnotes were well separated.  Remember, these are absolutely positioned elements, so they’re out of the document flow.  Since we <em>still</em> don’t have <a href="https://www.w3.org/TR/css3-exclusions/">CSS Exclusions</a>, there needs to be a way to deal with situations where there are two footnotes close to each other.  Without it, you get this sort of thing.</p>
  1209.  
  1210. <figure class="standalone">
  1211. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchored-sidenotes-02.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchored-sidenotes-02.png" alt=""  /></a>
  1212. <figcaption>Two sidenotes completely overlapping with each other.  This will not do.</figcaption>
  1213. </figure>
  1214.  
  1215. <p>I couldn’t figure out how to fix this problem, so I did what you do these days, which is I posted my problem to social media.  Pretty quickly, I got a reply from the brilliant <a href="https://kizu.dev/">Roman Komarov</a>, pointing me at <a href="https://codepen.io/kizu/pen/abRRavB">a Codepen that showed how to do what I needed</a>, plus some very cool highlighting techniques.  I <a href="https://codepen.io/meyerweb/pen/JjwdBWY"> forked it so I could strip it down</a> to the essentials, which is all I really needed for my use case, and also have some hope of understanding it.</p>
  1216.  
  1217. <p>Once I’d worked through it all and applied the results to <cite>TEoNW</cite>, I got exactly what I was after.</p>
  1218.  
  1219. <figure class="standalone">
  1220. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchored-sidenotes-03.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/anchored-sidenotes-03.png" alt="" /></a>
  1221. <figcaption>The same two sidenotes, except now there is no overlap.</figcaption>
  1222. </figure>
  1223.  
  1224. <p>But how?  It goes like this:</p>
  1225.  
  1226. <pre class="css">
  1227. <code>.endnotes li {
  1228. position: absolute;
  1229. anchor-name: --sidenote;
  1230. top: max(anchor(top) , calc(anchor(--sidenote bottom) + 0.67em));
  1231. bottom: auto;
  1232. left: calc(anchor(--main right) + 0.5em);
  1233. max-width: 23em;
  1234. }</code>
  1235. </pre>
  1236.  
  1237. <p>Whoa.  That’s a lot of functions working together there in the <code>top</code> value.  (CSS is becoming more and more functional, which I feel some kind of way about.)  It can all be verbalized as, “the position of the top edge of the list item is either the same as the top edge of its anchor, or two-thirds of an em below the bottom edge of the previous sidenote, whichever is further down”.</p>
  1238.  
  1239. <p>The browser knows how to do this because the list items have all been given an <code>anchor-name</code> of <code>--sidenote</code> (again, that could be anything, I just picked what made sense to me).  That means every one of the endnote list items will have that anchor name, and other things can be positioned against them.</p>
  1240.  
  1241. <p>Those styles mean that I have multiple elements bearing the same anchor name, though.  When any sidenote is positioned with respect to that anchor name, it has to pick just one of the anchors.  The specification says the named anchor that occurs most recently before the thing you’re positioning is what wins.  Given my setup, this means an anchored sidenote will use the previous sidenote as the anchor for its top edge.</p>
  1242.  
  1243. <p>At least, it will use the previous sidenote as its anchor <em>if</em> the bottom of the previous sidenote (plus two-thirds of an em) is lower than the top edge of its implicit anchor.  In a sense, every sidenote’s top edge has <em>two</em> anchors, and the <code>max()</code> function picks which one is actually used in every case.</p>
  1244.  
  1245. <p>CSS, man.</p>
  1246.  
  1247. <p>Remember that all this is experimental, and the specification (and thus how anchor positioning works) could change.  The best practices for accessibility are also not clear yet, from what I’ve been able to find.  As such, this may not be something you want to deploy in production, even as a progressive enhancement.  I’m holding off myself for the time being, which means none of the above is currently used in the published version of <cite>The Effects of Nuclear Weapons</cite>.  If people are interested, I can create a Codepen to illustrate.</p>
  1248.  
  1249. <p>I do know this is something the CSS Working Group is working on pretty hard right now, so I have hopes that things will finalize soon and support will spread.</p>
  1250.  
  1251. <p class="note">My thanks to <a href="https://kizu.dev/">Roman Komarov</a> for his review of and feedback on this article.  For more use cases of anchor positioning, see his lengthy (and quite lovely) article “<a href="https://kizu.dev/anchor-positioning-experiments/">Future CSS: Anchor Positioning</a>”.</p>
  1252. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2023/09/12/nuclear-anchored-sidenotes/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Nuclear%20Anchored%20Sidenotes%22">email Eric directly</a>.</p>]]></content:encoded>
  1253. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2023/09/12/nuclear-anchored-sidenotes/feed/</wfw:commentRss>
  1254. <slash:comments>7</slash:comments>
  1255. </item>
  1256. <item>
  1257. <title>Memories of Molly</title>
  1258. <link>https://meyerweb.com/eric/thoughts/2023/09/06/memories-of-molly/</link>
  1259. <comments>https://meyerweb.com/eric/thoughts/2023/09/06/memories-of-molly/#comments</comments>
  1260. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  1261. <pubDate>Wed, 06 Sep 2023 15:44:29 +0000</pubDate>
  1262. <category><![CDATA[Personal]]></category>
  1263. <category><![CDATA[Web]]></category>
  1264. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5315</guid>
  1265.  
  1266. <description><![CDATA[The Web is a little bit darker today, a fair bit poorer.]]></description>
  1267. <content:encoded><![CDATA[<p>The Web is a little bit darker today, a fair bit poorer: <a href="https://www.tucsonsentinel.com/local/report/090523_molly_holzschlag/tucsons-molly-holzschlag-known-as-the-fairy-godmother-web-dead-60/">Molly Holzschlag is dead</a>.  She lived hard, but I hope she died easy.  I am more sparing than most with my use of the word “friend”, and she was absolutely one.  To everyone.</p>
  1268.  
  1269. <p>If you don’t know her name, I’m sorry.  Too many didn’t.  She was one of the first web gurus, a title she adamantly rejected&#160;&#x202F;—&#x2009; “We’re all just <em>people</em>, people!”&#160;&#x202F;—&#x2009; but it fit nevertheless.  She was a groundbreaker, expanding and explaining the Web at its infancy.  So many people, on hearing the mournful news, have described her as a force of nature, and that’s a title she would have accepted with pride.  She was raucous, rambunctious, open-hearted, never ever close-mouthed, blazing with fire, and laughed (as she did everything) with her entire chest, constantly.  She was giving and took and she hurt and she wanted to heal everyone, all the time.  She was messily imperfect, would tell you so loudly and repeatedly, and gonzo in all the senses of that word.  Hunter S. Thompson should have written her obituary.</p>
  1270.  
  1271. <p>I could tell so many stories.  The time we were waiting to check into a hotel, talking about who knows what, and realized Little Richard was a few spots ahead of us in line.  Once he’d finished checking in, Molly walked right over to introduce herself and spend a few minutes talking with him.  An evening a group of us had dinner one the top floor of a building in Chiba City and I got the unexpectedly fresh shrimp hibachi.  The time she and I were chatting online about a talk or training gig, somehow got onto the subject of <a href="https://en.wikipedia.org/wiki/Nick_Drake">Nick Drake</a>, and coordinated a playing of “<a href="https://www.youtube.com/watch?v=VfSWWScqH5M"> Three Hours</a>” just to savor it together.  A night in San Francisco where the two of us went out for dinner before some conference or other, stopped at a bar just off Union Square so she could have a couple of drinks, and she got propositioned by the impressively drunk couple seated next to her after they’d failed to talk the two of us into hooking up.  The bartender couldn’t stop laughing.</p>
  1272.  
  1273. <div class="gallery">
  1274. <figure>
  1275. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/IMG_6394.jpg"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/IMG_6394.jpg" alt="" /></a>
  1276. <figcaption>At SXSW 2005 with Dave Shea, her co-author on <cite>The Zen of CSS</cite>, and wearing an <a href="http://gmpg.org/xfn/">XFN</a> shirt.</figcaption>
  1277. </figure>
  1278. <figure>
  1279. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/IMG_0985.jpg"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/IMG_0985.jpg" alt="" /></a>
  1280. <figcaption>Standing outside Moscone Center in San Francisco with Cia Romano.  I think this is that time we all got evacuated due to a fire alarm.</figcaption>
  1281. </figure>
  1282. </div>
  1283.  
  1284. <p>Or the time a bunch of us were gathered in New Orleans (again, some conference or other) and went to dinner at a jazz club, where we ended up seated next to the live jazz trio and she sang along with some of the songs.  She had a voice like a blues singer in a cabaret, brassy and smoky and full of hard-won joys, and she used it to great effect standing in front of Bill Gates to harangue him about Internet Explorer.  She raised it to fight like hell for the Web and its users, for the foundational principles of universal access and accessible development.  She put her voice on paper in some three dozen books, and was working on yet another when she died.  In one book, she managed to sneak past the editors an example that used a stick-figure <cite>Kama Sutra</cite> custom font face.  She could never resist a prank, particularly a bawdy one, as long as it didn’t hurt anyone.</p>
  1285.  
  1286. <div class="gallery">
  1287. <figure>
  1288. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/IMG_6293.jpg"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/IMG_6293.jpg" alt="" /></a>
  1289. <figcaption>Holding court in somebody’s hotel suite, with a baby <a href="https://ma.tt" class="acquaintance colleague met">Matt Mullenweg</a> in attendance.</figcaption>
  1290. </figure>
  1291. <figure>
  1292. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/IMG_0426.jpg"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/IMG_0426.jpg" alt="" /></a>
  1293. <figcaption>Once again holding court, this time at a bar with <a href="https://jasonsantamaria.com/" class="acquaintance colleague met">Jason Santa Maria</a>.</figcaption>
  1294. </figure>
  1295. </div>
  1296.  
  1297. <p>She made the trek to Cleveland at least once to attend and be part of the crew for one of our <a href="https://meyerweb.com/eric/thoughts/2005/12/29/bread-soup-and-love/">Bread and Soup</a> parties.  We put her to work rolling tiny matzoh balls and she immediately made ribald jokes about it, laughing harder at our one-up jokes than she had at her own.  She stopped by the house a couple of other times over the years, when she was in town for consulting work, “Auntie Molly” to our eldest and one of my few colleagues to have spent any time with Rebecca.  Those pictures were lost, and I still keenly regret that.</p>
  1298.  
  1299. <div class="gallery">
  1300. <figure>
  1301. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/IMG_7851.jpg"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/IMG_7851.jpg" alt="" /></a>
  1302. <figcaption>Rolling matzoh balls in our kitchen, <em>still</em> holding court.</figcaption>
  1303. </figure>
  1304. <figure>
  1305. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/IMG_6842.jpg"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/IMG_6842.jpg" alt="" /></a>
  1306. <figcaption>On top of a bus somewhere in the world, probably London, with my partner Kat.</figcaption>
  1307. </figure>
  1308. </div>
  1309.  
  1310. <p>There were so many things about what the Web became that she hated, that she’d spent so much time and energy fighting to avert, but she still loved it for what it could be and what it had been originally designed to be.  She took more than one fledgling web designer under her wing, boosted their skills and careers, and beamed with pride at their accomplishments.  She told a great story about one, I think it was Dunstan Orchard but I could be wrong, and his afternoon walk through a dry Arizona arroyo.</p>
  1311.  
  1312. <p>I could go on for pages, but I won’t; if this were a toast and she were here, she would have long ago heckled me (affectionately) into shutting up.  But if you have treasured memories of Molly, I’d love to hear them in the comments below, or on your own blog or social media or podcasts or anywhere.  She loved stories.  Tell hers.</p>
  1313.  
  1314. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2023/09/06/memories-of-molly/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Memories%20of%20Molly%22">email Eric directly</a>.</p>]]></content:encoded>
  1315. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2023/09/06/memories-of-molly/feed/</wfw:commentRss>
  1316. <slash:comments>38</slash:comments>
  1317. </item>
  1318. <item>
  1319. <title>Designing the Igalia Chats Logo</title>
  1320. <link>https://meyerweb.com/eric/thoughts/2023/08/22/igalia-chats-logo/</link>
  1321. <comments>https://meyerweb.com/eric/thoughts/2023/08/22/igalia-chats-logo/#respond</comments>
  1322. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  1323. <pubDate>Tue, 22 Aug 2023 13:52:54 +0000</pubDate>
  1324. <category><![CDATA[Design]]></category>
  1325. <category><![CDATA[Work]]></category>
  1326. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5302</guid>
  1327.  
  1328. <description><![CDATA[Last week, I designed a logo for the podcast I do at Igalia.]]></description>
  1329. <content:encoded><![CDATA[<p>One of the things I’ve been doing at <a href="https://igalia.com">Igalia</a> of late is podcasting with Brian Kardell.  It’s called “<a href="https://www.igalia.com/24-7/chats">Igalia Chats</a>”, and last week, I designed it a logo.  I tried out a number of different ideas, ran them past the Communication team for feedback, and settled on this one.</p>
  1330.  
  1331. <figure>
  1332. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/igalia-chats-logo.jpg"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/igalia-chats-logo.jpg" alt="The Igalia Chats logo, which combines the official full Igalia logo of a many-colored circle and the name of the company with the word “Chats” below the logo in a slightly larger font size than that used for the name of the company.  Next to them is a large stylized icon of a microphone." /></a>
  1333.   <figcaption>D&amp;AD Awards committee, you know where to find me.</figcaption>
  1334. </figure>
  1335.  
  1336. <p>And there you have it, the first logo I’ve designed in… well, in quite a while.  My work this time around was informed by a few things.</p>
  1337.  
  1338. <ul>
  1339. <li>Podcast apps, sites, etc.  expect a square image for the podcast’s logo.  This doesn’t mean you have to make the visible part of it square, exactly, but it does mean any wide-and-short logo will simultaneously feel cramped and lost in a vast void.  Or maybe just very far away.  The version shown in this post is <em>not</em> the square version, because this is not a podcast app and because I could.  The square version just adds more empty whitespace at the top and bottom, anyway.</li>
  1340. <li>I couldn’t really alter the official logo in any major way: the brand guidelines are pretty strong and shouldn’t be broken without collective approval.  Given the time that would take, I decided to just work with the logo as-is, and think about possible variants (say, the microphone icon in the blank diamond of the logo) in a later stage.  I did think about just not using the official logo at all, but that felt like it would end up looking too generic.  Besides, we hav e a pretty nifty logo there, so why not use it?</li>
  1341. <li>A typeface for the word “Chats” that works well with Igalia’s official logo.  I used <a href="https://www.stormtype.com/families/etelka">Etelka</a>, which is a font we already use on the web site, and I think is the basis of the semi-serifed letters in the official logo anyway.  Though I could be wrong about that; while I definitely have opinions about typefaces these days, I’m not very good at identifying them, or being able to distinguish between two similar fonts.  Call it typeface blindness.</li>
  1342. <li>Using open-source resources where possible; thus, the microphone icon came from <a href="https://thenounproject.com/">The Noun Project</a>.  I then modified it a bit (rounded the linecaps, shortened the pickup’s brace) to balance its visual weight with the rest of the design, and not crowd the letters too much.  I also added a subtle vertical gradient to the icon, which helped the word “Chats” to stand out a little more.  Gotta make the logo pop, donchaknow?</li>
  1343. </ul>
  1344.  
  1345. <p>There are probably some adjustments I’ll make after a bit of time, but I was determined not to let perfect be the enemy of shipping.  As for how I came to create the logo, you’re probably thinking fancy CSS Grid layout and custom fonts and all that jazz, but no, I just dumped everything into <a href="https://www.apple.com/keynote/">Keynote</a> and fiddled with ideas until I had some I liked.  It’s not a fantastic environment for this sort of work, I expect, but it’s Good Enough For Me™.</p>
  1346.  
  1347. <p>So, if you’re subscribed to <a href="https://www.igalia.com/24-7/chats">Igalia Chats</a> via your listening channel of choice, you should be seeing a new logo.  If you <em>aren’t</em> subscribed… try us, won’t you?  Brian and I talk about a lot of web-related stuff with a lot of really interesting people &#x202F;—&#x2009;&#160;most recently, with <a href="https://www.igalia.com/chats/polypane">Kilian Valkhof about the web development application Polypane</a>, with <a href="https://www.igalia.com/chats/undersea"> Stephen Shankland about undersea data cables</a>, with <a href="https://www.igalia.com/chats/zach"> Zach Leatherman about open-source work and funding</a>, and many more.  Plus sometimes we just talk with each other about what’s new in Web land, things <a href="https://www.igalia.com/chats/baseline"> like Google Baseline</a> or <a href="https://www.igalia.com/chats/safari-16">huge WebKit updates</a>.  And, yes, sometimes we talk about what Igalia is up to, like <a href="https://www.igalia.com/chats/servo"> our work on the Servo engine</a> or <a href="https://www.igalia.com/chats/steam-powered-open-source">the Steam Deck</a>.</p>
  1348.  
  1349. <p>This is one of the things I quite enjoy about working for Igalia&#160;&#x202F;—&#x2009;&#160;the way I can draw upon all the things I’ve learned over my many (many) years to create different things.  A logo last week, a thumbnail-building tool the week before, writing news posts, recording podcasts, doing audio production, figuring out transcription technology, and on and on and on.  It can sometimes be frustrating in the way all work can be, but it rarely gets boring. (And if that sounds good to you, <a href="https://www.igalia.com/jobs/open/">we are hiring</a> for a number of roles!)</p>
  1350.  
  1351. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2023/08/22/igalia-chats-logo/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Designing%20the%20Igalia%20Chats%20Logo%22">email Eric directly</a>.</p>]]></content:encoded>
  1352. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2023/08/22/igalia-chats-logo/feed/</wfw:commentRss>
  1353. <slash:comments>0</slash:comments>
  1354. </item>
  1355. <item>
  1356. <title>First-Person Scrollers</title>
  1357. <link>https://meyerweb.com/eric/thoughts/2023/06/20/first-person-scrollers/</link>
  1358. <comments>https://meyerweb.com/eric/thoughts/2023/06/20/first-person-scrollers/#comments</comments>
  1359. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  1360. <pubDate>Tue, 20 Jun 2023 12:16:06 +0000</pubDate>
  1361. <category><![CDATA[Browsers]]></category>
  1362. <category><![CDATA[Web]]></category>
  1363. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5282</guid>
  1364.  
  1365. <description><![CDATA[Web browsers have higher performance requirements than video games, and in essentially the same ways.  Seriously.]]></description>
  1366. <content:encoded><![CDATA[<p>I’ve played a lot of video games over the years, and the thing that just utterly blows my mind about them is how every frame is painted from scratch.  So in a game running at 30 frames per second, everything in the scene has to be calculated and drawn every 33 milliseconds, no matter how little or much has changed from one frame to the next.  In modern games, users generally demand 60 frames per second.  So everything you see on-screen gets calculated, placed, colored, textured, shaded, and what-have-you in 16 milliseconds (or less).  And then, in the next 16 milliseconds (or less), it has to be done <em>all over again</em>.  And there are games that render the entire scene in single-digits numbers of milliseconds!</p>
  1367.  
  1368. <p>I mean, I’ve done some simple 3D render coding in my day.  I’ve done hobbyist video game development; see <a href="https://gravitywars.meyerweb.com/">Gravity Wars</a>, for example (which I really do need to get back to and make less user-hostile).  So you’d think I’d be used to this concept, but somehow, I just never get there.  My pre-DOS-era brain rebels at the idea that everything has to be recalculated from scratch every frame, and doubly so that such a thing can be done in such infinitesimal slivers of time.</p>
  1369.  
  1370. <p>So you can imagine how I feel about the fact that <em>web browsers</em> operate in exactly the same way, and with the same performance requirements.</p>
  1371.  
  1372. <p>Maybe this shouldn’t come as a surprise.  After all, we have user interactions and embedded videos and resizable windows and page scrolling and stuff like that, never mind CSS animations and DOM manipulation, so the viewport often needs to be re-rendered to reflect the current state of things.  And to make all that feel smooth like butter, browser engines have to be able to display web pages at a minimum of 60 frames per second.</p>
  1373.  
  1374. <figure>
  1375. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/‎fps.jpg"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/‎fps.jpg" alt="" /></a>
  1376. <figcaption>Admittedly, this would be a popular UI for browsing social media.</figcaption>
  1377. </figure>
  1378.  
  1379. <p>This demand touches absolutely <em>everything</em>, and shapes the evolution of web technologies in ways I don’t think we fully appreciate.  You want to add a new selector type?  It has to be performant.  This is what blocked <code>:has()</code> (and similar proposals) for such a long time.  It wasn’t difficult to figure out how to select ancestor elements&#x202F;—&#x2009;it was very difficult to figure out how to do it really, really fast, so as not to lower typical rendering speed below that magic 60fps.  The same logic applies to new features like view transitions, or new filter functions, or element exclusions, or whatever you might dream up.  No matter how cool the idea, if it bogs rendering down too much, it’s a non-starter.</p>
  1380.  
  1381. <p>I should note that none of this is to say it’s impossible to get a browser below 60fps: pile on enough computationally expensive operations and you’ll still jank like crazy.  It’s more that the goal is to keep any new feature from dragging rendering performance down too far in reasonable situations, both alone and in combination with already-existing features.  What constitutes “down too far” and “reasonable situations” is honestly a little opaque, but that’s a conversation slash vigorous debate for another time.</p>
  1382.  
  1383. <p>I’m sure the people who’ve worked on browser engines have fascinating stories about what they do internally to safeguard rendering speed, and ideas they’ve had to spike because they were performance killers.  I would love to hear those stories, if any BigCo devrel teams are looking for podcast ideas, or would like to guest on <a href="https://igalia.com/24-7/chats">Igalia Chats</a>. (We’d love to have you on!)</p>
  1384.  
  1385. <p>Anyway, the point I’m making is that performance isn’t just a matter of low asset sizes and script tuning and server efficiency.  It’s also a question of the engine’s ability to redraw the contents of the viewport, no matter what changes for whatever reason, with reasonable anticipation of things that might affect the rendering, every 15 milliseconds, over and over and over and over and over again, just so we can scroll our web pages smoothly.  It’s kind of bananas, and yet, it also makes sense.  Welcome to the web.</p>
  1386.  
  1387. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2023/06/20/first-person-scrollers/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22First-Person%20Scrollers%22">email Eric directly</a>.</p>]]></content:encoded>
  1388. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2023/06/20/first-person-scrollers/feed/</wfw:commentRss>
  1389. <slash:comments>1</slash:comments>
  1390. </item>
  1391. <item>
  1392. <title>From ABC’s to 9999999</title>
  1393. <link>https://meyerweb.com/eric/thoughts/2023/04/10/from-abcs-to-9999999/</link>
  1394. <comments>https://meyerweb.com/eric/thoughts/2023/04/10/from-abcs-to-9999999/#comments</comments>
  1395. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  1396. <pubDate>Mon, 10 Apr 2023 16:20:33 +0000</pubDate>
  1397. <category><![CDATA[Personal]]></category>
  1398. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5262</guid>
  1399.  
  1400. <description><![CDATA[A year or so ago, I set about listening through my entire music library alphabetically by song title.]]></description>
  1401. <content:encoded><![CDATA[<p>The other week I crossed a midpoint, of sorts: as I was driving home from a weekly commitment, my iPhone segued from <a href="https://www.youtube.com/watch?v=rxwRzW6ehAU">Rush’s “Mystic Rhythms”</a> to <a href="https://www.youtube.com/watch?v=CXJBS1Up_kg">The Seatbelts’ “N.Y.  Rush”</a>, which is, lexicographically speaking, the middle of my iTu&#x202F;—&#x2009;&#160;oh <em> excuse</em> me, the middle of my Music Dot App Library, where I passed from the “M” songs into the “N” songs.</p>
  1402.  
  1403. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/Troubadour.jpg"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/Troubadour.jpg" alt="" class="pic" /></a>
  1404.  
  1405. <p>See, about a year or so ago, I took inspiration from <a href="https://www.kevinsmokler.com/" rel="acquaintance met">Kevin Smokler</a> to set about listening through my entire music library alphabetically by song title.  Thus, I started with <a href="https://www.youtube.com/watch?v=0TfXtcvmkiw">“ABC’s” by K’naan</a> and will end, probably in a year or so, with <a href="https://www.youtube.com/watch?v=wgVTmt6t3hg">“9999999” by Mike Morasky (a.k.a Aperture Science Psychoacoustics Laboratory)</a>.</p>
  1406.  
  1407. <p>Every time I have to drive my car for more than a few minutes, I’ll plug in my iPhone and continue the listen from where I left off.  This mainly happens during the aforementioned weekly commitment, which usually sees me driving for an hour or so.  I also listen to it while I’m doing chores around the house like installing ceiling fans or diagnosing half-dead Christmas light strings.</p>
  1408.  
  1409. <p>This sort of listen is, in many ways, like listening to the entire library on shuffle, because, as <a href="https://en.wikipedia.org/wiki/Jared_Spool">Jared Spool</a> used to point out (and probably still does), alphabetically sorting a long list of things is indistinguishable from having it randomized.  For me, the main difference between alphabetical and random is that it’s a lot easier to pick back up where you left off when working through alphabetically. (Yes, Music Dot App should do that automatically, but sometimes it forgets where it was.) You can also be a lot more certain that every song gets a listen, something that’s harder to ensure if you’re listening to a random shuffle of a couple thousand tracks and your software loses its place.</p>
  1410.  
  1411. <p>There are other advantages: sometimes, artists will use the same song title, and you get interesting combinations.  For example, there was “America”, which gave me <a href="https://www.youtube.com/watch?v=6cTC6rGE9jo">a song by K’naan</a> and then a same-titled, but <em>very</em> different, <a href="https://www.youtube.com/watch?v=z4BmaBNDmgQ">song by Spinal Tap</a>.  Similarly, there are titular combinations that pop out, like  <a href="https://www.youtube.com/watch?v=C0t0e1npyYM">“Come On” by The Goo Goo Dolls</a>, <a href="https://www.youtube.com/watch?v=_LiUG0Chubg">“Come On In, The Dreams Are Fine” by Dee-Lite</a>, and <a href="https://www.youtube.com/watch?v=En4szIQL9Yo">“Come On Over” by Elana Stone</a>.</p>
  1412.  
  1413. <p>Some of these combinations groove, some delight, some earn the stank face, and some make me literally laugh out loud.  And some aren’t related by title but still go together really, really well.  A recent example was the segue from <a href="https://www.youtube.com/watch?v=bzVjZu4fl4o">The Prodigy’s “Narayan”</a> to <a href="https://www.youtube.com/watch?v=NfQD1QiQ9o4">Radiohead’s “The National Anthem”</a>, which sonically flowed <em> just right</em> at the switchover, almost like they’d been composed to have that effect.  It made this old <a href="https://meyerweb.com/eric/yfo/">long-ago radio DJ</a> smile.</p>
  1414.  
  1415. <p>I say I took inspiration from Kevin because my listen has a couple of differences to his:</p>
  1416.  
  1417. <ul>
  1418. <li>Kevin has a “no skips, ever” rule, but I will skip songs that are repeats.  This happens a lot when you have both live and studio albums, as I do for a few artists (particularly
  1419. <a href="https://en.wikipedia.org/wiki/Rush_(band)">Rush</a>), or have copied tracks for lightly-engineered playlists, as I have a few times.  That said, if I have a song by one artist and a cover of that song by another, I don’t skip either of them.  For remixes or alternate recordings of a song by the same artist, I generally don’t skip, unless the remix is just the original song with a vaguely different beat track.</li>
  1420. <li>I filtered out most of my classical content before starting.  This is not because I dislike classical, but because they tend to sort together in unrelenting clumps &#x202F;—&#x2009;&#160;all of Beethoven’s and Mozart’s symphonies one after another after another, for example &#x202F;—&#x2009;&#160;and I wanted a varietal mix.  I did keep “classical” albums like <a href="https://en.wikipedia.org/wiki/Carreras_Domingo_Pavarotti_in_Concert"><cite>Carreras Domingo Pavarotti in Concert</cite></a> and <a href="https://en.wikipedia.org/wiki/Carmina_Burana_(Orff)"><cite>Carmina Burana</cite></a> because they have normal-length tracks with titles that scatter them throughout the sort.  The same reasoning was used to retain classic film and TV scores, even if I was stretching it a bit to leave in <a href="https://en.wikipedia.org/wiki/Cosmos:_A_Personal_Voyage#LP_and_cassette"><cite>The Music of Cosmos</cite></a> (the 1980 one), which prefixes all its tracks with Roman numerals…&#160;but each track is a medley, so it got a pass.  The whole-album-in-a-single-MP3 <a href="https://en.wikipedia.org/wiki/Osmos#Soundtrack"><cite>The Music of Osmos</cite></a>, on the other hand, did not.</li>
  1421. </ul>
  1422.  
  1423. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/Portal-2_-Songs-to-Test-By.jpg"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/Portal-2_-Songs-to-Test-By.jpg" alt="" class="pic" /></a>
  1424.  
  1425. <p>All that said, I have a much shorter road than Kevin: he has a library of over twelve <em>thousand</em> tracks, whereas my slightly-filtered library is just shy of 2,500 tracks, or right around 160 hours.  The repeated-song skips knock the total time down a bit, probably by a few hours but not much more than that.  So, figure at an average of 80 minutes per week, that’s about 120 weeks, or two years and four months to get from beginning to end.</p>
  1426.  
  1427. <p>And what will I do when I reach the end?  Probably go back to better curate the sorting (e.g., configuring <a href="https://www.youtube.com/watch?v=EU4L6THYAbM">Soundgarden’s “4th of July”</a> to be sorted as “Fourth of July”), create a playlist that cuts out the repeats ahead of time, and start over.  But we’ll see when I get there.  Maybe next time I’ll listen to it in reverse alphabetical order instead.</p>
  1428.  
  1429. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2023/04/10/from-abcs-to-9999999/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22From%20ABC’s%20to%209999999%22">email Eric directly</a>.</p>]]></content:encoded>
  1430. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2023/04/10/from-abcs-to-9999999/feed/</wfw:commentRss>
  1431. <slash:comments>1</slash:comments>
  1432. </item>
  1433. <item>
  1434. <title>Ventura Vexations</title>
  1435. <link>https://meyerweb.com/eric/thoughts/2023/04/04/ventura-vexations/</link>
  1436. <comments>https://meyerweb.com/eric/thoughts/2023/04/04/ventura-vexations/#comments</comments>
  1437. <dc:creator><![CDATA[Eric Meyer]]></dc:creator>
  1438. <pubDate>Tue, 04 Apr 2023 15:57:34 +0000</pubDate>
  1439. <category><![CDATA[Commentary]]></category>
  1440. <category><![CDATA[Mac]]></category>
  1441. <category><![CDATA[Rants]]></category>
  1442. <guid isPermaLink="false">https://meyerweb.com/eric/thoughts/?p=5253</guid>
  1443.  
  1444. <description><![CDATA[I’ve been a bit over a month now on my new 14” MacBook Pro, and I have complaints.  Not about the hardware, which is solid yet lightweight, super-quiet yet incredibly fast and powerful, long-lived on battery, and decent enough under the fingertips.  Plus, all the keyboard keys Just Work™, unlike the MBP it replaced!  So [&#8230;]]]></description>
  1445. <content:encoded><![CDATA[<p>I’ve been a bit over a month now on my new 14” MacBook Pro, and I have complaints.  Not about the hardware, which is solid yet lightweight, super-quiet yet incredibly fast and powerful, long-lived on battery, and decent enough under the fingertips.  Plus, all the keyboard keys Just Work<img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2122.png" alt="™" class="wp-smiley" style="height: 1em; max-height: 1em;" />, unlike the MBP it replaced!  So that’s nice.</p>
  1446.  
  1447. <p>No, my complaints are entirely about the user environment.  At first I thought this was because I skipped directly from OS X 10.14 to macOS 13, and simply wasn’t used to How The Kids Do Things These Days®, but <a href="https://me.dm/@stop/109989005813215592">apparently I would’ve felt the same</a> even if I’d kept current with OS updates.  So I’m going to gripe here in hopes someone who knows more than me will have recommendations to ameliorate my annoyance.</p>
  1448.  
  1449. <h3 id="dragthing-dismay">DragThing Dismay</h3>
  1450.  
  1451. <p>This isn’t on Apple, but still, it’s a huge loss for me.  I know <a href="https://meyerweb.com/eric/thoughts/2023/02/23/a-leap-of-decades/">I already complained about the lack of DragThing</a>, but I really, really do miss what it did for me.  You never know what you’ve got ’til it’s gone, right?  But let me be clear about exactly what it did for me, which so far as I can tell no macOS application does, nor does macOS itself.</p>
  1452.  
  1453. <p>The way I used DragThing was to have a long shelf down the right side of my monitor containing small-but-recognizable icons representing my most-used folders (home directory, Downloads, Documents, Applications, a few other folders) and a number of applications.  It stayed there all the time, and the icons were always there whether or not the application was running.</p>
  1454.  
  1455. <p>When I launched, say, Firefox, then there would be a little indicator next to its application icon in DragThing to indicate it was running.  When I quit Firefox, the indicator went away but the Firefox icon stayed.  And also, if I launched an application that wasn’t in the DragThing shelf, it did <em>not</em> add an icon for that application to the shelf. (I used the Dock at the bottom of the screen to show me that.)</p>
  1456.  
  1457. <p>There are super-powered application switchers available for macOS, but as far as I’ve seen, they only list the applications actually running.  Launch an application, its icon is added.  Quit an application, its icon disappears.  None of these switchers let me keep persistent static one-click shortcuts to launch a variety of applications and open commonly-used folders.</p>
  1458.  
  1459. <h3 id="dock-folder-disgruntlement">Dock Folder Disgruntlement</h3>
  1460.  
  1461. <p>Now I’m on to macOS itself.  Given the previous problem, the Dock is the only thing available to me, and I have gripes about it.  One of the bigger ones is rooted in folders kept on the Dock, to the right of the bar that divides them from the application icons.  When I click on them, I get a popup (wince) or a Stack (shudder) instead of them just opening the target folder in the Finder.</p>
  1462.  
  1463. <p>In the Before Times, I could create an alias to the folder and drop that in the Dock, the icon in the Dock would look like the target folder, and clicking on the alias opened the folder’s window.  If I do that now, the click-to-open part works, but the aliases all look like blank text documents with tiny arrows.  What the hell?</p>
  1464.  
  1465. <p>If I instead add actual folders (not aliases) to the Dock, holding down ⌥⌘ (option-command) when I click them does exactly what I want.  Only, I don’t want to have to hold down modifier keys, especially when using the trackpad.  I’ve mostly adapted to the key combo, but even on desktop I still sometimes click a folder and blink in irritation at the popup thingy for a second before remembering that things are stupider now.</p>
  1466.  
  1467. <h3 id="translucency-tribulation">Translucency Tribulation</h3>
  1468.  
  1469. <p>The other problem with the Dock is that mine is too opaque.  That’s because the nearly-transparent Finder menu bar was really not doing it for me, so acting on <a href="https://mastodon.social/@cbirdsong/109915043135251483">a helpful tip</a>, I went and checked the “Reduce Transparency” option in the Accessibility settings.  That fixed the menu bar nicely, but it also made the Dock opaque, which I didn’t actually want.  I can pretty easily live with it, but I do wish I could make just the menu bar opaque (without having to resort to desktop wallpaper hacks, which I suspect do not do well with changes of display resolution).</p>
  1470.  
  1471. <h3 id="shortcut-stupidity">Shortcut Stupidity</h3>
  1472.  
  1473. <figure>
  1474. <a href="https://meyerweb.com/eric/thoughts/wp-content/uploads/shortcut-stupidity.png"><img decoding="async" src="https://meyerweb.com/eric/thoughts/wp-content/uploads/shortcut-stupidity.png" alt="" /></a>
  1475. <figcaption>Seriously, Apple, what the hell.</figcaption>
  1476. </figure>
  1477.  
  1478. <p>And while I’m on the subject of the menu bar: no matter the application or even the Finder itself, dropdown menus from the menu bar render the actions you can do in black and the actions you can’t do in washed-out gray.  Cool.  But also, <em>all</em> the keyboard shortcuts are now a washed-out gray, which I keep instinctively thinking means they’ve been disabled or something.  They’re also a lot more difficult for my older eyes to pick out, and I have to flick my eyes back and forth to make sure a given keyboard shortcut corresponds to a thing I actually can do.  Seriously, Apple, what the <em>hell</em>?</p>
  1479.  
  1480. <h3 id="trash-can-troubles">Trash Can Troubles</h3>
  1481.  
  1482. <p>I used to have the Trash can on the desktop, down in the lower right corner, and now I guess I can’t.  I vaguely recall this is something DragThing made possible, so maybe that’s another reason to gripe about the lack of it, but it’s still bananas to me that the Trash can is not there by default.  I understand that I may be very old.</p>
  1483.  
  1484. <h3 id="preview-problems">Preview Problems</h3>
  1485.  
  1486. <p>On my old machine, Preview was probably the most rock-solid application on there.  On the new machine, Preview occasionally hangs on closing heavily-commented PDFs when I choose not to save changes.  I can force-quit it and so far haven’t experienced any data corruption, but it’s still annoying.</p>
  1487.  
  1488. <hr />
  1489.  
  1490. <p>Those are the things that have stood out the most to me about Ventura.  How about you?  What bothers you about your operating system (whichever one that is) and how would you like to see it fixed?</p>
  1491.  
  1492. <p>Oh, and I’ll follow this up soon with a post about what I like in Ventura, because it’s not all frowns and grumbles.</p>
  1493.  
  1494. <hr><p>Have something to say to all that?  You can <a href="https://meyerweb.com/eric/thoughts/2023/04/04/ventura-vexations/#commentform">add a comment to the post</a>, or <a href="mailto:eric@meyerweb.com?subject=In%20reply%20to%20%22Ventura%20Vexations%22">email Eric directly</a>.</p>]]></content:encoded>
  1495. <wfw:commentRss>https://meyerweb.com/eric/thoughts/2023/04/04/ventura-vexations/feed/</wfw:commentRss>
  1496. <slash:comments>5</slash:comments>
  1497. </item>
  1498. </channel>
  1499. </rss>
  1500.  

If you would like to create a banner that links to this page (i.e. this validation result), do the following:

  1. Download the "valid RSS" banner.

  2. Upload the image to your own server. (This step is important. Please do not link directly to the image on this server.)

  3. Add this HTML to your page (change the image src attribute if necessary):

If you would like to create a text link instead, here is the URL you can use:

http://www.feedvalidator.org/check.cgi?url=http%3A//meyerweb.com/eric/thoughts/rss2/full

Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda