<?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: Rowan Merewood</title>
    <description>The latest articles on DEV Community by Rowan Merewood (@rowan_m).</description>
    <link>https://dev.to/rowan_m</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%2F71348%2F57c2c065-f06d-4e52-b171-4008863eddbb.jpg</url>
      <title>DEV Community: Rowan Merewood</title>
      <link>https://dev.to/rowan_m</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rowan_m"/>
    <language>en</language>
    <item>
      <title>Gemini CLI and Jules: my March 2026 stack</title>
      <dc:creator>Rowan Merewood</dc:creator>
      <pubDate>Sun, 15 Mar 2026 17:28:43 +0000</pubDate>
      <link>https://dev.to/rowan_m/gemini-cli-and-jules-my-march-2026-stack-4146</link>
      <guid>https://dev.to/rowan_m/gemini-cli-and-jules-my-march-2026-stack-4146</guid>
      <description>&lt;p&gt;Development workflow is a very personal thing. As the various AI and LLM coding tools evolve, I've been trying different things to see what fits with how I want to code. This is for my personal projects, so it's important to me that I'm actually enjoying myself - it's not necessarily about maximum efficiency. It's also about updating how I work. I'm allergic to rabid hype, so getting into tools has required resisting that eye-twitch while I read a breathless summary about how another Markdown file is going to change my life.&lt;/p&gt;

&lt;p&gt;Also, I do find it a little bit intimidating when you see other people talking about how they've automated their entire workflow and have hordes of agents building a &lt;a href="https://steve-yegge.medium.com/welcome-to-the-wasteland-a-thousand-gas-towns-a5eb9bc8dc1f" rel="noopener noreferrer"&gt;Mad Max MMORPG&lt;/a&gt;. So, I thought I'd share my more incremental progress in case it's encouraging for anyone else. I do oscillate between feeling that everything is changing so quickly that I've got no hope of getting started and then finding something that really does make my life easier and even a bit more delightful.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Full disclosure:&lt;/strong&gt; these are mostly Google tools. I work there, I generally try to use our stuff. I even use the Linux VM inside ChromeOS as my primary development environment. Why? That's a great question.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;However, I've found a current combination that hits the right notes for me. It takes care of tedious scaffolding, highlights edge cases I wouldn't have considered, and keeps the actual implementation legible. Here's the tl;dr of my stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automated tests and static analysis on pull requests&lt;/li&gt;
&lt;li&gt;automated &lt;a href="https://firebase.google.com/docs/hosting" rel="noopener noreferrer"&gt;Firebase Hosting&lt;/a&gt; deploys on pull requests&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://geminicli.com/" rel="noopener noreferrer"&gt;Gemini CLI&lt;/a&gt; for active pair work&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jules.google/docs/" rel="noopener noreferrer"&gt;Jules&lt;/a&gt; for background suggestions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see all the code and configuration in the &lt;a href="https://github.com/rowan-m/fractious" rel="noopener noreferrer"&gt;&lt;code&gt;fractious&lt;/code&gt; GitHub repo&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The tl;dr of the tl;dr: &lt;strong&gt;make reviews easy.&lt;/strong&gt; That means adding more of the infrastructure I'd expect on a mature team project versus personal hobby toys. Long-term, maybe I'll finally do &lt;a href="http://www.extremeprogramming.org/" rel="noopener noreferrer"&gt;Extreme Programming&lt;/a&gt; like it's the '90s again. Those &lt;a href="https://jnco.com/" rel="noopener noreferrer"&gt;ultra-wide jeans&lt;/a&gt; are back in again, so anything is possible.&lt;/p&gt;

&lt;p&gt;Anyway, let's get into my (non-frosted) tips for my own environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project: Fractious
&lt;/h2&gt;

&lt;p&gt;If you follow me on any social media, you know I post a &lt;a href="https://bsky.app/search?q=from%3Arowan.fyi+fractious" rel="noopener noreferrer"&gt;disproportionate number of screenshots&lt;/a&gt; from my Mandelbrot fractal explorer, &lt;a href="https://fractious-deep.web.app/" rel="noopener noreferrer"&gt;Fractious&lt;/a&gt;. I need to write a proper blog post on the updates I've made, but the relevant bit here is that I'd "finished" the previous iteration but was frustrated by the limit to the zoom depth. Then &lt;a href="https://bsky.app/profile/rreverser.com/post/3mbw3kj4t6s2t" rel="noopener noreferrer"&gt;Ingvar suggested looking at BigDecimal.js&lt;/a&gt;, which I promptly did not do, going straight to asking Gemini what I could do instead. From there we successfully migrated from an entirely WebGL shader-based implementation to doing the reference calculation in Wasm and rendering the output with WebGPU.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqy6hngcku70m7im78ena.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqy6hngcku70m7im78ena.png" alt="Screenshot from my Fractious app zoomed in on some psychedelic swirls" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sweet spot here was: I knew what I wanted, I knew what a good architecture looked like, but I didn't know the exact syntax or steps to get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gemini CLI
&lt;/h2&gt;

&lt;p&gt;I tend to have three terminal tabs open: one running the build/dev server, one for my own Git commands, and one for &lt;a href="https://geminicli.com/" rel="noopener noreferrer"&gt;Gemini CLI&lt;/a&gt;. I then have a separate &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt; instance open where I'm either editing or checking diffs. This separation means I have my familiar coding setup for doing the exploratory stuff and then I switch over to Gemini when there's something I want it to do.&lt;/p&gt;

&lt;p&gt;I've tried the VS Code integration, &lt;a href="https://antigravity.google/" rel="noopener noreferrer"&gt;Antigravity&lt;/a&gt;, etc. and at least personally, and at the moment, it more often gets in my way than helps me. The AI jumping in all the time while I'm in the middle of typing tends to derail my train of thought rather than speed it up. This also means I tend towards longer periods in one mode or another. As in, I'm either doing things myself or I'm asking Gemini to work and then I'm reviewing. I don't really work in a mixed mode where the AI is collaborating alongside me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fcpmcqglf80l5rvhljnyp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fcpmcqglf80l5rvhljnyp.png" alt="Screenshot from Gemini CLI where it's suggesting some improvements to the codebase of this blog" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The biggest part of that in Fractious was implementing the Rust-based Wasm module. I don't know Rust and I don't know the tooling ecosystem. However, with Gemini I could get something that worked bootstrapped pretty quickly. It's much easier for me to learn and expand on a working example versus starting from a blank canvas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static analysis
&lt;/h2&gt;

&lt;p&gt;I also used Gemini to untangle the myriad of config files required for these tools. I maintain that YAML ain't a human-readable format (looking at you, GitHub Actions), but thankfully, it no longer has to be human-&lt;em&gt;writable&lt;/em&gt; at least.&lt;/p&gt;

&lt;p&gt;I integrated a few different tools here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;general linting with &lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;complexity and other checks with the &lt;a href="https://github.com/SonarSource/SonarJS/blob/master/packages/jsts/src/rules/README.md" rel="noopener noreferrer"&gt;SonarJS plugin for ESLint&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;security checks with, unsurprisingly, &lt;a href="https://github.com/eslint-community/eslint-plugin-security" rel="noopener noreferrer"&gt;&lt;code&gt;eslint-plugin-security&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;dependency checks with &lt;a href="https://github.com/sverweij/dependency-cruiser?tab=readme-ov-file#dependency-cruiser-" rel="noopener noreferrer"&gt;Dependency cruiser&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pipeline catches dead code, unnecessarily nested &lt;code&gt;if&lt;/code&gt; statements, and "cognitive complexity" (which I think is mostly &lt;a href="https://en.wikipedia.org/wiki/Cyclomatic_complexity" rel="noopener noreferrer"&gt;cyclomatic complexity&lt;/a&gt; with a fuzzier name), along with a whole bunch more.&lt;/p&gt;

&lt;p&gt;Again, this is all standard infrastructure for a work project accepting contributions. In this case, the contributor just happens to be a robot.&lt;/p&gt;

&lt;p&gt;I also dropped in a basic unit test harness with &lt;a href="https://vitest.dev/" rel="noopener noreferrer"&gt;&lt;code&gt;vitest&lt;/code&gt;&lt;/a&gt;. Yes, I'm a bad developer—I should have written the tests first. Not very Extreme Programming of me. However, having the harness in place means I can incrementally drop in tests (or ask the agent to do it) one by one.&lt;/p&gt;

&lt;p&gt;The crucial part is that these checks run on a GitHub Action against every pull request and push to &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preview builds
&lt;/h2&gt;

&lt;p&gt;I'm using &lt;a href="https://firebase.google.com/docs/hosting/" rel="noopener noreferrer"&gt;Firebase Hosting&lt;/a&gt;, which features a &lt;a href="https://firebase.google.com/docs/hosting/github-integration" rel="noopener noreferrer"&gt;GitHub Actions integration&lt;/a&gt; to deploy to a preview channel on every PR. This is vital because it creates a workflow where I can review, test, and approve incoming changes directly from my phone.&lt;/p&gt;

&lt;p&gt;This is nice because now it turns into an activity I can do wherever I am rather than needing to grab my laptop. The pull request comes in. The automatic checks and tests give me a base level of confidence in the contribution. I go play with the deployed app to make sure it works the way I expected. Then I peruse the diff to see if I agree with what's coming in. If I do, I just merge - if not then it waits until I'm at my laptop.&lt;/p&gt;

&lt;p&gt;As a bonus, reviewing PRs on my phone has taken a meaningful bite out of my reflexive doomscrolling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jules
&lt;/h2&gt;

&lt;p&gt;So, "where are these AI contributions coming from?" I hear my rare reader ask. &lt;a href="https://jules.google/docs/" rel="noopener noreferrer"&gt;Jules&lt;/a&gt; is a hosted agent that runs a bit more autonomously. It has a Suggestions feature and scheduled tasks that go through the codebase and look for issues, improvements, changes, etc. I go through these suggestions in the web interface and tell it to get started on the ones I'm interested in and then a bit later I get a pull request.&lt;/p&gt;

&lt;p&gt;The provided scheduled agents are quite helpful, with templates for performance, security, and UI improvements. It's useful looking through the prompts provided to see what those aspects are. For example, the performance agent includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"identify and implement ONE small performance improvement"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"never sacrifice code readability for micro-optimizations"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"measure the impact, add benchmark comments"&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F2eprb5z5ghdyuehcd8sp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F2eprb5z5ghdyuehcd8sp.png" alt="Screenshot from the Jules performance agent prompt" width="800" height="754"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So far this has resulted in pull requests that really are just changing one thing across 1-4 files. The performance ones often do come with a benchmark script showing the improvement. The UI improvement agent has been adding ARIA rules which do help with my unhealthy predilection for &lt;a href="https://github.com/rowan-m/fractious/pull/26" rel="noopener noreferrer"&gt;using emojis for labels and buttons&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are still quirks to manage. I avoid triggering multiple suggestions that touch the same code simultaneously to dodge merge conflicts. Occasionally, an agent will persistently re-suggest a change I've already rejected, just formatted slightly differently. I've also watched the performance agent optimize a block of code, only for the security agent to swing by later and revert it to the old, more robust implementation.&lt;/p&gt;

&lt;p&gt;Still, it's been pleasing getting a nice little list of improvements each morning. With the tests and preview deploys in place, there's very little effort or spin up time for me to get into looking at them. The scope of each change isn't overwhelming either - so I really can just pick up my phone and make a little progress.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fixvc0zkis9rkwo016si1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fixvc0zkis9rkwo016si1.png" alt="Screenshot of a GitHub pull request with ARIA improvements" width="800" height="702"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap it up
&lt;/h2&gt;

&lt;p&gt;I don't know if this is the workflow I'll have next month. Things do change quite quickly and I'm also learning and adapting as I go. Right now, this pattern of having the agent as a local partner or remote contributor is working quite well for me. However, as I start resuscitating those &lt;a href="https://cucumber.io/docs/bdd/" rel="noopener noreferrer"&gt;Behavior-Driven Development (BDD)&lt;/a&gt; skills I can see myself getting more comfortable with a hands-off approach when I have enough confidence in the test suite. That said, I'm an inherent tinkerer. I like to know how the app is working under the hood and on personal projects, &lt;a href="https://github.com/rowan-m/fractious/commit/b495a9b3df7945adb402a13ebdd143c478ff14fb" rel="noopener noreferrer"&gt;playing with the code is the entire point&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>tooling</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Modern noise</title>
      <dc:creator>Rowan Merewood</dc:creator>
      <pubDate>Sun, 01 Jun 2025 12:21:02 +0000</pubDate>
      <link>https://dev.to/rowan_m/modern-noise-4hji</link>
      <guid>https://dev.to/rowan_m/modern-noise-4hji</guid>
      <description>&lt;p&gt;This started as a simple noise machine with classic white, pink, and brown noise. However, at the time the first of April was coming up so I decided to add some additional noises that fit with the modern world. As with any worthwhile April Fool's joke, I ended up putting in far more effort than was really justified. However, I'm pretty happy with the end result and wanted to share a few of the build details.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://modern-noise.web.app/" rel="noopener noreferrer"&gt;⏯ Launch the Modern Noise generator!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The whole aesthetic is a shameless homage to the gorgeous &lt;a href="https://teenage.engineering/products/ep-133" rel="noopener noreferrer"&gt;Teenage Engineering K.O. II&lt;/a&gt;. Now, I've never even seen one in real life but I wasn't about to let that stop me having some kind of virtual approximation. Given its iconic look, the first step was assuming that other people had already recreated it... and they had.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steal her look
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fnl5ynzwgscm7l4reta6a.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fnl5ynzwgscm7l4reta6a.webp" alt="Realistic skeumorphic CSS calculator" width="467" height="880"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thomas McInnis created this &lt;a href="https://thomasmcinnis.com/posts/teenage-engineering-calculator/" rel="noopener noreferrer"&gt;realistic skeumorphic CSS calculator&lt;/a&gt;. I nabbed this as a base with the clean use of borders and box-shadows to get the button appearance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F0nqfb1m15uj4kaev7nbs.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0nqfb1m15uj4kaev7nbs.webp" alt="EP-133 K.O.II buttons" width="393" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nicolas Jesenberger crafted this set of &lt;a href="https://codepen.io/nicolasjesenberger/pen/gOqzJed" rel="noopener noreferrer"&gt;EP-133 K.O.II buttons&lt;/a&gt; where I like the actual use of a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element along with some subtle animations when the button is pressed. Bonus points here for using the actual Teenage Engineering font too!&lt;/p&gt;

&lt;p&gt;I've combined elements from both for the noise machine. I also wasn't looking for subtle animations, so I've cranked those up to get a chunky, clunky button you can almost feel when you press the screen.&lt;/p&gt;

&lt;p&gt;The volume slider is another story. I'll be honest, it probably has a whole bunch of issues in Safari and Firefox. Just look at all the footnotes for &lt;a href="https://caniuse.com/mdn-html_elements_input_type_range_vertical_orientation" rel="noopener noreferrer"&gt;vertically-oriented range sliders on Can I use...&lt;/a&gt;. I might get around to fixing it if people complain.&lt;/p&gt;


&lt;h2&gt;LCD marquee&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;marquee&amp;gt;&lt;/code&gt; tag is a classic in its beautiful mess of presentation and content lumped together. However, I still wanted that effect on the LCD display. I also wanted the appearance of the unlit pixels behind the text. Both of these came together reasonably neatly with the &lt;code&gt;ch&lt;/code&gt; unit.&lt;/p&gt;

&lt;p&gt;I'm using the &lt;a href="https://fonts.google.com/specimen/Doto" rel="noopener noreferrer"&gt;Doto font&lt;/a&gt; for the 5×9 mono grid font. Then I spent slightly longer than I should have creating &lt;a href="https://modern-noise.web.app/img/lcd-text-background.svg" rel="noopener noreferrer"&gt;a matching grid with SVG&lt;/a&gt;. Then I can just set that up as a character-sized background:&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="nt"&gt;background-image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;"/img/lcd-text-background.svg"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="nt"&gt;background-size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="nt"&gt;ch&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nt"&gt;background-origin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;content-box&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nt"&gt;background-position&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;75&lt;/span&gt;&lt;span class="o"&gt;%;&lt;/span&gt;
  &lt;span class="nt"&gt;background-repeat&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;repeat-x&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we animate the text from fully off the right side of the screen to fully off the left.&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="nc"&gt;.lcd-text&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;marquee&lt;/span&gt; &lt;span class="m"&gt;10s&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;infinite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;marquee&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-10ch&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;The trick here is instead of a smooth animation we're setting it to go in 20 &lt;code&gt;steps&lt;/code&gt; and the range of motion is 20 &lt;code&gt;ch&lt;/code&gt;. So each each step is a full character.&lt;/p&gt;

&lt;p&gt;It's not perfect, because it leaves an awkward gap where no text is on screen. I'm unsure if there's a CSS-only way of doing this or if I'd need to adjust the values based on the length of the content in the tag. I'll leave that as a &lt;code&gt;TODO&lt;/code&gt; for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bring the noise
&lt;/h2&gt;

&lt;p&gt;Now on to the rabbit hole that is noise generation. Zach Denton wrote the informative &lt;a href="https://noisehack.com/generate-noise-web-audio-api/" rel="noopener noreferrer"&gt;How to Generate Noise with the Web Audio API&lt;/a&gt; which gave me the code for white, pink, and brown noise. White noise is simple, literally just random values in a buffer. Pink noise is white noise passed through a -3dB per octave low-pass filter. Now, the Web Audio API has &lt;a href="https://developer.mozilla.org/docs/Web/API/BiquadFilterNode" rel="noopener noreferrer"&gt;&lt;code&gt;BiquadFilterNode&lt;/code&gt;&lt;/a&gt; with a &lt;code&gt;lowpass&lt;/code&gt; type - however this is -12dB per octave. Now, there's the even more low-level &lt;a href="https://developer.mozilla.org/docs/Web/API/IIRFilterNode" rel="noopener noreferrer"&gt;&lt;code&gt;IIRFilterNode&lt;/code&gt;&lt;/a&gt; which states, &lt;em&gt;"All even-ordered IIR filters can be created using &lt;code&gt;BiquadFilterNode&lt;/code&gt;. However, if you need to create an odd-ordered IIR filter, you'll need to use &lt;code&gt;IIRFilterNode&lt;/code&gt;."&lt;/em&gt; I naively assumed that creating a -3dB per octave filter would be as simple as passing in a different parameter.&lt;/p&gt;

&lt;p&gt;After some time reading the &lt;a href="https://courses.grainger.illinois.edu/ece401/sp2017/lecture17iir.pdf" rel="noopener noreferrer"&gt;IIR Filter slides from the Signal and Image Analysis module&lt;/a&gt; at the University of Illinois I realised that it was critical for me to identify the right poles and zeroes, but no matter how much I looked through those slides I have no idea what these terms really mean. It's possible that late-night coding doesn't always find me in the best frame of mind for learning new topics. Nonetheless, I feel relatively confident this is a Hard Problem. Hence the solution from Zach's blog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-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="nx"&gt;bufferSize&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="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;white&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="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&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;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;b0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.99886&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;b0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;white&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.0555179&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;b1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.99332&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;b1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;white&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.0750759&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;b2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.96900&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;b2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;white&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.1538520&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;b3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.86650&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;b3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;white&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.3104856&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;b4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.55000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;b4&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;white&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5329522&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;b5&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.7616&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;b5&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;white&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.0168980&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;output&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="nx"&gt;b0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b4&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b6&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;white&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5362&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;output&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="mf"&gt;0.11&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// (roughly) compensate for gain&lt;/span&gt;
    &lt;span class="nx"&gt;b6&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;white&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.115926&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;To understand what these magic constants are, I highly recommend reading &lt;a href="https://www.firstpr.com.au/dsp/pink-noise/" rel="noopener noreferrer"&gt;DSP generation of Pink (1/f) Noise&lt;/a&gt;. This is one of those sadly decaying corners of the web where you find that many of the links are pointing to long dead servers. Specifically, this code was posted by Paul Kellet in October, 1999 and still stands, 26 years later, as the best approximate of a -3dB per octave filter. Copying and pasting code you don't fully understand is an honourable and long-standing tradition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world experience
&lt;/h2&gt;

&lt;p&gt;Finally, we get to the actual joke part of the entire project, some 14 paragraphs through this post. Initially I was thinking I would make something genuinely relaxing, like intermittent bird sounds or wind chimes. However with the state of the world being what it is, it seems better to try and innoculate yourself against things with controlled exposure. The excellent &lt;a href="https://freesound.org" rel="noopener noreferrer"&gt;freesound.org&lt;/a&gt; has a huge catalogue of conveniently tagged clips. I grabbed a selection and converted them all to fairly compressed AAC files as I'm aiming for lightweight here, not gold-plated audiophile quality - I want this to load as fast as possible. Then I just play random sounds at random intervals to create the effect. So, thank you and credit to the various users below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screams
&lt;/h3&gt;

&lt;p&gt;A series of endless overlapping screams. Feel free to release your own tension and join in.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/akridiy/sounds/759454/" rel="noopener noreferrer"&gt;akridiy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/AlSarcoli007/sounds/220365/" rel="noopener noreferrer"&gt;AlSarcoli007&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/AlSarcoli007/sounds/220369/" rel="noopener noreferrer"&gt;AlSarcoli007&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/BlueFairy311/sounds/457573/" rel="noopener noreferrer"&gt;BlueFairy311&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/demensdeum/sounds/758591/" rel="noopener noreferrer"&gt;demensdeum&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/FreqMan/sounds/42847/" rel="noopener noreferrer"&gt;FreqMan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/jorickhoofd/sounds/179320/" rel="noopener noreferrer"&gt;jorickhoofd&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/Klangkobold/sounds/547961/" rel="noopener noreferrer"&gt;Klangkobold&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/Lithe-Fider/sounds/9703/" rel="noopener noreferrer"&gt;Lithe-Fider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/marionagm90/sounds/220655/" rel="noopener noreferrer"&gt;marionagm90&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/owly-bee/sounds/415352/" rel="noopener noreferrer"&gt;owly-bee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/queen_westeros/sounds/222586/" rel="noopener noreferrer"&gt;queen_westeros&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/Reitanna/sounds/253771/" rel="noopener noreferrer"&gt;Reitanna&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/roryshea/sounds/747831/" rel="noopener noreferrer"&gt;roryshea&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/sironboy/sounds/132106/" rel="noopener noreferrer"&gt;sironboy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/SweetNeo85/sounds/13797/" rel="noopener noreferrer"&gt;SweetNeo85&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/tcrocker68/sounds/235592/" rel="noopener noreferrer"&gt;tcrocker68&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/TheSubber13/sounds/239900/" rel="noopener noreferrer"&gt;TheSubber13&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/tomattka/sounds/400183/" rel="noopener noreferrer"&gt;tomattka&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/tonsil5/sounds/416541/" rel="noopener noreferrer"&gt;tonsil5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/Valerie-Vivegnis/sounds/767890/" rel="noopener noreferrer"&gt;Valerie-Vivegnis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/Vitouliss/sounds/173088/" rel="noopener noreferrer"&gt;Vitouliss&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Traffic
&lt;/h3&gt;

&lt;p&gt;Get the true experience of returning to the office by sitting in traffic for as long as it takes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/99021905683/sounds/571348/" rel="noopener noreferrer"&gt;99021905683&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/akahukas/sounds/708707/" rel="noopener noreferrer"&gt;akahukas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/AudioPapkin/sounds/716552/" rel="noopener noreferrer"&gt;AudioPapkin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/Debsound/sounds/176003/" rel="noopener noreferrer"&gt;Debsound&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/deleted_user_2104797/sounds/164625/" rel="noopener noreferrer"&gt;deleted_user_2104797&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/deleted_user_2104797/sounds/164627/" rel="noopener noreferrer"&gt;deleted_user_2104797&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/DeVern/sounds/349922/" rel="noopener noreferrer"&gt;DeVern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/etcd_09/sounds/435497/" rel="noopener noreferrer"&gt;etcd_09&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/G_M_D_THREE/sounds/513527/" rel="noopener noreferrer"&gt;G_M_D_THREE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/hyderpotter/sounds/268809/" rel="noopener noreferrer"&gt;hyderpotter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/iainmccurdy/sounds/643125/" rel="noopener noreferrer"&gt;iainmccurdy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/jorgens_/sounds/770985/" rel="noopener noreferrer"&gt;jorgens_&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/keweldog/sounds/182474/" rel="noopener noreferrer"&gt;keweldog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/LaurenPonder/sounds/635681/" rel="noopener noreferrer"&gt;LaurenPonder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/Robinhood76/sounds/106785/" rel="noopener noreferrer"&gt;Robinhood76&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/tlabrant1/sounds/658724/" rel="noopener noreferrer"&gt;tlabrant1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/wanaki/sounds/569613/" rel="noopener noreferrer"&gt;wanaki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Construction
&lt;/h3&gt;

&lt;p&gt;Yes, the sound of growth, a thriving economy, and all right outside your window all day!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/cognito%20perceptu/sounds/121531/" rel="noopener noreferrer"&gt;cognito perceptu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/cognito%20perceptu/sounds/17012/" rel="noopener noreferrer"&gt;cognito perceptu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/costamonteiro/sounds/69891/" rel="noopener noreferrer"&gt;costamonteiro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/esperri/sounds/118972/" rel="noopener noreferrer"&gt;esperri&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/fkurz/sounds/169127/" rel="noopener noreferrer"&gt;fkurz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/G.Lamont/sounds/118042/" rel="noopener noreferrer"&gt;G.Lamont&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/Heigh-hoo/sounds/22584/" rel="noopener noreferrer"&gt;Heigh-hoo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/InspectorJ/sounds/400991/" rel="noopener noreferrer"&gt;InspectorJ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/jorickhoofd/sounds/160045/" rel="noopener noreferrer"&gt;jorickhoofd&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/ldezem/sounds/386239/" rel="noopener noreferrer"&gt;ldezem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/ldezem/sounds/386300/" rel="noopener noreferrer"&gt;ldezem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/lextrack/sounds/273722/" rel="noopener noreferrer"&gt;lextrack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/moxobna/sounds/40158/" rel="noopener noreferrer"&gt;moxobna&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/pablodavilla/sounds/592111/" rel="noopener noreferrer"&gt;pablodavilla&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/Rock%20Savage/sounds/59263/" rel="noopener noreferrer"&gt;Rock Savage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/Sparrer/sounds/50506/" rel="noopener noreferrer"&gt;Sparrer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://freesound.org/people/WIM/sounds/17908/" rel="noopener noreferrer"&gt;WIM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TODO
&lt;/h2&gt;

&lt;p&gt;No personal project is ever finished, so here is a selection of some of the updates I'd like to add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subtle SVG noise to create a textured effect to the background and buttons.&lt;/li&gt;
&lt;li&gt;The LCD icons are definitely an MVP version, I want something that fits the aesthetic a bit better and has more interesting animation.&lt;/li&gt;
&lt;li&gt;Stereo or spatial effects on the audio. The generated noise should be more immersive and I'd like the screaming to sound as if it's coming from every direction.&lt;/li&gt;
&lt;li&gt;More use of the Media Session controls - probably adding a next track button that lets you change the noise type.&lt;/li&gt;
&lt;li&gt;Smoother start, stop, and switch of noises. The fade in on start is reasonable, but ending is far too abrupt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's plenty more to do, so if you've got requests then ping me on your preferred social network in the footer to let me know!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>css</category>
    </item>
    <item>
      <title>Create a 🖨️ printable zine with CSS</title>
      <dc:creator>Rowan Merewood</dc:creator>
      <pubDate>Wed, 08 Apr 2020 23:09:39 +0000</pubDate>
      <link>https://dev.to/rowan_m/create-a-printable-zine-with-css-5c0c</link>
      <guid>https://dev.to/rowan_m/create-a-printable-zine-with-css-5c0c</guid>
      <description>&lt;p&gt;I loved the ingenuity of zine templates, that origami or map-like magic of a single piece of paper folding into an entire little book. That made me think, surely we should be able to do this on the web - right? A web page that you can read on a screen, but when you press print it turns into that magical little book. The answer is: of course we can! If you want the short version, then jump right in and read about how to make a CSS zine... in the form of a CSS zine!&lt;/p&gt;

&lt;p&gt;Have a peek below ⤵️ or&lt;br&gt;
on ➡️ &lt;a href="https://zine-machine.glitch.me" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;code&gt;zine-machine.glitch.me&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/zine-machine?previewSize=100&amp;amp;path=index.html" alt="zine-machine on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;


&lt;p&gt;We're going to need two key bits of functionality to make this happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://bit.ly/rachel-on-print" rel="noopener noreferrer"&gt;Print Stylesheets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bit.ly/rachel-on-grids" rel="noopener noreferrer"&gt;CSS Grid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🏗️ HTML structure
&lt;/h2&gt;

&lt;p&gt;Let's see how we build this up. Step one is a nice, simple, semantic layout for our HTML:&lt;br&gt;
&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;main&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"zine"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;header&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"zine-page page-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"zine-page page-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"zine-page page-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"zine-page page-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"zine-page page-5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"zine-page page-6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"zine-page page-7"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;footer&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"zine-page page-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/footer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can style that for the screen however you want, the important bit is that we're going to wrap the styles that are just for screen display in a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media" rel="noopener noreferrer"&gt;media query&lt;/a&gt;:&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="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;screen&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;body&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;30rem&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="m"&gt;#ead1dc&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="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.zine&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="no"&gt;white&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;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#741b47&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#741b47&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.zine-page&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;10px&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;Nothing too exciting, but just so we can break up the text a little let's see what that looks like with a little text thrown in there:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9vnmo772opdosh10ebwx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9vnmo772opdosh10ebwx.png" alt="A simple linear screen layout of 8 pages" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🖨️ Print CSS
&lt;/h2&gt;

&lt;p&gt;Now the fun part, we can then use the matching &lt;code&gt;@media print&lt;/code&gt; query to enclose our print styles… but what are we going to put in there? First, let's see what we're trying to make. Here's the layout of the pages we need:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fy9luw53v0luit0tcvwdx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fy9luw53v0luit0tcvwdx.png" alt="Landscape print layout of pages 1 to 8" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, this doesn't flow in any kind of top-to-bottom order that would match the order of the content in our HTML. We don't want to add any weird markup to make this possible, so we're going to use CSS grid to essentially pick up and reposition each individual page where we need it.&lt;/p&gt;

&lt;p&gt;First, let's drop in a little CSS to hint how we'd like the page set up. We can use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@page" rel="noopener noreferrer"&gt;&lt;code&gt;@page&lt;/code&gt; rule&lt;/a&gt; for this.&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="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;print&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;@page&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;landscape&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;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;bleed&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="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;To be honest, it's going to be a bit hit or miss as to whether or not the combination of your browser, OS, and printer actually pays any attention to that… but it's nice to say we tried.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔲 CSS Grid
&lt;/h2&gt;

&lt;p&gt;Next up, let's create our grid. We've got our &lt;code&gt;.zine&lt;/code&gt; container which holds each &lt;code&gt;.zine-page&lt;/code&gt;.&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="nc"&gt;.zine&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;100vw&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;100vh&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-template-areas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="s1"&gt;"page-5 page-4 page-3 page-2"&lt;/span&gt;
    &lt;span class="s1"&gt;"page-6 page-7 page-8 page-1"&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;I ❤️ &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-areas" rel="noopener noreferrer"&gt;&lt;code&gt;grid-template-areas&lt;/code&gt;&lt;/a&gt; because you're essentially just dropping in a diagram that matches your layout. In this case, we've laid out some named areas mapping to each of our pages in a 4 × 2 grid.&lt;/p&gt;

&lt;p&gt;We need to map those grid areas up to our individual page elements though, so let's pop in that mapping:&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="nc"&gt;.page-1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.page-2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;grid-area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page-2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nt"&gt;you&lt;/span&gt; &lt;span class="nt"&gt;get&lt;/span&gt; &lt;span class="nt"&gt;the&lt;/span&gt; &lt;span class="nt"&gt;idea&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're getting somewhere! Let's take a look at the print preview for the page and see what we get!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvig53kcl4g1aux5z7el9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvig53kcl4g1aux5z7el9.png" alt="A basic grid layout for 8 pages" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's progress, but we need to flip a few of those pages upside down. That's easy enough with a CSS &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform" rel="noopener noreferrer"&gt;&lt;code&gt;transform&lt;/code&gt;&lt;/a&gt;:&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="nc"&gt;.page-5&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.page-4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.page-3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.page-2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;180deg&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;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fl5l1brvglcu6ocp910bz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fl5l1brvglcu6ocp910bz.png" alt="A basic grid print layout for 8 pages with the correct rotation" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's our layout done! Well, almost... when you print it out you need to fold along the edges of the page. You can do this just by folding in half, but let's pop some guidelines in just to make it clear. Now, we &lt;em&gt;could&lt;/em&gt; go in an but the relevant individual &lt;code&gt;border&lt;/code&gt; definitions on the edges of each &lt;code&gt;.zine-page&lt;/code&gt; but frankly that's a lot of work and it's doubly confusing to remember that &lt;code&gt;border-top&lt;/code&gt; will actually be at the bottom of the rotated pages. So, we're just going to use the simple trick of settings a background colour for our grid, and put gaps between our white pages so that the grid shows through. Simple!&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="nc"&gt;.zine&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;100vw&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;100vh&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-template-areas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="s1"&gt;"page-5 page-4 page-3 page-2"&lt;/span&gt;
    &lt;span class="s1"&gt;"page-6 page-7 page-8 page-1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&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="no"&gt;lightgrey&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;I've made that gap a bit bigger for the screenshot, but there we go!&lt;/p&gt;

&lt;p&gt;There's one slight tweak to the &lt;code&gt;transform&lt;/code&gt; though:&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="nc"&gt;.page-5&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.page-4&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.page-3&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.page-2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;180deg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-0.25px&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;For some reason, the pages don't seem to rotate around their &lt;em&gt;exact&lt;/em&gt; centre, which means that when the border is visible is doesn't exactly line up either. If like me, that ever so slight but niggling obvious misalignment keeps you lying awake at night then that little bump over from the &lt;code&gt;translateX&lt;/code&gt; will fix it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqc2mbbc547y27b6ah2ek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqc2mbbc547y27b6ah2ek.png" alt="A basic grid print layout for 8 pages with the correct rotation and border between" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! There are plenty of cool things to still do with different content for the screen versus print version. For example, you could make sure that any &lt;a href="https://css-tricks.com/snippets/css/print-url-after-links/" rel="noopener noreferrer"&gt;links in the document are added to the print output&lt;/a&gt; with:&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="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;print&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;a&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;" ("&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s1"&gt;") "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might also want to swap out images that would be ink-heavy in the print version for simpler line-art.&lt;/p&gt;

&lt;h2&gt;
  
  
  ♻️ Make and share your own!
&lt;/h2&gt;

&lt;p&gt;I'd love to see you make your own 'zine, like this hand-drawn one from Erika on electronics:&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;I'm sure there are some usability tweaks to add in the code too! So, go out there and &lt;a href="https://glitch.com/edit/#!/zine-machine?path=template.html:1:0" rel="noopener noreferrer"&gt;remix the project&lt;/a&gt; to show me what you create!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv5ifu3hfawfoaw3bnz5j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv5ifu3hfawfoaw3bnz5j.jpg" alt="The printed zine" width="800" height="799"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>css</category>
      <category>zine</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Prudent publishing to npm</title>
      <dc:creator>Rowan Merewood</dc:creator>
      <pubDate>Wed, 21 Aug 2019 17:43:23 +0000</pubDate>
      <link>https://dev.to/rowan_m/prudent-publishing-to-npm-1dkf</link>
      <guid>https://dev.to/rowan_m/prudent-publishing-to-npm-1dkf</guid>
      <description>&lt;p&gt;This post shares a few practices I find helpful for keeping my publishing of packages to npm a bit more isolated from my personal environment. I think there are good principles here, but they may be a bit specific to my project and platform. Please do comment with variants or tips for your own environment. Hey - maybe even do a whole post of your own!&lt;/p&gt;

&lt;h1&gt;
  
  
  💌 Sign up to npm with a separate email
&lt;/h1&gt;

&lt;p&gt;This is good practice for most developer channels - whether it's publishing an Android app in the Play Store, your account for a particular API you rely on, or your npm account. There are a number of reasons for doing this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⛔ Accounts get banned by mistake! You as a person on npm is different to the account that owns your packages. If one gets banned, it doesn't affect the other.&lt;/li&gt;
&lt;li&gt;🤝 Other people may need access. While an &lt;a href="https://docs.npmjs.com/orgs/" rel="noopener noreferrer"&gt;organization&lt;/a&gt; is the right solution long-term, sometimes there are emergencies or one-offs and sharing access to the one specific account is definitely better than your personal one!&lt;/li&gt;
&lt;li&gt;👔 This email may need to be public. You may want to provide support or perhaps you just get random enquiries. Using a separate address lets you manage that as it's own thing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No real instructions on this one... source that email address wherever you want!&lt;/p&gt;

&lt;h1&gt;
  
  
  🎭 Publish using a separate local user
&lt;/h1&gt;

&lt;p&gt;This has more explicit security benefits than using a separate email (which frankly is more about your personal happiness).&lt;/p&gt;

&lt;p&gt;Most of the issues here come from other packages getting compromised and injecting malicious code. Two specific incidents here were with some of the &lt;a href="https://eslint.org/blog/2018/07/postmortem-for-malicious-package-publishes" rel="noopener noreferrer"&gt;ESLint packages including code that stole npm tokens&lt;/a&gt; and the &lt;a href="https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident" rel="noopener noreferrer"&gt;&lt;code&gt;event-stream&lt;/code&gt; package getting compromised to steal cryptocurrency&lt;/a&gt;. Creating an isolated user helps mitigate this by reducing the exposure. So, for example if there is an exploited package that's able to kick in during the deployment process - it won't have access to your personal user. Likewise, an exploited package in your development environment won't have access to your deployment process.&lt;/p&gt;

&lt;p&gt;If you're being really conscientious, then you should really ensure &lt;em&gt;all&lt;/em&gt; your npm operations run as a separate user but that does start to turn into a lot of overhead.&lt;/p&gt;

&lt;p&gt;I'm a Linux user, so these instructions are all based on my Bash shell environment. Please do share if there are tweaks for your own platform!&lt;/p&gt;

&lt;p&gt;🐣 Right, let's get our user created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;adduser deploy-npm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You'll be prompted to create a password. Obviously use something solid, but we don't need to care what it is. We're going to use &lt;code&gt;sudo&lt;/code&gt; when we access it.&lt;/p&gt;

&lt;p&gt;🕵️ Now we need to configure the environment for the new &lt;code&gt;deploy-npm&lt;/code&gt; user. We'll use &lt;code&gt;sudo&lt;/code&gt; to log in to that account:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; deploy-npm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The user will need to have access to a version of Node and &lt;code&gt;npm&lt;/code&gt;. I use &lt;code&gt;nvm&lt;/code&gt; to manage this, so the first thing I'm doing is following their &lt;a href="https://github.com/nvm-sh/nvm#installation-and-update" rel="noopener noreferrer"&gt;install instructions and getting the LTS version of Node&lt;/a&gt;. You can go about this however you want. For example, maybe you already have Node installed system-wide and it's therefore immediately available for the new user.&lt;/p&gt;

&lt;p&gt;☑️ Verify your set-up just by checking the &lt;code&gt;npm&lt;/code&gt; version:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;👋 Now it's time to log in to the account! Unsurprisingly, that done with:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;🔐 If you haven't already enabled two-factor authentication on your account this is an ideal time to do it! This means even if your account is compromised or the tokens stolen the attacker still can't do anything without that second authentication mechanism.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm profile enable-2fa auth-and-writes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;💾 We also need a local copy of the repo. Our deploy user isn't going to commit anything and doesn't have SSH credentials set up anyway, so we're just going to use the HTTP version. This assumes your code is on GitHub, but any read-only checkout is what you're aiming for here.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git clone https://github.com/someUser/my-package.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;🔏 The assumption here is that you've already appropriately tagged the repo ready for a release. So, let's change into the directory and checkout the tag.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;my-package
&lt;span class="nv"&gt;$ &lt;/span&gt;git checkout tags/0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;🎁 That's all the set-up done, so now we publish! Assuming you did enable 2FA, you're going to get asked for your confirmation code here.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm-publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For my use, I've wrapped that up in a script that I can use to publish a given tag. This first one is running as the &lt;code&gt;deploy-npm&lt;/code&gt; user and is effectively just the commands we used above. I've saved it as &lt;code&gt;deploy-npm.sh&lt;/code&gt; in the user's home directory.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#! /bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; .nvm/nvm.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;my-package &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
git fetch &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
git checkout tags/&lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
npm publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Finally, I have a script in my own account that does the appropriate &lt;code&gt;sudo&lt;/code&gt; call to run as the &lt;code&gt;deploy-npm&lt;/code&gt; user.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#! /bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; deploy-npm /home/deploy-npm/deploy-npm.sh &lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;🤖 Now, after I've tagged up a release I just run:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;deploy-npm.sh 0.0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you're still looking to tighten things up, try this post from Liran with a selection of other useful tips. &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/lirantal" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F11421%2F316371.jpeg" alt="lirantal"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/lirantal/npm-security-tips-to-keep-you-safe-of-malicious-modules-25bp" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;npm security tips to keep you safe of malicious modules&lt;/h2&gt;
      &lt;h3&gt;Liran Tal ・ Aug 19 '19&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#npm&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#node&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#security&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



</description>
      <category>npm</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Save a developer with npm --save-dev</title>
      <dc:creator>Rowan Merewood</dc:creator>
      <pubDate>Tue, 20 Aug 2019 08:27:14 +0000</pubDate>
      <link>https://dev.to/rowan_m/save-a-developer-with-npm-save-dev-2ki0</link>
      <guid>https://dev.to/rowan_m/save-a-developer-with-npm-save-dev-2ki0</guid>
      <description>&lt;p&gt;I was going to cleverly call this post "&lt;code&gt;npm -g&lt;/code&gt; considered harmful", but unsurprisingly &lt;a href="https://twitter.com/zigg" rel="noopener noreferrer"&gt;Matt Behrens&lt;/a&gt; got there back in 2016 with &lt;a href="https://spin.atomicobject.com/2016/02/22/npm-install-dangerous/" rel="noopener noreferrer"&gt;this excellent article&lt;/a&gt;. As a separate lesson, reading it made me think there was no point me writing this because Matt had already explained this so well- but you know what?&lt;/p&gt;

&lt;p&gt;It's &lt;code&gt;new Date().getFullYear();&lt;/code&gt; and this pattern is still everywhere, so a little repetition ♻️ and amplification 📣 won't hurt.&lt;/p&gt;

&lt;h1&gt;
  
  
  ⚠️ What's the problem?
&lt;/h1&gt;

&lt;p&gt;I remember saying to a colleague recently, that one of my reasons for liking Linux was the convention of having all your dependencies centrally managed. Everything can be regularly updated in the background, no surprise 6GB downloads whenever I want to start an app (yeah - I'm looking at you, PlayStation), no need to remember where I downloaded the software... just one command.&lt;/p&gt;

&lt;p&gt;Of course, I was lying to myself - talking up the life I wish I had, not the tedious reality I actually experience. After running &lt;code&gt;apt-get&lt;/code&gt; it turns out I then need to run &lt;code&gt;npm -g update&lt;/code&gt;. I should also check &lt;code&gt;nvm  --install --lts&lt;/code&gt; to ensure that's up to date. Oh, I should also do a &lt;code&gt;flatpak update&lt;/code&gt; since that's where the cool kids are dropping apps now. Then of course, there's that project that decided to use some Python dependencies, so I better look up that oh-so-elegant one liner to pipe &lt;code&gt;pip list&lt;/code&gt; through 50 commands until it hits &lt;code&gt;xargs&lt;/code&gt;. Then, of course, just go through the stragglers that rolled their own update mechanisms because...&lt;/p&gt;

&lt;p&gt;(╯°□°)╯︵ ┻━┻&lt;/p&gt;

&lt;p&gt;Anyway ┬─┬ノ( º _ ºノ) it was early in the morning when I wrote this. I really do want to  use and contribute to your opensource project, but don't make it more work than it needs to be. Luckily, we can fix this together and it's going to make your life as a maintainer easier too.&lt;/p&gt;

&lt;p&gt;First, it's not just the overhead of updating packages that's an issue. A global dependency means there's (probably) one version on the system. If I have multiple packages that depend on different versions of that tool, I'm in trouble.&lt;/p&gt;

&lt;h1&gt;
  
  
  🆘 Save this developer
&lt;/h1&gt;

&lt;p&gt;🚫 So, if you read or are tempted to write documentation that advises this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm -g install my-cool-tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✔️ Instead, I want you to write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm --save-dev my-cool-tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will add &lt;code&gt;my-cool-tool&lt;/code&gt; as a development dependency for the project. That means there will be an executable accessible as &lt;code&gt;./node_modules/.bin/my-cool-tool&lt;/code&gt;. Of course, that's an unwieldy path to type each time.&lt;/p&gt;

&lt;p&gt;🚫 So, where you were going to just run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-cool-tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🚧 Instead we're going to make use of &lt;code&gt;"scripts"&lt;/code&gt; section in our &lt;code&gt;package.json&lt;/code&gt; because any command we put there will check our packages &lt;code&gt;./node_modules/.bin&lt;/code&gt; directory first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"my-cool-tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-cool-tool"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✔️ Now, you can run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run my-cool-tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  🏄 That's it!
&lt;/h1&gt;

&lt;p&gt;It's mostly just a translation, not a big shift - but I think they pay off is worth it. Now, if I want to use or contribute to your project then I just need to run &lt;code&gt;npm install&lt;/code&gt; and know that I've automatically got my developer environment set up. Who knows, that developer coming back to the project on a new machine a year later... may even be you!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>npm</category>
    </item>
    <item>
      <title>Form and function</title>
      <dc:creator>Rowan Merewood</dc:creator>
      <pubDate>Fri, 19 Jul 2019 16:30:54 +0000</pubDate>
      <link>https://dev.to/rowan_m/form-and-function-emm</link>
      <guid>https://dev.to/rowan_m/form-and-function-emm</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the second in a series of posts detailing how I built the&lt;/em&gt; &lt;a href="https://bit.ly/rotavo-pwa" rel="noopener noreferrer"&gt;🥑 &lt;em&gt;Rotavo PWA&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. Give it a whirl and see what you can draw!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/rowan_m/you-spin-me-right-r-und-k32"&gt;previous instalment&lt;/a&gt; we built the touch-controlled &lt;code&gt;&amp;lt;input-knob&amp;gt;&lt;/code&gt; component that let us create a rotating knob with a &lt;code&gt;value&lt;/code&gt; attribute corresponding to its angle. However, &lt;a href="https://paul-spinlan.glitch.me" rel="noopener noreferrer"&gt;fun&lt;/a&gt; as that might have been it's still missing some functionality to be practical for general use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;♻️ Freshen up&lt;/li&gt;
&lt;li&gt;👂 Quite the event&lt;/li&gt;
&lt;li&gt;🔒 Enforcing limits&lt;/li&gt;
&lt;li&gt;🧛‍♂️ Count rotations&lt;/li&gt;
&lt;li&gt;🎁 Bonus content&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ♻️ Freshen up
&lt;/h2&gt;

&lt;p&gt;First though, let's freshen up &lt;a href="https://building-rotavo-04.glitch.me/" rel="noopener noreferrer"&gt;the previous demo&lt;/a&gt; with a few cosmetic improvements. I said we were going to build something that resembled a volume control knob on a stereo. Now, we got the rotating behaviour in there but our knob was square... traditionally they're round. You were all kind enough not to mention that, but we should fix that before we move on.&lt;/p&gt;

&lt;p&gt;We also want an indicator for which way is "up" on our knob. As we saw in the &lt;a href="https://paul-spinlan.glitch.me" rel="noopener noreferrer"&gt;final demo&lt;/a&gt; last time, we can just add any content we need inside the element. So we'll drop in a little triangle indicator there:&lt;br&gt;
&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;input-knob&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"2.5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mark"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;▲&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&amp;lt;/input-knob&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;


&lt;p&gt;We then need to style that so it's central:&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="nc"&gt;.mark&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;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;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;font&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt; &lt;span class="m"&gt;200%&lt;/span&gt; &lt;span class="nb"&gt;monospace&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="m"&gt;#356211&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 primary change on the control is using &lt;code&gt;border-radius&lt;/code&gt; to round off those corners. We're also going to put the shadow on the outer &lt;code&gt;input-knob&lt;/code&gt; element as that does not rotate. If we had the shadow on the inner &lt;code&gt;::part(container)&lt;/code&gt; it looks as if the shadow moves around the element instead, which is not the effect we're after.&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="nt"&gt;input-knob&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&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;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0.3rem&lt;/span&gt; &lt;span class="m"&gt;0.3rem&lt;/span&gt; &lt;span class="n"&gt;rgba&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="m"&gt;0&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="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;input-knob&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;container&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&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="m"&gt;#cadbbc&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;1rem&lt;/span&gt; &lt;span class="nb"&gt;double&lt;/span&gt; &lt;span class="m"&gt;#356211&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#356211&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8rem&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;8rem&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;Rounded corners! The original CSS holy grail - revel in it.&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/building-rotavo-04?previewSize=100&amp;amp;path=index.html" alt="building-rotavo-04 on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;p&gt;There is also a little improvement we can make to the styling of &lt;code&gt;:host&lt;/code&gt; on our Shadow DOM template as well. Specifically, we want the cursor to change into the little 👆 pointer indicating to the user they can interact with it.&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="nd"&gt;:host&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="py"&gt;user-select&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="py"&gt;touch-action&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;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&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;h2&gt;
  
  
  👂 Quite the event
&lt;/h2&gt;

&lt;p&gt;Now we've added a little polish to our element, we've got something visually pleasing but it's hard to react to any changes when the user interacts with the element. In the same way that we listen for Pointer events inside the element, we want to emit our own events in order to respond to them in the wider application.&lt;/p&gt;

&lt;p&gt;Much like the Pointer events, we want to track the beginning, middle, and end of the interaction. That means we will &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events" rel="noopener noreferrer"&gt;create three &lt;code&gt;Event&lt;/code&gt;&lt;/a&gt; types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;knob-move-start&lt;/code&gt;: when the element is touched / clicked&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;knob-move-change&lt;/code&gt;: when the element is moved&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;knob-move-end&lt;/code&gt;: when the element is released&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're going to emit these events at the end of each handler inside of the element, because we want to be sure that we've done all the necessary work inside the element before anything attempts to process the event.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// class InputKnob&lt;/span&gt;
&lt;span class="nf"&gt;_rotationStart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ✂️ existing code hidden      &lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;knob-move-start&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="na"&gt;bubbles&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="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;_rotationChange&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ✂️ existing code hidden    &lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;knob-move-change&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="na"&gt;bubbles&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="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;_rotationEnd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ✂️ existing code hidden&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;knob-move-end&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="na"&gt;bubbles&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="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&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;Note, we need to make sure we specify &lt;code&gt;bubbles: true&lt;/code&gt; because our &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture" rel="noopener noreferrer"&gt;listener is going to be a parent element&lt;/a&gt;. You can try removing this and you'll see the event never "bubbles up" to the parent nodes.&lt;/p&gt;

&lt;p&gt;With these events firing, we can listen for them just like any other:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;knob-move-start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logEvent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Take a peek at the demo below to see how we're using the &lt;code&gt;logEvent()&lt;/code&gt; function to light up some &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; elements when the events fire. &lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/building-rotavo-05?previewSize=100&amp;amp;path=index.html" alt="building-rotavo-05 on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  ⚖️ Have some sense of proportion
&lt;/h2&gt;

&lt;p&gt;Currently the &lt;code&gt;value&lt;/code&gt; of the element maps directly to its angle. If we had a volume control that went from, say, 0 to 11 then that's what we want the value to match. Otherwise we're forcing our developer to do the conversion from the angle to the value themselves, which is just rude. To address this, we'll add a &lt;code&gt;scale&lt;/code&gt; attribute where our developer can specify the value for a full rotation.&lt;/p&gt;

&lt;p&gt;First, let's add that new attribute to the element. We want the usual attribute-to-property mirroring, however a little note - our default value is &lt;code&gt;1&lt;/code&gt; as &lt;code&gt;scale&lt;/code&gt; will be a multiplier and multiplying by &lt;code&gt;0&lt;/code&gt; will always give us... well, &lt;code&gt;0&lt;/code&gt; again. Let's drop that in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// class InputKnob&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;observedAttributes&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;value&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;scale&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;get&lt;/span&gt; &lt;span class="nf"&gt;scale&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scale&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scale&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="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="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scale&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="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scale&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scale&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;However now &lt;code&gt;value&lt;/code&gt; and &lt;code&gt;_angle&lt;/code&gt; are dependent on &lt;code&gt;scale&lt;/code&gt;, so we have a bit of linking up to do. Whenever one of our attributes changes, we need to make sure we recalculate:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;attributeChangedCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attrName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oldVal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newVal&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;_angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TWO_PI&lt;/span&gt; &lt;span class="o"&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;scale&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&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;scale&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="nf"&gt;_drawState&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, if our &lt;code&gt;scale&lt;/code&gt; is 10 and our &lt;code&gt;value&lt;/code&gt; is 5, then that should be half a rotation on the knob - or an &lt;code&gt;_angle&lt;/code&gt; of π - or pointing straight down.&lt;/p&gt;

&lt;p&gt;The matching part is now when the &lt;code&gt;_angle&lt;/code&gt; changes, we also need to update the &lt;code&gt;value&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// _rotationChange()&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;value&lt;/span&gt; &lt;span class="o"&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;_angle&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TWO_PI&lt;/span&gt; &lt;span class="o"&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;scale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So, to reverse what we had above if the angle comes out at π then we should expect a value of 5. That's... well, that's actually it for adding scale. So, you can verify that in the demo below. We've set the scale to 10, so ⬆️ = 0, ➡️ = 2.5, ⬇️ = 5, ⬅️ = 7.5. Give it a 🔃 below!&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/building-rotavo-06?previewSize=100&amp;amp;path=index.html" alt="building-rotavo-06 on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;p&gt;As a little bonus, take a peek at the CSS in this demo. The layout uses a CSS Grid layout with &lt;code&gt;grid-template-areas&lt;/code&gt; where you basically draw a little text diagram of the layout you want. So, the arrangement of the items above is literally:&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="nt"&gt;grid-template-areas&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; 
  &lt;span class="s1"&gt;".  ⬆️ . "&lt;/span&gt;
  &lt;span class="s1"&gt;"⬅️ 🎛️ ➡️"&lt;/span&gt;
  &lt;span class="s1"&gt;".  ⬇️ . "&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Would I recommend this in production? Who knows... I mean, I've seen far worse.&lt;/p&gt;
&lt;h2&gt;
  
  
  🔒 Enforcing limits
&lt;/h2&gt;

&lt;p&gt;While there's a certain whimsical freedom in being able to spin the knob infinitely, every so often we have a need to set some limits. A volume control would make no sense if it allowed values below zero, and if you can go higher than 11 - well, who knows what the consequences might be.&lt;/p&gt;

&lt;p&gt;Let's set up some attributes to hold the minimum and maximum limits for the element, appropriately named &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt;. This is, hopefully unsurprisingly, the same as the &lt;code&gt;scale&lt;/code&gt; attribute we added earlier.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// class InputKnob&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;observedAttributes&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;value&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;scale&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;min&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;max&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;get&lt;/span&gt; &lt;span class="nf"&gt;min&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;min&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;min&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min&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="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;max&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;max&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;max&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&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="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;max&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&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 default value is &lt;code&gt;null&lt;/code&gt; since we won't want to enforce the limit if it's not set. In other words, if the attribute is &lt;code&gt;null&lt;/code&gt;: &lt;a href="https://i.giphy.com/media/7JvlHfd7C2GDr7zfZF/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/7JvlHfd7C2GDr7zfZF/giphy.gif" title="The limit does not exist." alt="the limit does not exist" width="498" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That means instead of just calculating and setting the &lt;code&gt;_angle&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt; we need to check if they're within the bounds first. The calculations stay the same, we're just renaming to &lt;code&gt;_attemptedAngle&lt;/code&gt; and &lt;code&gt;_attemptedValue&lt;/code&gt;. Then we check to see if the limit is set that our attempted value is on the right side of it before we transfer the value over.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// _rotationChange()&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;_attemptedAngle&lt;/span&gt; &lt;span class="o"&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;_initialAngle&lt;/span&gt;
  &lt;span class="o"&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;_initialTouchAngle&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="nf"&gt;atan2&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;_touchY&lt;/span&gt; &lt;span class="o"&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;_centerY&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;_touchX&lt;/span&gt; &lt;span class="o"&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;_centerX&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;_attemptedAngle&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;_attemptedAngle&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;TWO_PI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;TWO_PI&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;_attemptedValue&lt;/span&gt; &lt;span class="o"&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;_attemptedAngle&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TWO_PI&lt;/span&gt; &lt;span class="o"&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;scale&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="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;min&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&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;_attemptedValue&lt;/span&gt; &lt;span class="o"&gt;&amp;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;min&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;max&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&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;_attemptedValue&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&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;max&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_angle&lt;/span&gt; &lt;span class="o"&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;_attemptedAngle&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;value&lt;/span&gt; &lt;span class="o"&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;_attemptedValue&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;With that logic in place, now we can add a knob that restricts its movement between two values:&lt;br&gt;
&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;input-knob&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt; &lt;span class="na"&gt;scale=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt; &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"2.5"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"7.5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Give it a go in the demo. Spin all you like, but those upper values are all off limits! ⛔&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/building-rotavo-07?previewSize=100&amp;amp;path=index.html" alt="building-rotavo-07 on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  🧛‍♂️ Count rotations
&lt;/h2&gt;

&lt;p&gt;If you're the type of engineer whose natural inclination is to immediately break the lovingly crafted code in front of you, then you may wondered, "what happens if &lt;code&gt;max&lt;/code&gt; is higher than the &lt;code&gt;scale&lt;/code&gt;?" Luckily, nothing breaks per se but it does make that &lt;code&gt;max&lt;/code&gt; value a bit meaningless as we can never reach it. Well... unless we can count the number of rotations. For example, one complete turn gets us to 10, another complete turn gets us to 20, and so on. Think of it like a winch or a crank pulling a bucket out of a well - as you turn the crank, the rope winds in or out until it reaches the top or bottom.&lt;/p&gt;

&lt;p&gt;We're not going to expose &lt;code&gt;_rotations&lt;/code&gt; as an attribute since it's a result of &lt;code&gt;value&lt;/code&gt; and &lt;code&gt;scale&lt;/code&gt;. If we did have it there, we would need to introduce some confusing precedence rules about what happens if you set things that conflict and... eugh, I'm not touching that. Anyway, let's get that derived &lt;code&gt;_rotations&lt;/code&gt; value initialised when the element is connected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;attributeChangedCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attrName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oldVal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newVal&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;_angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TWO_PI&lt;/span&gt; &lt;span class="o"&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;scale&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&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;scale&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;_rotations&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="nf"&gt;floor&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;value&lt;/span&gt; &lt;span class="o"&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;scale&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="nf"&gt;_drawState&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;You can see the parallel to how the angle is set: &lt;code&gt;_angle&lt;/code&gt; is the remainder (or modulus) of the &lt;code&gt;value&lt;/code&gt; divided by the &lt;code&gt;scale&lt;/code&gt;. The number of &lt;code&gt;_rotations&lt;/code&gt; is the whole value (or the quotient) of the &lt;code&gt;value&lt;/code&gt; divided by the &lt;code&gt;scale&lt;/code&gt;. That's the pairing of &lt;code&gt;a % b&lt;/code&gt; on the first lint and &lt;code&gt;Math.floor(a / b)&lt;/code&gt; on the second.&lt;/p&gt;

&lt;p&gt;To track when there's a change in rotation we are going to divide our element into four quadrants. A move between either of the top quadrants is going to count as a change in rotation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F4pvnl2xefizkrtikh8zu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F4pvnl2xefizkrtikh8zu.png" alt="A round element with quadrants overlaid. A circular path indicates a touch going from the top-left to the top-right quadrant resulting in an increment to the rotation." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moving in or out of the lower quadrants is going to just be movement within the same rotation. This change means we need to track the previous angle so we have something to compare against when we calculate the new one.&lt;/p&gt;

&lt;p&gt;The last bit to consider before we look at the code is that we now effectively have two modes of operation for our element. The first we've seen - spin the knob and once you go over the upper &lt;code&gt;scale&lt;/code&gt; then you loop round to &lt;code&gt;0&lt;/code&gt;. However, now we're tracking rotations we've got a &lt;code&gt;value&lt;/code&gt; that increases on every rotation. It's probably not a good idea to have a control that lets the user increase the value to infinity, so we should ensure that's bounded in some way. That means the check we're going to add is that we will only track &lt;code&gt;_rotations&lt;/code&gt; if the &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; values are set. I know, I know - we're not currently validating those attributes at all... but, I've got to save some content for the next article!&lt;/p&gt;

&lt;p&gt;Right, let's step through tracking that change in rotation:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// _rotationChange()&lt;/span&gt;
&lt;span class="c1"&gt;// Grab the previous angle for comparison&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;_previousAttemptedAngle&lt;/span&gt; &lt;span class="o"&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;_attemptedAngle&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;_attemptedAngle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ✂️ calculate attempted angle&lt;/span&gt;

&lt;span class="c1"&gt;// Track rotations if max and min are set&lt;/span&gt;
&lt;span class="k"&gt;if &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;max&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;min&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// +1 rotation if:&lt;/span&gt;
  &lt;span class="c1"&gt;// new angle is in the top-right quadrant, e.g. &amp;lt; ½π&lt;/span&gt;
  &lt;span class="c1"&gt;// old angle is in the top-left quadrant, e.g. &amp;gt; 1½π&lt;/span&gt;
  &lt;span class="k"&gt;if &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;_attemptedAngle&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1.57&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;_previousAttemptedAngle&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;4.71&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;_attemptedRotations&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="c1"&gt;// -1 rotation if:&lt;/span&gt;
  &lt;span class="c1"&gt;// old angle is in the top-right quadrant, e.g. &amp;lt; ½π&lt;/span&gt;
  &lt;span class="c1"&gt;// new angle is in the top-left quadrant, e.g. &amp;gt; 1½π&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_previousAttemptedAngle&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1.57&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;_attemptedAngle&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;4.71&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;_attemptedRotations&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// New value now includes the rotations&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;_attemptedValue&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;_attemptedAngle&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TWO_PI&lt;/span&gt; &lt;span class="o"&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;scale&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt; &lt;span class="o"&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;_attemptedRotations&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Update everything if the value is within bounds&lt;/span&gt;
&lt;span class="k"&gt;if &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;min&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&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;_attemptedValue&lt;/span&gt; &lt;span class="o"&gt;&amp;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;min&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;max&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&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;_attemptedValue&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&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;max&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_angle&lt;/span&gt; &lt;span class="o"&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;_attemptedAngle&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;_rotations&lt;/span&gt; &lt;span class="o"&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;_attemptedRotations&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;value&lt;/span&gt; &lt;span class="o"&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;_attemptedValue&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;Demo time! Let's bring it full circle (aaay! 🥁) with our first demo where we had an &lt;code&gt;&amp;lt;input type="range"&amp;gt;&lt;/code&gt; controlling the &lt;code&gt;&amp;lt;input-knob&amp;gt;&lt;/code&gt;. Flip it back and reverse it, so we've got an &lt;code&gt;&amp;lt;input-knob&amp;gt;&lt;/code&gt; controlling an &lt;code&gt;&amp;lt;input type="range"&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can set the tags up like so:&lt;br&gt;
&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;input-knob&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt; &lt;span class="na"&gt;scale=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt; &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/input-knob&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"range"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"progress"&lt;/span&gt; &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then using the same listener for &lt;code&gt;knob-move-change&lt;/code&gt; we update those values:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// logEvent()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;curValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;knob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;showValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;curValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;curValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now 10 rotations of the element should take you all the way from 0 to 💯. Ideal place to wrap things up for this entry, I think.&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/building-rotavo-08?previewSize=100&amp;amp;path=index.html" alt="building-rotavo-08 on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Next time we are going to ensure our component is accessible, because while the touch input is fun - it's not an option for everyone.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🎁 Bonus content
&lt;/h2&gt;

&lt;p&gt;Oh ho, I couldn't leave you without a little treat at the end now, could I? So, continuing the somewhat dubious tradition of borrowing my colleagues' faces (thank you / apologies to &lt;a href="https://twitter.com/jaffathecake" rel="noopener noreferrer"&gt;Jake Archibald&lt;/a&gt;) please feel free to discover what happens when you wind up this... "Jake in a box".&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/jake-in-the-box?previewSize=100&amp;amp;path=index.html" alt="jake-in-the-box on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;


</description>
      <category>javascript</category>
      <category>webcomponents</category>
      <category>rotavo</category>
    </item>
    <item>
      <title>You spin me right r und</title>
      <dc:creator>Rowan Merewood</dc:creator>
      <pubDate>Mon, 08 Jul 2019 09:52:54 +0000</pubDate>
      <link>https://dev.to/rowan_m/you-spin-me-right-r-und-k32</link>
      <guid>https://dev.to/rowan_m/you-spin-me-right-r-und-k32</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the first in a series of posts detailing how I built the&lt;/em&gt; &lt;a href="https://bit.ly/rotavo-pwa" rel="noopener noreferrer"&gt;🥑 &lt;em&gt;Rotavo PWA&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. Give it a whirl and see what you can draw!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;None of the cool kids are into skeuomorphism any more, but that doesn’t mean there’s no place for an analogue to physical controls in the software world. Let's see how we can recreate the (hopefully) intuitive experience adjusting the volume on your stereo 📻 by building our own rotating, touch-sensitive knob web component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🎛️ Sliders, tapes, and dials&lt;/li&gt;
&lt;li&gt;⚛️ A basic Custom Element&lt;/li&gt;
&lt;li&gt;👻 Enter the Shadow DOM&lt;/li&gt;
&lt;li&gt;🚧 Browser support&lt;/li&gt;
&lt;li&gt;🤝 Handle touch&lt;/li&gt;
&lt;li&gt;👆 Pointer Events and default behaviour&lt;/li&gt;
&lt;li&gt;📐 Touching trigonometry&lt;/li&gt;
&lt;li&gt;🔄 A touching demo&lt;/li&gt;
&lt;li&gt;🚧 Oh wait… browser support&lt;/li&gt;
&lt;li&gt;🖌️ Painting and performance&lt;/li&gt;
&lt;li&gt;🏆 Bonus content&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🎛️ Sliders, tapes, and dials
&lt;/h2&gt;

&lt;p&gt;We're going to use &lt;code&gt;input type="range"&lt;/code&gt; as the basis of the API we want our component to present. The snippet below will create a little slider that will go from from 0 up to 100 with a starting value of 42.&lt;br&gt;
&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;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"range"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"volume"&lt;/span&gt; &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"42"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.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%2Fj60edlf70dplue9svxu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fj60edlf70dplue9svxu5.png" alt="The default slider view in Chrome" width="580" height="134"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A solid foundation, but is it the best solution? If like me, you enjoy the light reading that is the proceedings of the &lt;a href="https://books.google.co.uk/books?id=I47_sQnnf_IC&amp;amp;pg=PA773&amp;amp;lpg=PA773&amp;amp;dq=" rel="noopener noreferrer"&gt;Symposium on Human Interface 2009, Held as Part of HCI International 2009&lt;/a&gt;, then nestling in part 2 around page 773, you'll find this valuable nugget:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pilots find it harder to read tape displays than analog dials&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We're going to need a little pilot lingo to make sense of the UX knowledge that's being dropped here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvhqci42ahgzz7dc5wrp1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvhqci42ahgzz7dc5wrp1.png" alt="Diagram showing a linear " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A "tape" display shows information in a linear fashion, named because it was literally a tape that spools up and down behind the glass. This might be used to show something like altitude or air speed. An alternative is the dial indicator that displays that same linear information in a rounded layout, often used on a car dashboard for speed and fuel level. The hypothesis here is that a human can quicker interpret a rough value from the dial as opposed to the tape because the entire range is visible.&lt;/p&gt;

&lt;p&gt;I'll be honest, this isn't overly relevant to building our component but I wanted your first impression to be that I was taking an evidence-based approach to my work. Plus, the more I think about it I'm not sure the benefit of seeing the whole scale in one go on a dial actually applies to a knob we can rotate multiple times… The truth is, I built this for fun and it's a happy accident that something of value came out of it. We shall park the Engineering vs. Art debate for now though and move on to creating this component.&lt;/p&gt;
&lt;h2&gt;
  
  
  ⚛️ A basic Custom Element
&lt;/h2&gt;

&lt;p&gt;I'm going to zip through the basics fairly quickly, so if you're looking for a proper introduction you should head over to "&lt;a href="https://developers.google.com/web/fundamentals/web-components/customelements" rel="noopener noreferrer"&gt;Custom Elements v1: Reusable Web Components&lt;/a&gt;".&lt;/p&gt;

&lt;p&gt;We're going to start simple: an element with a single &lt;code&gt;value&lt;/code&gt; attribute. A Custom Element needs a "&lt;code&gt;-&lt;/code&gt;" in the name to distinguish it from built-in elements, so let's go with &lt;code&gt;&amp;lt;input-knob&amp;gt;&lt;/code&gt; to provide that link back to &lt;code&gt;&amp;lt;input type="range"&amp;gt;&lt;/code&gt;:&lt;br&gt;
&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;input-knob&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/input-knob&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For convenience, we're going to map the attribute on the HTML element to the JavaScript class as well so that we can do things like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-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;knob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input-knob&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;knob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That means we need to extend the &lt;code&gt;HTMLElement&lt;/code&gt; class, wire up the attributes, and then connect it to our page:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InputKnob&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Nothing to do in here at the moment, but we do need to call the parent&lt;/span&gt;
    &lt;span class="c1"&gt;// constructor&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Watch for changes on the 'value' attribute&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;observedAttributes&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;value&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;// Map the JavaScript property to the HTML attribute&lt;/span&gt;
  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;value&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&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="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="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Respond to any changes to the observed attributes&lt;/span&gt;
  &lt;span class="nf"&gt;attributeChangedCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attrName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oldVal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newVal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// nothing yet&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Map the class to an element name and make it available to the page!&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;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input-knob&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;InputKnob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, if you load that up in the browser you will see… nothing. Nothing on the page and nothing in the console. Nothing is displaying, but also - nothing is explicitly broken. We've successfully attached a new element in the page, but all we've created is an empty element. So, it's working in the same way that adding an empty &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; to a page "works". Conveniently though, our new &lt;code&gt;&amp;lt;input-knob&amp;gt;&lt;/code&gt; element can be styled just like anything else on the page - let's make things a bit more obvious.&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="nt"&gt;input-knob&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="nb"&gt;block&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="m"&gt;#cadbbc&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;1rem&lt;/span&gt; &lt;span class="nb"&gt;dashed&lt;/span&gt; &lt;span class="m"&gt;#356211&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3rem&lt;/span&gt; &lt;span class="m"&gt;0.3rem&lt;/span&gt; &lt;span class="m"&gt;0.3rem&lt;/span&gt; &lt;span class="n"&gt;rgba&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="m"&gt;0&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="m"&gt;0.5&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;8rem&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;8rem&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;a href="https://media2.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%2Ft0efd3lsf3vqzpxnmf4y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ft0efd3lsf3vqzpxnmf4y.png" alt="A light-green square with a dark-green dashed border on a light-yellow background" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Et voilà, a visible square!&lt;/p&gt;
&lt;h2&gt;
  
  
  👻 Enter the Shadow DOM
&lt;/h2&gt;

&lt;p&gt;With the element display, we should do something with the &lt;code&gt;value&lt;/code&gt; attribute. Specifically, we want to rotate the element based on the attribute's value. The core of this is going to be a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties" rel="noopener noreferrer"&gt;CSS variable&lt;/a&gt; that we use in a &lt;code&gt;transform&lt;/code&gt;, like this:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0rad&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--angle&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;However, we don't want to just set this style directly on the &lt;code&gt;&amp;lt;input-knob&amp;gt;&lt;/code&gt; element as it's exposing an implementation detail to the user of our component. If a developer wants to add &lt;code&gt;&amp;lt;input-knob&amp;gt;&lt;/code&gt; to their page and apply a rotation, it's going to clash. To address this, we're going to make use of the &lt;a href="https://developers.google.com/web/fundamentals/web-components/shadowdom" rel="noopener noreferrer"&gt;Shadow DOM&lt;/a&gt; to create some markup that's essentially private to our component where we can add our own structure and style needed to enable the component's behaviour.&lt;/p&gt;

&lt;p&gt;We're going to start with this HTML:&lt;br&gt;
&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;style&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;#container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0rad&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--angle&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&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;"container"&lt;/span&gt; &lt;span class="na"&gt;part=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;slot&amp;gt;&amp;lt;/slot&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;p&gt;Let's break down what's happening here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;--angle&lt;/code&gt; CSS variable will be updated to control the rotation of the element&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;id="container"&lt;/code&gt; in the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is defining the wrapper element we're going to rotate&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;part="container"&lt;/code&gt; in the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; instructs that we want to make the element available for styling externally&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;slot&amp;gt;&lt;/code&gt; element defines where &lt;code&gt;&amp;lt;input-knob&amp;gt;&lt;/code&gt;content in the tag&lt;code&gt;&amp;lt;/input-knob&amp;gt;&lt;/code&gt; should be rendered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason we want to expose the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; for styling via &lt;code&gt;part="container"&lt;/code&gt; is because our now our "&lt;code&gt;&amp;lt;input-knob&amp;gt;&lt;/code&gt;" element is not going to rotate. So, that can still be styled - but that will remain fixed when the value changes. Instead we need to make use of the &lt;a href="https://meowni.ca/posts/part-theme-explainer/" rel="noopener noreferrer"&gt;&lt;code&gt;::part()&lt;/code&gt; selector&lt;/a&gt; in our style sheet. In this case, it's a simple addition.&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="nt"&gt;input-knob&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;container&lt;/span&gt;&lt;span class="o"&gt;)&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="nb"&gt;block&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="m"&gt;#cadbbc&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;1rem&lt;/span&gt; &lt;span class="nb"&gt;dashed&lt;/span&gt; &lt;span class="m"&gt;#356211&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3rem&lt;/span&gt; &lt;span class="m"&gt;0.3rem&lt;/span&gt; &lt;span class="m"&gt;0.3rem&lt;/span&gt; &lt;span class="n"&gt;rgba&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="m"&gt;0&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="m"&gt;0.5&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;8rem&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;8rem&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;Back to our element, now we know what structure we want it's time to drop that in a template our element can use. We'll define that just outside the &lt;code&gt;InputKnob&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-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;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;template&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
  &amp;lt;style&amp;gt;
    #container {
      --angle: 0rad;
      transform: rotate(var(--angle));
    }
  &amp;lt;/style&amp;gt;
  &amp;lt;div id="container" part="container"&amp;gt;
    &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
  &amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next we want to add a shadow root to our element and clone that template into it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InputKnob&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&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="nf"&gt;attachShadow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&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;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cloneNode&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;_container&lt;/span&gt; &lt;span class="o"&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;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;container&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;// ✂️ rest of the class omitted&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We're grabbing a reference to our &lt;code&gt;#container&lt;/code&gt; so that we can update that &lt;code&gt;--angle&lt;/code&gt; variable.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// class InputKnob&lt;/span&gt;
&lt;span class="nf"&gt;_drawState&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;_container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--angle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&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;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;rad`&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;We want to update that value when the component is first added to the page and whenever the &lt;code&gt;value&lt;/code&gt; is changed.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// class InputKnob&lt;/span&gt;
&lt;span class="nf"&gt;connectedCallback&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="nf"&gt;_drawState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;attributeChangedCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attrName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oldVal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newVal&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="nf"&gt;_drawState&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;With that, we've now got a component that rotates based on the &lt;code&gt;value&lt;/code&gt; attribute. We can verify that by opening up the browser console to try setting those values and watching the square rotate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fpirfard0raix8ja0mn7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpirfard0raix8ja0mn7x.png" alt="Screenshot of Chrome with Developer tools open" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, ok - you're underwhelmed, I should give you something interactive. Let's bring it full circle and add a slider to control the rotation. It's as simple as:&lt;br&gt;
&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;input-knob&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"2.5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/input-knob&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"range"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"angle"&lt;/span&gt; &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"6.28"&lt;/span&gt; &lt;span class="na"&gt;step=&lt;/span&gt;&lt;span class="s"&gt;"0.1"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;knob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input-knob&lt;/span&gt;&lt;span class="dl"&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;slider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[type=range]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Just take the value from the slider and set it straight on the knob&lt;/span&gt;
  &lt;span class="nx"&gt;slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;input&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;knob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Try it for yourself here below. The slider should take the square from 0 to just under 2π (6.28) radians or a full rotation.&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/building-rotavo-01?previewSize=100&amp;amp;path=index.html" alt="building-rotavo-01 on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  🚧 Browser support
&lt;/h2&gt;

&lt;p&gt;That was a little too clean though, eh? Hardly what modern web development is all about. I may have neglected to mention that there's a bit more going on in the above example to get it display in different browsers. In fact, if you check &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::part#Browser_compatibility" rel="noopener noreferrer"&gt;MDN's section on browser support for &lt;code&gt;::part()&lt;/code&gt;&lt;/a&gt; - hmm, well I should probably drop a pull request in for that. Basically, it's &lt;a href="https://chromestatus.com/features/5763933658939392" rel="noopener noreferrer"&gt;Chrome and other browsers have made positive noises&lt;/a&gt; but we're not there yet.&lt;/p&gt;

&lt;p&gt;First issue is addressing browsers that don't support Custom Elements or Shadow DOM as much as we need. Luckily, we can use the &lt;a href="https://github.com/webcomponents/polyfills/tree/master/packages/webcomponentsjs" rel="noopener noreferrer"&gt;&lt;code&gt;webcomponents.js&lt;/code&gt; polyfill&lt;/a&gt; which will detect what support is needed. Drop that script in before importing / defining the element. I'm grabbing it from &lt;code&gt;unpkg.com&lt;/code&gt;, but you can also just download the bundle or use &lt;code&gt;npm&lt;/code&gt; as per the instructions.&lt;br&gt;
&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;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/@webcomponents/webcomponentsjs"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Don't get lulled into a false sense of security - it gets dirty now. I couldn't find a good way of detecting support for &lt;code&gt;::part()&lt;/code&gt;, so bear with me. Remember we added the &lt;code&gt;part="container"&lt;/code&gt; attribute to our element? We're going to test if that attribute is accessible via JavaScript. If it isn't, we need to create some alternate structure to style. In this case, a &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; to wrap any content in the element and a &lt;code&gt;.fallback&lt;/code&gt; class.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// connectedCallback()&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&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;_container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// create a &amp;lt;span&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;span&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// move this element's child nodes into it&lt;/span&gt;
  &lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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;childNodes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// add the classes&lt;/span&gt;
  &lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fallback&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;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fallback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// and add the wrapper into this element&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wrapper&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 we duplicate the styling for the fallback elements:&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="nt"&gt;input-knob&lt;/span&gt;&lt;span class="nc"&gt;.fallback&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="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;input-knob&lt;/span&gt;&lt;span class="nc"&gt;.fallback&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="nc"&gt;.fallback&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="nb"&gt;block&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="m"&gt;#cadbbc&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;1rem&lt;/span&gt; &lt;span class="nb"&gt;dashed&lt;/span&gt; &lt;span class="m"&gt;#356211&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3rem&lt;/span&gt; &lt;span class="m"&gt;0.3rem&lt;/span&gt; &lt;span class="m"&gt;0.3rem&lt;/span&gt; &lt;span class="n"&gt;rgba&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="m"&gt;0&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="m"&gt;0.5&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;8rem&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;8rem&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;Ideally, instead of duplicating the whole block we could add the selector to our existing rule - however, that causes at least one of the other browsers to ignore the whole thing. So, copy/paste it is. Gross. Anyway, now we have an element that works just about everywhere and we shall never speak of this again. Let's move onto something more fun.&lt;/p&gt;
&lt;h2&gt;
  
  
  🤝 Handle touch
&lt;/h2&gt;

&lt;p&gt;Using the slider proves we can control the rotation, but things get a little more complicated if we want to be able to tap and drag the element around. We're going to need to do three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;a href="https://developers.google.com/web/updates/2016/10/pointer-events" rel="noopener noreferrer"&gt;Pointer Events&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Suppress some default browser behaviour&lt;/li&gt;
&lt;li&gt;Desperately try and remember some trigonometry from school&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  👆 Pointer Events and default behaviour
&lt;/h2&gt;

&lt;p&gt;Let's start with Pointer Events (frankly, it's a lovely API) and throw in a dash of dealing with defaults as neither of these are going to cause us much pain - unlike that last item on the list.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InputKnob&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;HTMLElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ✂️ existing code has been removed&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Capture the pointer so that the user can still rotate the element even&lt;/span&gt;
    &lt;span class="c1"&gt;// if they leave the bounding area of it.&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPointerCapture&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;pointerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Re-bind this for listeners so that this == instance of this class&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;_onPointerdown&lt;/span&gt; &lt;span class="o"&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;_onPointerdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onPointermove&lt;/span&gt; &lt;span class="o"&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;_onPointermove&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onPointerup&lt;/span&gt; &lt;span class="o"&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;_onPointerup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&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="nf"&gt;connectedCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Listen for pointerdown events when the element is connected to the page&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;pointerdown&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;_onPointerdown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;disconnectedCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// And stop listening if the element is removed&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;pointerdown&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;_onPointerdown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;_onPointerdown&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="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// A long press can bring up the context menu, we want to disable that when&lt;/span&gt;
    &lt;span class="c1"&gt;// the user is controlling the element&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;oncontextmenu&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="c1"&gt;// Only add listeners for the other events once interaction has started&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;pointermove&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;_onPointermove&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="nf"&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;pointerup&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;_onPointerup&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="nf"&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;pointercancel&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;_onPointerup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;_onPointermove&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="c1"&gt;// On all of these we're preventing the default behaviour as we assume our&lt;/span&gt;
    &lt;span class="c1"&gt;// web developer won't do something like put a &amp;lt;button&amp;gt; inside the element.&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;_onPointerup&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="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Re-enable the context menu&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;oncontextmenu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Release the pointer we captured earlier&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;releasePointerCapture&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;pointerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Remove all our listeners&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;pointermove&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;_onPointermove&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="nf"&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;pointerup&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;_onPointerup&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="nf"&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;pointercancel&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;_onPointerup&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;We also want to update the CSS in our &lt;code&gt;template&lt;/code&gt; to ensure some sensible interactions. We're going to make use of the &lt;code&gt;:host&lt;/code&gt; pseudo-class to select the outer &lt;code&gt;&amp;lt;input-knob&amp;gt;&lt;/code&gt; tag.&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="nt"&gt;host&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="py"&gt;user-select&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;-webkit-user-select&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="py"&gt;touch-action&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="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;#container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0rad&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--angle&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;There are three things happening in the above styles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, if you examine the &lt;code&gt;&amp;lt;input-knob&amp;gt;&lt;/code&gt; element from the previous example in DevTools, you'll see it has an &lt;code&gt;auto&lt;/code&gt; width and height and isn't taking up space in the document. We need the element to have a size if our user is going to have anything to touch!&lt;/li&gt;
&lt;li&gt;Secondly, the &lt;code&gt;user-select&lt;/code&gt; lines are preventing the user from selecting any text inside the control. Much like preventing the context menu from popping up, this stops odd text selection effects appearing when the user has their finger held down.&lt;/li&gt;
&lt;li&gt;Thirdly, the &lt;code&gt;touch-action&lt;/code&gt; tells the browser not to handle any of the usual gestures on this element - like panning, zooming, etc. as we're going to implement our own behaviour here.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  📐 Touching trigonometry
&lt;/h2&gt;

&lt;p&gt;Now the listeners are wired up, our element will receive pointer events that we can respond to. Let's dive into the math!&lt;/p&gt;

&lt;p&gt;As a gentle intro, we're creating a convenient constant outside our class for later use:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-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;TWO_PI&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next, we need to initialise some values when the user first touches the element. The easy ones to get are the coordinates of the pointer event, the centre of the element, and the starting angle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fj6bch0yzmkjqhewx48zb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fj6bch0yzmkjqhewx48zb.png" alt="A square element with a point indicated at the centre and one in the upper right of the square" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// _onPointerdown(e)&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;_touchX&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_touchY&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_centerX&lt;/span&gt; &lt;span class="o"&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;offsetLeft&lt;/span&gt; &lt;span class="o"&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;scrollLeft&lt;/span&gt; &lt;span class="o"&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;clientLeft&lt;/span&gt; &lt;span class="o"&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;offsetWidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&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;_centerY&lt;/span&gt; &lt;span class="o"&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;offsetTop&lt;/span&gt; &lt;span class="o"&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;scrollTop&lt;/span&gt; &lt;span class="o"&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;clientTop&lt;/span&gt; &lt;span class="o"&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;offsetHeight&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&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;_initialAngle&lt;/span&gt; &lt;span class="o"&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;_angle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next we need the starting angle of the touch away from a zero rotation. We can visualise this as drawing a right-angle triangle with one corner at the centre of the object and the hypotenuse going out to the user's pointer. We can get this angle from the 2 argument arctangent function, AKA &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2" rel="noopener noreferrer"&gt;&lt;code&gt;Math.atan2()&lt;/code&gt;&lt;/a&gt;. By taking the pointer event coordinates and subtracting the centre coordinates for the element, we are basically normalising them as if the centre of the element was 0, 0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fcx3ajhei4utuystk56ms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fcx3ajhei4utuystk56ms.png" alt="A square element with a point indicated at the centre and one in the upper right of the square with a right angle triangle drawn through those points" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// _onPointerdown(e)&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;_initialTouchAngle&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="nf"&gt;atan2&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;_touchY&lt;/span&gt; &lt;span class="o"&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;_centerY&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;_touchX&lt;/span&gt; &lt;span class="o"&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;_centerX&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now when the user moves the pointer, we want to track that movement so we can rotate the element accordingly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fep3a00cl9eyu19cp273n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fep3a00cl9eyu19cp273n.png" alt="The same square showing the point in the upper right moving down and further to the right" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And how to we work out what's happening here? The answer is, of course, even more triangles. First, we can do exactly the same &lt;code&gt;atan2()&lt;/code&gt; calculation we did before but with the new pointer location. If we subtract the starting angle from the current angle, that gives us the amount we've rotated in that touch. For the final angle of the element we take it's current rotation, i.e. the &lt;code&gt;value&lt;/code&gt; and then add the amount we've rotated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fa4ruphfzrspxl2sn9mi4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fa4ruphfzrspxl2sn9mi4.png" alt="The same square but rotated by the change in angle" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the code, it looks like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// _onPointermove(e)&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;_touchX&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_touchY&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_angle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="c1"&gt;// initial rotation of the element&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;_initialAngle&lt;/span&gt;
  &lt;span class="c1"&gt;// subtract the starting touch angle&lt;/span&gt;
  &lt;span class="o"&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;_initialTouchAngle&lt;/span&gt;
  &lt;span class="c1"&gt;// add the current touch angle&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="nf"&gt;atan2&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;_touchY&lt;/span&gt; &lt;span class="o"&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;_centerY&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;_touchX&lt;/span&gt; &lt;span class="o"&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;_centerX&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Normalise value back into a 2π range&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;_angle&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;_angle&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;TWO_PI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;TWO_PI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Done, update the value!&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;value&lt;/span&gt; &lt;span class="o"&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;_angle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  🔄 A touching demo
&lt;/h2&gt;

&lt;p&gt;Oh my goodness, take a breather. If you're still with me, you… you've earned this. You can reach out with your mouse or finger and just spin that square around all you like.&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/building-rotavo-02?previewSize=100&amp;amp;path=index.html" alt="building-rotavo-02 on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  🚧 Oh wait… browser support
&lt;/h2&gt;

&lt;p&gt;Sorry, I know, I know. &lt;a href="https://caniuse.com/#feat=pointer" rel="noopener noreferrer"&gt;Pointer Events is getting there in terms of support&lt;/a&gt;, but it's not everywhere. We are going to need to swap in &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent" rel="noopener noreferrer"&gt;Mouse&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Touch_events" rel="noopener noreferrer"&gt;Touch&lt;/a&gt; Events when Pointer isn't supported.&lt;/p&gt;

&lt;p&gt;First step is to separate the Pointer-specific logic from the business of actually responding to the event. To be honest, that's good software engineering practice anyway and is probably how it should have been written in the first place. I'm not showing the &lt;code&gt;_rotation*()&lt;/code&gt; methods as they literally are exactly the code that was there before.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;_onPointerdown&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="nf"&gt;preventDefault&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;_touchX&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_touchY&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="c1"&gt;// ✂️ Chop out existing code, extract into new method&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_rotationStart&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="nf"&gt;setPointerCapture&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;pointerId&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="nf"&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;pointermove&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;_onPointermove&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="nf"&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;pointerup&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;_onPointerup&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="nf"&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;pointercancel&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;_onPointerup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;_onPointermove&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="nf"&gt;preventDefault&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;_touchX&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_touchY&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="c1"&gt;// ✂️ Cut and paste again&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_rotationChange&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;_onPointerup&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="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// ✂️ One last time!&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_rotationEnd&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="nf"&gt;releasePointerCapture&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;pointerId&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="nf"&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;pointermove&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;_onPointermove&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="nf"&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;pointerup&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;_onPointerup&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="nf"&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;pointercancel&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;_onPointerup&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;We also need to hook up the new listeners if we can't use Pointer Events:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// connectedCallback()&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PointerEvent&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;window&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="nf"&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;pointerdown&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;_onPointerdown&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;touchstart&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;_onTouchstart&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="nf"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onMousedown&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 clean up when we're done:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// disconnectedCallback&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PointerEvent&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;window&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="nf"&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;pointerdown&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;_onPointerdown&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;touchstart&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;_onTouchstart&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="nf"&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;mousedown&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;_onMousedown&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 Mouse Events are easy to add in, since the surface we're using is identical to the Pointer equivalent:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;_onMousedown&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_touchX&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_touchY&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_rotationStart&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="nf"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onMousemove&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="nf"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onMouseup&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;_onMousemove&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="nf"&gt;preventDefault&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;_touchX&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_touchY&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_rotationChange&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;_onMouseup&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="nf"&gt;preventDefault&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="nf"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onMousemove&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="nf"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_onMouseup&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="nf"&gt;_rotationEnd&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;Touch Events are slightly more involved as there's scope for multiple touches. However, we're just assuming the first touch is the one we care about and… well, it hasn't caused a problem yet.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;_onTouchstart&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="nf"&gt;preventDefault&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;_touchX&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;changedTouches&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;clientX&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;_touchY&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;changedTouches&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;clientY&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="nf"&gt;_rotationStart&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="nf"&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;touchmove&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;_onTouchmove&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="nf"&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;touchend&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;_onTouchend&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="nf"&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;touchcancel&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;_onTouchend&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;_onTouchmove&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="nf"&gt;preventDefault&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;_touchX&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;targetTouches&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;clientX&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;_touchY&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;targetTouches&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;clientY&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="nf"&gt;_rotationChange&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;_onTouchend&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="nf"&gt;preventDefault&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="nf"&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;touchmove&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;_onTouchmove&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="nf"&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;touchend&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;_onTouchend&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="nf"&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;touchcancel&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;_onTouchend&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="nf"&gt;_rotationEnd&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;Whoo! That wasn't too bad - now our code has a better separation of concerns and cross-browser support!&lt;/p&gt;
&lt;h2&gt;
  
  
  🖌️ Painting and performance
&lt;/h2&gt;

&lt;p&gt;We're not quite done here though. Even though the element is functioning, we should take a peek at how well the element is performing. For this, you will want to open up the &lt;a href="https://building-rotavo-02.glitch.me" rel="noopener noreferrer"&gt;previous Glitch demo&lt;/a&gt; in a new window so you can fire up Chrome's DevTools. We want to enable "Paint flashing" - you can find it under the 3 dot menu → More tools → Rendering → Paint flashing, or if you don't enjoy three levels of menus then bring up the command palette with &lt;code&gt;Ctrl / Cmd + Shift + P&lt;/code&gt; and type "paint flashing". When you try rotating the element you will see a constant green rectangle drawn over it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqhxzqrpyqmd49hsg6ceq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqhxzqrpyqmd49hsg6ceq.png" alt="Paint flashing rectangle displaying when the element moves" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is bad. It means the browser is having to paint on every single interaction and that's not always a cheap operation. If you are on a laptop or high-end phone, you probably won't notice anything but this will hit your framerate on low-end or busy devices. Luckily, the fix is super simple to add. We want to provide an indication to the browser that we intend to change the element in some way. Conveniently, the &lt;code&gt;will-change&lt;/code&gt; property allows us to do exactly that. We specify this on the styling for our internal &lt;code&gt;#container&lt;/code&gt;.&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;#container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0rad&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--angle&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="py"&gt;will-change&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&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 the browser knows we intend to change the &lt;code&gt;transform&lt;/code&gt; property and it can optimise for that result. Compare this &lt;a href="https://building-rotavo-03.glitch.me" rel="noopener noreferrer"&gt;updated Glitch demo&lt;/a&gt; with this one line added. You should see a brief initial flash, but after that - no paint events at all. Success!&lt;/p&gt;
&lt;h2&gt;
  
  
  🏆 Bonus content
&lt;/h2&gt;

&lt;p&gt;So far, we haven't put any content within our element - we've just styled the element itself. If you look at the &lt;a href="https://bit.ly/rotavo-pwa" rel="noopener noreferrer"&gt;Rotavo app&lt;/a&gt; you'll see that the little 🥑 controls are just SVGs inside the tag. Turns out you can put any content you want inside that tag and render it. So, to finish things off for this article &lt;em&gt;(&lt;strong&gt;with a huge thank you to the ever patient &lt;a href="https://twitter.com/Paul_Kinlan" rel="noopener noreferrer"&gt;Paul Kinlan&lt;/a&gt; for lending me his face&lt;/strong&gt;)&lt;/em&gt; please feel free to get to know… Paul Spinlan.&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/paul-spinlan?previewSize=100&amp;amp;path=index.html" alt="paul-spinlan on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Next time we will explore adding custom events, min/max values, and counting rotations to the element so we can build an app around it!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webcomponents</category>
      <category>rotavo</category>
    </item>
  </channel>
</rss>
