<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ken Bellows</title>
    <description>The latest articles on DEV Community by Ken Bellows (@kenbellows).</description>
    <link>https://dev.to/kenbellows</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F83809%2Fb7c219b0-9408-4366-ad9e-62e992e91c6c.png</url>
      <title>DEV Community: Ken Bellows</title>
      <link>https://dev.to/kenbellows</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kenbellows"/>
    <language>en</language>
    <item>
      <title>String.prototype.search(): a method I wish I knew about a long time ago</title>
      <dc:creator>Ken Bellows</dc:creator>
      <pubDate>Tue, 23 Jun 2020 17:13:03 +0000</pubDate>
      <link>https://dev.to/kenbellows/string-prototype-search-a-method-i-wish-i-knew-about-a-long-time-ago-45n6</link>
      <guid>https://dev.to/kenbellows/string-prototype-search-a-method-i-wish-i-knew-about-a-long-time-ago-45n6</guid>
      <description>&lt;p&gt;&lt;strong&gt;tl;dr:&lt;/strong&gt;  &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search"&gt;&lt;code&gt;String.prototype.search()&lt;/code&gt;&lt;/a&gt; is basically &lt;code&gt;.indexOf()&lt;/code&gt; but with regexes. It's been supported in every browser since IE 4, but ES6 made it more powerful with &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/search"&gt;&lt;code&gt;Symbol.search&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;I have been writing JavaScript for just about 18 years. I started sometime in 2002, when IE 6 was king, Firefox was just being released, and Chrome did not exist.&lt;/p&gt;

&lt;p&gt;I've been writing JavaScript for almost two decades, and I've always been someone who loves digging into the docs, learning every feature available, every method of every object in the browser. But sometimes... sometimes I &lt;em&gt;still&lt;/em&gt;, after all this time, find something that's been around for a long time and I just didn't know about.&lt;/p&gt;

&lt;p&gt;Today I discovered one such method: &lt;code&gt;String.prototype.search()&lt;/code&gt;. And man, I &lt;em&gt;wish&lt;/em&gt; I had known about this one a loooong time ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.search()&lt;/code&gt; string method is pretty straightforward: as I mentioned in the tl;dr, it's basically &lt;code&gt;.indexOf()&lt;/code&gt;, but with one crucial difference: &lt;strong&gt;it uses regular expressions!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the demo from &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search"&gt;the MDN page&lt;/a&gt;. It demonstrates how you would find the first non-whitespace, non-alphanumeric character in a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paragraph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// any character that is not a word character or whitespace&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^\w\s]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paragraph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// expected output: 43&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paragraph&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;paragraph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;
&lt;span class="c1"&gt;// expected output: "."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This blew my mind when I saw it. Not because it's necessarily that crazy, but just because I never knew it was available to me. I have hacked together this method countless times over the years using the clunkier, less readable &lt;code&gt;String.prototype.match()&lt;/code&gt;. This method works, and it's my go-to solution when I want capture groups and all that, but for simply finding the index of the first instance of a certain pattern in a string, &lt;code&gt;.search(regex)&lt;/code&gt; is just so &lt;em&gt;clean&lt;/em&gt;. For one, to me at least, it's immediately obvious what's happening here, whereas the &lt;code&gt;.match()&lt;/code&gt; method always took me a minute to understand. For another, &lt;code&gt;.match()&lt;/code&gt; requires extra processing, because it has three kinds of return values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if it doesn't find a match, it returns &lt;code&gt;null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;if it finds a match:

&lt;ul&gt;
&lt;li&gt;if your regex had the global flag (&lt;code&gt;/.../g&lt;/code&gt;, like in MDN's example above), it returns an array of all matches, and there's no way to get their indices&lt;/li&gt;
&lt;li&gt;if your regex did not have the global flag, it returns an object with an &lt;code&gt;index&lt;/code&gt; property&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;code&gt;.match()&lt;/code&gt; gets complicated.&lt;/p&gt;

&lt;p&gt;Another option that I sometimes use is &lt;code&gt;RegExp.prototype.exec()&lt;/code&gt;. This has the advantage that it always returns an object with an &lt;code&gt;index&lt;/code&gt; property when it finds a match, regardless of the global flag, but the disadvantage that you still need to be careful about the global flag if you want to run it on multiple strings, because it starts searching from the index of the previous match. This can be useful sometimes, but isn't great in the simple case.&lt;/p&gt;

&lt;p&gt;Just to drive this point home, here's the side-by-side comparison:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// old way&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;paragraph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="c1"&gt;// new way&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;paragraph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I don't know. I get really excited about stuff like this. Maybe you don't. But if that didn't excite you, maybe this will:&lt;/p&gt;
&lt;h2&gt;
  
  
  How ES6 made it even more powerful
&lt;/h2&gt;

&lt;p&gt;The way I came across &lt;code&gt;String.prototype.search()&lt;/code&gt; was kind of funny. I was looking over the README for Paul Miller's fantastic polyfill library, &lt;a href="https://github.com/paulmillr/es6-shim"&gt;ES6 Shim&lt;/a&gt;, and I noticed this in &lt;a href="https://github.com/paulmillr/es6-shim#caveats"&gt;the "Caveats" section&lt;/a&gt; at the bottom:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Well-known &lt;code&gt;Symbol&lt;/code&gt;s

&lt;ul&gt;
&lt;li&gt;In order to make them work cross-realm, these are created with the global &lt;code&gt;Symbol&lt;/code&gt; registry via &lt;code&gt;Symbol.for&lt;/code&gt;. This does not violate the spec, but it does mean that &lt;code&gt;Symbol.for('Symbol.search') === Symbol.search&lt;/code&gt; will be &lt;code&gt;true&lt;/code&gt;, which it would not by default in a fresh compliant realm.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;If that makes no sense to you, let's do a 30-second crash course on Symbols. If it did make sense, skip the next section.&lt;/p&gt;
&lt;h3&gt;
  
  
  A brief aside about Symbols
&lt;/h3&gt;

&lt;p&gt;This will be a very quick overview, so if Symbols still don't make a ton of sense to you after this, I highly recommend doing some googling, because they're pretty important for leveling up in JS (IMHO).&lt;/p&gt;

&lt;p&gt;Symbols are a new primitive type introduced to JavaScript in ECMAScript 2015, a.k.a. ES6. The basic idea behind them is to create a perfectly unique key to use as an object property name, so that it's impossible for someone else to accidentally clobber your property later by using the same name, especially on shared objects and global window properties. Before Symbols, it was common to see keys on shared objects with lots of leading underscores, stuff like &lt;code&gt;___myThing&lt;/code&gt;, or with a randomly generated prefix, like &lt;code&gt;142857_myThing&lt;/code&gt;. This may seem like a rare edge case if you haven't encountered it, but trust me, this has been a source of frustration many times in JS history.&lt;/p&gt;

&lt;p&gt;For your standard, garden-variety Symbols, created with &lt;code&gt;Symbol('foo')&lt;/code&gt;, no one but you has access to them unless you pass them around. However, there is a special set of so-called "well-known Symbols" that everyone has access to. You can create your own by registering a name in the global Symbol registry with &lt;code&gt;Symbol.for()&lt;/code&gt;, as mentioned in the quote above, but there are also several well-known symbols defined by the browser as properties on the Symbol object. These are used as special property names that enable certain functionality for objects.&lt;/p&gt;

&lt;p&gt;Perhaps the most famous is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator"&gt;&lt;code&gt;Symbol.iterator&lt;/code&gt;&lt;/a&gt;, which lets us define custom iteration behavior for our classes, which is then used by &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax"&gt;the spread syntax&lt;/a&gt; and the [for ... of loop] to iterate over our object. I wrote a whole post about ES6 iterators and how they relate to generators a while back, if you're interested in a deep dive on this topic (it gets pretty wild when you dig deep):&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/kenbellows" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cvBskBu4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--_OlAq88G--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/83809/b7c219b0-9408-4366-ad9e-62e992e91c6c.png" alt="kenbellows image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/kenbellows/the-javascript-iteration-protocols-and-how-they-fit-in-1g8i" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;The JavaScript Iteration Protocols and How They Fit In&lt;/h2&gt;
      &lt;h3&gt;Ken Bellows ・ Dec 27 '18 ・ 12 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#es2015&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#es6&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#intermediate&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



&lt;p&gt;Okay, hopefully we all have at least enough understanding to follow the rest of the story here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Back to the story
&lt;/h3&gt;

&lt;p&gt;After reading the note in the Caveats section of ES6 Shim, my question was, "What the heck is &lt;code&gt;Symbol.search&lt;/code&gt; for?" I had never encountered this particular well-known Symbol before, so I read &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/search"&gt;the MDN page on &lt;code&gt;Symbol.search&lt;/code&gt;&lt;/a&gt;, which in turn led me to &lt;code&gt;String.prototype.search&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I've already gotten a bit long-winded here, so to wrap up quickly, the bottom line is this: when you call &lt;code&gt;myString.seach(x)&lt;/code&gt;, the engine checks whether the thing you passed in, &lt;code&gt;x&lt;/code&gt;, has a method defined under the key &lt;code&gt;[Symbol.search]&lt;/code&gt;. If not, it tries to convert to a &lt;code&gt;RegExp&lt;/code&gt; by calling &lt;code&gt;new RegExp(x)&lt;/code&gt;, which only works for strings.&lt;/p&gt;

&lt;p&gt;(&lt;strong&gt;Side note:&lt;/strong&gt; The MDN page is misleading here. It says: "If a non-RegExp object regexp is passed, it is implicitly converted to a RegExp with new RegExp(regexp)." But as we'll see next, this is not strictly true; it will not convert to a RegExp if you pass an object with a &lt;code&gt;[Symbol.search]&lt;/code&gt; property.)&lt;/p&gt;

&lt;p&gt;So what this means for us is that we can write a custom string search function and wrap it in an object. This may seem niche, since you can always just pass the string to the function, and this is certainly true. But something about the syntax feels nice to me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Find the index of the first character following a string like:&lt;/span&gt;
&lt;span class="c1"&gt;//    "Name:\t"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nameFinder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/Name:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// imagine this was read in from a file&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Customer Information
ID: 11223344
Name:   John Smith
Address:    123 Main Street
...`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customerNameStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nameFinder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customerName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customerNameStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;customerNameStart&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Imagine looping over a directory of customer info files in a Node script trying to extract their names, reusing this same search object each time, even storing the name finder and similar finders for other fields in a separate module and importing them. I think it could be neat! (Just me?)&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Honestly, I recognize that this is not super revolutionary or anything, and it probably won't change a lot of workflows. But to me, that isn't the important thing; what's most important to me is to know what tools are available. I honestly don't know when I would use a customer search object like the one above, but I think it's very cool that it's an option. And now that that I know about it, if I ever come across a situation where it really is useful, I'll have it in the back of my head. It's another Batarang on my utility belt.&lt;/p&gt;

&lt;p&gt;(Also, I just think metaprogramming stuff like this is really cool 😎)&lt;/p&gt;




&lt;h2&gt;
  
  
  Endnote
&lt;/h2&gt;

&lt;p&gt;Thanks if you read all this! It's niche, I know, and I get more excited than most devs I know about little things like this. But if you got excited about this article, let me know in the comments, or shoot me a DM!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>todayilearned</category>
      <category>todayisearched</category>
    </item>
    <item>
      <title>What's your preferred Node.js testing setup?</title>
      <dc:creator>Ken Bellows</dc:creator>
      <pubDate>Thu, 16 Jan 2020 16:13:46 +0000</pubDate>
      <link>https://dev.to/kenbellows/what-s-your-preferred-node-js-testing-setup-4e13</link>
      <guid>https://dev.to/kenbellows/what-s-your-preferred-node-js-testing-setup-4e13</guid>
      <description>&lt;p&gt;I'm currently working on building a new Node.js application from the ground up at work. This is the first time I've done this for anything other than a personal side project, and I'd really like to do it right.&lt;/p&gt;

&lt;p&gt;In pursuit of that goal, I want to introduce unit and integration tests early on in the process, but I don't have much (&lt;em&gt;read: any&lt;/em&gt;) experience with unit tests in Node, so I'm not sure what all is out there. I've used &lt;a href="https://jasmine.github.io/"&gt;Jasmine&lt;/a&gt; for AngularJS apps in the past, and while that was... rough, to say the least, I think that was more Angular's fault than Jasmine's, and I liked working with Jasmine overall. I've also heard good things about &lt;a href="https://mochajs.org/"&gt;Mocha&lt;/a&gt;, though I've never done more than a couple toy examples myself. And I guess there's this thing called &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt; that has popped up recently as well?&lt;/p&gt;

&lt;p&gt;Are these frameworks still applicable in the server-side world? Are there newer, better frameworks I should be aware of?&lt;/p&gt;

&lt;p&gt;How about for testing database interactions? I'd like to set up a harness that will run our migration scripts to build a little SQLite database on the fly for testing, something like that; is that the best approach?&lt;/p&gt;

&lt;p&gt;I'm relatively new to this kind of testing, and I know we have some very experienced DEVs here, so any advice would be very welcome!&lt;/p&gt;

&lt;p&gt;I'd also be happy to read any other articles that have been written on related subjects, so feel free to drop links!&lt;/p&gt;

&lt;p&gt;Thanks in advance! 🙏😁&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>testing</category>
      <category>discuss</category>
    </item>
    <item>
      <title>My confusions about TypeScript</title>
      <dc:creator>Ken Bellows</dc:creator>
      <pubDate>Fri, 03 Jan 2020 16:23:47 +0000</pubDate>
      <link>https://dev.to/kenbellows/my-confusions-about-typescript-1odm</link>
      <guid>https://dev.to/kenbellows/my-confusions-about-typescript-1odm</guid>
      <description>&lt;p&gt;I have heard endless good things about TypeScript in the last couple years, but I've never really had a chance to use it. So when I was tasked with writing a new API from scratch at work, I decided to use the opportunity to learn TypeScript by jumping into the deep end.&lt;/p&gt;

&lt;p&gt;So far, here are my positive takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I'm a huge fan of the added intellisense in my IDE (VS Code). I've always found the intellisense for regular JavaScript packages to be a bit flaky for some reason, but it's rock solid with TypeScript.&lt;/li&gt;
&lt;li&gt;The "might be undefined" checks have definitely saved me some time by pointing out places where I need to add a few null checks after &lt;code&gt;.get()&lt;/code&gt;ing something from a &lt;code&gt;Map&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;I have always liked being able to spell out my classes in JavaScript; I've often gone to extreme lengths to document JS classes with &lt;a href="https://jsdoc.app/"&gt;JSDoc&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But I've run into a few significant frustrations that have really slowed me down, and I'm hoping some of my much more experienced TypeScript DEV friends will be able to help me figure them out! 😎&lt;/p&gt;

&lt;h2&gt;
  
  
  Class types
&lt;/h2&gt;

&lt;p&gt;I can't figure out how to use or declare class types, especially when I need to pass around subclasses that extend a certain base class. This came up for me because I'm using &lt;a href="https://vincit.github.io/objection.js/"&gt;Objection.js&lt;/a&gt;, an ORM package that makes heavy use of static getters on classes. I need to pass around subclasses of Objection's &lt;code&gt;Model&lt;/code&gt; class to check relationship mappings and make queries, so I need a way to say, "This parameter is a class object that extends &lt;code&gt;Model&lt;/code&gt;". I wish I had something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleRelations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The best I've found so far is to use a rather annoying interface and update it every time I need to use another method from Objection's extensive API, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt;
  &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;QueryBuilder&lt;/span&gt;
  &lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;idColumn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="nx"&gt;relationMappings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;
  &lt;span class="c1"&gt;// etc.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleRelations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This works, but it's rather annoying to have to reinvent the wheel this way. Is there a more explicit way to tell TypeScript, "I mean a &lt;em&gt;class extending this type&lt;/em&gt;, not an &lt;em&gt;instance of this type&lt;/em&gt;"?&lt;/p&gt;

&lt;h2&gt;
  
  
  Overriding methods with different return types
&lt;/h2&gt;

&lt;p&gt;This is more a best-practice question than anything else. I've run into some cases where a base class declares a method that returns a particular type, but subclasses need to override that method and return a different type. One example is the &lt;a href="https://vincit.github.io/objection.js/api/model/static-properties.html#static-idColumn"&gt;&lt;code&gt;idColumn&lt;/code&gt; static getter&lt;/a&gt; used by Objection models, which can return either a &lt;code&gt;string&lt;/code&gt; or a &lt;code&gt;string[]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I've found that if I simply declare the base class as returning one type and the subclass as returning another, I get yelled at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Animal&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;idColumn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Dog&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Animal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;idColumn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="cm"&gt;/* ERROR
Class static side 'typeof Dog' incorrectly extends base class static side 'typeof Animal'.
  Types of property 'idColumn' are incompatible.
    Type 'string[]' is not assignable to type 'string'.
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If I declare the base class with a Union type, that seems to work, although adding another layer of subclass trying to use the original base class's type no breaks because of the middle class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Animal&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;idColumn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Dog&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Animal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;idColumn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Poodle&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Dog&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;idColumn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="cm"&gt;/*
Class static side 'typeof Poodle' incorrectly extends base class static side 'typeof Dog'...
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So I'm now torn. I like to be as specific as I can in my method signatures, but it seems I have two choices here: either always use the full union type &lt;code&gt;string | string[]&lt;/code&gt; as the return type of the &lt;code&gt;idColumn&lt;/code&gt; getter for all subclasses, or simply don't declare a return type for subclasses, only the base class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Animal&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;idColumn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Dog&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Animal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// this?&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;idColumn&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// or this?&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;idColumn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So my question here is, which is better? Is there an accepted paradigmatic solution here? I don't really like either; the former feels slightly misleading, but the latter feels incomplete. I'm leaning toward the latter in this case since it's immediately obvious what the type of a constant return value is, but in more complex cases involving an actual method with some complicated logic, I'm not sure how I'd handle it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dealing with simple objects
&lt;/h2&gt;

&lt;p&gt;Okay, this is a more minor annoyance, but it really bugs me. If I just want to say, "This function accepts/returns a plain ol' object with arbitrary keys and values", the only syntax I can find is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Used once on it's own, that's not the &lt;em&gt;worst&lt;/em&gt; thing I've ever seen, but I have a method that accepts an object-to-object Map and returns another one, and the method signature looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;converter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That's... that's not okay. I've run into more complex example as well, cases where I'm declaring interfaces with nested objects and such, and this syntax makes them near impossible to read. So my solution has been to declare a trivial interface called &lt;code&gt;SimpleObject&lt;/code&gt; to represent, well, a simple object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SimpleObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And like, that &lt;em&gt;works&lt;/em&gt;, but whenever I show anyone my code I have to explain this situation, and it just seems like an oversight that there's apparently no native name for simple objects in TypeScript. Am I missing something?&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thanks to anyone who took the time to read this, and thanks a million to anyone who helps me out or leaves a comment! I'm enjoying TypeScript on the whole, and I'm sure little quirks like this will become natural after a while, but if there is a better way to handle them, I'd love to know! 😁&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>help</category>
    </item>
    <item>
      <title>Chromium and the browser monoculture problem</title>
      <dc:creator>Ken Bellows</dc:creator>
      <pubDate>Wed, 12 Jun 2019 11:37:35 +0000</pubDate>
      <link>https://dev.to/kenbellows/chromium-and-the-browser-monoculture-problem-420n</link>
      <guid>https://dev.to/kenbellows/chromium-and-the-browser-monoculture-problem-420n</guid>
      <description>&lt;p&gt;Okay, folks. This is new territory for me. I'm stepping into the world of....&lt;/p&gt;

&lt;p&gt;&lt;em&gt;t h i n k p i e c e s&lt;/em&gt; 💥🤯💥&lt;/p&gt;

&lt;p&gt;I've been thinking a lot about an oooold problem in the web dev community, one that's been the subject of 🔥flamewars🔥 basically since web browsers have existed: &lt;strong&gt;"browser monoculture"&lt;/strong&gt;. This topic has flared up again recently as a result of &lt;a href="https://blogs.windows.com/windowsexperience/2018/12/06/microsoft-edge-making-the-web-better-through-more-open-source-collaboration/"&gt;Microsoft dropping their EdgeHTML browser engine and moving Edge to Google's Chromium engine&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have a crazy idea. I'm not entirely sold on it myself; I'm sure there are aspects I'm not considering. So I want some feedback on it. But first, I need to lay some groundwork.&lt;/p&gt;

&lt;h1&gt;
  
  
  What does "browser monoculture" even mean, and why should we care?
&lt;/h1&gt;

&lt;p&gt;A "browser monoculture" is when a single browser becomes so dominant that it triggers a chain reaction: it's effectively the only choice, so it's the only browser anyone uses, so it's the only one anyone cares about, so it's the only one &lt;em&gt;developers write code for&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's one main worry: the concern is that if one browser becomes too dominant, developers may begin to ignore other browsers and just target the set of CSS and JavaScript features supported by the dominant browser, never bothering to test for cross-compatibility in other browsers.&lt;/p&gt;

&lt;p&gt;And this isn't unfounded: this is exactly what happened in the early 2000s, at the height of &lt;a href="https://en.wikipedia.org/wiki/Browser_wars"&gt;the browser wars&lt;/a&gt;: Internet Explorer became so absolutely dominant that devs often specifically targeted IE, and many websites simply didn't work in any other browsers. The worst part was that IE was &lt;a href="http://jkorpela.fi/quirks-mode.html"&gt;super quirky&lt;/a&gt;, didn't follow the standards, and was &lt;em&gt;very&lt;/em&gt; slow to change or improve.&lt;/p&gt;

&lt;p&gt;And this is another major concern: when there's really only one player in the market, the push for that browser vendor to follow standards and play by the rules declines; they can effectively do whatever they want. You might think that the developer community would get mad and start moving away from that browser, providing a check to their power, but the thing is, most browser users aren't web developers. Most users don't know or care about this stuff, and they aren't going to start moving to a different browser just because some 🤓&lt;em&gt;nerds&lt;/em&gt;🤓 are complaining about "APIs" and "standards".&lt;/p&gt;

&lt;p&gt;That said, there is a third, less technical concern that non-developers do care a bit about: when everyone uses a single vendor, that vendor then has access to everyone's user habits, data, etc. This is not exactly unfounded, either; many people have big problems with &lt;a href="https://en.wikipedia.org/wiki/Privacy_concerns_regarding_Google"&gt;how Google has handled user privacy issues&lt;/a&gt; in the past.&lt;/p&gt;

&lt;h1&gt;
  
  
  An emerging Chromium monoculture?
&lt;/h1&gt;

&lt;p&gt;And that brings us to recent events. Google Chrome's underlying browser engine is developed as an open source project known as &lt;a href="https://www.chromium.org/Home"&gt;Chromium&lt;/a&gt;. It's designed such that it can be used as the basis for new browsers, and &lt;a href="https://en.wikipedia.org/wiki/Chromium_(web_browser)#Browsers_based_on_Chromium"&gt;&lt;em&gt;many&lt;/em&gt; browsers have been built on top of it&lt;/a&gt;, including browsers you've heard of. &lt;a href="https://web.archive.org/web/20130607164906/http://my.opera.com/ODIN/blog/2013/05/28/a-first-peek-at-opera-15-for-computers"&gt;Opera&lt;/a&gt; and &lt;a href="https://www.smashingmagazine.com/2016/10/whats-the-deal-with-the-samsung-internet-browser/"&gt;Samsung Internet&lt;/a&gt; both moved to Chromium-based builds in 2013, and of course, as mentioned earlier, the big news this year is that &lt;a href="https://blogs.windows.com/windowsexperience/2018/12/06/microsoft-edge-making-the-web-better-through-more-open-source-collaboration/"&gt;Microsoft Edge will be moving to Chromium as well&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So this is the concern: what if enough of the market share ends up on Chromium based browsers that we end up with a Chromium monoculture?&lt;/p&gt;

&lt;p&gt;Looking at current statistics, we do seem to be heading that way:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Browser&lt;/th&gt;
&lt;th&gt;% of global usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;62.70%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safari&lt;/td&gt;
&lt;td&gt;15.89%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;5.07%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Samsung Internet&lt;/td&gt;
&lt;td&gt;3.38%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UC Browser&lt;/td&gt;
&lt;td&gt;3.16%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opera&lt;/td&gt;
&lt;td&gt;2.55%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IE&lt;/td&gt;
&lt;td&gt;2.51%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edge&lt;/td&gt;
&lt;td&gt;2.17%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(Others, each &amp;lt;1%)&lt;/td&gt;
&lt;td&gt;2.57%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
May 2019 global browser usage statistics for browsers with &amp;gt;1% usage &lt;br&gt; (retrieved June 7, 2019, from &lt;a href="http://gs.statcounter.com/browser-market-share"&gt;statcounter.com&lt;/a&gt;)



&lt;p&gt;Chrome alone has over 60% of the global browser market share. Add in Samsung Internet, Opera, and Edge, the top Chromium-based (or soon-to-be-Chromium-based) browsers, and the figure goes up to &lt;em&gt;70.8%!!!&lt;/em&gt; Imagine if Apple decided they were tired of everyone moaning about Safari's missing features (as Safari now tends to lag the furthest behind in implementing cool new web platform APIs) and decided to go the same way as Microsoft. It's unlikely, but I could see it happening.&lt;/p&gt;

&lt;p&gt;Many of the new experimental browser projects of the last several years have also been based on Chromium, such as &lt;a href="https://vivaldi.com/"&gt;Vivaldi&lt;/a&gt;, &lt;a href="https://brave.com/"&gt;Brave&lt;/a&gt;, and &lt;a href="https://www.epicbrowser.com/"&gt;Epic&lt;/a&gt;. And this makes sense to me: Chromium is a very well-established, actively maintained, and easily extensible browser engine. The task of creating complete JavaScript, HTML, and CSS engines from scratch is absolutely monumental; Chromium has literally decades of work behind it (considering it's based on a WebKit fork), and catching up seems impossible, especially for an independent startup. Plus, these browsers aren't primarily trying to improve the state of web languages; they're focused on higher level features like security, privacy, etc. I don't fault them for using Chromium as a base and focusing on the features they care about.&lt;/p&gt;

&lt;p&gt;And that's kind of the point I want to get to: Chromium is beginning to be positioned as a de facto standard for browser engines, sort of a canonical implementation of W3C specifications. But we'll put a pin in that. 📌&lt;/p&gt;

&lt;h1&gt;
  
  
  What about Firefox? 🔥🦊
&lt;/h1&gt;

&lt;p&gt;Back in the days of the browser wars, Firefox was the hero we all needed, breaking up the Internet Explorer monopoly and charging gloriously into the new era of browser diversity and cooperative web standards.&lt;/p&gt;

&lt;p&gt;But unfortunately, tragically, Firefox usage has been falling over the years, really ever since Chrome came on the scene. I think Mozilla has done heroic work recently with Quantum, and they've often led the charge on implementing new web platform features, especially in CSS (&lt;a href="https://twitter.com/ken_bellows/status/1135930281444204544"&gt;subgrid!!! 😭&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;But here's the bottom line: Google is &lt;em&gt;huuuuuuuuge&lt;/em&gt;. They just have &lt;em&gt;so many people&lt;/em&gt; working there. I don't think Mozilla or the open source community around Firefox has enough manpower or institutional support to keep pace with Google.&lt;/p&gt;

&lt;p&gt;And I don't think the right solution is for Google to fire a bunch of their browser team so that Firefox can keep up, either. I love that they're constantly trying cool new things. So what do we do?&lt;/p&gt;

&lt;h1&gt;
  
  
  Do we have a monoculture?
&lt;/h1&gt;

&lt;p&gt;In an important sense, yes. There seems to be at least a good chance of a browser engine monoculture emerging in the next 5-10 years. At the moment, the two primary opponents to Chromium are Safari and Firefox, who collectively hold about 21% of usage. Chrome's usage numbers have been slowly but steadily increasing for years now, and probably will continue to do so. Now that Edge is Chromium based, I imagine its numbers will go up a bit as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Buuuut...&lt;/strong&gt; here's where I'm going to get controversial. I want to posit that this is a different kind of monoculture than what we saw in the 2000s with Internet Explorer 6, and it has the potential to become a &lt;em&gt;very&lt;/em&gt; different kind, one that may not really deserve the name "monoculture". Stick with me here!&lt;/p&gt;

&lt;h1&gt;
  
  
  Is it bad?
&lt;/h1&gt;

&lt;p&gt;Okay, let's dip our toe into controversial waters. Here's a take I've been developing:&lt;/p&gt;

&lt;p&gt;All three of the concerns I laid out earlier are based on a scenario where a single &lt;em&gt;browser&lt;/em&gt; becomes dominant, including all the hooks into a specific corporate structure with solitary business priorities and hooks into the company's proprietary ecosystem (e.g. hooking your Google account into your browser). But Chromium is already not that, and it has the potential to be quite the opposite.&lt;/p&gt;

&lt;p&gt;To recap, here are the three main concerns I've heard raised in browser monoculture discussions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One dominant browser will lead developers to target just that one and ignore compatibility with smaller browsers.&lt;/li&gt;
&lt;li&gt;When a single browser becomes too dominant, it loses a lot of the motivation to follow and contribute to shared web standards.&lt;/li&gt;
&lt;li&gt;The more people use a single browser, the larger the pool of user data and habits available to the company that owns that browser, which raises privacy concerns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These all change quote a bit when you recognize that we aren't talking about a &lt;em&gt;Chrome&lt;/em&gt; monoculture; we're talking &lt;em&gt;Chromium&lt;/em&gt;, the underlying browser engine. As it stands, Chromium is the basis for a variety of browsers, not just Google's flagship. From what I can tell (as a non-expert on Chromium's source, so please correct me), the Googley stuff that makes privacy advocates nervous is independent from the core Chromium browser engine. I mean, Microsoft clearly isn't going to ship an Edge with any hooks to Google's ecosystem still in it, so it's gotta be easily separable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy
&lt;/h2&gt;

&lt;p&gt;For me, this distinction between "browser" and "browser engine" already answers the main concerns about privacy. The engine itself isn't where the privacy issues lie. Heck, two of the Chromium-based experimental browsers I mentioned earlier, &lt;a href="https://brave.com/"&gt;Brave&lt;/a&gt; and &lt;a href="https://www.epicbrowser.com/"&gt;Epic&lt;/a&gt;, are all about providing a more secure, privacy-preserving option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Single Dominant Platform
&lt;/h2&gt;

&lt;p&gt;As for concerns about developers targeting a single web engine if it gets too popular, that's definitely a real thing. &lt;a href="https://www.theverge.com/2018/1/4/16805216/google-chrome-only-sites-internet-explorer-6-web-standards"&gt;Some claim this is happening already with Chrome.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But here's the thing: since it's the base of several different browsers, when Chromium gets a new JavaScript or CSS feature, it's not pushing Chrome ahead of the rest of the pack, it's pushing all of these browsers forward. So when the Verge (in the article linked above) says that Chrome is becoming the new IE, "with web developers primarily optimizing for Chrome and tweaking for rivals later", they should really replace "Chrome" with "Chromium-based browsers". To "optimize for Chrome" is also to optimize for Opera, Vivaldi, Samsung Browser, and soon Edge, because they all use the same engine.&lt;/p&gt;

&lt;p&gt;That being said, this is a problem for Firefox, Safari, and anyone else still trying to maintain a parallel implementation. Let me come back to this in a sec.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Standards
&lt;/h2&gt;

&lt;p&gt;This one is a big deal. Google has historically been a big player in pushing for and developing web platform standards for everyone to use, but as the Verge rightly points out, they've strayed from that message a bit in the last few years:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Whether you blame Google or the often slow moving World Wide Web Consortium (W3C), the results have been particularly evident throughout 2017. Google has been at the center of a lot of “works best with Chrome” messages we’re starting to see appear on the web. Google Meet, Allo, YouTube TV, Google Earth, and YouTube Studio Beta all block Windows 10’s default browser, Microsoft Edge, from accessing them and they all point users to download Chrome instead. Google Meet, Google Earth, and YouTube TV are also not supported on Firefox with messages to download Chrome. Google has publicly promised to support Earth on Edge and Firefox, and the company is “working to bring YouTube TV to more browsers in the future.”&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.theverge.com/2018/1/4/16805216/google-chrome-only-sites-internet-explorer-6-web-standards"&gt;The Verge, "Chrome is turning into the new Internet Explorer 6"&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;This isn't great. But from what I can tell, this is isn't always strictly a matter of Google ignoring the standards; it's often a matter of other browsers not keeping up with Google's pace on implementing cutting edge APIs. Again, Google has a &lt;em&gt;massive&lt;/em&gt; amount of resources to throw around, so IMO this is somewhat inevitable. But once again, since Google is contributing to Chromium, not just Chrome, all Chromium-based browsers get those benefits.&lt;/p&gt;

&lt;h1&gt;
  
  
  My crazy idea
&lt;/h1&gt;

&lt;p&gt;Okay, we did it, we laid all the groundwork. And by this point it might be obvious what my idea is. So here we go:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the entire web standardized on a single browser engine?&lt;/strong&gt; What if Chromium became the basis for a reference implementation of web standards, and all browsers converted to be based on it?&lt;/p&gt;

&lt;p&gt;Now before you get out the pitchforks, let me elaborate.&lt;/p&gt;

&lt;h2&gt;
  
  
  A collaborative engine
&lt;/h2&gt;

&lt;p&gt;I hope it's obvious, but I'll say it anyway: &lt;strong&gt;I'm &lt;em&gt;not&lt;/em&gt; suggesting we cede control of the web to Google.&lt;/strong&gt; Quite the opposite, actually. My vision is that the developers that currently spend countless hours writing the same code in parallel in different codebases instead are united under a single, common codebase.&lt;/p&gt;

&lt;p&gt;Ideally, this codebase would not be controlled by any single company. I'd love to see a common browser engine controlled by an independent nonprofit foundation, like &lt;a href="https://www.python.org/psf/"&gt;the Python Software Foundation&lt;/a&gt;, including (though not primarily made of) a few representatives from each participating browser vendor.&lt;/p&gt;

&lt;p&gt;Just imagine with me for a moment: What if Safari and Firefox and everyone else stopped maintaining separate codebases, duplicating a ton of effort and desperately trying to keep up with the pace of the behemoth that is Google's development team, and instead began contributing to a shared codebase? What if everyone benefitted from everyone else's work?&lt;/p&gt;

&lt;p&gt;We've seen examples of this. Opera developers &lt;a href="https://blogs.opera.com/desktop/2015/10/opera-33-our-contribution-to-chromium/"&gt;have done this in the past&lt;/a&gt;, and the Edge team &lt;a href="https://www.windowslatest.com/2019/05/08/microsoft-details-scrolling-improvements-for-edge-and-chromium/"&gt;has begun doing this as well&lt;/a&gt;, both teams bringing their expertise to benefit Chromium.&lt;/p&gt;

&lt;p&gt;I'm currently very frustrated that while &lt;a href="https://twitter.com/ken_bellows/status/1135930281444204544"&gt;CSS Subgrid has been implemented in Firefox Nightly&lt;/a&gt;, Chromium &lt;a href="https://bugs.chromium.org/p/chromium/issues/detail?id=618969"&gt;has yet to start working on it&lt;/a&gt;. Imagine if the Firefox devs' work contributed to Chrome as well! This problem would vanish!&lt;/p&gt;

&lt;p&gt;And this is a classic problem on the web platform in general: we all get excited by some awesome demo of a new Web API in a certain browser, but the immediate response is, "Sure, but how soon can I actually use that? How soon will all the other browsers implement it?" Maybe there's an opportunity here to get rid of this problem forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about the lost diversity of implementation?
&lt;/h2&gt;

&lt;p&gt;Here's a possible downside I'm worried about: one thing I love about the variety of browser engines currently in the wild is that they often implement the same feature in different ways, and one is often faster than others. I have to imagine (though to be honest I don't know) that there have been occasions when a Chrome dev has seen how Firefox solved a problem and borrowed an idea or two, and vice versa. Would we be giving this up? How big of a loss would this be?&lt;/p&gt;

&lt;p&gt;I have two thoughts here.&lt;/p&gt;

&lt;p&gt;First, I note that this isn't a big deal for other platforms; I don't hear much complaint that there's no big competition for CPython (the Python reference implementation) to encourage alternative ways of implementing features. But maybe that's not a counterargument. Maybe that's a bad thing for the Python community, and they would benefit from some competition. I don't know.&lt;/p&gt;

&lt;p&gt;Second, though, maybe there's a way to preserve this feature of the web to some degree. Yes, all browsers would be built on the same engine. But maybe the engine could be (or already is? Again, I'm no expert) built in such a way that each browser could build initial implementations of features on top of the core engine. Maybe this could develop into a regular process: when a new API is being discussed, or even after the spec is initially published, maybe each browser that's interested writes their implementation of the feature and tries it out. Maybe we spend 6 months or whatever with a different version of the feature in Firefox, Chrome, and Opera, then a committee at the foundation sits down together and hashes out which implementation should be merged into the master branch.&lt;/p&gt;

&lt;p&gt;This is a point I'd love specific feedback on. I'm not sure how it would go down, but it feels like there has to be a way to get it done.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;Okay, that's it. If you've read this far, &lt;strong&gt;holy crap, thank you so much&lt;/strong&gt;. This was a long, rambly one without any practical application, so I really appreciate anyone who gave me that much of their time.&lt;/p&gt;

&lt;p&gt;To say it once more, &lt;strong&gt;I'd really love feedback on this!&lt;/strong&gt; The growing disparity between browsers is something that's been on my mind a lot for a while. I don't think the current trajectory is sustainable, and I don't want either for a single browser to win out while still being corporately controlled, nor for browsers that are able to push ahead of the pack because of greater resources to be held back for longer and longer amounts of time while other browsers struggle to catch up, all the while being criticized as "the new IE6".&lt;/p&gt;

&lt;p&gt;I think we desperately need a new conversation on this topic. This is my contribution. Now please, give me yours! 😁&lt;/p&gt;

&lt;h1&gt;
  
  
  More resources
&lt;/h1&gt;

&lt;p&gt;Here's some other people talking about this topic that have informed my own opinion, including some stuff I linked in the article.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Verge, &lt;a href="https://www.theverge.com/2018/1/4/16805216/google-chrome-only-sites-internet-explorer-6-web-standards"&gt;"Chrome is turning into the new Internet Explorer 6"&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;@shoptalkshow Podcast (&lt;a class="comment-mentioned-user" href="https://dev.to/chriscoyier"&gt;@chriscoyier&lt;/a&gt;
, Dave Rupert),&lt;a href="https://shoptalkshow.com/episodes/362/#t=28:26"&gt;"Breaking Browser News" segment: "Google updates vs the rest of the industry"&lt;/a&gt; - This is what got me started thinking about the aspect of Firefox and other smaller engine teams not being able to keep up with Google's resources&lt;/li&gt;
&lt;li&gt;HTTP203 Podcast, &lt;a href="https://www.youtube.com/watch?v=IskiTVqHp18"&gt;"Browser Monoculture"&lt;/a&gt; - a somewhat ironic 2015 episode of Google's HTTP203 podcast in which Paul and Jake discuss the time Edge asked the community for suggestions, and the top response was "Stop doing your own thing and use WebKit instead"&lt;/li&gt;
&lt;li&gt;Mozilla, &lt;a href="https://blog.mozilla.org/blog/2018/12/06/goodbye-edge/"&gt;"Goodbye EdgeHTML"&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>browsers</category>
      <category>spicytake</category>
      <category>healthydebate</category>
    </item>
    <item>
      <title>AskDev: Real-time group project collaboration tools?</title>
      <dc:creator>Ken Bellows</dc:creator>
      <pubDate>Wed, 22 May 2019 15:15:01 +0000</pubDate>
      <link>https://dev.to/kenbellows/askdev-real-time-group-project-collaboration-tools-1od5</link>
      <guid>https://dev.to/kenbellows/askdev-real-time-group-project-collaboration-tools-1od5</guid>
      <description>&lt;p&gt;Hey DEV! I'm in grad school, and I'm taking a course for the summer semester all about Education Technology. The class is structured almost like an independent study, where we spend the semester researching a topic of personal interest and producing something (original research, a tool to solve a problem, or some educational content). As a web developer with a long-time interest in accessibility, I'm exploring a few different angles for building a tool to address accessibility issues with online courseware (MOOC platforms, learning management systems, that kinda stuff).&lt;/p&gt;

&lt;p&gt;So, I have a question for the community: are you aware of any software/web applications focused on long-distance group collaboration, such as for team projects and stuff? I'm looking for examples with real-time communication capabilities, file sharing (and ideally editing/reviewing) capabilities, shared notes, etc. I haven't had a reason to use this sort of app in the past, so I'm not familiar with any popular platforms or anything. My first thought is the Google suite (Hangouts/Chat + Google Drive basically), but are there more focused platforms out there?&lt;/p&gt;

&lt;p&gt;Thanks in advance!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EDIT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To clarify, I'm not looking for a tool to use myself, I'm looking for tools to examine for accessibility, and I'm thinking about building an alternative myself. So I'm especially looking for full-featured combination tools if they exist, especially tools specifically aimed at coursework and student group projects.&lt;/p&gt;

&lt;p&gt;And to clarify further, I'm definitely also interested in more general solutions that are often used by student groups for collaboration on school projects (e.g. G Suite, Slack), so let me know what you've used, especially if you're a current student!&lt;/p&gt;

</description>
      <category>help</category>
      <category>discuss</category>
      <category>research</category>
      <category>a11y</category>
    </item>
    <item>
      <title>What cool ideas have you seen for integrating new team members?</title>
      <dc:creator>Ken Bellows</dc:creator>
      <pubDate>Mon, 13 May 2019 18:31:05 +0000</pubDate>
      <link>https://dev.to/kenbellows/what-cool-ideas-have-you-seen-for-integrating-new-team-members-cdj</link>
      <guid>https://dev.to/kenbellows/what-cool-ideas-have-you-seen-for-integrating-new-team-members-cdj</guid>
      <description>&lt;p&gt;I came across this tweet from a couple days ago and it really struck a chord with me:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1127248044678737921-139" src="https://platform.twitter.com/embed/Tweet.html?id=1127248044678737921"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1127248044678737921-139');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1127248044678737921&amp;amp;theme=dark"
  }



&lt;/p&gt;
"We hired an engineer a few weeks ago who initiated a 1:1 coffee with a different person every day, until she’d met with the whole dev team. I thought this was an awesome idea, that I will definitely try next time I start a new job, and pass the idea on to you!"



&lt;p&gt;Trying to get to know your new team as quickly as you can, and helping them get to know you at the same time, seems like an amazing idea. It's awesome that this person took it upon themself, and it probably would be even better if it was implemented by the team as a normal part of onboarding. The top reply was actually someone saying that their office did exactly that:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1127633821606862848-678" src="https://platform.twitter.com/embed/Tweet.html?id=1127633821606862848"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1127633821606862848-678');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1127633821606862848&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;This is such a cool notion. Feeling like the new person in the room has often been one of the big barriers for me when I've joined new teams, especially when I was a younger dev on a team full of older and far more experienced people. Being the person in the room with the least experience is already intimidating, and when you don't know anyone on top of it, asking for help can be hard. Plus, no one else in the room knows what your skill level really is, where you need the most help, how they can assist most effectively, all that good stuff.&lt;/p&gt;

&lt;p&gt;All that to say, this seems like an awesome idea, and I hope I'll be able to use it myself. It looks like I'll be taking on a lead developer position soon, and will be building up the team from mostly outside hires in the next 6 months or so. This will be my first team lead position, so stuff like this is exactly what I need in my face right now, while I'm thinking about how to be effective in that role.&lt;/p&gt;

&lt;p&gt;Since reading this tweet, I've been thinking, and I bet there are plenty of other super cool ideas like this out there, ways to help new team members feel like a part of the group quickly, to integrate both with the people and the work painlessly. So who's got one for me? I'd love to hear them!&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>career</category>
      <category>inclusion</category>
    </item>
    <item>
      <title>"currentColor", the first CSS variable</title>
      <dc:creator>Ken Bellows</dc:creator>
      <pubDate>Mon, 29 Apr 2019 13:57:37 +0000</pubDate>
      <link>https://dev.to/kenbellows/currentcolor-the-first-css-variable-12dl</link>
      <guid>https://dev.to/kenbellows/currentcolor-the-first-css-variable-12dl</guid>
      <description>&lt;p&gt;The recent addition to CSS of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/--*" rel="noopener noreferrer"&gt;Custom Properties&lt;/a&gt;, a.k.a. CSS variables, was a very welcome addition to the language. It's opened the door for a lot of really cool techniques; there's a lot that you can only do when you can keep track of the state of your system and keep different bits in sync with each other, and variables give you a way to do that.&lt;/p&gt;

&lt;p&gt;But here's something that developers newer to CSS may not know, and those who've been around often forget: we've actually had another variable in CSS since long before Custom Properties came on the scene. It's much simpler, and limited in scope, so it can't do everything Custom Properties can do, but it's &lt;em&gt;way&lt;/em&gt; better supported, and with a bit of cleverness it can be used to accomplish some pretty cool stuff.&lt;/p&gt;

&lt;p&gt;This variable is the special CSS keyword &lt;code&gt;currentColor&lt;/code&gt;!&lt;/p&gt;

&lt;h1&gt;
  
  
  What is it?
&lt;/h1&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#currentColor_keyword" rel="noopener noreferrer"&gt;&lt;code&gt;currentColor&lt;/code&gt; keyword&lt;/a&gt; is basically what it sounds like: it's a special keyword that holds the value of the &lt;code&gt;color&lt;/code&gt; property at the location where it's used.&lt;/p&gt;

&lt;p&gt;So for a very simple example, you can use it to make a pull quote box with a border that's the same color as the text in the box:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nt"&gt;blockquote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;hotpink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kenbellows/embed/ZZONYN?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Snazzy, I think.&lt;/p&gt;

&lt;p&gt;Interestingly though, in this particular case, the &lt;code&gt;border-color&lt;/code&gt; rule is unnecessary. I always thought that the default &lt;code&gt;border-color&lt;/code&gt; value was &lt;code&gt;black&lt;/code&gt;, or else was set by the browser, but I recently discovered by accident that it actually defaults to &lt;code&gt;currentColor&lt;/code&gt;! So if &lt;code&gt;border-color&lt;/code&gt; just isn't specified, it will use &lt;code&gt;hotpink&lt;/code&gt; by default!&lt;/p&gt;

&lt;p&gt;Several properties actually default to &lt;code&gt;currentColor&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-color" rel="noopener noreferrer"&gt;border-color&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/caret-color" rel="noopener noreferrer"&gt;caret-color&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/column-rule-color" rel="noopener noreferrer"&gt;column-rule-color&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-color" rel="noopener noreferrer"&gt;text-decoration-color&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-emphasis-color" rel="noopener noreferrer"&gt;text-emphasis-color&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/outline-color" rel="noopener noreferrer"&gt;outline-color&lt;/a&gt; (sometimes; read the link and read about the &lt;code&gt;invert&lt;/code&gt; keyword, which is used if it's supported)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the way, I didn't know about almost any of those rules before I looked up which rules default to &lt;code&gt;currentColor&lt;/code&gt;! I need to read more about &lt;code&gt;text-emphasis&lt;/code&gt;; it's really weird and interesting. Man, &lt;em&gt;every&lt;/em&gt; time I dive into the docs to find something specific, I end up learning about something unexpected.&lt;/p&gt;

&lt;h1&gt;
  
  
  Browser support
&lt;/h1&gt;

&lt;p&gt;Of course, the first question you should ask whenever you learn about a cool feature of the web platform that you've never seen before is, "How good is its browser support? Can I actually use it?" Fortunately, the answers for &lt;code&gt;currentColor&lt;/code&gt; are "super good" and "yes!"&lt;/p&gt;

&lt;p&gt;Support actually goes back a really long time. Taking a look at &lt;a href="https://caniuse.com/#feat=currentColor" rel="noopener noreferrer"&gt;caniuse.com's overview of support&lt;/a&gt;, it's supported in every version of Edge, it's been in Chrome and Safari since version 4 (back then they used the same engine under the hood), and it's been in Firefox since version 2! Heck, it's even in Internet Explorer all the way back to IE 9! So don't worry about using it unless you're writing an intranet application for an organization that still hasn't upgraded past IE 8. (Tragically, I'm very aware that they still exist 😢)&lt;/p&gt;

&lt;h1&gt;
  
  
  Demo: Badges
&lt;/h1&gt;

&lt;p&gt;Okay, let's get practical.&lt;/p&gt;

&lt;p&gt;I discovered &lt;code&gt;currentColor&lt;/code&gt; while investigating the best way to implement a design for work. I needed to create a row of colored badges, each with a label on the badge and a numeric counter next to it. The background color of each badge matched the text color of its counter, and the label on each badge was colored to match the site background. For the sake of example, suppose the mockup was something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5w5i47cz0a3goy8271w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5w5i47cz0a3goy8271w.png" alt="Three colored badges with white text in a horizontal row, each followed by a number whose text color matches the badge color: a red "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each of those badges is identical, but for the color. Seems like a good use case for a reusable component! In a modern browser, you could use a CSS variable for the background of the badge and the foreground text color of its counter. But suppose you want/need to support IE 11, Edge 12-14, or another browser that doesn't support custom properties. Or maybe you already have some utility classes in your CSS to define text colors for various situations, like &lt;a href="https://getbootstrap.com/docs/3.4/css/#helper-classes-colors" rel="noopener noreferrer"&gt;Bootstrap's contextual color classes&lt;/a&gt;, e.g. &lt;code&gt;.text-danger&lt;/code&gt; and &lt;code&gt;.text-warning&lt;/code&gt;, so the idea of using the &lt;code&gt;color&lt;/code&gt; property to set the badge color is super convenient. How might you go about it, armed with your new knowledge of &lt;code&gt;currentColor&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;First we'll need a root wrapper element for the component. This is where the color will be set, and it's where you'd add a class to override the color.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"badge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.badge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;cornflowerblue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So what needs to be inside the badge component? Well, at first glance there are two regions: the bit with a colored background and white text, which I'll call the title block, and the digits off to the side that are colored to match the title block's background, which we've been calling the counter.&lt;/p&gt;

&lt;p&gt;The title block definitely needs its own block in the DOM, since it has a background color, but the counter probably doesn't, since it's just sitting off to the side, which is what inline text does anyway. So lets try this markup:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"badge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title-block"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Things
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  12345
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now the fun part! We want the background of the &lt;code&gt;.title-block&lt;/code&gt; to match the &lt;code&gt;color&lt;/code&gt; specified on the &lt;code&gt;.badge&lt;/code&gt;, so we can use &lt;code&gt;background: currentColor&lt;/code&gt;! We also want to set the &lt;code&gt;.title-block&lt;/code&gt;'s text to white, according to our mockup, so let's add &lt;code&gt;color: white&lt;/code&gt; as well. (If you've spotted the problem here, no spoilers!)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.title-block&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Great! Let's see how it looks:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kenbellows/embed/vMRLyP?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Ah. Well. That's not what we wanted at all. The title block seems to be missing... or rather, it's completely white. Why is that?&lt;/p&gt;

&lt;p&gt;Well, here's the thing: CSS is a declarative language, not an imperative one: it doesn't run top-to-bottom, keeping and updating state along the way. What this means is that when we write:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.something&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;the browser doesn't figure out &lt;code&gt;currentColor&lt;/code&gt;'s value, &lt;em&gt;then&lt;/em&gt; set &lt;code&gt;color: white&lt;/code&gt;. Instead, setting &lt;code&gt;color: white&lt;/code&gt; changes the value of &lt;code&gt;currentColor&lt;/code&gt; for the background to &lt;code&gt;white&lt;/code&gt; as well! This is a limitation of &lt;code&gt;currentColor&lt;/code&gt; that you need to keep in mind.&lt;/p&gt;

&lt;p&gt;It's an easy enough fix, if a little annoying. We just need another layer of nesting to encapsulate the &lt;code&gt;color: white&lt;/code&gt; rule. CSS rules often inherit downward to children, but never upward to parents, so this lets &lt;code&gt;.title-block&lt;/code&gt; inherit its &lt;code&gt;currentColor&lt;/code&gt; value from &lt;code&gt;.badge&lt;/code&gt;, while being able to change the &lt;code&gt;color&lt;/code&gt; within its child.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"badge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title-block"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title-block-text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Things
    &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  12345
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.title-block&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentColor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.title-block-text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now let's take another look:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kenbellows/embed/rbdxpJ?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Perfect!&lt;/p&gt;

&lt;p&gt;Now that we've got our component, all we need to do is repeat it a few times and define a color-override class for each be instance.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"error badge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title-block"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title-block-text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Errors
    &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  123
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"warning badge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title-block"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title-block-text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Warnings
    &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  45
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"success badge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title-block"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title-block-text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Successes
    &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  12345
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.warning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;goldenrod&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.success&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kenbellows/embed/mgxPbV?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Awesome! We met the requirement!&lt;/p&gt;

&lt;p&gt;And this little badge component is pretty handy; there's still more to play with. We can try different colors, put emoji in the title area, add a background color to the counter area... Lots of possibilities!&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kenbellows/embed/ZZxEzz?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;What else can you come up with?&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;To recap: there's a cool, undervalued keyword in CSS, &lt;code&gt;currentColor&lt;/code&gt;, that can be used as a variable of sorts for certain use cases. It has much more broad support than CSS Custom Properties (a.k.a. CSS variables), and it is perhaps a bit more intuitive to use when developing a reusable component, since you only need to set the &lt;code&gt;color&lt;/code&gt; property to populate it's value, which feels very nice.&lt;/p&gt;

&lt;p&gt;However, it's clear that &lt;code&gt;currentColor&lt;/code&gt; has limitations. The most glaring when comparing to Custom Properties is that it's only a color value, so it can't be used to store length values, image URLs, or fancy gradients. The other main point of caution is that you can't use an inherited &lt;code&gt;color&lt;/code&gt; value in &lt;code&gt;currentColor&lt;/code&gt;, say for the background or border color, while also setting your own &lt;code&gt;color&lt;/code&gt; for text in the same block; setting &lt;code&gt;color&lt;/code&gt; will update the value of &lt;code&gt;currentColor&lt;/code&gt; everywhere within that block. You need to introduce a child element to keep your &lt;code&gt;color&lt;/code&gt; value in a new scope.&lt;/p&gt;

&lt;p&gt;So in short, it has limits and caveats, but I personally have found &lt;code&gt;currentColor&lt;/code&gt; to be very useful, and it has let me write much cleaner code in a few tricky situations. My absolute favorite feature is just being able to set &lt;code&gt;color&lt;/code&gt; on a root element of a component and see the whole thing update. Give it a try, see what you can do with it!&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Do you use a CSS framework based on CSS Grid?</title>
      <dc:creator>Ken Bellows</dc:creator>
      <pubDate>Wed, 24 Apr 2019 13:29:57 +0000</pubDate>
      <link>https://dev.to/kenbellows/do-you-use-a-css-framework-based-on-css-grid-4fna</link>
      <guid>https://dev.to/kenbellows/do-you-use-a-css-framework-based-on-css-grid-4fna</guid>
      <description>&lt;p&gt;Ever since the advent of CSS Grid, I never feel the need to reach for a CSS framework like Bootstrap or Foundation. I find that Grid gives me everything I need out of the box.&lt;/p&gt;

&lt;p&gt;But with that said, my day job still supports a large user base with very old browsers, so we're unable to use Grid (for now, though that may be changing soon! 🤞), so I haven't used Grid on a huge scale yet, only for small-to-medium size sites. Maybe there's value in a framework for larger, more nested and complex layouts?&lt;/p&gt;

&lt;p&gt;Has anyone here used Grid for larger applications? Do you use a CSS framework that does the Grid stuff for you? Have you tried it and liked/hated it?&lt;/p&gt;

&lt;p&gt;Or have you even used one for smaller ones? Did you find it easier than writing Grid rules by hand? Do I need to take a second look?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>css</category>
      <category>webdev</category>
      <category>askdev</category>
    </item>
    <item>
      <title>Why I care about the Semantic Web</title>
      <dc:creator>Ken Bellows</dc:creator>
      <pubDate>Mon, 22 Apr 2019 13:15:11 +0000</pubDate>
      <link>https://dev.to/kenbellows/why-i-care-about-the-semantic-web-2kn7</link>
      <guid>https://dev.to/kenbellows/why-i-care-about-the-semantic-web-2kn7</guid>
      <description>&lt;p&gt;This one's going to be a bit more personal and a bit less technical than my usual fare. It's based on a response I gave to a comment left on a previous article of mine, and I think it's worth expanding into it's own whole thing.&lt;/p&gt;

&lt;p&gt;I wrote &lt;a href="https://dev.to/kenbellows/stop-using-so-many-divs-an-intro-to-semantic-html-3i9i"&gt;an article&lt;/a&gt; a few weeks ago all about semantic HTML, encouraging web developers to choose a semantic element over a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; whenever there's an appropriate one. The article absolutely exploded (at least, way more than anything I've ever written before), which was just so cool.&lt;/p&gt;

&lt;p&gt;But after having a few conversations in comments and on Twitter, I realized that a lot of readers seemed to be very excited about and focused on one aspect of semantic tags: how much cleaner semantic code can be than traditional &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; slinging. And to be sure, this is an important aspect, but in my opinion it's not the most important thing.&lt;/p&gt;

&lt;p&gt;In particular, one of the conversations I had in the discussion section (the one that prompted this article) was about whether it's a good idea to go beyond the provided semantic tags and using custom non-standard tags to mark up the document with even more readable HTML. In my opinion, this isn't a good idea, and I explained why over there, but this conversation really highlighted for me that if we're not careful, we can focus too much on how markup looks and forget why standards are defined in the first place.&lt;/p&gt;

&lt;h1&gt;
  
  
  Three reasons
&lt;/h1&gt;

&lt;p&gt;After lots of reflection and discussion, I've found that there are basically three main reasons to write semantic HTML, three things that make it something that everyone who writes HTML for the web should understand and use:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code ergonomics&lt;/strong&gt;, a.k.a. it's better for &lt;strong&gt;developers&lt;/strong&gt; - Semantic elements are much easier to read, both as HTML tags and as CSS selectors. Their purpose and meaning are clearly defined, which makes the structure of your document much more apparent. They also often have certain default styles and behaviors that make our lives as developers easier because we don't have to spend time adding them ourselves.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SEO&lt;/strong&gt;, a.k.a. it's better for &lt;strong&gt;business&lt;/strong&gt; - Search engines like Google pay attention to semantic HTML on your page, especially if you move past just semantic elements and use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Microdata"&gt;microdata attributes&lt;/a&gt;, also defined as part of HTML5, to call out the data on your page that search engines care about. Google has been paying attention to this stuff &lt;a href="https://webmasters.googleblog.com/2010/03/microdata-support-for-rich-snippets.html"&gt;since at least 2010&lt;/a&gt;, and they &lt;a href="https://developers.google.com/search/docs/guides/intro-structured-data"&gt;use microdata&lt;/a&gt; to create those cards that come up when you search for a business.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Accessibility&lt;/strong&gt;, a.k.a. it's better for &lt;strong&gt;users&lt;/strong&gt; - Many semantic elements play a very important role in helping users navigate and use your site more easily. This applies double to users who interact with your site in a way other than the mouse + keyboard + monitor + colors + speakers method that is far too often the only perspective considered. Assistive technologies that help users often rely heavily on certain semantic elements to understand web documents, including things like headings (&lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;-&lt;code&gt;&amp;lt;h6&amp;gt;&lt;/code&gt;), region elements like &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt;, the &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; element to call out navigation within the document and/or the rest of the site.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Based on the discussion section on my article and some of the conversations I had surrounding it, I think I made a mistake: I focused a bit too much in my article on reason 1, how much nicer the code is for developers to write, read, and maintain. Don't get me wrong, semantic HTML code definitely is way nicer and more maintainable in almost every case, and that's great for developers. But in my opinion, reason 1 is actually the &lt;em&gt;least&lt;/em&gt; important of the three. And the most important, as you've undoubtedly guessed from the title of this article, is reason 3, accessibility.&lt;/p&gt;

&lt;p&gt;So here's today's thesis statement:&lt;/p&gt;

&lt;h1&gt;
  
  
  Semantic HTML is not primarily about improving code readability
&lt;/h1&gt;

&lt;p&gt;If the primary motivation of semantic HTML was cleaner code that uses tag names to convey the purpose of a element instead of using CSS classes and HTML attributes, then the W3C could have gone even further: instead of standardizing a very specific and not-all-that-exhaustive set of semantic elements, they could have simply standardized the use of custom tag names devised on the fly, the same way we name CSS classes or JavaScript functions. But they didn't, and I think there are good reasons for that.&lt;/p&gt;

&lt;p&gt;To me, the biggest reason is that we need the computer to understand what we're doing. When we talk about the Semantic Web™, we don't just mean that the tags convey semantics to us humans; they hold specific meanings for browsers, which can in turn &lt;a href="https://www.w3.org/TR/html-aam-1.0/"&gt;communicate their meanings out to assistive technologies&lt;/a&gt;. But the browser can't do this with non-standard tags, because those tags don't have any defined meanings or behaviors for the browser to communicate.&lt;/p&gt;

&lt;p&gt;Custom elements, properly defined with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry"&gt;&lt;code&gt;CustomElementRegistry&lt;/code&gt;&lt;/a&gt;, allow developers to provide those semantics to the browser, which is pretty cool. Components defined by a web framework with JavaScript and/or a template backing them are tricky, and have to be done right. By default, they're just non-standard tags too, but the component template can add semantic elements attributes, and the JavaScript can add the behaviors needed to provide accessible behaviors.&lt;/p&gt;

&lt;h1&gt;
  
  
  The bottom line
&lt;/h1&gt;

&lt;p&gt;To reiterate, the Semantic Web isn't just a matter of preference or style or convenience. It has a huge direct impact on the lives of many, many users, those who rely on assistive technology. And assistive technologies in turn rely on the semantics they can parse from the HTML to help those users.&lt;/p&gt;

&lt;p&gt;If you've never tried to use the web with a screen reader before, please do. I think every web developer needs to do this periodically in order to better understand how many of their users interact with the web, and how honestly horrible a lot of the web can be for users who rely on assistive tech. On a site built without any semantics, basically all &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s and &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;s and even non-standard elements, the best a screen-reader can do is read the text top to bottom, with no way to let the user easily navigate the page. But mark it up with semantic elements and the screen-reader can give the user an outline of the page, highlight the important &lt;a href="https://www.w3.org/TR/2019/NOTE-wai-aria-practices-1.1-20190207/examples/landmarks/"&gt;landmarks&lt;/a&gt;, and give them handy jump points to move around the page a hundred times more easily.&lt;/p&gt;

&lt;h1&gt;
  
  
  Forgotten and ignored users
&lt;/h1&gt;

&lt;p&gt;In my experience, &lt;strong&gt;there's a tragic lack of attention paid to semantics in web development training&lt;/strong&gt;, and this knowledge gap actively hurts the users that need it. That's a big part of why I write articles about semantic web technologies and techniques. I want to help spread this information as broadly as possible and make sure every web dev possible finds out about it. Semantic HTML, microdata, ARIA, all these Semantic Web tools are crucial for building a web that's made for everyone. Accessibility should become as foundational to web development training as the difference between a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and a &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This stuff directly impacts the lives of many people, much more directly than we often think or talk about. And that's the big point: because this stuff isn't talked about enough, because it isn't taught in HTML 101 as a foundational part of the platform, we forget about it, and we forget about the users that need it. And not only are they forgotten, but when they are remembered, far too often they are intentionally ignored. I've heard and read too many stories from developers that know and care about accessibility of joining a team, asking about the accessibility shortcomings of the application, and recommending improvements, but being shut down because it would take too much development time and effort. I've experienced this myself.&lt;/p&gt;

&lt;p&gt;And in honesty, they aren't wrong to say that there's a cost to training an established team on accessibility principles and techniques, and updating a large existing codebase to be more accessible. But that's just another huge reason that accessibility should be taught from the start! &lt;a href="http://www.karlgroves.com/2011/11/30/how-expensive-is-accessibility/"&gt;There's no cost to building an accessible application from the start, but there is an initial cost to retro-fitting it later.&lt;/a&gt; But the fact that it costs something should by no means stop anyone from doing it. If you need business motivations: (1) you'll have access to more users, which means more potential customers, (2) your code will be more maintainable, and (3) you'll have better SEO. But also, &lt;em&gt;it's just the right thing to do&lt;/em&gt;, and I don't think we should always frame it in profit terms.&lt;/p&gt;

&lt;p&gt;Ultimately, what I'm trying to do by writing about web semantics and accessibility is to play some small part in improving the experiences of one of the most underserved, forgotten, and ignored groups of users on the web.&lt;/p&gt;

&lt;h1&gt;
  
  
  Further reading
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://www.clarissapeterson.com/2012/11/html5-accessibility/"&gt;Accessibility in HTML5&lt;/a&gt; - an incredible introduction to semantic HTML, the ARIA standard, and general web accessibility concepts and best practices; &lt;em&gt;strong&lt;/em&gt; recommendation for anyone writing HTML, it's a great refresher even for experienced devs&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://www.karlgroves.com/2011/11/30/how-expensive-is-accessibility/"&gt;How Expensive is Web Accessibility?&lt;/a&gt; - a really interesting and practical discussion of how to add accessibility to an existing codebase in an effective (and cost-effective) way&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.html5accessibility.com/"&gt;html5accessibility.com&lt;/a&gt; - implementation status of accessibility features for each element included in HTML5&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.smashingmagazine.com/2015/03/web-accessibility-with-accessibility-api/"&gt;Accessibility APIs: A Key To Web Accessibility&lt;/a&gt; - a Smashing Mag article discussing the basics of how the browser exposes semantics with assistive tech, and how we as developers can take this further with browser APIs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.washington.edu/accessibility/web/landmarks/"&gt;ARIA Landmark Roles&lt;/a&gt; - Landmarks are the most basic, foundational, and easy-to-understand piece of how the browser interprets HTML semantics. Semantic elements introduced in HTML5 often have these roles by default (see "Accessibility in HTML5" link above), so we don't usually need to manually assign roles if we use them&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.w3.org/TR/2019/NOTE-wai-aria-practices-1.1-20190207/examples/landmarks/"&gt;ARIA Landmarks Example&lt;/a&gt; - A set of examples from the W3C demonstrating how to effectively use ARIA Landmarks&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>html</category>
      <category>webdev</category>
      <category>semantics</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Rewriting an old project! Part 2: JavaScript</title>
      <dc:creator>Ken Bellows</dc:creator>
      <pubDate>Fri, 12 Apr 2019 14:48:51 +0000</pubDate>
      <link>https://dev.to/kenbellows/rewriting-an-old-project-part-2-javascript-32mk</link>
      <guid>https://dev.to/kenbellows/rewriting-an-old-project-part-2-javascript-32mk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
This is Part 2 of a miniseries in which I'm revisiting an old weekend hack of mine from college, which I called &lt;a href="https://github.com/kenbellows/jsphere"&gt;jSphere&lt;/a&gt; (because I was lazy and couldn't come up with anything more interesting).&lt;/p&gt;

&lt;p&gt;If you haven't read &lt;a href="https://dev.to/kenbellows/rewriting-an-old-project-part-1-html-css-4o5p"&gt;Part 1&lt;/a&gt;, which was all about the HTML and CSS, I recommend giving it a skim, or at least reading the Backstory section at the beginning.&lt;/p&gt;

&lt;p&gt;As I said last time, I'm not going to discuss what the code &lt;em&gt;does&lt;/em&gt; in this post, just its structure and syntax. But I do plan to write a proper walkthrough and tutorial later.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Last time...
&lt;/h1&gt;

&lt;p&gt;In Part 1, time we went over the markup and styling of , and I already found that pretty interesting. But, I mean, this project is primarily a JavaScript project. It's all about that &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;For reference, here's what we're rebuilding:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kenbellows/embed/MxZrjy?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Okay. Where to begin? Man, there was so much to change about this code, and a lot of it was interwoven, which makes it tough to really talk about step-by-step. So what I'll have to do is just... go for it, I guess.&lt;/p&gt;

&lt;p&gt;Please, feel free to critique or otherwise comment on the code here! I love feedback and (constructive) criticism, I'm always interested in how other people would approach the same tasks, and I've even been known to enjoy a little &lt;a href="https://en.wiktionary.org/wiki/bikeshedding"&gt;bikeshedding&lt;/a&gt; now and then.&lt;/p&gt;

&lt;h1&gt;
  
  
  Minutia: nah.
&lt;/h1&gt;

&lt;p&gt;Let me mention up front here that there are some changes I won't explicitly discuss, because they're too minor. Yes, I changed all the &lt;code&gt;var&lt;/code&gt;s to &lt;code&gt;const&lt;/code&gt;s and &lt;code&gt;let&lt;/code&gt;s, I reordered some functions, I changed all the TitleCasedMethods to normalCamelCase (&lt;em&gt;why did I capitalize the first letter of all my methods? was I insane?&lt;/em&gt;). But you don't care about all those things. I'm going to focus on the really large things, the things I found interesting or funny, and the things that really demonstrate how far the language has come in seven years.&lt;/p&gt;

&lt;p&gt;Let's start with a funny one.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bro, do you even loop?
&lt;/h1&gt;

&lt;p&gt;I fixed a spot where I had done something super weird, maybe because I was rushing and didn't think it through. This is in &lt;code&gt;Sphere&lt;/code&gt;'s constructor, when I'm generating all the &lt;code&gt;Dot&lt;/code&gt; instances needed to represent the points on the surface of the sphere. I use a nested &lt;code&gt;for&lt;/code&gt; loop to move around the sphere in rings, top to bottom, adding dots to its surface as I went. This is fine, but I wrote the loops in a super weird way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;angstep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Loop from 0 to 2*pi, creating one row of points at each step&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;angxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;angxy&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;angxy&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="nx"&gt;angstep&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;angyz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;angyz&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;angyz&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="nx"&gt;angstep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Loop from 0 to 2*pi, creating one point at each step&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;px&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angxy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angyz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;py&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angxy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angyz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;pz&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angyz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;pfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pz&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;py&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;pz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;pfg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;What's up with the &lt;code&gt;i&lt;/code&gt; and &lt;code&gt;j&lt;/code&gt; variables there, hanging out outside of the loops? Why aren't they created and incremented within the loops? I definitely knew that a &lt;code&gt;for&lt;/code&gt; loop can have multiple variables; why didn't I just do this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for (var i=0, angxy=0; angxy&amp;lt;2*Math.PI; i++, angxy+=angstep) { ... }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;But also, why do I even need those index counters in the first place? Why do I use &lt;code&gt;this.points[i] =&lt;/code&gt; and &lt;code&gt;this.points[i][j] =&lt;/code&gt; to add to the arrays? Why aren't I just using &lt;code&gt;[].push()&lt;/code&gt; to add stuff to the array? And why on Earth did I use &lt;code&gt;new Array()&lt;/code&gt; instead of &lt;code&gt;[]&lt;/code&gt;? How much OOP was I on???&lt;/p&gt;

&lt;p&gt;So I got rid of all that nonsense. I also decided to make &lt;code&gt;this.points&lt;/code&gt; a single flat list of points, rather than a 2D matrix with rows, because I realized while refactoring some other code that the &lt;em&gt;only&lt;/em&gt; way I ever referenced &lt;code&gt;this.points&lt;/code&gt; was with a couple of nested loops to reach each point, usually with weird &lt;code&gt;for ... in&lt;/code&gt; loops like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;First, it does &lt;em&gt;work&lt;/em&gt;, but using &lt;code&gt;for...in&lt;/code&gt; loops on arrays like this is strange, and it's unexpected to say the least. I was very confused when I first saw it, and I imagine any other devs reading it would be thrown off as well.&lt;/p&gt;

&lt;p&gt;Second, I never used &lt;code&gt;i&lt;/code&gt; or &lt;code&gt;j&lt;/code&gt; for anything other than indexing into &lt;code&gt;this.points&lt;/code&gt;, which makes this a great candidate for the much more elegant &lt;code&gt;for...of&lt;/code&gt; loop, which didn't exist when I originally wrote this code.&lt;/p&gt;

&lt;p&gt;I refactored to fix all of these problems, and here's where I wound up. The constructor loops were updated to this (which also includes a change to the Dot constructor that I'll talk about in a bit):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The angle delta to use when calculating the surface point positions;&lt;/span&gt;
&lt;span class="c1"&gt;// a larger angstep means fewer points on the surface&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;angstep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="c1"&gt;// Loop from 0 to 2*pi, creating one row of points at each step&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;angxy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;angxy&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;angxy&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="nx"&gt;angstep&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;angyz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;angyz&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;angyz&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="nx"&gt;angstep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Loop from 0 to 2*pi, creating one point at each step&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Dot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angxy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angyz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angxy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angyz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angyz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;fg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angyz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And the loops over the elements of &lt;code&gt;this.points&lt;/code&gt; now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;point&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Ahh... So much better. 😌&lt;/p&gt;

&lt;h1&gt;
  
  
  Classes!
&lt;/h1&gt;

&lt;p&gt;Since this project was all about this interactive sphere composed of a bunch of dots on its surface, and since I was doing a CS degree in 2012 and the Functional Revolution of the last few years hadn't begun yet, the project is largely composed of two high-level object types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Dot&lt;/code&gt; - represents a single dot on the surface of the sphere&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Sphere&lt;/code&gt; - represents the whole sphere, with all of its &lt;code&gt;Dot&lt;/code&gt;s&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In 2012, the native &lt;code&gt;class&lt;/code&gt; was yet to be standardized, so I wrote everything in the old-school &lt;code&gt;function&lt;/code&gt;-as-a-constructor style. For exapmle, here's part of the &lt;code&gt;Sphere&lt;/code&gt; constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;fg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;fgColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;bgColor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Default Values:  { x: 0, y: 0, z: 0, fg: true, fgcolor: #7EE37E, bgcolor: "#787878" }&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;fg&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fgColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;fgColor&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;fgColor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#7EE37E&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bgColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;bgColor&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;bgColor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#787878&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Store the context's fillStyle&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;tmpStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Set the fillStyle for the dot&lt;/span&gt;
        &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fg&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fgColor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bgColor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Draw the dot&lt;/span&gt;
        &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillCircle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fg&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Restore the previous fillStyle&lt;/span&gt;
        &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tmpStyle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, there's another issue here, which probably had to do with my limited understanding of JS prototypes: every instance of &lt;code&gt;Dot&lt;/code&gt; defines its own copy of the &lt;code&gt;Draw&lt;/code&gt; function, rather than referring to a class method. What I should have written is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;fg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;fgColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;bgColor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;Dot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;protoype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Store the context's fillStyle&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;tmpStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Set the fillStyle for the dot&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fg&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fgColor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bgColor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Draw the dot&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillCircle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fg&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Restore the previous fillStyle&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tmpStyle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This declares a single copy of the function, rather than wasting memory on a new function every time.&lt;/p&gt;

&lt;p&gt;But that's a side issue anyway, since the native &lt;code&gt;class&lt;/code&gt;es introduced in ES2015 (aka ES6) handles that under the hood!&lt;/p&gt;

&lt;p&gt;But before we get to that, let mention one more thing: ES2015 also introduced &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring"&gt;object destructuring&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters"&gt;function parameter default values&lt;/a&gt;, and &lt;a href="https://davidwalsh.name/destructuring-function-arguments"&gt;using both of those things together&lt;/a&gt; to basically get the equivalent of named parameters with default values, as seen in languages like Python and Ruby!&lt;/p&gt;

&lt;p&gt;Aaaaand one &lt;em&gt;more&lt;/em&gt; thing: ES2015 &lt;em&gt;also&lt;/em&gt; introduced &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign"&gt;&lt;code&gt;Object.assign()&lt;/code&gt;&lt;/a&gt;, which I like to use  to initialize &lt;code&gt;this&lt;/code&gt; with properties much more succinctly than the traditional one-by-one method. (Man, ES2015 was some good stuff!)&lt;/p&gt;

&lt;p&gt;Using these two techniques together, I can eliminate this nasty snippet from the the &lt;code&gt;Dot&lt;/code&gt; constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Gross 🤢&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;fg&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fgColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;fgColor&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;fgColor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#7EE37E&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bgColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;bgColor&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;bgColor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#787878&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Putting all these improvements together, I can rewrite the &lt;code&gt;Dot&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Dot&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fgColor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#7EE37E&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bgColor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#787878&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fgColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bgColor&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Store the context's fillStyle&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;tmpStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Set the fillStyle for the dot&lt;/span&gt;
        &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fg&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fgColor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bgColor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Draw the dot&lt;/span&gt;
        &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillCircle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fg&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Restore the previous fillStyle&lt;/span&gt;
        &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tmpStyle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So much cleaner!&lt;/p&gt;

&lt;h1&gt;
  
  
  Re-separating my concerns
&lt;/h1&gt;

&lt;p&gt;As I was rewriting my classes as I just described, I started to find places where &lt;code&gt;Dot&lt;/code&gt;s were doing stuff that was better handled by &lt;code&gt;Sphere&lt;/code&gt;, and &lt;code&gt;Sphere&lt;/code&gt; was repeatedly performing operations on each &lt;code&gt;Dot&lt;/code&gt; that could be much more cleanly handled internally by each of them.&lt;/p&gt;

&lt;p&gt;First, that &lt;code&gt;Draw&lt;/code&gt; method of the &lt;code&gt;Dot&lt;/code&gt; class up there? Yeah, it was never actually being called. It turned out that I needed some context from the &lt;code&gt;Sphere&lt;/code&gt; about each &lt;code&gt;Dot&lt;/code&gt; in order to draw it correctly, namely where it was located on the surface of the sphere and how the sphere was currently rotated. And rather than pass all that context around to each &lt;code&gt;Dot&lt;/code&gt;, it was much cleaner to do all my drawing right from the &lt;code&gt;Sphere&lt;/code&gt; class. So... I deleted the &lt;code&gt;Draw&lt;/code&gt; method on &lt;code&gt;Dot&lt;/code&gt;, as well as the &lt;code&gt;fgColor&lt;/code&gt; and &lt;code&gt;bgColor&lt;/code&gt; properties that went with it.&lt;/p&gt;

&lt;p&gt;Second, there are two major ways that the &lt;code&gt;Dot&lt;/code&gt;s are changed to move the &lt;code&gt;Sphere&lt;/code&gt; around: they're &lt;em&gt;translated&lt;/em&gt; (moved around), and they're &lt;em&gt;scaled&lt;/em&gt; (grown or shrunken). I had a bunch of repeated code in my event handlers where each &lt;code&gt;Dot&lt;/code&gt;'s properties were being manually recalculated and updated in the same way, so I figured I'd &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;DRY&lt;/a&gt; up my code and move the logic to change a &lt;code&gt;Dot&lt;/code&gt; into the &lt;code&gt;Dot&lt;/code&gt; class itself. I gave &lt;code&gt;Dot&lt;/code&gt; two new methods: &lt;code&gt;scale&lt;/code&gt; and &lt;code&gt;translate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After all that, my &lt;code&gt;Dot&lt;/code&gt; class looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Dot&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fg&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Scale this dot's position by multiplying all coordinates by the given scale factor
     * @param {Number} scaleFactor
     */&lt;/span&gt;
    &lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scaleFactor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="nx"&gt;scaleFactor&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="nx"&gt;scaleFactor&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="nx"&gt;scaleFactor&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Move this dot to a new position indicated by the given x, y, and z distances
     * @param {Number} [x=0]
     * @param {Number} [y=0]
     * @param {Number} [z=0]
     */&lt;/span&gt;
    &lt;span class="nx"&gt;translate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Don't mess with the prototype!
&lt;/h1&gt;

&lt;p&gt;I made several changes that came from experience. Before I left college, I really had little to no knowledge of accepted community best practices, or why they were important. For example, I have a few utility functions for drawing on the canvas. In my old code, I added them to the &lt;code&gt;CanvasRenderingContext2D&lt;/code&gt; prototype directly, as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;fillCircle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctxFillCircle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is not ideal. Suppose a real &lt;code&gt;ctx.fillCircle&lt;/code&gt; were added to the spec one day, and it was different from my code. In this minor demo it probably doesn't matter much, as I'm the only one working with the code, but in a project with multiple authors, it could be very confusing to come across a standardized method that looks different than it should. Even future me might be confused!&lt;/p&gt;

&lt;p&gt;So I refactored: a &lt;code&gt;ctx&lt;/code&gt; parameter was added to each function, all references to &lt;code&gt;this&lt;/code&gt; in the functions were changed to &lt;code&gt;ctx&lt;/code&gt;,  I removed the above lines that modified the &lt;code&gt;CanvasRenderingContext2D&lt;/code&gt; prototype, and I changed the code to call the functions directly and pass &lt;code&gt;ctx&lt;/code&gt; as the first argument rather than calling them as a method of &lt;code&gt;ctx&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Removing jQuery
&lt;/h1&gt;

&lt;p&gt;Finally, jQuery. I love it, it did a lot of good for the web, but in most cases, including mine, it's no longer needed.&lt;/p&gt;

&lt;p&gt;I basically used jQuery for three things in this project, two of which are trivially replaced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I used the &lt;code&gt;$(function() { ... })&lt;/code&gt; wrapper to delay code execution until the page loaded. As I discussed in Part 1, this is unnecessary if you add the &lt;code&gt;defer&lt;/code&gt; attribute to any &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags that need to wait. (&lt;a href="https://dev.to/crazytim/comment/a255"&gt;Thanks again, @crazytim!&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;I used the classic &lt;code&gt;$()&lt;/code&gt; selector function in several places to get elements. This is now (and actually was then, though I was unfamiliar with it) a native part of the platform with &lt;code&gt;document.querySelector&lt;/code&gt; and &lt;code&gt;document.querySelectorAll&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The tough one: I used the &lt;code&gt;jquery.events.drag&lt;/code&gt; plugin to handle mouse-based interactivity. This one takes slightly more doing to replace.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first two are super basic, and I won't bother showing them. Instead, I'll focus on that third one: the &lt;code&gt;jquery.event.drag&lt;/code&gt; event plugin.&lt;/p&gt;

&lt;p&gt;Here's how I was using it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#canvas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;drag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;init&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="nx"&gt;startX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;startY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;drag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctrlKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;altKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HiddenFun2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;startX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;startY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Zoom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;startX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;startY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shiftKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;altKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HiddenFun1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;startX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;startY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;startX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;startY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;        
            &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;startX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;startY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;startX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;startY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So when dragging begins, store the starting position of the cursor, then when dragging continues, perform certain actions based on the modifier keys used, and update the cursor position for the next movement.&lt;/p&gt;

&lt;p&gt;So how would we do this with regular old &lt;code&gt;mouse&lt;/code&gt; events?&lt;/p&gt;

&lt;p&gt;I implemented it in three stages: drag start, drag, and drag end. The drag start event is &lt;code&gt;mousedown&lt;/code&gt;; we'll record the starting position of the cursor, and add the drag and drag end event listeners. The drag event will be &lt;code&gt;mousemove&lt;/code&gt;; here we'll do all the logic to check for modifier keys and actually manipulate the sphere. Finally, I'll use two events for drag stop: &lt;code&gt;mouseup&lt;/code&gt; and &lt;code&gt;mouseout&lt;/code&gt;. That way, we don't get that weird case where you move your mouse out of the window and then let go, and the app is stuck in the dragging state until you click again. Those events just remove the event handlers registered on drag start.&lt;/p&gt;

&lt;p&gt;I decided to add &lt;code&gt;mousemove&lt;/code&gt;, &lt;code&gt;mouseup&lt;/code&gt;, and &lt;code&gt;mouseout&lt;/code&gt; to the &lt;code&gt;window&lt;/code&gt; so that you can move your mouse around the page after clicking. And I had to suppress the &lt;code&gt;mouseout&lt;/code&gt; event on the &lt;code&gt;canvas&lt;/code&gt; and &lt;code&gt;body&lt;/code&gt; elements using &lt;code&gt;event.stopPropagation()&lt;/code&gt; to prevent prematurely triggering the drag stop when moving your mouse in and out of the canvas.&lt;/p&gt;

&lt;p&gt;Here's what all that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousedown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragStartHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;dragStartHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="nx"&gt;dragOrigin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragStopHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragStopHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;dragHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctrlKey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metaKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;altKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hiddenFun2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shiftKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;altKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hiddenFun1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;dragStopHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragStopHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragStopHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So even that wasn't too bad! But then I realized I had forgotten about...&lt;/p&gt;

&lt;h1&gt;
  
  
  Touch support for mobile 😭
&lt;/h1&gt;

&lt;p&gt;After implementing my updated code, I visited the page on my phone; nothing worked. Then it hit me: &lt;em&gt;phones don't have mouses!&lt;/em&gt; 🤦&lt;/p&gt;

&lt;p&gt;Some quick research reminded me of the relatively recent &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent"&gt;&lt;code&gt;pointer&lt;/code&gt; events&lt;/a&gt; (&lt;code&gt;pointermove&lt;/code&gt;, &lt;code&gt;pointerup&lt;/code&gt;, etc.), which are made to unify lots of different kinds of pointer-ish interactions: mouse, touch, stylus, new stuff we haven't thought of yet. The problem (for now) is that &lt;a href="https://caniuse.com/#feat=pointer"&gt;mobile support is spotty&lt;/a&gt;: &lt;code&gt;pointer&lt;/code&gt; events work on Chrome for Android, Opera Mobile, Android Browser, Samsung Browser, and IE Mobile (???), but not Firefox for Android, iOS Safari, or most other mobile browsers..&lt;/p&gt;

&lt;p&gt;The other problem is that the controls to do anything other than spin the sphere currently require modifier keys on the keyboard to be pressed (Shift+drag to pan, Ctrl+drag to zoom). So &lt;code&gt;pointer&lt;/code&gt; events on their own will only enable rotating, nothing else.&lt;/p&gt;

&lt;p&gt;A proper solution would be to learn about &lt;code&gt;touch&lt;/code&gt; events and how to implement gestures like pinch-zoom and 2-finger-pan, but I'll be honest, I looked into it a little and it's &lt;em&gt;so&lt;/em&gt; complex! More than I want to get into right now. So the &lt;code&gt;pointer&lt;/code&gt; events will have to do as a half-measure that only fixes rotating and only works on some mobile devices. And I'm not thrilled about it 😞&lt;/p&gt;

&lt;p&gt;Anyway, here's what all of that looks like. I'm testing for &lt;code&gt;pointer&lt;/code&gt; event support by checking whether the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; has an &lt;code&gt;onpointermove&lt;/code&gt; property, and if not I fall back to &lt;code&gt;mouse&lt;/code&gt; events, since there are desktop browsers that don't support &lt;code&gt;pointer&lt;/code&gt; events, too (looking at you, Safari 😡).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Prefer 'pointer' events when available&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pointerEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onpointermove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pointer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Shorthands for 'mouse' or 'pointer' events&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;downEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;upEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;moveEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outEvt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;move&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;out&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evtType&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pointerEvent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;evtType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Drag events&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;downEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragStartHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;dragStartHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="nx"&gt;dragOrigin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;upEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragStopHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragStopHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;dragHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctrlKey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metaKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;altKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hiddenFun2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shiftKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;altKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hiddenFun1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;sphere&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;dragOrigin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;dragStopHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moveEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;upEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragStopHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragStopHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outEvt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;By the way, while working on this I discovered that &lt;a href="https://developers.google.com/web/tools/chrome-devtools/device-mode/"&gt;Chrome's dev tools are very nice for emulating mobile devices with touch events&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Oh, one final thing. I had to add a CSS rule to the canvas to make touch work properly: &lt;code&gt;touch-action: none;&lt;/code&gt;. This basically prevents the browser from capturing touch events to try and scroll the page, and lets me use them in my JavaScript.&lt;/p&gt;

&lt;h1&gt;
  
  
  Finished product
&lt;/h1&gt;

&lt;p&gt;It was a lot of iteration, and to be honest I tweaked it a bunch more while writing this article, but it's out there! Here's what it looks like with all the updates:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kenbellows/embed/bZzxMP?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;There are still things I'd like to fix. The biggest issue I have is that it's not mobile friendly, really at all. Not only do zoom and pan not work on mobile, but the webpage itself is almost completely non-responsive. I'd like to add some CSS to handle especially small page sizes more cleanly. Maybe I'll tweak it some more after this 🤔 As I said at the top, I'd love any feedback you might have! Heck, if you're feeling generous, make a PR against &lt;a href="https://github.com/kenbellows/jsphere"&gt;the repo&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;But here's my overall takeaway: HTML, CSS, and JavaScript have improved a ton in the last 7 years, and so have I as a developer. Experience is an amazing thing!&lt;/p&gt;

&lt;p&gt;In the final part of this series, which I'll write...eventually, I'll walk through the JavaScript itself and talk about how it works. That one will really be a standalone tutorial on how to build a thing with &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; in vanilla JavaScript, not really dependent on these previous two posts.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>css</category>
      <category>html</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Rewriting an old project! Part 1: HTML &amp; CSS</title>
      <dc:creator>Ken Bellows</dc:creator>
      <pubDate>Mon, 08 Apr 2019 20:52:32 +0000</pubDate>
      <link>https://dev.to/kenbellows/rewriting-an-old-project-part-1-html-css-4o5p</link>
      <guid>https://dev.to/kenbellows/rewriting-an-old-project-part-1-html-css-4o5p</guid>
      <description>&lt;h1&gt;
  
  
  Backstory
&lt;/h1&gt;

&lt;p&gt;I'm a guy who's been around the web for a while. I learned JavaScript at age 12 (talking 2003 here, IIRC) when I found &lt;a href="https://www.amazon.com/JavaScript-Weekend-Course-Steven-Disbrow/dp/0764548042"&gt;Steven W. Disbrow's &lt;em&gt;JavaScript Weekend Crash Course&lt;/em&gt;&lt;/a&gt; on my dad's bookshelf, sat down with it at the Windows 95 machine my dad had rescued for me from the trash heap at his corporate IT job, opened Notepad and IE6, and started hacking away. As I recall, this is roughly the code that got me hooked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;10000000000000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And of course, the reason it got me hooked was that, when I added enough zeroes to the number in the conditional, it crashed my browser. And that meant I was a&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;..................................................................................
..................888.....................888.....................................
..................888.....................888.....................................
..................888.....................888.....................................
..................88888b...8888b....d8888b888..888..d88b..888d888.................
..................888."88b...."88bd88P"...888..88Pd8P..Y8b888P"...................
..................888..888.d888888888.....888888K.88888888888.....................
..................888..888888..888Y88b....888."88bY8b.....888.....................
..................888..888"Y888888."Y8888P888..888."Y8888.888.....................
..................................................................................
.............._____________________________________________________...............
....._,aad888P""""""""""Y888888888888888888888888888888P"""""""""""Y888baa,_......
....aP'$$$$$$$$$$$$$$$$$$$$$`""Ybaa,.........,aadP""'$$$$$$$$$$$$$$$$$$$$$`Ya.....
...dP$$$$$$$$$$$$$$$$$$$$$$$$$$$$$`"b,.....,d"'$$$$$$$$$$$$$$$$$$$$$$$$$$$$$Yb....
...8l$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$8l_____8l$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$8l...
..[8l$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$8l"""""8l$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$8l]..
...8l$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$d8.......8b$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$8l...
...8l$$$$$$$$$$$$$$$$$$$$$$$$$$$$$dP/.......\Yb$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$8l...
...8l$$$$$$$$$$$$$$$$$$$$$$$$$$$,dP/.........\Yb,$$$$$$$$$$$$$$$$$$$$$$$$$$$$8l...
...8l$$$$$$$$$$$$$$$$$$$$$$$$,adP'/...........\`Yba,$$$$$$$$$$$$$$$$$$$$$$$$$8l...
...Yb$$$$$$$$$$$$$$$$$$$$$,adP'...................`Yba,$$$$$$$$$$$$$$$$$$$$$dP....
....Yb$$$$$$$$$$$$$$$$,aadP'.........................`Ybaa,$$$$$$$$$$$$$$$$dP.....
.....`Yb$$$$$$$$$$,aadP'.................................`Ybaa,$$$$$$$$$$dP'/.....
.......`Ybaaaaad8P"'.........................................`"Y8baaaaadP'........
..................................................................................
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
hacker 😎&lt;br&gt;(btw, ascii art lifted from &lt;a href="http://ascii.co.uk/art/hacker"&gt;here&lt;/a&gt; and &lt;a href="https://www.asciiart.eu/clothing-and-accessories/glasses"&gt;here&lt;/a&gt;, with some modifications)





&lt;p&gt;Anyhow. Fast forward like 8 or 9 years. I'm at college, senior year, 2012 I think, avoiding work. I've never lost my love for the web, though I'm a little closeted about it, given that it was the height of the "JS Sucks" movement. But I often spent a portion of my procrastination hours hacking on small JS projects to get my hit. I ran across a tutorial (probably &lt;a href="http://rectangleworld.com/blog/archives/298"&gt;this one&lt;/a&gt;, though I'm not sure) talking about the relatively new HTML5 &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; and how to make a little faux-3D sphere using dots with varying shades of gray to simulate depth. And I thought the result was cool, so I decided to try and figure it out on my own without reading the tutorial.&lt;/p&gt;

&lt;p&gt;I did, and I was pretty happy with the result. I even added some interactivity that the original didn't have, which was fun to figure out. Here it is, copy-pasted into a CodePen:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kenbellows/embed/MxZrjy?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Looking back at the code now, it's still not terrible for the time. But man, it needs some updating. 2012 was a long time ago, and both I and the web have significantly improved since then.&lt;/p&gt;

&lt;p&gt;So I thought I'd update the code, and it seemed like a fun idea to write it up as I go, to see what's changed in our wonderful web world (www) since 2012.&lt;/p&gt;

&lt;p&gt;If you want to follow along, the repo is here: &lt;a href="https://github.com/kenbellows/jsphere/"&gt;https://github.com/kenbellows/jsphere/&lt;/a&gt;&lt;br&gt;
And the demo is here: &lt;a href="https://kenbellows.github.io/jsphere/"&gt;https://kenbellows.github.io/jsphere/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repo now has the new code pushed to the &lt;code&gt;master&lt;/code&gt; and &lt;code&gt;gh-pages&lt;/code&gt; branches, but I'll keep around the prior state of things in a branch named &lt;a href="https://github.com/kenbellows/jsphere/tree/gh-pages__old"&gt;&lt;code&gt;gh-pages__old&lt;/code&gt;&lt;/a&gt;, so check there for the complete previous code that went basically untouched for around 7 years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; I'm not going to be discussing the &lt;em&gt;content&lt;/em&gt; of the code here, what the code does or anything, just how I've updated the structure and syntax and such. However, I do plan to write a final post that discusses what the code actually does, and walks through it in proper tutorial style.&lt;/p&gt;
&lt;h1&gt;
  
  
  HTML
&lt;/h1&gt;

&lt;p&gt;So first. That HTML. Not great. Here's the structure, very slightly abbreviated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"text/html; charset=UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./js/jquery-1.7.1.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./js/jquery.event.drag-2.0.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./js/sphere.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- github link banner omitted --&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"header-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;jSphere&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"header-subtitle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Just to see what I could do.&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"controls"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"controls-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Controls&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"controls-body"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"c-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Rotate:  Click and drag&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"c-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Pan:     Hold shift, click and drag&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"c-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Zoom:    Hold ctrl, click and drag&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"canvas"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"700"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"psst"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Psst. There are a couple (that's 2) hidden key combos that do some things that I found by accident, so play around.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Okay. Reflections:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;del&gt;No need to put the script tags in the header. Just drop them at the end of the body. This removes the need for the jQuery &lt;code&gt;$(function(){ ... })&lt;/code&gt; wrapper.&lt;/del&gt; &lt;strong&gt;Update:&lt;/strong&gt; As pointed out in &lt;a href="https://dev.to/crazytim/comment/a255"&gt;a comment by @crazytim&lt;/a&gt;, this approach is &lt;em&gt;also&lt;/em&gt; pretty outdated! &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;s at the end of the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; can't start downloading until the rest of the HTML is parsed and dealt with, which slows things down, and there's a standard solution to this: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer"&gt;the &lt;code&gt;defer&lt;/code&gt; attribute&lt;/a&gt;, which tells the browser to download the file so it's ready, but wait until the DOM is fully parsed before running it. Exactly what I need! So I should really keep my script in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, but use &lt;code&gt;&amp;lt;script defer ...&amp;gt;&lt;/code&gt;, which sill gets rid of the &lt;code&gt;$(function(){ ... })&lt;/code&gt; wrapper. Thanks Tim!&lt;/li&gt;
&lt;li&gt;Also, we don't need jQuery anymore. It made the world a ton easier back in the day, but these days it's just not necessary, especially for a project as small as this one. I'm pretty sure I only imported it to use the &lt;code&gt;$()&lt;/code&gt; element selector function and the &lt;code&gt;jquery.event.drag&lt;/code&gt; plugin, both of which can be eliminated now.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/kenbellows/stop-using-so-many-divs-an-intro-to-semantic-html-3i9i"&gt;Semantic HTML&lt;/a&gt;! I will never write another &lt;code&gt;&amp;lt;div id="header"&amp;gt;&lt;/code&gt; again. Just use &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;! Same for &lt;code&gt;&amp;lt;div id="content"&amp;gt;&lt;/code&gt;: that's basically just &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Man&lt;/em&gt; I loved &lt;code&gt;id&lt;/code&gt;s! &lt;em&gt;Every single element in the body&lt;/em&gt; has an &lt;code&gt;id&lt;/code&gt;! That's so unnecessary. We have a rich CSS selector syntax (and did back then, too) to take care of finding the right things on the page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, here's how I'm restructuring the page. I've collapsed &lt;code&gt;id="controls"&lt;/code&gt; and &lt;code&gt;id="content"&lt;/code&gt; within the &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; block and significantly rearranged its insides, and that will be reflected in the CSS as well, but we'll get there next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"text/html; charset=UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"./css/sphere.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"./css/fonts.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./js/sphere.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- github link banner omitted --&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;jSphere&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Just to see what I could do.&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"controls"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Controls&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"controls-listing"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Rotate:  Click and drag&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Pan:     Hold shift, click and drag&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Zoom:    Hold ctrl, click and drag&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"canvas"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"700"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"psst"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Psst. There are a couple (that's 2) hidden key combos that do some things that I found by accident, so play around.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The big changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;del&gt;Moved the script to the end of the body&lt;/del&gt; (see note above) Added the &lt;code&gt;defer&lt;/code&gt; attribute to the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag to eliminate the need for any &lt;code&gt;onready&lt;/code&gt; type event handlers.&lt;/li&gt;
&lt;li&gt;Removed jQuery. I'll have to redo the mouse interaction code that relied on the &lt;code&gt;jquery.event.drag&lt;/code&gt; plugin, but that's okay; it's much easier these days anyhow.&lt;/li&gt;
&lt;li&gt;Refactored the markup to use the semantic elements &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt;. Restructured the code to be more semantic. The previous structure was heavily influenced by the CSS layout modes available at the time (or lack thereof), so it's going to be a lot simpler to do what I want now.&lt;/li&gt;
&lt;li&gt;On that note, got rid of almost every &lt;code&gt;id&lt;/code&gt;. The only one I left is the one for the &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt; tag, and I do think that sections need labels; there's often more than one, and they often need to be specifically selected in CSS, so they have value IMO.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Okay, now for the CSS to match.&lt;/p&gt;

&lt;h1&gt;
  
  
  CSS
&lt;/h1&gt;

&lt;p&gt;Here's the old stuff. Remember that it goes with the first HTML sample above. I'm skipping the font and color stuff, as it's all pretty standard and stayed the same. The interesting bits are the layout and selectors related to the refactored HTML from above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#header-title&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;#header-subtitle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#header-subtitle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;810px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;900px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-50px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#canvas&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#controls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;810&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-50px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#controls-title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;75px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#controls-body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#c-1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/* Rotate */&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#c-2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/* Pan */&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#c-3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;/* Zoom */&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#psst&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Hoo that's a lot of absolute positioning and fixed lengths. Now, I do want to emphasize that this was a little weekend hack that I didn't want to spend a lot of time on, and I didn't have all the layout methods available to me that we have now, but still... seems unnecessary. I was rushing, I think.&lt;/p&gt;

&lt;p&gt;Of particular interest are the three &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tags describing the Rotate, Pan, and Zoom controls. I gave them each an &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;#c-1&lt;/code&gt;, &lt;code&gt;#c-2&lt;/code&gt;, and &lt;code&gt;#c-3&lt;/code&gt;, and positioned them absolutely to get the alignment I wanted. I think this was caused by the difficulty of centering one item and side-aligning the other two at the time. You could do it with tables, but... tables for layout were taboo even in 2012. I wouldn't dare except as an absolute last resort. Absolute positioning was definitely better in my mind.&lt;/p&gt;

&lt;p&gt;(In retrospect, this was very silly of me, and a quick CSS table (I think those were supported at the time?) or even an HTML table would have been a much cleaner solution.)&lt;/p&gt;

&lt;p&gt;Nowadays, we have two more options for layout: Flexbox and Grid. My first instinct when rewriting was just to lay down a quick flex container with a &lt;code&gt;justify-items: space-between&lt;/code&gt;, since it's fewer lines. But I remembered after implementing that this is a classic corner-case where Flexbox acts slightly differently than you might expect, unless you really understand how it works. &lt;/p&gt;

&lt;p&gt;Don't get me wrong, it behaves in a totally fine and predictable and useful way, just not how I thought it did. I initially wrote up an explanation of that distinction here, comparing it to CSS Grid, but it turned out super long, so I published it in its own post here: &lt;a href="https://dev.to/kenbellows/flex-items-are-not-grid-columns-4p2i"&gt;Flex items are not grid columns&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The outcome of that post is that I used the following CSS on the parent &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; element from the new HTML above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="nf"&gt;#controls-listing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;grid-auto-flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;/* automatically place new items in new columns */&lt;/span&gt;
    &lt;span class="py"&gt;grid-auto-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c"&gt;/* auto-columns should be 1fr wide */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I used the auto-flow method rather than, say, &lt;code&gt;grid-template-columns: 1fr 1fr 1fr&lt;/code&gt;, &lt;br&gt;
because I like to future-proof my code as long as it doesn't add much work, and this method neatly handles any number of items, so I can add more controls later if I want to.&lt;/p&gt;

&lt;p&gt;I also used a couple &lt;code&gt;text-align&lt;/code&gt; rules on the grid items to center-align them by default, then left-align the first item and right-align the last one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="nf"&gt;#controls-listing&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="nf"&gt;#controls-listing&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="nd"&gt;:first-child&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="nf"&gt;#controls-listing&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="nd"&gt;:last-child&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The rest of the CSS changes are mostly updated selectors to (1) take into account the HTML restructuring and (2) use element selectors where possible instead of all those &lt;code&gt;#id&lt;/code&gt; references. Her's the full final result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;header&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;810px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;stretch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#controls&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;h3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="nf"&gt;#controls-listing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/** removing the default ul padding*/&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c"&gt;/* flexbox fallback if grid is not supported */&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;space-between&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;grid-auto-flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;grid-auto-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="nf"&gt;#controls-listing&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;list-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="nf"&gt;#controls-listing&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="nd"&gt;:first-child&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="nf"&gt;#controls-listing&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="nd"&gt;:last-child&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;canvas&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.psst&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That's a &lt;em&gt;whole&lt;/em&gt; lot less CSS, and I hope you'll agree it's a whole lot cleaner and more readable. Flexbox and Grid have really made a difference in that area, and I'm very happy about the future of the web.&lt;/p&gt;

&lt;h1&gt;
  
  
  Cliffhanger
&lt;/h1&gt;

&lt;p&gt;So that's it for the HTML and CSS. The JavaScript is a &lt;em&gt;whole&lt;/em&gt; other ball of wax, and requires its own post for sure. So I'll leave it here. I hope you've enjoyed this; it's definitely a bit cathartic to tear apart old code and congratulate myself on having improved as a developer in the last 7ish years, but it's also very exciting to see how much easier even very simple layouts like this one are in the new era of CSS layout methods.&lt;/p&gt;

&lt;p&gt;See you next time!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>css</category>
      <category>html</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Flex items are not grid columns</title>
      <dc:creator>Ken Bellows</dc:creator>
      <pubDate>Mon, 01 Apr 2019 16:59:53 +0000</pubDate>
      <link>https://dev.to/kenbellows/flex-items-are-not-grid-columns-4p2i</link>
      <guid>https://dev.to/kenbellows/flex-items-are-not-grid-columns-4p2i</guid>
      <description>&lt;p&gt;We often talk about Flexbox's growy-shrinky rules, like &lt;code&gt;flex-grow&lt;/code&gt; and &lt;code&gt;justify/align-content/items&lt;/code&gt;, as working basically identically to the &lt;code&gt;fr&lt;/code&gt; unit introduced with CSS Grid. That works as an introduction to &lt;code&gt;fr&lt;/code&gt;s for devs who are familiar with Flexbox, but it's not really true. There's a subtle but very important difference that sometimes shows up.&lt;/p&gt;

&lt;h1&gt;
  
  
  Scenario: Three differently-sized text items in a row
&lt;/h1&gt;

&lt;p&gt;Let me lay down a scenario, as I love to do. This is a specific case of a general scenario that I have encountered pretty often, and it was a common frustration that's solved neatly by CSS Grid.&lt;/p&gt;

&lt;p&gt;Consider a case where we have 3 inline elements, e.g. text labels, buttons, or images, of different sizes that need to be displayed neatly in a row. We want the first label to be left-aligned, the last one to be right-aligned, and the middle one to be centered on the page.&lt;/p&gt;

&lt;p&gt;Here's an example of this that comes from an old weekend project of mine from college (that I hope to write more extensively about in future articles, but that's beside the point). This project was an experiment with the HTML5 &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element, and it had an element of keyboard and mouse interaction. There were three primary controls, and I had three text labels above the canvas listing these controls:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxt9d7eaj0ql3p3sdm3j4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxt9d7eaj0ql3p3sdm3j4.png" alt="Three text labels" width="800" height="90"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So that's the goal. The most important requirement, the one that will cause problems, is that the middle item must be centered on the page: its center line should match the center line of the page. (In some cases, "page" is just "container", but "page" is easier to demonstrate.) I've included the heading above the labels to give a useful visual landmark to compare against.&lt;/p&gt;

&lt;h1&gt;
  
  
  Old school cool
&lt;/h1&gt;

&lt;p&gt;Back in college, in the pre-flexbox days of 2012(ish), I solved this with some &lt;em&gt;very&lt;/em&gt; brittle HTML+CSS that used a lot of absolute positioning:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kenbellows/embed/VRqqGE?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I've absolutely positioned the first and last labels to be on the same line as the middle element, stuck to the left or right. This... &lt;em&gt;works&lt;/em&gt;, of course, but could be disrupted by certain changes to the surrounding layout, is very non-reactive, is pretty unreadable, and is inflexible: if you wanted to add a fourth control, everything would become a lot more complex. &lt;/p&gt;

&lt;h1&gt;
  
  
  Flexbox
&lt;/h1&gt;

&lt;p&gt;If we were going to write this with modern CSS, the first thought might be to use Flexbox. It was certainly my first thought; this is a 1-d layout situation, which is what Flexbox is best at. Plus, I was pretty sure I could do it in two lines of CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#controls-body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;space-between&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done! Right?&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kenbellows/embed/JzwBBR?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;... Nope.&lt;/p&gt;

&lt;p&gt;While we do have three items, with one pulled left, another pulled right, and the other floating between them, you should see right away that the middle item is not centered properly; namely, instead of being centered &lt;em&gt;on the page&lt;/em&gt;, it's centered &lt;em&gt;between the other items&lt;/em&gt;. This would work fine if the items on the end are always the same size, but otherwise it's a no-go.&lt;/p&gt;

&lt;p&gt;Another thing you might try is to not use &lt;code&gt;justify-contents&lt;/code&gt; on the parent, but instead give all columns &lt;code&gt;flex-grow: 1&lt;/code&gt;, then add some &lt;code&gt;text-align&lt;/code&gt; rules to each. But this turns out to have exactly the same visual result:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kenbellows/embed/drwagy?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Why don't these work? Well, we'll get to that in a minute. First, we'll look at something that does work.&lt;/p&gt;

&lt;h1&gt;
  
  
  Grid
&lt;/h1&gt;

&lt;p&gt;Let's take the intuition from the second Flexbox attempt, to give each inline item its own "column" and make those "columns" the same size, and translate it over to CSS Grid, where we can drop the quotes: we'll literally define a column in a grid for each item and give them the same size using the &lt;code&gt;fr&lt;/code&gt; unit introduced with Grid.&lt;/p&gt;

&lt;p&gt;There are two ways to do this. The most obvious is to define three explicit columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#controls-body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice and clean. But I like to future-proof my code, as long as it doesn't add much work or complexity. So instead of explicitly defining a set of columns, let's use Grid's auto-flow rules to handle any number of items, in case I want to add more controls to my demo later on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#controls-body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;grid-auto-flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c"&gt;/* automatically place new items in new columns */&lt;/span&gt;
    &lt;span class="py"&gt;grid-auto-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c"&gt;/* auto-columns should be 1fr wide */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combine these rules with the &lt;code&gt;text-align&lt;/code&gt; rules above, and we get a winner:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kenbellows/embed/moaGKo?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;So a grid with three &lt;code&gt;1fr&lt;/code&gt; columns works, but a flex container with three &lt;code&gt;flex-grow: 1&lt;/code&gt; items, or with &lt;code&gt;justify-content: space-between&lt;/code&gt;, doesn't. Why? What's the difference?&lt;/p&gt;

&lt;h1&gt;
  
  
  The difference
&lt;/h1&gt;

&lt;p&gt;So here's the thing. We often talk about Flexbox's flexy "distribute space evenly" rules, like &lt;code&gt;flex-grow&lt;/code&gt; and &lt;code&gt;justify-content: space-between&lt;/code&gt;, as being effectively the same as the &lt;code&gt;fr&lt;/code&gt; unit introduced with CSS Grid. That works as an intuitive explainer of &lt;code&gt;fr&lt;/code&gt;s for devs who are familiar with Flexbox but new to Grid, but it's not really true. There's a subtle but very important difference that sometimes shows up, and it will explain the discrepancy here.&lt;/p&gt;

&lt;p&gt;In Flexbox, &lt;code&gt;space-between&lt;/code&gt;, &lt;code&gt;flex-grow&lt;/code&gt;, etc. divide up the &lt;strong&gt;remaining space&lt;/strong&gt; after all items are placed into the area. So if you have a &lt;code&gt;100px&lt;/code&gt; container with &lt;code&gt;justify-content: space-between&lt;/code&gt;, as we do here, and three items of sizes &lt;code&gt;10px&lt;/code&gt;, &lt;code&gt;40px&lt;/code&gt; and &lt;code&gt;20px&lt;/code&gt;, Flexbox does the following calculation (roughly):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt;
          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;
          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;
&lt;span class="n"&gt;space&lt;/span&gt; &lt;span class="n"&gt;between&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="c1"&gt;# children - 1)
&lt;/span&gt;                    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
                    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting layout is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10px item | 15px space | 40px item | 15px space | 20px item
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And the problem is that centering is not maintained. That 40px item's center point falls at &lt;code&gt;10px + 15px + (40px/2 = 20px)&lt;/code&gt;, which is &lt;code&gt;45px&lt;/code&gt;. So it's 5 pixels off from the true center of the container, which is at &lt;code&gt;50px&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Grid's &lt;code&gt;fr&lt;/code&gt; unit is different. It lays out the &lt;strong&gt;total space&lt;/strong&gt; given to a grid item &lt;em&gt;before considering the size of the item&lt;/em&gt;. So if we make our &lt;code&gt;100px&lt;/code&gt; container a grid-container with &lt;code&gt;grid-template-columns: 1fr 1fr 1fr&lt;/code&gt;, Grid does the following calculation before it even considers the contents of the grid-items:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixed&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="n"&gt;widths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;
&lt;span class="n"&gt;pixels&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;how&lt;/span&gt; &lt;span class="n"&gt;many&lt;/span&gt; &lt;span class="n"&gt;frs&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="n"&gt;across&lt;/span&gt; &lt;span class="nb"&gt;all&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="n"&gt;widths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
              &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;33.333&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;

&lt;span class="n"&gt;So&lt;/span&gt; &lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="sb"&gt;`1fr`&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="n"&gt;gets&lt;/span&gt; &lt;span class="mf"&gt;33.333&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;even&lt;/span&gt; &lt;span class="n"&gt;third&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;means&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="n"&gt;will&lt;/span&gt; &lt;span class="n"&gt;fall&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="sb"&gt;`33.333px + (half of 33.333px = 16.667px)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;which&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;nice&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt; &lt;span class="sb"&gt;`50px`&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;One&lt;/span&gt; &lt;span class="n"&gt;very&lt;/span&gt; &lt;span class="n"&gt;important&lt;/span&gt; &lt;span class="n"&gt;thing&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt; &lt;span class="n"&gt;totally&lt;/span&gt; &lt;span class="n"&gt;didn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t explain up there is the `fixed size column widths` variable, which in our case is `0`. This is the sum of any columns with values defined in a fixed unit, like `px`, `rem`, `in` (yeah, you can use inches in CSS! Nice for print layouts), or even `%`, which is fixed for the current size of the browser window. So for example if we used the rule `grid-template-columns: 30px 1fr 1fr`, then the calculation changes to this:
&amp;gt;```

python
remainder = available space - (fixed size column widths)
          = 100px - 30px
          = 70px
pixels per fr = remainder / (how many frs used across all column widths)
              = 70px / (1fr + 1fr) = 70px / 2
              = 35px


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;So in this case, each &lt;code&gt;1fr&lt;/code&gt; column gets 35px.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Hopefully this gives some more insight into how Flexbox works, and gives someone who's still been holding out a reason to reconsider looking into Grid. It's shocking to me how many people I still encounter who are deeply skeptical of Grid.&lt;/p&gt;

&lt;p&gt;As a final note, I want to be clear on something here: I am &lt;em&gt;not&lt;/em&gt; saying that the &lt;code&gt;fr&lt;/code&gt; unit is superior to Flexbox's behaviors. There are absolutely cases where Flexbox's behavior is exactly what you want. In many, many cases, it's more important to evenly divide the remaining space among siblings than to keep them all sitting in the same amount of space. Flexbox also allows for a lot more complexity around how items grow and shrink ("flex") based on the container. Maybe I'll write a thing about that at some point. Anyway, my point here is by no means to say that Grid's &lt;code&gt;fr&lt;/code&gt; units are better than Flexbox, only that they are easier for &lt;em&gt;this specific case&lt;/em&gt;. I still love Flexbox 💕💕💕&lt;/p&gt;




&lt;h1&gt;
  
  
  Endnote: tables
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Sigh.&lt;/em&gt; Yes, yes, I know: you can do it with tables. Pretty cleanly, actually, especially if you use CSS tables instead of actual tables:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
css
#controls-body {
  width: 100%;
  display: table;
  table-layout: fixed;
}
p {
  display: table-cell;
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That plus the &lt;code&gt;text-align&lt;/code&gt; rules gives us another winner:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/kenbellows/embed/JzwxLV?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;And honestly, this would have been much a better solution for me in 2012, even if I had to use actual tables because of limited browser support for CSS tables.&lt;/p&gt;

&lt;p&gt;But here's my big problem with this. It's not just the oft-repeated, rarely-explained wisdom to "never use tables for layout" (although, seriously, now that we have Grid, &lt;em&gt;never use tables for layout&lt;/em&gt;). This really is a decent enough solution in a pre-Grid context; it's clean, it's robust enough to handle adding extra items, and the code is readable, as long as you know what CSS tables are.&lt;/p&gt;

&lt;p&gt;No, my problem comes in one word: &lt;em&gt;responsiveness&lt;/em&gt;. Because it's not responsive, especially with an HTML table, and CSS tables aren't much better    . Sure, you could drop the items into a single column for the mobile view. But suppose you had 4 columns, and wanted a 2x2 layout at an intermediate range. How would you do it with tables? You'd be hard pressed to do it without falling back to absolute positioning shenanigans again.&lt;/p&gt;

&lt;p&gt;I won't say it's impossible; I can think of a couple options already, but they're not pretty, and they all have drawbacks. And most importantly, most, if not all, are specific to the current number of items, which makes for brittle code.&lt;/p&gt;

&lt;p&gt;Grids, on the other hand, are &lt;em&gt;amazingly&lt;/em&gt; responsive. It's trivial to redefine a grid's rows and columns, or to place things on a grid differently, so at minimum a media query breakpoint would be easy. (I wrote a whole article raving about Grid Areas, a feature that makes breakpoints that much more useful: &lt;a href="https://dev.to/kenbellows/css-grid-areas-are-amazing-1gha"&gt;CSS Grid Areas are amazing&lt;/a&gt;.) But in many cases, a breakpoint is unnecessary; Grid was built from the ground up with responsiveness in mind, and has many feature that support it, like &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/repeat"&gt;&lt;code&gt;repeat(auto-fill, ...)&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/minmax"&gt;&lt;code&gt;minmax()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For more on all this, I highly recommend checking out Rachel Andrew's &lt;a href="https://gridbyexample.com"&gt;gridbyexample.com&lt;/a&gt; and Jen Simmons' &lt;a href="https://www.youtube.com/channel/UC7TizprGknbDalbHplROtag"&gt;Layout Land&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>css</category>
      <category>flexbox</category>
      <category>grid</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
