<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://web.dev/</id>
  <title>Boris Smus on web.dev</title>
  <updated>2026-04-15T23:21:06Z</updated>
  <author>
    <name>Boris Smus</name>
  </author>
  <link href="https://web.dev/authors/smus/feed.xml" rel="self"/>
  <link href="https://web.dev/"/>
  <icon>https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qjz6fGVI9DfXFbR2n4KC.png?auto=format</icon>
  <logo>https://web.dev/images/shared/rss-banner.png</logo>
  <subtitle>Google Developer Relations</subtitle>
  
  
  <entry>
    <title>Easy high DPI images</title>
    <link href="https://web.dev/easy-high-dpi-images/"/>
    <updated>2013-03-28T00:00:00Z</updated>
    <id>https://web.dev/easy-high-dpi-images/</id>
    <content type="html" mode="escaped">&lt;p&gt;Displays with high pixel density are quickly becoming the norm. Content
creators need to adapt to this fact. This is a short guide on how to
serve high quality images on the web today, without polyfills,
JavaScript, CSS hacks, and browser features that aren&#39;t quite implemented
yet. In a word: without drastic changes to your workflow.&lt;/p&gt;
&lt;p&gt;There are many responsive image proposals today, many of which involve
significant changes for the web developer. The standards-track &lt;code&gt;srcset&lt;/code&gt;
&lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; attribute is difficult to implement, especially with the
complexity of &lt;code&gt;srcset&lt;/code&gt;&#39;s additional viewport-based selection:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;banner-HD.jpeg 2x, banner-phone.jpeg 100w, banner-phone-HD.jpeg 100w 2x&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Even though the &lt;code&gt;image-set&lt;/code&gt; CSS property only uses &lt;code&gt;devicePixelRatio&lt;/code&gt; to
decide which image to load, it still forces developers to write a lot
of extra markup for every image.&lt;/p&gt;
&lt;p&gt;Other proposals, like the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element are even more verbose.
Furthermore, they are not standards-track, so their ubiquitous
availability is even further out than the srcset attribute. JavaScript
and server side solutions are the only other alternative, but these
approaches have their own drawbacks as covered in &lt;a href=&quot;http://www.html5rocks.com/en/mobile/high-dpi/&quot; rel=&quot;noopener&quot;&gt;other
articles&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This article will go through several uses of images commonly found on
the web and propose simple solutions that work on screens with high
pixel densities as well as ordinary ones. For the purposes of this
discussion, any device that reports &lt;code&gt;window.devicePixelRatio&lt;/code&gt; greater
than 1 can be considered high DPI, since that means that CSS pixels
aren&#39;t the same as device pixels, and that images are being scaled up.&lt;/p&gt;
&lt;p&gt;Here&#39;s a summary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use CSS/SVG rather than raster imagery if possible.&lt;/li&gt;
&lt;li&gt;Use images optimized for high density displays by default.&lt;/li&gt;
&lt;li&gt;Use PNGs for simple drawings and pixel art (eg. logos).&lt;/li&gt;
&lt;li&gt;Use compressed JPEGs for images with a variety of colors (eg. photos).&lt;/li&gt;
&lt;li&gt;Always set explicit sizes (using CSS or HTML) on all image elements.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;simple-drawings-and-pixel-art&quot;&gt;Simple drawings and pixel art &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/easy-high-dpi-images/#simple-drawings-and-pixel-art&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Small images can often be avoided entirely by using CSS features or SVG.
There is no need to use images for rounded corners, for example, since
the &lt;code&gt;border-radius&lt;/code&gt; CSS property is widely supported. Similarly, custom
fonts are widely supported, so using &amp;quot;imaged&amp;quot; text is unadvisable.&lt;/p&gt;
&lt;p&gt;However, in some cases, like logos, an image may be the only way
forward. For example, this Chrome logo has a natural size of 256x256. On
a Retina display, you can see the line aliasing at diagonals and curves,
which looks chunky and bad, especially when compared to crisply rendered
text:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Chrome 1x&quot; decoding=&quot;async&quot; height=&quot;256&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 256px) 256px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uBEYI93uReLt091i15rF.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uBEYI93uReLt091i15rF.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uBEYI93uReLt091i15rF.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uBEYI93uReLt091i15rF.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uBEYI93uReLt091i15rF.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uBEYI93uReLt091i15rF.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uBEYI93uReLt091i15rF.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uBEYI93uReLt091i15rF.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uBEYI93uReLt091i15rF.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/uBEYI93uReLt091i15rF.png?auto=format&amp;w=512 512w&quot; width=&quot;256&quot; /&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt=&quot;Png 1x&quot; decoding=&quot;async&quot; height=&quot;200&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 200px) 200px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vbPPnlqHU5Z4sWmhDhi2.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vbPPnlqHU5Z4sWmhDhi2.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vbPPnlqHU5Z4sWmhDhi2.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vbPPnlqHU5Z4sWmhDhi2.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vbPPnlqHU5Z4sWmhDhi2.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vbPPnlqHU5Z4sWmhDhi2.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vbPPnlqHU5Z4sWmhDhi2.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vbPPnlqHU5Z4sWmhDhi2.png?auto=format&amp;w=400 400w&quot; width=&quot;200&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Natural dimensions: &lt;code&gt;256x256px&lt;/code&gt;, asset size: &lt;code&gt;31 kB&lt;/code&gt;, format: &lt;code&gt;PNG&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Convinced? Good. Now let&#39;s use a high density image. You might be tempted to
save space by storing your logo as a JPEG, but this may not be a good
idea, since saving logos and other graphics in a lossy format tends to
introduce artifacts. In this case, I&#39;ve exaggerated the problem by using
a very high compression, but notice the banding on the gradients, the
speckles on white backgrounds and the messy lines:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Chrome 2x&quot; decoding=&quot;async&quot; height=&quot;512&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 512px) 512px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/z3iHBrCE5ZsRql3OlKan.jpg?auto=format&amp;w=1024 1024w&quot; width=&quot;512&quot; /&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt=&quot;Jpeg 2x&quot; decoding=&quot;async&quot; height=&quot;200&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 200px) 200px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eAwgjoCGKNcwH8aHin4j.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eAwgjoCGKNcwH8aHin4j.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eAwgjoCGKNcwH8aHin4j.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eAwgjoCGKNcwH8aHin4j.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eAwgjoCGKNcwH8aHin4j.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eAwgjoCGKNcwH8aHin4j.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eAwgjoCGKNcwH8aHin4j.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/eAwgjoCGKNcwH8aHin4j.png?auto=format&amp;w=400 400w&quot; width=&quot;200&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Natural dimensions: &lt;code&gt;512x512px&lt;/code&gt;, asset size: &lt;code&gt;13 kB&lt;/code&gt;, format: &lt;code&gt;JPEG&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The thing to do for relatively small images is to use 2x PNGs. Be aware
that the difference in size between a 1x and 2x PNG is generally quite
high (52 kB in this case). However, in the case of a logo, it is your
website&#39;s face and the first thing your visitors will see. By skimping
too much on quality in exchange for size, it will also be the last thing
your visitors will see!&lt;/p&gt;
&lt;p&gt;Here is the Chrome logo in all its glory, sized down to half its natural
dimensions for 2x displays:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Chrome 2x&quot; decoding=&quot;async&quot; height=&quot;512&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 512px) 512px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/TJwyZv7LQiRJdo1WkY1q.png?auto=format&amp;w=1024 1024w&quot; width=&quot;512&quot; /&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt=&quot;Png 2x&quot; decoding=&quot;async&quot; height=&quot;200&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 200px) 200px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aZtUA4p757tG4DkipCQd.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aZtUA4p757tG4DkipCQd.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aZtUA4p757tG4DkipCQd.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aZtUA4p757tG4DkipCQd.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aZtUA4p757tG4DkipCQd.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aZtUA4p757tG4DkipCQd.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aZtUA4p757tG4DkipCQd.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aZtUA4p757tG4DkipCQd.png?auto=format&amp;w=400 400w&quot; width=&quot;200&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Natural dimensions: &lt;code&gt;512x512px&lt;/code&gt;, asset size: &lt;code&gt;83 kB&lt;/code&gt;, format: &lt;code&gt;PNG&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The markup to make the above render is the following:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;chrome2x.png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 256px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 256px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Note that I have specified a width and height on the image. This is
necessary because the natural size of the image is 512px. It&#39;s also good
for performance because the rendering engine has a good grasp on the
size of the element and won&#39;t need to work too hard to compute it.&lt;/p&gt;
&lt;p&gt;One possible optimization that might work is to reduce the 24-bit PNG to
a paletted 8-bit one. This works for images with a small number of
colors, the Chrome logo included. To do this optimization, you can use a
tool such as &lt;a href=&quot;http://pngquant.org/&quot; rel=&quot;noopener&quot;&gt;http://pngquant.org/&lt;/a&gt;. You can see a bit of banding here,
but this file is just 13 kB, which is a whopping 6x size saving
compared to the original 512x512 PNG.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Chrome 2x 8bit&quot; decoding=&quot;async&quot; height=&quot;512&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 512px) 512px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/s1yYo71Yl7hOIxEB2YHB.png?auto=format&amp;w=1024 1024w&quot; width=&quot;512&quot; /&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;img alt=&quot;Png 2x 8bit&quot; decoding=&quot;async&quot; height=&quot;183&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 191px) 191px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gvXG52EdAlv7L5ZvNiae.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gvXG52EdAlv7L5ZvNiae.png?auto=format&amp;w=191 191w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gvXG52EdAlv7L5ZvNiae.png?auto=format&amp;w=218 218w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gvXG52EdAlv7L5ZvNiae.png?auto=format&amp;w=248 248w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gvXG52EdAlv7L5ZvNiae.png?auto=format&amp;w=283 283w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gvXG52EdAlv7L5ZvNiae.png?auto=format&amp;w=323 323w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gvXG52EdAlv7L5ZvNiae.png?auto=format&amp;w=368 368w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/gvXG52EdAlv7L5ZvNiae.png?auto=format&amp;w=382 382w&quot; width=&quot;191&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Natural dimensions: &lt;code&gt;512x512px&lt;/code&gt;, asset size: &lt;code&gt;13 kB&lt;/code&gt;, format: &lt;code&gt;PNG, 8-bit palette&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;images-with-a-variety-of-colors&quot;&gt;Images with a variety of colors &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/easy-high-dpi-images/#images-with-a-variety-of-colors&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I wrote an HTML5Rocks article &lt;a href=&quot;http://www.html5rocks.com/en/mobile/high-dpi/&quot; rel=&quot;noopener&quot;&gt;surveying a number of different responsive
image techniques&lt;/a&gt;, and did some research around compressing
1x and 2x JPEG and comparing resulting sizes and visual quality. Here&#39;s
one such tile from the above article:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Tile.&quot; decoding=&quot;async&quot; height=&quot;453&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/y4mvARiiIIC5IrBU12Zf.jpg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;I&#39;ve labeled the images with their compression level (indicated by JPEG
quality), their size (in bytes), and my subjective opinion on their
comparative visual fidelity (ranked by numbers). The interesting bit
here is that the highly compressed 2x image (labeled 3) is &lt;strong&gt;smaller in
size&lt;/strong&gt; and &lt;strong&gt;looks better&lt;/strong&gt; than the uncompressed 1x image (labeled 4).
In other words, between images 4 and 3, we&#39;ve managed to improve the
quality of the image by doubling each dimension and significantly
increasing the compression, while at the same time, reducing the size by
2 kB.&lt;/p&gt;
&lt;h2 id=&quot;compression,-dimensions-and-visual-quality&quot;&gt;Compression, dimensions and visual quality &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/easy-high-dpi-images/#compression,-dimensions-and-visual-quality&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I wanted to get a bit more insight into tradeoffs between compression
level, image dimensions, visual quality and image size. I ran a study
with the following hypothesis based on the study above:&lt;/p&gt;
&lt;h3 id=&quot;hypothesis&quot;&gt;Hypothesis &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/easy-high-dpi-images/#hypothesis&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With enough compression, a 2x image will look equivalent to the same
image in 1x size at some other (lower) compression. However, in this
case, the highly compressed 2x image will be smaller in size than the 1x
image.&lt;/p&gt;
&lt;h3 id=&quot;process&quot;&gt;Process &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/easy-high-dpi-images/#process&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Given a 2x image, generate the 1x one.&lt;/li&gt;
&lt;li&gt;Compress both images at various levels.&lt;/li&gt;
&lt;li&gt;Create a test page that shows both image sets side-by-side.&lt;/li&gt;
&lt;li&gt;Find the place in the two sets where the images are equivalent.&lt;/li&gt;
&lt;li&gt;Note equivalent image sizes and compression levels.&lt;/li&gt;
&lt;li&gt;Try it on both a 1x and a 2x display.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I built a side-by-side image comparison app similar to &lt;a href=&quot;http://laurashoe.com/2011/10/21/lightroom-quick-tip-of-the-week-viewing-and-zooming-in-on-two-photos-side-by-side/&quot; rel=&quot;noopener&quot;&gt;Lightroom&#39;s
compare view&lt;/a&gt;. The intention is to show a 1x and a 2x
images side-by-side, but also allow you to zoom into any section of the
image to get more detail. You can also select between JPEG and WebP
formats and change compression quality to see file size and image
quality comparisons. The idea is to tweak settings over several images,
figure out what compression quality, scaling and format vs. image
quality tradeoff you are comfortable with, and use that setting for all
of your images.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Comparison screenshot&quot; decoding=&quot;async&quot; height=&quot;409&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/AJNGdL37Eurh9VCAOIBJ.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;The tool itself is &lt;a href=&quot;http://borismus.github.com/image-zoom&quot; rel=&quot;noopener&quot;&gt;available for you to play with&lt;/a&gt;. You can
&lt;strong&gt;zoom into the image&lt;/strong&gt; by selecting a sub-area to zoom into.&lt;/p&gt;
&lt;h3 id=&quot;analysis&quot;&gt;Analysis &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/easy-high-dpi-images/#analysis&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I&#39;ll say up front that image quality is a subjective thing. Also, your
particular use case will likely dictate where your priorities lie in the
visual fidelity vs. filesize spectrum. Additionally, different kinds of
image features react differently to scaling and compression quality, so
a one-size-fits-all solution may not necessarily work here. The point of
the tool is to help you build up an intuition for image quality
compressions, scales and formats.&lt;/p&gt;
&lt;p&gt;From playing with the image zoomer, a few things quickly became apparent
to me. Firstly, I prefer &lt;code&gt;quality=30 dpr=2x&lt;/code&gt; images to &lt;code&gt;quality=90 dpr=1x&lt;/code&gt; images for the increase in detail. These images are comparable
in file size as well (in the plane case, the compressed 2x image is 76
kB whereas the uncompressed 1x is 80 kB).&lt;/p&gt;
&lt;p&gt;Exceptions to this rule are highly compressed (&lt;code&gt;quality&amp;lt;30&lt;/code&gt;) images with
gradients. These tend to suffer from color banding, which is equally bad
regardless of image scale. The bird and car samples found in the tool
are examples of this.&lt;/p&gt;
&lt;p&gt;WebP images look way cleaner than JPEG, especially at low compression
levels. This color banding seems to be much less of an issue. Lastly,
WebP images are much more compact.&lt;/p&gt;
&lt;h2 id=&quot;caveats-and-fin&quot;&gt;Caveats and fin &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/easy-high-dpi-images/#caveats-and-fin&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Making images look good on high density displays is only half of the
image related problems caused by a huge variation in screens. In some
cases, you might want to serve entirely different images depending on
viewport size. For example, Obama&#39;s headshot might be appropriate for a
phone-sized screen, but the stand in front of him and flag behind him
and some might be a better fit for a laptop display.&lt;/p&gt;
&lt;p&gt;I deliberately avoided this &amp;quot;art direction&amp;quot; topic to focus on high DPI
images only. This problem can be solved by a number of different
approaches: using media queries and background images, via JavaScript,
via some new features like &lt;code&gt;image-set&lt;/code&gt;, or on the server. This topic is
covered in &lt;a href=&quot;http://www.html5rocks.com/en/mobile/high-dpi/&quot; rel=&quot;noopener&quot;&gt;High DPI Images for Variable Pixel Densities&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&#39;ll sign off with a few open issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Effects of high compression on performance. What are the penalties of
decoding highly compressed images?&lt;/li&gt;
&lt;li&gt;What are the performance penalties of having to resize the image down
when a 2x image is loaded on a 1x display?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To summarize, opt for CSS and SVG instead of using raster images. If
raster images are strictly required, use PNGs for images with a limited
palettes and many solid colors, and use JPEGs for images with many
colors and gradients. The great thing about this approach is that your
markup is virtually unchanged. All that is required of the web developer
is to generate 2x assets and size your images properly in the DOM.&lt;/p&gt;
&lt;p&gt;For further reading, check out &lt;a href=&quot;http://filamentgroup.com/lab/rwd_img_compression/&quot; rel=&quot;noopener&quot;&gt;Scott Jehl&#39;s article&lt;/a&gt; on a
similar topic.  May your images look sharp and your cell data usage be
low!&lt;/p&gt;
</content>
    <author>
      <name>Boris Smus</name>
    </author>
  </entry>
  
  <entry>
    <title>High DPI images for variable pixel densities</title>
    <link href="https://web.dev/high-dpi/"/>
    <updated>2012-08-22T00:00:00Z</updated>
    <id>https://web.dev/high-dpi/</id>
    <content type="html" mode="escaped">&lt;p&gt;One of the features of today&#39;s complex device landscape is that there&#39;s
a &lt;a href=&quot;http://en.wikipedia.org/wiki/List_of_displays_by_pixel_density&quot; rel=&quot;noopener&quot;&gt;very wide range of screen pixel densities&lt;/a&gt; available.
Some devices feature very high resolution displays, while others
straggle behind. Application developers need to support a range of
pixel densities, which can be quite challenging. On the mobile web, the
challenges are compounded by several factors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Large variety of devices with different form factors.&lt;/li&gt;
&lt;li&gt;Constrained network bandwidth and battery life.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In terms of images, the goal of web app developers is to &lt;strong&gt;serve the
best quality images as efficiently as possible&lt;/strong&gt;. This article will
cover some useful techniques for doing this today and in the near
future.&lt;/p&gt;
&lt;h3 id=&quot;avoid-images-if-possible&quot;&gt;Avoid images if possible &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#avoid-images-if-possible&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Before opening this can of worms, remember that the web has many
powerful technologies that are largely resolution- and DPI-independent.
Specifically, text, SVG and much of CSS will &amp;quot;just work&amp;quot; because of the
automatic pixel scaling feature of the web (via
&lt;a href=&quot;http://www.quirksmode.org/blog/archives/2012/06/devicepixelrati.html&quot; rel=&quot;noopener&quot;&gt;devicePixelRatio&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;That said, you can&#39;t always avoid raster images. For example, you may be
given assets that would be quite hard to replicate in pure SVG/CSS, or
you are dealing with a photograph. While you could convert the image
into SVG automatically, vectorizing photographs makes little sense
because scaled-up versions usually don&#39;t look good.&lt;/p&gt;
&lt;h2 id=&quot;background&quot;&gt;Background &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#background&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;a-very-short-history-of-display-density&quot;&gt;A very short history of display density &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#a-very-short-history-of-display-density&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the early days, computer displays had a pixel density of 72 or 96dpi
(&lt;a href=&quot;http://en.wikipedia.org/wiki/Dots_per_inch&quot; rel=&quot;noopener&quot;&gt;dots per inch&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Displays gradually improved in pixel density, largely driven by the
mobile use case, in which users generally hold their phones closer to
their faces, making pixels more visible. By 2008, 150dpi phones were the
new norm. The trend in increased display density continued, and today&#39;s
new phones sport 300dpi displays (branded &amp;quot;Retina&amp;quot; by Apple).&lt;/p&gt;
&lt;p&gt;The holy grail, of course, is a display in which pixels are completely
invisible. For the phone form factor, the current generation of
Retina/HiDPI displays may be close to that ideal. But new classes of
hardware and wearables like &lt;a href=&quot;http://en.wikipedia.org/wiki/Project_Glass&quot; rel=&quot;noopener&quot;&gt;Project Glass&lt;/a&gt; will likely continue
to drive increased pixel density.&lt;/p&gt;
&lt;p&gt;In practice, low density images should look the same on new screens as
they did on old ones, but compared to the crisp imagery high density
users are used to seeing, the low density images look jarring and
pixelated. The following is a rough simulation of how a 1x image will
look on a 2x display. In contrast, the 2x image looks quite good.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Baboon 1x&quot; decoding=&quot;async&quot; height=&quot;200&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 200px) 200px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/rWMbSX4iWVoRx5ctDu1H.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/rWMbSX4iWVoRx5ctDu1H.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/rWMbSX4iWVoRx5ctDu1H.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/rWMbSX4iWVoRx5ctDu1H.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/rWMbSX4iWVoRx5ctDu1H.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/rWMbSX4iWVoRx5ctDu1H.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/rWMbSX4iWVoRx5ctDu1H.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/rWMbSX4iWVoRx5ctDu1H.jpg?auto=format&amp;w=400 400w&quot; width=&quot;200&quot; /&gt;
&lt;img alt=&quot;Baboon 2x&quot; decoding=&quot;async&quot; height=&quot;400&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Xoh9FkidCXhfjID6ZMrM.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Xoh9FkidCXhfjID6ZMrM.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Xoh9FkidCXhfjID6ZMrM.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Xoh9FkidCXhfjID6ZMrM.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Xoh9FkidCXhfjID6ZMrM.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Xoh9FkidCXhfjID6ZMrM.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Xoh9FkidCXhfjID6ZMrM.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Xoh9FkidCXhfjID6ZMrM.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Xoh9FkidCXhfjID6ZMrM.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Xoh9FkidCXhfjID6ZMrM.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Xoh9FkidCXhfjID6ZMrM.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Xoh9FkidCXhfjID6ZMrM.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Xoh9FkidCXhfjID6ZMrM.jpg?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
&lt;figcaption&gt;Baboons! at differing pixel densities.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;pixels-on-the-web&quot;&gt;Pixels on the web &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#pixels-on-the-web&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When the web was designed, 99% of displays were 96dpi (or &lt;a href=&quot;http://blogs.msdn.com/b/fontblog/archive/2005/11/08/490490.aspx&quot; rel=&quot;noopener&quot;&gt;pretended to
be&lt;/a&gt;), and few provisions were made for variation on this
front. Because of a large variation in screen sizes and densities, we
needed a standard way to make images look good across a variety of
screen densities and dimensions.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://inamidst.com/stuff/notes/csspx&quot; rel=&quot;noopener&quot;&gt;HTML specification&lt;/a&gt; recently tackled this problem by
defining a reference pixel that manufacturers use to determine the size
of a CSS pixel.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; It is recommended that the reference pixel be the visual angle of one pixel on a device with a pixel density of 96dpi and a distance from the reader of an arm&#39;s length. For a nominal arm&#39;s length of 28 inches, the visual angle is therefore about 0.0213 degrees. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Using the reference pixel, a manufacturer can determine the size of the
device’s physical pixel relative to the standard or ideal pixel. This
ratio is called the device pixel ratio.&lt;/p&gt;
&lt;h3 id=&quot;calculating-the-device-pixel-ratio&quot;&gt;Calculating the device pixel ratio &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#calculating-the-device-pixel-ratio&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Suppose a smart phone has a screen with a physical pixel size of 180
pixels per inch (ppi). Calculating the device pixel ratio takes three
steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Compare the actual distance at which the device is held to the
distance for the reference pixel.&lt;/p&gt;
&lt;p&gt;Per the spec, we know that at 28 inches, the ideal is 96 pixels per
inch. However, since it&#39;s a smart phone, people hold the device closer
to their faces than they hold a laptop. Let&#39;s estimate that distance to
be 18 inches.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Multiply the distance ratio against the standard density (96ppi) to
get the ideal pixel density for the given distance.&lt;/p&gt;
&lt;p&gt;idealPixelDensity = (28/18) * 96 = 150 pixels per inch (approximately)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Take the ratio of the physical pixel density to the ideal pixel
density to get the device pixel ratio.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;devicePixelRatio&lt;/code&gt; = 180/150 = 1.2&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;
&lt;img alt=&quot;How devicePixelRatio is calculated.&quot; decoding=&quot;async&quot; height=&quot;282&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 465px) 465px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6UE7CKkGhk9TPzNicgRr.png?auto=format&amp;w=930 930w&quot; width=&quot;465&quot; /&gt;
&lt;figcaption&gt;A diagram showing one reference angular pixel, to help
illustrate how devicePixelRatio is calculated.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;So now when a browser needs to know how to resize an image to fit the
screen according to the ideal or standard resolution, the browser refers
to the device pixel ratio of 1.2 - which says, for every ideal pixel,
this device has 1.2 physical pixels. The formula to go between ideal
(as defined by the web spec) and physical (dots on device screen) pixels
is the following:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;physicalPixels &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;devicePixelRatio &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; idealPixels&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Historically, device vendors have tended to round &lt;code&gt;devicePixelRatios&lt;/code&gt;
(DPRs). Apple&#39;s iPhone and iPad report DPR of 1, and their Retina
equivalents report 2. The &lt;a href=&quot;http://www.w3.org/TR/CSS21/syndata.html#length-units&quot; rel=&quot;noopener&quot;&gt;CSS specification&lt;/a&gt; recommends that&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;the pixel unit refer to the whole number of device pixels that best
approximates the reference pixel.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One reason why round ratios can be better is because they may lead to
fewer &lt;a href=&quot;http://ejohn.org/blog/sub-pixel-problems-in-css/&quot; rel=&quot;noopener&quot;&gt;sub-pixel artifacts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, the reality of the device landscape is much more varied, and
Android phones often have DPRs of 1.5. The Nexus 7 tablet has a DPR of
~1.33, which was arrived at by a calculation similar to the one above.
Expect to see more devices with variable DPRs in the future. Because of
this, you should never assume that your clients will have integer DPRs.&lt;/p&gt;
&lt;h2 id=&quot;overview-of-hidpi-image-techniques&quot;&gt;Overview of HiDPI image techniques &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#overview-of-hidpi-image-techniques&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are many techniques for solving the problem of showing the best
quality images as fast as possible, broadly falling into two categories:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Optimizing single images, and&lt;/li&gt;
&lt;li&gt;Optimizing selection between multiple images.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Single image approaches: use one image, but do something clever with it.
These approaches have the drawback that you will inevitably sacrifice
performance, since you will be downloading HiDPI images even on older
devices with lower DPI. Here are some approaches for the single image
case:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Heavily compressed HiDPI image&lt;/li&gt;
&lt;li&gt;Totally awesome image format&lt;/li&gt;
&lt;li&gt;Progressive image format&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Multiple image approaches: use multiple images, but do something clever
to pick which to load. These approaches have inherent overhead for the
developer to create multiple versions of the same asset and then figure
out a decision strategy. Here are the options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;Server side delivery&lt;/li&gt;
&lt;li&gt;CSS media queries&lt;/li&gt;
&lt;li&gt;Built-in browser features (&lt;code&gt;image-set()&lt;/code&gt;, &lt;code&gt;&amp;lt;img srcset&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;heavily-compressed-hidpi-image&quot;&gt;Heavily compressed HiDPI image &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#heavily-compressed-hidpi-image&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Images already &lt;a href=&quot;http://httparchive.org/interesting.php#bytesperpage&quot; rel=&quot;noopener&quot;&gt;comprise a whopping 60% of bandwidth&lt;/a&gt; spent downloading an
average website. By serving HiDPI images to all clients, we will
increase this number. How much bigger will it grow?&lt;/p&gt;
&lt;p&gt;I ran some tests which generated 1x and 2x image fragments with JPEG
quality at 90, 50 and 20. Here is &lt;a href=&quot;https://www.html5rocks.com/static/demos/high-dpi/process_images.sh&quot; rel=&quot;noopener&quot;&gt;shell script&lt;/a&gt; I used
(employing &lt;a href=&quot;http://www.imagemagick.org/script/index.php&quot; rel=&quot;noopener&quot;&gt;ImageMagick&lt;/a&gt;) to generate them:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Tiles example 1.&quot; decoding=&quot;async&quot; height=&quot;453&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/vJ1hpZkLBBYVxUISlnAm.jpg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;img alt=&quot;Tiles example 2.&quot; decoding=&quot;async&quot; height=&quot;453&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/BrgoLVJZkKZe9EfRShim.jpg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;img alt=&quot;Tiles example 3.&quot; decoding=&quot;async&quot; height=&quot;453&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/p8Te0ieLtme11wN9xt6s.jpg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;figcaption&gt;Samples of images at different compressions and pixel
densities.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;From this small, unscientific sampling, it seems that compressing large
images provides a good quality-to-size tradeoff. For my eye, heavily
compressed 2x imagery actually looks better than uncompressed 1x
pictures.&lt;/p&gt;
&lt;p&gt;Of course, serving low quality, highly compressed 2x imagery to 2x
devices is worse than serving higher quality ones, and the above
approach incurs image quality penalties. If you compare quality: 90
images to quality: 20 images, you will see a drop in crispness and
increased graininess. These artifacts may not be acceptable in cases
where high quality images are key (for example, a photo viewer
application), or for app developers that are not willing to compromise.&lt;/p&gt;
&lt;p&gt;The above comparison was made entirely with compressed JPEGs. It&#39;s worth
noting that there are &lt;a href=&quot;http://www.labnol.org/software/tutorials/jpeg-vs-png-image-quality-or-bandwidth/5385/&quot; rel=&quot;noopener&quot;&gt;many tradeoffs&lt;/a&gt; between the widely
implemented image formats (JPEG, PNG, GIF), which brings us to…&lt;/p&gt;
&lt;h3 id=&quot;totally-awesome-image-format&quot;&gt;Totally awesome image format &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#totally-awesome-image-format&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;WebP is a pretty &lt;a href=&quot;https://developers.google.com/speed/webp/docs/webp_lossless_alpha_study&quot; rel=&quot;noopener&quot;&gt;compelling image format&lt;/a&gt; that compresses
very well while keeping high image fidelity. Of course, it&#39;s &lt;a href=&quot;http://caniuse.com/#search=webp&quot; rel=&quot;noopener&quot;&gt;not
implemented everywhere&lt;/a&gt; yet!&lt;/p&gt;
&lt;p&gt;One way is to check for WebP support is via JavaScript. You load a 1px
image via data-uri, wait for either loaded or error events fired, and
then verify that the size is correct. &lt;a href=&quot;http://modernizr.github.com/Modernizr/test/&quot; rel=&quot;noopener&quot;&gt;Modernizr&lt;/a&gt; ships with
such a &lt;a href=&quot;https://github.com/Modernizr/Modernizr/commit/d1fa2a62d7912d5f253ac1ab9ae2ce9430c3ef92&quot; rel=&quot;noopener&quot;&gt;feature detection script&lt;/a&gt;, which is available
via &lt;code&gt;Modernizr.webp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A better way of doing this, however, is directly
in CSS using the &lt;a href=&quot;http://www.w3.org/TR/css3-images/#image-notation&quot; rel=&quot;noopener&quot;&gt;image() function&lt;/a&gt;. So if you have a WebP
image and JPEG fallback, you can write the following:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;#pic&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;foo.webp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;foo.jpg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;There are a few problems with this approach. Firstly, &lt;code&gt;image()&lt;/code&gt; is not
at all widely implemented. Secondly, while WebP compression blows JPEG
out of the water, it&#39;s still a relatively incremental improvement –
about 30% smaller based on this &lt;a href=&quot;https://developers.google.com/speed/webp/gallery1&quot; rel=&quot;noopener&quot;&gt;WebP gallery&lt;/a&gt;. Thus, WebP
alone isn&#39;t enough to address the high DPI problem.&lt;/p&gt;
&lt;h3 id=&quot;progressive-image-formats&quot;&gt;Progressive image formats &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#progressive-image-formats&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Progressive image formats like JPEG 2000, Progressive JPEG, Progressive
PNG and GIF have the (somewhat debated) benefit of seeing the image come
into place before it&#39;s fully loaded. They may incur some size overhead,
though there is conflicting evidence about this. &lt;a href=&quot;http://www.codinghorror.com/blog/2005/12/progressive-image-rendering.html&quot; rel=&quot;noopener&quot;&gt;Jeff Atwood
claimed&lt;/a&gt; that progressive mode &amp;quot;adds about 20% to the size of PNG images, and
about 10% to the size of JPEG and GIF images&amp;quot;. However, &lt;a href=&quot;http://www.yuiblog.com/blog/2008/12/05/imageopt-4/&quot; rel=&quot;noopener&quot;&gt;Stoyan Stefanov
claimed&lt;/a&gt; that for large files, progressive mode is more efficient (in
most cases).&lt;/p&gt;
&lt;p&gt;At first glance, progressive images look very promising in the context
of serving the best quality images as fast as possible. The idea is
that the browser can stop downloading and decoding an image once it
knows that additional data won&#39;t increase the image quality (ie. all of
the fidelity improvements are sub-pixel).&lt;/p&gt;
&lt;p&gt;While connections are easy to terminate, they are often expensive to
restart. For a site with many images, the most efficient approach is to
keep a single HTTP connection alive, reusing it for as long as possible.
If the connection is terminated prematurely because one image has been
downloaded enough, the browser then needs to create a new connection,
which can be really &lt;a href=&quot;http://serverfault.com/questions/387627/why-do-mobile-networks-have-high-latencies-how-can-they-be-reduced&quot; rel=&quot;noopener&quot;&gt;slow in low latency&lt;/a&gt; environments.&lt;/p&gt;
&lt;p&gt;One workaround to this is to use the &lt;a href=&quot;http://stackoverflow.com/questions/1434647/using-the-http-range-header-with-a-range-specifier-other-than-bytes&quot; rel=&quot;noopener&quot;&gt;HTTP Range&lt;/a&gt; request, which lets
browsers specify a range of bytes to fetch. A smart browser could make a
HEAD request to get at the header, process it, decide how much of the
image is actually needed, and then fetch. Unfortunately HTTP Range is
poorly supported in web servers, making this approach impractical.&lt;/p&gt;
&lt;p&gt;Finally, an obvious limitation of this approach is that you don&#39;t get to
choose which image to load, only varying fidelities of the same image.
As a result, this doesn&#39;t address the &amp;quot;&lt;a href=&quot;http://blog.cloudfour.com/a-framework-for-discussing-responsive-images-solutions/&quot; rel=&quot;noopener&quot;&gt;art direction&lt;/a&gt;&amp;quot; use case.&lt;/p&gt;
&lt;h3 id=&quot;use-javascript-to-decide-which-image-to-load&quot;&gt;Use JavaScript to decide which image to load &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#use-javascript-to-decide-which-image-to-load&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The first, and most obvious approach to deciding which image to load is
to use JavaScript in the client. This approach lets you find out
everything about your user agent and do the right thing. You can
determine device pixel ratio via &lt;code&gt;window.devicePixelRatio&lt;/code&gt;, get screen
width and height, and even potentially do some network connection
sniffing via navigator.connection or issuing a fake request, like the
&lt;a href=&quot;https://github.com/adamdbradley/foresight.js&quot; rel=&quot;noopener&quot;&gt;foresight.js library&lt;/a&gt; does. Once you&#39;ve collected all of
this information, you can decide which image to load.&lt;/p&gt;
&lt;p&gt;There are approximately &lt;a href=&quot;https://docs.google.com/a/google.com/spreadsheet/ccc?key=0Al0lI17fOl9DdDgxTFVoRzFpV3VCdHk2NTBmdVI2OXc#gid=0&quot; rel=&quot;noopener&quot;&gt;one million JavaScript libraries&lt;/a&gt; that
do something like the above, and unfortunately none of them are
particularly outstanding.&lt;/p&gt;
&lt;p&gt;One big drawback to this approach is that using JavaScript means that
you will delay image loading until after the look-ahead parser has
finished. This essentially means that images won&#39;t even start
downloading until after the &lt;code&gt;pageload&lt;/code&gt; event fires.  More on this in
&lt;a href=&quot;http://blog.cloudfour.com/the-real-conflict-behind-picture-and-srcset/&quot; rel=&quot;noopener&quot;&gt;Jason Grigsby&#39;s article&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;decide-what-image-to-load-on-the-server&quot;&gt;Decide what image to load on the server &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#decide-what-image-to-load-on-the-server&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can defer the decision to the server-side by writing custom request
handlers for each image you serve. Such a handler would check for Retina
support based on User-Agent (the only piece of information relayed to
the server). Then, based on whether the server-side logic wants to serve
HiDPI assets, you load the appropriate asset (named according to some
known convention).&lt;/p&gt;
&lt;p&gt;Unfortunately, the User-Agent doesn&#39;t necessarily provide enough
information to decide whether a device should receive high or low
quality images. Also, it goes without saying that anything related to
User-Agent is a hack and should be avoided if possible.&lt;/p&gt;
&lt;h3 id=&quot;use-css-media-queries&quot;&gt;Use CSS media queries &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#use-css-media-queries&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Being declarative, CSS media queries let you state your intention, and
let the browser do the right thing on your behalf. In addition to the most
common use of media queries — matching device size — you can
also match &lt;code&gt;devicePixelRatio&lt;/code&gt;. The associated media query is
device-pixel-ratio, and has associated min and max variants, as you
might expect. If you want to load high DPI images and the device pixel
ratio exceeds a threshold, here&#39;s what you might do:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;#my-image&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;low.png&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@media&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;only&lt;/span&gt; screen &lt;span class=&quot;token keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;min-device-pixel-ratio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.5&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;#my-image&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;high.png&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;It gets a little more complicated with all of the vendor prefixes mixed
in, especially because of insane &lt;a href=&quot;https://developer.mozilla.org/en/CSS/Media_queries#-moz-device-pixel-ratio&quot; rel=&quot;noopener&quot;&gt;differences in placement&lt;/a&gt; of
&amp;quot;min&amp;quot; and &amp;quot;max&amp;quot; prefixes:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@media&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;only&lt;/span&gt; screen &lt;span class=&quot;token keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;min--moz-device-pixel-ratio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.5&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;-o-min-device-pixel-ratio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 3/2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;-webkit-min-device-pixel-ratio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.5&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;min-device-pixel-ratio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.5&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;#my-image&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;high.png&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;With this approach, you regain the benefits of look-ahead parsing, which
was lost with the JS solution. You also gain the flexibility of choosing
your responsive breakpoints (for example, you can have low, mid and high
DPI images), which was lost with the server-side approach.&lt;/p&gt;
&lt;p&gt;Unfortunately it&#39;s still a little unwieldy, and leads to strange looking
CSS (or requires preprocessing). Also, this approach is restricted to
CSS properties, so there&#39;s no way to set an &lt;code&gt;&amp;lt;img src&amp;gt;&lt;/code&gt;, and your images
must all be elements with a background. Finally, by relying strictly on
device pixel ratio, you can end up in situations where your High-DPI
smart phone ends up downloading a massive 2x image asset while on an
&lt;a href=&quot;http://en.wikipedia.org/wiki/Enhanced_Data_Rates_for_GSM_Evolution&quot; rel=&quot;noopener&quot;&gt;EDGE connection&lt;/a&gt;. This isn&#39;t the best user experience.&lt;/p&gt;
&lt;h3 id=&quot;use-new-browser-features&quot;&gt;Use new browser features &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#use-new-browser-features&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There&#39;s been a lot of recent discussion around web platform support for
the high DPI image problem. Apple recently broke into the space,
bringing the &lt;a href=&quot;http://dev.w3.org/csswg/css4-images/#image-set-notation&quot; rel=&quot;noopener&quot;&gt;image-set()&lt;/a&gt; CSS function to WebKit. As a result, both
Safari and Chrome support it. Since it&#39;s a CSS function, &lt;code&gt;image-set()&lt;/code&gt;
doesn&#39;t address the problem for &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags. Enter
&lt;a href=&quot;http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content-1.html#attr-img-srcset&quot; rel=&quot;noopener&quot;&gt;@srcset&lt;/a&gt;, which addresses this issue but (at the time of
this writing) has no reference implementations (yet!). The next section
goes deeper into &lt;code&gt;image-set&lt;/code&gt; and &lt;code&gt;srcset&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;browser-features-for-high-dpi-support&quot;&gt;Browser features for high DPI support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#browser-features-for-high-dpi-support&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ultimately, the decision about which approach you take depends on your
particular requirements. That said, keep in mind that all of the
aforementioned approaches have drawbacks. Looking forward, however, once
&lt;code&gt;image-set&lt;/code&gt; and srcset are widely supported, they will be the
appropriate solutions to this problem. For the time being, let&#39;s talk
about some best practices that can bring us as close to that ideal
future as possible.&lt;/p&gt;
&lt;p&gt;Firstly, how are these two different? Well, &lt;code&gt;image-set()&lt;/code&gt; is a CSS
function, appropriate for use as a value of the background CSS property.
srcset is an attribute specific to &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements, with similar syntax.
Both of these tags let you specify image declarations, but the srcset
attribute lets you also configure which image to load based on viewport
size.&lt;/p&gt;
&lt;h3 id=&quot;best-practices-for-image-set&quot;&gt;Best practices for image-set &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#best-practices-for-image-set&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;image-set()&lt;/code&gt; CSS function is available prefixed as
&lt;code&gt;-webkit-image-set()&lt;/code&gt;. The syntax is quite simple, taking a one or more
comma separated image declarations, which consist of a URL string or
&lt;code&gt;url()&lt;/code&gt; function followed by the associated resolution. For example:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;token function&quot;&gt;-webkit-image-set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;icon1x.jpg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; 1x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;icon2x.jpg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; 2x&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;What this tells the browser is that there are two images to choose from.
One of them is optimized for 1x displays, and the other for 2x displays.
The browser then gets to choose which one to load, based on a variety of
factors, which might even include network speed, if the browser is smart
enough (not currently implemented as far as I know).&lt;/p&gt;
&lt;p&gt;In addition to loading the correct image, the browser will also scale it
accordingly. In other words, the browser assumes that 2 images are
twice as large as 1x images, and so will scale the 2x image down by a
factor of 2, so that the image appears to be the same size on the page.&lt;/p&gt;
&lt;p&gt;Instead of specifying 1x, 1.5x or Nx, you can also specify a certain
device pixel density in dpi.&lt;/p&gt;
&lt;p&gt;This works well, except in browsers that don&#39;t support the &lt;code&gt;image-set&lt;/code&gt;
property, which will show no image at all! This is clearly bad, so you
&lt;strong&gt;must&lt;/strong&gt; use a fallback (or series of fallbacks) to address that issue:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;icon1x.jpg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;-webkit-image-set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;icon1x.jpg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; 1x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;icon2x.jpg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; 2x&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* This will be useful if image-set gets into the platform, unprefixed.&lt;br /&gt;    Also include other prefixed versions of this */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token property&quot;&gt;background-image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;image-set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;icon1x.jpg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; 1x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;icon2x.jpg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; 2x&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The above will load the appropriate asset in browsers that support
image-set, and fall back to the 1x asset otherwise. The obvious caveat
is that while &lt;code&gt;image-set()&lt;/code&gt; browser support is low, most user agents will
get the 1x asset.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.html5rocks.com/static/demos/high-dpi/image-set/index.html&quot; rel=&quot;noopener&quot;&gt;This demo&lt;/a&gt; uses the &lt;code&gt;image-set()&lt;/code&gt; to load the correct
image, falling back to the 1x asset if this CSS function isn&#39;t
supported.&lt;/p&gt;
&lt;p&gt;At this point, you may be wondering why not just polyfill (that is,
build a JavaScript shim for) &lt;code&gt;image-set()&lt;/code&gt; and call it a day? As it
turns out, it&#39;s quite difficult to implement efficient polyfills for CSS
functions. (For a detailed explanation why, see this &lt;a href=&quot;http://lists.w3.org/Archives/Public/www-style/2012Jul/0023.html&quot; rel=&quot;noopener&quot;&gt;www-style
discussion&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&quot;image-srcset&quot;&gt;Image srcset &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#image-srcset&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here is an example of srcset:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;my awesome image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;banner.jpeg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;banner-HD.jpeg 2x, banner-phone.jpeg 640w, banner-phone-HD.jpeg 640w 2x&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As you can see, in addition to x declarations that &lt;code&gt;image-set&lt;/code&gt; provides,
the srcset element also takes w and h values which correspond to the
size of the viewport, attempting to serve the most relevant version. The
above would serve banner-phone.jpeg to devices with viewport width under
640px, banner-phone-HD.jpeg to small screen high DPI devices,
banner-HD.jpeg to high DPI devices with screens greater than 640px, and
banner.jpeg to everything else.&lt;/p&gt;
&lt;h3 id=&quot;using-image-set-for-image-elements&quot;&gt;Using image-set for image elements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#using-image-set-for-image-elements&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Because the srcset attribute on img elements is not implemented in most
browsers, it may be tempting to replace your img elements with &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s
with backgrounds and use the image-set approach. This will work, with
caveats. The drawback here is that the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag has long-time
semantic value. In practice, this is important mostly for web crawlers
and accessibility reasons.&lt;/p&gt;
&lt;p&gt;If you end up using &lt;code&gt;-webkit-image-set&lt;/code&gt;, you might be tempted to use the
background CSS property. The drawback of this approach is that you need
to specify image size, which is unknown if you are using a non-1x image.
Rather than doing this, you can use the content CSS property as follows:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;my-content-image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;-webkit-image-set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;icon1x.jpg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; 1x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;icon2x.jpg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; 2x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This will automatically scale the image based on devicePixelRatio. See
&lt;a href=&quot;https://www.html5rocks.com/static/demos/high-dpi/image-set/as-content.html&quot; rel=&quot;noopener&quot;&gt;this example&lt;/a&gt; of the above technique in action,
with an additional fallback to &lt;code&gt;url()&lt;/code&gt; for browsers that don&#39;t support
&lt;code&gt;image-set&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;polyfilling-srcset&quot;&gt;Polyfilling srcset &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#polyfilling-srcset&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One handy feature of &lt;code&gt;srcset&lt;/code&gt; is that it comes with a natural fallback.
In the case where the srcset attribute is not implemented, all browsers
know to process the src attribute. Also, since it&#39;s just an HTML
attribute, it&#39;s possible to create &lt;a href=&quot;https://github.com/borismus/srcset-polyfill&quot; rel=&quot;noopener&quot;&gt;polyfills with
JavaScript&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This polyfill comes with &lt;a href=&quot;https://github.com/borismus/srcset-polyfill/blob/master/tests/srcset-tests.js&quot; rel=&quot;noopener&quot;&gt;unit tests&lt;/a&gt; to ensure that it&#39;s
as close to the &lt;a href=&quot;http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content-1.html#attr-img-srcset&quot; rel=&quot;noopener&quot;&gt;specification&lt;/a&gt; as possible. In addition, there
are checks in place that prevent the polyfill from executing any code if
srcset is implemented natively.&lt;/p&gt;
&lt;p&gt;Here is a &lt;a href=&quot;https://www.html5rocks.com/static/demos/high-dpi/srcset-polyfill/demo/index.html&quot; rel=&quot;noopener&quot;&gt;demo of the polyfill&lt;/a&gt; in action.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/high-dpi/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There is no magic bullet for solving the problem of high DPI images.&lt;/p&gt;
&lt;p&gt;The easiest solution is to avoid images entirely, opting for SVG and CSS
instead. However, this isn&#39;t always realistic, especially if you have
high quality imagery on your site.&lt;/p&gt;
&lt;p&gt;Approaches in JS, CSS and using the server-side all have their strengths
and weaknesses. The most promising approach, however, is to leverage new
browser features. Though browser support for &lt;code&gt;image-set&lt;/code&gt; and &lt;code&gt;srcset&lt;/code&gt; is
still incomplete, there are reasonable fallbacks to use today.&lt;/p&gt;
&lt;p&gt;To summarize, my recommendations are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For background images, use &lt;a href=&quot;https://web.dev/high-dpi/#toc-image-set&quot;&gt;image-set&lt;/a&gt; with the
appropriate fallbacks for browsers that don&#39;t support it.&lt;/li&gt;
&lt;li&gt;For content images, use a &lt;a href=&quot;https://web.dev/high-dpi/#toc-polyfill&quot;&gt;srcset polyfill&lt;/a&gt;, or
fallback to &lt;a href=&quot;https://web.dev/high-dpi/#toc-image-set-srcset&quot;&gt;using image-set&lt;/a&gt; (see above).&lt;/li&gt;
&lt;li&gt;For situations where you&#39;re willing to sacrifice image quality,
consider using heavily &lt;a href=&quot;https://web.dev/high-dpi/#toc-compress&quot;&gt;compressed 2x images&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Boris Smus</name>
    </author>
  </entry>
  
  <entry>
    <title>A non-responsive approach to building cross-device webapps</title>
    <link href="https://web.dev/mobile-cross-device/"/>
    <updated>2012-04-28T00:00:00Z</updated>
    <id>https://web.dev/mobile-cross-device/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;media-queries-are-great,-but&quot;&gt;Media queries are great, but… &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-cross-device/#media-queries-are-great,-but&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Media queries are awesome, a godsend for website developers that want to
make small tweaks to their stylesheets to give a better experience for
users on devices of various sizes. Media queries essentially let you
customize the CSS of your site depending on screen size. Before you dive
into this article, learn more about &lt;a href=&quot;https://www.html5rocks.com/mobile/responsivedesign&quot; rel=&quot;noopener&quot;&gt;responsive design&lt;/a&gt; and check
out some fine examples of media queries usage here: &lt;a href=&quot;http://mediaqueri.es/&quot; rel=&quot;noopener&quot;&gt;mediaqueri.es&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As Brad Frost points out in an &lt;a href=&quot;http://bradfrostweb.com/blog/web/responsive-web-design-missing-the-point/&quot; rel=&quot;noopener&quot;&gt;earlier article&lt;/a&gt;, changing the look
is only one of many things to consider when building for the mobile web.
If the only thing you do when you build your mobile website is customize
your layout with media queries, then we have the following situation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All devices get the same JavaScript, CSS, and assets (images, videos),
resulting in longer than necessary load times.&lt;/li&gt;
&lt;li&gt;All devices get the same initial DOM, potentially forcing developers
to write overly complicated CSS.&lt;/li&gt;
&lt;li&gt;Little flexibility to specify custom interactions tailored to each
device.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;webapps-need-more-than-media-queries&quot;&gt;Webapps need more than media queries &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-cross-device/#webapps-need-more-than-media-queries&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Don&#39;t get me wrong. I don&#39;t hate responsive design via media queries,
and definitely think it has a place in the world. Furthermore, some of
the above mentioned issues can be resolved with approaches such as
&lt;a href=&quot;http://www.alistapart.com/articles/responsive-images-how-they-almost-worked-and-what-we-need/&quot; rel=&quot;noopener&quot;&gt;responsive images&lt;/a&gt;, dynamic script loading, etc. However, at a
certain point, you may find yourself doing too many incremental tweaks,
and may be better off serving different versions.&lt;/p&gt;
&lt;p&gt;As the UIs you build increase in complexity, and you gravitate toward
single-page webapps, you&#39;ll want to do more to customize UIs for each
type of device. This article will teach you how to do these
customizations with a minimal amount of effort. The general approach
involves classifying your visitor&#39;s device into the right device
class, and serving the appropriate version to that device, while
maximizing code reuse between versions.&lt;/p&gt;
&lt;h2 id=&quot;what-device-classes-are-you-targeting&quot;&gt;What device classes are you targeting? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-cross-device/#what-device-classes-are-you-targeting&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are tons of internet-connected devices out there, and nearly all
of them have browsers. The complication lies in their diversity: Mac
laptops, Windows workstations, iPhones, iPads, Android phones with touch
input, scroll wheels, keyboards, voice input, devices with pressure
sensitivity, smart watches, toasters and refrigerators, and many more.
Some of these devices are ubiquitous, while others are very rare.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;A variety of devices.&quot; decoding=&quot;async&quot; height=&quot;346&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 590px) 590px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/oWgUWNao9paWJQxjv3mT.png?auto=format&amp;w=1180 1180w&quot; width=&quot;590&quot; /&gt;
&lt;figcaption&gt;A variety of devices (&lt;a href=&quot;http://www.flickr.com/photos/brad_frost/6164723945/in/set-72157627712478230/&quot;&gt;source&lt;/a&gt;).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To create a good user experience, you need to know who your users are
and what devices they are using. If you build a user interface for a
desktop user with a mouse and a keyboard and give it to a smartphone
user, your interface will be a frustration because it&#39;s designed for
another screen size, and another input modality.&lt;/p&gt;
&lt;p&gt;There are two extreme ends to the spectrum of approaches:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Build one version that works on all devices. UX will suffer as a
result, since different devices have different design considerations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build a version for each device you want to support. This will take
forever, because you&#39;ll be building too many versions of your
application. Also, when the next new smartphone arrives
(which happens roughly weekly), you will be forced to create yet
another version.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There is a fundamental tradeoff here: the more device categories you
have, the better a user experience you can deliver, but the more work it
will take to design, implement and maintain.&lt;/p&gt;
&lt;p&gt;Creating a separate version for each device class you decide on may be a
good idea for performance reasons or if the versions you want to serve
to different device classes vary hugely. Otherwise, &lt;a href=&quot;https://www.html5rocks.com/mobile/responsivedesign&quot; rel=&quot;noopener&quot;&gt;responsive web
design&lt;/a&gt; is a perfectly reasonable approach.&lt;/p&gt;
&lt;h3 id=&quot;a-potential-solution&quot;&gt;A potential solution &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-cross-device/#a-potential-solution&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here&#39;s a compromise: classify devices into categories, and design the
best possible experience for each category. What categories you choose
depend on your product and target user. Here&#39;s a sample classification
that nicely spans popular web-capable devices that exist today.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;small screens + touch (mostly phones)&lt;/li&gt;
&lt;li&gt;large screens + touch (mostly tablets)&lt;/li&gt;
&lt;li&gt;large screens + keyboard/mouse (mostly desktops/laptops)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is only one of many possible breakdowns, but one that makes a lot
of sense at the time of writing. Missing from the above list are mobile
devices without touch screens (eg. feature phones, some dedicated ebook
readers). However, most of these have keyboard navigation or screen
reader software installed, which will work fine if you build your site
with accessibility in mind.&lt;/p&gt;
&lt;h3 id=&quot;examples-of-form-factor-specific-web-apps&quot;&gt;Examples of form factor-specific web apps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-cross-device/#examples-of-form-factor-specific-web-apps&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are many examples of web properties serving entirely different
versions for different form factors. Google search does this, as does
Facebook. Considerations for this include both performance (fetching
assets, rendering pages) and more general user experience.&lt;/p&gt;
&lt;p&gt;In the world of native apps, many developers choose to tailor their
experience to a device class. For example, &lt;a href=&quot;http://flipboard.com/&quot; rel=&quot;noopener&quot;&gt;Flipboard&lt;/a&gt; for
iPad has a very different UI compared to Flipboard on iPhone. The tablet
version is optimized for two hand use and horizontal flipping while the
phone version is intended for single hand interaction and a vertical
flip. Many other iOS applications also provide significantly different
phone and tablet versions, such as &lt;a href=&quot;http://culturedcode.com/things/&quot; rel=&quot;noopener&quot;&gt;Things&lt;/a&gt; (todo list), and
&lt;a href=&quot;http://showyou.com/&quot; rel=&quot;noopener&quot;&gt;Showyou&lt;/a&gt; (social video), featured below:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Significant UI customization for phone and tablet.&quot; decoding=&quot;async&quot; height=&quot;488&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 683px) 683px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/yTMaKFumzZXSB3qcrAgp.png?auto=format&amp;w=1366 1366w&quot; width=&quot;683&quot; /&gt;
&lt;figcaption&gt;Significant UI customization for phone and tablet.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;approach-#1-server-side-detection&quot;&gt;Approach #1: Server-side detection &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-cross-device/#approach-#1-server-side-detection&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On the server, we have a much more limited understanding of the device
that we&#39;re dealing with. Probably the most useful clue that&#39;s available
is the user agent string, which is supplied via the User-Agent header on
every request. Because of this, the same UA sniffing approach will work
here. In fact, the DeviceAtlas and WURFL projects do this already (and
give a whole lot of additional information about the device).&lt;/p&gt;
&lt;p&gt;Unfortunately each of these present their own challenges. WURFL is very
large, containing 20MB of XML, potentially incurring significant
server-side overhead for each request. There are projects that split the
XML for performance reasons. DeviceAtlas is not open source, and
requires a paid license to use.&lt;/p&gt;
&lt;p&gt;There are simpler, free alternatives too, like the &lt;a href=&quot;http://detectmobilebrowsers.com/&quot; rel=&quot;noopener&quot;&gt;Detect Mobile
Browsers&lt;/a&gt; project. The drawback, of course, is that device
detection will inevitably be less comprehensive. Also, it only
distinguishes between mobile and non-mobile devices, providing limited
tablet support only through an &lt;a href=&quot;http://detectmobilebrowsers.com/about&quot; rel=&quot;noopener&quot;&gt;ad-hoc set of tweaks&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;approach-#2-client-side-detection&quot;&gt;Approach #2: Client-side detection &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-cross-device/#approach-#2-client-side-detection&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We can learn a lot about the user&#39;s browser and device by using feature
detection. The main things we need to determine are if the device has
touch capability, and if it&#39;s a large or small screen.&lt;/p&gt;
&lt;p&gt;We need to draw the line somewhere to distinguish small and big touch
devices. What about edge cases like the 5&amp;quot; Galaxy Note? The following
graphic shows a bunch of popular Android and iOS devices overlaid (with
corresponding screen resolutions). The asterisk indicates that the
device comes or can come in doubled density. Though the pixel density
may be doubled, CSS still reports the same sizes.&lt;/p&gt;
&lt;p&gt;A quick aside on pixels in CSS: CSS pixels on the mobile web &lt;a href=&quot;http://www.quirksmode.org/blog/archives/2010/04/a_pixel_is_not.html&quot; rel=&quot;noopener&quot;&gt;aren&#39;t the
same&lt;/a&gt; as screen pixels. iOS retina devices introduced the
practice of doubling pixel density (eg. iPhone 3GS vs 4, iPad 2 vs 3).
The retina Mobile Safari UAs still report the same device-width to avoid
breaking the web. As other devices (eg. Android) get higher resolution
displays, they are doing the same device-width trick.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Device resolution (in pixels).&quot; decoding=&quot;async&quot; height=&quot;528&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 336px) 336px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/KmYlWUztM5Lm23vm0RFq.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/KmYlWUztM5Lm23vm0RFq.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/KmYlWUztM5Lm23vm0RFq.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/KmYlWUztM5Lm23vm0RFq.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/KmYlWUztM5Lm23vm0RFq.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/KmYlWUztM5Lm23vm0RFq.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/KmYlWUztM5Lm23vm0RFq.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/KmYlWUztM5Lm23vm0RFq.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/KmYlWUztM5Lm23vm0RFq.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/KmYlWUztM5Lm23vm0RFq.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/KmYlWUztM5Lm23vm0RFq.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/KmYlWUztM5Lm23vm0RFq.png?auto=format&amp;w=672 672w&quot; width=&quot;336&quot; /&gt;
&lt;figcaption&gt;Device resolution (in pixels).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Complicating this decision, however, is the importance of considering
both portrait and landscape modes. We don&#39;t want to reload the page or
load additional scripts every time we re-orient the device, though we
may want to render the page differently.&lt;/p&gt;
&lt;p&gt;In the following diagram, squares represent the max dimensions of each
device, as a result of overlaying the portrait and landscape outlines
(and completing the square):&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Portrait + landscape resolution (in pixels)&quot; decoding=&quot;async&quot; height=&quot;590&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 651px) 651px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ew9tsr3K7PH4SnlY32uc.png?auto=format&amp;w=1302 1302w&quot; width=&quot;651&quot; /&gt;
&lt;figcaption&gt;Portrait + landscape resolution (in pixels)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;By setting the threshold to &lt;code&gt;650px&lt;/code&gt;, we classify iPhone, Galaxy Nexus as
smalltouch, and iPad, Galaxy Tab as &amp;quot;tablet&amp;quot;. The androgynous Galaxy
Note is in this case classified as &amp;quot;phone&amp;quot;, and will get the phone
layout.&lt;/p&gt;
&lt;p&gt;And so, a reasonable strategy might look like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hasTouch&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isSmall&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    device &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PHONE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    device &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TABLET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  device &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DESKTOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;See a minimal sample of the &lt;a href=&quot;https://www.html5rocks.com/static/demos/cross-device/feature/index.html&quot; rel=&quot;noopener&quot;&gt;feature-detection approach&lt;/a&gt; in action.&lt;/p&gt;
&lt;p&gt;The alternative approach here is to use UA sniffing to detect device
type. Basically you create a set of heuristics and match them against
your user&#39;s &lt;code&gt;navigator.userAgent&lt;/code&gt;. Pseudo code looks something like
this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; ua &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userAgent&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; re &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;RULES&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ua&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;re&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    device &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;RULES&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;re&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;See a sample of the &lt;a href=&quot;https://www.html5rocks.com/static/demos/cross-device/ua/index.html&quot; rel=&quot;noopener&quot;&gt;UA-detection approach&lt;/a&gt; in action.&lt;/p&gt;
&lt;h3 id=&quot;a-note-on-client-side-loading&quot;&gt;A note on client-side loading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-cross-device/#a-note-on-client-side-loading&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you&#39;re doing UA detection on your server, you can decide what CSS,
JavaScript and DOM to serve when you get a new request. However, if
you&#39;re doing client-side detection, the situation is more complex. You
have several options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Redirect to a device-type-specific URL that contains the version for
this device type.&lt;/li&gt;
&lt;li&gt;Dynamically load the device type-specific assets.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first approach is straightforward, requiring a redirect such as
&lt;code&gt;window.location.href = &#39;/tablet&#39;&lt;/code&gt;. However, the location will now have
this device type information appended to it, so you may want to use the
&lt;a href=&quot;http://diveintohtml5.info/history.html&quot; rel=&quot;noopener&quot;&gt;History API&lt;/a&gt; to clean up your URL. Unfortunately this
approach involves a redirect, which can be slow, especially on mobile
devices.&lt;/p&gt;
&lt;p&gt;The second approach is quite a bit more complex to implement. You need a
mechanism to dynamically load CSS and JS, and (browser-depending), you
may not be able to do things like customize &lt;code&gt;&amp;lt;meta viewport&amp;gt;&lt;/code&gt;. Also,
since there&#39;s no redirect, you&#39;re stuck with the original HTML that was
served. Of course, you can manipulate it with JavaScript, but this may
be slow and/or inelegant, depending on your application.&lt;/p&gt;
&lt;h2 id=&quot;deciding-client-or-server&quot;&gt;Deciding client or server &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-cross-device/#deciding-client-or-server&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These are the tradeoffs between the approaches:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro client&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;More future proof since based on screen sizes/capabilities rather than UA.&lt;/li&gt;
&lt;li&gt;No need to constantly update UA list.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Pro server&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full control of what version to serve to what devices.&lt;/li&gt;
&lt;li&gt;Better performance: no need for client redirects or dynamic loading.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My personal preference is to start with device.js and client-side
detection. As your application evolves, if you find client-side redirect
to be a significant performance drawback, you can easily remove the
device.js script, and implement UA detection on the server.&lt;/p&gt;
&lt;h2 id=&quot;introducing-devicejs&quot;&gt;Introducing device.js &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-cross-device/#introducing-devicejs&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Device.js is a starting point for doing semantic, media query-based
device detection without needing special server-side configuration,
saving the time and effort required to do user agent string parsing.&lt;/p&gt;
&lt;p&gt;The idea is that you provide search-engine-friendly markup (&lt;a href=&quot;http://blog.whatwg.org/the-road-to-html-5-link-relations#rel-alternate&quot; rel=&quot;noopener&quot;&gt;link
rel=alternate&lt;/a&gt;) at the top of your &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; indicating which
versions of your site you want to provide.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;alternate&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://foo.com&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;desktop&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;only screen and (touch-enabled: 0)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Next, you can either do server-side UA detection and handle
version redirection on your own, or use the device.js script to do
feature-based client-side redirection.&lt;/p&gt;
&lt;p&gt;For more information, see the &lt;a href=&quot;https://github.com/borismus/device.js&quot; rel=&quot;noopener&quot;&gt;device.js project page&lt;/a&gt;, and
also a &lt;a href=&quot;http://borismus.github.com/device.js/sample/&quot; rel=&quot;noopener&quot;&gt;fake application&lt;/a&gt; that uses device.js for
client-side redirection.&lt;/p&gt;
&lt;h2 id=&quot;recommendation-mvc-with-form-factor-specific-views&quot;&gt;Recommendation: MVC with form-factor specific views&lt;/h2&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-cross-device/#recommendation-mvc-with-form-factor-specific-views&quot;&gt;#&lt;/a&gt;
&lt;p&gt;By now you&#39;re probably thinking that I&#39;m telling you to build three
completely separate apps, one for each device type. No! Code sharing is
the key.&lt;/p&gt;
&lt;p&gt;Hopefully you have been using an MVC-like framework, such as Backbone,
Ember, etc. If you have been, you are familiar with the principle of
separation of concerns, specifically that your UI (view layer) should be
decoupled from your logic (model layer). If this is new to you, get
started with some of these &lt;a href=&quot;http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller&quot; rel=&quot;noopener&quot;&gt;resources on MVC&lt;/a&gt;, and &lt;a href=&quot;http://addyosmani.github.com/todomvc/&quot; rel=&quot;noopener&quot;&gt;MVC in
JavaScript&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The cross-device story fits neatly into your existing MVC framework. You
can easily move your views into separate files, creating a custom view
for each device type. Then you can serve the same code to all devices,
except the view layer.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Cross-device MVC.&quot; decoding=&quot;async&quot; height=&quot;398&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 545px) 545px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/aXsJmGcCIAs0NBVPpRoP.png?auto=format&amp;w=1090 1090w&quot; width=&quot;545&quot; /&gt;
&lt;figcaption&gt;Cross-device MVC.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Your project might have the following structure (of course, you are free
to choose the structure that makes the most sense depending on your
application):&lt;/p&gt;
&lt;p&gt;models/ (shared models)
item.js
item-collection.js&lt;/p&gt;
&lt;p&gt;controllers/ (shared controllers)
item-controller.js&lt;/p&gt;
&lt;p&gt;versions/ (device-specific stuff)
tablet/
desktop/
phone/ (phone-specific code)
style.css
index.html
views/
item.js
item-list.js&lt;/p&gt;
&lt;p&gt;This sort of structure enables you to fully control what assets each
version loads, since you have custom HTML, CSS and JavaScript for each
device. This is very powerful, and can lead to the leanest, most
performant way of developing for the cross-device web, without relying
on tricks such as adaptive images.&lt;/p&gt;
&lt;p&gt;Once you run your favorite build tool, you&#39;ll concatenate and minify all
of your JavaScript and CSS into single files for faster loading, with
your production HTML looking something like the following (for phone,
using device.js):&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;doctype&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Mobile Web Rocks! (Phone Edition)&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Every version of your webapp should include a list of all&lt;br /&gt;        versions. --&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;alternate&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://foo.com&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;desktop&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;only screen and (touch-enabled: 0)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;alternate&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://m.foo.com&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;phone&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;only screen and (max-device-width: 650px)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;alternate&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://tablet.foo.com&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;tablet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token attr-name&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;only screen and (min-device-width: 650px)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Viewport is very important, since it affects results of media&lt;br /&gt;        query matching. --&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;viewport&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;width=device-width&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Include device.js in each version for redirection. --&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;device.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;phone.min.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;phone.min.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Note that the &lt;code&gt;(touch-enabled: 0)&lt;/code&gt; media query is non-standard (only
implemented in Firefox behind a &lt;code&gt;moz&lt;/code&gt; vendor prefix), but is handled
correctly (thanks to &lt;a href=&quot;http://modernizr.com/&quot; rel=&quot;noopener&quot;&gt;Modernizr.touch&lt;/a&gt;) by device.js.&lt;/p&gt;
&lt;h3 id=&quot;version-override&quot;&gt;Version override &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-cross-device/#version-override&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Device detection can sometimes go wrong, and in some cases, a user may
prefer to look at the tablet layout on their phone (perhaps they are
using a Galaxy Note), so it&#39;s important to give your users a choice of
which version of your site to use if they want to manually override.&lt;/p&gt;
&lt;p&gt;The usual approach is to provide a link to the desktop version from your
mobile version. This is easy enough to implement, but device.js supports
this functionality with the &lt;code&gt;device&lt;/code&gt; GET parameter.&lt;/p&gt;
&lt;h2 id=&quot;concluding&quot;&gt;Concluding &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-cross-device/#concluding&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To summarize, when building cross-device single-page UIs, that don&#39;t fit
neatly into the world of responsive design, do this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Pick a set of device classes to support, and criteria by which to
classify devices into classes.&lt;/li&gt;
&lt;li&gt;Build your MVC app with strong separation of concerns, splitting
views from the rest of the codebase.&lt;/li&gt;
&lt;li&gt;Use &lt;a href=&quot;https://github.com/borismus/device.js&quot; rel=&quot;noopener&quot;&gt;device.js&lt;/a&gt; to do client side device class detection.&lt;/li&gt;
&lt;li&gt;When you&#39;re ready, package your script and stylesheets into one of
each per device class.&lt;/li&gt;
&lt;li&gt;If client-side redirection performance is an issue, abandon device.js
and switch to serverside UA-detection.&lt;/li&gt;
&lt;/ol&gt;
</content>
    <author>
      <name>Boris Smus</name>
    </author>
  </entry>
  
  <entry>
    <title>Developing game audio with the Web Audio API</title>
    <link href="https://web.dev/webaudio-games/"/>
    <updated>2012-02-28T00:00:00Z</updated>
    <id>https://web.dev/webaudio-games/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;introduction&quot;&gt;Introduction &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-games/#introduction&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Audio is a huge part of what makes multimedia experiences so
compelling. If you&#39;ve ever tried watching a movie with the sound off,
you&#39;ve probably noticed this.&lt;/p&gt;
&lt;p&gt;Games are no exception! My fondest video game memories are of the music
and sound effects. Now, in many cases nearly two decades after playing
my favorites, I still can&#39;t get &lt;a href=&quot;http://en.wikipedia.org/wiki/Koji_Kondo&quot; rel=&quot;noopener&quot;&gt;Koji Kondo&lt;/a&gt;&#39;s Zelda
&lt;a href=&quot;http://www.youtube.com/watch?v=4qJ-xEZhGms&quot; rel=&quot;noopener&quot;&gt;compositions&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/Matt_Uelmen&quot; rel=&quot;noopener&quot;&gt;Matt Uelmen&lt;/a&gt;&#39;s atmospheric &lt;a href=&quot;http://www.youtube.com/watch?v=Q2evIg-aYw8&quot; rel=&quot;noopener&quot;&gt;Diablo
soundtrack&lt;/a&gt; out of my head. The same catchiness applies to sound
effects, such as the instantly recognizable unit click responses from
Warcraft, and samples from Nintendo&#39;s classics.&lt;/p&gt;
&lt;p&gt;Game audio presents some interesting challenges. To create convincing
game music, designers need to adjust to  potentially unpredictable game
state a player finds themselves in. In practice, parts of the game can
go on for an unknown duration, sounds can interact with the environment
and mix in complex ways, such as room effects and relative sound
positioning. Finally, there can be a large number of sounds playing at
once, all of which need to sound good together and render without
introducing performance penalties.&lt;/p&gt;
&lt;h2 id=&quot;game-audio-on-the-web&quot;&gt;Game Audio on the Web &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-games/#game-audio-on-the-web&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For simple games, using the &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; tag may be sufficient. However, many
browsers provide poor implementations, which result in audio glitches
and high latency. This is hopefully a temporary problem, since vendors
are working hard to improve their respective implementations. For a
glimpse into the state of the &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; tag, there is a nice test suite
at &lt;a href=&quot;http://areweplayingyet.org/&quot; rel=&quot;noopener&quot;&gt;areweplayingyet.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Looking deeper into the &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; tag specification, however, it becomes
clear that there are many things that simply can&#39;t be done with it,
which isn&#39;t surprising, since it was designed for media playback. Some
limitations include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No ability to apply filters to the sound signal&lt;/li&gt;
&lt;li&gt;No way to access the raw PCM data&lt;/li&gt;
&lt;li&gt;No concept of position and direction of sources and listeners&lt;/li&gt;
&lt;li&gt;No fine-grained timing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the rest of the article, I&#39;ll dive into some of these topics in the
context of game audio written with the Web Audio API. For a brief
introduction to this API, take a look at the &lt;a href=&quot;http://www.html5rocks.com/tutorials/webaudio/intro/&quot; rel=&quot;noopener&quot;&gt;getting started
tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;background-music&quot;&gt;Background music &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-games/#background-music&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Games often have background music playing on a loop.&lt;/p&gt;
&lt;p&gt;It can get very annoying if your loop is short and predictable. If a player
is stuck in an area or level, and the same sample continuously plays in
the background, it may be worthwhile to gradually fade out the track to
prevent further frustration. Another strategy is to have mixes of
various intensity that gradually crossfade into one another, depending
on the context of the game.&lt;/p&gt;
&lt;p&gt;For example, if your player is in a zone with an epic boss battle, you
might have several mixes varying in emotional range from atmospheric to
foreshadowing to intense. Music synthesis software often allows you to
export several mixes (of the same length) based on a piece by picking
the set of tracks to use in the export. That way you have some internal
consistency and avoid having jarring transitions as you cross-fade from
one track to another.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Garageband&quot; decoding=&quot;async&quot; height=&quot;200&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 552px) 552px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/JhCRz1ycPc08Ai6ZVcFR.png?auto=format&amp;w=1104 1104w&quot; width=&quot;552&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Then, using the Web Audio API, you can import all of these samples using
something like the &lt;a href=&quot;http://www.html5rocks.com/tutorials/webaudio/intro/js/buffer-loader.js&quot; rel=&quot;noopener&quot;&gt;BufferLoader class&lt;/a&gt; via XHR (this is
covered in depth in the &lt;a href=&quot;http://www.html5rocks.com/tutorials/webaudio/intro/&quot; rel=&quot;noopener&quot;&gt;introductory Web Audio API article&lt;/a&gt;.
Loading sounds takes time, so assets that are used in the game should be
loaded on page load, at the start of the level, or perhaps incrementally
while the player is playing.&lt;/p&gt;
&lt;p&gt;Next, you create a source for each node, and a gain node for each
source, and connect the graph.&lt;/p&gt;
&lt;p&gt;After doing this, you can play back all of these sources simultaneously
on a loop, and since they are all the same length, the Web Audio API
will guarantee that they will remain aligned. As the character gets
nearer or further from the final boss battle, the game can vary the gain
values for each of the respective nodes in the chain, using a gain
amount algorithm like the following:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Assume gains is an array of AudioGainNode, normVal is the intensity&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// between 0 and 1.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; normVal &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gains&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// First reset gains on all nodes.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; gains&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    gains&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Decide which two nodes we are currently between, and do an equal&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// power crossfade between them.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; leftNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;floor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Normalize the value between 0 and 1.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; leftNode&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; gain1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; gain2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Set the two gains accordingly.&lt;/span&gt;&lt;br /&gt;gains&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;leftNode&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; gain1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Check to make sure that there&#39;s a right node.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;leftNode &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; gains&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// If there is, adjust its gain.&lt;/span&gt;&lt;br /&gt;    gains&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;leftNode &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; gain2&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In the above approach, two sources play at once, and we crossfade
between them using equal power curves (as described in the
&lt;a href=&quot;http://www.html5rocks.com/tutorials/webaudio/intro/&quot; rel=&quot;noopener&quot;&gt;intro&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&quot;the-missing-link-audio-tag-to-web-audio&quot;&gt;The missing link: Audio tag to Web Audio &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-games/#the-missing-link-audio-tag-to-web-audio&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Many game developers today use the &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; tag for their background
music, because it is well suited to streaming content. Now you can bring
content from the &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; tag into a Web Audio context.&lt;/p&gt;
&lt;p&gt;This technique can be useful since the &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; tag can work with
streaming content, which lets you immediately play the background music
instead of having to wait for it all to download. By bringing the stream
into the Web Audio API, you can manipulate or analyze the stream. The
following example applies a low pass filter to the music played through
the &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; tag:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; audioElement &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;audio&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; mediaSourceNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createMediaElementSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;audioElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Create the filter&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; filter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createBiquadFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Create the audio graph.&lt;/span&gt;&lt;br /&gt;mediaSourceNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;For a more complete discussion about integrating the &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; tag with the
Web Audio API, see this &lt;a href=&quot;http://updates.html5rocks.com/2012/02/HTML5-audio-and-the-Web-Audio-API-are-BFFs&quot; rel=&quot;noopener&quot;&gt;short article&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;sound-effects&quot;&gt;Sound effects &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-games/#sound-effects&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Games often play back sound effects in response to user input or changes
in game state. Like background music, however, sound effects can get
annoying very quickly. To avoid this, it&#39;s often useful to have a pool
of similar but different sounds to play. This can vary from mild
variations of footstep samples, to drastic variations, as seen in the
&lt;a href=&quot;http://www.youtube.com/watch?v=MXgr6SYYNZM&quot; rel=&quot;noopener&quot;&gt;Warcraft series&lt;/a&gt; in response to clicking on units.&lt;/p&gt;
&lt;p&gt;Another key feature of sound effects in games is that there can be many
of them simultaneously. Imagine you&#39;re in the middle of a gunfight with
multiple actors shooting machine guns. Each machine gun fires many times
per second, causing tens of sound effects to be played at the same time.
Playing back sound from multiple, precisely timed sources simultaneously
is one place the Web Audio API really shines.&lt;/p&gt;
&lt;p&gt;The following example creates a machine gun round from multiple
individual bullet samples by creating multiple sound sources whose
playback is staggered in time.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; time &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTime&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; rounds&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;makeSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffers&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;M4A1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;noteOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; interval&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Now, if all of the machine guns in your game sounded exactly like this,
that would be pretty boring. Of course they would vary by sound based on
distance from the target and relative position (more on this later), but
even that might not be enough. Luckily the Web Audio API provides a way
to easily tweak the example above in two ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;With a subtle shift in time between bullets firing&lt;/li&gt;
&lt;li&gt;By altering playbackRate of each sample (also changing pitch) to
better simulate randomness of the real world.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For a more real-life example of these techniques in action, take a look
at the &lt;a href=&quot;http://chromium.googlecode.com/svn/trunk/samples/audio/o3d-webgl-samples/pool.html&quot; rel=&quot;noopener&quot;&gt;Pool Table demo&lt;/a&gt;, which uses random sampling and varies
playbackRate for a more interesting ball collision sound.&lt;/p&gt;
&lt;h2 id=&quot;3d-positional-sound&quot;&gt;3D positional sound &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-games/#3d-positional-sound&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Games are often set in a world with some geometric properties, either in
2D or in 3D. If this is the case, stereo positioned audio can greatly
increase the immersiveness of the experience. Luckily, Web Audio API
comes with built-in hardware accelerated positional audio features that
are quite straight forward to use. By the way, you should make sure that
you&#39;ve got stereo speakers (preferably headphones) for the following
example to make sense.&lt;/p&gt;
&lt;p&gt;In the above example, there is a listener (person icon) in the middle of
the canvas, and the mouse affects the position of the source (speaker
icon). The above is a simple example of using &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#AudioPannerNode-section&quot; rel=&quot;noopener&quot;&gt;AudioPannerNode&lt;/a&gt; to
achieve this sort of effect. The basic idea of the sample above is to
respond to mouse movement by setting the position of the audio source,
as follows:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;PositionSample&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;changePosition&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Position coordinates are in normalized canvas coordinates&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// with -0.5 &amp;lt; x, y &amp;lt; 0.5&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isPlaying&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;play&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; mul &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; position&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;size&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;position&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;size&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;panner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setPosition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; mul&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; mul&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Things to know about Web Audio&#39;s treatment of spatialization:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The listener is at the origin (0, 0, 0) by default.&lt;/li&gt;
&lt;li&gt;Web Audio positional APIs are unitless, so I introduced a multiplier
to make the demo sound better.&lt;/li&gt;
&lt;li&gt;Web Audio uses the y-is-up cartesian coordinates (the opposite of most
computer graphics systems). That&#39;s why I&#39;m swapping the y-axis in the
snippet above&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;advanced-sound-cones&quot;&gt;Advanced: sound cones &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-games/#advanced-sound-cones&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The positional model is very powerful and quite advanced, largely based
on &lt;a href=&quot;http://connect.creativelabs.com/openal/Documentation/OpenAL%201.1%20Specification.pdf&quot; rel=&quot;noopener&quot;&gt;OpenAL&lt;/a&gt;. For more details, see sections 3 and 4 of the
above-linked spec.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Position model&quot; decoding=&quot;async&quot; height=&quot;231&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 441px) 441px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j53QhZyQVM7dI9LA0byD.png?auto=format&amp;w=882 882w&quot; width=&quot;441&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;There is a single AudioListener attached to the Web Audio API
context which can be configured in space through position and
orientation. Each source can be passed through an AudioPannerNode, which
spatializes the input audio. The panner node has position and
orientation, as well as a distance and directional model.&lt;/p&gt;
&lt;p&gt;The distance model specifies the amount of gain depending on proximity
to the source, while the directional model can be configured by
specifying  an inner and outer cone, which determine amount of (usually
negative) gain if the listener is within the inner cone, between the
inner and outer cone, or outside the outer cone.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; panner &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createPanner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;panner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coneOuterGain &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;panner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coneOuterAngle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;180&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;panner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coneInnerAngle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Though my example is in 2D, this model generalizes easily to the third
dimension. For an example of sound spatialized in 3D, see this
&lt;a href=&quot;http://chromium.googlecode.com/svn/trunk/samples/audio/simple.html&quot; rel=&quot;noopener&quot;&gt;positional sample&lt;/a&gt;. In addition to position, the Web Audio
sound model also optionally includes velocity for doppler shifts. This
example shows the &lt;a href=&quot;http://chromium.googlecode.com/svn/trunk/samples/audio/doppler.html&quot; rel=&quot;noopener&quot;&gt;doppler effect&lt;/a&gt; in more detail.&lt;/p&gt;
&lt;p&gt;For more information on this topic, read this detailed tutorial on
[mixing positional audio and WebGL][webgl].&lt;/p&gt;
&lt;h2 id=&quot;room-effects-and-filters&quot;&gt;Room effects and filters &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-games/#room-effects-and-filters&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In reality, the way sound is perceived greatly depends on the room in
which that sound is heard. The same creaky door will sound very
different in a basement, compared to an large open hall. Games with high
production value will want to imitate these effects, since creating a
separate set of samples for each environment is prohibitively expensive,
and would lead to even more assets, and a larger amount of game data.&lt;/p&gt;
&lt;p&gt;Loosely speaking, the audio term for the difference between the raw
sound and the way it sounds in reality is the &lt;a href=&quot;http://en.wikipedia.org/wiki/Impulse_response&quot; rel=&quot;noopener&quot;&gt;impulse
response&lt;/a&gt;. These impulse responses can be painstakingly
recorded, and in fact there are &lt;a href=&quot;https://www.google.com/search?q=impulse+responses&quot; rel=&quot;noopener&quot;&gt;sites that host&lt;/a&gt; many of
these pre-recorded impulse response files (stored as audio) for your
convenience.&lt;/p&gt;
&lt;p&gt;For a lot more information about how impulse responses can be created
from a given environment, read through the &amp;quot;Recording Setup&amp;quot; section in
the &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#Convolution-section&quot; rel=&quot;noopener&quot;&gt;Convolution&lt;/a&gt; part of the Web Audio API spec.&lt;/p&gt;
&lt;p&gt;More importantly for our purposes, the Web Audio API provides an easy
way to apply these impulse responses to our sounds using the
&lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#ConvolverNode-section&quot; rel=&quot;noopener&quot;&gt;ConvolverNode&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Make a source node for the sample.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createBufferSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Make a convolver node for the impulse response.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; convolver &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createConvolver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;convolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;impulseResponseBuffer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Connect the graph.&lt;/span&gt;&lt;br /&gt;source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;convolver&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;convolver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Also see this &lt;a href=&quot;http://chromium.googlecode.com/svn/trunk/samples/audio/convolution-effects.html&quot; rel=&quot;noopener&quot;&gt;demo of room effects&lt;/a&gt; on the Web Audio API spec
page, as well as &lt;a href=&quot;http://kevincennis.com/audio/&quot; rel=&quot;noopener&quot;&gt;this example&lt;/a&gt; which gives you control over dry
(raw) and wet (processed via convolver) mixing of a great Jazz standard.&lt;/p&gt;
&lt;h2 id=&quot;the-final-countdown&quot;&gt;The final countdown &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-games/#the-final-countdown&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So you&#39;ve built a game, configured your positional audio, and now you
have a large number of AudioNodes in your graph, all playing back
simultaneously. Great, but there&#39;s still one more thing to consider:&lt;/p&gt;
&lt;p&gt;Since multiple sounds just stack on top of one another with no
normalization, you may find yourself in a situation where you are
exceeding the threshold of your speaker&#39;s capability. Like images
exceeding past their canvas boundaries, sounds can also clip if the
waveform exceeds its maximum threshold, producing a distinct distortion.
The waveform looks something like this:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Clipping&quot; decoding=&quot;async&quot; height=&quot;259&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/j3CSxOfegwYj1wk2aLXf.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Here&#39;s a real example of clipping in action. The waveform looks bad:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Clipping&quot; decoding=&quot;async&quot; height=&quot;288&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 589px) 589px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/QMULrVsb7ddmX0P1debJ.png?auto=format&amp;w=1178 1178w&quot; width=&quot;589&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;It&#39;s important to listen to harsh distortions like the one above, or
conversely, overly subdued mixes that force your listeners to crank up
the volume. If you&#39;re in this situation, you really need to fix it!&lt;/p&gt;
&lt;h3 id=&quot;detect-clipping&quot;&gt;Detect clipping &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-games/#detect-clipping&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;From a technical perspective, clipping happens when the value of the
signal in any channel exceeds the valid range, namely between -1 and 1.
Once this is detected, it&#39;s useful to give visual feedback that this is
happening. To do this reliably, put a &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#JavaScriptAudioNode-section&quot; rel=&quot;noopener&quot;&gt;JavaScriptAudioNode&lt;/a&gt; into
your graph. The audio graph would be setup as follows:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Assume entire sound output is being piped through the mix node.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; meter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createJavaScriptNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2048&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;meter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;onaudioprocess &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; processAudio&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;mix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;meter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;meter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;And clipping could be detected in the following &lt;code&gt;processAudio&lt;/code&gt; handler:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;processAudio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputBuffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getChannelData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; isClipping &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Iterate through buffer to check if any of the |values| exceeds 1.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; absValue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;absValue &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        isClipping &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In general, be careful not to overuse the &lt;code&gt;JavaScriptAudioNode&lt;/code&gt; for
performance reasons. In this case, an alternative implementation of
metering could poll a &lt;code&gt;RealtimeAnalyserNode&lt;/code&gt; in the audio graph for
&lt;code&gt;getByteFrequencyData&lt;/code&gt;, at render time, as determined by
&lt;code&gt;requestAnimationFrame&lt;/code&gt;. This approach is more efficient, but misses
most of the signal (including places where it potentially clips), since
rendering happens at most at 60 times a second, whereas the audio signal
changes far more quickly.&lt;/p&gt;
&lt;p&gt;Because clip detection is so important, it&#39;s likely that we will see a
built-in &lt;code&gt;MeterNode&lt;/code&gt; Web Audio API node in the future.&lt;/p&gt;
&lt;h3 id=&quot;prevent-clipping&quot;&gt;Prevent clipping &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-games/#prevent-clipping&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;By adjusting the gain on the master AudioGainNode, you can subdue your
mix to a level that prevents clipping. However, in practice, since the
sounds playing in your game may depend on a huge variety of factors, it
can be difficult to decide on the master gain value that prevents
clipping for all states. In general, you should tweak gains to
anticipate the worst case, but this is more of an art than a science.&lt;/p&gt;
&lt;h3 id=&quot;add-a-bit-of-sugar&quot;&gt;Add a bit of sugar &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-games/#add-a-bit-of-sugar&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Compressors are commonly used in music and game production to smooth
over the signal and control spikes in the overall signal. This
functionality is available in the Web Audio world via the
&lt;code&gt;DynamicsCompressorNode&lt;/code&gt;, which can be inserted in your audio graph to
give a louder, richer and fuller sound, and also help with clipping.
Directly quoting the spec, this node&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; …lowers the volume of the loudest parts of the signal and raises the volume of the softest parts… It is especially important in games and musical applications where large numbers of individual sounds are played simultaneous to control the overall signal level and help avoid clipping. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Using dynamics compression is generally a good idea, especially in a
game setting, where, as previously discussed, you don&#39;t know exactly
what sounds will play and when. &lt;a href=&quot;http://labs.dinahmoe.com/plink&quot; rel=&quot;noopener&quot;&gt;Plink&lt;/a&gt; from DinahMoe labs is a
great example of this, since the sounds that are played back completely
depends on you and other participants. A compressor is useful in most
cases, except some rare ones, where you&#39;re dealing with painstakingly
mastered tracks that have been tuned to sound &amp;quot;just right&amp;quot; already.&lt;/p&gt;
&lt;p&gt;Implementing this is simply a matter of including a
DynamicsCompressorNode in your audio graph, generally as the last node
before the destination.:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Assume the output is all going through the mix node.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; compressor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createDynamicsCompressor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;mix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;compressor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;compressor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;For more detail about dynamics compression, &lt;a href=&quot;http://en.wikipedia.org/wiki/Dynamic_range_compression&quot; rel=&quot;noopener&quot;&gt;this Wikipedia
article&lt;/a&gt; is very informative.&lt;/p&gt;
&lt;p&gt;To summarize, listen carefully for clipping and prevent it by inserting
a master gain node. Then tightening the whole mix by using a dynamics
compressor node. Your audio graph might look something like this:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Final result&quot; decoding=&quot;async&quot; height=&quot;166&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 488px) 488px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/783OhZNEAjZnQp7xXKeB.png?auto=format&amp;w=976 976w&quot; width=&quot;488&quot; /&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-games/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;That covers what I think are the most important aspects of game audio
development using the Web Audio API. With these techniques, you can
build truly compelling audio experiences right in your browser. Before I
sign off, let me leave you with a browser-specific tip: be sure to
pause sound if your tab goes to the background using the &lt;a href=&quot;http://www.samdutton.com/pageVisibility/&quot; rel=&quot;noopener&quot;&gt;page
visibility API&lt;/a&gt;, otherwise you will create a potentially
frustrating experience for your user.&lt;/p&gt;
&lt;p&gt;For additional information about Web Audio, take a look at the
more introductory &lt;a href=&quot;http://www.html5rocks.com/tutorials/webaudio/intro/&quot; rel=&quot;noopener&quot;&gt;getting started article&lt;/a&gt;, and if you have a
question, see if it&#39;s already answered in the &lt;a href=&quot;http://updates.html5rocks.com/2012/01/Web-Audio-FAQ&quot; rel=&quot;noopener&quot;&gt;web audio FAQ&lt;/a&gt;.
Finally, if you have additional questions, ask them on &lt;a href=&quot;http://stackoverflow.com/questions/tagged/web-audio&quot; rel=&quot;noopener&quot;&gt;Stack
Overflow&lt;/a&gt; using the &lt;a href=&quot;http://stackoverflow.com/questions/tagged/web-audio&quot; rel=&quot;noopener&quot;&gt;web-audio&lt;/a&gt; tag.&lt;/p&gt;
&lt;p&gt;Before I sign off, let me leave you with some awesome uses of the Web
Audio API in real games today:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://fieldrunnershtml5.appspot.com/&quot; rel=&quot;noopener&quot;&gt;Field Runners&lt;/a&gt;, and a writeup about some of the
&lt;a href=&quot;http://weblog.bocoup.com/fieldrunners-playing-to-the-strengths-of-html5-audio-and-web-audio/&quot; rel=&quot;noopener&quot;&gt;technical details&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://chrome.angrybirds.com/&quot; rel=&quot;noopener&quot;&gt;Angry Birds&lt;/a&gt;, recently switched to Web Audio API. See
&lt;a href=&quot;http://googlecode.blogspot.com/2012/01/angry-birds-chrome-now-uses-web-audio.html&quot; rel=&quot;noopener&quot;&gt;this writeup&lt;/a&gt; for more information.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://skid.gamagio.com/play/&quot; rel=&quot;noopener&quot;&gt;Skid Racer&lt;/a&gt;, which makes great use of spatialized audio.&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Boris Smus</name>
    </author>
  </entry>
  
  <entry>
    <title>Working with Custom Elements</title>
    <link href="https://web.dev/customelements/"/>
    <updated>2011-10-14T00:00:00Z</updated>
    <id>https://web.dev/customelements/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-warn-bg color-state-warn-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block color-state-warn-text&quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Warning sign&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M23 21L12 2 1 21h22zm-12-3v-2h2v2h-2zm0-4h2v-4h-2v4z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; This article describes an old version of Custom Elements (v0). If you&#39;re interested in using Custom Elements, check out our new article, &amp;quot;&lt;a href=&quot;https://web.dev/custom-elements-v1/&quot;&gt;Custom Elements v1 - Reusable Web Components&lt;/a&gt;.&amp;quot; It covers everything in the newer Custom Elements v1 spec shipped in Chrome 53, Safari 10, and Firefox 63. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;introduction&quot;&gt;Introduction &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#introduction&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The web severely lacks expression. To see what I mean, take a peek at a &amp;quot;modern&amp;quot; web app like GMail:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Gmail&quot; decoding=&quot;async&quot; height=&quot;470&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/xDJlKi0xvNl9gqZ6tDGB.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;There&#39;s nothing modern about &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; soup. And yet, this is how we build web apps. It&#39;s sad.
Shouldn&#39;t we demand more from our platform?&lt;/p&gt;
&lt;h3 id=&quot;sexy-markup-lets-make-it-a-thing&quot;&gt;Sexy markup. Let&#39;s make it a thing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#sexy-markup-lets-make-it-a-thing&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;HTML gives us an excellent tool for structuring a document but its vocabulary is limited to elements
the &lt;a href=&quot;http://www.whatwg.org/specs/web-apps/current-work/multipage/&quot; rel=&quot;noopener&quot;&gt;HTML standard&lt;/a&gt; defines.&lt;/p&gt;
&lt;p&gt;What if the markup for GMail wasn&#39;t atrocious? What if it was beautiful:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;hangout-module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;hangout-chat&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Paul, Addy&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;hangout-discussion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;hangout-message&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Paul&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;profile&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;profile.png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token attr-name&quot;&gt;profile&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;118075919496626375791&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;datetime&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;2013-07-17T12:02&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Feelin&#39; this Web Components thing.&lt;br /&gt;        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Heard of it?&lt;br /&gt;        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;hangout-message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;hangout-discussion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;hangout-chat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;hangout-chat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;...&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;hangout-chat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;hangout-module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;How refreshing! This app totally makes sense too. It&#39;s &lt;strong&gt;meaningful&lt;/strong&gt;, &lt;strong&gt;easy to understand&lt;/strong&gt;,
and best of all, it&#39;s &lt;strong&gt;maintainable&lt;/strong&gt;. Future me/you will know exactly what it does
just by examining its declarative backbone.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting started &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#getting-started&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://html.spec.whatwg.org/multipage/custom-elements.html&quot; rel=&quot;noopener&quot;&gt;Custom Elements&lt;/a&gt;
&lt;strong&gt;allow web developers to define new types of HTML elements&lt;/strong&gt;. The spec is one of several new API primitives landing under the &lt;a href=&quot;https://www.w3.org/TR/2012/WD-components-intro-20120522/&quot; rel=&quot;noopener&quot;&gt;Web Components&lt;/a&gt; umbrella, but it&#39;s quite possibly the most important. Web Components don&#39;t exist
without the features unlocked by custom elements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define new HTML/DOM elements&lt;/li&gt;
&lt;li&gt;Create elements that extend from other elements&lt;/li&gt;
&lt;li&gt;Logically bundle together custom functionality into a single tag&lt;/li&gt;
&lt;li&gt;Extend the API of existing DOM elements&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;registering-new-elements&quot;&gt;Registering new elements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#registering-new-elements&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Custom elements are created using &lt;code&gt;document.registerElement()&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; XFoo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x-foo&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XFoo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The first argument to &lt;code&gt;document.registerElement()&lt;/code&gt; is the element&#39;s tag name.
The name &lt;strong&gt;must contain a dash (-)&lt;/strong&gt;. So for example, &lt;code&gt;&amp;lt;x-tags&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;my-element&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;my-awesome-app&amp;gt;&lt;/code&gt; are all valid names, while &lt;code&gt;&amp;lt;tabs&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;foo_bar&amp;gt;&lt;/code&gt; are not. This restriction allows the parser
to distinguish custom elements from regular elements but also ensures forward
compatibility when new tags are added to HTML.&lt;/p&gt;
&lt;p&gt;The second argument is an (optional) object describing the element&#39;s &lt;code&gt;prototype&lt;/code&gt;.
This is the place to add custom functionality (e.g. public properties and methods) to your elements.
&lt;a href=&quot;https://web.dev/customelements/#adding-js-properties-and-methods&quot;&gt;More on that&lt;/a&gt; later.&lt;/p&gt;
&lt;p&gt;By default, custom elements inherit from &lt;code&gt;HTMLElement&lt;/code&gt;. Thus, the previous example is equivalent to:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; XFoo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x-foo&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;A call to &lt;code&gt;document.registerElement(&#39;x-foo&#39;)&lt;/code&gt; teaches the browser about the new element,
and returns a constructor that you can use to create instances of &lt;code&gt;&amp;lt;x-foo&amp;gt;&lt;/code&gt;.
Alternatively, you can use the other &lt;a href=&quot;https://web.dev/customelements/#instantiating-elements&quot;&gt;techniques of instantiating elements&lt;/a&gt;
if you don&#39;t want to use the constructor.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; If it&#39;s undesirable that the constructor ends up on the global &lt;code&gt;window&lt;/code&gt; object, put it in a namespace (&lt;code&gt;var myapp = {}; myapp.XFoo = document.registerElement(&#39;x-foo&#39;);&lt;/code&gt;) or drop it on the floor. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;extending-elements&quot;&gt;Extending elements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#extending-elements&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Custom elements allows you to extend existing (native) HTML elements as well as other
custom elements. To extend an element, you need to pass &lt;code&gt;registerElement()&lt;/code&gt; the name
and &lt;code&gt;prototype&lt;/code&gt; of the element to inherit from.&lt;/p&gt;
&lt;h4 id=&quot;extending-native-elements&quot;&gt;Extending native elements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#extending-native-elements&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Say you aren&#39;t happy with Regular Joe &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;. You&#39;d like to
supercharge its capabilities to be a &amp;quot;Mega Button&amp;quot;. To extend the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element,
create a new element that inherits the &lt;code&gt;prototype&lt;/code&gt; of &lt;code&gt;HTMLButtonElement&lt;/code&gt; and &lt;code&gt;extends&lt;/code&gt;
the name of the element. In this case, &amp;quot;button&amp;quot;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; MegaButton &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;mega-button&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HTMLButtonElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;button&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; To create &lt;strong&gt;element A&lt;/strong&gt; that extends &lt;strong&gt;element B&lt;/strong&gt;, &lt;strong&gt;element A&lt;/strong&gt; must inherit the &lt;code&gt;prototype&lt;/code&gt; of &lt;strong&gt;element B&lt;/strong&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Custom elements that inherit from native elements are called &lt;em&gt;type extension custom elements&lt;/em&gt;.
They inherit from a specialized version of &lt;code&gt;HTMLElement&lt;/code&gt; as a way to say, &amp;quot;element X is a Y&amp;quot;.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;is&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;mega-button&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h4 id=&quot;extending-a-custom-element&quot;&gt;Extending a custom element &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#extending-a-custom-element&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To create an &lt;code&gt;&amp;lt;x-foo-extended&amp;gt;&lt;/code&gt; element that extends the &lt;code&gt;&amp;lt;x-foo&amp;gt;&lt;/code&gt; custom element, simply inherit its prototype
and say what tag you&#39;re inheriting from:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; XFooProto &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; XFooExtended &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x-foo-extended&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; XFooProto&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;x-foo&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;See &lt;a href=&quot;https://web.dev/customelements/#adding-js-properties-and-methods&quot;&gt;Adding JS properties and methods&lt;/a&gt; below for more information on creating element prototypes.&lt;/p&gt;
&lt;h3 id=&quot;how-elements-are-upgraded&quot;&gt;How elements are upgraded &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#how-elements-are-upgraded&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Have you ever wondered why the HTML parser doesn&#39;t throw a fit on non-standard tags?
For example, it&#39;s perfectly happy if we declare &lt;code&gt;&amp;lt;randomtag&amp;gt;&lt;/code&gt; on the page. According to the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/dom.html#htmlunknownelement&quot; rel=&quot;noopener&quot;&gt;HTML specification&lt;/a&gt;:&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The &lt;code&gt;HTMLUnknownElement&lt;/code&gt; interface must be used for HTML elements that are not defined by this specification. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Sorry &lt;code&gt;&amp;lt;randomtag&amp;gt;&lt;/code&gt;! You&#39;re non-standard and inherit from &lt;code&gt;HTMLUnknownElement&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The same is not true for custom elements. &lt;strong&gt;Elements with valid custom element names inherit from &lt;code&gt;HTMLElement&lt;/code&gt;.&lt;/strong&gt; You can verify this fact by firing up the Console: &lt;code&gt;Ctrl + Shift + J&lt;/code&gt; (or &lt;code&gt;Cmd + Opt + J&lt;/code&gt; on Mac), and paste in the following lines of code; they return &lt;code&gt;true&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// &quot;tabs&quot; is not a valid custom element name&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;tabs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__proto__ &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLUnknownElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// &quot;x-tabs&quot; is a valid custom element name&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x-tabs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;__proto__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; &lt;code&gt;&amp;lt;x-tabs&amp;gt;&lt;/code&gt; will still be an &lt;code&gt;HTMLUnknownElement&lt;/code&gt; in browsers that don&#39;t support &lt;code&gt;document.registerElement()&lt;/code&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;unresolved-elements&quot;&gt;Unresolved elements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#unresolved-elements&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Because custom elements are registered by script using &lt;code&gt;document.registerElement()&lt;/code&gt;, &lt;strong&gt;they can be
declared or created &lt;em&gt;before&lt;/em&gt; their definition is registered&lt;/strong&gt; by the browser. For example,
you can declare &lt;code&gt;&amp;lt;x-tabs&amp;gt;&lt;/code&gt; on the page but end up invoking &lt;code&gt;document.registerElement(&#39;x-tabs&#39;)&lt;/code&gt; much later.&lt;/p&gt;
&lt;p&gt;Before elements are upgraded to their definition, they&#39;re called &lt;strong&gt;unresolved elements&lt;/strong&gt;.
These are HTML elements that have a valid custom element name but haven&#39;t been registered.&lt;/p&gt;
&lt;p&gt;This table might help keep things straight:&lt;/p&gt;
&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Inherits from&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unresolved element&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HTMLElement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;x-tabs&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;my-element&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unknown element&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HTMLUnknownElement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;tabs&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;foo_bar&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Think of unresolved elements as in limbo. They&#39;re potential candidates for the browser to upgrade later on. The browser says, &amp;quot;You have all the right qualities I&#39;m looking for in a new element. I promise to upgrade you when I&#39;m given your definition&amp;quot;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;instantiating-elements&quot;&gt;Instantiating elements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#instantiating-elements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The common techniques of creating elements still apply to custom elements.
As with any standard element, they can be declared in HTML or created in DOM
using JavaScript.&lt;/p&gt;
&lt;h3 id=&quot;instantiating-custom-tags&quot;&gt;Instantiating custom tags &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#instantiating-custom-tags&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Declare&lt;/strong&gt; them:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;x-foo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;x-foo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Create DOM&lt;/strong&gt; in JS:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; xFoo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x-foo&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;xFoo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Thanks!&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Use the &lt;strong&gt;&lt;code&gt;new&lt;/code&gt; operator&lt;/strong&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; xFoo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XFoo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xFoo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;instantiating-type-extension-elements&quot;&gt;Instantiating type extension elements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#instantiating-type-extension-elements&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Instantiating type extension-style custom elements is strikingly close to custom tags.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Declare&lt;/strong&gt; them:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- &amp;lt;button&gt; &quot;is a&quot; mega button --&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;is&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;mega-button&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Create DOM&lt;/strong&gt; in JS:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; megaButton &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;button&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;mega-button&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// megaButton instanceof MegaButton === true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As you can see, there&#39;s now an overloaded version of &lt;code&gt;document.createElement()&lt;/code&gt;
that takes the &lt;code&gt;is=&amp;quot;&amp;quot;&lt;/code&gt; attribute as its second parameter.&lt;/p&gt;
&lt;p&gt;Use the &lt;strong&gt;&lt;code&gt;new&lt;/code&gt; operator&lt;/strong&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; megaButton &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MegaButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;megaButton&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;So far, we&#39;ve learned how to use &lt;code&gt;document.registerElement()&lt;/code&gt; to tell the browser about a new tag…but
it doesn&#39;t do much. Let&#39;s add properties and methods.&lt;/p&gt;
&lt;h2 id=&quot;adding-js-properties-and-methods&quot;&gt;Adding JS properties and methods &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#adding-js-properties-and-methods&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The powerful thing about custom elements is that you can bundle tailored functionality
with the element by defining properties and methods on the element definition.
Think of this as a way to create a public API for your element.&lt;/p&gt;
&lt;p&gt;Here&#39;s a full example:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; XFooProto &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// 1. Give x-foo a foo() method.&lt;/span&gt;&lt;br /&gt;XFooProto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;foo() called&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// 2. Define a property read-only &quot;bar&quot;.&lt;/span&gt;&lt;br /&gt;Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defineProperty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;XFooProto&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// 3. Register x-foo&#39;s definition.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; XFoo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x-foo&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; XFooProto&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// 4. Instantiate an x-foo.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; xfoo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x-foo&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// 5. Add it to the page.&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;xfoo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Of course there are umpteen thousand ways to construct a &lt;code&gt;prototype&lt;/code&gt;. If you&#39;re not
a fan of creating prototypes like this, here&#39;s a more condensed version of the same thing:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; XFoo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x-foo&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function-variable function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function-variable function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;foo() called&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The first format allows for the use of ES5 &lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Object.defineProperty&lt;/code&gt;&lt;/a&gt;. The second allows the use of &lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/get&quot; rel=&quot;noopener&quot;&gt;get/set&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;lifecycle-callback-methods&quot;&gt;Lifecycle callback methods &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#lifecycle-callback-methods&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Elements can define special methods for tapping into interesting times of their existence.
These methods are appropriately named the &lt;strong&gt;lifecycle callbacks&lt;/strong&gt;. Each has a specific name and purpose:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Callback name&lt;/th&gt;
      &lt;th&gt;Called when&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;createdCallback&lt;/td&gt;
      &lt;td&gt;an instance of the element is created&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;attachedCallback&lt;/td&gt;
      &lt;td&gt;an instance was inserted into the document&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;detachedCallback&lt;/td&gt;
      &lt;td&gt;an instance was removed from the document&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;attributeChangedCallback(attrName, oldVal, newVal)&lt;/td&gt;
      &lt;td&gt;an attribute was added, removed, or updated&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; defining &lt;code&gt;createdCallback()&lt;/code&gt; and &lt;code&gt;attachedCallback()&lt;/code&gt; on &lt;code&gt;&amp;lt;x-foo&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; proto &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;proto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;createdCallback&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;proto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;attachedCallback&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; XFoo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x-foo&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; proto&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;All of the lifecycle callbacks are optional&lt;/strong&gt;, but define them if/when it makes sense.
For example, say your element is sufficiently complex and opens a connection to IndexedDB
in &lt;code&gt;createdCallback()&lt;/code&gt;. Before it gets removed from the DOM, do the necessary
cleanup work in &lt;code&gt;detachedCallback()&lt;/code&gt;. &lt;strong&gt;Note:&lt;/strong&gt; you shouldn&#39;t rely on this,
for example, if the user closes the tab, but think of it as a possible optimization hook.&lt;/p&gt;
&lt;p&gt;Another use case lifecycle callbacks is for setting up default event listeners
on the element:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;proto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;createdCallback&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Thanks!&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; People won&#39;t use your elements if they&#39;re clunky. The lifecycle callbacks can help you be a good citizen! &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;adding-markup&quot;&gt;Adding markup &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#adding-markup&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;ve created &lt;code&gt;&amp;lt;x-foo&amp;gt;&lt;/code&gt;, given it a JavaScript API, but it&#39;s blank! Shall we
give it some HTML to render?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/customelements/#lifecycle&quot;&gt;Lifecycle callbacks&lt;/a&gt; come in handy here. Particularly, we can use
&lt;code&gt;createdCallback()&lt;/code&gt; to endow an element with some default HTML:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; XFooProto &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;XFooProto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;createdCallback&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;**I&#39;m an x-foo-with-markup!**&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; XFoo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x-foo-with-markup&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; XFooProto&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Instantiating this tag and inspecting in the DevTools (right-click, select Inspect Element) should show:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;▾&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;x-foo-with-markup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  **I&#39;m an x-foo-with-markup!**&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;x-foo-with-markup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;encapsulating-the-internals-in-shadow-dom&quot;&gt;Encapsulating the internals in Shadow DOM &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#encapsulating-the-internals-in-shadow-dom&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;By itself, &lt;a href=&quot;https://web.dev/shadowdom/&quot;&gt;Shadow DOM&lt;/a&gt; is a powerful tool for
encapsulating content. Use it in conjunction with custom elements and things get magical!&lt;/p&gt;
&lt;p&gt;Shadow DOM gives custom elements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A way to hide their guts, thus shielding users from gory implementation details.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/shadowdom-201/&quot;&gt;Style encapsulation&lt;/a&gt;…fo&#39; free.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Creating an element from Shadow DOM is like creating one that
renders basic markup. The difference is in &lt;code&gt;createdCallback()&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; XFooProto &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;XFooProto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;createdCallback&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// 1. Attach a shadow root on the element.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; shadow &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createShadowRoot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// 2. Fill it with markup goodness.&lt;/span&gt;&lt;br /&gt;    shadow&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;**I&#39;m in the element&#39;s Shadow DOM!**&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; XFoo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x-foo-shadowdom&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; XFooProto&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Instead of setting the element&#39;s &lt;code&gt;.innerHTML&lt;/code&gt;, I&#39;ve created a
Shadow Root for &lt;code&gt;&amp;lt;x-foo-shadowdom&amp;gt;&lt;/code&gt; and then filled it with markup.
With the &amp;quot;Show Shadow DOM&amp;quot; setting enabled in the DevTools, you&#39;ll see a
&lt;code&gt;#shadow-root&lt;/code&gt; that can be expanded:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;▾&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;x&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;foo&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;shadowdom&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  ▾#shadow&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;root&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;I&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;m in the element&#39;&lt;/span&gt;s Shadow &lt;span class=&quot;token constant&quot;&gt;DOM&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;x&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;foo&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;shadowdom&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;That&#39;s the Shadow Root!&lt;/p&gt;
&lt;h3 id=&quot;creating-elements-from-a-template&quot;&gt;Creating elements from a template &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#creating-elements-from-a-template&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://html.spec.whatwg.org/multipage/scripting.html#the-template-element&quot; rel=&quot;noopener&quot;&gt;HTML Templates&lt;/a&gt; are another new API primitive that fits nicely into the world of custom elements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; registering an element created from a &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; and Shadow DOM:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;template id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sdtemplate&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;    p &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; orange&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;style&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;I&lt;/span&gt;&#39;m &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; Shadow &lt;span class=&quot;token constant&quot;&gt;DOM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; My markup was stamped from a &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;gt&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;template&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; proto &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HTMLElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prototype&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;createdCallback&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function-variable function&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; t &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;#sdtemplate&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; clone &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;importNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createShadowRoot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;clone&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;x-foo-from-template&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; proto&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;script&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;template id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sdtemplate&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;host p &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; orange&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;style&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;I&lt;/span&gt;&#39;m &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; Shadow &lt;span class=&quot;token constant&quot;&gt;DOM&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; My markup was stamped from a &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;template&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;gt&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;template&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;demoarea&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;x&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;foo&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;from&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;template&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;x&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;foo&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;from&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;template&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;These few lines of code pack a lot of punch. Let&#39;s understanding everything that is happening:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We&#39;ve registered a new element in HTML: &lt;code&gt;&amp;lt;x-foo-from-template&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;The element&#39;s DOM was created from a &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The element&#39;s scary details are hidden away using Shadow DOM&lt;/li&gt;
&lt;li&gt;Shadow DOM gives the element style encapsulation (e.g. &lt;code&gt;p {color: orange;}&lt;/code&gt; isn&#39;t
turning the entire page orange)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So good!&lt;/p&gt;
&lt;h2 id=&quot;styling-custom-elements&quot;&gt;Styling custom elements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#styling-custom-elements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As with any HTML tag, users of your custom tag can style it with selectors:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;app-panel&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;[is=&quot;x-item&quot;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; opacity 400ms ease-in-out&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.3&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;flex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 50%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;[is=&quot;x-item&quot;]:hover&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1.0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rgb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;255&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 255&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; white&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;app-panel &gt; [is=&quot;x-item&quot;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 5px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;list-style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; none&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0 7px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;app-panel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;li&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;is&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;x-item&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Do&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;li&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;is&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;x-item&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Re&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;li&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;is&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;x-item&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Mi&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;li&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;app-panel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;styling-elements-that-use-shadow-dom&quot;&gt;Styling elements that use Shadow DOM &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#styling-elements-that-use-shadow-dom&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The rabbit hole goes much &lt;em&gt;much&lt;/em&gt; deeper when you bring Shadow DOM into the mix.
&lt;a href=&quot;https://web.dev/customelements/#encapsulating-the-internals-in-shadow-dom&quot;&gt;Custom elements that use Shadow DOM&lt;/a&gt; inherit its great benefits.&lt;/p&gt;
&lt;p&gt;Shadow DOM infuses an element with style encapsulation. Styles defined in a Shadow Root don&#39;t
leak out of the host and don&#39;t bleed in from the page. &lt;strong&gt;In the case of a custom element, the element itself is the host.&lt;/strong&gt; The properties of style encapsulation also allow custom elements to
define default styles for themselves.&lt;/p&gt;
&lt;p&gt;Shadow DOM styling is a huge topic! If you want to learn more about it, I recommend a few of my other articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;&lt;a href=&quot;http://www.polymer-project.org/articles/styling-elements.html&quot; rel=&quot;noopener&quot;&gt;A Guide to Styling Elements&lt;/a&gt;&amp;quot; on &lt;a href=&quot;http://www.polymer-project.org/&quot; rel=&quot;noopener&quot;&gt;Polymer&lt;/a&gt;&#39;s documentation.&lt;/li&gt;
&lt;li&gt;&amp;quot;&lt;a href=&quot;https://web.dev/shadowdom-201/&quot;&gt;Shadow DOM 201: CSS &amp;amp; Styling&lt;/a&gt;&amp;quot; here.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;fouc-prevention-using-unresolved&quot;&gt;FOUC prevention using :unresolved &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#fouc-prevention-using-unresolved&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To mitigate &lt;a href=&quot;http://en.wikipedia.org/wiki/Flash_of_unstyled_content&quot; rel=&quot;noopener&quot;&gt;FOUC&lt;/a&gt;, custom elements spec
out a new CSS pseudo class, &lt;code&gt;:unresolved&lt;/code&gt;. Use it to target &lt;a href=&quot;https://web.dev/customelements/#unresolved-elements&quot;&gt;unresolved elements&lt;/a&gt;,
right up until the point where the browser invokes your &lt;code&gt;createdCallback()&lt;/code&gt; (see &lt;a href=&quot;https://web.dev/customelements/#lifecycle-callback-methods&quot;&gt;lifecycle methods&lt;/a&gt;).
Once that happens, the element is no longer an unresolved element. The upgrade process is
complete and the element has transformed into its definition.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; CSS &lt;code&gt;:unresolved&lt;/code&gt; is supported natively in Chrome 29. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: fade in &amp;quot;x-foo&amp;quot; tags when they&#39;re registered:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;x-foo&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; opacity 300ms&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;x-foo:unresolved&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;opacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Keep in mind that &lt;code&gt;:unresolved&lt;/code&gt; only applies to &lt;a href=&quot;https://web.dev/customelements/#unresolved-elements&quot;&gt;unresolved elements&lt;/a&gt;,
not to elements that inherit from &lt;code&gt;HTMLUnknownElement&lt;/code&gt; (see &lt;a href=&quot;https://web.dev/customelements/#how-elements-are-upgraded&quot;&gt;How elements are upgraded&lt;/a&gt;).&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;/* apply a dashed border to all unresolved elements */&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;:unresolved&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px dashed red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; inline-block&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;/* x-panel&#39;s that are unresolved are red */&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;x-panel:unresolved&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;/* once the definition of x-panel is registered, it becomes green */&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;x-panel&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; green&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; block&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 5px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; block&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;panel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    I&#39;m black because :unresolved doesn&#39;t apply to &quot;panel&quot;.&lt;br /&gt;    It&#39;s not a valid custom element name.&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;panel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;x-panel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;I&#39;m red because I match x-panel:unresolved.&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;x-panel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;history-and-browser-support&quot;&gt;History and browser support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#history-and-browser-support&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;feature-detection&quot;&gt;Feature detection &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#feature-detection&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Feature detecting is a matter of checking if &lt;code&gt;document.registerElement()&lt;/code&gt; exists:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;supportsCustomElements&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;registerElement&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;supportsCustomElements&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Good to go!&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Use other libraries to create components.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;browser-support&quot;&gt;Browser support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#browser-support&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;document.registerElement()&lt;/code&gt; first started landing behind a flag in Chrome 27 and Firefox ~23. However, the specification has evolved quite a bit since then. Chrome 31 is the first to have
true support for the updated spec.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Custom elements can be enabled in Chrome 31 under &amp;quot;Experimental Web Platform features&amp;quot; in &lt;code&gt;about:flags&lt;/code&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Until browser support is stellar, there&#39;s a &lt;a href=&quot;http://www.polymer-project.org/platform/custom-elements.html&quot; rel=&quot;noopener&quot;&gt;polyfill&lt;/a&gt; which is used by Google&#39;s &lt;a href=&quot;http://polymer-project.org/&quot; rel=&quot;noopener&quot;&gt;Polymer&lt;/a&gt; and Mozilla&#39;s &lt;a href=&quot;http://www.x-tags.org/&quot; rel=&quot;noopener&quot;&gt;X-Tag&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;what-happened-to-htmlelementelement&quot;&gt;What happened to HTMLElementElement? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#what-happened-to-htmlelementelement&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For those that have followed the standardization work, you know there was once &lt;code&gt;&amp;lt;element&amp;gt;&lt;/code&gt;.
It was the bees knees. You could use it to declaratively register new elements:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;element&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;my-element&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    ...&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;element&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Unfortunately, there were too many timing issues with the &lt;a href=&quot;https://web.dev/customelements/#how-elements-are-upgraded&quot;&gt;upgrade process&lt;/a&gt;,
corner cases, and Armageddon-like scenarios to work it all out. &lt;code&gt;&amp;lt;element&amp;gt;&lt;/code&gt; had to be shelved. In August 2013, Dimitri Glazkov posted to &lt;a href=&quot;http://lists.w3.org/Archives/Public/public-webapps/2013JulSep/0287.html&quot; rel=&quot;noopener&quot;&gt;public-webapps&lt;/a&gt; announcing its removal, at least for now.&lt;/p&gt;
&lt;p&gt;It&#39;s worth noting that Polymer implements a declarative form of element registration
with &lt;code&gt;&amp;lt;polymer-element&amp;gt;&lt;/code&gt;. How? It uses &lt;code&gt;document.registerElement(&#39;polymer-element&#39;)&lt;/code&gt; and
the techniques I described in &lt;a href=&quot;https://web.dev/customelements/#creating-elements-from-a-template&quot;&gt;Creating elements from a template&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/customelements/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Custom elements give us the tool to extend HTML&#39;s vocabulary, teach it new tricks,
and jump through the wormholes of the web platform. Combine them with the other
new platform primitives like Shadow DOM and &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;, and we start to realize
the picture of Web Components. Markup can be sexy again!&lt;/p&gt;
&lt;p&gt;If you&#39;re interested in getting started with web components, I recommend checking
out &lt;a href=&quot;http://polymer-project.org/&quot; rel=&quot;noopener&quot;&gt;Polymer&lt;/a&gt;. It&#39;s got more than enough to get you going.&lt;/p&gt;
</content>
    <author>
      <name>Boris Smus</name>
    </author>
  </entry>
  
  <entry>
    <title>Getting started with Web Audio API</title>
    <link href="https://web.dev/webaudio-intro/"/>
    <updated>2011-10-14T00:00:00Z</updated>
    <id>https://web.dev/webaudio-intro/</id>
    <content type="html" mode="escaped">&lt;p&gt;Before the HTML5 &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; element, Flash or another plugin was required
to break the silence of the web. While audio on the web no longer
requires a plugin, the audio tag brings significant limitations for
implementing sophisticated games and interactive applications.&lt;/p&gt;
&lt;p&gt;The Web Audio API is a high-level JavaScript API for processing and
synthesizing audio in web applications. The goal of this API is to
include capabilities found in modern game audio engines and some of the
mixing, processing, and filtering tasks that are found in modern desktop
audio production applications. What follows is a gentle introduction to
using this powerful API.&lt;/p&gt;
&lt;h2 id=&quot;getting-started-with-the-audiocontext&quot;&gt;Getting started with the AudioContext &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-intro/#getting-started-with-the-audiocontext&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;An AudioContext is for managing and playing all sounds. To produce
a sound using the Web Audio API, create one or more sound sources
and connect them to the sound destination provided by the &lt;code&gt;AudioContext&lt;/code&gt;
instance. This connection doesn&#39;t need to be direct, and can go through
any number of intermediate &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#AudioNode-section&quot; rel=&quot;noopener&quot;&gt;AudioNodes&lt;/a&gt; which act as processing
modules for the audio signal. This &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#ModularRouting-section&quot; rel=&quot;noopener&quot;&gt;routing&lt;/a&gt; is described in greater
detail at the Web Audio &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html&quot; rel=&quot;noopener&quot;&gt;specification&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A single instance of &lt;code&gt;AudioContext&lt;/code&gt; can support multiple sound inputs
and complex audio graphs, so we will only need one of these for each
audio application we create.&lt;/p&gt;
&lt;p&gt;The following snippet creates an &lt;code&gt;AudioContext&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;load&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; init&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AudioContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Web Audio API is not supported in this browser&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;For older WebKit-based browsers, use the &lt;code&gt;webkit&lt;/code&gt; prefix, as with
&lt;code&gt;webkitAudioContext&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Many of the interesting Web Audio API functionality such as creating
AudioNodes and decoding audio file data are methods of &lt;code&gt;AudioContext&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;loading-sounds&quot;&gt;Loading sounds &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-intro/#loading-sounds&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Web Audio API uses an AudioBuffer for short- to medium-length
sounds.  The basic approach is to use &lt;a href=&quot;https://developer.mozilla.org/XMLHttpRequest/Using_XMLHttpRequest&quot; rel=&quot;noopener&quot;&gt;XMLHttpRequest&lt;/a&gt; for
fetching sound files.&lt;/p&gt;
&lt;p&gt;The API supports loading audio file data in multiple formats, such as
WAV, MP3, AAC, OGG and &lt;a href=&quot;http://en.wikipedia.org/wiki/Audio_file_format&quot; rel=&quot;noopener&quot;&gt;others&lt;/a&gt;. Browser support for different
audio formats &lt;a href=&quot;https://developer.mozilla.org/Media_formats_supported_by_the_audio_and_video_elements#Browser_compatibility&quot; rel=&quot;noopener&quot;&gt;varies&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The following snippet demonstrates loading a sound sample:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; dogBarkingBuffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AudioContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadDogSound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;XMLHttpRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;GET&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;responseType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;arraybuffer&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Decode asynchronously&lt;/span&gt;&lt;br /&gt;    request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onload&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decodeAudioData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        dogBarkingBuffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; buffer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; onError&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The audio file data is binary (not text), so we set the &lt;code&gt;responseType&lt;/code&gt;
of the request to &lt;code&gt;&#39;arraybuffer&#39;&lt;/code&gt;. For more information about
&lt;code&gt;ArrayBuffers&lt;/code&gt;, see this &lt;a href=&quot;http://www.html5rocks.com/tutorials/file/xhr2/&quot; rel=&quot;noopener&quot;&gt;article about XHR2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once the (undecoded) audio file data has been received, it can be kept
around for later decoding, or it can be decoded right away using the
AudioContext &lt;code&gt;decodeAudioData()&lt;/code&gt; method. This method takes the
&lt;code&gt;ArrayBuffer&lt;/code&gt; of audio file data stored in &lt;code&gt;request.response&lt;/code&gt; and
decodes it asynchronously (not blocking the main JavaScript execution
thread).&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;decodeAudioData()&lt;/code&gt; is finished, it calls a callback function which
provides the decoded PCM audio data as an &lt;code&gt;AudioBuffer&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;playing-sounds&quot;&gt;Playing sounds &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-intro/#playing-sounds&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;figure&gt;
&lt;img alt=&quot;A simple audio graph&quot; decoding=&quot;async&quot; height=&quot;128&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 305px) 305px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ZC6uytCyqSnBjfdI6GAT.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ZC6uytCyqSnBjfdI6GAT.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ZC6uytCyqSnBjfdI6GAT.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ZC6uytCyqSnBjfdI6GAT.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ZC6uytCyqSnBjfdI6GAT.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ZC6uytCyqSnBjfdI6GAT.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ZC6uytCyqSnBjfdI6GAT.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ZC6uytCyqSnBjfdI6GAT.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ZC6uytCyqSnBjfdI6GAT.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ZC6uytCyqSnBjfdI6GAT.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/ZC6uytCyqSnBjfdI6GAT.png?auto=format&amp;w=610 610w&quot; width=&quot;305&quot; /&gt;
&lt;figcaption&gt;A simple audio graph&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Once one or more &lt;code&gt;AudioBuffers&lt;/code&gt; are loaded, then we&#39;re ready to play
sounds. Let&#39;s assume we&#39;ve just loaded an &lt;code&gt;AudioBuffer&lt;/code&gt; with the sound
of a dog barking and that the loading has finished. Then we can play
this buffer with a the following code.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AudioContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;playSound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createBufferSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// creates a sound source&lt;/span&gt;&lt;br /&gt;    source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; buffer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;                    &lt;span class=&quot;token comment&quot;&gt;// tell the source which sound to play&lt;/span&gt;&lt;br /&gt;    source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;       &lt;span class=&quot;token comment&quot;&gt;// connect the source to the context&#39;s destination (the speakers)&lt;/span&gt;&lt;br /&gt;    source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;noteOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;                          &lt;span class=&quot;token comment&quot;&gt;// play the source now&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This &lt;code&gt;playSound()&lt;/code&gt; function could be called every time somebody presses a key or
clicks something with the mouse.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;noteOn(time)&lt;/code&gt; function makes it easy to schedule precise sound
playback for games and other time-critical applications. However, to get
this scheduling working properly, ensure that your sound buffers are
pre-loaded.&lt;/p&gt;
&lt;h2 id=&quot;abstracting-the-web-audio-api&quot;&gt;Abstracting the Web Audio API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-intro/#abstracting-the-web-audio-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Of course, it would be better to create a more general loading system
which isn&#39;t hard-coded to loading this specific sound. There are many
approaches for dealing with the many short- to medium-length sounds that
an audio application or game would use–here&#39;s one way using a BufferLoader (not part of web standard).&lt;/p&gt;
&lt;p&gt;The following is an example of how you can use the &lt;code&gt;BufferLoader&lt;/code&gt; class.
Let&#39;s create two &lt;code&gt;AudioBuffers&lt;/code&gt;; and, as soon as they are loaded,
let&#39;s play them back at the same time.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;onload &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; init&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; bufferLoader&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AudioContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    bufferLoader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BufferLoader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token string&quot;&gt;&#39;../sounds/hyper-reality/br-jam-loop.wav&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token string&quot;&gt;&#39;../sounds/hyper-reality/laughter.wav&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    finishedLoading&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    bufferLoader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;finishedLoading&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;bufferList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Create two sources and play them both together.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; source1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createBufferSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; source2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createBufferSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    source1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; bufferList&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    source2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; bufferList&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    source1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    source2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    source1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;noteOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    source2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;noteOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;dealing-with-time-playing-sounds-with-rhythm&quot;&gt;Dealing with time: playing sounds with rhythm &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-intro/#dealing-with-time-playing-sounds-with-rhythm&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Web Audio API lets developers precisely schedule playback. To
demonstrate this, let&#39;s set up a simple rhythm track. Probably the
most widely known drumkit pattern is the following:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;A simple rock drum pattern&quot; decoding=&quot;async&quot; height=&quot;144&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 500px) 500px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/up8ymqCZECvUWIUdLN3x.png?auto=format&amp;w=1000 1000w&quot; width=&quot;500&quot; /&gt;
&lt;figcaption&gt;A simple rock drum pattern&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;in which a hihat is played every eighth note, and kick and snare are
played alternating every quarter, in 4/4 time.&lt;/p&gt;
&lt;p&gt;Supposing we have loaded the &lt;code&gt;kick&lt;/code&gt;, &lt;code&gt;snare&lt;/code&gt; and &lt;code&gt;hihat&lt;/code&gt; buffers, the
code to do this is simple:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; bar &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; bar &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; bar&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; time &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; startTime &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; bar &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; eighthNoteTime&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Play the bass (kick) drum on beats 1, 5&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;playSound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;kick&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;playSound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;kick&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; time &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; eighthNoteTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Play the snare drum on beats 3, 7&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;playSound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;snare&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; time &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; eighthNoteTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;playSound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;snare&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; time &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; eighthNoteTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Play the hi-hat every eighth note.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;playSound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hihat&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; time &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; eighthNoteTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Here, we make only one repeat instead of the unlimited loop we see in
the sheet music. The function &lt;code&gt;playSound&lt;/code&gt; is a method that plays a
buffer at a specified time, as follows:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;playSound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; time&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createBufferSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; buffer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;noteOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;changing-the-volume-of-a-sound&quot;&gt;Changing the volume of a sound &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-intro/#changing-the-volume-of-a-sound&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the most basic operations you might want to do to a sound is
change its volume. Using the Web Audio API, we can route our source to
its destination through an &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#AudioGainNode-section&quot; rel=&quot;noopener&quot;&gt;AudioGainNode&lt;/a&gt; in order to manipulate the
volume:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Audio graph with a gain node&quot; decoding=&quot;async&quot; height=&quot;145&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 405px) 405px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/13mtS9N4QlPK5riWY45G.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/13mtS9N4QlPK5riWY45G.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/13mtS9N4QlPK5riWY45G.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/13mtS9N4QlPK5riWY45G.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/13mtS9N4QlPK5riWY45G.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/13mtS9N4QlPK5riWY45G.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/13mtS9N4QlPK5riWY45G.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/13mtS9N4QlPK5riWY45G.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/13mtS9N4QlPK5riWY45G.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/13mtS9N4QlPK5riWY45G.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/13mtS9N4QlPK5riWY45G.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/13mtS9N4QlPK5riWY45G.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/13mtS9N4QlPK5riWY45G.png?auto=format&amp;w=810 810w&quot; width=&quot;405&quot; /&gt;
&lt;figcaption&gt;Audio graph with a gain node&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This connection setup can be achieved as follows:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Create a gain node.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; gainNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createGainNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Connect the source to the gain node.&lt;/span&gt;&lt;br /&gt;source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gainNode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Connect the gain node to the destination.&lt;/span&gt;&lt;br /&gt;gainNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;After the graph has been set up, you can programmatically change the
volume by manipulating the &lt;code&gt;gainNode.gain.value&lt;/code&gt; as follows:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Reduce the volume.&lt;/span&gt;&lt;br /&gt;gainNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;cross-fading-between-two-sounds&quot;&gt;Cross-fading between two sounds &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-intro/#cross-fading-between-two-sounds&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now, suppose we have a slightly more complex scenario, where we&#39;re
playing multiple sounds but want to cross fade between them. This is a
common case in a DJ-like application, where we have two turntables and
want to be able to pan from one sound source to another.&lt;/p&gt;
&lt;p&gt;This can be done with the following audio graph:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Audio graph with two sources connected through gain nodes&quot; decoding=&quot;async&quot; height=&quot;266&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 406px) 406px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qEZqDhwWU9F74BghKtZ6.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qEZqDhwWU9F74BghKtZ6.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qEZqDhwWU9F74BghKtZ6.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qEZqDhwWU9F74BghKtZ6.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qEZqDhwWU9F74BghKtZ6.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qEZqDhwWU9F74BghKtZ6.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qEZqDhwWU9F74BghKtZ6.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qEZqDhwWU9F74BghKtZ6.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qEZqDhwWU9F74BghKtZ6.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qEZqDhwWU9F74BghKtZ6.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qEZqDhwWU9F74BghKtZ6.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qEZqDhwWU9F74BghKtZ6.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/qEZqDhwWU9F74BghKtZ6.png?auto=format&amp;w=812 812w&quot; width=&quot;406&quot; /&gt;
&lt;figcaption&gt;Audio graph with two sources connected through gain nodes&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To set this up, we simply create two &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#AudioGainNode-section&quot; rel=&quot;noopener&quot;&gt;AudioGainNodes&lt;/a&gt;, and connect
each source through the nodes, using something like this function:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createBufferSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Create a gain node.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; gainNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createGainNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; buffer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Turn on looping.&lt;/span&gt;&lt;br /&gt;    source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loop &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Connect source to gain.&lt;/span&gt;&lt;br /&gt;    source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;gainNode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Connect gain to destination.&lt;/span&gt;&lt;br /&gt;    gainNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;gainNode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; gainNode&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;equal-power-crossfading&quot;&gt;Equal power crossfading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-intro/#equal-power-crossfading&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A naive linear crossfade approach exhibits a volume dip as you pan
between the samples.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;A linear crossfade&quot; decoding=&quot;async&quot; height=&quot;226&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 393px) 393px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/iB9cw4RuxbViCNeKNjAp.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/iB9cw4RuxbViCNeKNjAp.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/iB9cw4RuxbViCNeKNjAp.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/iB9cw4RuxbViCNeKNjAp.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/iB9cw4RuxbViCNeKNjAp.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/iB9cw4RuxbViCNeKNjAp.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/iB9cw4RuxbViCNeKNjAp.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/iB9cw4RuxbViCNeKNjAp.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/iB9cw4RuxbViCNeKNjAp.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/iB9cw4RuxbViCNeKNjAp.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/iB9cw4RuxbViCNeKNjAp.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/iB9cw4RuxbViCNeKNjAp.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/iB9cw4RuxbViCNeKNjAp.png?auto=format&amp;w=786 786w&quot; width=&quot;393&quot; /&gt;
&lt;figcaption&gt;A linear crossfade&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To address this issue, we use an equal power curve, in which the
corresponding gain curves are non-linear, and intersect at a higher
amplitude. This minimizes volume dips between audio regions, resulting
in a more even crossfade between regions that might be slightly
different in level.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;An equal power crossfade.&quot; decoding=&quot;async&quot; height=&quot;226&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 393px) 393px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/sosgEa1pg8gFYiIDj2xL.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/sosgEa1pg8gFYiIDj2xL.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/sosgEa1pg8gFYiIDj2xL.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/sosgEa1pg8gFYiIDj2xL.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/sosgEa1pg8gFYiIDj2xL.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/sosgEa1pg8gFYiIDj2xL.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/sosgEa1pg8gFYiIDj2xL.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/sosgEa1pg8gFYiIDj2xL.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/sosgEa1pg8gFYiIDj2xL.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/sosgEa1pg8gFYiIDj2xL.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/sosgEa1pg8gFYiIDj2xL.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/sosgEa1pg8gFYiIDj2xL.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/sosgEa1pg8gFYiIDj2xL.png?auto=format&amp;w=786 786w&quot; width=&quot;393&quot; /&gt;
&lt;figcaption&gt;An equal power crossfade&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;playlist-crossfading&quot;&gt;Playlist crossfading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-intro/#playlist-crossfading&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Another common crossfader application is for a music player application.
When a song changes, we want to fade the current track out, and fade the
new one in, to avoid a jarring transition. To do this, schedule a
crossfade into the future. While we could use &lt;code&gt;setTimeout&lt;/code&gt; to do this
scheduling, this is &lt;a href=&quot;http://stackoverflow.com/questions/2779154/understanding-javascript-timer-thread-issues&quot; rel=&quot;noopener&quot;&gt;not precise&lt;/a&gt;. With the Web Audio API, we
can use the &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#AudioParam-section&quot; rel=&quot;noopener&quot;&gt;AudioParam&lt;/a&gt; interface to schedule future values for
parameters such as the gain value of an &lt;code&gt;AudioGainNode&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Thus, given a playlist, we can transition between tracks by scheduling a
gain decrease on the currently playing track, and a gain increase on the
next one, both slightly before the current track finishes playing:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;playHelper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;bufferNow&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; bufferLater&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; playNow &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bufferNow&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; playNow&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; gainNode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; playNow&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gainNode&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; duration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; bufferNow&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;duration&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; currTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTime&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Fade the playNow track in.&lt;/span&gt;&lt;br /&gt;    gainNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;linearRampToValueAtTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; currTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    gainNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;linearRampToValueAtTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; currTime &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FADE_TIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Play the playNow track.&lt;/span&gt;&lt;br /&gt;    source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;noteOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// At the end of the track, fade it out.&lt;/span&gt;&lt;br /&gt;    gainNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;linearRampToValueAtTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; currTime &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; duration&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FADE_TIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    gainNode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gain&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;linearRampToValueAtTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; currTime &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; duration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Schedule a recursive track change with the tracks swapped.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; recurse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; arguments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;callee&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;recurse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bufferLater&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; bufferNow&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;duration &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FADE_TIME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The Web Audio API provides a convenient set of &lt;code&gt;RampToValue&lt;/code&gt; methods to
gradually change the value of a parameter, such as
&lt;code&gt;linearRampToValueAtTime&lt;/code&gt; and &lt;code&gt;exponentialRampToValueAtTime&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While the transition timing function can be picked from built-in linear
and exponential ones (as above), you can also specify your own value
curve via an array of values using the &lt;code&gt;setValueCurveAtTime&lt;/code&gt; function.&lt;/p&gt;
&lt;h2 id=&quot;applying-a-simple-filter-effect-to-a-sound&quot;&gt;Applying a simple filter effect to a sound &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-intro/#applying-a-simple-filter-effect-to-a-sound&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;figure&gt;
&lt;img alt=&quot;An audio graph with a BiquadFilterNode&quot; decoding=&quot;async&quot; height=&quot;145&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 464px) 464px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/NqA6kYKPeryYyb6Zrj57.png?auto=format&amp;w=928 928w&quot; width=&quot;464&quot; /&gt;
&lt;figcaption&gt;An audio graph with a BiquadFilterNode&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The Web Audio API lets you pipe sound from one audio node into another,
creating a potentially complex chain of processors to add complex
effects to your soundforms.&lt;/p&gt;
&lt;p&gt;One way to do this is to place &lt;a href=&quot;https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#BiquadFilterNode-section&quot; rel=&quot;noopener&quot;&gt;BiquadFilterNode&lt;/a&gt;s between your sound
source and destination. This type of audio node can do a variety of
low-order filters which can be used to build graphic equalizers and even
more complex effects, mostly to do with selecting which parts of the
frequency spectrum of a sound to emphasize and which to subdue.&lt;/p&gt;
&lt;p&gt;Supported types of filters include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Low pass filter&lt;/li&gt;
&lt;li&gt;High pass filter&lt;/li&gt;
&lt;li&gt;Band pass filter&lt;/li&gt;
&lt;li&gt;Low shelf filter&lt;/li&gt;
&lt;li&gt;High shelf filter&lt;/li&gt;
&lt;li&gt;Peaking filter&lt;/li&gt;
&lt;li&gt;Notch filter&lt;/li&gt;
&lt;li&gt;All pass filter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And all of the filters include parameters to specify some amount of
&lt;a href=&quot;http://en.wikipedia.org/wiki/Gain&quot; rel=&quot;noopener&quot;&gt;gain&lt;/a&gt;, the frequency at which to apply the filter, and a quality factor.
The low-pass filter keeps the lower frequency range, but discards high
frequencies. The break-off point is determined by the frequency value,
and the &lt;a href=&quot;http://en.wikipedia.org/wiki/Audio_filter#Self_oscillation&quot; rel=&quot;noopener&quot;&gt;Q factor&lt;/a&gt; is unitless, and determines the shape of the
graph. The gain only affects certain filters, such as the low-shelf and
peaking filters, and not this low-pass filter.&lt;/p&gt;
&lt;p&gt;Let&#39;s setup a simple low-pass filter to extract only the bases from a
sound sample:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Create the filter&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; filter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createBiquadFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Create the audio graph.&lt;/span&gt;&lt;br /&gt;source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Create and specify parameters for the low-pass filter.&lt;/span&gt;&lt;br /&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Low-pass filter. See BiquadFilterNode docs&lt;/span&gt;&lt;br /&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;frequency&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;440&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Set cutoff to 440 HZ&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Playback the sound.&lt;/span&gt;&lt;br /&gt;source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;noteOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In general, frequency controls need to be tweaked to work on a
logarithmic scale since human hearing itself works on the same principle
(that is, A4 is 440hz, and A5 is 880hz). For more details, see the
&lt;code&gt;FilterSample.changeFrequency&lt;/code&gt; function in the source code link above.&lt;/p&gt;
&lt;p&gt;Lastly, note that the sample code lets you connect and disconnect the
filter, dynamically changing the AudioContext graph. We can disconnect
AudioNodes from the graph by calling &lt;code&gt;node.disconnect(outputNumber)&lt;/code&gt;.
For example, to re-route the graph from going through a filter, to a
direct connection, we can do the following:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Disconnect the source and filter.&lt;/span&gt;&lt;br /&gt;source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;disconnect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;disconnect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Connect the source directly.&lt;/span&gt;&lt;br /&gt;source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;further-listening&quot;&gt;Further listening &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/webaudio-intro/#further-listening&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;ve covered the basics of the API, including loading and playing audio
samples. We&#39;ve built audio graphs with gain nodes and filters, and
scheduled sounds and audio parameter tweaks to enable some common sound
effects. At this point, you are ready to go and build some sweet web
audio applications!&lt;/p&gt;
&lt;p&gt;If you are seeking inspiration, many developers have already created
&lt;a href=&quot;http://chromium.googlecode.com/svn/trunk/samples/audio/samples.html&quot; rel=&quot;noopener&quot;&gt;great work&lt;/a&gt; using the Web Audio API. Some of my favorite
include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://audiojedit.herokuapp.com/&quot; rel=&quot;noopener&quot;&gt;AudioJedit&lt;/a&gt;, an in-browser sound splicing tool that uses
SoundCloud permalinks.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://labs.dinahmoe.com/ToneCraft/&quot; rel=&quot;noopener&quot;&gt;ToneCraft&lt;/a&gt;, a sound sequencer where sounds are created by
stacking 3D blocks.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://labs.dinahmoe.com/plink/&quot; rel=&quot;noopener&quot;&gt;Plink&lt;/a&gt;, a collaborative music-making game using Web Audio and Web
Sockets.&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Boris Smus</name>
    </author>
  </entry>
  
  <entry>
    <title>Multi-touch web development</title>
    <link href="https://web.dev/mobile-touch/"/>
    <updated>2011-08-21T00:00:00Z</updated>
    <id>https://web.dev/mobile-touch/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;introduction&quot;&gt;Introduction &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#introduction&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mobile devices such as smartphones and tablets usually have a capacitive
touch-sensitive screen to capture interactions made with the user&#39;s fingers. As
the mobile web evolves to enable increasingly sophisticated applications, web
developers need a way to handle these events. For example, nearly any
fast-paced game requires the player to press multiple buttons at once, which,
in the context of a touchscreen, implies multi-touch.&lt;/p&gt;
&lt;p&gt;Apple introduced their &lt;a href=&quot;http://developer.apple.com/library/safari/#documentation/UserExperience/Reference/TouchEventClassReference/TouchEvent/TouchEvent.html#//apple_ref/doc/uid/TP40009358&quot; rel=&quot;noopener&quot;&gt;touch events API&lt;/a&gt; in iOS 2.0. Android has been catching up to this de-facto
standard and closing the gap. Recently a W3C working group has come together to
work on this &lt;a href=&quot;http://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html&quot; rel=&quot;noopener&quot;&gt;touch events specification&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this article I’ll dive into the touch events API provided by iOS and
Android devices, as well as desktop Chrome on hardware that supports touch, and explore what sorts of applications you can build, present some
best practices, and cover useful techniques that make it easier to develop
touch-enabled applications.&lt;/p&gt;
&lt;h2 id=&quot;touch-events&quot;&gt;Touch events &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#touch-events&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Three basic touch events are outlined in the spec and implemented
widely across mobile devices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;touchstart&lt;/strong&gt;: a finger is placed on a DOM element.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;touchmove&lt;/strong&gt;: a finger is dragged along a DOM element.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;touchend&lt;/strong&gt;: a finger is removed from a DOM element.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each touch event includes three lists of touches:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;touches&lt;/strong&gt;: a list of all fingers currently on the screen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;targetTouches&lt;/strong&gt;: a list of fingers on the current DOM element.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;changedTouches&lt;/strong&gt;: a list of fingers involved in the current
event. For example, in a touchend event, this will be the finger that was removed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These lists consist of objects that contain touch information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;identifier&lt;/strong&gt;: a number that uniquely identifies the current finger in the touch session.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;target&lt;/strong&gt;: the DOM element that was the target of the action.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;client/page/screen coordinates&lt;/strong&gt;: where on the screen the action happened.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;radius coordinates and rotationAngle&lt;/strong&gt;: describe the ellipse that approximates finger shape.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;touch-enabled-apps&quot;&gt;Touch-enabled apps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#touch-enabled-apps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;touchstart&lt;/strong&gt;, &lt;strong&gt;touchmove&lt;/strong&gt;, and
&lt;strong&gt;touchend&lt;/strong&gt; events provide a rich enough feature set to support
virtually any kind of touch-based interaction – including all of the usual
multi-touch gestures like pinch-zoom, rotation, and so on.&lt;/p&gt;
&lt;p&gt;This snippet lets you drag a DOM element around using single-finger
touch:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; obj &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchmove&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// If there&#39;s exactly one finger inside this element&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;targetTouches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; touch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;targetTouches&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Place element where the finger is&lt;/span&gt;&lt;br /&gt;    obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; touch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pageX &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;px&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;top &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; touch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pageY &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;px&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Below is a &lt;a href=&quot;https://github.com/borismus/MagicTouch/blob/master/samples/tracker.html&quot; rel=&quot;noopener&quot;&gt;sample&lt;/a&gt;
that displays all current touches on the screen.  It’s useful just to get a
feeling for the responsiveness of the device.&lt;/p&gt;
&lt;figure&gt;
&lt;a href=&quot;https://github.com/borismus/MagicTouch/blob/master/samples/tracker.html&quot;&gt;
  &lt;img alt=&quot;Finger tracking.&quot; decoding=&quot;async&quot; height=&quot;217&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 650px) 650px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/Piarbck9MukTti9Kbadd.png?auto=format&amp;w=1300 1300w&quot; width=&quot;650&quot; /&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Setup canvas and expose context via ctx variable&lt;/span&gt;&lt;br /&gt;canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchmove&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; touch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;beginPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;arc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;touch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pageX&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; touch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pageY&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;demos&quot;&gt;Demos &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#demos&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A number of interesting multi-touch demos are already in the wild, such
as this &lt;a href=&quot;http://paulirish.com/demo/multi&quot; rel=&quot;noopener&quot;&gt;canvas-based drawing&lt;/a&gt; demo
by Paul Irish and others.&lt;/p&gt;
&lt;figure&gt;
&lt;a href=&quot;http://paulirish.com/demo/multi&quot;&gt;
  &lt;img alt=&quot;Drawing screenshot&quot; decoding=&quot;async&quot; height=&quot;244&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 650px) 650px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/6MOW324g39nsDYgL3IA3.png?auto=format&amp;w=1300 1300w&quot; width=&quot;650&quot; /&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;And &lt;a href=&quot;http://borismus.github.com/mobile-web-samples/browser-ninja/&quot; rel=&quot;noopener&quot;&gt;Browser Ninja&lt;/a&gt;, a tech
demo that is a Fruit Ninja clone using CSS3 transforms and transitions, as well as
canvas:&lt;/p&gt;
&lt;figure&gt;
&lt;a href=&quot;http://borismus.github.com/mobile-web-samples/browser-ninja/&quot;&gt;
  &lt;img alt=&quot;Browser ninja&quot; decoding=&quot;async&quot; height=&quot;336&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 650px) 650px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/C7cAmEUzjayqdfdwuhuk.png?auto=format&amp;w=1300 1300w&quot; width=&quot;650&quot; /&gt;
&lt;/a&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;best-practices&quot;&gt;Best practices &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#best-practices&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;prevent-zooming&quot;&gt;Prevent zooming &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#prevent-zooming&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Default settings don&#39;t work very well for multi-touch, since your swipes and
gestures are often associated with browser behavior, such as scrolling and
zooming.&lt;/p&gt;
&lt;p&gt;To disable zooming, setup your viewport so that it is not user scalable using
the following meta tag:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;meta name=&quot;viewport&quot; &lt;br /&gt;  content=&quot;width=device-width, initial-scale=1.0, user-scalable=no&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Check out &lt;a href=&quot;https://www.html5rocks.com/mobile/mobifying.html#toc-meta-viewport&quot; rel=&quot;noopener&quot;&gt;this mobile HTML5 article&lt;/a&gt; for more information on setting your viewport.&lt;/p&gt;
&lt;h3 id=&quot;prevent-scrolling&quot;&gt;Prevent scrolling &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#prevent-scrolling&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Some mobile devices have default behaviors for touchmove, such as the
classic iOS overscroll effect, which causes the view to bounce back when
scrolling exceeds the bounds of the content. This is confusing in many
multi-touch applications, and can easily be disabled:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchmove&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;render-carefully&quot;&gt;Render carefully &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#render-carefully&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you are writing a multi-touch application that involves complex
multi-finger gestures, be careful how you react to touch events, since you will
be handling so many at once. Consider the sample in the previous section that
draws all touches on the screen. You could draw as soon as there is a touch
input:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchmove&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;renderTouches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;But this technique does not scale with number of fingers on the screen.
Instead, you could track all of the fingers, and render in a loop to get far
better performance:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; touches &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchmove&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  touches &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Setup a 60fps timer&lt;/span&gt;&lt;br /&gt;timer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setInterval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;renderTouches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;touches&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; &lt;strong&gt;Tip:&lt;/strong&gt; setInterval is not great for animations, since it doesn&#39;t take into account the browser&#39;s own rendering loop. Modern desktop browsers provide &lt;a href=&quot;https://web.dev/tutorials/speed/html5/#toc-request-ani-frame&quot;&gt;requestAnimationFrame&lt;/a&gt;, which is a much better option for performance and battery life reasons. Once supported in mobile browsers, this will be the preferred way of doing things. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;make-use-of-targettouches-and-changedtouches&quot;&gt;Make use of targetTouches and changedTouches &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#make-use-of-targettouches-and-changedtouches&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Remember that event.touches is an array of &lt;strong&gt;ALL&lt;/strong&gt;
fingers in contact with the screen, not just the ones on the DOM element&#39;s
target. You might find it much more useful to use event.targetTouches or
event.changedTouches instead.&lt;/p&gt;
&lt;p&gt;Finally, since you are developing for mobile, you should be aware of
general mobile best practices, which are covered in &lt;a href=&quot;https://www.html5rocks.com/mobile/mobifying.html&quot; rel=&quot;noopener&quot;&gt;Eric Bidelman&#39;s article&lt;/a&gt;, as well as this &lt;a href=&quot;http://www.w3.org/TR/mwabp/&quot; rel=&quot;noopener&quot;&gt;W3C document&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;device-support&quot;&gt;Device support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#device-support&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Unfortunately, touch event implementations vary greatly in completeness and
quality. I wrote a &lt;a href=&quot;https://github.com/borismus/MagicTouch/blob/master/index.html&quot; rel=&quot;noopener&quot;&gt;diagnostics script&lt;/a&gt; that displays some basic information about the touch API
implementation, including which events are supported, and touchmove firing
resolution. I tested Android 2.3.3 on Nexus One and Nexus S hardware, Android
3.0.1 on Xoom, and iOS 4.2 on iPad and iPhone.&lt;/p&gt;
&lt;p&gt;In a nutshell, all tested browsers support the &lt;strong&gt;touchstart&lt;/strong&gt;,
&lt;strong&gt;touchend&lt;/strong&gt;, and &lt;strong&gt;touchmove&lt;/strong&gt; events.&lt;/p&gt;
&lt;p&gt;The spec provides three additional touch events, but no tested browsers
support them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;touchenter&lt;/strong&gt;: a moving finger enters a DOM element.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;touchleave&lt;/strong&gt;: a moving finger leaves a DOM element.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;touchcancel&lt;/strong&gt;: a touch is interrupted (implementation specific).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Within each touch list, the tested browsers also provide the
&lt;strong&gt;touches&lt;/strong&gt;, &lt;strong&gt;targetTouches&lt;/strong&gt; and
&lt;strong&gt;changedTouches&lt;/strong&gt; touch lists. However, no tested browsers
support radiusX, radiusY or rotationAngle, which specify the shape of the
finger touching the screen.&lt;/p&gt;
&lt;p&gt;During a touchmove, events fire at roughly 60 times a second across all
tested devices.&lt;/p&gt;
&lt;h3 id=&quot;android-233-nexus&quot;&gt;Android 2.3.3 (Nexus) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#android-233-nexus&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;On the Android Gingerbread Browser (tested on Nexus One and Nexus S), there
is no multi-touch support. This is a &lt;a href=&quot;http://code.google.com/p/android/issues/detail?id=11909&quot; rel=&quot;noopener&quot;&gt;known issue&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;android-301-xoom&quot;&gt;Android 3.0.1 (Xoom) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#android-301-xoom&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;On Xoom&#39;s browser, there is basic multi-touch support, but it only works on
a single DOM element. The browser does not correctly respond to two
simultaneous touches on different DOM elements. In other words, the following
will react to two simultaneous touches:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;obj1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchmove&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;targetTouches&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; touch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;targetTouches&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touched &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; touch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;identifier&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;But the following will not:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; objs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;obj1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; obj2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; objs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; obj &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; objs&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  obj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touchmove&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;targetTouches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;touched &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;targetTouches&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;identifier&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;ios-4x-ipad,-iphone&quot;&gt;iOS 4.x (iPad, iPhone) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#ios-4x-ipad,-iphone&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;iOS devices fully support multi-touch, are capable of tracking quite a few
fingers and provide a very responsive touch experience in the browser.&lt;/p&gt;
&lt;h2 id=&quot;developer-tools&quot;&gt;Developer tools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#developer-tools&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In mobile development, it&#39;s often easier to start prototyping on the desktop
and then tackle the mobile-specific parts on the devices you intend to support.
Multi-touch is one of those features that&#39;s difficult to test on the PC, since
most PCs don&#39;t have touch input.&lt;/p&gt;
&lt;p&gt;Having to test on mobile can lengthen your development cycle, since every
change you make needs to be pushed out to a server and then loaded on the
device. Then, once running, there’s little you can do to debug your
application, since tablets and smartphones lack web developer tooling.&lt;/p&gt;
&lt;p&gt;A solution to this problem is to simulate touch events on your development
machine. For single-touches, touch events can be simulated based on mouse
events. Multi-touch events can be simulated if you have a device with touch
input, such as a modern Apple MacBook.&lt;/p&gt;
&lt;h3 id=&quot;single-touch-events&quot;&gt;Single-touch events &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#single-touch-events&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you would like to simulate single-touch events on your desktop, Chrome provides touch event emulation from the developer tools.  Open up the Developer tools, then select the Settings gear, then &amp;quot;Overrides&amp;quot; or &amp;quot;Emulation&amp;quot;, and turn on &amp;quot;Emulate touch events&amp;quot;.&lt;/p&gt;
&lt;p&gt;For other browsers, you may wish to try out &lt;a href=&quot;http://www.vodori.com/blog/phantom-limb.html&quot; rel=&quot;noopener&quot;&gt;Phantom Limb&lt;/a&gt;, which
simulates touch events on pages and also gives a giant hand to boot.&lt;/p&gt;
&lt;p&gt;There&#39;s also the &lt;a href=&quot;https://github.com/dotmaster/Touchable-jQuery-Plugin&quot; rel=&quot;noopener&quot;&gt;Touchable&lt;/a&gt;
jQuery plugin that unifies touch and mouse events across platforms.&lt;/p&gt;
&lt;h3 id=&quot;multi-touch-events&quot;&gt;Multi-touch events &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mobile-touch/#multi-touch-events&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To enable your multi-touch web application to work in your browser on your
multi-touch trackpad (such as a Apple MacBook or MagicPad), I&#39;ve created the &lt;a href=&quot;http://github.com/borismus/MagicTouch&quot; rel=&quot;noopener&quot;&gt;MagicTouch.js polyfill&lt;/a&gt;. It
captures touch events from your trackpad and turns them into
standard-compatible touch events.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download and install the &lt;a href=&quot;https://github.com/fajran/npTuioClient&quot; rel=&quot;noopener&quot;&gt;npTuioClient NPAPI plugin&lt;/a&gt; into
~/Library/Internet Plug-Ins/.&lt;/li&gt;
&lt;li&gt;Download the &lt;a href=&quot;https://github.com/fajran/tongseng&quot; rel=&quot;noopener&quot;&gt;TongSeng TUIO app&lt;/a&gt; for Mac’s MagicPad and start
the server.&lt;/li&gt;
&lt;li&gt;Download &lt;a href=&quot;http://github.com/borismus/MagicTouch&quot; rel=&quot;noopener&quot;&gt;MagicTouch.js&lt;/a&gt;, a javascript library to
simulate spec-compatible touch events based on npTuioClient callbacks.&lt;/li&gt;
&lt;li&gt;Include the magictouch.js script and npTuioClient plugin in your
application as follows:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  ...&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/path/to/magictouch.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  ...&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;object&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;tuio&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;application/x-tuio&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    Touch input plugin failed to load!&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You may need to enable the plugin.&lt;/p&gt;
&lt;p&gt;A live demo with magictouch.js is available at &lt;a href=&quot;http://www.paulirish.com/demo/multi&quot; rel=&quot;noopener&quot;&gt;paulirish.com/demo/multi&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;6wL-pLX9Kq0&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;p&gt;I tested this approach only with Chrome 10, but it should work on other
modern browsers with only minor tweaks.&lt;/p&gt;
&lt;p&gt;If your computer does not have multi-touch input, you can simulate touch
events using other TUIO trackers, such as the &lt;a href=&quot;http://reactivision.sourceforge.net/&quot; rel=&quot;noopener&quot;&gt;reacTIVision&lt;/a&gt;. For
more information, see the &lt;a href=&quot;http://www.tuio.org/&quot; rel=&quot;noopener&quot;&gt;TUIO project page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note that your gestures might be identical to OS-level multi-touch gestures.
On OS X, you can configure system-wide events by going to the Trackpad
preference pane in System Preferences.&lt;/p&gt;
&lt;p&gt;As multi-touch features become more widely supported across mobile browsers,
I&#39;m very excited to see new web applications take full advantage of this rich
API.&lt;/p&gt;
</content>
    <author>
      <name>Boris Smus</name>
    </author>
  </entry>
  
  <entry>
    <title>Improving HTML5 Canvas performance</title>
    <link href="https://web.dev/canvas-performance/"/>
    <updated>2011-08-16T00:00:00Z</updated>
    <id>https://web.dev/canvas-performance/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;introduction&quot;&gt;Introduction &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#introduction&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;HTML5 canvas, which started as an experiment from Apple, is the most
widely supported standard for 2D &lt;a href=&quot;http://en.wikipedia.org/wiki/Immediate_mode&quot; rel=&quot;noopener&quot;&gt;immediate mode graphics&lt;/a&gt; on
the web.  Many developers now rely on it for a wide variety of
multimedia projects, visualizations, and games. However, as the
applications we build increase in complexity, developers inadvertently
hit the performance wall.
There’s a lot of disconnected wisdom about optimizing canvas
performance. This article aims to consolidate some of this body into a
more readily digestible resource for developers. This article includes
fundamental optimizations that apply to all computer graphics
environments as well as canvas-specific techniques that are subject to
change as canvas implementations improve. In particular, as browser
vendors implement canvas GPU acceleration, some of the outlined
performance techniques discussed will likely become less impactful. This
will be noted where appropriate.
Note that this article does not go into usage of HTML5 canvas. For that,
check out these &lt;a href=&quot;http://www.html5rocks.com/en/tutorials/#canvas&quot; rel=&quot;noopener&quot;&gt;canvas related articles&lt;/a&gt; on HTML5Rocks,
this &lt;a href=&quot;http://diveintohtml5.info/canvas.html&quot; rel=&quot;noopener&quot;&gt;chapter on the Dive into HTML5 site&lt;/a&gt; or the &lt;a href=&quot;https://developer.mozilla.org/canvas_tutorial&quot; rel=&quot;noopener&quot;&gt;MDN Canvas&lt;/a&gt;.
tutorial.&lt;/p&gt;
&lt;h2 id=&quot;performance-testing&quot;&gt;Performance testing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#performance-testing&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To address the quickly changing world of HTML5 canvas, &lt;a href=&quot;http://jsperf.com/&quot; rel=&quot;noopener&quot;&gt;JSPerf&lt;/a&gt;
(&lt;a href=&quot;http://jsperf.com/&quot; rel=&quot;noopener&quot;&gt;jsperf.com&lt;/a&gt;) tests verify that every proposed optimization
still works.  JSPerf is a web application that allows developers to
write JavaScript performance tests. Each test focuses on a result that
you’re trying to achieve (for example, clearing the canvas), and
includes multiple approaches that achieve the same result. JSPerf runs
each approach as many times as possible over a short time period and
gives a statistically meaningful number of iterations per second. Higher
scores are always better!
Visitors to a JSPerf performance test page can run the test on their
browser, and let JSPerf store the normalized test results on
&lt;a href=&quot;http://www.browserscope.org/&quot; rel=&quot;noopener&quot;&gt;Browserscope&lt;/a&gt; (&lt;a href=&quot;http://www.browserscope.org/&quot; rel=&quot;noopener&quot;&gt;browserscope.org&lt;/a&gt;). Because the optimization
techniques in this article are backed up by a JSPerf result, you can
return to see up-to-date information about whether or not the technique
still applies. I’ve written a small &lt;a href=&quot;https://github.com/borismus/jsperfview&quot; rel=&quot;noopener&quot;&gt;helper application&lt;/a&gt; that
renders these results as graphs, embedded throughout this article.&lt;/p&gt;
&lt;p&gt;All of the performance results in this article are keyed on the
browser version. This turns out to be a limitation, since we don&#39;t know what OS
the browser was running on, or even more importantly, whether or not HTML5
canvas was hardware accelerated when the performance test ran. You can find out
if Chrome&#39;s HTML5 canvas is hardware accelerated by visiting
&lt;code&gt;about:gpu&lt;/code&gt; in the address bar.&lt;/p&gt;
&lt;h2 id=&quot;pre-render-to-an-off-screen-canvas&quot;&gt;Pre-render to an off-screen canvas &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#pre-render-to-an-off-screen-canvas&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you’re re-drawing similar primitives to the screen across multiple
frames, as is often the case when writing a game, you can make large
performance gains by pre-rendering large parts of the scene.
Pre-rendering means using a separate off-screen canvas (or canvases) on
which to render temporary images, and then rendering the off-screen
canvases back onto the visible one.
For example, suppose you’re redrawing Mario running at 60 frames a
second. You could either redraw his hat, moustache, and “M” at each
frame, or pre-render Mario before running the animation.
no pre-rendering:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// canvas, context are defined&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;drawMario&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;render&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;pre-rendering:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; m_canvas &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvas&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;m_canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;m_canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; m_context &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; m_canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;2d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;drawMario&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m_context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;drawImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m_canvas&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;render&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Note the use of &lt;code&gt;requestAnimationFrame&lt;/code&gt;, which is discussed in more detail
in a later section.&lt;/p&gt;
&lt;p&gt;This technique is especially effective when the rendering operation
(&lt;code&gt;drawMario&lt;/code&gt; in the above example) is expensive. A good example of this is
text rendering, which is a very expensive operation.&lt;/p&gt;
&lt;p&gt;However, the poor performance of the
“pre-rendered loose” test case. When pre-rendering, it’s important to
make sure that your temporary canvas fits snugly around the image you
are drawing, otherwise the performance gain of off-screen rendering is
counterweighted by the performance loss of copying one large canvas onto
another (which varies as a function of source target size). A snug
canvas in the above test is simply smaller:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;can2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;can2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Compared to the loose one that yields poorer performance:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;can3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;can3&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;batch-canvas-calls-together&quot;&gt;Batch canvas calls together &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#batch-canvas-calls-together&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since drawing is an expensive operation, it’s more efficient to load the
drawing state machine with a long set of commands, and then have it dump
them all onto the video buffer.&lt;/p&gt;
&lt;p&gt;For example, when drawing multiple lines, it&#39;s more efficient to create one
path with all the lines in it and draw it with a single draw call. In other
words, rather than drawing separate lines:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; points&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; p1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; points&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; p2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; points&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;beginPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;moveTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lineTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;We get better performance from drawing a single polyline:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;beginPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; points&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; p1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; points&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; p2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; points&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;moveTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lineTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stroke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This applies to the world of HTML5 canvas as well. When drawing a
complex path, for example, it’s better to put all of the points into the
path, rather than rendering the segments separately (&lt;a href=&quot;http://jsperf.com/batching-line-drawing-calls/2&quot; rel=&quot;noopener&quot;&gt;jsperf&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Note, however, that with Canvas, there’s an important exception to this
rule: if the primitives involved in drawing the desired object have
small bounding boxes (for example, horizontal and vertical lines), it
may actually be more efficient to render them separately
(&lt;a href=&quot;http://jsperf.com/batching-line-drawing-calls&quot; rel=&quot;noopener&quot;&gt;jsperf&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id=&quot;avoid-unnecessary-canvas-state-changes&quot;&gt;Avoid unnecessary canvas state changes &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#avoid-unnecessary-canvas-state-changes&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The HTML5 canvas element is implemented on top of a state machine that
tracks things like fill and stroke styles, as well as previous points
that make up the current path. When trying to optimize graphics
performance, it’s tempting to focus solely on the graphics rendering.
However, manipulating the state machine can also incur a performance
overhead.
If you use multiple fill colors to render a scene, for example, it’s
cheaper to render by color rather than by placement on the canvas. To
render a pinstripe pattern, you could render a stripe, change colors,
render the next stripe, etc:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;STRIPES&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fillStyle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;COLOR1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;COLOR2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fillRect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;GAP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;GAP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;480&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Or render all odd stripes and then all even stripes:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fillStyle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;COLOR1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;STRIPES&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fillRect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;GAP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;GAP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;480&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fillStyle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;COLOR2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;STRIPES&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fillRect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;GAP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;GAP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;480&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As expected, the interlaced approach is slower because changing the
state machine is expensive.&lt;/p&gt;
&lt;h2 id=&quot;render-screen-differences-only,-not-the-whole-new-state&quot;&gt;Render screen differences only, not the whole new state &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#render-screen-differences-only,-not-the-whole-new-state&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As one would expect, rendering less on the screen is cheaper than
rendering more. If you have only incremental differences between
redraws, you can get a significant performance boost by just drawing the
difference. In other words, rather than clearing the whole screen before
drawing:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fillRect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Keep track of the drawn bounding box, and only clear that.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fillRect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;last&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; last&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; last&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; last&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;If you are familiar with computer graphics, you might also know this
technique as “redraw regions”, where the previously rendered bounding
box is saved, and then cleared on each rendering.
This technique also applies to pixel-based rendering contexts, as is
illustrated by this JavaScript &lt;a href=&quot;http://jsconf.eu/2010/speaker/lessons_learnt_pushing_browser.html&quot; rel=&quot;noopener&quot;&gt;Nintendo emulator talk&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;use-multiple-layered-canvases-for-complex-scenes&quot;&gt;Use multiple layered canvases for complex scenes &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#use-multiple-layered-canvases-for-complex-scenes&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As mentioned before, drawing large images is expensive and should be
avoided if possible. In addition to using another canvas for rendering
off screen, as illustrated in the pre-rendering section, we can also use
canvases layered on top of one another. By using transparency in the
foreground canvas, we can rely on the GPU to composite the alphas
together at render time. You might set this up as follows, with two
absolutely positioned canvases one on top of the other.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;canvas&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;bg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;640&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;480&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;z-index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;canvas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;canvas&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;fg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;640&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;480&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; absolute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;z-index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;canvas&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The advantage over having just one canvas here, is that when we draw or
clear the foreground canvas, we don’t ever modify the background. If
your game or multimedia app can be split up into a foreground and
background, consider rendering these on separate canvases to get a
significant performance boost.&lt;/p&gt;
&lt;p&gt;You can often take advantage of imperfect human perception and render
the background just once or at a slower speed compared to the foreground
(which is likely to occupy most of your user’s attention). For example,
you can render the foreground every time you render, but render the
background only every Nth frame.
Also note that this approach generalizes well for any number of
composite canvases if your application works better with a this sort of
structure.&lt;/p&gt;
&lt;h2 id=&quot;avoid-shadowblur&quot;&gt;Avoid shadowBlur &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#avoid-shadowblur&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like many other graphics environments, HTML5 canvas allows developers to
blur primitives, but this operation can be very expensive:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shadowOffsetX &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shadowOffsetY &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shadowBlur &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shadowColor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;rgba(255, 0, 0, 0.5)&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fillRect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;150&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;know-various-ways-to-clear-the-canvas&quot;&gt;Know various ways to clear the canvas &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#know-various-ways-to-clear-the-canvas&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since HTML5 canvas is an &lt;a href=&quot;http://en.wikipedia.org/wiki/Immediate_mode&quot; rel=&quot;noopener&quot;&gt;immediate mode&lt;/a&gt; drawing paradigm,
the scene needs to be redrawn explicitly at each frame. Because of this,
clearing the canvas is a fundamentally important operation for HTML5
canvas apps and games.
As mentioned in the &lt;strong&gt;Avoid canvas state changes&lt;/strong&gt; section,
clearing the entire canvas is often undesirable, but if you &lt;strong&gt;must&lt;/strong&gt; do
it, there are two options: calling &lt;code&gt;context.clearRect(0, 0, width, height)&lt;/code&gt; or using a canvas-specific hack to do it:
&lt;code&gt;canvas.width = canvas.width&lt;/code&gt;;.At the time of writing, &lt;code&gt;clearRect&lt;/code&gt; generally outperforms the width
reset version, but in some cases using the &lt;code&gt;canvas.width&lt;/code&gt; resetting hack
is significantly faster in Chrome 14&lt;/p&gt;
&lt;p&gt;Be careful with this tip, since it depends heavily on the underlying
canvas implementation and is very much subject to change. For more
information, see &lt;a href=&quot;http://simonsarris.com/blog/346-how-you-clear-your-canvas-matters&quot; rel=&quot;noopener&quot;&gt;Simon Sarris&#39; article on clearing the canvas&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;avoid-floating-point-coordinates&quot;&gt;Avoid floating point coordinates &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#avoid-floating-point-coordinates&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;HTML5 canvas supports sub-pixel rendering, and there’s no way to turn it
off. If you draw with coordinates that are not integers, it
automatically uses anti-aliasing to try to to smooth out the lines.
Here’s the visual effect, taken from
&lt;a href=&quot;http://sebleedelisle.com/2011/02/html5-canvas-sprite-optimisation&quot; rel=&quot;noopener&quot;&gt;this sub-pixel canvas performance article by Seb Lee-Delisle&lt;/a&gt;:&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Sub-pixel&quot; decoding=&quot;async&quot; height=&quot;250&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 350px) 350px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/8hVJtN0JP4ePNtD3XZWQ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/8hVJtN0JP4ePNtD3XZWQ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/8hVJtN0JP4ePNtD3XZWQ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/8hVJtN0JP4ePNtD3XZWQ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/8hVJtN0JP4ePNtD3XZWQ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/8hVJtN0JP4ePNtD3XZWQ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/8hVJtN0JP4ePNtD3XZWQ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/8hVJtN0JP4ePNtD3XZWQ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/8hVJtN0JP4ePNtD3XZWQ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/8hVJtN0JP4ePNtD3XZWQ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/8hVJtN0JP4ePNtD3XZWQ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/T4FyVKpzu4WKF1kBNvXepbi08t52/8hVJtN0JP4ePNtD3XZWQ.png?auto=format&amp;w=700 700w&quot; width=&quot;350&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;If the smoothed sprite is not the effect you seek, it can be much faster
to convert your coordinates to integers using &lt;code&gt;Math.floor&lt;/code&gt; or
&lt;code&gt;Math.round&lt;/code&gt; (&lt;a href=&quot;http://jsperf.com/drawimage-whole-pixels&quot; rel=&quot;noopener&quot;&gt;jsperf&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;To convert your floating point coordinates to integers, you can use
several clever techniques, the most performant of which involve adding
one half to the target number, and then performing bitwise operations on
the result to eliminate the fractional part.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// With a bitwise or.&lt;/span&gt;&lt;br /&gt;rounded &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; somenum&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// A double bitwise not.&lt;/span&gt;&lt;br /&gt;rounded &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;~&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; somenum&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Finally, a left bitwise shift.&lt;/span&gt;&lt;br /&gt;rounded &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; somenum&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The full performance breakdown is here
(&lt;a href=&quot;http://jsperf.com/math-round-vs-hack/3&quot; rel=&quot;noopener&quot;&gt;jsperf&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Note that this sort of optimization should no longer matter once canvas
implementations are GPU accelerated which will be able to quickly
render non-integer coordinates.&lt;/p&gt;
&lt;h2 id=&quot;optimize-your-animations-with-requestanimationframe&quot;&gt;Optimize your animations with &lt;code&gt;requestAnimationFrame&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#optimize-your-animations-with-requestanimationframe&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The relatively new &lt;code&gt;requestAnimationFrame&lt;/code&gt; API is the recommended way of
implementing interactive applications in the browser. Rather than
command the browser to render at a particular fixed tick rate, you
politely ask the browser to call your rendering routine and get called
when the browser is available. As a nice side effect, if the page is not
in the foreground, the browser is smart enough not to render.
The &lt;code&gt;requestAnimationFrame&lt;/code&gt; callback aims for a 60 FPS callback rate but
doesn’t guarantee it, so you need to keep track of how much time passed
since the last render. This can look something like the following:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; lastRender &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; delta &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; lastRender&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  x &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; delta&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  y &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; delta&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fillRect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;W&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;H&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;render&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Note that this use of &lt;code&gt;requestAnimationFrame&lt;/code&gt; applies to canvas as well as
other rendering technologies such as WebGL.
At the time of writing, this API is only available in Chrome, Safari and
Firefox, so you should use &lt;a href=&quot;http://paulirish.com/2011/requestanimationframe-for-smart-animating/&quot; rel=&quot;noopener&quot;&gt;this shim&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;most-mobile-canvas-implementations-are-slow&quot;&gt;Most mobile canvas implementations are slow &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#most-mobile-canvas-implementations-are-slow&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let’s talk about mobile. &lt;s&gt;Unfortunately at the time of writing, only iOS
5.0 beta running Safari 5.1 has GPU accelerated mobile canvas
implementation. Without GPU acceleration, mobile browsers don’t
generally have powerful enough CPUs for modern canvas-based
applications.&lt;/s&gt;  A number of the JSPerf tests described above perform an
order of magnitude worse on mobile compared to desktop, greatly
restricting the kinds of cross-device apps you can expect to
successfully run.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To recap, this article covered a comprehensive set of useful
optimization techniques that will help you develop performant HTML5
canvas-based projects. Now that you’ve learned something new here, go
forth and optimize your awesome creations. Or, if you don’t currently
have a game or application to optimize, check out
&lt;a href=&quot;http://www.chromeexperiments.com/&quot; rel=&quot;noopener&quot;&gt;Chrome Experiments&lt;/a&gt; and &lt;a href=&quot;http://creativejs.com/&quot; rel=&quot;noopener&quot;&gt;Creative JS&lt;/a&gt; for inspiration.&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;References &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/canvas-performance/#references&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Immediate_mode&quot; rel=&quot;noopener&quot;&gt;Immediate&lt;/a&gt; mode vs. &lt;a href=&quot;http://en.wikipedia.org/wiki/Retained_mode&quot; rel=&quot;noopener&quot;&gt;retained&lt;/a&gt; mode.&lt;/li&gt;
&lt;li&gt;Other HTML5Rocks &lt;a href=&quot;http://www.html5rocks.com/tutorials/#canvas&quot; rel=&quot;noopener&quot;&gt;canvas articles&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;http://diveintohtml5.info/canvas.html&quot; rel=&quot;noopener&quot;&gt;Canvas section&lt;/a&gt; of Dive into HTML5.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://jsperf.com/&quot; rel=&quot;noopener&quot;&gt;JSPerf&lt;/a&gt; lets developers create JS performance tests.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.browserscope.org/&quot; rel=&quot;noopener&quot;&gt;Browserscope&lt;/a&gt; stores browser performance data.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/borismus/jsperfview&quot; rel=&quot;noopener&quot;&gt;JSPerfView&lt;/a&gt;, which renders JSPerf tests as charts.&lt;/li&gt;
&lt;li&gt;Simon&#39;s &lt;a href=&quot;http://simonsarris.com/blog/346-how-you-clear-your-canvas-matters&quot; rel=&quot;noopener&quot;&gt;blog post&lt;/a&gt; on clearing the canvas, and his book, &lt;a href=&quot;http://www.amazon.com/gp/product/0672336278?tag=simonsarris-20&quot; rel=&quot;noopener&quot;&gt;HTML5 Unleashed&lt;/a&gt; which includes chapters on Canvas performance.&lt;/li&gt;
&lt;li&gt;Sebastian&#39;s &lt;a href=&quot;http://sebleedelisle.com/2011/02/html5-canvas-sprite-optimisation&quot; rel=&quot;noopener&quot;&gt;blog post&lt;/a&gt; on sub-pixel rendering performance.&lt;/li&gt;
&lt;li&gt;Ben&#39;s &lt;a href=&quot;http://jsconf.eu/2010/speaker/lessons_learnt_pushing_browser.html&quot; rel=&quot;noopener&quot;&gt;talk&lt;/a&gt; about optimizing a JS NES emulator.&lt;/li&gt;
&lt;li&gt;The new &lt;a href=&quot;http://www.html5rocks.com/en/tutorials/canvas/inspection/&quot; rel=&quot;noopener&quot;&gt;canvas profiler&lt;/a&gt; in the Chrome DevTools.&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Boris Smus</name>
    </author>
  </entry>
</feed>
