<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://web.dev/</id>
  <title>Thomas Steiner on web.dev</title>
  <updated>2026-04-15T23:21:06Z</updated>
  <author>
    <name>Thomas Steiner</name>
  </author>
  <link href="https://web.dev/authors/thomassteiner/feed.xml" rel="self"/>
  <link href="https://web.dev/"/>
  <icon>https://web-dev.imgix.net/image/admin/8PLpVmFef6mj72MVWeiN.jpg?auto=format</icon>
  <logo>https://web.dev/images/shared/rss-banner.png</logo>
  <subtitle>Tom is a Developer Advocate</subtitle>
  
  
  <entry>
    <title>Compiling mkbitmap to WebAssembly</title>
    <link href="https://web.dev/compiling-mkbitmap-to-webassembly/"/>
    <updated>2023-06-29T00:00:00Z</updated>
    <id>https://web.dev/compiling-mkbitmap-to-webassembly/</id>
    <content type="html" mode="escaped">&lt;p&gt;In &lt;a href=&quot;https://web.dev/what-is-webassembly/&quot;&gt;What is WebAssembly and where did it come from?&lt;/a&gt;, I explained how we ended up with the WebAssembly of today. In this article, I will show you my approach of compiling an existing C program, &lt;code&gt;mkbitmap&lt;/code&gt;, to WebAssembly. It&#39;s more complex than the &lt;a href=&quot;https://web.dev/what-is-webassembly/#compiling-to-webassembly&quot;&gt;hello world&lt;/a&gt; example, as it includes working with files, communicating between the WebAssembly and JavaScript lands, and drawing to a canvas, but it&#39;s still manageable enough to not overwhelm you.&lt;/p&gt;
&lt;p&gt;The article is written for web developers who want to learn WebAssembly and shows step-by-step how you might proceed if you wanted to compile something like &lt;code&gt;mkbitmap&lt;/code&gt; to WebAssembly. As a fair warning, not getting an app or library to compile on the first run is completely normal, which is why some of the steps described below ended up not working, so I needed to backtrack and try again differently. The article doesn&#39;t show the magic final compilation command as if it had dropped from the sky, but rather describes my actual progress, some frustrations included.&lt;/p&gt;
&lt;h2 id=&quot;about-mkbitmap&quot;&gt;About &lt;code&gt;mkbitmap&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#about-mkbitmap&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://potrace.sourceforge.net/mkbitmap.1.html&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;mkbitmap&lt;/code&gt;&lt;/a&gt; C program reads an image and applies one or more of the following operations to it, in this order: inversion, highpass filtering, scaling, and thresholding. Each operation can be individually controlled and turned on or off. The principal use of &lt;code&gt;mkbitmap&lt;/code&gt; is to convert color or grayscale images into a format suitable as input for other programs, particularly the tracing program &lt;a href=&quot;https://potrace.sourceforge.net/potrace.1.html&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;potrace&lt;/code&gt;&lt;/a&gt; that forms the basis of &lt;a href=&quot;https://web.dev/svgcode/&quot;&gt;SVGcode&lt;/a&gt;. As a preprocessing tool, &lt;code&gt;mkbitmap&lt;/code&gt; is particularly useful for converting scanned line art, such as cartoons or handwritten text, to high-resolution bilevel images.&lt;/p&gt;
&lt;p&gt;You use &lt;code&gt;mkbitmap&lt;/code&gt; by passing it a number of options and one or multiple file names. For all details, see the tool&#39;s &lt;a href=&quot;https://potrace.sourceforge.net/mkbitmap.1.html&quot; rel=&quot;noopener&quot;&gt;man page&lt;/a&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ mkbitmap &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;filename&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;figure&gt;
  &lt;img alt=&quot;Cartoon image in color.&quot; decoding=&quot;async&quot; height=&quot;235&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 239px) 239px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/IxTMIGl9KB2HQCI57VVk.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/IxTMIGl9KB2HQCI57VVk.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/IxTMIGl9KB2HQCI57VVk.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/IxTMIGl9KB2HQCI57VVk.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/IxTMIGl9KB2HQCI57VVk.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/IxTMIGl9KB2HQCI57VVk.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/IxTMIGl9KB2HQCI57VVk.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/IxTMIGl9KB2HQCI57VVk.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/IxTMIGl9KB2HQCI57VVk.png?auto=format&amp;w=478 478w&quot; width=&quot;239&quot; /&gt;
  &lt;figcaption&gt;The original image (&lt;a href=&quot;https://potrace.sourceforge.net/mkbitmap.html&quot;&gt;Source&lt;/a&gt;).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Cartoon image converted to grayscale after preprocessing.&quot; decoding=&quot;async&quot; height=&quot;470&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 478px) 478px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/EGN9P3V1AK8BneYniU1T.png?auto=format&amp;w=956 956w&quot; width=&quot;478&quot; /&gt;
  &lt;figcaption&gt;First scaled, then thresholded: &lt;code&gt;mkbitmap -f 2 -s 2 -t 0.48&lt;/code&gt; (&lt;a href=&quot;https://potrace.sourceforge.net/mkbitmap.html&quot;&gt;Source&lt;/a&gt;).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;get-the-code&quot;&gt;Get the code &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#get-the-code&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first step is to obtain the source code of &lt;code&gt;mkbitmap&lt;/code&gt;. You can find it on the &lt;a href=&quot;https://potrace.sourceforge.net/#downloading&quot; rel=&quot;noopener&quot;&gt;project&#39;s website&lt;/a&gt;. At the time of this writing, &lt;a href=&quot;https://potrace.sourceforge.net/download/1.16/potrace-1.16.tar.gz&quot; rel=&quot;noopener&quot;&gt;potrace-1.16.tar.gz&lt;/a&gt; is the latest version.&lt;/p&gt;
&lt;h2 id=&quot;compile-and-install-locally&quot;&gt;Compile and install locally &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#compile-and-install-locally&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The next step is to compile and install the tool locally to get a feeling for how it behaves. The &lt;a href=&quot;https://potrace.sourceforge.net/INSTALL&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;INSTALL&lt;/code&gt;&lt;/a&gt; file contains the following instructions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;cd&lt;/code&gt; to the directory containing the package&#39;s source code and type
&lt;code&gt;./configure&lt;/code&gt; to configure the package for your system.&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;configure&lt;/code&gt; might take a while.  While running, it prints
some messages telling which features it is checking for.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Type &lt;code&gt;make&lt;/code&gt; to compile the package.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optionally, type &lt;code&gt;make check&lt;/code&gt; to run any self-tests that come with
the package, generally using the just-built uninstalled binaries.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Type &lt;code&gt;make install&lt;/code&gt; to install the programs and any data files and
documentation.  When installing into a prefix owned by root, it is
recommended that the package be configured and built as a regular
user, and only the &lt;code&gt;make install&lt;/code&gt; phase executed with root
privileges.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By following these steps, you should end up with two executables, &lt;code&gt;potrace&lt;/code&gt; and &lt;code&gt;mkbitmap&lt;/code&gt;—the latter is the focus of this article. You can verify it worked correctly by running &lt;code&gt;mkbitmap --version&lt;/code&gt;. Here is the output of all four steps from my machine, heavily trimmed for brevity:&lt;/p&gt;
&lt;p&gt;Step 1, &lt;code&gt;./configure&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ./configure&lt;br /&gt;checking &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; a BSD-compatible install&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. /usr/bin/install -c&lt;br /&gt;checking whether build environment is sane&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token function&quot;&gt;yes&lt;/span&gt;&lt;br /&gt;checking &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; a thread-safe &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; -p&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. ./install-sh -c -d&lt;br /&gt;checking &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; gawk&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. no&lt;br /&gt;checking &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; mawk&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. no&lt;br /&gt;checking &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; nawk&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. no&lt;br /&gt;checking &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; awk&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token function&quot;&gt;awk&lt;/span&gt;&lt;br /&gt;checking whether &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt; sets &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;MAKE&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token function&quot;&gt;yes&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;config.status: executing libtool commands&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Step 2, &lt;code&gt;make&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt;&lt;br /&gt;/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-recursive&lt;br /&gt;Making all &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; src&lt;br /&gt;clang -DHAVE_CONFIG_H -I. -I&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;mv&lt;/span&gt; -f .deps/main.Tpo .deps/main.Po&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;make&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 punctuation&quot;&gt;]&lt;/span&gt;: Nothing to be &lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; `all-am&#39;.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Step 3, &lt;code&gt;make check&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt; check&lt;br /&gt;Making check &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; src&lt;br /&gt;make&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;: Nothing to be &lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;check&#39;.&lt;br /&gt;Making check &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; doc&lt;br /&gt;make&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;: Nothing to be &lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;check&lt;span class=&quot;token string&quot;&gt;&#39;.&lt;br /&gt;[…]&lt;br /&gt;============================================================================&lt;br /&gt;Testsuite summary for potrace 1.16&lt;br /&gt;============================================================================&lt;br /&gt;# TOTAL: 8&lt;br /&gt;# PASS:  8&lt;br /&gt;# SKIP:  0&lt;br /&gt;# XFAIL: 0&lt;br /&gt;# FAIL:  0&lt;br /&gt;# XPASS: 0&lt;br /&gt;# ERROR: 0&lt;br /&gt;============================================================================&lt;br /&gt;make[1]: Nothing to be done for `check-am&#39;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Step 4, &lt;code&gt;sudo make install&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;&lt;br /&gt;Password:&lt;br /&gt;Making &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; src&lt;br /&gt; &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;/./install-sh -c -d &lt;span class=&quot;token string&quot;&gt;&#39;/usr/local/bin&#39;&lt;/span&gt;&lt;br /&gt;  /bin/sh &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;/libtool   --mode&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;install /usr/bin/install -c potrace mkbitmap &lt;span class=&quot;token string&quot;&gt;&#39;/usr/local/bin&#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;br /&gt;make&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 punctuation&quot;&gt;]&lt;/span&gt;: Nothing to be &lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; `install-data-am&#39;.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;To check if it worked, run &lt;code&gt;mkbitmap --version&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ mkbitmap --version&lt;br /&gt;mkbitmap &lt;span class=&quot;token number&quot;&gt;1.16&lt;/span&gt;. Copyright &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;C&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2001&lt;/span&gt;-2019 Peter Selinger.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;If you get the version details, you have successfully compiled and installed &lt;code&gt;mkbitmap&lt;/code&gt;. Next, make the equivalent of these steps work with WebAssembly.&lt;/p&gt;
&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; If you followed the preceding platform compilation steps, run &lt;code&gt;make clean&lt;/code&gt; before proceeding with the WebAssembly compilation steps to remove previous compile artifacts. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;compile-mkbitmap-to-webassembly&quot;&gt;Compile &lt;code&gt;mkbitmap&lt;/code&gt; to WebAssembly &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#compile-mkbitmap-to-webassembly&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://emscripten.org/&quot; rel=&quot;noopener&quot;&gt;Emscripten&lt;/a&gt; is a tool for compiling C/C++ programs to WebAssembly. Emscripten&#39;s &lt;a href=&quot;https://emscripten.org/docs/compiling/Building-Projects.html&quot; rel=&quot;noopener&quot;&gt;Building Projects&lt;/a&gt; documentation states the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Building large projects with Emscripten is very easy. Emscripten provides two simple scripts that configure your makefiles to use &lt;code&gt;emcc&lt;/code&gt; as a drop-in replacement for &lt;code&gt;gcc&lt;/code&gt;—in most cases the rest of your project&#39;s current build system remains unchanged.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The documentation then goes on (a little edited for brevity):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Consider the case where you normally build with the following commands: &lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;./configure&lt;/code&gt; &lt;br /&gt;
&lt;code&gt;make&lt;/code&gt; &lt;br /&gt;
&lt;br /&gt;
To build with Emscripten, you would instead use the following commands: &lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;emconfigure ./configure&lt;/code&gt;&lt;br /&gt;
&lt;code&gt;emmake make&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So essentially &lt;code&gt;./configure&lt;/code&gt; becomes &lt;code&gt;emconfigure ./configure&lt;/code&gt; and &lt;code&gt;make&lt;/code&gt; becomes &lt;code&gt;emmake make&lt;/code&gt;. The following demonstrates how to do this with &lt;code&gt;mkbitmap&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Step 0, &lt;code&gt;make clean&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt; clean&lt;br /&gt;Making clean &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; src&lt;br /&gt; &lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; -f potrace mkbitmap&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt; -z &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; -f&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; -rf .libs _libs&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 function&quot;&gt;rm&lt;/span&gt; -f *.lo&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Step 1, &lt;code&gt;emconfigure ./configure&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ emconfigure ./configure&lt;br /&gt;configure: ./configure&lt;br /&gt;checking &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; a BSD-compatible install&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. /usr/bin/install -c&lt;br /&gt;checking whether build environment is sane&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token function&quot;&gt;yes&lt;/span&gt;&lt;br /&gt;checking &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; a thread-safe &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; -p&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. ./install-sh -c -d&lt;br /&gt;checking &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; gawk&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. no&lt;br /&gt;checking &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; mawk&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. no&lt;br /&gt;checking &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; nawk&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. no&lt;br /&gt;checking &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; awk&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;. &lt;span class=&quot;token function&quot;&gt;awk&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;config.status: executing libtool commands&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Step 2, &lt;code&gt;emmake make&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ emmake &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt;&lt;br /&gt;make: &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt;&lt;br /&gt;/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-recursive&lt;br /&gt;Making all &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; src&lt;br /&gt;/opt/homebrew/Cellar/emscripten/3.1.36/libexec/emcc -DHAVE_CONFIG_H -I. -I&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;     -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;mv&lt;/span&gt; -f .deps/main.Tpo .deps/main.Po&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;make&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 punctuation&quot;&gt;]&lt;/span&gt;: Nothing to be &lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; `all&#39;.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;If everything went well, there should now be &lt;code&gt;.wasm&lt;/code&gt; files somewhere in the directory. You can find them by running &lt;code&gt;find . -name &amp;quot;*.wasm&amp;quot;&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; -name &lt;span class=&quot;token string&quot;&gt;&quot;*.wasm&quot;&lt;/span&gt;&lt;br /&gt;./a.wasm&lt;br /&gt;./src/mkbitmap.wasm&lt;br /&gt;./src/potrace.wasm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The two last ones look promising, so &lt;code&gt;cd&lt;/code&gt; into the &lt;code&gt;src/&lt;/code&gt; directory. There are now also two new corresponding files, &lt;code&gt;mkbitmap&lt;/code&gt; and &lt;code&gt;potrace&lt;/code&gt;. For this article, only &lt;code&gt;mkbitmap&lt;/code&gt; is relevant. The fact that they don&#39;t have the &lt;code&gt;.js&lt;/code&gt; extension is a little confusing, but they are in fact JavaScript files, verifiable with a quick &lt;code&gt;head&lt;/code&gt; call:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; src/&lt;br /&gt;$ &lt;span class=&quot;token function&quot;&gt;head&lt;/span&gt; -n &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt; mkbitmap&lt;br /&gt;// include: shell.js&lt;br /&gt;// The Module object: Our interface to the outside world. We &lt;span class=&quot;token function&quot;&gt;import&lt;/span&gt;&lt;br /&gt;// and &lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; values on it. There are various ways Module can be used:&lt;br /&gt;// &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;. Not defined. We create it here&lt;br /&gt;// &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;. A &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; parameter, function&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Module&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;generated code&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 number&quot;&gt;3&lt;/span&gt;. pre-run appended it, var Module &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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;generated code&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;br /&gt;// &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;. External script tag defines var Module.&lt;br /&gt;// We need to check &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; Module already exists &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e.g. &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; above&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;.&lt;br /&gt;// Substitution will be replaced with actual code on later stage of the build,&lt;br /&gt;// this way Closure Compiler will not mangle it &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e.g. &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;. above&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;.&lt;br /&gt;// Note that &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; you want to run closure, and also to use Module&lt;br /&gt;// after the generated code, you will need to define   var Module &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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;// before the code. Then that object will be used &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; the code, and you&lt;br /&gt;// can &lt;span class=&quot;token builtin class-name&quot;&gt;continue&lt;/span&gt; to use Module afterwards as well.&lt;br /&gt;var Module &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; typeof Module &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;undefined&#39;&lt;/span&gt; ? Module &lt;span class=&quot;token builtin class-name&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;br /&gt;// --pre-jses are emitted after the Module integration code, so that they can&lt;br /&gt;// refer to Module &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;if they choose&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; they can also define Module&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Rename the JavaScript file to &lt;code&gt;mkbitmap.js&lt;/code&gt; by calling &lt;code&gt;mv mkbitmap mkbitmap.js&lt;/code&gt; (and &lt;code&gt;mv potrace potrace.js&lt;/code&gt; respectively if you want).
Now it&#39;s time for the first test to see if it worked by executing the file with Node.js on the command line by running &lt;code&gt;node mkbitmap.js --version&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; mkbitmap.js --version&lt;br /&gt;mkbitmap &lt;span class=&quot;token number&quot;&gt;1.16&lt;/span&gt;. Copyright &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;C&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2001&lt;/span&gt;-2019 Peter Selinger.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You have successfully compiled &lt;code&gt;mkbitmap&lt;/code&gt; to WebAssembly. Now the next step is to make it work in the browser.&lt;/p&gt;
&lt;h2 id=&quot;mkbitmap-with-webassembly-in-the-browser&quot;&gt;&lt;code&gt;mkbitmap&lt;/code&gt; with WebAssembly in the browser &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#mkbitmap-with-webassembly-in-the-browser&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Copy the &lt;code&gt;mkbitmap.js&lt;/code&gt; and the &lt;code&gt;mkbitmap.wasm&lt;/code&gt; files to a new directory called &lt;code&gt;mkbitmap&lt;/code&gt; and create an &lt;code&gt;index.html&lt;/code&gt; HTML boilerplate file that loads the &lt;code&gt;mkbitmap.js&lt;/code&gt; JavaScript file.&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;html&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;lang&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;en&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;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;charset&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;utf-8&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;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;mkbitmap&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;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;mkbitmap.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;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;html&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;Start a local server that serves the &lt;code&gt;mkbitmap&lt;/code&gt; directory and open it in your browser. You should see a prompt that asks you for input. This is as expected, since, &lt;a href=&quot;https://potrace.sourceforge.net/mkbitmap.1.html#:~:text=A%20filename%20of%20%22%2D%22%20may%20be%20given%20to%20specify%20reading%20from%20standard%20input&quot; rel=&quot;noopener&quot;&gt;according to the tool&#39;s man page&lt;/a&gt;, &lt;em&gt;&amp;quot;[i]f no filename arguments are given, then mkbitmap acts as a filter, reading from standard input&amp;quot;&lt;/em&gt;, which for Emscripten by default is a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/prompt&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;prompt()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&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; Serving from the &lt;code&gt;file:&lt;/code&gt; protocol doesn&#39;t work for &lt;code&gt;.wasm&lt;/code&gt; files, you really need to start a local server. &lt;/div&gt;&lt;/aside&gt;
&lt;img alt=&quot;The mkbitmap app showing a prompt that asks for input.&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DbDQJnxA4GylWKZcmlJK.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;prevent-automatic-execution&quot;&gt;Prevent automatic execution &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#prevent-automatic-execution&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To stop &lt;code&gt;mkbitmap&lt;/code&gt; from executing immediately and instead make it wait for user input, you need to understand Emscripten&#39;s &lt;a href=&quot;https://emscripten.org/docs/api_reference/module.html&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Module&lt;/code&gt;&lt;/a&gt; object. &lt;code&gt;Module&lt;/code&gt; is a global JavaScript object with attributes that Emscripten-generated code calls at various points in its execution.
You can provide an implementation of &lt;code&gt;Module&lt;/code&gt; to control the execution of code.
When an Emscripten application starts up, it looks at the values on the &lt;code&gt;Module&lt;/code&gt; object and applies them.&lt;/p&gt;
&lt;p&gt;In the case of &lt;code&gt;mkbitmap&lt;/code&gt;, set &lt;a href=&quot;https://emscripten.org/docs/api_reference/module.html#Module.noInitialRun&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Module.noInitialRun&lt;/code&gt;&lt;/a&gt; to &lt;code&gt;true&lt;/code&gt; to prevent the initial run that caused the prompt to appear. Create a script called &lt;code&gt;script.js&lt;/code&gt;, include it &lt;em&gt;before&lt;/em&gt; the &lt;code&gt;&amp;lt;script src=&amp;quot;mkbitmap.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt; in &lt;code&gt;index.html&lt;/code&gt; and add the following code to &lt;code&gt;script.js&lt;/code&gt;. When you now reload the app, the prompt should be gone.&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; Module &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 comment&quot;&gt;// Don&#39;t run main() at page load&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;noInitialRun&lt;/span&gt;&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 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;create-a-modular-build-with-some-more-build-flags&quot;&gt;Create a modular build with some more build flags &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#create-a-modular-build-with-some-more-build-flags&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To provide input to the app, you can use Emscripten&#39;s &lt;a href=&quot;https://emscripten.org/docs/api_reference/Filesystem-API.html&quot; rel=&quot;noopener&quot;&gt;file system&lt;/a&gt; support in &lt;code&gt;Module.FS&lt;/code&gt;. The &lt;a href=&quot;https://emscripten.org/docs/api_reference/Filesystem-API.html#including-file-system-support&quot; rel=&quot;noopener&quot;&gt;Including File System Support&lt;/a&gt; section of the documentation states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Emscripten decides whether to include file system support automatically. Many programs don&#39;t need files, and file system support is not negligible in size, so Emscripten avoids including it when it doesn&#39;t see a reason to. That means that if your C/C++ code does not access files, then the &lt;code&gt;FS&lt;/code&gt; object and other file system APIs will not be included in the output. And, on the other hand, if your C/C++ code does use files, then file system support will be automatically included.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Unfortunately &lt;code&gt;mkbitmap&lt;/code&gt; is one of the cases where Emscripten does not automatically include file system support, so you need to explicitly tell it to do so. This means you need to follow the &lt;code&gt;emconfigure&lt;/code&gt; and &lt;code&gt;emmake&lt;/code&gt; steps described previously, with a couple more flags set via a &lt;code&gt;CFLAGS&lt;/code&gt; argument. The following flags may come in useful for other projects, too.&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; When looking at the following commands, you might wonder about the missing space after &lt;code&gt;-s&lt;/code&gt; that you may see in other online tutorials. Some &lt;code&gt;-s&lt;/code&gt; options may require quoting, or the space between &lt;code&gt;-s&lt;/code&gt; and the next argument may confuse &lt;code&gt;CMake&lt;/code&gt;. To avoid those problems, I recommend you generally use the &lt;code&gt;-sX=Y&lt;/code&gt; notation, that is, without a space. &lt;/div&gt;&lt;/aside&gt;
&lt;ul&gt;
&lt;li&gt;Set &lt;a href=&quot;https://github.com/emscripten-core/emscripten/blob/fd9b4862cd3729b58ecc0f26ce03f9b9513615b0/src/settings.js#L918-L926&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;-sFILESYSTEM=1&lt;/code&gt;&lt;/a&gt; so file system support is included.&lt;/li&gt;
&lt;li&gt;Set &lt;a href=&quot;https://github.com/emscripten-core/emscripten/blob/fd9b4862cd3729b58ecc0f26ce03f9b9513615b0/src/settings.js#L862-L869&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;-sEXPORTED_RUNTIME_METHODS=FS,callMain&lt;/code&gt;&lt;/a&gt; so &lt;code&gt;Module.FS&lt;/code&gt; and &lt;code&gt;Module.callMain&lt;/code&gt; are exported.&lt;/li&gt;
&lt;li&gt;Set &lt;a href=&quot;https://github.com/emscripten-core/emscripten/blob/fd9b4862cd3729b58ecc0f26ce03f9b9513615b0/src/settings.js#L1162-L1224&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;-sMODULARIZE=1&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://github.com/emscripten-core/emscripten/blob/fd9b4862cd3729b58ecc0f26ce03f9b9513615b0/src/settings.js#L1226-L1229&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;-sEXPORT_ES6&lt;/code&gt;&lt;/a&gt; to generate a modern ES6 module.&lt;/li&gt;
&lt;li&gt;Set &lt;a href=&quot;https://github.com/emscripten-core/emscripten/blob/fd9b4862cd3729b58ecc0f26ce03f9b9513615b0/src/settings.js#L70-L74&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;-sINVOKE_RUN=0&lt;/code&gt;&lt;/a&gt; to prevent the initial run that caused the prompt to appear.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, in this particular case, you need to set the &lt;a href=&quot;https://www.gnu.org/software/autoconf/manual/autoconf-2.69/html_node/Hosts-and-Cross_002dCompilation.html&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;--host&lt;/code&gt;&lt;/a&gt; flag to &lt;code&gt;wasm32&lt;/code&gt; to tell the &lt;code&gt;configure&lt;/code&gt; script that you are compiling for WebAssembly.&lt;/p&gt;
&lt;p&gt;The final &lt;code&gt;emconfigure&lt;/code&gt; command looks like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ emconfigure ./configure --host&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;wasm32 &lt;span class=&quot;token assign-left variable&quot;&gt;CFLAGS&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;-sFILESYSTEM=1 -sEXPORTED_RUNTIME_METHODS=FS,callMain -sMODULARIZE=1 -sEXPORT_ES6 -sINVOKE_RUN=0&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Do not forget to run &lt;code&gt;emmake make&lt;/code&gt; again and copy the freshly created files over to the &lt;code&gt;mkbitmap&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;Modify &lt;code&gt;index.html&lt;/code&gt; so that it only loads the ES module &lt;code&gt;script.js&lt;/code&gt;, from which you then import the &lt;code&gt;mkbitmap.js&lt;/code&gt; module.&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;html&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;lang&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;en&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;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;charset&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;utf-8&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;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;mkbitmap&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;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 comment&quot;&gt;&amp;lt;!-- No longer load `mkbitmap.js` here --&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;script.js&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;module&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;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;html&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;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;// This is `script.js`.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; loadWASM &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./mkbitmap.js&#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 keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;const&lt;/span&gt; Module &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadWASM&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;  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;Module&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 function&quot;&gt;run&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;When you open the app now in the browser, you should see the &lt;code&gt;Module&lt;/code&gt; object logged to the DevTools console, and the prompt is gone, since the &lt;code&gt;main()&lt;/code&gt; function of &lt;code&gt;mkbitmap&lt;/code&gt; is no longer called at the start.&lt;/p&gt;
&lt;img alt=&quot;The mkbitmap app with a white screen, showing the Module object logged to the DevTools console.&quot; decoding=&quot;async&quot; height=&quot;502&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VDD7kemQvnMsC7vsGnEr.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;manually-execute-the-main-function&quot;&gt;Manually execute the main function &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#manually-execute-the-main-function&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The next step is to manually call &lt;code&gt;mkbitmap&lt;/code&gt;&#39;s &lt;code&gt;main()&lt;/code&gt; function by running &lt;code&gt;Module.callMain()&lt;/code&gt;. The &lt;code&gt;callMain()&lt;/code&gt; function takes an array of arguments, which match one-by-one what you would pass on the command line. If on the command line you would run &lt;code&gt;mkbitmap -v&lt;/code&gt;, you would call &lt;code&gt;Module.callMain([&#39;-v&#39;])&lt;/code&gt; in the browser. This logs the &lt;code&gt;mkbitmap&lt;/code&gt; version number to the DevTools console.&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;// This is `script.js`.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; loadWASM &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./mkbitmap.js&#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 keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;const&lt;/span&gt; Module &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadWASM&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;  Module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;callMain&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 string&quot;&gt;&#39;-v&#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 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 function&quot;&gt;run&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;img alt=&quot;The mkbitmap app with a white screen, showing the mkbitmap version number logged to the DevTools console.&quot; decoding=&quot;async&quot; height=&quot;502&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/i4MTQgb47ColZt4pcAqr.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;redirect-the-standard-output&quot;&gt;Redirect the standard output &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#redirect-the-standard-output&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The standard output (&lt;code&gt;stdout&lt;/code&gt;) by default is the console. However, you can redirect it to something else, for example, a function that stores the output to a variable. This means you can add the output to the HTML by setting the &lt;a href=&quot;https://emscripten.org/docs/api_reference/module.html#Module.print&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Module.print&lt;/code&gt;&lt;/a&gt; property.&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;// This is `script.js`.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; loadWASM &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./mkbitmap.js&#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 keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;let&lt;/span&gt; consoleOutput &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Powered by &#39;&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;const&lt;/span&gt; Module &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadWASM&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-variable function&quot;&gt;print&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 parameter&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consoleOutput &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; text&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;  Module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;callMain&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 string&quot;&gt;&#39;-v&#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 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;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; consoleOutput&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 function&quot;&gt;run&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;img alt=&quot;The mkbitmap app showing the mkbitmap version number.&quot; decoding=&quot;async&quot; height=&quot;502&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/61iaKchi6p0NmbfipNhO.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;get-the-input-file-into-the-memory-file-system&quot;&gt;Get the input file into the memory file system &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#get-the-input-file-into-the-memory-file-system&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To get the input file into the memory file system, you need the equivalent of &lt;code&gt;mkbitmap filename&lt;/code&gt; on the command line. To understand how I approach this, first some background on how &lt;code&gt;mkbitmap&lt;/code&gt; expects its input and creates its output.&lt;/p&gt;
&lt;p&gt;Supported input formats of &lt;code&gt;mkbitmap&lt;/code&gt; are &lt;a href=&quot;https://en.wikipedia.org/wiki/Netpbm&quot; rel=&quot;noopener&quot;&gt;PNM&lt;/a&gt; (&lt;a href=&quot;https://en.wikipedia.org/wiki/Netpbm#PBM_example&quot; rel=&quot;noopener&quot;&gt;PBM&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Netpbm#PGM_example&quot; rel=&quot;noopener&quot;&gt;PGM&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Netpbm#PPM_example&quot; rel=&quot;noopener&quot;&gt;PPM&lt;/a&gt;) and &lt;a href=&quot;https://en.wikipedia.org/wiki/BMP_file_format&quot; rel=&quot;noopener&quot;&gt;BMP&lt;/a&gt;. The output formats are PBM for bitmaps, and PGM for graymaps. If a &lt;a href=&quot;https://potrace.sourceforge.net/mkbitmap.1.html#:~:text=Input/output%20options%3A-,filename,the%20input%20filename%20by%20changing%20its%20suffix%20to%20%22.pbm%22%20or%20%22.pgm%22.,-If%20the%20name&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;filename&lt;/code&gt;&lt;/a&gt; argument is given, &lt;code&gt;mkbitmap&lt;/code&gt; will by default create an output file whose name is obtained from the input file name by changing its suffix to &lt;a href=&quot;https://en.wikipedia.org/wiki/Netpbm#File_formats&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;.pbm&lt;/code&gt;&lt;/a&gt;. For example, for the input file name &lt;code&gt;example.bmp&lt;/code&gt;, the output file name would be &lt;code&gt;example.pbm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Emscripten provides a virtual file system that simulates the local file system, so that native code using synchronous file APIs can be compiled and run with little or no change.
For &lt;code&gt;mkbitmap&lt;/code&gt; to read an input file as if it was passed as a &lt;code&gt;filename&lt;/code&gt; command line argument, you need to use the &lt;a href=&quot;https://emscripten.org/docs/api_reference/Filesystem-API.html#id2&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FS&lt;/code&gt;&lt;/a&gt; object that Emscripten provides.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;FS&lt;/code&gt; object is backed by an in-memory file system (commonly referred to as &lt;a href=&quot;https://emscripten.org/docs/api_reference/Filesystem-API.html#memfs&quot; rel=&quot;noopener&quot;&gt;MEMFS&lt;/a&gt;) and has a &lt;a href=&quot;https://emscripten.org/docs/api_reference/Filesystem-API.html#FS.writeFile&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;writeFile()&lt;/code&gt;&lt;/a&gt; function that you use to write files to the virtual file system. You use &lt;code&gt;writeFile()&lt;/code&gt; as shown in the following code sample.&lt;/p&gt;
&lt;p&gt;To verify the file write operation worked, run the &lt;code&gt;FS&lt;/code&gt; object&#39;s &lt;a href=&quot;https://emscripten.org/docs/api_reference/Filesystem-API.html#FS.readdir&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;readdir()&lt;/code&gt;&lt;/a&gt; function with the parameter &lt;code&gt;&#39;/&#39;&lt;/code&gt;. You will see &lt;code&gt;example.bmp&lt;/code&gt; and a number of default files that &lt;a href=&quot;https://emscripten.org/docs/api_reference/Filesystem-API.html#file-system-api&quot; rel=&quot;noopener&quot;&gt;are always created automatically&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note that the previous call to &lt;code&gt;Module.callMain([&#39;-v&#39;])&lt;/code&gt; for printing the version number was removed. This is due to the fact that &lt;code&gt;Module.callMain()&lt;/code&gt; is a function that generally expects to only be run once.&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;// This is `script.js`.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; loadWASM &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./mkbitmap.js&#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 keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;const&lt;/span&gt; Module &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadWASM&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;const&lt;/span&gt; buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://example.com/example.bmp&#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 function&quot;&gt;then&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 parameter&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;arrayBuffer&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;  Module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;example.bmp&#39;&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;Uint8Array&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;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;Module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readdir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/&#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 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 function&quot;&gt;run&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;img alt=&quot;The mkbitmap app showing an array of files in the memory file system, including example.bmp.&quot; decoding=&quot;async&quot; height=&quot;386&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/HkJ5LUs5qcpP7Fwtu5iU.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;first-actual-execution&quot;&gt;First actual execution &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#first-actual-execution&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With everything in place, execute &lt;code&gt;mkbitmap&lt;/code&gt; by running &lt;code&gt;Module.callMain([&#39;example.bmp&#39;])&lt;/code&gt;. Log the contents of the MEMFS&#39; &lt;code&gt;&#39;/&#39;&lt;/code&gt; folder, and you should see the newly created &lt;code&gt;example.pbm&lt;/code&gt; output file next to the &lt;code&gt;example.bmp&lt;/code&gt; input file.&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;// This is `script.js`.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; loadWASM &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./mkbitmap.js&#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 keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;const&lt;/span&gt; Module &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadWASM&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;const&lt;/span&gt; buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://example.com/example.bmp&#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 function&quot;&gt;then&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 parameter&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;arrayBuffer&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;  Module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;example.bmp&#39;&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;Uint8Array&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;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  Module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;callMain&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 string&quot;&gt;&#39;example.bmp&#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 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;Module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readdir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/&#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 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 function&quot;&gt;run&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;img alt=&quot;The mkbitmap app showing an array of files in the memory file system, including example.bmp and example.pbm.&quot; decoding=&quot;async&quot; height=&quot;502&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/vGFJy0pHyNPxdUUPdIcZ.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;get-the-output-file-out-of-the-memory-file-system&quot;&gt;Get the output file out of the memory file system &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#get-the-output-file-out-of-the-memory-file-system&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;FS&lt;/code&gt; object&#39;s &lt;a href=&quot;https://emscripten.org/docs/api_reference/Filesystem-API.html#FS.readFile&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;readFile()&lt;/code&gt;&lt;/a&gt; function enables getting the &lt;code&gt;example.pbm&lt;/code&gt; created in the last step out of the memory file system. The function returns a &lt;code&gt;Uint8Array&lt;/code&gt; that you convert to a &lt;code&gt;File&lt;/code&gt; object and save to disk, as browsers don&#39;t generally support &lt;a href=&quot;https://en.wikipedia.org/wiki/Netpbm#File_formats&quot; rel=&quot;noopener&quot;&gt;PBM&lt;/a&gt; files for direct in-browser viewing.
(There are more elegant ways to &lt;a href=&quot;https://web.dev/patterns/files/save-a-file/&quot;&gt;save a file&lt;/a&gt;, but using a dynamically created &lt;code&gt;&amp;lt;a download&amp;gt;&lt;/code&gt; is the most widely supported one.) Once the file is saved, you can open it in your favorite image viewer.&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;// This is `script.js`.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; loadWASM &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./mkbitmap.js&#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 keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;const&lt;/span&gt; Module &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadWASM&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;const&lt;/span&gt; buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://example.com/example.bmp&#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 function&quot;&gt;then&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 parameter&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;arrayBuffer&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;  Module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;example.bmp&#39;&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;Uint8Array&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;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  Module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;callMain&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 string&quot;&gt;&#39;example.bmp&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;example.pbm&#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;encoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;binary&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &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;File&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;output&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 string&quot;&gt;&#39;example.pbm&#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;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/x-portable-bitmap&#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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; a &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;a&#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;  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&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;  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;download &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&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;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;run&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;img alt=&quot;macOS Finder with a preview of the input .bmp file and the output .pbm file.&quot; decoding=&quot;async&quot; height=&quot;519&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/RstHspfwZIn7FW9aetVW.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;add-an-interactive-ui&quot;&gt;Add an interactive UI &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#add-an-interactive-ui&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To this point, the input file is hardcoded and &lt;code&gt;mkbitmap&lt;/code&gt; runs with &lt;a href=&quot;https://potrace.sourceforge.net/mkbitmap.1.html#:~:text=Normally%2C%20the%20following%20options%20are%20preselected%20by%20default%3A%20%2Df%204%20%2Ds%202%20%2D3%20%2Dt%200.45.&quot; rel=&quot;noopener&quot;&gt;default parameters&lt;/a&gt;. The final step is to let the user dynamically select an input file, tweak the &lt;code&gt;mkbitmap&lt;/code&gt; parameters, and then run the tool with the selected options.&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;// Corresponds to `mkbitmap -o output.pbm input.bmp -s 8 -3 -f 4 -t 0.45`.&lt;/span&gt;&lt;br /&gt;Module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;callMain&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 string&quot;&gt;&#39;-o&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;output.pbm&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;input.bmp&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;-s&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;8&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;-3&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;-f&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;4&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;-t&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;0.45&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The PBM image format is not particularly hard to parse, so with &lt;a href=&quot;https://github.com/megawac/pbm-formatter&quot; rel=&quot;noopener&quot;&gt;some JavaScript code&lt;/a&gt;, you could even show a preview of the output image. See the &lt;a href=&quot;https://glitch.com/edit/#!/mkbitmap&quot; rel=&quot;noopener&quot;&gt;source code&lt;/a&gt; of the embedded &lt;a href=&quot;https://mkbitmap.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt; below for one way to do this.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 950px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/mkbitmap?attributionHidden=true&amp;sidebarCollapsed=true&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;mkbitmap on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Congratulations, you have successfully compiled &lt;code&gt;mkbitmap&lt;/code&gt; to WebAssembly and made it work in the browser! There were some dead ends and you had to compile the tool more than once until it worked, but as I wrote above, that&#39;s part of the experience. Also remember the &lt;a href=&quot;https://stackoverflow.com/questions/tagged/webassembly&quot; rel=&quot;noopener&quot;&gt;StackOverflow&#39;s &lt;code&gt;webassembly&lt;/code&gt; tag&lt;/a&gt; if you get stuck. Happy compiling!&lt;/p&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by &lt;a href=&quot;https://www.linkedin.com/in/samclegg/&quot; rel=&quot;noopener&quot;&gt;Sam Clegg&lt;/a&gt; and &lt;a href=&quot;https://rachelandrew.co.uk/&quot; rel=&quot;noopener&quot;&gt;Rachel Andrew&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>What is WebAssembly and where did it come from?</title>
    <link href="https://web.dev/what-is-webassembly/"/>
    <updated>2023-06-29T00:00:00Z</updated>
    <id>https://web.dev/what-is-webassembly/</id>
    <content type="html" mode="escaped">&lt;p&gt;Ever since the web became a platform not just for documents but also for apps, some of the most advanced apps have pushed web browsers to their limits. The approach of going &amp;quot;closer to the metal&amp;quot; by interfacing with lower-level languages in order to improve performance is encountered in many higher-level languages. As an example, Java has the &lt;a href=&quot;https://web.archive.org/web/20080330002620/http://today.java.net/pub/a/today/2006/10/19/invoking-assembly-language-from-java.html&quot; rel=&quot;noopener&quot;&gt;Java Native Interface&lt;/a&gt;. For JavaScript, this lower-level language is WebAssembly. In this article, you will discover what assembly language is, and why it can be useful on the web, then learn how WebAssembly was created via the interim solution of asm.js.&lt;/p&gt;
&lt;h2 id=&quot;assembly-language&quot;&gt;Assembly language &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/what-is-webassembly/#assembly-language&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Have you ever programmed in assembly language? In computer programming, assembly language, often referred to simply as Assembly and commonly abbreviated as ASM or asm, is &lt;em&gt;any&lt;/em&gt; low-level programming language with a very strong correspondence between the instructions in the language and the architecture&#39;s machine code instructions.&lt;/p&gt;
&lt;p&gt;For example, looking at the &lt;a href=&quot;https://software.intel.com/en-us/download/intel-64-and-ia-32-architectures-sdm-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4&quot; rel=&quot;noopener&quot;&gt;Intel® 64 and IA-32 Architectures&lt;/a&gt; (PDF), the &lt;a href=&quot;https://www.felixcloutier.com/x86/mul&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;MUL&lt;/code&gt;&lt;/a&gt; instruction (for &lt;strong&gt;mul&lt;/strong&gt;tiplication) performs an unsigned multiplication of the first operand (destination operand) and the second operand (source operand), and stores the result in the destination operand. Very simplified, the destination operand is an implied operand located in register &lt;code&gt;AX&lt;/code&gt;, and the source operand is located in a general-purpose register like &lt;code&gt;CX&lt;/code&gt;. The result is stored again in register &lt;code&gt;AX&lt;/code&gt;. Consider the following x86 code example:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;mov ax, &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Set the value of register AX to &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;.&lt;br /&gt;mov cx, &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Set the value of register CX to &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;.&lt;br /&gt;mul cx     &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Multiply the value of register AX &lt;span class=&quot;token punctuation&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;           &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; and the value of register CX &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;, and&lt;br /&gt;           &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; store the result &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; register AX.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;For comparison, if tasked with the objective of multiplying 5 and 10, you would probably write code similar to the following in JavaScript:&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;const&lt;/span&gt; factor1 &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;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; factor2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&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;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; factor1 &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; factor2&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The advantage of going the assembly route is that such low-level and machine-optimized code is much more efficient than high-level and human-optimized code. In the preceding case it doesn&#39;t matter, but you can imagine that for more complex operations, the difference can be significant.&lt;/p&gt;
&lt;p&gt;As the name suggests, x86 code is dependent on the x86 architecture. What if there were a way of writing assembly code that was not dependent on a specific architecture, but that would inherit the performance benefits of assembly?&lt;/p&gt;
&lt;h2 id=&quot;asmjs&quot;&gt;asm.js &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/what-is-webassembly/#asmjs&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first step to writing assembly code with no architecture dependencies was &lt;a href=&quot;http://asmjs.org/spec/latest/&quot; rel=&quot;noopener&quot;&gt;asm.js&lt;/a&gt;, a strict subset of JavaScript that could be used as a low-level, efficient target language for compilers. This sub-language effectively described a sandboxed virtual machine for memory-unsafe languages like C or C++. A combination of static and dynamic validation allowed JavaScript engines to employ an ahead-of-time (AOT) optimizing compilation strategy for valid asm.js code. Code written in statically-typed languages with manual memory management (such as C) was translated by a source-to-source compiler such as the &lt;a href=&quot;https://web.archive.org/web/20130420191339/http://kripken.github.io/mloc_emscripten_talk/&quot; rel=&quot;noopener&quot;&gt;early Emscripten&lt;/a&gt; (based on LLVM).&lt;/p&gt;
&lt;p&gt;Performance was improved by limiting language features to those amenable to AOT. Firefox 22 was the first browser to &lt;a href=&quot;https://www.mozilla.org/firefox/22.0/releasenotes/&quot; rel=&quot;noopener&quot;&gt;support asm.js&lt;/a&gt;, released under the name &lt;a href=&quot;https://blog.mozilla.org/luke/2013/03/21/asm-js-in-firefox-nightly/&quot; rel=&quot;noopener&quot;&gt;OdinMonkey&lt;/a&gt;. Chrome added &lt;a href=&quot;https://v8.dev/blog/v8-release-61#asm.js-is-now-validated-and-compiled-to-webassembly&quot; rel=&quot;noopener&quot;&gt;asm.js support&lt;/a&gt; in version 61. While asm.js still works in browsers, it has been superseded by WebAssembly. The reason to use asm.js at this point would be as an alternative for browsers that don&#39;t have WebAssembly support.&lt;/p&gt;
&lt;h2 id=&quot;webassembly&quot;&gt;WebAssembly &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/what-is-webassembly/#webassembly&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;WebAssembly is a low-level assembly-like language with a compact binary format that runs with near-native performance and provides languages such as C/C++ and Rust, and many more with a compilation target so that they run on the web. Support for memory-managed languages such as Java, Kotlin, and Dart is in the works and should become available soon. WebAssembly is designed to run alongside JavaScript, allowing both to work together.&lt;/p&gt;
&lt;p&gt;Apart from the browser, WebAssembly programs also run in other runtimes thanks to &lt;a href=&quot;https://wasi.dev/&quot; rel=&quot;noopener&quot;&gt;WASI&lt;/a&gt;, the WebAssembly System Interface, a modular system interface for WebAssembly. WASI is created to be portable across operating systems, with the objective of being secure and the ability to run in a sandboxed environment.&lt;/p&gt;
&lt;p&gt;WebAssembly code (binary code, that is, bytecode) is intended to be run on a portable virtual stack machine (VM). The bytecode is designed to be faster to parse and execute than JavaScript and to have a compact code representation.&lt;/p&gt;
&lt;p&gt;Conceptual execution of instructions proceeds by way of a traditional program counter that advances through the instructions. In practice, most Wasm engines compile the Wasm bytecode to machine code, and then execute that. Instructions fall into two categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Control instructions&lt;/strong&gt; that form control constructs and pop their argument value(s) off the stack, may change the program counter, and push result value(s) onto the stack.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple instructions&lt;/strong&gt; that pop their argument value(s) from the stack, apply an operator to the values, and then push the result value(s) onto the stack, followed by an implicit advancement of the program counter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Going back to the example before, the following WebAssembly code would be equivalent to the x86 code from the beginning of the article:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;i32.const &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Push the integer value &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; onto the stack.&lt;br /&gt;i32.const &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Push the integer value &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; onto the stack.&lt;br /&gt;i32.mul      &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Pop the two &lt;span class=&quot;token function&quot;&gt;most&lt;/span&gt; recent items on the stack,&lt;br /&gt;             &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; multiply them, and push the result onto the stack.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;While asm.js is implemented all in software, that is, its code can run in any JavaScript engine (even if unoptimized), WebAssembly required new functionality that all browser vendors agreed on. &lt;a href=&quot;https://github.com/WebAssembly/design/issues/150&quot; rel=&quot;noopener&quot;&gt;Announced in 2015&lt;/a&gt; and first released in March 2017, WebAssembly became a &lt;a href=&quot;https://www.w3.org/TR/wasm-core-1/&quot; rel=&quot;noopener&quot;&gt;W3C recommendation&lt;/a&gt; on December 5, 2019. The W3C maintains the standard with contributions from all major browser vendors and other interested parties. Since 2017, browser support is universal.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 57, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      57
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 52, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      52
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 16, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      16
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 11, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      11
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;WebAssembly has two representations: &lt;a href=&quot;https://developer.mozilla.org/docs/WebAssembly/Understanding_the_text_format&quot; rel=&quot;noopener&quot;&gt;textual&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/docs/WebAssembly/Text_format_to_wasm&quot; rel=&quot;noopener&quot;&gt;binary&lt;/a&gt;. What you see above is the textual representation.&lt;/p&gt;
&lt;h3 id=&quot;textual-representation&quot;&gt;Textual representation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/what-is-webassembly/#textual-representation&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The textual representation is based on &lt;a href=&quot;https://developer.mozilla.org/docs/WebAssembly/Understanding_the_text_format#s-expressions&quot; rel=&quot;noopener&quot;&gt;S-expressions&lt;/a&gt; and commonly uses the file extension &lt;code&gt;.wat&lt;/code&gt; (for &lt;strong&gt;W&lt;/strong&gt;eb&lt;strong&gt;A&lt;/strong&gt;ssembly &lt;strong&gt;t&lt;/strong&gt;ext format). If you really wanted to, you could write it by hand. Taking the multiplication example from above and making it more useful by no longer hardcoding the factors, you can probably make sense of the following code:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;module&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;func &lt;span class=&quot;token variable&quot;&gt;$mul&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;param &lt;span class=&quot;token variable&quot;&gt;$factor1&lt;/span&gt; i32&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;param &lt;span class=&quot;token variable&quot;&gt;$factor2&lt;/span&gt; i32&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result i32&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    local.get &lt;span class=&quot;token variable&quot;&gt;$factor1&lt;/span&gt;&lt;br /&gt;    local.get &lt;span class=&quot;token variable&quot;&gt;$factor2&lt;/span&gt;&lt;br /&gt;    i32.mul&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;export &lt;span class=&quot;token string&quot;&gt;&quot;mul&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;func &lt;span class=&quot;token variable&quot;&gt;$mul&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;binary-representation&quot;&gt;Binary representation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/what-is-webassembly/#binary-representation&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The binary format that uses the file extension &lt;code&gt;.wasm&lt;/code&gt; is not meant for human consumption, let alone human creation. Using a tool like &lt;a href=&quot;https://webassembly.github.io/wabt/demo/wat2wasm/&quot; rel=&quot;noopener&quot;&gt;wat2wasm&lt;/a&gt;, you can convert the above code to the following binary representation. (The comments are not usually part of the binary representation, but added by the wat2wasm tool for better understandability.)&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;0000000: 0061 736d                             &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; WASM_BINARY_MAGIC&lt;br /&gt;0000004: 0100 0000                             &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; WASM_BINARY_VERSION&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section &lt;span class=&quot;token string&quot;&gt;&quot;Type&quot;&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;br /&gt;0000008: 01                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section code&lt;br /&gt;0000009: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section size &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;guess&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;000000a: 01                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; num types&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; func &lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;000000b: &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt;                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; func&lt;br /&gt;000000c: 02                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; num params&lt;br /&gt;000000d: 7f                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i32&lt;br /&gt;000000e: 7f                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i32&lt;br /&gt;000000f: 01                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; num results&lt;br /&gt;0000010: 7f                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i32&lt;br /&gt;0000009: 07                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; FIXUP section size&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section &lt;span class=&quot;token string&quot;&gt;&quot;Function&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;0000011: 03                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section code&lt;br /&gt;0000012: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section size &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;guess&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;0000013: 01                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; num functions&lt;br /&gt;0000014: 00                                    &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 number&quot;&gt;0&lt;/span&gt; signature index&lt;br /&gt;0000012: 02                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; FIXUP section size&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section &lt;span class=&quot;token string&quot;&gt;&quot;Export&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;0000015: 07                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section code&lt;br /&gt;0000016: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section size &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;guess&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;0000017: 01                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; num exports&lt;br /&gt;0000018: 03                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; string length&lt;br /&gt;0000019: 6d75 6c                          mul  &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; name&lt;br /&gt;000001c: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; kind&lt;br /&gt;000001d: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; func index&lt;br /&gt;0000016: 07                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; FIXUP section size&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section &lt;span class=&quot;token string&quot;&gt;&quot;Code&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;000001e: 0a                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section code&lt;br /&gt;000001f: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section size &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;guess&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;0000020: 01                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; num functions&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; body &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;0000021: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; func body size &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;guess&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;0000022: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; decl count&lt;br /&gt;0000023: &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; local.get&lt;br /&gt;0000024: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; index&lt;br /&gt;0000025: &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; local.get&lt;br /&gt;0000026: 01                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; index&lt;br /&gt;0000027: 6c                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i32.mul&lt;br /&gt;0000028: 0b                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; end&lt;br /&gt;0000021: 07                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; FIXUP func body size&lt;br /&gt;000001f: 09                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; FIXUP section size&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;br /&gt;0000029: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section code&lt;br /&gt;000002a: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; section size &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;guess&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;000002b: 04                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; string length&lt;br /&gt;000002c: 6e61 6d65                       name  &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; custom section name&lt;br /&gt;0000030: 01                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; name subsection &lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt;&lt;br /&gt;0000031: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; subsection size &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;guess&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;0000032: 01                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; num names&lt;br /&gt;0000033: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; elem index&lt;br /&gt;0000034: 03                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; string length&lt;br /&gt;0000035: 6d75 6c                          mul  &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; elem name &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;0000031: 06                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; FIXUP subsection size&lt;br /&gt;0000038: 02                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; name &lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt;&lt;br /&gt;0000039: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; subsection size &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;guess&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;000003a: 01                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; num functions&lt;br /&gt;000003b: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; index&lt;br /&gt;000003c: 02                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; num locals&lt;br /&gt;000003d: 00                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; index&lt;br /&gt;000003e: 07                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; string length&lt;br /&gt;000003f: &lt;span class=&quot;token number&quot;&gt;6661&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6374&lt;/span&gt; 6f72 &lt;span class=&quot;token number&quot;&gt;31&lt;/span&gt;            factor1  &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; name &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;0000046: 01                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; index&lt;br /&gt;0000047: 07                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; string length&lt;br /&gt;0000048: &lt;span class=&quot;token number&quot;&gt;6661&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6374&lt;/span&gt; 6f72 &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;            factor2  &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;local&lt;/span&gt; name &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;0000039: &lt;span class=&quot;token number&quot;&gt;15&lt;/span&gt;                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; FIXUP subsection size&lt;br /&gt;000002a: &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt;                                    &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; FIXUP section size&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;compiling-to-webassembly&quot;&gt;Compiling to WebAssembly &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/what-is-webassembly/#compiling-to-webassembly&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As you see, neither &lt;code&gt;.wat&lt;/code&gt; nor &lt;code&gt;.wasm&lt;/code&gt; are particularly very human-friendly. This is where a compiler like &lt;a href=&quot;https://emscripten.org/&quot; rel=&quot;noopener&quot;&gt;Emscripten&lt;/a&gt; comes into play.
It lets you compile from higher-level languages like C and C++. There are other compilers for other languages like Rust and many more. Consider the following C code:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token macro property&quot;&gt;&lt;span class=&quot;token directive-hash&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;token directive keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&amp;lt;stdio.h&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&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;printf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello World\n&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 keyword&quot;&gt;return&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Usually, you would compile this C program with the compiler &lt;code&gt;gcc&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ gcc hello.c -o hello&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;With &lt;a href=&quot;https://emscripten.org/docs/getting_started/downloads.html&quot; rel=&quot;noopener&quot;&gt;Emscripten installed&lt;/a&gt;, you compile it to WebAssembly using the &lt;code&gt;emcc&lt;/code&gt; command and almost the same arguments:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ emcc hello.c -o hello.html&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This will create a &lt;code&gt;hello.wasm&lt;/code&gt; file and the HTML wrapper file &lt;code&gt;hello.html&lt;/code&gt;. When you serve the file &lt;code&gt;hello.html&lt;/code&gt; from a web server, you will see &lt;code&gt;&amp;quot;Hello World&amp;quot;&lt;/code&gt; printed to the DevTools console.&lt;/p&gt;
&lt;p&gt;There&#39;s also a way to compile to WebAssembly without the HTML wrapper:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ emcc hello.c -o hello.js&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As before, this will create a &lt;code&gt;hello.wasm&lt;/code&gt; file, but this time a &lt;code&gt;hello.js&lt;/code&gt; file instead of the HTML wrapper. To test, you run the resulting JavaScript file &lt;code&gt;hello.js&lt;/code&gt; with, for example, Node.js:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; hello.js&lt;br /&gt;Hello World&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;learn-more&quot;&gt;Learn more &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/what-is-webassembly/#learn-more&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This brief introduction to WebAssembly is just the tip of the iceberg.
Learn more about WebAssembly in the &lt;a href=&quot;https://developer.mozilla.org/docs/WebAssembly&quot; rel=&quot;noopener&quot;&gt;WebAssembly documentation&lt;/a&gt; on MDN
and consult the &lt;a href=&quot;https://emscripten.org/docs/index.html&quot; rel=&quot;noopener&quot;&gt;Emscripten documentation&lt;/a&gt;. Truth be told, working with WebAssembly can feel a bit like the &lt;a href=&quot;https://knowyourmeme.com/memes/how-to-draw-an-owl&quot; rel=&quot;noopener&quot;&gt;How to draw an owl meme&lt;/a&gt;, especially since web developers familiar with HTML, CSS, and JavaScript are not necessarily versed in the to-be-compiled-from languages like C. Luckily there are channels like &lt;a href=&quot;https://stackoverflow.com/questions/tagged/webassembly&quot; rel=&quot;noopener&quot;&gt;StackOverflow&#39;s &lt;code&gt;webassembly&lt;/code&gt; tag&lt;/a&gt; where experts are often happy to help if you ask nicely.&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; Move on to the article &lt;a href=&quot;https://web.dev/compiling-mkbitmap-to-webassembly/&quot;&gt;Compiling &lt;code&gt;mkbitmap&lt;/code&gt; to WebAssembly&lt;/a&gt; for a beginner-friendly introduction to compiling a not completely trivial but also not overly complex C program to WebAssembly. At the example of &lt;a href=&quot;https://potrace.sourceforge.net/mkbitmap.1.html&quot;&gt;&lt;code&gt;mkbitmap&lt;/code&gt;&lt;/a&gt;, this article shows how to use a Wasm program as a library in JavaScript that works with files as input and that outputs images. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/what-is-webassembly/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by &lt;a href=&quot;https://github.com/jakobkummerow&quot; rel=&quot;noopener&quot;&gt;Jakob Kummerow&lt;/a&gt;, &lt;a href=&quot;https://www.linkedin.com/in/derek-schuff-117b11b1&quot; rel=&quot;noopener&quot;&gt;Derek Schuff&lt;/a&gt;, and &lt;a href=&quot;https://rachelandrew.co.uk/&quot; rel=&quot;noopener&quot;&gt;Rachel Andrew&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>The origin private file system</title>
    <link href="https://web.dev/origin-private-file-system/"/>
    <updated>2023-06-08T00:00:00Z</updated>
    <id>https://web.dev/origin-private-file-system/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 128 128&quot; style=&quot;enable-background:new 0 0 128 128&quot; xml:space=&quot;preserve&quot;&gt;&lt;path d=&quot;M7.5 123.3c2.2 2.4 11.6-1.9 19-5.3C32 115.4 54 106.3 65 101.6c3-1.2 7.3-2.9 10.4-7 2.8-3.6 10-19-4.6-34.7-15-16-30.4-11.5-36.2-7.5A28.5 28.5 0 0 0 27.3 63c-5.2 11.6-12.7 32.9-15.7 41.3-2.3 6.1-6.4 16.5-4.1 19z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M18 86.8s.7 6.5 6.1 14.6c6.3 9.6 15 11.2 15 11.2l-5.8 2.4s-6.5-2-12.7-10.4c-3.8-5.3-5-11.6-5-11.6l2.3-6.2zm-5.6 15.4s1.5 5.6 4.7 9.7c3.8 5 8.6 6.5 8.6 6.5l-4.5 2s-3.3-.8-7-5.5c-2.9-3.5-3.7-7.6-3.7-7.6l1.9-5.1z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M10 116.4c-.2-.5-.2-1 0-1.4l25.4-53 4.2 16-26.8 38.7c-.7 1-2.3 1-2.8-.2z&quot; style=&quot;opacity:.44&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M41.6 83.2c12 14 25.5 12.2 30 8.6 4.5-3.5 8.1-15.6-3.7-29.3C55.3 48.2 41.3 52.2 38 55.3s-7.4 15.1 3.5 27.9z&quot; style=&quot;fill: var(--color-state-good-bg);&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M82.5 89c-4.3-3.7-6.6-3-9.7-1.8a26 26 0 0 1-18.9 0l2.6-6.2c5 1.7 8.7 1 12-1 4-2.4 9.6-5.6 18.3 1.6 3.6 3 7.3 5.1 10 4.2 2-.7 3-3.6 3.6-6l.2-1.3c.4-3.7 1.2-11.6 7.1-15.7 6.4-4.3 13-4.3 13-4.3l1.2 12c-3-.5-5.2.1-7 1-6.7 3.8-.8 18.2-11.3 23-10.1 4.8-18.4-3.3-21-5.6zM45.4 73.7l-4.3-3.9c8-8.9 5.8-15.4 4.3-20.2A26.4 26.4 0 0 1 44 38.8c-3-3.8-4.4-7.8-4.5-8-1.9-5.7-.5-11.2 2.8-16.4C48.7 4 60.5 4 60.5 4l4 10.5c-3-.1-12.8 0-15.8 4.8-3.8 6-1.3 9.6-1.2 10 .8-1 1.5-1.7 2.2-2.3 4.8-4.2 9-4.8 11.6-4.6a11 11 0 0 1 7.6 4.2c2 2.7 3 6.2 2.3 9.4a11 11 0 0 1-5.8 7.4 16 16 0 0 1-13 1.5v.3l.6 2c1.8 5.4 5 14.1-7.6 26.5zm7.4-37.5c.5.4 1.1.8 1.8 1 2 .8 4.4.6 7-.8 1.5-.9 1.7-1.7 1.7-2 .2-.9 0-2-.7-2.8a2.8 2.8 0 0 0-2-1.2c-1.6-.2-3.6.8-5.6 2.6-1 .9-1.7 2-2.3 3.2zm10 39.1-6.2-.1s3-16.7 12.5-19.5c1.8-.5 3.7-1 5.7-1.3l4-.8c.1-1.6-.5-3.6-1.3-5.9-.6-1.7-1.2-3.5-1.5-5.5-.6-3.9.4-7.3 3-9.6 3-2.9 7.9-3.8 13.4-2.5 3.2.7 5.5 2.2 7.6 3.6 2.9 2 4.6 3 8.2.5 4.3-2.9-1.4-14.3-4.4-21l11.3-4.6c1.5 3.3 8.8 20.3 4 30a11.5 11.5 0 0 1-8.1 6.2c-8 1.8-12.6-1.3-16-3.6-1.6-1-3-1.9-4.6-2.3-10.6-3 4.2 12.6-2.7 19.6-4.2 4.2-14.4 5.3-15 5.5-6.6 1.6-10 11.3-10 11.3z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M44 38.8c-.2 2.2-.3 3.5.3 6.4 2.7 2 8.7 2 8.7 2l-.6-2v-.3c-6.1-3-8.4-6.1-8.4-6.1zm-12.5 9.8-10.3-5 5.1-7.5 8.2 5.4zm-15.2-14A26 26 0 0 1 5 28.9l5.2-6c1.6 1.2 5 3.5 7.2 3.8l-1.1 7.9zM25.6 21.3 18 18.8a18 18 0 0 0 .7-8.3l7.9-1.3c.6 4 .3 8.2-1 12zM73 15.3l7.9-1.7L83 23.9l-7.8 1.7zM92.5 17.8 87 12c2.8-2.8 3.5-6.3 3.5-6.4L98.4 7c-.1.6-1.1 6.3-6 10.9zM95.5 48.6l7-2.2 2.3 7.7-7 2.1zM97.5 113l-7.9-1c.3-2.7-1.8-6.2-2.4-7l6.4-4.8c.5.6 4.7 6.4 4 12.8zM120.4 102.9c-3-.5-6-.6-9.1-.5l-.3-8c3.5-.2 7 0 10.6.6l-1.2 7.9zM109.6 113.9l5.6-5.7 7.8 7.6-5.6 5.7zM93.1 63.3 99 70l-6.6 5.8-5.8-6.6z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt; &lt;/span&gt;&lt;strong&gt;Celebration&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The origin private file system allows web apps to store and manipulate files in their very own origin-specific virtual filesystem, including low-level file manipulation, byte-by-byte access, and file streaming. The origin private file system is supported across all major browsers. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;browser-support&quot;&gt;Browser support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#browser-support&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The origin private file system is supported by modern browsers and is standardized by the Web Hypertext Application Technology Working Group (&lt;a href=&quot;https://whatwg.org/&quot; rel=&quot;noopener&quot;&gt;WHATWG&lt;/a&gt;) in the &lt;a href=&quot;https://fs.spec.whatwg.org/&quot; rel=&quot;noopener&quot;&gt;File System Living Standard&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 86, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      86
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 111, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      111
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 86, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      86
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 15.2, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      15.2
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/StorageManager/getDirectory#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;motivation&quot;&gt;Motivation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#motivation&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you think of files on your computer, you probably think about a file hierarchy: files organized in folders that you can explore with your operating system&#39;s file explorer. For example, on Windows, for a user called Tom, their To Do list might live in &lt;code&gt;C:\Users\Tom\Documents\ToDo.txt&lt;/code&gt;. In this example, &lt;code&gt;ToDo.txt&lt;/code&gt; is the file name, and &lt;code&gt;Users&lt;/code&gt;, &lt;code&gt;Tom&lt;/code&gt;, and &lt;code&gt;Documents&lt;/code&gt; are folder names. &lt;code&gt;C:\&lt;/code&gt; on Windows  represents the root directory of the drive.&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; In this article, I use the terms &lt;em&gt;folder&lt;/em&gt; and &lt;em&gt;directory&lt;/em&gt; interchangeably, disregarding the difference between the file system concept (directory) and the graphical user interface &lt;a href=&quot;https://en.wikipedia.org/wiki/Directory_%28computing%29#Folder_metaphor&quot;&gt;metaphor&lt;/a&gt; (folder). &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;traditional-way-of-working-with-files-on-the-web&quot;&gt;Traditional way of working with files on the web &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#traditional-way-of-working-with-files-on-the-web&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To edit the To Do list in a web application, this is the traditional flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The user &lt;em&gt;uploads&lt;/em&gt; the file to a server or &lt;em&gt;opens&lt;/em&gt; it on the client with &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/input/file&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The user makes their changes, and then &lt;em&gt;downloads&lt;/em&gt; the resulting file with an injected &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLAnchorElement/download&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;a download=&amp;quot;ToDo.txt&amp;gt;&lt;/code&gt;&lt;/a&gt; that you programmatically &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLElement/click&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;click()&lt;/code&gt;&lt;/a&gt; via JavaScript.&lt;/li&gt;
&lt;li&gt;For opening folders, you use a special attribute in &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLInputElement/webkitdirectory&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot; webkitdirectory&amp;gt;&lt;/code&gt;&lt;/a&gt;, which, despite its proprietary name, has practically universal browser support.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;modern-way-of-working-with-files-on-the-web&quot;&gt;Modern way of working with files on the web &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#modern-way-of-working-with-files-on-the-web&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This flow is not representative of how users think of editing files, and means users end up with downloaded &lt;em&gt;copies&lt;/em&gt; of their input files. Therefore,  the File System Access API introduced three picker methods—&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/showOpenFilePicker&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;showOpenFilePicker()&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/showSaveFilePicker&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;showSaveFilePicker()&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/showDirectoryPicker&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;showDirectoryPicker()&lt;/code&gt;&lt;/a&gt;—that do exactly what their name suggests. They enable a flow as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;ToDo.txt&lt;/code&gt; with &lt;code&gt;showOpenFilePicker()&lt;/code&gt;, and get a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileSystemFileHandle&lt;/code&gt;&lt;/a&gt; object.&lt;/li&gt;
&lt;li&gt;From the &lt;code&gt;FileSystemFileHandle&lt;/code&gt; object, get a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/File&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;File&lt;/code&gt;&lt;/a&gt; by calling the file handle&#39;s &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/getFile&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;getFile()&lt;/code&gt;&lt;/a&gt; method.&lt;/li&gt;
&lt;li&gt;Modify the file, then call &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemHandle/requestPermission&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;requestPermission({mode: &#39;readwrite&#39;})&lt;/code&gt;&lt;/a&gt; on the handle.&lt;/li&gt;
&lt;li&gt;If the user accepts the permission request, save the changes back to the original file.&lt;/li&gt;
&lt;li&gt;Alternatively, call &lt;code&gt;showSaveFilePicker()&lt;/code&gt; and let the user pick a new file. (If the user picks a previously opened file, its contents will be overwritten.) For repeat saves, you can keep the file handle around, so you don&#39;t have to show the file save dialog again.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;restrictions-of-working-with-files-on-the-web&quot;&gt;Restrictions of working with files on the web &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#restrictions-of-working-with-files-on-the-web&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Files and folders that are accessible via these methods live in what can be called the &lt;em&gt;user-visible&lt;/em&gt; file system. Files saved from the web, and executable files specifically, are marked with the &lt;a href=&quot;https://textslashplain.com/2016/04/04/downloads-and-the-mark-of-the-web/&quot; rel=&quot;noopener&quot;&gt;mark of the web&lt;/a&gt;, so there&#39;s an additional warning the operating system can show before a potentially dangerous file gets executed. As an additional security feature, files obtained from the web are also protected by &lt;a href=&quot;https://safebrowsing.google.com/&quot; rel=&quot;noopener&quot;&gt;Safe Browsing&lt;/a&gt;, which, for the sake of simplicity and in the context of this article, you can think of as a cloud-based virus scan. When you write data to a file using the File System Access API, writes are  not in-place, but use a temporary file. The file itself is not modified unless it passes all these security checks. As you can imagine, this work makes file operations relatively slow, despite improvements applied where possible, for example, &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/detail?id=1413443&quot; rel=&quot;noopener&quot;&gt;on macOS&lt;/a&gt;. Still every &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream/write&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;write()&lt;/code&gt;&lt;/a&gt; call is self-contained, so under the hood it opens the file, seeks to the given offset, and finally writes data.&lt;/p&gt;
&lt;h3 id=&quot;files-as-the-foundation-of-processing&quot;&gt;Files as the foundation of processing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#files-as-the-foundation-of-processing&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;At the same time, files are an excellent way to record data. For example, &lt;a href=&quot;https://www.sqlite.org/&quot; rel=&quot;noopener&quot;&gt;SQLite&lt;/a&gt; stores entire databases in a single file. Another example are &lt;a href=&quot;https://en.wikipedia.org/wiki/Mipmap&quot; rel=&quot;noopener&quot;&gt;mipmaps&lt;/a&gt; used in image processing. Mipmaps are pre-calculated, optimized sequences of images, each of which is a progressively lower resolution representation of the previous, which makes many operations like zooming faster. So how can web applications get the benefits of files, but without the performance costs of traditional web-based file processing? The answer is the &lt;em&gt;origin private file system&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-user-visible-versus-the-origin-private-file-system&quot;&gt;The user-visible versus the origin private file system &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#the-user-visible-versus-the-origin-private-file-system&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Unlike the user-visible file system browsed via the operating system&#39;s file explorer, with files and folders you can read, write, move, and rename, the origin private file system is not meant to be seen by users. Files and folders in the origin private file system, as the name suggests, are private, and more concretely, private to the &lt;a href=&quot;https://developer.mozilla.org/docs/Glossary/Origin&quot; rel=&quot;noopener&quot;&gt;origin&lt;/a&gt; of a site. Discover the origin of a page by typing &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Location/origin&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;location.origin&lt;/code&gt;&lt;/a&gt; in the DevTools Console. For example, the origin of the page &lt;code&gt;https://developer.chrome.com/articles/&lt;/code&gt; is &lt;code&gt;https://developer.chrome.com&lt;/code&gt; (that is, the part &lt;code&gt;/articles&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; part of the origin). You can read more about the theory of origins in &lt;a href=&quot;https://web.dev/same-site-same-origin/#origin&quot;&gt;Understanding &amp;quot;same-site&amp;quot; and &amp;quot;same-origin&amp;quot;&lt;/a&gt;. All pages that share the same origin can see the same origin private file system data, so &lt;code&gt;https://developer.chrome.com/docs/extensions/mv3/getstarted/extensions-101/&lt;/code&gt; can see the same details as the previous example. Each origin has its own independent origin private file system, which means the origin private file system of &lt;code&gt;https://developer.chrome.com&lt;/code&gt; is completely distinct from the one of, say, &lt;a href=&quot;https://web.dev/&quot;&gt;&lt;code&gt;https://web.dev&lt;/code&gt;&lt;/a&gt;. On Windows, the root directory of the user-visible file system is &lt;code&gt;C:\&lt;/code&gt;. The equivalent for the origin private file system is an initially empty root directory per origin accessed by calling the asynchronous method &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/StorageManager/getDirectory&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;navigator.storage.getDirectory()&lt;/code&gt;&lt;/a&gt;. For a comparison of the user-visible file system and the origin private file system, see the following diagram. The diagram shows that apart from the root directory, everything else is conceptually the same, with a hierarchy of files and folders to organize and arrange as needed for your data and storage needs.&lt;/p&gt;
&lt;img alt=&quot;Diagram of the user-visible file system and the origin private file system with two exemplary file hierarchies. The entry point for the user-visible file system is a symbolic harddisk, the entry point for the origin private file system is calling of the method &amp;#x27;navigator.storage.getDirectory&amp;#x27;.&quot; decoding=&quot;async&quot; height=&quot;722&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xej6CL5VFJuGJgXPkeKJ.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;specifics-of-the-origin-private-file-system&quot;&gt;Specifics of the origin private file system &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#specifics-of-the-origin-private-file-system&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Just like other storage mechanisms in the browser (for example, &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/localStorage&quot; rel=&quot;noopener&quot;&gt;localStorage&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IndexedDB_API/Using_IndexedDB&quot; rel=&quot;noopener&quot;&gt;IndexedDB&lt;/a&gt;), the origin private file system is subject to browser quota restrictions. When a user &lt;a href=&quot;https://support.google.com/chrome/answer/2392709&quot; rel=&quot;noopener&quot;&gt;clears all browsing data&lt;/a&gt; or &lt;a href=&quot;https://developer.chrome.com/docs/devtools/storage/cache/#deletecache&quot; rel=&quot;noopener&quot;&gt;all site data&lt;/a&gt;, the origin private file system will be deleted, too. Call &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/StorageManager/estimate&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;navigator.storage.estimate()&lt;/code&gt;&lt;/a&gt; and in the resulting response object see the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/StorageManager/estimate#usage&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;usage&lt;/code&gt;&lt;/a&gt; entry to see how much storage your app already consumes, which is broken down by storage mechanism in the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/StorageManager/estimate#usagedetails&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;usageDetails&lt;/code&gt;&lt;/a&gt; object, where you want to look at the &lt;code&gt;fileSystem&lt;/code&gt; entry specifically. Since the origin private file system is not visible to the user, there are no permissions prompts and no Safe Browsing checks.&lt;/p&gt;
&lt;h2 id=&quot;getting-access-to-the-root-directory&quot;&gt;Getting access to the root directory &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#getting-access-to-the-root-directory&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To get access to the root directory, run the command below. You end up with an empty directory handle, more specifically, a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileSystemDirectoryHandle&lt;/code&gt;&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 keyword&quot;&gt;const&lt;/span&gt; opfsRoot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDirectory&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;// A FileSystemDirectoryHandle whose type is &quot;directory&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// and whose name is &quot;&quot;.&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;opfsRoot&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;main-thread-or-web-worker&quot;&gt;Main thread or Web Worker &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#main-thread-or-web-worker&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are two ways of using the origin private file system: on the &lt;a href=&quot;https://developer.mozilla.org/docs/Glossary/Main_thread&quot; rel=&quot;noopener&quot;&gt;main thread&lt;/a&gt; or in a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Worker&quot; rel=&quot;noopener&quot;&gt;Web Worker&lt;/a&gt;. Web Workers cannot block the main thread, which means in this context APIs can be synchronous, a pattern generally disallowed on the main thread. Synchronous APIs can be faster as they avoid having to deal with promises, and file operations are typically synchronous in languages like C that can be compiled to WebAssembly.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-c&quot;&gt;&lt;code class=&quot;language-c&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// This is synchronous C code.&lt;/span&gt;&lt;br /&gt;FILE &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;f&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;f &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fopen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;example.txt&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;w+&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 function&quot;&gt;fputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Some text\n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; f&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;fclose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;f&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 need the fastest possible file operations and/or you deal with &lt;a href=&quot;https://developer.mozilla.org/es/docs/WebAssembly&quot; rel=&quot;noopener&quot;&gt;WebAssembly&lt;/a&gt;, skip down to &lt;a href=&quot;https://example.com/&quot; rel=&quot;noopener&quot;&gt;Using the origin private file system in a Web Worker&lt;/a&gt;. Else, you can read on.&lt;/p&gt;
&lt;h2 id=&quot;using-the-origin-private-file-system-on-the-main-thread&quot;&gt;Using the origin private file system on the main thread &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#using-the-origin-private-file-system-on-the-main-thread&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;creating-new-files-and-folders&quot;&gt;Creating new files and folders &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#creating-new-files-and-folders&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Once you have a root folder, create files and folders using the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/getFileHandle&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;getFileHandle()&lt;/code&gt;&lt;/a&gt; and the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/getDirectoryHandle&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;getDirectoryHandle()&lt;/code&gt;&lt;/a&gt; methods respectively. By passing &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/getFileHandle#create&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;{create: true}&lt;/code&gt;&lt;/a&gt;, the file or folder will be created if it doesn&#39;t exist. Build up a hierarchy of files by calling these functions using a newly created directory as the starting point.&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;const&lt;/span&gt; fileHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; opfsRoot&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFileHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my first file&#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;create&lt;/span&gt;&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;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;const&lt;/span&gt; directoryHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; opfsRoot&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDirectoryHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my first folder&#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;create&lt;/span&gt;&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;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;const&lt;/span&gt; nestedFileHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; directoryHandle&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFileHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my first nested file&#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;create&lt;/span&gt;&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;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;const&lt;/span&gt; nestedDirectoryHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; directoryHandle&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDirectoryHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my first nested folder&#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;create&lt;/span&gt;&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;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;img alt=&quot;The resulting file hierarchy from the earlier code sample.&quot; decoding=&quot;async&quot; height=&quot;196&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 438px) 438px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VtWQ4T2a1gph0kFzhRwN.png?auto=format&amp;w=876 876w&quot; width=&quot;438&quot; /&gt;
&lt;h3 id=&quot;accessing-existing-files-and-folders&quot;&gt;Accessing existing files and folders &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#accessing-existing-files-and-folders&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you know their name, access previously created files and folders by calling the &lt;code&gt;getFileHandle()&lt;/code&gt; or the &lt;code&gt;getDirectoryHandle()&lt;/code&gt; methods, passing in the name of the file or folder.&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;const&lt;/span&gt; existingFileHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; opfsRoot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFileHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my first file&#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;const&lt;/span&gt; existingDirectoryHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; opfsRoot&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDirectoryHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&#39;my first folder&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;getting-the-file-associated-with-a-file-handle-for-reading&quot;&gt;Getting the file associated with a file handle for reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#getting-the-file-associated-with-a-file-handle-for-reading&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;FileSystemFileHandle&lt;/code&gt; represents a file on the file system. To obtain the associated &lt;code&gt;File&lt;/code&gt;, use the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/getFile&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;getFile()&lt;/code&gt;&lt;/a&gt; method. A &lt;code&gt;File&lt;/code&gt; object is a specific kind of &lt;a href=&quot;https://developer.mozilla.org/es/docs/Web/API/Blob&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Blob&lt;/code&gt;&lt;/a&gt;, and can be used in any context that a &lt;code&gt;Blob&lt;/code&gt; can. In particular, &lt;a href=&quot;https://developer.mozilla.org/es/docs/Web/API/FileReader&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileReader&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://developer.mozilla.org/es/docs/Web/API/URL/createObjectURL&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;URL.createObjectURL()&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://developer.mozilla.org/es/docs/Web/API/createImageBitmap&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;createImageBitmap()&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/send&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;XMLHttpRequest.send()&lt;/code&gt;&lt;/a&gt; accept both &lt;code&gt;Blobs&lt;/code&gt; and &lt;code&gt;Files&lt;/code&gt;. If you will, obtaining a &lt;code&gt;File&lt;/code&gt; from a &lt;code&gt;FileSystemFileHandle&lt;/code&gt; &amp;quot;frees&amp;quot; the data, so you can access it and make it available to the user-visible file system.&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;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFile&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;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 keyword&quot;&gt;await&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&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;h3 id=&quot;writing-to-a-file-by-streaming&quot;&gt;Writing to a file by streaming &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#writing-to-a-file-by-streaming&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Stream data into a file by calling &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/createWritable&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;createWritable()&lt;/code&gt;&lt;/a&gt; which creates a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileSystemWritableFileStream&lt;/code&gt;&lt;/a&gt; to that you then &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemWritableFileStream/write&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;write()&lt;/code&gt;&lt;/a&gt; the contents. At the end, you need to &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WritableStream/close&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;close()&lt;/code&gt;&lt;/a&gt; the stream.&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;const&lt;/span&gt; contents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Some text&#39;&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;// Get a writable stream.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; writable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWritable&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;// Write the contents of the file to the stream.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; writable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;contents&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;// Close the stream, which persists the contents.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; writable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&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;h3 id=&quot;deleting-files-and-folders&quot;&gt;Deleting files and folders &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#deleting-files-and-folders&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Delete files and folders by calling their file or directory handle&#39;s particular &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemHandle/remove&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;remove()&lt;/code&gt;&lt;/a&gt; method. To delete a folder including all subfolders, pass the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemHandle/remove#recursive&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;{recursive: true}&lt;/code&gt;&lt;/a&gt; option.&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;await&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&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;await&lt;/span&gt; directoryHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&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;recursive&lt;/span&gt;&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;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; The &lt;code&gt;remove()&lt;/code&gt; method is currently only implemented in Chrome. You can feature-detect support via &lt;code&gt;&#39;remove&#39; in FileSystemFileHandle.prototype&lt;/code&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;As an alternative, if you know the name of the to-be-deleted file or folder in a directory, use the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/removeEntry&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;removeEntry()&lt;/code&gt;&lt;/a&gt; method.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;directoryHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEntry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my first nested file&#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;/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; As a quick tip, &lt;code&gt;await (await navigator.storage.getDirectory()).remove({recursive: true})&lt;/code&gt; is the fastest way to clear the entire origin private file system. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;moving-and-renaming-files-and-folders&quot;&gt;Moving and renaming files and folders &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#moving-and-renaming-files-and-folders&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Rename and move files and folders using the &lt;a href=&quot;https://github.com/whatwg/fs/pull/10&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;move()&lt;/code&gt;&lt;/a&gt; method. Moving and renaming can happen together or in isolation.&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;// Rename a file.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my first renamed file&#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;// Move a file to another directory.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nestedDirectoryHandle&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;// Move a file to another directory and rename it.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fileHandle&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nestedDirectoryHandle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;my first renamed and now nested file&#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;/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; Renaming and moving folders is not implemented yet in Chrome. You also can&#39;t move files from the origin private file system to the user-visible file system. You can &lt;a href=&quot;https://web.dev/origin-private-file-system/#copying-a-file-from-the-origin-private-file-system-to-the-user-visible-file-system&quot;&gt;copy&lt;/a&gt; them, though. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;resolving-the-path-of-a-file-or-folder&quot;&gt;Resolving the path of a file or folder &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#resolving-the-path-of-a-file-or-folder&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To learn where a given file or folder is located  in relation to a reference directory, use the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/resolve&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;resolve()&lt;/code&gt;&lt;/a&gt; method, passing it a &lt;code&gt;FileSystemHandle&lt;/code&gt; as the argument. To obtain the full path of a file or folder in the origin private file system, use the root directory as the reference directory obtained via &lt;code&gt;navigator.storage.getDirectory()&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;const&lt;/span&gt; relativePath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; opfsRoot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nestedDirectoryHandle&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;// `relativePath` is `[&#39;my first folder&#39;, &#39;my first nested folder&#39;]`.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;checking-if-two-file-or-folder-handles-point-to-the-same-file-or-folder&quot;&gt;Checking if two file or folder handles point to the same file or folder &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#checking-if-two-file-or-folder-handles-point-to-the-same-file-or-folder&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Sometimes you have two handles and don&#39;t know if they point at the same file or folder. To check whether this is the case, use the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemHandle/isSameEntry&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;isSameEntry()&lt;/code&gt;&lt;/a&gt; method.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isSameEntry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nestedFileHandle&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;// Returns `false`.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;listing-the-contents-of-a-folder&quot;&gt;Listing the contents of a folder &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#listing-the-contents-of-a-folder&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;FileSystemDirectoryHandle&lt;/code&gt; is an &lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols&quot; rel=&quot;noopener&quot;&gt;asynchronous iterator&lt;/a&gt; that you iterate over with a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/for-await...of&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;for await…of&lt;/code&gt;&lt;/a&gt; loop. As an asynchronous iterator, it also supports the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/entries&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;entries()&lt;/code&gt;&lt;/a&gt;, the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/values&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;values()&lt;/code&gt;&lt;/a&gt;, and the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryHandle/keys&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;keys()&lt;/code&gt;&lt;/a&gt; methods, from which you can choose depending on what information you need:&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 keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; directoryHandle&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 keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; directoryHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;entries&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;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 keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; handle &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; directoryHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&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;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 keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; name &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; directoryHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&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;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;recursively-listing-the-contents-of-a-folder-and-all-subfolders&quot;&gt;Recursively listing the contents of a folder and all subfolders &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#recursively-listing-the-contents-of-a-folder-and-all-subfolders&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dealing with asynchronous loops and functions paired with recursion is easy to get wrong. The function below can serve as a starting point for listing the contents of a folder and all its subfolders, including all files and their sizes. You can simplify the function if you don&#39;t need the file sizes by, where it says &lt;code&gt;directoryEntryPromises.push&lt;/code&gt;, not pushing the &lt;code&gt;handle.getFile()&lt;/code&gt; promise, but the &lt;code&gt;handle&lt;/code&gt; directly.&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;const&lt;/span&gt; getDirectoryEntriesRecursive &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    directoryHandle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    relativePath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.&#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 operator&quot;&gt;=&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;const&lt;/span&gt; fileHandles &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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; directoryHandles &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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entries &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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Get an iterator of the files and folders in the directory.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; directoryIterator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; directoryHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&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;const&lt;/span&gt; directoryEntryPromises &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;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 keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; directoryIterator&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;const&lt;/span&gt; nestedPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;relativePath&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&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 keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;file&#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;        fileHandles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&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; handle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nestedPath &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;        directoryEntryPromises&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;          handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFile&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;then&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 parameter&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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 literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token literal-property property&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token literal-property property&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;size&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token literal-property property&quot;&gt;lastModified&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastModified&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token literal-property property&quot;&gt;relativePath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; nestedPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              handle&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;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 keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;directory&#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;        directoryHandles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&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; handle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nestedPath &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;        directoryEntryPromises&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&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;async&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;=&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 literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token literal-property property&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token literal-property property&quot;&gt;relativePath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; nestedPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token literal-property property&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;                  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getDirectoryEntriesRecursive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nestedPath&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;              handle&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;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;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;const&lt;/span&gt; directoryEntries &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;directoryEntryPromises&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;    directoryEntries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&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 parameter&quot;&gt;directoryEntry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      entries&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;directoryEntry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; directoryEntry&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 keyword&quot;&gt;return&lt;/span&gt; entries&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;h2 id=&quot;using-the-origin-private-file-system-in-a-web-worker&quot;&gt;Using the origin private file system in a Web Worker &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#using-the-origin-private-file-system-in-a-web-worker&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As outlined before, Web Workers can&#39;t block the main thread, which is why in this context synchronous methods are allowed.&lt;/p&gt;
&lt;h3 id=&quot;getting-a-synchronous-access-handle&quot;&gt;Getting a synchronous access handle &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#getting-a-synchronous-access-handle&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The entry point to the fastest possible file operations is a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileSystemSyncAccessHandle&lt;/code&gt;&lt;/a&gt;, obtained from a regular &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileSystemFileHandle&lt;/code&gt;&lt;/a&gt; by calling &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle/createSyncAccessHandle&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;createSyncAccessHandle()&lt;/code&gt;&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 keyword&quot;&gt;const&lt;/span&gt; fileHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; opfsRoot&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFileHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my highspeed file.txt&#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;create&lt;/span&gt;&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;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;const&lt;/span&gt; syncAccessHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createSyncAccessHandle&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;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; It might seem confusing, but you actually get a &lt;em&gt;synchronous&lt;/em&gt; &lt;code&gt;FileSystemSyncAccessHandle&lt;/code&gt; from a regular &lt;code&gt;FileSystemFileHandle&lt;/code&gt;. Also note that the &lt;code&gt;createSyncAccessHandle()&lt;/code&gt; method is &lt;em&gt;asynchronous&lt;/em&gt;, despite the &lt;code&gt;Sync&lt;/code&gt; in its name. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;synchronous-in-place-file-methods&quot;&gt;Synchronous in-place file methods &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#synchronous-in-place-file-methods&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Once you have a synchronous access handle, you get access to fast in-place file methods that are all synchronous.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/getSize&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;getSize()&lt;/code&gt;&lt;/a&gt;: Returns the size of the file in bytes.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/write&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;write()&lt;/code&gt;&lt;/a&gt;: Writes the content of a buffer into the, optionally at a given offset, and returns the number of written bytes. Checking the returned number of written bytes allows callers to detect and handle errors and partial writes.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/read&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;read()&lt;/code&gt;&lt;/a&gt;: Reads the contents of the file into a buffer, optionally at a given offset.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/truncate&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;truncate()&lt;/code&gt;&lt;/a&gt;: Resizes the file to the given size.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/flush&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;flush()&lt;/code&gt;&lt;/a&gt;: Ensures that the contents of the file contain all the modifications done through &lt;code&gt;write()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemSyncAccessHandle/close&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;close()&lt;/code&gt;&lt;/a&gt;: Closes the access handle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is an example that uses all the methods mentioned above.&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;const&lt;/span&gt; opfsRoot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDirectory&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;const&lt;/span&gt; fileHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; opfsRoot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFileHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fast&#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;create&lt;/span&gt;&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;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;const&lt;/span&gt; accessHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createSyncAccessHandle&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;const&lt;/span&gt; textEncoder &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;TextEncoder&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;const&lt;/span&gt; textDecoder &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;TextDecoder&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;// Initialize this variable for the size of the file.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; size&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// The current size of the file, initially `0`.&lt;/span&gt;&lt;br /&gt;size &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; accessHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSize&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;// Encode content to write to the file.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; textEncoder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Some text&#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;// Write the content at the beginning of the file.&lt;/span&gt;&lt;br /&gt;accessHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; size&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;// Flush the changes.&lt;/span&gt;&lt;br /&gt;accessHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flush&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;// The current size of the file, now `9` (the length of &quot;Some text&quot;).&lt;/span&gt;&lt;br /&gt;size &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; accessHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSize&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;// Encode more content to write to the file.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; moreContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; textEncoder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;More content&#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;// Write the content at the end of the file.&lt;/span&gt;&lt;br /&gt;accessHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;moreContent&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;at&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; size&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;// Flush the changes.&lt;/span&gt;&lt;br /&gt;accessHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flush&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;// The current size of the file, now `21` (the length of&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// &quot;Some textMore content&quot;).&lt;/span&gt;&lt;br /&gt;size &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; accessHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSize&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;// Prepare a data view of the length of the file.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dataView &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;DataView&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;ArrayBuffer&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;&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;// Read the entire file into the data view.&lt;/span&gt;&lt;br /&gt;accessHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dataView&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;// Logs `&quot;Some textMore content&quot;`.&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;textDecoder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dataView&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;// Read starting at offset 9 into the data view.&lt;/span&gt;&lt;br /&gt;accessHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dataView&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;at&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&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;// Logs `&quot;More content&quot;`.&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;textDecoder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dataView&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;// Truncate the file after 4 bytes.&lt;/span&gt;&lt;br /&gt;accessHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;truncate&lt;/span&gt;&lt;span class=&quot;token punctuation&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;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; Note that the first parameter for &lt;code&gt;read()&lt;/code&gt; and &lt;code&gt;write()&lt;/code&gt; is an &lt;a href=&quot;https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer&quot;&gt;&lt;code&gt;ArrayBuffer&lt;/code&gt;&lt;/a&gt; or an &lt;code&gt;ArrayBufferView&lt;/code&gt; like a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/DataView&quot;&gt;&lt;code&gt;DataView&lt;/code&gt;&lt;/a&gt;. You cannot directly manipulate the contents of an &lt;code&gt;ArrayBuffer&lt;/code&gt;. Instead, you create one of the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypedArray&quot;&gt;typed array objects&lt;/a&gt; like an &lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int8Array&quot;&gt;&lt;code&gt;Int8Array&lt;/code&gt;&lt;/a&gt; or a &lt;code&gt;DataView&lt;/code&gt; object which represents the buffer in a specific format, and use that to read and write the contents of the buffer. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;copying-a-file-from-the-origin-private-file-system-to-the-user-visible-file-system&quot;&gt;Copying a file from the origin private file system to the user-visible file system &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#copying-a-file-from-the-origin-private-file-system-to-the-user-visible-file-system&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As mentioned above, moving files from the origin private file system to the user-visible file system isn&#39;t possible, but you can copy files. Since &lt;code&gt;showSaveFilePicker()&lt;/code&gt; is only exposed on the main thread, but not in the Worker thread, be sure to run the code there.&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;// On the main thread, not in the Worker. This assumes&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// `fileHandle` is the `FileSystemFileHandle` you obtained&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// the `FileSystemSyncAccessHandle` from in the Worker&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// thread. Be sure to close the file in the Worker thread first.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fileHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; opfsRoot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFileHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fast&#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;try&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;// Obtain a file handle to a new file in the user-visible file system&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// with the same name as the file in the origin private file system.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; saveHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;showSaveFilePicker&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;suggestedName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#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;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; writable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; saveHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWritable&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;await&lt;/span&gt; writable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFile&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 keyword&quot;&gt;await&lt;/span&gt; writable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;debugging-the-origin-private-file-system&quot;&gt;Debugging the origin private file system &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#debugging-the-origin-private-file-system&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Until built-in DevTools support is added (see &lt;a href=&quot;https://crbug.com/1284595&quot; rel=&quot;noopener&quot;&gt;crbug/1284595&lt;/a&gt;), use the &lt;a href=&quot;https://chrome.google.com/webstore/detail/opfs-explorer/acndjpgkpaclldomagafnognkcgjignd&quot; rel=&quot;noopener&quot;&gt;OPFS Explorer&lt;/a&gt; Chrome extension to debug the origin private file system. The screenshot above from the section &lt;a href=&quot;https://web.dev/origin-private-file-system/#creating-new-files-and-folders&quot;&gt;Creating new files and folders&lt;/a&gt; is taken straight from the extension by the way.&lt;/p&gt;
&lt;img alt=&quot;The OPFS Explorer Chrome DevTools extension in the Chrome Web Store.&quot; decoding=&quot;async&quot; height=&quot;612&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kmE7qbP61UlLcCxBkMMQ.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;After installing the extension, open the Chrome DevTools, select the &lt;strong&gt;OPFS Explorer&lt;/strong&gt; tab, and you&#39;re then ready to inspect the file hierarchy. Save files from the origin private file system to the user-visible file system by clicking the file name and delete files and folders by clicking the trash can icon.&lt;/p&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;See the origin private file system in action (if you install the OPFS Explorer extension) in a &lt;a href=&quot;https://sqlite-wasm-opfs.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt; that uses it as a backend for a SQLite database compiled to WebAssembly. Be sure to check out the &lt;a href=&quot;https://glitch.com/edit/#!/sqlite-wasm-opfs&quot; rel=&quot;noopener&quot;&gt;source code on Glitch&lt;/a&gt;. Note how the embedded version below does not use the origin private file system backend (because the iframe is cross-origin), but when you open the demo in a separate tab, it does.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 420px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/sqlite-wasm-opfs?attributionHidden=true&amp;sidebarCollapsed=true&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;sqlite-wasm-opfs on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#conclusions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The origin private file system, as specified by the WHATWG, has shaped the way we use and interact with files on the web. It has enabled new use cases that were impossible to achieve with the user-visible file system. All major browser vendors—Apple, Mozilla, and Google—are on-board and share a joint vision. The development of the origin private file system is very much a collaborative effort, and feedback from developers and users is essential to its progress. As we continue to refine and improve the standard, feedback on the &lt;a href=&quot;https://github.com/whatwg/fs&quot; rel=&quot;noopener&quot;&gt;whatwg/fs repository&lt;/a&gt; in the form of Issues or Pull Requests is welcome.&lt;/p&gt;
&lt;h2 id=&quot;related-links&quot;&gt;Related links &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#related-links&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://fs.spec.whatwg.org/&quot; rel=&quot;noopener&quot;&gt;File System Standard spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/whatwg/fs&quot; rel=&quot;noopener&quot;&gt;File System Standard repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webkit.org/blog/12257/the-file-system-access-api-with-origin-private-file-system/&quot; rel=&quot;noopener&quot;&gt;The File System API with Origin Private File System WebKit post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/opfs-explorer/acndjpgkpaclldomagafnognkcgjignd&quot; rel=&quot;noopener&quot;&gt;OPFS Explorer extension&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/origin-private-file-system/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by &lt;a href=&quot;https://github.com/a-sully&quot; rel=&quot;noopener&quot;&gt;Austin Sully&lt;/a&gt;, &lt;a href=&quot;https://ca.linkedin.com/in/enoel19&quot; rel=&quot;noopener&quot;&gt;Etienne Noël&lt;/a&gt;, and &lt;a href=&quot;https://rachelandrew.co.uk/&quot; rel=&quot;noopener&quot;&gt;Rachel Andrew&lt;/a&gt;. Hero image by &lt;a href=&quot;https://unsplash.com/@rumpf&quot; rel=&quot;noopener&quot;&gt;Christina Rumpf&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/XWDMmk-yW7Q&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>PWAs in app stores</title>
    <link href="https://web.dev/pwas-in-app-stores/"/>
    <updated>2023-03-31T00:00:00Z</updated>
    <id>https://web.dev/pwas-in-app-stores/</id>
    <content type="html" mode="escaped">&lt;p&gt;PWAs can be accessed through a web browser, but they can also be installed on a user&#39;s home screen as outlined in the articles in the section &lt;a href=&quot;https://web.dev/progressive-web-apps/#provide-an-installable-experience&quot;&gt;Provide an installable experience&lt;/a&gt;. One of the challenges with PWAs, however, is distributing them to app stores. This is where &lt;a href=&quot;https://pwabuilder.com/&quot; rel=&quot;noopener&quot;&gt;PWABuilder&lt;/a&gt; comes in.&lt;br /&gt;
PWABuilder is a powerful tool that allows developers to create packages that can be submitted to various app stores:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://play.google.com/store&quot; rel=&quot;noopener&quot;&gt;Google Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apps.microsoft.com/&quot; rel=&quot;noopener&quot;&gt;Microsoft Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.apple.com/app-store/&quot; rel=&quot;noopener&quot;&gt;Apple App Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.oculus.com/experiences/quest/&quot; rel=&quot;noopener&quot;&gt;Meta Quest Store&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One of the major advantages of using PWABuilder to create packages is that it simplifies the process of publishing your web application to app stores. Normally, submitting an app to app stores requires a lot of work, including writing code in languages web developers may not necessarily be familiar with, creating app icons, configuring various settings, and testing the app across different devices and operating systems. PWABuilder takes care of many of these tasks automatically, reducing the amount of time and effort required to publish apps.&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; Internally, PWABuilder uses a command line tool called &lt;a href=&quot;https://github.com/GoogleChromeLabs/bubblewrap&quot;&gt;&lt;code&gt;bubblewrap&lt;/code&gt;&lt;/a&gt;, which you can learn more about in the article &lt;a href=&quot;https://developer.chrome.com/docs/android/trusted-web-activity/quick-start/&quot;&gt;Trusted Web Activities Quick Start Guide&lt;/a&gt;. Rather than use PWABuilder, you can run &lt;code&gt;bubblewrap&lt;/code&gt; directly if you prefer the command line over graphical user interfaces. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-in-app-stores/#prerequisites&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If your web app meets a few baseline PWA requirements, you can use PWABuilder to validate, score, and package your application for stores.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your PWA needs to be published at a public URL.&lt;/li&gt;
&lt;li&gt;It must have a complete &lt;a href=&quot;https://developer.mozilla.org/docs/Web/Manifest&quot; rel=&quot;noopener&quot;&gt;web app manifest&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The app has to be served with HTTPS.&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;p&gt;To publish your package, you will also need a developer account with each platform you want to publish with. For the Microsoft and Google Play stores, these cost a one time fee. For the Apple App Store, an account costs a recurring fee per year. Meta Quest accounts are free.&lt;/p&gt; &lt;p&gt;By submitting to stores, you need to play by their rules. These rules may require you to use their payment mechanisms and pay a commission for each purchase. They may also restrict certain types of content. Carefully assess these aspects for each store you want to publish to.&lt;/p&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;packaging&quot;&gt;Packaging &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-in-app-stores/#packaging&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can have an application package for your PWA in a few steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;From the homepage of PWABuilder, enter a URL to start the packaging process. PWABuilder will take you to the report card page for your application, where you can view scores and action items for your progressive web app.&lt;/li&gt;
&lt;li&gt;To proceed to packaging your app, click the &lt;strong&gt;Package for Stores&lt;/strong&gt; button on the top right of the scorecard page.&lt;/li&gt;
&lt;li&gt;Browse the options for packaging, and select a platform by clicking &lt;strong&gt;Generate Package&lt;/strong&gt;. You will be prompted for metadata related to your application, which will vary depending on platform.&lt;/li&gt;
&lt;li&gt;Lastly, select &lt;strong&gt;Download Package&lt;/strong&gt; to download your package.&lt;/li&gt;
&lt;/ul&gt;
&lt;img alt=&quot;SVGcode PWA in the PWABuilder user interface.&quot; decoding=&quot;async&quot; height=&quot;1136&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zBI3DyULt7jcI9ynDbbN.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;submitting&quot;&gt;Submitting &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-in-app-stores/#submitting&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For guidance on how to publish a PWA to a specific store, be sure to check out the platform-specific articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pwabuilder.com/#/builder/windows&quot; rel=&quot;noopener&quot;&gt;Microsoft Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pwabuilder.com/#/builder/android&quot; rel=&quot;noopener&quot;&gt;Google Play Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pwabuilder.com/#/builder/app-store&quot; rel=&quot;noopener&quot;&gt;Apple App Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pwabuilder.com/#/builder/meta&quot; rel=&quot;noopener&quot;&gt;Meta Quest Store&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;example&quot;&gt;Example &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-in-app-stores/#example&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have generated store packages for one of my apps, &lt;a href=&quot;https://web.dev/svgcode&quot;&gt;SVGcode&lt;/a&gt;, using PWABuilder.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=de.svgco.twa&quot;&gt;&lt;img width=&quot;200px&quot; src=&quot;https://raw.githubusercontent.com/tomayac/SVGcode/main/public/badges/play-store.svg&quot; alt=&quot;Get it on Google Play.&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.microsoft.com/en-us/p/svgcode/9plhxdgsw1rj#activetab=pivot:overviewtab&quot;&gt;&lt;img width=&quot;200px&quot; src=&quot;https://raw.githubusercontent.com/tomayac/SVGcode/main/public/badges/microsoft-store.svg&quot; alt=&quot;Get it from Microsoft.&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Apart from these stores, you can of course get the app in the browser as well.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://svgco.de/&quot;&gt;&lt;img width=&quot;200px&quot; src=&quot;https://raw.githubusercontent.com/tomayac/SVGcode/main/public/badges/web-browser.svg&quot; alt=&quot;Use it in your web browser.&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>JavaScript import maps are now supported cross-browser</title>
    <link href="https://web.dev/import-maps-in-all-modern-browsers/"/>
    <updated>2023-03-28T00:00:00Z</updated>
    <id>https://web.dev/import-maps-in-all-modern-browsers/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 128 128&quot; style=&quot;enable-background:new 0 0 128 128&quot; xml:space=&quot;preserve&quot;&gt;&lt;path d=&quot;M7.5 123.3c2.2 2.4 11.6-1.9 19-5.3C32 115.4 54 106.3 65 101.6c3-1.2 7.3-2.9 10.4-7 2.8-3.6 10-19-4.6-34.7-15-16-30.4-11.5-36.2-7.5A28.5 28.5 0 0 0 27.3 63c-5.2 11.6-12.7 32.9-15.7 41.3-2.3 6.1-6.4 16.5-4.1 19z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M18 86.8s.7 6.5 6.1 14.6c6.3 9.6 15 11.2 15 11.2l-5.8 2.4s-6.5-2-12.7-10.4c-3.8-5.3-5-11.6-5-11.6l2.3-6.2zm-5.6 15.4s1.5 5.6 4.7 9.7c3.8 5 8.6 6.5 8.6 6.5l-4.5 2s-3.3-.8-7-5.5c-2.9-3.5-3.7-7.6-3.7-7.6l1.9-5.1z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M10 116.4c-.2-.5-.2-1 0-1.4l25.4-53 4.2 16-26.8 38.7c-.7 1-2.3 1-2.8-.2z&quot; style=&quot;opacity:.44&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M41.6 83.2c12 14 25.5 12.2 30 8.6 4.5-3.5 8.1-15.6-3.7-29.3C55.3 48.2 41.3 52.2 38 55.3s-7.4 15.1 3.5 27.9z&quot; style=&quot;fill: var(--color-state-good-bg);&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M82.5 89c-4.3-3.7-6.6-3-9.7-1.8a26 26 0 0 1-18.9 0l2.6-6.2c5 1.7 8.7 1 12-1 4-2.4 9.6-5.6 18.3 1.6 3.6 3 7.3 5.1 10 4.2 2-.7 3-3.6 3.6-6l.2-1.3c.4-3.7 1.2-11.6 7.1-15.7 6.4-4.3 13-4.3 13-4.3l1.2 12c-3-.5-5.2.1-7 1-6.7 3.8-.8 18.2-11.3 23-10.1 4.8-18.4-3.3-21-5.6zM45.4 73.7l-4.3-3.9c8-8.9 5.8-15.4 4.3-20.2A26.4 26.4 0 0 1 44 38.8c-3-3.8-4.4-7.8-4.5-8-1.9-5.7-.5-11.2 2.8-16.4C48.7 4 60.5 4 60.5 4l4 10.5c-3-.1-12.8 0-15.8 4.8-3.8 6-1.3 9.6-1.2 10 .8-1 1.5-1.7 2.2-2.3 4.8-4.2 9-4.8 11.6-4.6a11 11 0 0 1 7.6 4.2c2 2.7 3 6.2 2.3 9.4a11 11 0 0 1-5.8 7.4 16 16 0 0 1-13 1.5v.3l.6 2c1.8 5.4 5 14.1-7.6 26.5zm7.4-37.5c.5.4 1.1.8 1.8 1 2 .8 4.4.6 7-.8 1.5-.9 1.7-1.7 1.7-2 .2-.9 0-2-.7-2.8a2.8 2.8 0 0 0-2-1.2c-1.6-.2-3.6.8-5.6 2.6-1 .9-1.7 2-2.3 3.2zm10 39.1-6.2-.1s3-16.7 12.5-19.5c1.8-.5 3.7-1 5.7-1.3l4-.8c.1-1.6-.5-3.6-1.3-5.9-.6-1.7-1.2-3.5-1.5-5.5-.6-3.9.4-7.3 3-9.6 3-2.9 7.9-3.8 13.4-2.5 3.2.7 5.5 2.2 7.6 3.6 2.9 2 4.6 3 8.2.5 4.3-2.9-1.4-14.3-4.4-21l11.3-4.6c1.5 3.3 8.8 20.3 4 30a11.5 11.5 0 0 1-8.1 6.2c-8 1.8-12.6-1.3-16-3.6-1.6-1-3-1.9-4.6-2.3-10.6-3 4.2 12.6-2.7 19.6-4.2 4.2-14.4 5.3-15 5.5-6.6 1.6-10 11.3-10 11.3z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M44 38.8c-.2 2.2-.3 3.5.3 6.4 2.7 2 8.7 2 8.7 2l-.6-2v-.3c-6.1-3-8.4-6.1-8.4-6.1zm-12.5 9.8-10.3-5 5.1-7.5 8.2 5.4zm-15.2-14A26 26 0 0 1 5 28.9l5.2-6c1.6 1.2 5 3.5 7.2 3.8l-1.1 7.9zM25.6 21.3 18 18.8a18 18 0 0 0 .7-8.3l7.9-1.3c.6 4 .3 8.2-1 12zM73 15.3l7.9-1.7L83 23.9l-7.8 1.7zM92.5 17.8 87 12c2.8-2.8 3.5-6.3 3.5-6.4L98.4 7c-.1.6-1.1 6.3-6 10.9zM95.5 48.6l7-2.2 2.3 7.7-7 2.1zM97.5 113l-7.9-1c.3-2.7-1.8-6.2-2.4-7l6.4-4.8c.5.6 4.7 6.4 4 12.8zM120.4 102.9c-3-.5-6-.6-9.1-.5l-.3-8c3.5-.2 7 0 10.6.6l-1.2 7.9zM109.6 113.9l5.6-5.7 7.8 7.6-5.6 5.7zM93.1 63.3 99 70l-6.6 5.8-5.8-6.6z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt; &lt;/span&gt;&lt;strong&gt;Celebration&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; This web feature is now available in all three browser engines! &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;ES modules are a modern way to include and reuse JavaScript code in web applications. They are
supported by modern browsers and provide several benefits over older, non-modular approaches to
JavaScript development.&lt;/p&gt;
&lt;p&gt;A modern way to use ES modules is with the &lt;code&gt;&amp;lt;script type=&amp;quot;importmap&amp;quot;&amp;gt;&lt;/code&gt; tag. This tag allows you to
define a mapping of external module names to their corresponding URLs, which makes it easier to
include and use external modules in your code.&lt;/p&gt;
&lt;p&gt;To use the &lt;code&gt;&amp;lt;script type=&amp;quot;importmap&amp;quot;&amp;gt;&lt;/code&gt; approach, you first need to add it to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section of
your HTML document. Inside the tag, you can define a JSON object that maps module names to their
corresponding URLs. For 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;script&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;importmap&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 class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string-property property&quot;&gt;&quot;imports&quot;&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 string-property property&quot;&gt;&quot;browser-fs-access&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://unpkg.com/browser-fs-access@0.33.0/dist/index.modern.js&quot;&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&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This code defines a single external module named &lt;code&gt;&amp;quot;browser-fs-access&amp;quot;&lt;/code&gt; and maps it to the URL of the
&lt;a href=&quot;https://github.com/GoogleChromeLabs/browser-fs-access&quot; rel=&quot;noopener&quot;&gt;browser-fs-access&lt;/a&gt; library, hosted on the unpkg CDN. With this mapping in place, you can now use the &lt;code&gt;import&lt;/code&gt;
keyword to include the browser-fs-access library in your code. Note that the &lt;code&gt;import&lt;/code&gt; keyword is
only available inside a &lt;code&gt;script&lt;/code&gt; tag with the &lt;code&gt;type=&amp;quot;module&amp;quot;&lt;/code&gt; attribute.&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 punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Select a text file&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 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;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;module&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 class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;fileOpen&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;browser-fs-access&#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 keyword&quot;&gt;const&lt;/span&gt; button &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;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;  button&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;async&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;=&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;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fileOpen&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;mimeTypes&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 string&quot;&gt;&#39;text/plain&#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;    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 keyword&quot;&gt;await&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&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 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&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Using the &lt;code&gt;&amp;lt;script type=&amp;quot;importmap&amp;quot;&amp;gt;&lt;/code&gt; tag and the &lt;code&gt;import&lt;/code&gt; keyword provides several benefits over
older, non-modular approaches to JavaScript development. It allows you to clearly and explicitly
specify the external modules your code depends on, which makes it easier to understand and maintain
your code. Overall, using ES modules with the &lt;code&gt;&amp;lt;script type=&amp;quot;importmap&amp;quot;&amp;gt;&lt;/code&gt; tag is a modern and
powerful way to include and reuse JavaScript code in web applications. You can feature-detect
support 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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;HTMLScriptElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;supports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;importmap&#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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// The importmap feature is supported.&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;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 89, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      89
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 108, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      108
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 89, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      89
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 16.4, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      16.4
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/import-maps-in-all-modern-browsers/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/import-maps/&quot; rel=&quot;noopener&quot;&gt;Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.logrocket.com/es-modules-in-browsers-with-import-maps/&quot; rel=&quot;noopener&quot;&gt;Using ES modules in browsers with import-maps&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/import-maps-in-all-modern-browsers/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hero image by &lt;a href=&quot;https://unsplash.com/@chuttersnap&quot; rel=&quot;noopener&quot;&gt;CHUTTERSNAP&lt;/a&gt; on
&lt;a href=&quot;https://unsplash.com/photos/fN603qcEA7g&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Push notifications are now supported cross-browser</title>
    <link href="https://web.dev/push-notifications-in-all-modern-browsers/"/>
    <updated>2023-03-28T00:00:00Z</updated>
    <id>https://web.dev/push-notifications-in-all-modern-browsers/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 128 128&quot; style=&quot;enable-background:new 0 0 128 128&quot; xml:space=&quot;preserve&quot;&gt;&lt;path d=&quot;M7.5 123.3c2.2 2.4 11.6-1.9 19-5.3C32 115.4 54 106.3 65 101.6c3-1.2 7.3-2.9 10.4-7 2.8-3.6 10-19-4.6-34.7-15-16-30.4-11.5-36.2-7.5A28.5 28.5 0 0 0 27.3 63c-5.2 11.6-12.7 32.9-15.7 41.3-2.3 6.1-6.4 16.5-4.1 19z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M18 86.8s.7 6.5 6.1 14.6c6.3 9.6 15 11.2 15 11.2l-5.8 2.4s-6.5-2-12.7-10.4c-3.8-5.3-5-11.6-5-11.6l2.3-6.2zm-5.6 15.4s1.5 5.6 4.7 9.7c3.8 5 8.6 6.5 8.6 6.5l-4.5 2s-3.3-.8-7-5.5c-2.9-3.5-3.7-7.6-3.7-7.6l1.9-5.1z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M10 116.4c-.2-.5-.2-1 0-1.4l25.4-53 4.2 16-26.8 38.7c-.7 1-2.3 1-2.8-.2z&quot; style=&quot;opacity:.44&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M41.6 83.2c12 14 25.5 12.2 30 8.6 4.5-3.5 8.1-15.6-3.7-29.3C55.3 48.2 41.3 52.2 38 55.3s-7.4 15.1 3.5 27.9z&quot; style=&quot;fill: var(--color-state-good-bg);&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M82.5 89c-4.3-3.7-6.6-3-9.7-1.8a26 26 0 0 1-18.9 0l2.6-6.2c5 1.7 8.7 1 12-1 4-2.4 9.6-5.6 18.3 1.6 3.6 3 7.3 5.1 10 4.2 2-.7 3-3.6 3.6-6l.2-1.3c.4-3.7 1.2-11.6 7.1-15.7 6.4-4.3 13-4.3 13-4.3l1.2 12c-3-.5-5.2.1-7 1-6.7 3.8-.8 18.2-11.3 23-10.1 4.8-18.4-3.3-21-5.6zM45.4 73.7l-4.3-3.9c8-8.9 5.8-15.4 4.3-20.2A26.4 26.4 0 0 1 44 38.8c-3-3.8-4.4-7.8-4.5-8-1.9-5.7-.5-11.2 2.8-16.4C48.7 4 60.5 4 60.5 4l4 10.5c-3-.1-12.8 0-15.8 4.8-3.8 6-1.3 9.6-1.2 10 .8-1 1.5-1.7 2.2-2.3 4.8-4.2 9-4.8 11.6-4.6a11 11 0 0 1 7.6 4.2c2 2.7 3 6.2 2.3 9.4a11 11 0 0 1-5.8 7.4 16 16 0 0 1-13 1.5v.3l.6 2c1.8 5.4 5 14.1-7.6 26.5zm7.4-37.5c.5.4 1.1.8 1.8 1 2 .8 4.4.6 7-.8 1.5-.9 1.7-1.7 1.7-2 .2-.9 0-2-.7-2.8a2.8 2.8 0 0 0-2-1.2c-1.6-.2-3.6.8-5.6 2.6-1 .9-1.7 2-2.3 3.2zm10 39.1-6.2-.1s3-16.7 12.5-19.5c1.8-.5 3.7-1 5.7-1.3l4-.8c.1-1.6-.5-3.6-1.3-5.9-.6-1.7-1.2-3.5-1.5-5.5-.6-3.9.4-7.3 3-9.6 3-2.9 7.9-3.8 13.4-2.5 3.2.7 5.5 2.2 7.6 3.6 2.9 2 4.6 3 8.2.5 4.3-2.9-1.4-14.3-4.4-21l11.3-4.6c1.5 3.3 8.8 20.3 4 30a11.5 11.5 0 0 1-8.1 6.2c-8 1.8-12.6-1.3-16-3.6-1.6-1-3-1.9-4.6-2.3-10.6-3 4.2 12.6-2.7 19.6-4.2 4.2-14.4 5.3-15 5.5-6.6 1.6-10 11.3-10 11.3z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M44 38.8c-.2 2.2-.3 3.5.3 6.4 2.7 2 8.7 2 8.7 2l-.6-2v-.3c-6.1-3-8.4-6.1-8.4-6.1zm-12.5 9.8-10.3-5 5.1-7.5 8.2 5.4zm-15.2-14A26 26 0 0 1 5 28.9l5.2-6c1.6 1.2 5 3.5 7.2 3.8l-1.1 7.9zM25.6 21.3 18 18.8a18 18 0 0 0 .7-8.3l7.9-1.3c.6 4 .3 8.2-1 12zM73 15.3l7.9-1.7L83 23.9l-7.8 1.7zM92.5 17.8 87 12c2.8-2.8 3.5-6.3 3.5-6.4L98.4 7c-.1.6-1.1 6.3-6 10.9zM95.5 48.6l7-2.2 2.3 7.7-7 2.1zM97.5 113l-7.9-1c.3-2.7-1.8-6.2-2.4-7l6.4-4.8c.5.6 4.7 6.4 4 12.8zM120.4 102.9c-3-.5-6-.6-9.1-.5l-.3-8c3.5-.2 7 0 10.6.6l-1.2 7.9zM109.6 113.9l5.6-5.7 7.8 7.6-5.6 5.7zM93.1 63.3 99 70l-6.6 5.8-5.8-6.6z&quot; fill=&quot;currentColor&quot;&gt;&lt;/path&gt;&lt;/svg&gt; &lt;/span&gt;&lt;strong&gt;Celebration&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; This web feature is now available in all three browser engines! &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Push notifications were standardized in 2016 with the release of the Push API and the Notification API, which are part of the W3C&#39;s Web Applications Working Group. These APIs provide the necessary functionality for web developers to incorporate push notifications into their web applications and for users to receive and interact with notifications on their web browsers. Push messages are notifications that are sent to a user&#39;s web browser from a website or application that the user has previously granted permission to send notifications. These messages can be used to alert the user of new content or updates, remind them of upcoming events or deadlines, or provide other important information. Push messages can be particularly useful for applications that need to deliver timely, relevant information to their users, such as news or sports apps, or for e-commerce websites that want to send users notifications about special offers or sales.&lt;/p&gt;
&lt;p&gt;To sign up for push notifications, first check if your browser supports them by checking for the &lt;code&gt;serviceWorker&lt;/code&gt; and &lt;code&gt;PushManager&lt;/code&gt; objects in the &lt;code&gt;navigator&lt;/code&gt; and &lt;code&gt;window&lt;/code&gt; objects.&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; &lt;strong&gt;Safari for iOS and iPadOS&lt;/strong&gt; supports push notifications as of version 16.4, but only for apps that were added to the Home Screen. Apple calls these Home Screen web apps. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;If push notifications are supported, use the &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt; keywords to register the service worker and subscribe for push notifications. Here is an example of how you can do this using JavaScript:&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;// Check if the browser supports push notifications.&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 string&quot;&gt;&quot;serviceWorker&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PushManager&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&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;    &lt;span class=&quot;token comment&quot;&gt;// Register the service worker.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; swReg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/sw.js&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;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Subscribe for push notifications.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pushSubscription &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; swReg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pushManager&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&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;userVisibleOnly&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Save the push subscription to the database.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;savePushSubscription&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pushSubscription&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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&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;// Handle errors.&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Error subscribing for push notifications.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&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 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;// Push notifications are not supported by the browser.&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Push notifications are not supported by the browser.&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;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 42, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      42
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 44, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      44
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 17, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      17
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 16, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      16
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/PushEvent/PushEvent#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&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; Prior to version 16, &lt;strong&gt;Safari on macOS&lt;/strong&gt; supported a &lt;a href=&quot;https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/NotificationProgrammingGuideForWebsites/PushNotifications/PushNotifications.html#//apple_ref/doc/uid/TP40013225-CH3-SW1&quot;&gt;proprietary version of push notifications&lt;/a&gt;. Since version 16, Safari for macOS now supports standard push notifications as other browsers. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-in-all-modern-browsers/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/push-notifications-overview/&quot;&gt;Push notifications overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Push_API&quot; rel=&quot;noopener&quot;&gt;Push API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/&quot; rel=&quot;noopener&quot;&gt;Web Push for Web Apps on iOS and iPadOS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/push-notifications-in-all-modern-browsers/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hero image by &lt;a href=&quot;https://unsplash.com/@jeans514&quot; rel=&quot;noopener&quot;&gt;Jean Bach&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/X8GTI5tx6UA&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>How to use multiple screens</title>
    <link href="https://web.dev/patterns/web-apps/multiple-screens/"/>
    <updated>2022-10-10T00:00:00Z</updated>
    <id>https://web.dev/patterns/web-apps/multiple-screens/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;the-modern-way&quot;&gt;The modern way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/multiple-screens/#the-modern-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-windowgetscreendetails&quot;&gt;Using &lt;code&gt;window.getScreenDetails()&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/multiple-screens/#using-windowgetscreendetails&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To make sure your browser supports the &lt;code&gt;getScreenDetails()&lt;/code&gt; method, first check if it exists
on the &lt;code&gt;window&lt;/code&gt; object. Then, call &lt;code&gt;window.getScreenDetails()&lt;/code&gt; to get attached screens. Adding an
event listener to adapt to changed screen details allows you to manage multiple screens according to
the needs of your app.&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;&lt;span class=&quot;token string&quot;&gt;&#39;getScreenDetails&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&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;// The Window Management API is supported.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; screenDetails &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getScreenDetails&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;  screenDetails&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;screenschange&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;// Handle screens change.&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 100, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      100
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 100, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
100
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-classic-way&quot;&gt;The classic way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/multiple-screens/#the-classic-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-windowscreen-method&quot;&gt;Using the window.screen method &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/multiple-screens/#using-the-windowscreen-method&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There&#39;s no classic way to control multi-screen layouts, but you can fall back to controlling the
current screen by using the &lt;code&gt;window.screen&lt;/code&gt; property and polyfill the new API shape.&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;&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 string&quot;&gt;&#39;getScreenDetails&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&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;// Returning a one-element array with the current screen,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// noting that there might be more.&lt;/span&gt;&lt;br /&gt;  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;getScreenDetails&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;screen&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 to `false`, noting that this might be a lie.&lt;/span&gt;&lt;br /&gt;  window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isExtended &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 punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      1
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      1
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 12, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      12
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Window/screen#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;progressive-enhancement&quot;&gt;Progressive enhancement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/multiple-screens/#progressive-enhancement&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The demo below shows how you can handle multiple screens with the Window Management API.
The code checks the browser capability first and then falls back to the classic way.&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;const&lt;/span&gt; detectButton &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;#detectScreen&#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;const&lt;/span&gt; createButton &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;#create&#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;const&lt;/span&gt; permissionLabel &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;#permissionStatus&#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;const&lt;/span&gt; screensAvailLabel &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;#screensAvail&#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;const&lt;/span&gt; popupUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./popup.html&#39;&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;let&lt;/span&gt; screenDetails &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&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;let&lt;/span&gt; permission &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&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;let&lt;/span&gt; currentScreenLength &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;detectButton&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;async&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;=&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 string&quot;&gt;&#39;getScreenDetails&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&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;    screenDetails &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getScreenDetails&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;    screenDetails&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;screenschange&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;screenDetails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;screens&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; currentScreenLength&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;        currentScreenLength &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; screenDetails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;screens&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;updateScreenInfo&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 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;      permission &lt;span class=&quot;token operator&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;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;permissions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&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;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;window-management&#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 punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;granted&#39;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Granted&#39;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;No Permission&#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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;    currentScreenLength &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; screenDetails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;screens&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;updateScreenInfo&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 keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    screenDetails &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    permission &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Window Management API - NOT SUPPORTED&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    currentScreenLength &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;br /&gt;    &lt;span class=&quot;token function&quot;&gt;updateScreenInfo&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;createButton&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 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;=&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;let&lt;/span&gt; screen &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; screenDetails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;screens&lt;span class=&quot;token punctuation&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;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&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; currentScreenLength&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;  options &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 literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;availLeft&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;availTop&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;availWidth&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;availHeight&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;  window&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;popupUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;_blank&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFeaturesFromOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&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 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;getFeaturesFromOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;options&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 punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;left=&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;br /&gt;    options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;,top=&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;br /&gt;    options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;y &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;,width=&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;br /&gt;    options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;,height=&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;br /&gt;    options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&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;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function-variable function&quot;&gt;updateScreenInfo&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;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  screensAvailLabel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentScreenLength&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  permissionLabel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; permission&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 string&quot;&gt;&#39;getScreenDetails&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; screenDetails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;screens&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &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;    createButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &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 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;    createButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &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 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;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/multiple-screens/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/articles/window-management/&quot; rel=&quot;noopener&quot;&gt;Managing several displays with the Window Management API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Screen&quot; rel=&quot;noopener&quot;&gt;Screen API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/multiple-screens/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
</content>
    <author>
      <name>Palances Liao</name>
    </author><author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>How to let the user share the website they are on</title>
    <link href="https://web.dev/patterns/web-apps/share/"/>
    <updated>2022-10-10T00:00:00Z</updated>
    <id>https://web.dev/patterns/web-apps/share/</id>
    <content type="html" mode="escaped">&lt;p&gt;Letting the user share the website they are on is a common web apps pattern
that you can find on many news sites, blogs, or shopping sites. Since linking is
one of the web&#39;s super powers, the hope is to acquire traffic from users who see
the shared link on social networking sites, or who receive it via chat messages
or even plain old school via email.&lt;/p&gt;
&lt;h2 id=&quot;the-modern-way&quot;&gt;The modern way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/share/#the-modern-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-web-share-api&quot;&gt;Using the Web Share API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/share/#using-the-web-share-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/web-share/&quot;&gt;Web Share API&lt;/a&gt; lets the user share data like
the URL of the page they are on, along with a title and descriptive text.
The &lt;code&gt;navigator.share()&lt;/code&gt; method of the Web Share API invokes the native sharing
mechanism of the device. It returns a promise and takes a single argument with
the to-be-shared data. Possible values are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;url&lt;/code&gt;: A string representing the URL to be shared.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;text&lt;/code&gt;: A string representing text to be shared.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;title&lt;/code&gt;: A string representing a title to be shared. May be ignored by the browser.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 89, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      89
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 71, Behind a flag&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;flag&quot; title=&quot;Behind a flag&quot; aria-label=&quot;Behind a flag&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 93, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
93
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari 12.1, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
12.1
&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Navigator/share#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-classic-way&quot;&gt;The classic way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/share/#the-classic-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-a-social-networking-sites-share-intent&quot;&gt;Using a social networking site&#39;s share intent &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/share/#using-a-social-networking-sites-share-intent&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Not all browsers support the Web Share API yet. A fallback is thus to integrate with
your target audience&#39;s most popular social networking sites. A popular example
is Twitter, whose &lt;a href=&quot;https://developer.twitter.com/en/docs/twitter-for-websites/tweet-button/guides/web-intent&quot; rel=&quot;noopener&quot;&gt;Web Intent URL&lt;/a&gt; allows for a text and a URL to be shared. The method typically consists
of crafting a URL and opening it in a browser.&lt;/p&gt;
&lt;h2 id=&quot;ui-considerations&quot;&gt;UI considerations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/share/#ui-considerations&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It is a nice touch to respect the platform&#39;s established share icon according to the UI
guidelines of the operating system vendors.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows:
&lt;svg style=&quot;display: block; background-color: white; width: 24px; height: 24px;&quot; width=&quot;48&quot; height=&quot;48&quot; viewBox=&quot;0 0 48 48&quot; fill=&quot;none&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
&lt;path d=&quot;M31.605 6.83811C31.2415 6.49733 30.7103 6.40497 30.2531 6.60304C29.7959 6.80111 29.5 7.25178 29.5 7.75003V13.2223C29.1425 13.2305 28.7251 13.2514 28.26 13.2944C26.725 13.4362 24.6437 13.8204 22.4841 14.799C18.0824 16.7935 13.5579 21.1728 12.5081 30.3581C12.4493 30.8729 12.7141 31.3706 13.174 31.6094C13.6338 31.8482 14.1932 31.7785 14.5805 31.4343C18.9164 27.5801 22.9778 25.9209 25.9168 25.2155C27.3897 24.862 28.5872 24.7466 29.4032 24.718C29.4361 24.7169 29.4684 24.7158 29.5 24.715V30.25C29.5 30.7483 29.7959 31.1989 30.2531 31.397C30.7103 31.5951 31.2415 31.5027 31.605 31.162L43.605 19.9119C43.857 19.6756 44 19.3455 44 19C44 18.6545 43.857 18.3244 43.605 18.0881L31.605 6.83811ZM30.606 15.7422L30.6257 15.7438L30.6285 15.7441L30.6269 15.7439C30.9779 15.7787 31.3272 15.6635 31.5888 15.4268C31.8506 15.1899 32 14.8532 32 14.5V10.6353L40.9224 19L32 27.3647V23.5C32 22.8696 31.5462 22.34 30.9051 22.2597L30.9036 22.2595L30.902 22.2593L30.8982 22.2588L30.8883 22.2577L30.8597 22.2545C30.8368 22.252 30.8062 22.249 30.768 22.2456C30.6917 22.2389 30.5853 22.2309 30.4506 22.2242C30.1812 22.2109 29.7982 22.2026 29.3156 22.2195C28.3503 22.2534 26.9854 22.3881 25.3333 22.7845C22.6531 23.4278 19.2341 24.7565 15.5547 27.4384C17.0405 21.3588 20.4181 18.4798 23.5159 17.0761C25.3563 16.2422 27.15 15.9076 28.49 15.7838C29.1577 15.7221 29.7057 15.7134 30.081 15.7196C30.2684 15.7227 30.412 15.7295 30.5052 15.7351C30.5517 15.738 30.5856 15.7405 30.606 15.7422ZM12.25 8.00003C8.79822 8.00003 6 10.7983 6 14.25V35.75C6 39.2018 8.79822 42 12.25 42H33.75C37.2018 42 40 39.2018 40 35.75V33.5C40 32.8097 39.4404 32.25 38.75 32.25C38.0596 32.25 37.5 32.8097 37.5 33.5V35.75C37.5 37.8211 35.8211 39.5 33.75 39.5H12.25C10.1789 39.5 8.5 37.8211 8.5 35.75V14.25C8.5 12.179 10.1789 10.5 12.25 10.5H20.5C21.1904 10.5 21.75 9.94039 21.75 9.25003C21.75 8.55967 21.1904 8.00003 20.5 8.00003H12.25Z&quot; fill=&quot;#212121&quot;&gt;&lt;/path&gt;
&lt;/svg&gt;&lt;/li&gt;
&lt;li&gt;Apple:
&lt;svg style=&quot;display: block; background-color: white&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; width=&quot;24&quot;&gt;&lt;path d=&quot;M0 0h24v24H0V0z&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M16 5l-1.42 1.42-1.59-1.59V16h-1.98V4.83L9.42 6.42 8 5l4-4 4 4zm4 5v11c0 1.1-.9 2-2 2H6c-1.11 0-2-.9-2-2V10c0-1.11.89-2 2-2h3v2H6v11h12V10h-3V8h3c1.1 0 2 .89 2 2z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/li&gt;
&lt;li&gt;Android and other operating systems:
&lt;svg style=&quot;display: block; background-color: white&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; width=&quot;24&quot;&gt;&lt;path d=&quot;M0 0h24v24H0z&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;&lt;path d=&quot;M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;progressive-enhancement&quot;&gt;Progressive enhancement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/share/#progressive-enhancement&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The snippet below uses the Web Share API when it is supported, then falls back to
Twitter&#39;s Web Intent URL.&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;// DOM references&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; button &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;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 keyword&quot;&gt;const&lt;/span&gt; icon &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; button&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;.icon&#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;const&lt;/span&gt; canonical &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;link[rel=&quot;canonical&quot;]&#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;// Find out if the user is on a device made by Apple.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;IS_MAC&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;Mac&lt;span class=&quot;token alternation keyword&quot;&gt;|&lt;/span&gt;iPhone&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;platform&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;// Find out if the user is on a Windows device.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;IS_WINDOWS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;Win&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;platform&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;// For Apple devices or Windows, use the platform-specific share icon.&lt;/span&gt;&lt;br /&gt;icon&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;share&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IS_MAC&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;mac&#39;&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 constant&quot;&gt;IS_WINDOWS&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;windows&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;button&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;async&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;=&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;// Title and text are identical, since the title may actually be ignored.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Use the canonical URL, if it exists, else, the current location.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canonical&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&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;// Feature detection to see if the Web Share API is supported.&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 string&quot;&gt;&#39;share&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;share&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;        url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        title&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 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;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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 the user cancels, an `AbortError` is thrown.&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;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AbortError&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;        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Fallback to use Twitter&#39;s Web Intent URL.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// (https://developer.twitter.com/en/docs/twitter-for-websites/tweet-button/guides/web-intent)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shareURL &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;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://twitter.com/intent/tweet&#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;const&lt;/span&gt; params &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;URLSearchParams&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;  params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;text&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; text&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;  params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;url&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  shareURL&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;search &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; params&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;open&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shareURL&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;_blank&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;popup,noreferrer,noopener&#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;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/share/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/web-share/&quot;&gt;Web Share API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/share/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>New patterns for amazing apps</title>
    <link href="https://web.dev/new-patterns-for-amazing-apps/"/>
    <updated>2022-10-10T00:00:00Z</updated>
    <id>https://web.dev/new-patterns-for-amazing-apps/</id>
    <content type="html" mode="escaped">&lt;p&gt;No matter what you build—be it a next generation video editing app, an addictive word game, or a
future online social networking app—you will always find yourself in need of a few basic building
blocks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The video editing app will probably allow the user to &lt;em&gt;save&lt;/em&gt; the edited video.&lt;/li&gt;
&lt;li&gt;Your game will maybe allow the user to &lt;em&gt;share&lt;/em&gt; game progress with friends.&lt;/li&gt;
&lt;li&gt;An online social networking app will highly likely allow the user to &lt;em&gt;paste&lt;/em&gt; images into a post.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;no-universal-way-to-realize-these-patterns&quot;&gt;No universal way to realize these patterns &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/new-patterns-for-amazing-apps/#no-universal-way-to-realize-these-patterns&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These were just a couple of examples of such patterns, and there are many more. But all of these
have one thing in common: there is no universal way to realize them.&lt;/p&gt;
&lt;h3 id=&quot;sharing-progress&quot;&gt;Sharing progress &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/new-patterns-for-amazing-apps/#sharing-progress&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For example, not all browsers implement the &lt;a href=&quot;https://web.dev/web-share/&quot;&gt;Web Share API&lt;/a&gt;, so in some cases you will
have to fall back to a different approach, like
&lt;a href=&quot;https://developer.twitter.com/en/docs/twitter-for-websites/tweet-button/guides/web-intent&quot; rel=&quot;noopener&quot;&gt;Twitter&#39;s Web Intents&lt;/a&gt;,
or copying to the clipboard, which is the
&lt;a href=&quot;https://twitter.com/powerlanguish/status/1471493886031773707&quot; rel=&quot;noopener&quot;&gt;approach&lt;/a&gt; chosen in
&lt;a href=&quot;https://www.nytimes.com/games/wordle/index.html&quot; rel=&quot;noopener&quot;&gt;Wordle&lt;/a&gt; when the Web Share API isn&#39;t implemented.
Phew, barely got this one:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Wordle 471 6/6&lt;br /&gt;&lt;br /&gt;⬛⬛⬛⬛🟨&lt;br /&gt;🟩⬛⬛⬛🟨&lt;br /&gt;🟩🟩🟩⬛⬛&lt;br /&gt;🟩🟩🟩⬛⬛&lt;br /&gt;🟩🟩🟩🟩⬛&lt;br /&gt;🟩🟩🟩🟩🟩&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;saving-files&quot;&gt;Saving files &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/new-patterns-for-amazing-apps/#saving-files&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When it comes to saving, the go-to approach is to use the
&lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt;, so you end up with a &lt;code&gt;FileSystemFileHandle&lt;/code&gt;, which
allows you to implement a true &lt;a href=&quot;https://web.dev/excalidraw-and-fugu/#saving-files&quot;&gt;save, edit, save flow&lt;/a&gt;. The next
best thing is to fall back to a classic &lt;code&gt;&amp;lt;a download&amp;gt;&lt;/code&gt;, which likewise lets the user save data, but
has the downside of creating new files on each download, so they end up with &lt;code&gt;my-video.mp4&lt;/code&gt;,
&lt;code&gt;my-video (1).mp4&lt;/code&gt;, &lt;code&gt;my-video (2).mp4&lt;/code&gt;, etc.&lt;/p&gt;
&lt;h3 id=&quot;pasting-images&quot;&gt;Pasting images &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/new-patterns-for-amazing-apps/#pasting-images&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To conclude the introductory examples, not all browsers support pasting images into a web app, so
you can fall back to using the Drag and Drop API or showing a file picker, which is not as elegant as
the &lt;a href=&quot;https://web.dev/async-clipboard&quot;&gt;Async Clipboard API&lt;/a&gt;, but at least it works.&lt;/p&gt;
&lt;h2 id=&quot;the-new-patterns&quot;&gt;The new patterns &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/new-patterns-for-amazing-apps/#the-new-patterns&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With this out of the way, the new pattern sections are:&lt;/p&gt;
&lt;h3 id=&quot;clipboard-patterns&quot;&gt;Clipboard patterns &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/new-patterns-for-amazing-apps/#clipboard-patterns&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/patterns/clipboard/&quot;&gt;Clipboard patterns&lt;/a&gt; for everything concerned with the system clipboard like
copying and pasting of all sorts of things.&lt;/p&gt;
&lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;200&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/eWdb4ici0H9p8tueeUUG.svg&quot; width=&quot;200&quot; /&gt;
&lt;h3 id=&quot;files-patterns&quot;&gt;Files patterns &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/new-patterns-for-amazing-apps/#files-patterns&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/patterns/files/&quot;&gt;Files patterns&lt;/a&gt; for everything concerned with files and directories; be it
saving, opening, dragging and dropping, receiving or sharing.&lt;/p&gt;
&lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;200&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Smch2ZFw5PxkiQZPkl3v.svg&quot; width=&quot;200&quot; /&gt;
&lt;h3 id=&quot;web-apps-patterns&quot;&gt;Web apps patterns &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/new-patterns-for-amazing-apps/#web-apps-patterns&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://web.dev/patterns/web-apps/&quot;&gt;Web apps patterns&lt;/a&gt; for everything concerned with advanced app
features like providing app shortcuts, periodically syncing data in the background, showing app
badges, and many more.&lt;/p&gt;
&lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;200&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Yyuu9Wob0ix4z2jgBY1S.svg&quot; width=&quot;200&quot; /&gt;
&lt;h2 id=&quot;feedback&quot;&gt;Feedback &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/new-patterns-for-amazing-apps/#feedback&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I hope these patterns will help you build amazing apps, and I&#39;m looking forward to your feedback!
You can provide feedback by tweeting at &lt;a href=&quot;https://twitter.com/ChromiumDev&quot; rel=&quot;noopener&quot;&gt;@ChromiumDev&lt;/a&gt; or
&lt;a href=&quot;https://github.com/GoogleChrome/web.dev/issues/new/choose&quot; rel=&quot;noopener&quot;&gt;filing an Issue&lt;/a&gt;. In both cases, tag
&lt;code&gt;@tomayac&lt;/code&gt; to make sure I see it.&lt;/p&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/new-patterns-for-amazing-apps/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;m grateful to &lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt; for his help with reviewing and editing
the patterns. Thanks to &lt;a href=&quot;https://github.com/petele&quot; rel=&quot;noopener&quot;&gt;Pete LePage&lt;/a&gt;,
&lt;a href=&quot;https://twitter.com/devnook&quot; rel=&quot;noopener&quot;&gt;Ewa Gasperowicz&lt;/a&gt;, &lt;a href=&quot;https://twitter.com/rachelandrew&quot; rel=&quot;noopener&quot;&gt;Rachel Andrew&lt;/a&gt;,
&lt;a href=&quot;https://www.linkedin.com/in/kenpascal/&quot; rel=&quot;noopener&quot;&gt;Ken Pascal&lt;/a&gt;,
and &lt;a href=&quot;https://twitter.com/matthiasrohmer&quot; rel=&quot;noopener&quot;&gt;Matthias Rohmer&lt;/a&gt;
for all their technical and organizational support and
encouragements to land this. The entire patterns project would not have been possible without the
help of the authors of the individual patterns, namely
&lt;a href=&quot;https://www.harrytheo.com/&quot; rel=&quot;noopener&quot;&gt;Harry Theodoulou&lt;/a&gt;, &lt;a href=&quot;https://web.dev/authors/conwayt/&quot;&gt;Tony Conway&lt;/a&gt;,
&lt;a href=&quot;https://web.dev/new-patterns-for-amazing-apps/authors/pliao/&quot;&gt;Palances Liao&lt;/a&gt;, &lt;a href=&quot;https://web.dev/authors/chuijun/&quot;&gt;Cecilia Cong&lt;/a&gt;,
&lt;a href=&quot;https://github.com/beaufortfrancois&quot; rel=&quot;noopener&quot;&gt;François Beaufort&lt;/a&gt;, and
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Sample pattern</title>
    <link href="https://web.dev/handbook/content-types/example-pattern/"/>
    <updated>2022-10-10T00:00:00Z</updated>
    <id>https://web.dev/handbook/content-types/example-pattern/</id>
    <content type="html" mode="escaped">&lt;p&gt;The pattern layout supports the full range of markdown elements/blocks as demonstrated in
the &lt;a href=&quot;https://web.dev/handbook/content-types/example-post&quot;&gt;example post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin dictum a massa
sit amet ullamcorper. Suspendisse auctor ultrices ante, nec tempus nibh varius
at.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-quaternary-box-bg color-quaternary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &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;Code brackets&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;M9.41 16.59L8 18l-6-6 6-6 1.41 1.41L4.83 12l4.58 4.59zm5.18-9.18L16 6l6 6-6 6-1.41-1.41L19.17 12l-4.58-4.59z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Try it&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; &lt;a href=&quot;https://web.dev/handbook/content-types/example-pattern/#&quot;&gt;Sample link&lt;/a&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;image,-inline&quot;&gt;Image, Inline &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/content-types/example-pattern/#image,-inline&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;figure data-float=&quot;right&quot;&gt;
  &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;306&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 200px) 200px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WHh2eQoyBxylhgPdYOis.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WHh2eQoyBxylhgPdYOis.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WHh2eQoyBxylhgPdYOis.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WHh2eQoyBxylhgPdYOis.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WHh2eQoyBxylhgPdYOis.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WHh2eQoyBxylhgPdYOis.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WHh2eQoyBxylhgPdYOis.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WHh2eQoyBxylhgPdYOis.png?auto=format&amp;w=400 400w&quot; width=&quot;200&quot; /&gt;
  &lt;figcaption&gt;
    Inline right, outlined image.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin dictum a massa
sit amet ullamcorper. Suspendisse auctor ultrices ante, nec tempus nibh varius
at. Cras ligula lacus, porta vitae maximus a, ultrices a mauris. Vestibulum
porta dolor erat, vel molestie dolor posuere in. Nam vel elementum augue. Nam
quis enim blandit, posuere justo dignissim, scelerisque diam. Fusce aliquet urna
ac blandit ullamcorper. Proin et semper nibh, sit amet imperdiet velit. Morbi at
quam sem. Integer et erat ac mi scelerisque suscipit et vitae nulla. Aliquam
scelerisque efficitur ante ut facilisis. Aenean et risus fringilla, hendrerit
sapien et, tincidunt orci. Aenean sed tellus aliquam, consectetur metus in,
tempus enim.&lt;/p&gt;
&lt;h2 id=&quot;sample-snippet&quot;&gt;Sample snippet &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/content-types/example-pattern/#sample-snippet&quot;&gt;#&lt;/a&gt;&lt;/h2&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;const&lt;/span&gt; button &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;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;br /&gt;button&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;async&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;=&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;Testing 123&#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;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/handbook/content-types/example-pattern/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>How to copy images</title>
    <link href="https://web.dev/patterns/clipboard/copy-images/"/>
    <updated>2022-10-10T00:00:00Z</updated>
    <id>https://web.dev/patterns/clipboard/copy-images/</id>
    <content type="html" mode="escaped">&lt;p&gt;Many modern browsers support copying images to the clipboard in the formats PNG and SVG. Other formats are not yet supported for security reasons.&lt;/p&gt;
&lt;h2 id=&quot;the-modern-way&quot;&gt;The modern way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-images/#the-modern-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-async-clipboard-api&quot;&gt;Using the Async Clipboard API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-images/#using-the-async-clipboard-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/patterns/clipboard/copy-images/%E2%80%8B%E2%80%8Bhttps://developer.mozilla.org/docs/Web/API/Clipboard/write&quot;&gt;&lt;code&gt;Clipboard.write()&lt;/code&gt;&lt;/a&gt; method takes an array of &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ClipboardItem&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ClipboardItem&lt;/code&gt;&lt;/a&gt; objects and returns a Promise that resolves when the image is successfully written to the clipboard. &lt;code&gt;Clipboard.write()&lt;/code&gt; can only be used from the &lt;code&gt;window&lt;/code&gt; object that has focus.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 66, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      66
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 87, Behind a flag&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;flag&quot; title=&quot;Behind a flag&quot; aria-label=&quot;Behind a flag&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
79
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari 13.1, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
13.1
&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Clipboard/write#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-classic-way&quot;&gt;The classic way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-images/#the-classic-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-navigatorclipboardwritetext&quot;&gt;Using &lt;code&gt;navigator.clipboard.writeText()&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-images/#using-navigatorclipboardwritetext&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While not all browsers support &lt;code&gt;navigator.clipboard.write()&lt;/code&gt; for binary data
yet, they all support &lt;code&gt;navigator.clipboard.writeText()&lt;/code&gt;. If you want to copy
an SVG image, instead of copying the image itself, you can copy the SVG source code. For PNG images, you are unfortunately out of luck.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 66, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      66
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 63, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      63
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      79
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 13.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      13.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Clipboard/writeText#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;progressive-enhancement&quot;&gt;Progressive enhancement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-images/#progressive-enhancement&quot;&gt;#&lt;/a&gt;&lt;/h2&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;const&lt;/span&gt; button &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;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 keyword&quot;&gt;const&lt;/span&gt; img &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;img&#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;button&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;async&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;=&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;const&lt;/span&gt; responsePromise &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;img&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src&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;    &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 string&quot;&gt;&#39;write&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&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;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&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;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClipboardItem&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 string-property property&quot;&gt;&#39;image/svg+xml&#39;&lt;/span&gt;&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;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; blob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; responsePromise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;blob&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 function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&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;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 comment&quot;&gt;// Image copied as image.&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;const&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; responsePromise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&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 keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&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;// Image copied as source code.&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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-images/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/async-clipboard/&quot;&gt;Unblocking clipboard access&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/clipboard/copy-images/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>How to drag and drop directories</title>
    <link href="https://web.dev/patterns/files/drag-and-drop-directories/"/>
    <updated>2022-10-10T00:00:00Z</updated>
    <id>https://web.dev/patterns/files/drag-and-drop-directories/</id>
    <content type="html" mode="escaped">&lt;p&gt;The
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API&quot; rel=&quot;noopener&quot;&gt;HTML Drag and Drop interfaces&lt;/a&gt;
enable web applications to accept
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop&quot; rel=&quot;noopener&quot;&gt;dragged and dropped files&lt;/a&gt;
on a web page. During a drag and drop operation, dragged file and directory items are associated
with file entries and directory entries respectively. When it comes to dragging and dropping files
into the browser, there are two ways of doing it: the modern and the classic way.&lt;/p&gt;
&lt;h2 id=&quot;the-modern-way&quot;&gt;The modern way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-directories/#the-modern-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-file-system-access-apis-datatransferitemgetasfilesystemhandle-method&quot;&gt;Using the File System Access API&#39;s &lt;code&gt;DataTransferItem.getAsFileSystemHandle()&lt;/code&gt; method &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-directories/#using-the-file-system-access-apis-datatransferitemgetasfilesystemhandle-method&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;DataTransferItem.getAsFileSystemHandle()&lt;/code&gt; method returns a promise with a
&lt;code&gt;FileSystemFileHandle&lt;/code&gt; object if the dragged item is a file, and a promise with a
&lt;code&gt;FileSystemDirectoryHandle&lt;/code&gt; object if the dragged item is a directory. These handles let you read,
and optionally write back to the file or directory. Note that the Drag and Drop interface&#39;s
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/DataTransferItem/kind&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;DataTransferItem.kind&lt;/code&gt;&lt;/a&gt; will be
&lt;code&gt;&amp;quot;file&amp;quot;&lt;/code&gt; for both files &lt;em&gt;and&lt;/em&gt; directories, whereas the File System Access API&#39;s
&lt;a href=&quot;https://wicg.github.io/file-system-access/#dom-filesystemhandle-kind&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileSystemHandle.kind&lt;/code&gt;&lt;/a&gt; will
be &lt;code&gt;&amp;quot;file&amp;quot;&lt;/code&gt; for files and &lt;code&gt;&amp;quot;directory&amp;quot;&lt;/code&gt; for directories.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 86, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      86
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 86, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
86
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/DataTransferItem/getAsFileSystemHandle#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-classic-way&quot;&gt;The classic way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-directories/#the-classic-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-non-standard-datatransferitemwebkitgetasentry-method&quot;&gt;Using the non-standard &lt;code&gt;DataTransferItem.webkitGetAsEntry()&lt;/code&gt; method &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-directories/#using-the-non-standard-datatransferitemwebkitgetasentry-method&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;DataTransferItem.webkitGetAsEntry()&lt;/code&gt; method returns the drag data item&#39;s &lt;code&gt;FileSystemFileEntry&lt;/code&gt;
if the item is a file, and &lt;code&gt;FileSystemDirectoryEntry&lt;/code&gt; if the item is a directory. While you can read
the file or directory, there is no way to write back to them. This method has the disadvantage that
is not on the standards track, but has the advantage that it supports directories.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 13, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      13
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 50, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      50
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 14, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      14
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 11.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      11.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/DataTransferItem/webkitGetAsEntry#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;progressive-enhancement&quot;&gt;Progressive enhancement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-directories/#progressive-enhancement&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The snippet below uses the modern File System Access API&#39;s
&lt;code&gt;DataTransferItem.getAsFileSystemHandle()&lt;/code&gt; method when it is supported, then falls back to the
non-standard &lt;code&gt;DataTransferItem.webkitGetAsEntry()&lt;/code&gt; method, and finally falls back to the classic
&lt;code&gt;DataTransferItem.getAsFile()&lt;/code&gt; method. Be sure to check the type of each &lt;code&gt;handle&lt;/code&gt;, since it could be
either of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FileSystemDirectoryHandle&lt;/code&gt; when the modern code path is chosen.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FileSystemDirectoryEntry&lt;/code&gt; when the non-standard code path is chosen.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All types have a &lt;code&gt;name&lt;/code&gt; property, so logging it is fine and will always work.&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;// Run feature detection.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supportsFileSystemAccessAPI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;getAsFileSystemHandle&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataTransferItem&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 keyword&quot;&gt;const&lt;/span&gt; supportsWebkitGetAsEntry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;webkitGetAsEntry&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataTransferItem&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;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// This is the drag and drop zone.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; elem &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;main&#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;// Prevent navigation.&lt;/span&gt;&lt;br /&gt;elem&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;dragover&#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 parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  e&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Visually highlight the drop zone.&lt;/span&gt;&lt;br /&gt;elem&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;dragenter&#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 parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;outline &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;solid red 1px&#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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Visually unhighlight the drop zone.&lt;/span&gt;&lt;br /&gt;elem&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;dragleave&#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 parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;outline &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// This is where the drop is handled.&lt;/span&gt;&lt;br /&gt;elem&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;drop&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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 operator&quot;&gt;=&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;// Prevent navigation.&lt;/span&gt;&lt;br /&gt;  e&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 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;supportsFileSystemAccessAPI &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;supportsWebkitGetAsEntry&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;// Cannot handle directories.&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 comment&quot;&gt;// Unhighlight the drop zone.&lt;/span&gt;&lt;br /&gt;  elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;outline &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#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;// Prepare an array of promises…&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fileHandlesPromises &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 operator&quot;&gt;...&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;items&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// …by including only files (where file misleadingly means actual file _or_&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// directory)…&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&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 parameter&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;file&#39;&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;// …and, depending on previous feature detection…&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&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 parameter&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;br /&gt;      supportsFileSystemAccessAPI&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// …either get a modern `FileSystemHandle`…&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAsFileSystemHandle&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;// …or a classic `FileSystemFileEntry`.&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;webkitGetAsEntry&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;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Loop over the array of promises.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; fileHandlesPromises&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 is where we can actually exclusively act on the directories.&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;handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;directory&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isDirectory&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 template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Directory: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;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;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-directories/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/drag-and-drop/&quot;&gt;Drag and Drop API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-directories/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>How to drag and drop files</title>
    <link href="https://web.dev/patterns/files/drag-and-drop-files/"/>
    <updated>2022-10-10T00:00:00Z</updated>
    <id>https://web.dev/patterns/files/drag-and-drop-files/</id>
    <content type="html" mode="escaped">&lt;p&gt;The
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API&quot; rel=&quot;noopener&quot;&gt;HTML Drag and Drop interfaces&lt;/a&gt;
enable web applications to accept
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop&quot; rel=&quot;noopener&quot;&gt;dragged and dropped files&lt;/a&gt;
on a web page. During a drag and drop operation, dragged file and directory items are associated
with file entries and directory entries respectively. When it comes to dragging and dropping files
into the browser, there are two ways of doing it: the modern and the classic way.&lt;/p&gt;
&lt;h2 id=&quot;the-modern-way&quot;&gt;The modern way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-files/#the-modern-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-file-system-access-apis-datatransferitemgetasfilesystemhandle-method&quot;&gt;Using the File System Access API&#39;s &lt;code&gt;DataTransferItem.getAsFileSystemHandle()&lt;/code&gt; method &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-files/#using-the-file-system-access-apis-datatransferitemgetasfilesystemhandle-method&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;DataTransferItem.getAsFileSystemHandle()&lt;/code&gt; method returns a promise with a
&lt;code&gt;FileSystemFileHandle&lt;/code&gt; object if the dragged item is a file, and a promise with a
&lt;code&gt;FileSystemDirectoryHandle&lt;/code&gt; object if the dragged item is a directory. These handles let you read,
and optionally write back to the file or directory. Note that the Drag and Drop interface&#39;s
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/DataTransferItem/kind&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;DataTransferItem.kind&lt;/code&gt;&lt;/a&gt; will be
&lt;code&gt;&amp;quot;file&amp;quot;&lt;/code&gt; for both files &lt;em&gt;and&lt;/em&gt; directories, whereas the File System Access API&#39;s
&lt;a href=&quot;https://wicg.github.io/file-system-access/#dom-filesystemhandle-kind&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileSystemHandle.kind&lt;/code&gt;&lt;/a&gt; will
be &lt;code&gt;&amp;quot;file&amp;quot;&lt;/code&gt; for files and &lt;code&gt;&amp;quot;directory&amp;quot;&lt;/code&gt; for directories.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 86, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      86
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 86, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
86
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/DataTransferItem/getAsFileSystemHandle#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-classic-way&quot;&gt;The classic way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-files/#the-classic-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-classic-datatransferitemgetasfile-method&quot;&gt;Using the classic &lt;code&gt;DataTransferItem.getAsFile()&lt;/code&gt; method &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-files/#using-the-classic-datatransferitemgetasfile-method&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;DataTransferItem.getAsFile()&lt;/code&gt; method returns the drag data item&#39;s &lt;code&gt;File&lt;/code&gt; object. If the item is
not a file, this method returns &lt;code&gt;null&lt;/code&gt;. While you can read the file, there is no way to write back
to it. This method has the disadvantage that it does not support directories.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 11, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      11
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 50, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      50
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 12, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      12
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 5.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      5.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/DataTransferItem/getAsFile#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;progressive-enhancement&quot;&gt;Progressive enhancement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-files/#progressive-enhancement&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The snippet below uses the modern File System Access API&#39;s
&lt;code&gt;DataTransferItem.getAsFileSystemHandle()&lt;/code&gt; method when it is supported, then falls back to the
non-standard &lt;code&gt;DataTransferItem.webkitGetAsEntry()&lt;/code&gt; method, and finally falls back to the classic
&lt;code&gt;DataTransferItem.getAsFile()&lt;/code&gt; method. Be sure to check the type of each &lt;code&gt;handle&lt;/code&gt;, since it could be
either of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FileSystemFileHandle&lt;/code&gt; when the modern code path is chosen.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;File&lt;/code&gt; when the classic code path is chosen.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All types have a &lt;code&gt;name&lt;/code&gt; property, so logging it is fine and will always work.&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;// Run feature detection.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supportsFileSystemAccessAPI &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;getAsFileSystemHandle&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DataTransferItem&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;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// This is the drag and drop zone.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; elem &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;main&#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;// Prevent navigation.&lt;/span&gt;&lt;br /&gt;elem&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;dragover&#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 parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  e&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Visually highlight the drop zone.&lt;/span&gt;&lt;br /&gt;elem&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;dragenter&#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 parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;outline &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;solid red 1px&#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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Visually unhighlight the drop zone.&lt;/span&gt;&lt;br /&gt;elem&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;dragleave&#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 parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;outline &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// This is where the drop is handled.&lt;/span&gt;&lt;br /&gt;elem&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;drop&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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 operator&quot;&gt;=&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;// Prevent navigation.&lt;/span&gt;&lt;br /&gt;  e&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 comment&quot;&gt;// Unhighlight the drop zone.&lt;/span&gt;&lt;br /&gt;  elem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;outline &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&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;// Prepare an array of promises…&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fileHandlesPromises &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 operator&quot;&gt;...&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;items&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// …by including only files (where file misleadingly means actual file _or_&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// directory)…&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&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 parameter&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;file&#39;&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;// …and, depending on previous feature detection…&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&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 parameter&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;br /&gt;      supportsFileSystemAccessAPI&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// …either get a modern `FileSystemHandle`…&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAsFileSystemHandle&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;// …or a classic `File`.&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAsFile&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;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Loop over the array of promises.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; fileHandlesPromises&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 is where we can actually exclusively act on the files.&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;handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;file&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isFile&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 template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;File: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;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;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-files/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/drag-and-drop/&quot;&gt;Drag and Drop API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/drag-and-drop-files/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>How to open a directory</title>
    <link href="https://web.dev/patterns/files/open-a-directory/"/>
    <updated>2022-10-10T00:00:00Z</updated>
    <id>https://web.dev/patterns/files/open-a-directory/</id>
    <content type="html" mode="escaped">&lt;p&gt;Dealing with directories is not something you will cope with on a daily basis,
but occasionally the use case arises, such as wanting to process all images in a directory.
With the File System Access API, users can now open directories in the browser
and decide if they need write access or not.&lt;/p&gt;
&lt;h2 id=&quot;the-modern-way&quot;&gt;The modern way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-a-directory/#the-modern-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-file-system-access-apis-showdirectorypicker-method&quot;&gt;Using the File System Access API&#39;s &lt;code&gt;showDirectoryPicker()&lt;/code&gt; method &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-a-directory/#using-the-file-system-access-apis-showdirectorypicker-method&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To open a directory, call
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/showDirectoryPicker&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;showDirectoryPicker()&lt;/code&gt;&lt;/a&gt;,
which returns a promise with the picked directory. If you need write access, you can pass &lt;code&gt;{ mode: &#39;readwrite&#39; }&lt;/code&gt; to the method.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 86, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      86
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 86, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
86
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/web/api/window/showdirectorypicker#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-classic-way&quot;&gt;The classic way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-a-directory/#the-classic-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-lessinput-type=file-webkitdirectorygreater-element&quot;&gt;Using the &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot; webkitdirectory&amp;gt;&lt;/code&gt; element &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-a-directory/#using-the-lessinput-type=file-webkitdirectorygreater-element&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot; webkitdirectory&amp;gt;&lt;/code&gt; element on a page allows the user to click it and open
a directory. The trick now consists of inserting the element invisibly into a page with JavaScript and click it programmatically.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 7, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      7
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 50, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      50
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 13, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      13
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 11.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      11.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLInputElement/webkitdirectory#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;progressive-enhancement&quot;&gt;Progressive enhancement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-a-directory/#progressive-enhancement&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The method below uses the File System Access API when it&#39;s supported
and else falls back to the classic approach. In both cases the function
returns a directory, but in case of where the File System Access API
is supported, each file object also has a &lt;code&gt;FileSystemDirectoryHandle&lt;/code&gt; stored in
the &lt;code&gt;directoryHandle&lt;/code&gt; property and a &lt;code&gt;FileSystemFileHandle&lt;/code&gt; stored in the &lt;code&gt;handle&lt;/code&gt; property,
so you can optionally serialize the handles to disk.&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;const&lt;/span&gt; openDirectory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;// Feature detection. The API needs to be supported&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// and the app not run in an iframe.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supportsFileSystemAccess &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;showDirectoryPicker&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&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;span class=&quot;token operator&quot;&gt;=&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;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;self &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;top&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;catch&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 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 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;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 the File System Access API is supported…&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;supportsFileSystemAccess&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;let&lt;/span&gt; directoryStructure &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&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;// Recursive function that walks the directory structure.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getFiles&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;dirHandle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dirHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; dirs &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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; files &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;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 keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; dirHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&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 keyword&quot;&gt;const&lt;/span&gt; nestedPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;path&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&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 keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;file&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;          files&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;            entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFile&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;then&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 parameter&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;              file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;directoryHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dirHandle&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;              file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; entry&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; 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;file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;webkitRelativePath&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 literal-property property&quot;&gt;configurable&lt;/span&gt;&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 literal-property property&quot;&gt;enumerable&lt;/span&gt;&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 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 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;=&gt;&lt;/span&gt; nestedPath&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;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 keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;directory&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;          dirs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nestedPath&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;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 operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dirs&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;flat&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 operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&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;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;try&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;// Open the directory.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;showDirectoryPicker&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;        mode&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 comment&quot;&gt;// Get the directory structure.&lt;/span&gt;&lt;br /&gt;      directoryStructure &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AbortError&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;        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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 keyword&quot;&gt;return&lt;/span&gt; directoryStructure&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;// Fallback if the File System Access API is not supported.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&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 parameter&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; input &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;input&#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;    input&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 string&quot;&gt;&#39;file&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;webkitdirectory &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;br /&gt;    input&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;change&#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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;let&lt;/span&gt; files &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&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;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;files&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 keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;showPicker&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLInputElement&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;      input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showPicker&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 keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&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 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;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-a-directory/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-a-directory/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>How to open one or multiple files</title>
    <link href="https://web.dev/patterns/files/open-one-or-multiple-files/"/>
    <updated>2022-10-10T00:00:00Z</updated>
    <id>https://web.dev/patterns/files/open-one-or-multiple-files/</id>
    <content type="html" mode="escaped">&lt;p&gt;Dealing with files is one of the most common operations for apps on the web.
Traditionally, users needed to upload a file, make some changes to it, and then
download it again, resulting in a copy in the Downloads folder.
With the File System Access API, users can now open files
directly, make modifications, and save back the changes to the original file.&lt;/p&gt;
&lt;h2 id=&quot;the-modern-way&quot;&gt;The modern way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-one-or-multiple-files/#the-modern-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-file-system-access-apis-showopenfilepicker-method&quot;&gt;Using the File System Access API&#39;s &lt;code&gt;showOpenFilePicker()&lt;/code&gt; method &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-one-or-multiple-files/#using-the-file-system-access-apis-showopenfilepicker-method&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To open a file, call
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/showOpenFilePicker&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;showOpenFilePicker()&lt;/code&gt;&lt;/a&gt;,
which returns a promise with an array of
the picked file or files. If you need multiple files, you can pass &lt;code&gt;{ multiple: true, }&lt;/code&gt; to the method.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 86, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      86
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 86, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
86
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/web/api/window/showopenfilepicker#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-classic-way&quot;&gt;The classic way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-one-or-multiple-files/#the-classic-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-lessinput-type=filegreater-element&quot;&gt;Using the &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt; element &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-one-or-multiple-files/#using-the-lessinput-type=filegreater-element&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt; element on a page allows the user to click it and open
one or multiple files. The trick now consists of inserting the element invisibly into a page with JavaScript and clicking it programmatically.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      1
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      1
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 12, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      12
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/input#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;progressive-enhancement&quot;&gt;Progressive enhancement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-one-or-multiple-files/#progressive-enhancement&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The method below uses the File System Access API when it&#39;s supported
and else falls back to the classic approach. In both cases the function
returns an array of files, but in case of where the File System Access API
is supported, each file object also has a &lt;code&gt;FileSystemFileHandle&lt;/code&gt; stored in
the &lt;code&gt;handle&lt;/code&gt; property, so you can optionally serialize the handle to disk.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;openFileOrFiles&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;multiple &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&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;// Feature detection. The API needs to be supported&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// and the app not run in an iframe.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supportsFileSystemAccess &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;showOpenFilePicker&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&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;span class=&quot;token operator&quot;&gt;=&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;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;self &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;top&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;catch&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 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 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;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 the File System Access API is supported…&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;supportsFileSystemAccess&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;let&lt;/span&gt; fileOrFiles &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&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;      &lt;span class=&quot;token comment&quot;&gt;// Show the file picker, optionally allowing multiple files.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handles &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;showOpenFilePicker&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; multiple &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;// Only one file is requested.&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;multiple&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;// Add the `FileSystemFileHandle` as `.handle`.&lt;/span&gt;&lt;br /&gt;        fileOrFiles &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; handles&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 function&quot;&gt;getFile&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;        fileOrFiles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; handles&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;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;        fileOrFiles &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;          handles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFile&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;// Add the `FileSystemFileHandle` as `.handle`.&lt;/span&gt;&lt;br /&gt;            file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; handle&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; file&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;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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;// Fail silently if the user has simply canceled the dialog.&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;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;AbortError&#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;        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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 keyword&quot;&gt;return&lt;/span&gt; fileOrFiles&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;// Fallback if the File System Access API is not supported.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&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 parameter&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;// Append a new `&amp;lt;input type=&quot;file&quot; multiple? /&gt;` and hide it.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; input &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;input&#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;    input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;display &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;none&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    input&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 string&quot;&gt;&#39;file&#39;&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;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&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;multiple&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;      input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;multiple &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 punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// The `change` event fires when the user interacts with the dialog.&lt;/span&gt;&lt;br /&gt;    input&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;change&#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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;// Remove the `&amp;lt;input type=&quot;file&quot; multiple? /&gt;` again from the DOM.&lt;/span&gt;&lt;br /&gt;      input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&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;// If no files were selected, return.&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;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&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 comment&quot;&gt;// Return all files or just one file.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;multiple &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&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 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 comment&quot;&gt;// Show the picker.&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 string&quot;&gt;&#39;showPicker&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLInputElement&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;      input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showPicker&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 keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&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 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;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-one-or-multiple-files/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/open-one-or-multiple-files/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>How to save a file</title>
    <link href="https://web.dev/patterns/files/save-a-file/"/>
    <updated>2022-10-10T00:00:00Z</updated>
    <id>https://web.dev/patterns/files/save-a-file/</id>
    <content type="html" mode="escaped">&lt;p&gt;Dealing with files is one of the most common operations for apps on the web.
Traditionally, users needed to upload a file, make some changes to it, and then
download it again, resulting in a copy in the Downloads folder.
With the File System Access API, users can now open files
directly, make modifications, and save back the changes to the original file.&lt;/p&gt;
&lt;h2 id=&quot;the-modern-way&quot;&gt;The modern way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/save-a-file/#the-modern-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-file-system-access-apis-showsavefilepicker-method&quot;&gt;Using the File System Access API&#39;s &lt;code&gt;showSaveFilePicker()&lt;/code&gt; method &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/save-a-file/#using-the-file-system-access-apis-showsavefilepicker-method&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To save a file, call
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/showSaveFilePicker&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;showSaveFilePicker()&lt;/code&gt;&lt;/a&gt;,
which returns a promise with &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileSystemFileHandle&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileSystemFileHandle&lt;/code&gt;&lt;/a&gt;. You can pass the desired file name to the method as &lt;code&gt;{ suggestedName: &#39;example.txt&#39; }&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 86, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      86
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 86, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
86
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/web/api/window/showsavefilepicker#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-classic-way&quot;&gt;The classic way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/save-a-file/#the-classic-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-lessa-downloadgreater-element&quot;&gt;Using the &lt;code&gt;&amp;lt;a download&amp;gt;&lt;/code&gt; element &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/save-a-file/#using-the-lessa-downloadgreater-element&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;a download&amp;gt;&lt;/code&gt; element on a page allows the user to click it and download
a file. The trick now consists of inserting the element invisibly into a page with JavaScript and clicking it programmatically.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 15, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      15
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 20, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      20
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 13, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      13
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 10.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      10.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLAnchorElement/download#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;progressive-enhancement&quot;&gt;Progressive enhancement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/save-a-file/#progressive-enhancement&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The method below uses the File System Access API when it&#39;s supported
and else falls back to the classic approach. In both cases the function
saves the file, but in case of where the File System Access API
is supported, the user will get a file save dialog where they can choose
where the file should be saved.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;saveFile&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;blob&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; suggestedName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;// Feature detection. The API needs to be supported&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// and the app not run in an iframe.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supportsFileSystemAccess &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;showSaveFilePicker&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&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;span class=&quot;token operator&quot;&gt;=&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;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;self &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;top&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;catch&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 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 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;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 the File System Access API is supported…&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;supportsFileSystemAccess&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;      &lt;span class=&quot;token comment&quot;&gt;// Show the file save dialog.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;showSaveFilePicker&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;        suggestedName&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 comment&quot;&gt;// Write the blob to the file.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; writable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWritable&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;await&lt;/span&gt; writable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&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;await&lt;/span&gt; writable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&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 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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;// Fail silently if the user has simply canceled the dialog.&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;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;AbortError&#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;        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Fallback if the File System Access API is not supported…&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Create the blob URL.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; blobURL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&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 `&amp;lt;a download&gt;` element and append it invisibly.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; a &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;a&#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;  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; blobURL&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;download &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; suggestedName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;display &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;none&#39;&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;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&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;// Programmatically click the element.&lt;/span&gt;&lt;br /&gt;  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&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;// Revoke the blob URL and remove the element.&lt;/span&gt;&lt;br /&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 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;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blobURL&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;    a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&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 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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/save-a-file/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/files/save-a-file/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>How to create an app badge</title>
    <link href="https://web.dev/patterns/web-apps/badges/"/>
    <updated>2022-10-10T00:00:00Z</updated>
    <id>https://web.dev/patterns/web-apps/badges/</id>
    <content type="html" mode="escaped">&lt;p&gt;Badges are used to convey non-urgent information to the user. For example,
they are used to indicate the status of an app, or the number of unread items.
The classic way of creating an app badge is to add a number to the favicon.
On modern browsers, after an app has been installed, there is a built-in way
to add a badge to the app icon in the operating system&#39;s task bar.&lt;/p&gt;
&lt;h2 id=&quot;the-modern-way&quot;&gt;The modern way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/badges/#the-modern-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-navigatorsetappbadge-method&quot;&gt;Using the &lt;code&gt;navigator.setAppBadge()&lt;/code&gt; method &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/badges/#using-the-navigatorsetappbadge-method&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;navigator.setAppBadge()&lt;/code&gt; method sets a badge on the icon associated with
the installed app. The method takes an optional single argument, which is
an integer which will be used as the value of the badge. Setting the number to 0
clears the app badge. Not providing an argument results in a generic badge, commonly
displayed as a colored dot.&lt;/p&gt;
&lt;img alt=&quot;App icon showing the actual icon with the number 3 as the badge value.&quot; decoding=&quot;async&quot; height=&quot;388&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 282px) 282px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Niy9c011GzoVQmPbSEF5.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Niy9c011GzoVQmPbSEF5.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Niy9c011GzoVQmPbSEF5.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Niy9c011GzoVQmPbSEF5.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Niy9c011GzoVQmPbSEF5.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Niy9c011GzoVQmPbSEF5.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Niy9c011GzoVQmPbSEF5.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Niy9c011GzoVQmPbSEF5.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Niy9c011GzoVQmPbSEF5.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Niy9c011GzoVQmPbSEF5.png?auto=format&amp;w=564 564w&quot; width=&quot;282&quot; /&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 81, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      81
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 81, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
81
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Navigator/setAppBadge#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-classic-way&quot;&gt;The classic way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/badges/#the-classic-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;adding-a-number-to-the-favicon&quot;&gt;Adding a number to the favicon &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/badges/#adding-a-number-to-the-favicon&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If the app is not installed yet, you can add a number to the favicon. There are many
ways of doing so, for example, by drawing the favicon dynamically to a canvas with
the badge info added and displaying it as a Blob URL, or to craft an SVG image with
the badge info as a data URL.&lt;/p&gt;
&lt;img alt=&quot;Favicon showing the actual icon with the number 5 as the badge value.&quot; decoding=&quot;async&quot; height=&quot;86&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 760px) 760px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/lGNsfETYtEFncrktRX6F.png?auto=format&amp;w=1520 1520w&quot; width=&quot;760&quot; /&gt;
&lt;h2 id=&quot;progressive-enhancement&quot;&gt;Progressive enhancement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/badges/#progressive-enhancement&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The snippet below uses a custom element
&lt;a href=&quot;https://github.com/fallaciousreasoning/badgable-favicon#readme&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;favicon-badge&amp;gt;&amp;lt;/favicon-badge&amp;gt;&lt;/code&gt;&lt;/a&gt;
that lets the developer declaratively set a badge on the favicon specified via the &lt;code&gt;src&lt;/code&gt; attribute
by passing an integer to the &lt;code&gt;badge&lt;/code&gt; attribute. When the user installs the application, the badge
is &amp;quot;upgraded&amp;quot; to a native operating system badge.&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;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://unpkg.com/favicon-badge@2.0.0/dist/FavIconBadge.js&#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;// DOM references.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; favicon &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;favicon-badge&#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;const&lt;/span&gt; installButton &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;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;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Feature detection.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; supportsAppBadge &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;setAppBadge&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;let&lt;/span&gt; setAppBadge&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;// For the demo simply set the badge between [0, 9].&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;let&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;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getAppBadgeValue&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;span class=&quot;token operator&quot;&gt;=&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;i &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&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;    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;&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;return&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;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;// Set the badge on the favicon.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;setAppBadgeFavicon&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 parameter&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  favicon&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;badge &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value&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;// Set the native operating system badge.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;setAppBadgeNative&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 parameter&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAppBadge&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 punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// If the app is installed and the Badging API is supported,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// set the badge on the native operating system. Else, fall&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// back to the favicon.&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;br /&gt;  &lt;span class=&quot;token function&quot;&gt;matchMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(display-mode: standalone)&#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;matches &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;br /&gt;  supportsAppBadge&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;  setAppBadge &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; setAppBadgeNative&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;  setAppBadge &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; setAppBadgeFavicon&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 comment&quot;&gt;// Set a new badge every second.&lt;/span&gt;&lt;br /&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 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;=&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;setAppBadge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAppBadgeValue&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 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;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;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// If installation is supported…&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 string&quot;&gt;&#39;BeforeInstallPromptEvent&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&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;let&lt;/span&gt; installEvent &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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;onInstall&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;span class=&quot;token operator&quot;&gt;=&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;// After installation, &quot;upgrade&quot; to the native operating system badge.&lt;/span&gt;&lt;br /&gt;    installButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &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;    installEvent &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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;supportsAppBadge&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;      favicon&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;badge &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;      setAppBadge &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; setAppBadgeNative&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;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// …listen for the `beforeinstallprompt` event.&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;beforeinstallprompt&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;    installEvent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    installButton&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;disabled &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 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;// Deal with installation.&lt;/span&gt;&lt;br /&gt;  installButton&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;async&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;=&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;installEvent&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;    installEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;prompt&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;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; installEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userChoice&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;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;outcome &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;accepted&#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;onInstall&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Listen for the `appinstalled` in case the user installs the app manually.&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;appinstalled&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;onInstall&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 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;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/badges/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/patterns/web-apps/badges/badging-api/&quot;&gt;Badging for app icons&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/badges/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>How to access contacts from the address book</title>
    <link href="https://web.dev/patterns/web-apps/contacts/"/>
    <updated>2022-10-10T00:00:00Z</updated>
    <id>https://web.dev/patterns/web-apps/contacts/</id>
    <content type="html" mode="escaped">&lt;p&gt;Sometimes you want to let users of your app select one of their contacts to message via an email or
chat application, or help them discover which of their contacts have already joined a social
platform.&lt;/p&gt;
&lt;h2 id=&quot;the-modern-way&quot;&gt;The modern way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/contacts/#the-modern-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-the-contact-picker-api&quot;&gt;Using the Contact Picker API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/contacts/#using-the-contact-picker-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To get contacts from the address book, you need to use the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Contact_Picker_API&quot; rel=&quot;noopener&quot;&gt;Contact Picker API&lt;/a&gt;, which allows
users to select entries from their contact list and share limited details of the selected entries
with your app. Several properties like &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;tel&lt;/code&gt;, &lt;code&gt;address&lt;/code&gt;, and &lt;code&gt;icon&lt;/code&gt; are available.
To find out about the concretely supported properties, call &lt;code&gt;navigator.contacts.getProperties()&lt;/code&gt;. To
allow the user to select multiple contacts, pass &lt;code&gt;{multiple: true}&lt;/code&gt; as the second parameter of
&lt;code&gt;navigator.contacts.select()&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/ContactsManager#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;The Contact Picker API is available in the Android version of Chrome from version 80.&lt;/p&gt;
&lt;h2 id=&quot;the-classic-way&quot;&gt;The classic way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/contacts/#the-classic-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;using-a-regular-form&quot;&gt;Using a regular form &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/contacts/#using-a-regular-form&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The fallback method is to use a regular form that lets the user enter the contact&#39;s details.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      1
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      1
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 12, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      12
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari ≤4, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      ≤4
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/form#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;progressive-enhancement&quot;&gt;Progressive enhancement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/contacts/#progressive-enhancement&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If the Contact Picker API is supported, hide the static form fields and show a picker button
instead.&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;const&lt;/span&gt; button &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;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 keyword&quot;&gt;const&lt;/span&gt; name &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;.name&#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;const&lt;/span&gt; address &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;.address&#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;const&lt;/span&gt; email &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;.email&#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;const&lt;/span&gt; tel &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;.tel&#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;const&lt;/span&gt; pre &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;pre&#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;const&lt;/span&gt; autofills &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;querySelectorAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.autofill&#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 keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;contacts&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;  button&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hidden &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 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;const&lt;/span&gt; autofill &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; autofills&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;    autofill&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parentElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;display &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;none&#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;  address&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parentElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;display &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;block&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  button&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;async&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;=&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;const&lt;/span&gt; props &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 string&quot;&gt;&#39;name&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;email&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;tel&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;address&#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;const&lt;/span&gt; opts &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 literal-property property&quot;&gt;multiple&lt;/span&gt;&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;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;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;contact&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;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contacts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; opts&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;      name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; contact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      address&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; contact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;address&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      tel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; contact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tel&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      email&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; contact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;email&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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;      pre&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textContent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&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;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;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/contacts/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Contact_Picker_API&quot; rel=&quot;noopener&quot;&gt;Contact Picker API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/contact-picker/&quot;&gt;A contact picker for the web&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/patterns/web-apps/contacts/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>MishiPay&#39;s PWA increases transactions 10 times and saves 2.5 years of queuing</title>
    <link href="https://web.dev/mishipay/"/>
    <updated>2022-03-28T00:00:00Z</updated>
    <id>https://web.dev/mishipay/</id>
    <content type="html" mode="escaped">&lt;p&gt;MishiPay empowers shoppers to scan and pay for their shopping with their smartphones, rather than
wasting time queuing at the checkout. With MishiPay&#39;s &lt;a href=&quot;https://mishipay.com/&quot; rel=&quot;noopener&quot;&gt;Scan &amp;amp; Go&lt;/a&gt; technology,
shoppers can use their own phone to scan the barcode on items and pay for them, then simply leave
the store. &lt;a href=&quot;https://www.adyen.com/en_GB/landing/online/uk/2019/bnb/report&quot; rel=&quot;noopener&quot;&gt;Studies&lt;/a&gt; reveal that
in-store queuing costs the global retail sector about $200 billion annually.&lt;/p&gt;
&lt;p&gt;Our technology relies on device hardware capabilities such as GPS sensors and cameras that allow
users to locate MishiPay-enabled stores, scan item barcodes within the physical store, and then pay
using the digital payment method of their choice. The initial versions of our Scan &amp;amp; Go technology
were platform-specific iOS and Android applications, and early adopters loved the technology. Read
on to learn how switching to a PWA increased transactions by 10 times and saved 2.5 years of
queuing!&lt;/p&gt;
&lt;ul class=&quot;stats&quot;&gt;
  &lt;div class=&quot;stats__item&quot;&gt;
    &lt;p class=&quot;stats__figure&quot;&gt;
      10&lt;sub&gt;×&lt;/sub&gt;
    &lt;/p&gt;
    &lt;p&gt;Increased transactions&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;stats__item&quot;&gt;
    &lt;p class=&quot;stats__figure&quot;&gt;
      2.5 years
   &lt;/p&gt;
    &lt;p&gt;Queuing saved&lt;/p&gt;
  &lt;/div&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;challenge&quot;&gt;Challenge &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mishipay/#challenge&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Users find our technology extremely helpful when waiting in a queue or check-out line, as it allows
them to skip the queue and have a smooth in-store experience. But the hassle of downloading an
Android or iOS application made users not choose our technology despite the value. It was a growing
challenge for MishiPay, and we needed to increase user adoption with a lower barrier of entry.&lt;/p&gt;
&lt;h2 id=&quot;solution&quot;&gt;Solution &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mishipay/#solution&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our efforts at building and launching the PWA helped us remove the installation hassle and
encouraged new users to try our technology inside a physical store, skip the queue, and have a
seamless shopping experience. Since the launch, we have seen a massive spike in user adoption with
our PWA compared to our platform-specific applications.&lt;/p&gt;
&lt;figure&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/jtJZ418n11SOtHLTsOht.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    Side-by-side comparison of directly launching the PWA (left, faster) vs. installing and launching the Android app (right, slower).
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Transactions by platform. ¡OS: 16397 (3.98%). Android: 13769 (3.34%). Web: 382184 (92.68%).&quot; decoding=&quot;async&quot; height=&quot;415&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/I5FMltbcuDHqgqEQMyqA.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/I5FMltbcuDHqgqEQMyqA.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/I5FMltbcuDHqgqEQMyqA.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/I5FMltbcuDHqgqEQMyqA.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/I5FMltbcuDHqgqEQMyqA.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/I5FMltbcuDHqgqEQMyqA.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/I5FMltbcuDHqgqEQMyqA.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/I5FMltbcuDHqgqEQMyqA.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/I5FMltbcuDHqgqEQMyqA.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/I5FMltbcuDHqgqEQMyqA.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/I5FMltbcuDHqgqEQMyqA.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    The majority of all transactions happen on the web.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;technical-deep-dive&quot;&gt;Technical deep-dive &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mishipay/#technical-deep-dive&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;locating-mishipay-enabled-stores&quot;&gt;Locating MishiPay enabled stores &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mishipay/#locating-mishipay-enabled-stores&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To enable this feature, we rely on the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Geolocation/getCurrentPosition&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;getCurrentPosition()&lt;/code&gt;&lt;/a&gt;
API along with an IP-based fallback solution.&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;const&lt;/span&gt; geoOptions &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 literal-property property&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&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;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;enableHighAccuracy&lt;/span&gt;&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 literal-property property&quot;&gt;maximumAge&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 punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;geolocation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getCurrentPosition&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 parameter&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; cords &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; position&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coords&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 template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Latitude :  &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;cords&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;latitude&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;    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 template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Longitude :  &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;cords&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;longitude&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;debug&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Error: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;code&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;/**&lt;br /&gt;     * Invoke the IP based location services&lt;br /&gt;     * to fetch the latitude and longitude of the user.&lt;br /&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;  geoOptions&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;This approach worked well in the earlier versions of the app, but was later proven to be a huge pain
point for MishiPay&#39;s users for the following reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Location inaccuracies in the IP-based fallback solutions.&lt;/li&gt;
&lt;li&gt;A growing listing of MishiPay-enabled stores per region requires users to scroll a list and
identify the correct store.&lt;/li&gt;
&lt;li&gt;Users accidentally occasionally choose the wrong store, causing the purchases to be recorded
incorrectly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To address these issues, we embedded unique geolocated QR codes on the in-store displays for each
store. It paved the way for a faster onboarding experience. Users simply scan the geolocated QR
codes printed on marketing material present in the stores to access the Scan &amp;amp; Go web application.
This way, they can avoid typing in the web address &lt;code&gt;mishipay.shop&lt;/code&gt; to access the service.&lt;/p&gt;
&lt;figure&gt;
  &lt;video autoplay=&quot;&quot; height=&quot;600&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; width=&quot;1296&quot; style=&quot;--vid-width: 1296; --vid-height: 600&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/NqyMBZGYzGSNqLE7soXR.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    In-store scanning experience using the PWA.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;scanning-products&quot;&gt;Scanning products &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mishipay/#scanning-products&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A core feature in the MishiPay app is the barcode scanning as this empowers our users to scan their
own purchases and see the running total even before they would otherwise have reached a cash
register.&lt;/p&gt;
&lt;p&gt;To build a scanning experience on the web, we have identified three core layers.&lt;/p&gt;
&lt;img alt=&quot;Diagram showing the three main thread layers: video stream, processing layer, and decoder layer.&quot; decoding=&quot;async&quot; height=&quot;358&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jRJeqbGW7yqU8VpdbjBO.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;video-stream&quot;&gt;Video stream &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mishipay/#video-stream&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With the help of the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/MediaDevices/getUserMedia&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;getUserMedia()&lt;/code&gt;&lt;/a&gt; method, we
can access the user&#39;s rear view camera with the constraints listed below. Invoking the method
automatically triggers a prompt for users to accept or deny access to their camera. Once we have
access to the video stream, we can relay it to a video element as shown below:&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;/**&lt;br /&gt; * Video Stream Layer&lt;br /&gt; * https://developer.mozilla.org/docs/Web/API/MediaDevices/getUserMedia&lt;br /&gt; */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; canvasEle &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;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;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; videoEle &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;videoElement&#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;const&lt;/span&gt; canvasCtx &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; canvasEle&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;fetchVideoStream&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;fetchVideoStream&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;let&lt;/span&gt; constraints &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 literal-property property&quot;&gt;video&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 literal-property property&quot;&gt;facingMode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;environment&#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 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;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mediaDevices &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&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;    navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mediaDevices&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUserMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;constraints&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 function&quot;&gt;then&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 parameter&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        videoEle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;srcObject &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; stream&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        videoStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; stream&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        videoEle&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 comment&quot;&gt;// Initiate frame capture - Processing Layer.&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 function&quot;&gt;catch&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 parameter&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;debug&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&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;warn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Failed to access the stream:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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 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;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;warn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;getUserMedia API not supported!!&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;processing-layer&quot;&gt;Processing layer &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mishipay/#processing-layer&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For detecting a barcode in a given video stream, we need to periodically capture frames and transfer
them to the decoder layer. To capture a frame, we simply draw the streams from &lt;code&gt;VideoElement&lt;/code&gt; onto
an &lt;code&gt;HTMLCanvasElement&lt;/code&gt; using the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/drawImage&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;drawImage()&lt;/code&gt;&lt;/a&gt;
method of the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Canvas_API&quot; rel=&quot;noopener&quot;&gt;Canvas API&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;/**&lt;br /&gt; * Processing Layer - Frame Capture&lt;br /&gt; * https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Manipulating_video_using_canvas&lt;br /&gt; */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;captureFrames&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;videoEle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;readyState &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; videoEle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;HAVE_ENOUGH_DATA&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;const&lt;/span&gt; canvasHeight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvasEle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; videoEle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;videoHeight&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;const&lt;/span&gt; canvasWidth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvasEle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; videoEle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;videoWidth&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;    canvasCtx&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;videoEle&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; canvasWidth&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; canvasHeight&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;// Transfer the `canvasEle` to the decoder for barcode detection.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decodeBarcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvasEle&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;    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;Video feed not available yet&#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 advanced use cases, this layer also performs some pre-processing tasks such as cropping,
rotating, or converting to grayscale. These tasks can be CPU-intensive and result in the application
being unresponsive given that barcode scanning is a long-running operation. With the help of the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/OffscreenCanvas&quot; rel=&quot;noopener&quot;&gt;OffscreenCanvas&lt;/a&gt; API, we can offload
the CPU-intensive task to a web worker. On devices that support hardware graphics acceleration,
WebGL API and its
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WebGL2RenderingContext&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;WebGL2RenderingContext&lt;/code&gt;&lt;/a&gt; can
optimize gains on the CPU-intensive pre-processing tasks.&lt;/p&gt;
&lt;h3 id=&quot;decoder-layer&quot;&gt;Decoder layer &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mishipay/#decoder-layer&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The final layer is the decoder layer which is responsible for decoding barcodes from the frames
captured by the processing layer. Thanks to the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Barcode_Detection_API&quot; rel=&quot;noopener&quot;&gt;Shape Detection API&lt;/a&gt; (which is
not yet available on all browsers) the browser itself decodes the barcode from an
&lt;code&gt;ImageBitmapSource&lt;/code&gt;, which can be an &lt;code&gt;img&lt;/code&gt; element, an SVG &lt;code&gt;image&lt;/code&gt; element, a &lt;code&gt;video&lt;/code&gt; element, a
&lt;code&gt;canvas&lt;/code&gt; element, a &lt;code&gt;Blob&lt;/code&gt; object, an &lt;code&gt;ImageData&lt;/code&gt; object, or an &lt;code&gt;ImageBitmap&lt;/code&gt; object.&lt;/p&gt;
&lt;img alt=&quot;Diagram showing the three main thread layers: video stream, processing layer, and Shape Detection API.&quot; decoding=&quot;async&quot; height=&quot;358&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GR4od5fHOxys6lrxttPn.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&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;/**&lt;br /&gt; * Barcode Decoder with Shape Detection API&lt;br /&gt; * https://web.dev/shape-detection/&lt;br /&gt; */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decodeBarcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;canvas&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;const&lt;/span&gt; formats &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 string&quot;&gt;&#39;aztec&#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;code_128&#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;code_39&#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;code_93&#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;codabar&#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;data_matrix&#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;ean_13&#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;ean_8&#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;itf&#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;pdf417&#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;qr_code&#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;upc_a&#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;upc_e&#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;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; barcodeDetector &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;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;BarcodeDetector&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;    formats&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 keyword&quot;&gt;try&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;const&lt;/span&gt; barcodes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; barcodeDetector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;detect&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;&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;barcodes&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; barcodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; barcodes&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 string&quot;&gt;&#39;rawValue&#39;&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;undefined&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;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 keyword&quot;&gt;throw&lt;/span&gt; e&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 devices that don&#39;t support the Shape Detection API yet, we need a fallback solution to decode
the barcodes. The Shape Detection API exposes a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/BarcodeDetector/getSupportedFormats&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;getSupportedFormats()&lt;/code&gt;&lt;/a&gt;
method which helps switch between the Shape Detection API and the fallback solution.&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;// Feature detection.&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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;BarceodeDetector&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&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 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;// Check supported barcode formats.&lt;/span&gt;&lt;br /&gt;BarcodeDetector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSupportedFormats&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 function&quot;&gt;then&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 parameter&quot;&gt;supportedFormats&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  supportedFormats&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&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 parameter&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&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;format&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;img alt=&quot;Flow diagram showing how, dependent on Barcode Detector support and the supported barcode formats, either the Shape Detection API or the fallback solution  is being used.&quot; decoding=&quot;async&quot; height=&quot;404&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GmrPC7mFTdtsEdS1BDWp.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;fallback-solution&quot;&gt;Fallback solution &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mishipay/#fallback-solution&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Several open-source and enterprise scanning libraries are available that can be easily integrated
with any web application to implement scanning. Here are some of the libraries that MishiPay
recommend.&lt;/p&gt;
&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Library Name&lt;/th&gt;
        &lt;th&gt;Type&lt;/th&gt;
        &lt;th&gt;Wasm Solution&lt;/th&gt;
        &lt;th&gt;Barcode Formats&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://github.com/serratus/quaggaJS&quot;&gt;QuaggaJs&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;Open Source&lt;/td&gt;
        &lt;td&gt;No&lt;/td&gt;
        &lt;td&gt;1D&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://github.com/zxing-js/library&quot;&gt;ZxingJs&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;Open Source&lt;/td&gt;
        &lt;td&gt;No&lt;/td&gt;
        &lt;td&gt;1D &amp; 2D (Limited)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://codecorp.com/products&quot;&gt;CodeCorp&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;Enterprise&lt;/td&gt;
        &lt;td&gt;Yes&lt;/td&gt;
        &lt;td&gt;1D &amp; 2D&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://docs.scandit.com/stable/web/&quot;&gt;Scandit&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;Enterprise&lt;/td&gt;
        &lt;td&gt;Yes&lt;/td&gt;
        &lt;td&gt;1D &amp; 2D&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
    &lt;caption style=&quot;max-width:initial;&quot;&gt;
      Comparison of open-source and commercial barcode scanning libraries
    &lt;/caption&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;All the above libraries are full-fledged SDKs that compose all the layers discussed above. They also
expose interfaces to support various scanning operations. Depending on the barcode formats and
detection speed needed for the business case, a decision can be between Wasm and non-Wasm solutions.
Despite the overhead of requiring an additional resource (Wasm) to decode the barcode, Wasm
solutions outperform the non-Wasm solution in terms of accuracy.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.scandit.com/stable/web/&quot; rel=&quot;noopener&quot;&gt;Scandit&lt;/a&gt; was our primary choice. It supports all barcode
formats required for our business use cases; it beats all the available open-source libraries in
scanning speed.&lt;/p&gt;
&lt;h2 id=&quot;future-of-scanning&quot;&gt;Future of scanning &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mishipay/#future-of-scanning&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once the Shape Detection API is fully supported by all major browsers, we could potentially have a
new HTML element &lt;code&gt;&amp;lt;scanner&amp;gt;&lt;/code&gt; that has the capabilities required for a barcode scanner. Engineering
at MishiPay believes there is a solid use case for the barcode scanning functionality to be a new
HTML element due to the growing number of open source and licensed libraries that are enabling
experiences such as Scan &amp;amp; Go and many others.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mishipay/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;App fatigue is an issue that developers face when their products enter the market. Users often want
to understand the value that an application gives them before they download it. In a store, where
MishiPay saves shoppers&#39; time and improves their experience, it is counterintuitive to wait for a
download before they can use an application. This is where our PWA helps. By eliminating the barrier
to entry, we have increased our transactions by 10 times and enabled our users to save 2.5 years of
waiting in the queue.&lt;/p&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mishipay/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by &lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Nikil Mathew</name>
    </author><author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>PWAs on Oculus Quest 2</title>
    <link href="https://web.dev/pwas-on-oculus-2/"/>
    <updated>2022-01-10T00:00:00Z</updated>
    <id>https://web.dev/pwas-on-oculus-2/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;the-oculus-quest-2&quot;&gt;The Oculus Quest 2 &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#the-oculus-quest-2&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.oculus.com/quest-2/&quot; rel=&quot;noopener&quot;&gt;Oculus Quest 2&lt;/a&gt; is a virtual reality (VR) headset created
by Oculus, a division of Meta. It is the successor to the company&#39;s previous headset, the Oculus
Quest. The device is capable of running as both a standalone headset with an internal, Android-based
operating system, and with Oculus-compatible VR software running on a desktop computer when
connected over USB or Wi-Fi. It uses the Qualcomm
&lt;a href=&quot;https://www.qualcomm.com/news/onq/2020/10/29/oculus-quest-2-how-snapdragon-xr2-powers-next-generation-vr&quot; rel=&quot;noopener&quot;&gt;Snapdragon XR2&lt;/a&gt;
system on a chip with 6 GB of RAM. The Quest 2&#39;s display is a singular fast-switch LCD
panel with 1,832 × 1,920 pixels per eye resolution that runs at a refresh rate of up to
120 Hz.&lt;/p&gt;
&lt;img alt=&quot;Oculus Quest 2 device with controllers.&quot; decoding=&quot;async&quot; height=&quot;304&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g0IQmlLaOiLWqwQuvnhQ.jpeg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;the-oculus-browser&quot;&gt;The Oculus Browser &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#the-oculus-browser&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Currently there are three browsers available for the Oculus Quest 2:
&lt;a href=&quot;https://www.oculus.com/experiences/quest/4812663595466206/&quot; rel=&quot;noopener&quot;&gt;Wolvic&lt;/a&gt;, a successor to
&lt;a href=&quot;https://www.oculus.com/experiences/quest/2180252408763702/&quot; rel=&quot;noopener&quot;&gt;Firefox Reality&lt;/a&gt;, and the built-in
&lt;a href=&quot;https://www.oculus.com/experiences/quest/1916519981771802&quot; rel=&quot;noopener&quot;&gt;Oculus Browser&lt;/a&gt;. This article focuses on
the latter. The Oculus website
&lt;a href=&quot;https://developer.oculus.com/documentation/web/browser-intro/&quot; rel=&quot;noopener&quot;&gt;introduces&lt;/a&gt; the Oculus Browser as
follows.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;Oculus Browser provides support for the latest web standards and other technologies to help you
create VR experiences on the web. Today&#39;s 2D web sites work great in Oculus Browser because it&#39;s
powered by the Chromium rendering engine. It&#39;s further optimized for Oculus headsets to get the best
performance and to enable web developers take advantage of the full potential of VR with new APIs,
like WebXR. Through WebXR, we&#39;re opening the doors to the next frontier of the web.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;img alt=&quot;Oculus Browser with three browser windows open.&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/R4SwrV05Pqap583Rzr4L.jpeg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;user-agent&quot;&gt;User agent &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#user-agent&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The browser&#39;s user agent string at the time of writing is as follows.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Mozilla/5.0 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;X11&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Linux x86_64&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Quest &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;AppleWebKit/537.36 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;KHTML, like Gecko&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;OculusBrowser/18.1.0.2.46.337441587&lt;br /&gt;SamsungBrowser/4.0&lt;br /&gt;Chrome/95.0.4638.74&lt;br /&gt;VR&lt;br /&gt;Safari/537.36&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As you can see, the current version &lt;code&gt;18.1.0.2.46.337441587&lt;/code&gt; of the Oculus Browser is based on Chrome
&lt;code&gt;95.0.4638.74&lt;/code&gt;, that is only one version behind the current stable version of Chrome, which is
&lt;code&gt;96.0.4664.110&lt;/code&gt;. If the user switches to mobile mode, &lt;code&gt;VR&lt;/code&gt; changes to &lt;code&gt;Mobile VR&lt;/code&gt;.&lt;/p&gt;
&lt;img alt=&quot;Oculus Browser About page.&quot; decoding=&quot;async&quot; height=&quot;449&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/E3929PxcGa7GCxKoTffh.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;user-interface&quot;&gt;User interface &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#user-interface&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The browser&#39;s user interface (shown above) has the following features (top row from left to right):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Back button&lt;/li&gt;
&lt;li&gt;Reload button&lt;/li&gt;
&lt;li&gt;Site information&lt;/li&gt;
&lt;li&gt;URL bar&lt;/li&gt;
&lt;li&gt;Create bookmark button&lt;/li&gt;
&lt;li&gt;Resize button with narrow, medium, and wide options as well as a zoom feature&lt;/li&gt;
&lt;li&gt;Request mobile website button&lt;/li&gt;
&lt;li&gt;Menu button with the following options:
&lt;ul&gt;
&lt;li&gt;Enter private mode&lt;/li&gt;
&lt;li&gt;Close all tabs&lt;/li&gt;
&lt;li&gt;Settings&lt;/li&gt;
&lt;li&gt;Bookmarks&lt;/li&gt;
&lt;li&gt;Downloads&lt;/li&gt;
&lt;li&gt;History&lt;/li&gt;
&lt;li&gt;Clear browsing data&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The bottom row includes the following features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Close button&lt;/li&gt;
&lt;li&gt;Minimize button&lt;/li&gt;
&lt;li&gt;Three dots button with back, forward, and reload options&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;refresh-rate-and-device-pixel-ratio&quot;&gt;Refresh rate and device pixel ratio &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#refresh-rate-and-device-pixel-ratio&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For Oculus Quest 2, Oculus Browser renders both 2D web page content and WebXR at a
90 Hz refresh rate. When watching fullscreen media, Oculus Browser optimizes the device refresh
rate based on the frame rate of the video, for example, 24 fps. The Oculus Quest 2 has a
device pixel ratio of 1.5 for crisp text.&lt;/p&gt;
&lt;h2 id=&quot;pwas-in-oculus-browser-and-the-oculus-store&quot;&gt;PWAs in Oculus Browser and the Oculus Store &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#pwas-in-oculus-browser-and-the-oculus-store&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On October 28, 2021, &lt;a href=&quot;https://twitter.com/jacobrossi&quot; rel=&quot;noopener&quot;&gt;Jacob Rossi&lt;/a&gt;, Product Management Lead at
Meta (Oculus), &lt;a href=&quot;https://twitter.com/jacobrossi/status/1453776349299019778&quot; rel=&quot;noopener&quot;&gt;shared&lt;/a&gt; that
&lt;a href=&quot;https://developer.oculus.com/pwa/&quot; rel=&quot;noopener&quot;&gt;PWAs were coming&lt;/a&gt; to Oculus Quest and Oculus Quest 2. In
the following, I describe the PWA experience on Oculus and explain how to build, sideload, and test
your PWA on the Oculus Quest 2.&lt;/p&gt;
&lt;h3 id=&quot;state-sharing&quot;&gt;State sharing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#state-sharing&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Login state is shared between Oculus Browser and PWAs, allowing users to seamlessly switch between
the two. Naturally, &lt;a href=&quot;https://developers.facebook.com/docs/facebook-login/overview/&quot; rel=&quot;noopener&quot;&gt;Facebook Login&lt;/a&gt;
is supported out of the box. The Oculus Browser includes a password manager that allows users to
store and share their passwords securely between the browser and installed app experiences.&lt;/p&gt;
&lt;h3 id=&quot;pwa-window-sizes&quot;&gt;PWA window sizes &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#pwa-window-sizes&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Browser windows and windows of installed PWAs can be freely resized by the user. The height can vary
between 625 px and 1,200 px. The width can be set between 400 px and 2,000 px.
The default dimensions are 1,000 × 625 px.&lt;/p&gt;
&lt;h3 id=&quot;interacting-with-pwas&quot;&gt;Interacting with PWAs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#interacting-with-pwas&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;PWAs can be controlled with the Oculus left and right controllers, Bluetooth mice and keyboards, and
via hand tracking. Scrolling works via the thumb sticks on the Oculus controllers, or by pinching
the thumb and the index finger and moving in the desired direction. To select something, the user
can point and pinch.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/20e7SXFMLC22gqW8HzRa.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;h3 id=&quot;permissions-for-pwas&quot;&gt;Permissions for PWAs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#permissions-for-pwas&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Permissions in Oculus Browser work pretty much the same way as in Chrome. The state is shared
between apps running in the browser and installed PWAs, so users can switch between the two
experiences without having to grant the same permissions again.&lt;/p&gt;
&lt;p&gt;Albeit many permissions are implemented, not all features are supported. For example, while
requesting the geolocation permission succeeds, the device never actually gets a location.
Similarly, the various hardware APIs like &lt;a href=&quot;https://web.dev/hid/&quot;&gt;WebHID&lt;/a&gt;, &lt;a href=&quot;https://web.dev/bluetooth/&quot;&gt;Web Bluetooth&lt;/a&gt;, etc. all
pass feature detection, but don&#39;t actually show a picker that would let the user pair the Oculus
with a hardware device. I suppose feature detectability of APIs will be refined once the browser
matures.&lt;/p&gt;
&lt;img alt=&quot;Permissions in Oculus Browser.&quot; decoding=&quot;async&quot; height=&quot;575&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/fTX92hn8bIMpWVMoBUjE.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;debugging-pwas-via-chrome-devtools&quot;&gt;Debugging PWAs via Chrome DevTools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#debugging-pwas-via-chrome-devtools&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After
&lt;a href=&quot;https://developer.oculus.com/documentation/native/android/mobile-device-setup/#enable-developer-mode&quot; rel=&quot;noopener&quot;&gt;enabling Developer Mode&lt;/a&gt;,
debugging PWAs on Oculus Quest 2 works exactly as described in
&lt;a href=&quot;https://developer.chrome.com/docs/devtools/remote-debugging/&quot; rel=&quot;noopener&quot;&gt;Remote debug Android devices&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;On the Oculus device, browse to the desired site in Oculus Browser.&lt;/li&gt;
&lt;li&gt;Launch Google Chrome on your computer and navigate to &lt;code&gt;chrome://inspect/#devices&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Find the Oculus device in question, which will be followed by a set of Oculus Browser tabs
currently open on the device.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;inspect&lt;/strong&gt; on the desired Oculus Browser tab.&lt;/li&gt;
&lt;/ol&gt;
&lt;img alt=&quot;Inspecting an app running on the Oculus Quest 2 with Chrome DevTools.&quot; decoding=&quot;async&quot; height=&quot;476&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/GiTjoiMuU2FoJOXLMEk2.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;app-discovery&quot;&gt;App discovery &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#app-discovery&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;People can use the browser itself or the &lt;a href=&quot;https://www.oculus.com/experiences/quest/&quot; rel=&quot;noopener&quot;&gt;Oculus Store&lt;/a&gt;
to discover PWAs. Just like with any other browser, installed PWAs also work in Oculus Browser as
websites running in a tab. When a user visits a site, the Oculus Browser will help them discover the
app if (and only if) it is available in the Oculus Store. For users that already have the app
installed, Oculus Browser will help them easily switch to the app if they desire.&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; Currently the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/BeforeInstallPromptEvent&quot;&gt;&lt;code&gt;BeforeInstallPrompt&lt;/code&gt; event&lt;/a&gt; will &lt;em&gt;not&lt;/em&gt; fire in Oculus Browser, despite feature detection reporting it to be supported. &lt;/div&gt;&lt;/aside&gt;
&lt;img alt=&quot;Oculus Browser inviting the user in a prompt to install the MyEmail app.&quot; decoding=&quot;async&quot; height=&quot;343&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 512px) 512px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/YNM3CfMKqnLuCJLw3I0U.png?auto=format&amp;w=1024 1024w&quot; width=&quot;512&quot; /&gt;
&lt;h2 id=&quot;exemplary-pwas-on-the-oculus-quest-2&quot;&gt;Exemplary PWAs on the Oculus Quest 2 &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#exemplary-pwas-on-the-oculus-quest-2&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;pwas-by-meta&quot;&gt;PWAs by Meta &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#pwas-by-meta&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Multiple Meta divisions have created PWAs for the Oculus Quest 2, for example
&lt;a href=&quot;https://www.oculus.com/experiences/quest/6102857836422862&quot; rel=&quot;noopener&quot;&gt;Instagram&lt;/a&gt; and
&lt;a href=&quot;https://www.oculus.com/experiences/quest/6126469507395223&quot; rel=&quot;noopener&quot;&gt;Facebook&lt;/a&gt;. These PWAs run in standalone
app windows that don&#39;t have a URL bar and that can be freely resized.&lt;/p&gt;
&lt;img alt=&quot;Facebook Oculus Quest 2 app.&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/m5NoDaB7hyFOvrxHF9oS.jpeg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;img alt=&quot;Instagram Oculus Quest 2 app&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/gxYCh0Z9R3vXRU0MWsIB.jpeg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h3 id=&quot;pwas-by-other-developers&quot;&gt;PWAs by other developers &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#pwas-by-other-developers&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;At the time of this writing, there is a small but growing number of PWAs for the Oculus Quest 2
on the Oculus Store. &lt;a href=&quot;https://www.oculus.com/experiences/quest/4949538568409451&quot; rel=&quot;noopener&quot;&gt;Spike&lt;/a&gt; lets users
experience all the essential work tools like email, chat, calls, notes, tasks, and to-dos from their
inbox in a virtual environment hub right in the Spike app.&lt;/p&gt;
&lt;img alt=&quot;Spike Oculus Quest 2 app.&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/yATTFGQRz75I2JrIAUSz.jpeg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;Another example is &lt;a href=&quot;https://www.oculus.com/experiences/quest/4636949963023496&quot; rel=&quot;noopener&quot;&gt;Smartsheet&lt;/a&gt;, a
dynamic workspace that provides project management, automated workflows, and rapid building of new
solutions.&lt;/p&gt;
&lt;p&gt;More PWAs like Slack, Dropbox, or Canva are coming, as teased in a
&lt;a href=&quot;https://www.facebook.com/watch/?v=4637130066326723&quot; rel=&quot;noopener&quot;&gt;video&lt;/a&gt; featuring Jacob Rossi that was released
in the context of Facebook&#39;s Connect conference in 2021.&lt;/p&gt;
&lt;h2 id=&quot;creating-pwas-for-oculus&quot;&gt;Creating PWAs for Oculus &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#creating-pwas-for-oculus&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Meta outlined the required steps in their
&lt;a href=&quot;https://developer.oculus.com/documentation/web/pwa-gs/&quot; rel=&quot;noopener&quot;&gt;documentation&lt;/a&gt;. In general, PWAs that are
installable in Chrome should oftentimes work out of the box on Oculus.&lt;/p&gt;
&lt;h3 id=&quot;web-app-manifest-requirements&quot;&gt;Web App Manifest requirements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#web-app-manifest-requirements&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are some important differences compared to
&lt;a href=&quot;https://web.dev/install-criteria/#criteria&quot;&gt;Chrome&#39;s installability criteria&lt;/a&gt; and the
&lt;a href=&quot;https://w3c.github.io/manifest/&quot; rel=&quot;noopener&quot;&gt;Web App Manifest spec&lt;/a&gt;. For example, Oculus only supports
left-to-right languages at the moment, whereas the Web App Manifest spec enforces no such
constraints. Another example is &lt;code&gt;start_url&lt;/code&gt;, which Chrome strictly requires for an app to be
installable, but which on Oculus is optional. Oculus offers a
&lt;a href=&quot;https://developer.oculus.com/documentation/web/pwa-packaging/&quot; rel=&quot;noopener&quot;&gt;command line tool&lt;/a&gt; that lets
developers create PWAs for the Oculus Quest 2, which allows them to pass the missing (or
override the existing) parameters in the Web App Manifest.&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;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(Required) The name of the PWA. Currently Oculus only supports left-to-right languages for the name.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;display&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(Required) Either &lt;code&gt;&quot;standalone&quot;&lt;/code&gt; or &lt;code&gt;&quot;minimal-ui&quot;&lt;/code&gt;. Oculus currently doesn&#39;t support any other values.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;short_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(Required) A shorter version of the app name, if needed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scope&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(Optional) The URL or paths that should be considered as part of the app.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;start_url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(Optional) The URL to show at app launch.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Oculus has a number of optional &lt;strong&gt;proprietary&lt;/strong&gt; Web App Manifest fields that can be used to
customize the PWA experience.&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;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ovr_package_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(Optional) Sets the package name of the APK generated for the PWA. This must be in reverse domain name notation, e.g., &lt;code&gt;&quot;com.company.app.pwa&quot;&lt;/code&gt;. If not set, developers must provide a package name to the command line tool with the (then required) parameter &lt;code&gt;--package-name&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ovr_multi_tab_enabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(Optional) If &lt;code&gt;true&lt;/code&gt;, this boolean field will give the PWA a tab bar similar to Oculus Browser. In multi-tab PWAs, internal links that target a new tab (&lt;code&gt;target=&quot;_new&quot;&lt;/code&gt; or &lt;code&gt;target=&quot;_blank&quot;&lt;/code&gt;) will open in new tabs within the PWA window. This differs from single-tab PWAs where such links would open to a Oculus Browser window. &lt;strong&gt;This feature is currently being standardized as &lt;a href=&quot;https://github.com/w3c/manifest/issues/737&quot; rel=&quot;noopener&quot;&gt;Tabbed Application Mode&lt;/a&gt;.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ovr_scope_extensions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(Optional) Allows a PWA to include more web pages within the scope of the web application. It consists of a JSON dictionary containing extension URLs or wildcard patterns. &lt;strong&gt;This feature is currently being standardized as &lt;a href=&quot;https://github.com/WICG/manifest-incubations/blob/gh-pages/scope_extensions-explainer.md&quot; rel=&quot;noopener&quot;&gt;Scope Extensions for Web Apps&lt;/a&gt;.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h3 id=&quot;packaging-pwas-with-bubblewrap-cli&quot;&gt;Packaging PWAs with Bubblewrap CLI &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#packaging-pwas-with-bubblewrap-cli&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/GoogleChromeLabs/bubblewrap&quot; rel=&quot;noopener&quot;&gt;Bubblewrap&lt;/a&gt; is an open source set of libraries and
a command line tool (CLI) for Node.js. Bubblewrap is developed by the Google Chrome team, to help
developers generate, build, and sign an Android project that launches your PWA as a
&lt;a href=&quot;https://developer.chrome.com/docs/android/trusted-web-activity/&quot; rel=&quot;noopener&quot;&gt;Trusted Web Activity&lt;/a&gt; (TWA).&lt;/p&gt;
&lt;p&gt;Meta Quest Browser currently doesn&#39;t fully support TWA, but starting from version 1.18.0,
Bubblewrap &lt;a href=&quot;https://github.com/GoogleChromeLabs/bubblewrap/releases/tag/v1.18.0&quot; rel=&quot;noopener&quot;&gt;supports&lt;/a&gt;
packaging PWAs for Meta Quest devices.&lt;/p&gt;
&lt;p&gt;It can generate &lt;em&gt;universal&lt;/em&gt; APK files that open a TWA on regular Android devices and the Meta Quest
Browser on Meta Quest devices.&lt;/p&gt;
&lt;p&gt;Assuming you have &lt;a href=&quot;https://nodejs.org/&quot; rel=&quot;noopener&quot;&gt;Node.js&lt;/a&gt; installed, Bubblewrap CLI can be installed with the
following command:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; i -g @bubblewrap/cli&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;When running Bubblewrap for the first time, it will offer to automatically download and install the
required external dependencies—Java Development Kit (JDK) and Android SDK Build Tools.&lt;/p&gt;
&lt;p&gt;To generate a Meta Quest compatible Android project that wraps your PWA, run the &lt;code&gt;init&lt;/code&gt; command
with the &lt;code&gt;--metaquest&lt;/code&gt; flag and follow the wizard:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;bubblewrap init --manifest&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://your.web.app/manifest.json&quot;&lt;/span&gt; --metaquest&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Once the project has been generated, build and sign it by running:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;bubblewrap build&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This will output a file called &lt;code&gt;app-release-signed.apk&lt;/code&gt;. This file can be installed on the device
or published to the Meta Quest Store, Google Play Store or any of the other Android app distribution
platforms.&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; Pro tip: you can also use the &lt;code&gt;--chromeosonly&lt;/code&gt; flag in addition to the &lt;code&gt;--metaquest&lt;/code&gt; flag to make APK files compatible not only with Meta Quest and regular Android devices, but also with &lt;a href=&quot;https://chromeos.dev/&quot;&gt;ChromeOS&lt;/a&gt; devices. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;packaging-pwas-with-oculus-platform-utility&quot;&gt;Packaging PWAs with Oculus Platform Utility &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#packaging-pwas-with-oculus-platform-utility&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.oculus.com/documentation/web/pwa-packaging/#download-the-cli&quot; rel=&quot;noopener&quot;&gt;Oculus Platform Utility&lt;/a&gt;
is the official command line tool developed by Meta for publishing apps for Oculus Rift and Meta
Quest devices.&lt;/p&gt;
&lt;p&gt;It also allows to package PWAs for Meta Quest devices with the &lt;code&gt;create-pwa&lt;/code&gt; command and publish
them to the Meta Quest Store and App Lab.&lt;/p&gt;
&lt;p&gt;Set the output file name via the &lt;code&gt;-o&lt;/code&gt; parameter and the path to Android SDK via the &lt;code&gt;--android-sdk&lt;/code&gt;
parameter.&lt;/p&gt;
&lt;p&gt;Point the tool at the live URL of the web app manifest via the &lt;code&gt;--web-manifest-url&lt;/code&gt; parameter.&lt;/p&gt;
&lt;p&gt;If you don&#39;t have a manifest on your live PWA or wish to override the live manifest, you can still
generate an APK for your PWA using a local manifest file and the &lt;code&gt;--manifest-content-file&lt;/code&gt;
parameter.&lt;/p&gt;
&lt;p&gt;To leave the manifest as pure as possible, use the &lt;code&gt;--package-name&lt;/code&gt; parameter with a value in
reverse domain name notation (for example, &lt;code&gt;com.company.app.pwa&lt;/code&gt;), rather than adding the
proprietary &lt;code&gt;ovr_package_name&lt;/code&gt; field to the manifest.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ovr-platform-util create-pwa -o output.apk --android-sdk ~/bin/android-10 --manifest-content-file manifest.json --package-name com.company.app.pwa&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; APK files generated by Oculus Platform Utility are only compatible with Meta Quest devices and cannot be run on regular Android devices. Also they can only be published to the Meta Quest Store and App Lab. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;packaging-pwas-with-pwabuilder&quot;&gt;Packaging PWAs with PWABuilder &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#packaging-pwas-with-pwabuilder&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Using &lt;a href=&quot;https://pwabuilder.com/&quot; rel=&quot;noopener&quot;&gt;PWABuilder&lt;/a&gt; is in the authors&#39; view the easiest and therefore
recommended way to package PWAs for Meta Quest at the moment.&lt;/p&gt;
&lt;p&gt;PWABuilder is an &lt;a href=&quot;https://github.com/pwa-builder&quot; rel=&quot;noopener&quot;&gt;open source&lt;/a&gt; project developed by Microsoft,
that allows developers to package and sign their PWAs for publishing to various stores, including
Microsoft Store, Google Play Store, App Store, and Meta Quest Store.&lt;/p&gt;
&lt;p&gt;Packaging PWAs with PWABuilder is as easy as entering the URL of a PWA, entering/editing the
metadata for the app, and clicking the &lt;strong&gt;Generate&lt;/strong&gt; button.&lt;/p&gt;
&lt;p&gt;PWABuilder gives developers the choice of what tool under the hood to use for packaging PWAs for
Meta Quest devices.&lt;/p&gt;
&lt;p&gt;You can choose the &lt;strong&gt;Meta Quest&lt;/strong&gt; option to use the Oculus Platform Utility.&lt;/p&gt;
&lt;img alt=&quot;PWABuilder packaging options.&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/nmHjO4Tbr01psSEfaaOO.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Pro tip: PWABuilder uses a slightly modified version of Oculus Platform Utility that generates much smaller APK files by &lt;a href=&quot;https://github.com/pwa-builder/pwabuilder-oculus/pull/7&quot;&gt;removing unused code and resources&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;You can choose the &lt;strong&gt;Android&lt;/strong&gt; option to use the Bubblewrap and select the &lt;strong&gt;Meta Quest compatible&lt;/strong&gt;
checkbox.&lt;/p&gt;
&lt;img alt=&quot;Packaging PWAs with PWABuilder using Bubblewrap.&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/jLnuUwLBi73HGXiv3XUy.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Pro tip: since PWABuilder is a PWA itself that works everywhere there is a browser, you can open it in the Meta Quest Browser and perform all the steps right on your Meta Quest. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;installing-pwas-with-adb&quot;&gt;Installing PWAs with ADB &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#installing-pwas-with-adb&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; To enable Android Debug Bridge (ADB) on the Meta Quest device, you must &lt;a href=&quot;https://developer.oculus.com/documentation/native/android/mobile-device-setup/&quot;&gt;enable developer mode&lt;/a&gt; in the companion mobile app. Before you can put your device in developer mode, you must belong to (or have created) a developer organization in the Oculus Developer Center. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;After creating the APK file, you can sideload it to the Meta Quest device using the ADB via
USB or Wi-Fi:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;adb &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; app-release-signed.apk&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;If you use the Bubblewrap CLI for packaging PWAs, it provides a convenient alias command to
sideload the APK file:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;bubblewrap &lt;span class=&quot;token function&quot;&gt;install&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; Pro tip: you can use &lt;a href=&quot;https://yume-chan.github.io/ya-webadb/install&quot;&gt;Android Web Toolbox&lt;/a&gt; to sideload APK files. It&#39;s an &lt;a href=&quot;https://github.com/yume-chan/ya-webadb/&quot;&gt;open source&lt;/a&gt; web app that allows you to access all ADB functionality right from the browser even from another Android device. No installation or drivers are required thanks to the &lt;a href=&quot;https://web.dev/usb/&quot;&gt;Web USB API&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Sideloaded apps appear in an &lt;strong&gt;Unknown Sources&lt;/strong&gt; section in the app drawer.&lt;/p&gt;
&lt;h3 id=&quot;app-submission&quot;&gt;App submission &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#app-submission&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Submission and consideration for the Oculus Store is only available if you have been approved as an Meta Quest Store developer. Distribution of PWAs via &lt;a href=&quot;https://developer.oculus.com/blog/introducing-app-lab-a-new-way-to-distribute-oculus-quest-apps/&quot;&gt;App Lab&lt;/a&gt; is not currently available. The Meta team will share more on when and how you can submit a PWA to App Lab soon. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Uploading and submitting PWAs to the Oculus Store is
&lt;a href=&quot;https://developer.oculus.com/documentation/web/pwa-submit-app/&quot; rel=&quot;noopener&quot;&gt;covered&lt;/a&gt; in detail in the Oculus
Developer Center docs.&lt;/p&gt;
&lt;p&gt;Apart from submitting apps to the Oculus Store, developers can also distribute their apps via platforms
like &lt;a href=&quot;https://sidequestvr.com/&quot; rel=&quot;noopener&quot;&gt;SideQuest&lt;/a&gt; directly to consumers safely and securely, without requiring
store approval. This allows them to get an app directly to end users, even if it is early in development,
experimental, or aimed at a unique audience.&lt;/p&gt;
&lt;h2 id=&quot;testing-multi-tab-apps&quot;&gt;Testing multi-tab apps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#testing-multi-tab-apps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To test multi-tab apps, I created a little &lt;a href=&quot;https://tomayac.github.io/oculus-pwa-test/&quot; rel=&quot;noopener&quot;&gt;test PWA&lt;/a&gt;
that demonstrates the various link features: namely opening a new in-PWA tab, staying on the current
tab, opening a new browser window, and opening in a WebView staying on the current tab. Create a
locally installable copy of this app by running the commands below on your machine.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ovr-platform-util create-pwa -o test.apk --android-skd ~/bin/android-10 --web-manifest-url https://tomayac.github.io/oculus-pwa-test/manifest.json --package-name com.example.pwa&lt;br /&gt;adb &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; test.apk&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Here&#39;s a screencast of the test app.&lt;/p&gt;
&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;3ZlxCjW9rtg&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;h2 id=&quot;an-oculus-version-of-svgcode&quot;&gt;An Oculus version of SVGcode &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#an-oculus-version-of-svgcode&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To take the instructions for a spin, I created an Oculus version of my most recent PWA,
&lt;a href=&quot;https://web.dev/svgcode/&quot;&gt;SVGcode&lt;/a&gt;. You can download the resulting APK file
&lt;a href=&quot;https://drive.google.com/file/d/1ieGjwIXGGWmh0j9WpBdWqP7Bns3McNr1/view?usp=sharing&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;output.apk&lt;/code&gt;&lt;/a&gt;
from my Google Drive. If you want to investigate the package further, I have a
&lt;a href=&quot;https://drive.google.com/drive/folders/1EFtXK9ApJiJitfysZS_Z7iIWWiKEglu-?usp=sharing&quot; rel=&quot;noopener&quot;&gt;decompiled version&lt;/a&gt;,
too. Find the build instructions in
&lt;a href=&quot;https://github.com/tomayac/SVGcode/blob/6b23adf63a0a3a1b3828866dbb7db0f10206397f/package.json#L16&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using the app on Oculus works fine, including the ability to open and save files. The Oculus Browser
doesn&#39;t support the &lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt;, but the
&lt;a href=&quot;https://web.dev/browser-fs-access/#the-traditional-way-of-dealing-with-files&quot;&gt;fallback approach&lt;/a&gt; helps. The only
thing that didn&#39;t function is pinch-zooming. My expectation was for it to work by pressing the
trigger button on both controllers and then moving the controllers in opposed directions. Other than
that, everything else was performant and responsive, as you can see in the embedded screencast.&lt;/p&gt;
&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;Gjc0IR17kAk&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;h2 id=&quot;immersive-3d-webxr-pwas&quot;&gt;Immersive 3D WebXR PWAs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#immersive-3d-webxr-pwas&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;PWA support on Oculus Quest is not limited to flat 2D apps. Developers can build immersive 3D
experiences for VR using the &lt;a href=&quot;https://web.dev/tags/webxr/&quot;&gt;WebXR API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Wondering how various prompts (PWA install, permission requests, notifications) are handled
from within VR, if at all?&lt;/p&gt;
&lt;p&gt;Here&#39;s a screencast of
&lt;a href=&quot;https://immersive-web.github.io/webxr-samples/tests/permission-request.html&quot; rel=&quot;noopener&quot;&gt;User Agent Prompts test&lt;/a&gt;
from the &lt;a href=&quot;https://immersive-web.github.io/&quot; rel=&quot;noopener&quot;&gt;Immersive Web Working Group&lt;/a&gt;&#39;s
&lt;a href=&quot;https://immersive-web.github.io/webxr-samples/tests/&quot; rel=&quot;noopener&quot;&gt;WebXR Tests&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/4fvDWOo2XxF67M8r3W3O.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;p&gt;As you can see, entering VR mode requires the user&#39;s permission. Permissions are asked once per origin.
Requesting permissions leaves the immersive mode. Notifications are currently not supported.&lt;/p&gt;
&lt;h3 id=&quot;hand-tracking&quot;&gt;Hand tracking &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#hand-tracking&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can use your hands to interact with PWAs in immersive mode thanks to the
&lt;a href=&quot;https://immersive-web.github.io/webxr-hand-input/&quot; rel=&quot;noopener&quot;&gt;WebXR Hand Input API&lt;/a&gt; and Meta&#39;s
&lt;a href=&quot;https://ai.facebook.com/blog/hand-tracking-deep-neural-networks/&quot; rel=&quot;noopener&quot;&gt;AI-based hand-tracking system&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&#39;s a screencast of
&lt;a href=&quot;https://immersive-web.github.io/webxr-samples/immersive-hands.html&quot; rel=&quot;noopener&quot;&gt;Hand Tracking Sample&lt;/a&gt;
from the Immersive Web Working Group&#39;s &lt;a href=&quot;https://immersive-web.github.io/webxr-samples/&quot; rel=&quot;noopener&quot;&gt;WebXR Samples&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/nDm0rY5DvtyTbDgUqFTS.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;h3 id=&quot;augmentedmixed-reality-passthrough&quot;&gt;Augmented/Mixed Reality (Passthrough) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#augmentedmixed-reality-passthrough&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As announced at Meta Connect 2022, Meta Quest Browser
&lt;a href=&quot;https://developer.oculus.com/documentation/web/webxr-mixed-reality/&quot; rel=&quot;noopener&quot;&gt;has added support&lt;/a&gt; for
&lt;a href=&quot;https://web.dev/web-ar/&quot;&gt;WebXR Augmented Reality (AR)&lt;/a&gt;, also known as Mixed Reality (MR), on Meta Quest 2 and Meta
Quest Pro devices.&lt;/p&gt;
&lt;p&gt;Let&#39;s check a slightly modified A-Frame
&lt;a href=&quot;https://aframe.io/docs/1.3.0/introduction/#getting-started&quot; rel=&quot;noopener&quot;&gt;starter example&lt;/a&gt; with scaled-down
models and hidden sky and plane for augmented reality.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://aframe.io/&quot; rel=&quot;noopener&quot;&gt;A-Frame&lt;/a&gt; is an open source web framework for building 3D/VR/AR experiences
entirely out of declarative, reusable &lt;a href=&quot;https://web.dev/custom-elements-v1/&quot;&gt;custom HTML elements&lt;/a&gt; that are easy to
read, understand, and copy-and-paste.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 420px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi; xr-spatial-tracking; fullscreen&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/a-frame-hello-world-ar?attributionHidden=true&amp;sidebarCollapsed=true&amp;path=index.html&amp;previewSize=50&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;a-frame-hello-world-ar on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Here&#39;s a screencast of this demo on Meta Quest 2.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/RmPUmnh57gDZ2bbGkOWR.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Meta Quest 2 has monochrome cameras, so the passthrough is in grayscale, while Meta Quest Pro has
color cameras.&lt;/p&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#conclusions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;PWAs on Oculus Quest 2 are a lot of fun and very promising. The endless virtual canvas that
lets users scale their screen to whatever fits the current task best has a lot of potential to
change the way we work in the future. While typing in VR with hand tracking is still in its infancy
and, at least for me, doesn&#39;t work very reliably yet, it works well enough for entering URLs or
typing short texts.&lt;/p&gt;
&lt;p&gt;What I like the most about PWAs on the Oculus Quest 2 is that they are just regular PWAs that
can be used unchanged in a browser tab or through a thin APK wrapper without any platform-specific
APIs. Targeting multiple platforms with the same code has never been easier. Here&#39;s to PWAs in VR
and AR on the web. The future is bright!&lt;/p&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/pwas-on-oculus-2/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Oculus Quest 2 photo by &lt;a href=&quot;https://flickr.com/people/191783462@N03/&quot; rel=&quot;noopener&quot;&gt;Maximilian Prandstätter&lt;/a&gt; on
&lt;a href=&quot;https://flickr.com/photos/191783462@N03/50844634326&quot; rel=&quot;noopener&quot;&gt;Flickr&lt;/a&gt;. Oculus Store images of
&lt;a href=&quot;https://www.oculus.com/experiences/quest/6102857836422862&quot; rel=&quot;noopener&quot;&gt;Instagram&lt;/a&gt;,
&lt;a href=&quot;https://www.oculus.com/experiences/quest/6126469507395223&quot; rel=&quot;noopener&quot;&gt;Facebook&lt;/a&gt;,
&lt;a href=&quot;https://www.oculus.com/experiences/quest/1916519981771802&quot; rel=&quot;noopener&quot;&gt;Oculus Browser&lt;/a&gt;, and
&lt;a href=&quot;https://www.oculus.com/experiences/quest/4949538568409451&quot; rel=&quot;noopener&quot;&gt;Spike&lt;/a&gt; apps as well as
&lt;a href=&quot;https://developer.oculus.com/pwa/&quot; rel=&quot;noopener&quot;&gt;app discoverability&lt;/a&gt; illustration and
&lt;a href=&quot;https://support.oculus.com/articles/headsets-and-accessories/controllers-and-hand-tracking/hand-tracking-quest-2/&quot; rel=&quot;noopener&quot;&gt;hand tracking&lt;/a&gt;
animation courtesy of Meta. Hero image by Arnau Marín i Puig. This post was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author><author>
      <name>Alexey Rodionov</name>
    </author>
  </entry>
  
  <entry>
    <title>Designcember Calculator</title>
    <link href="https://web.dev/designcember-calculator/"/>
    <updated>2021-12-26T00:00:00Z</updated>
    <id>https://web.dev/designcember-calculator/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;the-challenge&quot;&gt;The challenge &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/designcember-calculator/#the-challenge&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;m a kid of the 1980s. A thing that was all the rage back when I was in high school were solar
calculators. We were all given a
&lt;a href=&quot;https://en.wikipedia.org/wiki/TI-30#/media/File:TI-30X_SOLAR,_2.jpg&quot; rel=&quot;noopener&quot;&gt;TI-30X SOLAR&lt;/a&gt; by the school,
and I have fond memories of us benchmarking our calculators against each other by calculating the
factorial of 69, the highest number the TI-30X could handle. (The speed variance was very
measurable, I have still no idea why.)&lt;/p&gt;
&lt;p&gt;Now, almost 28 years later, I thought it would be a fun Designcember challenge to recreate the
calculator in HTML, CSS, and JavaScript. Being not much of a designer, I did not start from scratch,
but with a &lt;a href=&quot;https://codepen.io/sassjajc/pen/zNJgKg&quot; rel=&quot;noopener&quot;&gt;CodePen&lt;/a&gt; by
&lt;a href=&quot;https://codepen.io/sassjajc&quot; rel=&quot;noopener&quot;&gt;Sassja Ceballos&lt;/a&gt;.&lt;/p&gt;
&lt;img alt=&quot;CodePen view with stacked HTML, CSS, and JS panels on the left and the calculator preview on the right.&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/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/DvhjfNHAYtSgJ6jK8uoI.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;make-it-installable&quot;&gt;Make it installable &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/designcember-calculator/#make-it-installable&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While not a bad start, I decided to pump it up for full skeuomorphic awesomeness. The first step was
to make it a PWA so it could be installed. I maintain a
&lt;a href=&quot;https://glitch.com/edit/#!/baseline-pwa&quot; rel=&quot;noopener&quot;&gt;baseline PWA template on Glitch&lt;/a&gt; that I remix whenever I
need a quick demo. Its service worker will not win you any coding award and it is definitely &lt;em&gt;not&lt;/em&gt;
production-ready, but it is sufficient to trigger Chromium&#39;s mini infobar so the app can be
installed.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;self&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;install&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;skipWaiting&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;self&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;activate&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clients&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;claim&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;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;waitUntil&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;async&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;=&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 string&quot;&gt;&#39;navigationPreload&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;registration&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;await&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;navigationPreload&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enable&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 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;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;br /&gt;self&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;fetch&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;respondWith&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;async&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;=&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;        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;preloadResponse&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;response&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; response&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;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&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;request&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;catch&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Offline&#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;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 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;h2 id=&quot;blending-with-mobile&quot;&gt;Blending with mobile &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/designcember-calculator/#blending-with-mobile&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that the app is installable, the next step is to make it blend in with the operating system apps as much as possible. On
mobile, I can do this by setting the display mode to &lt;code&gt;fullscreen&lt;/code&gt; in the Web App Manifest.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;display&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fullscreen&quot;&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;On devices with a camera hole or notch,
&lt;a href=&quot;https://webkit.org/blog/7929/designing-websites-for-iphone-x/&quot; rel=&quot;noopener&quot;&gt;tweaking the viewport&lt;/a&gt; so that the
content covers the whole screen makes the app look gorgeous.&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;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;initial-scale=1, viewport-fit=cover&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;img alt=&quot;Designcember Calculator running fullscreen on a Pixel 6 Pro phone.&quot; decoding=&quot;async&quot; height=&quot;773&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/kEOrYPiDbg6DSTGHeB0C.jpg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;blending-with-desktop&quot;&gt;Blending with desktop &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/designcember-calculator/#blending-with-desktop&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On desktop, there is a cool feature I can use:
&lt;a href=&quot;https://web.dev/window-controls-overlay/&quot;&gt;Window Controls Overlay&lt;/a&gt;, which allows me to put content in the title
bar of the app window. The first step is to override the display mode fallback sequence so it tries
to use &lt;code&gt;window-controls-overlay&lt;/code&gt; first when it is available.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;display_override&quot;&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 string&quot;&gt;&quot;window-controls-overlay&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;This makes the title bar effectively go away and the content moves up into the title bar area as if
the title bar were not there. My idea is to move the skeuomorphic solar cell up into the title bar
and the rest of the calculator UI down accordingly, which I can do with some CSS that uses
the &lt;code&gt;titlebar-area-*&lt;/code&gt; environment variables. You will notice that all the selectors carry a &lt;code&gt;wco&lt;/code&gt;
class, which will be relevant a couple of paragraphs down.&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;#calc_solar_cell.wco&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;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; fixed&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0.25rem + &lt;span class=&quot;token function&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlebar-area-x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&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 property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0.75rem + &lt;span class=&quot;token function&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlebar-area-y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&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 property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlebar-area-width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; - 0.5rem&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;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlebar-area-height&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 33px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; - 0.5rem&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 selector&quot;&gt;#calc_display_surface.wco&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;margin-top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlebar-area-height&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 33px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; - 0.5rem&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;Next, I need to decide which elements to make draggable, since the title bar that I would usually
use for dragging is not available. In the style of a classic widget, I can even make the
entire calculator draggable by applying &lt;code&gt;(-webkit-)app-region: drag&lt;/code&gt;, apart from the buttons, which
get &lt;code&gt;(-webkit-)app-region: no-drag&lt;/code&gt; so they cannot be used to drag.&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;#calc_inside.wco,&lt;br /&gt;#calc_solar_cell.wco&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;-webkit-app-region&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; drag&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;app-region&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; drag&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 selector&quot;&gt;button&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;-webkit-app-region&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; no-drag&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;app-region&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; no-drag&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 final step is to make the app reactive to window controls overlay changes. In a true progressive
enhancement approach, I only load the code for this feature when the browser supports it.&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;&lt;span class=&quot;token string&quot;&gt;&#39;windowControlsOverlay&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/wco.js&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Whenever the window controls overlay geometry changes, I modify the app to make
it look as natural as possible. It is a good idea to debounce this event, since it can be triggered
frequently when the user resizes the window. Namely, I apply the &lt;code&gt;wco&lt;/code&gt; class to some elements, so my
CSS from above kicks in, and I also change the theme color. I can detect if the window controls
overlay is visible by checking the &lt;code&gt;navigator.windowControlsOverlay.visible&lt;/code&gt; property.&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;const&lt;/span&gt; meta &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;meta[name=&quot;theme-color&quot;]&#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;const&lt;/span&gt; nodes &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;querySelectorAll&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;#calc_display_surface, #calc_solar_cell, #calc_outside, #calc_inside&#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;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;toggleWCO&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;span class=&quot;token operator&quot;&gt;=&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;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowControlsOverlay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visible&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;    meta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#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 keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    meta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;#385975&#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;  nodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&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 parameter&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;wco&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowControlsOverlay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visible&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;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;debounce&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 parameter&quot;&gt;func&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; wait&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;let&lt;/span&gt; timeout&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 keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;executedFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;args&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;later&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;span class=&quot;token operator&quot;&gt;=&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;clearTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timeout&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;func&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;args&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;span class=&quot;token function&quot;&gt;clearTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timeout&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;    timeout &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;later&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; wait&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;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;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowControlsOverlay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ongeometrychange &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;debounce&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 parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;toggleWCO&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 number&quot;&gt;250&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 function&quot;&gt;toggleWCO&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;Now with all this in place, I get a calculator widget that feels almost like the classic
&lt;a href=&quot;https://en.wikipedia.org/wiki/Winamp&quot; rel=&quot;noopener&quot;&gt;Winamp&lt;/a&gt; with one of the oldschool
&lt;a href=&quot;https://en.wikipedia.org/wiki/Winamp#/media/File:Winamp5.png&quot; rel=&quot;noopener&quot;&gt;Winamp  themes&lt;/a&gt;. I can now
freely place the calculator on my desktop and activate the window controls feature by clicking the
chevron in the upper right corner.&lt;/p&gt;
&lt;img alt=&quot;Designcember Calculator running in standalone mode with the Window Controls Overlay feature active. The display spells &amp;#x27;Google&amp;#x27; in the calculator alphabet.&quot; decoding=&quot;async&quot; height=&quot;430&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 267px) 267px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9iqLIfTrHYv0O8J1tBrr.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9iqLIfTrHYv0O8J1tBrr.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9iqLIfTrHYv0O8J1tBrr.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9iqLIfTrHYv0O8J1tBrr.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9iqLIfTrHYv0O8J1tBrr.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9iqLIfTrHYv0O8J1tBrr.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9iqLIfTrHYv0O8J1tBrr.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9iqLIfTrHYv0O8J1tBrr.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9iqLIfTrHYv0O8J1tBrr.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9iqLIfTrHYv0O8J1tBrr.png?auto=format&amp;w=534 534w&quot; width=&quot;267&quot; /&gt;
&lt;h2 id=&quot;an-actually-working-solar-cell&quot;&gt;An actually working solar cell &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/designcember-calculator/#an-actually-working-solar-cell&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For the ultimate geekery, I of course needed to make the solar cell actually work. The calculator
should only be functioning if there is enough light. The way I modeled this is through setting the
CSS &lt;code&gt;opacity&lt;/code&gt; of the digits on the display via a CSS variable &lt;code&gt;--opacity&lt;/code&gt; that I control via
JavaScript.&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;:root&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.75&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 selector&quot;&gt;#calc_expression,&lt;br /&gt;#calc_result&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; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--opacity&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;To detect if enough light is available for the calculator to work, I use the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/AmbientLightSensor&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;AmbientLightSensor&lt;/code&gt;&lt;/a&gt; API. For
this API to be available, I needed to set the &lt;code&gt;#enable-generic-sensor-extra-classes&lt;/code&gt; flag in
&lt;code&gt;about:flags&lt;/code&gt; and request the &lt;code&gt;&#39;ambient-light-sensor&#39;&lt;/code&gt; permission. As before, I use progressive
enhancement to only load the relevant code when the API is supported.&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;&lt;span class=&quot;token string&quot;&gt;&#39;AmbientLightSensor&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/als.js&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The sensor returns the ambient light in &lt;a href=&quot;https://en.wikipedia.org/wiki/Lux&quot; rel=&quot;noopener&quot;&gt;lux&lt;/a&gt; units whenever a
new reading is available. Based on a
&lt;a href=&quot;https://en.wikipedia.org/wiki/Lux#Illuminance&quot; rel=&quot;noopener&quot;&gt;table of values&lt;/a&gt; of typical light situations, I came
up with a very simple formula to convert the lux value to a value between 0 and 1 that I
programmatically assign to the &lt;code&gt;--opacity&lt;/code&gt; variable.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;luxToOpacity&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 parameter&quot;&gt;lux&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;lux &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;250&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;1&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;return&lt;/span&gt; lux &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;250&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;const&lt;/span&gt; sensor &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;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AmbientLightSensor&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;sensor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onreading&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;span class=&quot;token operator&quot;&gt;=&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;Current light level:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sensor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;illuminance&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;documentElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setProperty&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;--opacity&#39;&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;luxToOpacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sensor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;illuminance&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;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;sensor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onerror&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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;state&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;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;permissions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&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;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ambient-light-sensor&#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;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;state &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;granted&#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;    sensor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&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 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;In the video below you can see how the calculator starts working once I turn the room light up
enough. And there you have it: a skeuomorphic solar calculator that actually works. My good old
time-tested TI-30X SOLAR has come a long way indeed.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/lXUTb872NtCdw7fqrQY3.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/designcember-calculator/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Be sure to play with the &lt;a href=&quot;https://designcember-calculator.glitch.me/&quot; rel=&quot;noopener&quot;&gt;Designcember Calculator demo&lt;/a&gt;
and check out the &lt;a href=&quot;https://glitch.com/edit/#!/designcember-calculator&quot; rel=&quot;noopener&quot;&gt;source code on Glitch&lt;/a&gt;. (To
install the app, you need to open it in its own window. The embedded version below will not trigger
the mini infobar.)&lt;/p&gt;
&lt;!-- Copy and Paste Me --&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 750px; width: 400px;&quot;&gt;
  &lt;iframe src=&quot;https://designcember-calculator.glitch.me/&quot; title=&quot;designcember-calculator on Glitch&quot; allow=&quot;geolocation; microphone; camera; midi; vr; encrypted-media&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;
  &lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Happy &lt;a href=&quot;https://designcember.com/&quot; rel=&quot;noopener&quot;&gt;Designcember&lt;/a&gt;!&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>SVGcode: a PWA to convert raster images to SVG vector graphics</title>
    <link href="https://web.dev/svgcode/"/>
    <updated>2021-11-19T00:00:00Z</updated>
    <id>https://web.dev/svgcode/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; In a hurry? Go &lt;a href=&quot;https://svgco.de/&quot;&gt;straight to the SVGcode app&lt;/a&gt; and read the article later. &lt;/div&gt;&lt;/aside&gt;
&lt;figure data-size=&quot;full&quot;&gt;
  &lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;kcvfyQh6J-0&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
  &lt;figcaption&gt;
    (If you prefer watching over reading, this article is also available as a &lt;a href=&quot;https://youtu.be/kcvfyQh6J-0&quot;&gt;video&lt;/a&gt;.)
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;from-raster-to-vector&quot;&gt;From raster to vector &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#from-raster-to-vector&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Have you ever scaled an image and the result was pixelated and unsatisfactory? If
so, you have probably dealt with a raster image format such as WebP, PNG, or JPG.&lt;/p&gt;
&lt;figure&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/bIiC6vyZLqgGWPuFF9od.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    Scaling up a raster image makes it look pixelated.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In contrast, vector graphics are images that are defined by points in a coordinate system. These
points are connected by lines and curves to form polygons and other shapes. Vector graphics have an
advantage over raster graphics in that they may be scaled up or down to any resolution
without pixelation.&lt;/p&gt;
&lt;figure&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/yM32DfKFp8ooBAshjlUE.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    Scaling up a vector image with no loss of quality.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;introducing-svgcode&quot;&gt;Introducing SVGcode &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#introducing-svgcode&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have built a PWA called &lt;a href=&quot;https://svgco.de/&quot; rel=&quot;noopener&quot;&gt;SVGcode&lt;/a&gt; that can help you convert raster images to
vectors. Credit where credit is due: I didn&#39;t invent this. With SVGcode, I just stand on the
shoulders of a command line tool called &lt;a href=&quot;http://potrace.sourceforge.net/&quot; rel=&quot;noopener&quot;&gt;Potrace&lt;/a&gt; by
&lt;a href=&quot;https://www.mathstat.dal.ca/~selinger/&quot; rel=&quot;noopener&quot;&gt;Peter Selinger&lt;/a&gt; that I have
&lt;a href=&quot;https://www.npmjs.com/package/esm-potrace-wasm&quot; rel=&quot;noopener&quot;&gt;converted to Web Assembly&lt;/a&gt;, so it can be used in a
Web app.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;SVGcode application screenshot.&quot; decoding=&quot;async&quot; height=&quot;483&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/xMosQFxacBsz116CcFwy.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The &lt;a href=&quot;https://svgco.de/&quot;&gt;SVGcode&lt;/a&gt; app.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;using-svgcode&quot;&gt;Using SVGcode &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#using-svgcode&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First, I want to show you how to use the app. I start with the teaser image for Chrome Dev Summit
that I downloaded from the ChromiumDev Twitter channel. This is a PNG raster image that I then
drag onto the SVGcode app. When I drop the file, the app traces the image color by color,
until a vectorized version of the input appears. I can now zoom into the image, and as you can see,
the edges stay sharp. But zooming in on the Chrome logo, you can see that the tracing wasn&#39;t
perfect, and especially the outlines of the logo look a bit speckled. I can improve the result by
de-speckling the tracing by suppressing speckles of up to, say, five pixels.&lt;/p&gt;
&lt;figure&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/WvmYtNTHAbINP3ec1h1w.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    Converting a dropped image to SVG.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;posterization-in-svgcode&quot;&gt;Posterization in SVGcode &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#posterization-in-svgcode&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;An important step for vectorization, especially for photographic images, is posterizing the input
image to reduce the number of colors. SVGcode allows me to do this per color channel, and see the
resulting SVG as I make changes. When I&#39;m happy with the result, I can save the SVG to my hard disk
and use it wherever I like.&lt;/p&gt;
&lt;figure&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/BzcR6yPyuQ0TIgzwYbLy.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    Posterizing an image to reduce the number of colors.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;apis-used-in-svgcode&quot;&gt;APIs used in SVGcode &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#apis-used-in-svgcode&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that you have seen what the app is capable of, let me show you some of the APIs that help make
the magic happen.&lt;/p&gt;
&lt;h3 id=&quot;progressive-web-app&quot;&gt;Progressive Web App &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#progressive-web-app&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;SVGcode is an installable Progressive Web App and therefore fully offline enabled. The app is based
on the
&lt;a href=&quot;https://github.com/vitejs/vite/tree/main/packages/create-vite/template-vanilla&quot; rel=&quot;noopener&quot;&gt;Vanilla JS template&lt;/a&gt;
for &lt;a href=&quot;https://github.com/vitejs/vite&quot; rel=&quot;noopener&quot;&gt;Vite.js&lt;/a&gt; and uses the popular
&lt;a href=&quot;https://github.com/antfu/vite-plugin-pwa&quot; rel=&quot;noopener&quot;&gt;Vite plugin PWA&lt;/a&gt;, which creates a service worker that
uses &lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox.js&lt;/a&gt; under the hood. Workbox is a set
of libraries that can power a production-ready service worker for Progressive Web Apps, This pattern
may not necessarily work for all apps, but for SVGcode&#39;s use case it&#39;s great.&lt;/p&gt;
&lt;h4 id=&quot;window-controls-overlay&quot;&gt;Window Controls Overlay &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#window-controls-overlay&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To maximize the available screen real estate, SVGcode uses
&lt;a href=&quot;https://web.dev/window-controls-overlay/&quot;&gt;Window Controls Overlay&lt;/a&gt; customization by moving its main menu up into
the titlebar area. You can see this get activated at the end of the install flow.&lt;/p&gt;
&lt;figure&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/aDk3LFxexL6g2NbH4RCA.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    Installing SVGcode and activating the Window Controls Overlay customization.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;file-system-access-api&quot;&gt;File System Access API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#file-system-access-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To open input image files and save the resulting SVGs, I use the
&lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt;. This allows me to keep a reference to previously
opened files and to continue where I left off, even after an app reload. Whenever an image gets
saved, it is optimized via the &lt;a href=&quot;https://github.com/svg/svgo&quot; rel=&quot;noopener&quot;&gt;svgo&lt;/a&gt; library, which may take a moment,
depending on the complexity of the SVG. Showing the file save dialog requires a user gesture. It is
therefore important to obtain the file handle before the SVG optimization happens, so the user
gesture is not invalidated by the time the optimized SVG is ready.&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;try&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;let&lt;/span&gt; svg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; svgOutput&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; handle &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 comment&quot;&gt;// To not consume the user gesture obtain the handle before preparing the&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// blob, which may take longer.&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;supported&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;    handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;showSaveFilePicker&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;types&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;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SVG file&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;accept&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 string-property property&quot;&gt;&#39;image/svg+xml&#39;&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 string&quot;&gt;&#39;.svg&#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 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 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;showToast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i18n&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;optimizingSVG&#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 number&quot;&gt;Infinity&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;  svg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;optimizeSVG&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svg&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;showToast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i18n&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;savedSVG&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; blob &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;Blob&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;svg&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 literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/svg+xml&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fileSave&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&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;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SVG file&#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; handle&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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;showToast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;h4 id=&quot;drag-an-drop&quot;&gt;Drag an drop &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#drag-an-drop&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;For opening an input image, I can either use the file open feature, or, as you have seen above, just
drag and drop an image file onto the app. The file open feature is pretty straightforward, more
interesting is the drag and drop case. What&#39;s particularly nice about this is that you can
get a file system handle from the data transfer item via the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/DataTransferItem/getAsFileSystemHandle&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;getAsFileSystemHandle()&lt;/code&gt;&lt;/a&gt;
method. As mentioned before, I can persist this handle, so it&#39;s ready when the app gets reloaded.&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;&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;drop&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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 operator&quot;&gt;=&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;  dropContainer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;dropenter&#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;const&lt;/span&gt; item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;items&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 keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;file&#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;    inputImage&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;br /&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;&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 operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blobURL&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;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;once&lt;/span&gt;&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;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 keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAsFileSystemHandle&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;file&#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;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 keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFile&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;const&lt;/span&gt; blobURL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&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;    inputImage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; blobURL&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FILE_HANDLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handle&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;For more details, check out the article on the &lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt; and,
if you&#39;re interested, study the SVGcode source code in
&lt;a href=&quot;https://github.com/tomayac/SVGcode/blob/main/src/js/filesystem.js&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;src/js/filesystem.js&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;async-clipboard-api&quot;&gt;Async Clipboard API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#async-clipboard-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;SVGcode is also fully integrated with the operating system&#39;s clipboard via the Async Clipboard API.
You can paste images from the operating system&#39;s file explorer into the app either by clicking the
paste image button or by pressing command or control plus v on your keyboard.&lt;/p&gt;
&lt;figure&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/9wHL0Uc7eHFEFF99anaB.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    Pasting an image from the file explorer into SVGcode.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The Async Clipboard API has recently gained the ability to deal with SVG images as well, so you can
also copy an SVG image and paste it into another application for further processing.&lt;/p&gt;
&lt;figure&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/KiGt5UHOvZZEvPhtwIny.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    Copying an image from SVGcode into SVGOMG.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;copyButton&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;async&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;=&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;let&lt;/span&gt; svg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; svgOutput&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;showToast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i18n&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;optimizingSVG&#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 number&quot;&gt;Infinity&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;  svg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;optimizeSVG&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svg&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;const&lt;/span&gt; textBlob &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;Blob&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;svg&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 literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text/plain&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; svgBlob &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;Blob&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;svg&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 literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/svg+xml&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&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;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClipboardItem&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;svgBlob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; svgBlob&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;textBlob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; textBlob&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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;showToast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i18n&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;copiedSVG&#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 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;To learn more, read the &lt;a href=&quot;https://web.dev/async-clipboard/&quot;&gt;Async Clipboard&lt;/a&gt; article, or see the file
&lt;a href=&quot;https://github.com/tomayac/SVGcode/blob/main/src/js/clipboard.js&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;src/js/clipboard.js&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;file-handling&quot;&gt;File Handling &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#file-handling&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of my favorite features of SVGcode is how well it blends in with the operating system. As an
installed PWA, it can become a file handler, or even the default file handler, for image files. This
means that when I&#39;m in the Finder on my macOS machine, I can right-click an image and open it with
SVGcode. This feature is called File Handling and works based on the file_handlers property in the
Web App Manifest and the launch queue, which allows the app to consume the passed file.&lt;/p&gt;
&lt;figure&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/DEQLkm1vrt226xsAoysI.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    Opening a file from the desktop with installed SVGcode app.
  &lt;/figcaption&gt;
&lt;/figure&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;launchQueue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setConsumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;launchParams&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;launchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&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 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;const&lt;/span&gt; handle &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; launchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&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;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFile&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;image/&#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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; blobURL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&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;      inputImage&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;br /&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;&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 operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blobURL&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;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;once&lt;/span&gt;&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;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;      inputImage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; blobURL&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FILE_HANDLE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handle&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;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;For more information, see &lt;a href=&quot;https://web.dev/file-handling/&quot;&gt;Let installed web applications be file handlers&lt;/a&gt;, and view the source code in
&lt;a href=&quot;https://github.com/tomayac/SVGcode/blob/main/src/js/filehandling.js&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;src/js/filehandling.js&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;web-share-files&quot;&gt;Web Share (Files) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#web-share-files&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Another example of blending in with the operating system is the app&#39;s share feature. Assuming I want
to make edits to an SVG created with SVGcode, one way to deal with this would be to save the file,
launch the SVG editing app, and then open the SVG file from there. A smoother flow, though, is to
use the &lt;a href=&quot;https://web.dev/web-share/#sharing-files&quot;&gt;Web Share API&lt;/a&gt;, which allows for files to be shared directly. So if
the SVG editing app is a share target, it can directly receive the file without deviation.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;shareSVGButton&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;async&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;=&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;let&lt;/span&gt; svg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; svgOutput&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  svg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;optimizeSVG&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svg&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;const&lt;/span&gt; suggestedFileName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;getSuggestedFileName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FILE_HANDLE&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 string&quot;&gt;&#39;Untitled.svg&#39;&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;const&lt;/span&gt; file &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;File&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;svg&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; suggestedFileName&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;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/svg+xml&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &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 literal-property property&quot;&gt;files&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;file&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;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;canShare&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&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;      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;share&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;AbortError&#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;        console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;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;figure&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/p3Dn9FXrNLPtb4syXnfl.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    Sharing an SVG image to Gmail.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;web-share-target-files&quot;&gt;Web Share Target (Files) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#web-share-target-files&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The other way round, SVGcode can also act as a share target and receive files from other apps. To
make this work, the app needs to let the operating system know via the
&lt;a href=&quot;https://web.dev/web-share-target/&quot;&gt;Web Share Target API&lt;/a&gt; what types of data it can accept. This happens via a
dedicated field in the Web App Manifest.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;share_target&quot;&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 property&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://svgco.de/share-target/&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;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;POST&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;&quot;enctype&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;multipart/form-data&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;&quot;params&quot;&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 property&quot;&gt;&quot;files&quot;&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image&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;&quot;accept&quot;&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 string&quot;&gt;&quot;image/jpeg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/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;image/gif&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;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;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 &lt;code&gt;action&lt;/code&gt; route does not actually exist, but is handled purely in the service worker&#39;s &lt;code&gt;fetch&lt;/code&gt;
handler, which then passes on received files for actual processing in the app.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;self&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;fetch&#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 parameter&quot;&gt;fetchEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;br /&gt;    fetchEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&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 function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/share-target/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;br /&gt;    fetchEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;method &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;POST&#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;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; fetchEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;respondWith&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;async&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;=&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;const&lt;/span&gt; formData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fetchEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;formData&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;const&lt;/span&gt; image &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; formData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;image&#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;const&lt;/span&gt; keys &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&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;const&lt;/span&gt; mediaCache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&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;br /&gt;          keys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&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 parameter&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;media&#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 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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; mediaCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;shared-image&#39;&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;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;image&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; Response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./?share-target&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;303&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;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;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;figure&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/SVtll3ShgNigvkVLu7cn.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    Sharing a screenshot to SVGcode.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Alright, this was a quick tour through some of the advanced app features in SVGcode. I hope this app
can become an essential tool for your image processing needs alongside other amazing apps like
&lt;a href=&quot;https://squoosh.app/&quot; rel=&quot;noopener&quot;&gt;Squoosh&lt;/a&gt; or &lt;a href=&quot;https://jakearchibald.github.io/svgomg/&quot; rel=&quot;noopener&quot;&gt;SVGOMG&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;SVGcode is available at &lt;a href=&quot;https://svgco.de/&quot; rel=&quot;noopener&quot;&gt;svgco.de&lt;/a&gt;. See what I did there? You can
&lt;a href=&quot;https://github.com/tomayac/SVGcode&quot; rel=&quot;noopener&quot;&gt;review its source code on GitHub&lt;/a&gt;. Note that since Potrace is
GPL-licensed, so is SVGcode. And with that, happy vectorizing! I hope SVGcode will be useful, and
some of its features can inspire your next app.&lt;/p&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/svgcode/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by &lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>JavaScript eventing deep dive</title>
    <link href="https://web.dev/eventing-deepdive/"/>
    <updated>2021-09-10T00:00:00Z</updated>
    <id>https://web.dev/eventing-deepdive/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;eventstoppropagation-and-eventpreventdefault&quot;&gt;Event.stopPropagation() and Event.preventDefault() &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/eventing-deepdive/#eventstoppropagation-and-eventpreventdefault&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;JavaScript event handling is often straightforward. This is especially true when dealing with a
simple (relatively flat) HTML structure. Things get a bit more involved though when events are
traveling (or propagating) through a hierarchy of elements. This is typically when developers reach
for &lt;code&gt;stopPropagation()&lt;/code&gt; and/or &lt;code&gt;preventDefault()&lt;/code&gt; to solve the problems they&#39;re experiencing. If
you&#39;ve ever thought to yourself &amp;quot;I&#39;ll just try &lt;code&gt;preventDefault()&lt;/code&gt; and if that doesn&#39;t work I&#39;ll try
&lt;code&gt;stopPropagation()&lt;/code&gt; and if that doesn&#39;t work, I&#39;ll try both,&amp;quot; then this article is for you! I will
explain exactly what each method does, when to use which one, and provide you with a variety of
working examples for you to explore. My goal is to end your confusion once and for all.&lt;/p&gt;
&lt;p&gt;Before we dive too deeply though, it&#39;s important to briefly touch on the two kinds of event handling
possible in JavaScript (in all modern browsers that is—Internet Explorer prior to version 9 did not
support event capturing at all).&lt;/p&gt;
&lt;h2 id=&quot;eventing-styles-capturing-and-bubbling&quot;&gt;Eventing styles (capturing and bubbling) &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/eventing-deepdive/#eventing-styles-capturing-and-bubbling&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All modern browsers support event capturing, but it is very rarely used by developers.
Interestingly, it was the only form of eventing that Netscape originally supported. Netscape&#39;s
biggest rival, Microsoft Internet Explorer, did not support event capturing at all, but rather, only
supported another style of eventing called event bubbling. When the W3C was formed, they found merit
in both styles of eventing and declared that browsers should support both, via a third parameter to
the &lt;code&gt;addEventListener&lt;/code&gt; method. Originally, that parameter was just a boolean, but all modern
browsers support an &lt;code&gt;options&lt;/code&gt; object as the third parameter, which you can use to specify (among
other things) if you want to use event capturing or not:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;someElement&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; myClickHandler&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;capture&lt;/span&gt;&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 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;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 the &lt;code&gt;options&lt;/code&gt; object is optional, as is its &lt;code&gt;capture&lt;/code&gt; property. If either is omitted, the
default value for &lt;code&gt;capture&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt;, meaning event bubbling will be used.&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; For more details about &lt;code&gt;addEventListener&lt;/code&gt;, including its legacy syntax, see &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener&quot;&gt;&lt;code&gt;EventTarget.addEventListener&lt;/code&gt;&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;event-capturing&quot;&gt;Event capturing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/eventing-deepdive/#event-capturing&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;What does it mean if your event handler is &amp;quot;listening in the capturing phase?&amp;quot; To understand this,
we need to know how events originate and how they travel. The following is true of &lt;em&gt;all&lt;/em&gt; events,
even if you, as the developer, don&#39;t leverage it, care about it, or think about it.&lt;/p&gt;
&lt;p&gt;All events begin at the window and first go through the capturing phase. This means that when an
event is dispatched, it starts the window and travels &amp;quot;downwards&amp;quot; towards its target element
&lt;em&gt;first&lt;/em&gt;. This happens even if you are only listening in the bubbling phase. Consider the following
example markup and JavaScript:&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;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;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;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;A&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;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;B&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;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;C&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 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;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;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;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;html&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;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;&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;C&#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 function&quot;&gt;addEventListener&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;click&#39;&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 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;    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;#C was clicked&#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;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 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;When a user clicks on element &lt;code&gt;#C&lt;/code&gt;, an event, originating at the &lt;code&gt;window&lt;/code&gt;, is dispatched. This event
will then propagate through its descendants as follows:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;window&lt;/code&gt; =&amp;gt; &lt;code&gt;document&lt;/code&gt; =&amp;gt; &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; =&amp;gt; &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; =&amp;gt; and so on, until it reaches the target.&lt;/p&gt;
&lt;p&gt;It does not matter if nothing is listening for a click event at the &lt;code&gt;window&lt;/code&gt; or &lt;code&gt;document&lt;/code&gt; or
&lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element or &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element (or any other element on the way to its target). An event still
originates at the &lt;code&gt;window&lt;/code&gt; and begins its journey as just described.&lt;/p&gt;
&lt;p&gt;In our example, the click event will then &lt;em&gt;propagate&lt;/em&gt; (this is an important word as it will tie
directly into how the &lt;code&gt;stopPropagation()&lt;/code&gt; method works and will be explained later in this document)
&lt;em&gt;from the &lt;code&gt;window&lt;/code&gt;&lt;/em&gt; to its target element (in this case, &lt;code&gt;#C&lt;/code&gt;) by way of every element between the
&lt;code&gt;window&lt;/code&gt; and &lt;code&gt;#C&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This means that the click event will begin at &lt;code&gt;window&lt;/code&gt; and the browser will ask the following
questions:&lt;/p&gt;
&lt;p&gt;&amp;quot;Is anything listening for a click event on the &lt;code&gt;window&lt;/code&gt; in the capturing phase?&amp;quot; If so, the
appropriate event handlers will fire. In our example, nothing is, so no handlers will fire.&lt;/p&gt;
&lt;p&gt;Next, the event will &lt;em&gt;propagate&lt;/em&gt; to the &lt;code&gt;document&lt;/code&gt; and the browser will ask: &amp;quot;Is anything listening
for a click event on the &lt;code&gt;document&lt;/code&gt; in the capturing phase?&amp;quot; If so, the appropriate event handlers
will fire.&lt;/p&gt;
&lt;p&gt;Next, the event will &lt;em&gt;propagate&lt;/em&gt; to the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element and the browser will ask: &amp;quot;Is anything
listening for a click on the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element in the capturing phase?&amp;quot; If so, the appropriate event
handlers will fire.&lt;/p&gt;
&lt;p&gt;Next, the event will &lt;em&gt;propagate&lt;/em&gt; to the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element and the browser will ask: &amp;quot;Is anything
listening for a click event on the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element in the capturing phase?&amp;quot; If so, the appropriate
event handlers will fire.&lt;/p&gt;
&lt;p&gt;Next, the event will &lt;em&gt;propagate&lt;/em&gt; to the &lt;code&gt;#A&lt;/code&gt; element. Again, the browser will ask: &amp;quot;Is anything
listening for a click event on &lt;code&gt;#A&lt;/code&gt; in the capturing phase and if so, the appropriate event handlers
will fire.&lt;/p&gt;
&lt;p&gt;Next, the event will &lt;em&gt;propagate&lt;/em&gt; to the &lt;code&gt;#B&lt;/code&gt; element (and the same question will be asked).&lt;/p&gt;
&lt;p&gt;Finally, the event will reach its target and the browser will ask: &amp;quot;Is anything listening for a
click event on the &lt;code&gt;#C&lt;/code&gt; element in the capturing phase?&amp;quot; The answer this time is &amp;quot;yes!&amp;quot; This brief
period of time when the event is &lt;em&gt;at&lt;/em&gt; the target, is known as the &amp;quot;target phase.&amp;quot; At this point, the
event handler will fire, the browser will console.log &amp;quot;#C was clicked&amp;quot; and then we&#39;re done, right?
&lt;em&gt;Wrong!&lt;/em&gt; We&#39;re not done at all. The process continues, but now it changes to the bubbling phase.&lt;/p&gt;
&lt;h3 id=&quot;event-bubbling&quot;&gt;Event bubbling &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/eventing-deepdive/#event-bubbling&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The browser will ask:&lt;/p&gt;
&lt;p&gt;&amp;quot;Is anything listening for a click event on &lt;code&gt;#C&lt;/code&gt; in the bubbling phase?&amp;quot; Pay close attention here.
It is completely possible to listen for clicks (or any event type) in &lt;em&gt;both&lt;/em&gt; the capturing &lt;em&gt;and&lt;/em&gt;
bubbling phases. And if you had wired up event handlers in both phases (e.g. by calling
&lt;code&gt;.addEventListener()&lt;/code&gt; twice, once with &lt;code&gt;capture = true&lt;/code&gt; and once with &lt;code&gt;capture = false&lt;/code&gt;), then yes,
both event handlers would absolutely fire for the same element. But it&#39;s also important to note that
they fire in different phases (one in the capturing phase and one in the bubbling phase).&lt;/p&gt;
&lt;p&gt;Next, the event will &lt;em&gt;propagate&lt;/em&gt; (more commonly stated as &amp;quot;bubble&amp;quot; because it seems as though the
event is traveling &amp;quot;up&amp;quot; the DOM tree) to its parent element, &lt;code&gt;#B&lt;/code&gt;, and the browser will ask: &amp;quot;Is
anything listening for click events on &lt;code&gt;#B&lt;/code&gt; in the bubbling phase?&amp;quot; In our example, nothing is, so
no handlers will fire.&lt;/p&gt;
&lt;p&gt;Next, the event will bubble to &lt;code&gt;#A&lt;/code&gt; and the browser will ask: &amp;quot;Is anything listening for click
events on &lt;code&gt;#A&lt;/code&gt; in the bubbling phase?&amp;quot;&lt;/p&gt;
&lt;p&gt;Next, the event will bubble to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;: &amp;quot;Is anything listening for click events on the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;
element in the bubbling phase?&amp;quot;&lt;/p&gt;
&lt;p&gt;Next, the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element: &amp;quot;Is anything listening for click events on the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element in the
bubbling phase?&lt;/p&gt;
&lt;p&gt;Next, the &lt;code&gt;document&lt;/code&gt;: &amp;quot;Is anything listening for click events on the &lt;code&gt;document&lt;/code&gt; in the bubbling
phase?&amp;quot;&lt;/p&gt;
&lt;p&gt;Finally, the &lt;code&gt;window&lt;/code&gt;: &amp;quot;Is anything listening for click events on the window in the bubbling phase?&amp;quot;&lt;/p&gt;
&lt;p&gt;Phew! That was a long journey, and our event is probably very tired by now, but believe it or not,
that is the journey every event goes through! Most of the time, this is never noticed because
developers are typically only interested in one event phase or the other (and it is usually the
bubbling phase).&lt;/p&gt;
&lt;p&gt;It&#39;s worth spending some time playing around with event capturing and event bubbling and logging
some notes to the console as handlers fire. It&#39;s very insightful to see the path that an event
takes. Here is an example that listens to every element in both phases.&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;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;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;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;A&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;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;B&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;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;C&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 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;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;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;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;html&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;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;&lt;span class=&quot;token function&quot;&gt;addEventListener&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;click&#39;&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 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;    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;click on document in capturing phase&#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;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 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;// document.documentElement == &amp;lt;html&gt;&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;documentElement&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;br /&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;br /&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;    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;click on &amp;lt;html&gt; in capturing phase&#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;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 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;addEventListener&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;click&#39;&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 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;    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;click on &amp;lt;body&gt; in capturing phase&#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;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 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;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;A&#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 function&quot;&gt;addEventListener&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;click&#39;&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 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;    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;click on #A in capturing phase&#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;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 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;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;B&#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 function&quot;&gt;addEventListener&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;click&#39;&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 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;    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;click on #B in capturing phase&#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;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 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;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;C&#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 function&quot;&gt;addEventListener&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;click&#39;&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 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;    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;click on #C in capturing phase&#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;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 punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;document&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;br /&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;br /&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;    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;click on document in bubbling phase&#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;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 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;// document.documentElement == &amp;lt;html&gt;&lt;/span&gt;&lt;br /&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;documentElement&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;br /&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;br /&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;    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;click on &amp;lt;html&gt; in bubbling phase&#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;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 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;addEventListener&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;click&#39;&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 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;    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;click on &amp;lt;body&gt; in bubbling phase&#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;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 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;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;A&#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 function&quot;&gt;addEventListener&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;click&#39;&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 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;    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;click on #A in bubbling phase&#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;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 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;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;B&#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 function&quot;&gt;addEventListener&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;click&#39;&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 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;    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;click on #B in bubbling phase&#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;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 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;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;C&#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 function&quot;&gt;addEventListener&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;click&#39;&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 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;    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;click on #C in bubbling phase&#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;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 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 console output will depend on which element you click. If you were to click on the &amp;quot;deepest&amp;quot;
element in the DOM tree (the &lt;code&gt;#C&lt;/code&gt; element), you will see every single one of these event handlers
fire. With a bit of CSS styling to make it more obvious which element is which, here is the console
output &lt;code&gt;#C&lt;/code&gt; element (with a screenshot as well):&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on document in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on &amp;lt;html&gt; in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on &amp;lt;body&gt; in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #A in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #B in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #C in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #C in bubbling phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #B in bubbling phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #A in bubbling phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on &amp;lt;body&gt; in bubbling phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on &amp;lt;html&gt; in bubbling phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on document in bubbling phase&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You can interactively play with this in the live demo below. Click on the &lt;code&gt;#C&lt;/code&gt; element and observe the console output.&lt;/p&gt;
&lt;div style=&quot;height: 680px; width: 100%;&quot;&gt;
  &lt;iframe src=&quot;https://silicon-brawny-cardinal.glitch.me/event-capturing-and-bubbling.html&quot; loading=&quot;lazy&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;eventstoppropagation&quot;&gt;&lt;code&gt;event.stopPropagation()&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/eventing-deepdive/#eventstoppropagation&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With an understanding of where events originate and how they travel (i.e. propagate) through the DOM
in both the capturing phase and the bubbling phase, we can now turn our attention to
&lt;code&gt;event.stopPropagation()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;stopPropagation()&lt;/code&gt; method can be called on (most) native DOM events. I say &amp;quot;most&amp;quot; because there
are a few on which calling this method won&#39;t do anything (because the event doesn&#39;t propagate to
begin with). Events like &lt;code&gt;focus&lt;/code&gt;, &lt;code&gt;blur&lt;/code&gt;, &lt;code&gt;load&lt;/code&gt;, &lt;code&gt;scroll&lt;/code&gt;, and a few others fall into this
category. You can call &lt;code&gt;stopPropagation()&lt;/code&gt; but nothing interesting will happen, since these events
don&#39;t propagate.&lt;/p&gt;
&lt;h2 id=&quot;but-what-does-stoppropagation-do&quot;&gt;But what does &lt;code&gt;stopPropagation&lt;/code&gt; do? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/eventing-deepdive/#but-what-does-stoppropagation-do&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It does, pretty much, just what it says. When you call it, the event will, from that point, cease
propagating to any elements it would otherwise travel to. This is true of &lt;em&gt;both&lt;/em&gt; directions
(capturing and bubbling). So if you call &lt;code&gt;stopPropagation()&lt;/code&gt; anywhere in the capturing phase, the
event will never make it to the target phase or bubbling phase. If you call it in the bubbling
phase, it will have already gone through the capturing phase, but it will cease &amp;quot;bubbling up&amp;quot; from
the point at which you called it.&lt;/p&gt;
&lt;p&gt;Returning to our same example markup, what do you think would happen, if we called
&lt;code&gt;stopPropagation()&lt;/code&gt; in the capturing phase at the &lt;code&gt;#B&lt;/code&gt; element?&lt;/p&gt;
&lt;p&gt;It would result in the following output:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on document in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on &amp;lt;html&gt; in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on &amp;lt;body&gt; in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #A in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #B in capturing phase&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You can interactively play with this in the live demo below. Click on the &lt;code&gt;#C&lt;/code&gt; element in the live demo and observe the console output.&lt;/p&gt;
&lt;div style=&quot;height: 680px; width: 100%;&quot;&gt;
  &lt;iframe src=&quot;https://silicon-brawny-cardinal.glitch.me/stop-propagation-capturing-B.html&quot; loading=&quot;lazy&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;How about stopping propagation at &lt;code&gt;#A&lt;/code&gt; in the bubbling phase? That would result in the following
output:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on document in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on &amp;lt;html&gt; in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on &amp;lt;body&gt; in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #A in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #B in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #C in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #C in bubbling phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #B in bubbling phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #A in bubbling phase&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You can interactively play with this in the live demo below. Click on the &lt;code&gt;#C&lt;/code&gt; element in the live demo and observe the console output.&lt;/p&gt;
&lt;div style=&quot;height: 680px; width: 100%;&quot;&gt;
  &lt;iframe src=&quot;https://silicon-brawny-cardinal.glitch.me/stop-propagation-bubbling-A.html&quot; loading=&quot;lazy&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;One more, just for fun. What happens if we call &lt;code&gt;stopPropagation()&lt;/code&gt; in the &lt;em&gt;target phase&lt;/em&gt; for &lt;code&gt;#C&lt;/code&gt;?
Recall that the &amp;quot;target phase&amp;quot; is the name given to the period of time when the event is &lt;em&gt;at&lt;/em&gt; its
target. It would result in the following output:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on document in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on &amp;lt;html&gt; in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on &amp;lt;body&gt; in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #A in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #B in capturing phase&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click on #C in capturing phase&quot;&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; It would be technically more accurate if I had logged &amp;quot;click on #C in target phase&amp;quot;. However, I chose to use the term &amp;quot;capturing&amp;quot; and &amp;quot;bubbling&amp;quot; for both of &lt;code&gt;#C&lt;/code&gt;&#39;s event handlers to make it clear that handlers can be executed in both phases of the event&#39;s lifecycle. Just know that technically, the time when the event is at &lt;code&gt;#C&lt;/code&gt; is officially known as the &amp;quot;target phase&amp;quot;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Note that the event handler for &lt;code&gt;#C&lt;/code&gt; in which we log &amp;quot;click on #C in the capturing phase&amp;quot; &lt;em&gt;still&lt;/em&gt;
executes, but the one in which we log &amp;quot;click on #C in the bubbling phase&amp;quot; does not. This should make
perfect sense. We called &lt;code&gt;stopPropagation()&lt;/code&gt; &lt;em&gt;from&lt;/em&gt; the former, so that is the point at which the
event&#39;s propagation will cease.&lt;/p&gt;
&lt;p&gt;You can interactively play with this in the live demo below. Click on the &lt;code&gt;#C&lt;/code&gt; element in the live demo and observe the console output.&lt;/p&gt;
&lt;div style=&quot;height: 680px; width: 100%;&quot;&gt;
  &lt;iframe src=&quot;https://silicon-brawny-cardinal.glitch.me/stop-propagation-capturing-C.html&quot; loading=&quot;lazy&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;In any of these live demos, I encourage you to play around. Try clicking on the &lt;code&gt;#A&lt;/code&gt; element only or
the &lt;code&gt;body&lt;/code&gt; element only. Try to predict what will happen and then observe if you are correct. At
this point, you should be able to predict pretty accurately.&lt;/p&gt;
&lt;h2 id=&quot;eventstopimmediatepropagation&quot;&gt;&lt;code&gt;event.stopImmediatePropagation()&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/eventing-deepdive/#eventstopimmediatepropagation&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What is this strange, and not oft-used method? It&#39;s similar to &lt;code&gt;stopPropagation&lt;/code&gt;, but rather than
stopping an event from traveling to descendents (capturing) or ancestors (bubbling), this method
only applies when you have more than one event handler wired up to a single element. Since
&lt;code&gt;addEventListener()&lt;/code&gt; supports a multicast style of eventing, it&#39;s completely possible to wire up an
event handler to a single element more than once. When this happens, (in most browsers), event
handlers are executed in the order they were wired up. Calling &lt;code&gt;stopImmediatePropagation()&lt;/code&gt; prevents
any subsequent handlers from firing. Consider the following 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;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;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;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;A&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;I am the #A element&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;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;html&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;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;&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;A&#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 function&quot;&gt;addEventListener&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;click&#39;&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 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;    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;When #A is clicked, I shall run first!&#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;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 punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&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;A&#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 function&quot;&gt;addEventListener&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;click&#39;&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 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;    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;When #A is clicked, I shall run second!&#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;    e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stopImmediatePropagation&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;br /&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 punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&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;A&#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 function&quot;&gt;addEventListener&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;click&#39;&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 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;    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;When #A is clicked, I would have run third, if not for stopImmediatePropagation&#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;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 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 example will result in the following console output:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;When #A is clicked, I shall run first!&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string&quot;&gt;&quot;When #A is clicked, I shall run second!&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Note that the third event handler never runs due to the fact that the second event handler calls
&lt;code&gt;e.stopImmediatePropagation()&lt;/code&gt;. If we had instead called &lt;code&gt;e.stopPropagation()&lt;/code&gt;, the third handler
would still run.&lt;/p&gt;
&lt;div style=&quot;height: 680px; width: 100%;&quot;&gt;
  &lt;iframe src=&quot;https://silicon-brawny-cardinal.glitch.me/stop-immediate-propagation.html&quot; loading=&quot;lazy&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;eventpreventdefault&quot;&gt;&lt;code&gt;event.preventDefault()&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/eventing-deepdive/#eventpreventdefault&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If &lt;code&gt;stopPropagation()&lt;/code&gt; prevents an event from traveling &amp;quot;downwards&amp;quot; (capturing) or &amp;quot;upwards&amp;quot;
(bubbling), what then, does &lt;code&gt;preventDefault()&lt;/code&gt; do? It sounds like it does something similar. Does
it?&lt;/p&gt;
&lt;p&gt;Not really. While the two are often confused, they actually don&#39;t have much to do with each other.
When you see &lt;code&gt;preventDefault()&lt;/code&gt;, in your head, add the word &amp;quot;action.&amp;quot; Think &amp;quot;prevent the default
action.&amp;quot;&lt;/p&gt;
&lt;p&gt;And what is the default action you may ask? Unfortunately, the answer to that isn&#39;t quite as clear
because it&#39;s highly dependent on the element + event combination in question. And to make matters
even more confusing, sometimes there is no default action at all!&lt;/p&gt;
&lt;p&gt;Let&#39;s begin with a very simple example to understand. What do you expect to happen when you click a
link on a web page? Obviously, you expect the browser to navigate to the URL specified by that link.
In this case, the element is an anchor tag and the event is a click event. That combination (&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; +
&lt;code&gt;click&lt;/code&gt;) has a &amp;quot;default action&amp;quot; of navigating to the link&#39;s href. What if you wanted to &lt;em&gt;prevent&lt;/em&gt;
the browser from performing that default action? That is, suppose you want to prevent the browser
from navigating to the URL specified by the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; element&#39;s &lt;code&gt;href&lt;/code&gt; attribute? This is what
&lt;code&gt;preventDefault()&lt;/code&gt; will do for you. Consider this 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;a&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;avett&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;https://www.theavettbrothers.com/welcome&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;The Avett Brothers&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;a&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;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;&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;avett&#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 function&quot;&gt;addEventListener&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;click&#39;&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 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;    e&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;    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;Maybe we should just play some of their music right here instead?&#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;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 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;You can interactively play with this in the live demo below. Click the link &lt;em&gt;The Avett Brothers&lt;/em&gt;
and observe the console output (and the fact that you are not redirected to the Avett Brothers website).&lt;/p&gt;
&lt;div style=&quot;height: 620px; width: 100%;&quot;&gt;
  &lt;iframe src=&quot;https://silicon-brawny-cardinal.glitch.me/prevent-default.html&quot; loading=&quot;lazy&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Normally, clicking the link labelled The Avett Brothers would result in browsing to
&lt;code&gt;www.theavettbrothers.com&lt;/code&gt;. In this case though, we&#39;ve wired up a click event handler to the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;
element and specified that the default action should be prevented. Thus, when a user clicks this
link, they won&#39;t be navigated anywhere, and instead the console will simply log &amp;quot;Maybe we should
just play some of their music right here instead?&amp;quot;&lt;/p&gt;
&lt;p&gt;What other element/event combinations allow you to prevent the default action? I cannot possibly
list them all, and sometimes you have to just experiment to see. But briefly, here are a few:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; element + &amp;quot;submit&amp;quot; event: &lt;code&gt;preventDefault()&lt;/code&gt; for this combination will prevent a form
from submitting. This is useful if you want to perform validation and should something fail, you
can conditionally call preventDefault to stop the form from submitting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; element + &amp;quot;click&amp;quot; event: &lt;code&gt;preventDefault()&lt;/code&gt; for this combination prevents the browser from
navigating to the URL specified in the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; element&#39;s href attribute.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;document&lt;/code&gt; + &amp;quot;mousewheel&amp;quot; event: &lt;code&gt;preventDefault()&lt;/code&gt; for this combination prevents page scrolling
with the mousewheel (scrolling with keyboard would still work though). &lt;br /&gt; &lt;small&gt;↜ This requires
calling &lt;code&gt;addEventListener()&lt;/code&gt; with &lt;code&gt;{ passive: false }&lt;/code&gt;&lt;/small&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;document&lt;/code&gt; + &amp;quot;keydown&amp;quot; event: &lt;code&gt;preventDefault()&lt;/code&gt; for this combination is lethal. It renders the
page largely useless, preventing keyboard scrolling, tabbing, and keyboard highlighting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;document&lt;/code&gt; + &amp;quot;mousedown&amp;quot; event: &lt;code&gt;preventDefault()&lt;/code&gt; for this combination will prevent text
highlighting with the mouse and any other &amp;quot;default&amp;quot; action that one would invoke with a mouse
down.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; element + &amp;quot;keypress&amp;quot; event: &lt;code&gt;preventDefault()&lt;/code&gt; for this combination will prevent
characters typed by the user from reaching the input element (but don&#39;t do this; there is
rarely, if ever, a valid reason for it).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;document&lt;/code&gt; + &amp;quot;contextmenu&amp;quot; event: &lt;code&gt;preventDefault()&lt;/code&gt; for this combination prevents the native
browser context menu from appearing when a user right-clicks or long-presses (or any other way in
which a context menu might appear).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is not an exhaustive list by any means, but hopefully it gives you a good idea of how
&lt;code&gt;preventDefault()&lt;/code&gt; can be used.&lt;/p&gt;
&lt;h2 id=&quot;a-fun-practical-joke&quot;&gt;A fun practical joke? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/eventing-deepdive/#a-fun-practical-joke&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;What happens if you &lt;code&gt;stopPropagation()&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; &lt;code&gt;preventDefault()&lt;/code&gt; in the capturing phase, starting at
the document? Hilarity ensues! The following code snippet will render any web page just about
completely useless:&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;preventEverything&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;  e&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;  e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stopPropagation&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;  e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stopImmediatePropagation&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;document&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; preventEverything&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;document&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;keydown&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; preventEverything&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;document&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;mousedown&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; preventEverything&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;document&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;contextmenu&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; preventEverything&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;document&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;mousewheel&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; preventEverything&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;capture&lt;/span&gt;&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;span class=&quot;token literal-property property&quot;&gt;passive&lt;/span&gt;&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;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;I don&#39;t really know why you&#39;d ever want to do this (except maybe to play a joke on someone), but it
is useful to think about what&#39;s happening here, and realize why it creates the situation it does.&lt;/p&gt;
&lt;p&gt;All events originate at &lt;code&gt;window&lt;/code&gt;, so in this snippet, we&#39;re stopping, dead in their tracks, all
&lt;code&gt;click&lt;/code&gt;, &lt;code&gt;keydown&lt;/code&gt;, &lt;code&gt;mousedown&lt;/code&gt;, &lt;code&gt;contextmenu&lt;/code&gt;, and &lt;code&gt;mousewheel&lt;/code&gt; events from ever getting to any
elements that might be listening for them. We also call &lt;code&gt;stopImmediatePropagation&lt;/code&gt; so that any
handlers wired up to the document after this one, will be thwarted as well.&lt;/p&gt;
&lt;p&gt;Note that &lt;code&gt;stopPropagation()&lt;/code&gt; and &lt;code&gt;stopImmediatePropagation()&lt;/code&gt; aren&#39;t (at least not mostly) what
render the page useless. They simply prevent events from getting where they would otherwise go.&lt;/p&gt;
&lt;p&gt;But we also call &lt;code&gt;preventDefault()&lt;/code&gt;, which you&#39;ll recall prevents the default &lt;em&gt;action&lt;/em&gt;. So any
default actions (like mousewheel scroll, keyboard scroll or highlight or tabbing, link clicking,
context menu display, etc.) are all prevented, thus leaving the page in a fairly useless state.&lt;/p&gt;
&lt;h2 id=&quot;live-demos&quot;&gt;Live demos &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/eventing-deepdive/#live-demos&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To explore all the examples from this article again in one place, check out the embedded demo
below.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 420px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/silicon-brawny-cardinal?attributionHidden=true&amp;sidebarCollapsed=true&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;silicon-brawny-cardinal on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/eventing-deepdive/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hero image by &lt;a href=&quot;https://unsplash.com/@pastorthomasbwilson&quot; rel=&quot;noopener&quot;&gt;Tom Wilson&lt;/a&gt; on
&lt;a href=&quot;https://unsplash.com/photos/Em2hPK55o8g&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Stephen Stchur</name>
    </author><author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>User preference media features client hints headers</title>
    <link href="https://web.dev/user-preference-media-features-headers/"/>
    <updated>2021-08-02T00:00:00Z</updated>
    <id>https://web.dev/user-preference-media-features-headers/</id>
    <content type="html" mode="escaped">&lt;p&gt;CSS media queries, and specifically
&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#mf-user-preferences&quot; rel=&quot;noopener&quot;&gt;user preference media features&lt;/a&gt; like
&lt;a href=&quot;https://web.dev/prefers-color-scheme/&quot;&gt;&lt;code&gt;prefers-color-scheme&lt;/code&gt;&lt;/a&gt; or
&lt;a href=&quot;https://web.dev/prefers-reduced-motion/&quot;&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt;&lt;/a&gt;, have a potentially
&lt;a href=&quot;https://webkit.org/blog/8892/dark-mode-in-web-inspector/#:~:text=Implementing%20Dark%20Mode%20took%20over%201%2C000%20lines%20of%20CSS.&quot; rel=&quot;noopener&quot;&gt;significant impact&lt;/a&gt;
on the amount of CSS that needs to be delivered by a page, and on the experience the user is going
to have when the page loads.&lt;/p&gt;
&lt;p&gt;Focusing on &lt;code&gt;prefers-color-scheme&lt;/code&gt;—but highlighting that the reasoning applies to other user
preference media features as well—it is a best practice to not load CSS for the particular
non-matching color scheme in the critical rendering path, and instead to initially only load the
currently relevant CSS. One way of doing so is
&lt;a href=&quot;https://web.dev/prefers-color-scheme/#loading-strategy&quot;&gt;via &lt;code&gt;&amp;lt;link media&amp;gt;&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, high-traffic sites like &lt;a href=&quot;https://www.google.com/&quot; rel=&quot;noopener&quot;&gt;Google Search&lt;/a&gt; that wish to honor user
preference media features like &lt;code&gt;prefers-color-scheme&lt;/code&gt; and that inline CSS for performance reasons,
need to know about the preferred color scheme (or other user preference media features respectively)
ideally at request time, so that the initial HTML payload already has the right CSS inlined.&lt;/p&gt;
&lt;p&gt;Additionally, and specifically for &lt;code&gt;prefers-color-scheme&lt;/code&gt;, sites by all means want to avoid a
&lt;a href=&quot;https://css-tricks.com/flash-of-inaccurate-color-theme-fart/&quot; rel=&quot;noopener&quot;&gt;flash of inaccurate color theme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Sec-CH-Prefers-Color-Scheme&lt;/code&gt; and the &lt;code&gt;Sec-CH-Prefers-Reduced-Motion&lt;/code&gt; client hint headers are the first of a
&lt;a href=&quot;https://wicg.github.io/user-preference-media-features-headers/&quot; rel=&quot;noopener&quot;&gt;series of user preference media features client hints headers&lt;/a&gt;
that aims to solve this issue.&lt;/p&gt;
&lt;h3 id=&quot;background-on-client-hints&quot;&gt;Background on client hints &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-preference-media-features-headers/#background-on-client-hints&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8942&quot; rel=&quot;noopener&quot;&gt;HTTP Client Hints&lt;/a&gt; defines an &lt;code&gt;Accept-CH&lt;/code&gt; response
header that servers can use to advertise their use of request headers for proactive content
negotiation, colloquially referred to as client hints. The
&lt;a href=&quot;https://wicg.github.io/user-preference-media-features-headers/&quot; rel=&quot;noopener&quot;&gt;User Preference Media Features Client Hints Headers&lt;/a&gt;
proposal defines a set of client hints aimed at conveying user preference media features. These
client hints are named after the corresponding user preference media feature that they report on.
For example, the currently preferred color scheme as per &lt;code&gt;prefers-color-scheme&lt;/code&gt; is reported via the
aptly named &lt;code&gt;Sec-CH-Prefers-Color-Scheme&lt;/code&gt; client hint.&lt;/p&gt;
&lt;h3 id=&quot;background-on-critical-client-hints&quot;&gt;Background on critical client hints &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-preference-media-features-headers/#background-on-critical-client-hints&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The client hints proposed in
&lt;a href=&quot;https://wicg.github.io/user-preference-media-features-headers/&quot; rel=&quot;noopener&quot;&gt;User Preference Media Features Client Hints Headers&lt;/a&gt;
will presumably most commonly be used as
&lt;a href=&quot;https://tools.ietf.org/html/draft-davidben-http-client-hint-reliability-02&quot; rel=&quot;noopener&quot;&gt;Critical Client Hints&lt;/a&gt;.
Critical Client Hints are Client Hints which meaningfully change the resulting resource. Such a
resource should be fetched consistently across page loads (including the &lt;em&gt;initial&lt;/em&gt; page load) to
avoid jarring user-visible switches.&lt;/p&gt;
&lt;h3 id=&quot;client-hint-syntax&quot;&gt;Client hint syntax &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-preference-media-features-headers/#client-hint-syntax&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;User preference media features consist of a name (like &lt;code&gt;prefers-reduced-motion&lt;/code&gt;) and allowed values
(like &lt;code&gt;no-preference&lt;/code&gt; or &lt;code&gt;reduce&lt;/code&gt;). Each client hint header field is represented as
&lt;a href=&quot;https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-15&quot; rel=&quot;noopener&quot;&gt;Structured Headers for HTTP&lt;/a&gt;
object containing an
&lt;a href=&quot;https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-15#section-3.3&quot; rel=&quot;noopener&quot;&gt;item&lt;/a&gt; whose value
is a &lt;a href=&quot;https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-15#section-3.3.3&quot; rel=&quot;noopener&quot;&gt;string&lt;/a&gt;. For
example, to convey that the user prefers a dark theme and reduced motion, the client hints look like
in the example below.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;GET / HTTP/2&lt;br /&gt;Host: example.com&lt;br /&gt;Sec-CH-Prefers-Color-Scheme: &lt;span class=&quot;token string&quot;&gt;&quot;dark&quot;&lt;/span&gt;&lt;br /&gt;Sec-CH-Prefers-Reduced-Motion: &lt;span class=&quot;token string&quot;&gt;&quot;reduce&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The CSS equivalent of the information conveyed in the above client hints is
&lt;code&gt;@media (prefers-color-scheme: dark) {}&lt;/code&gt; and &lt;code&gt;@media (prefers-reduced-motion: reduce) {}&lt;/code&gt;
respectively.&lt;/p&gt;
&lt;h2 id=&quot;complete-list-of-the-client-hints&quot;&gt;Complete list of the client hints &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-preference-media-features-headers/#complete-list-of-the-client-hints&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; While the &lt;a href=&quot;https://wicg.github.io/user-preference-media-features-headers/&quot;&gt;User Preference Media Features Client Hints Headers&lt;/a&gt; proposal defines a set of client hints, Chromium at the time of writing only supports &lt;code&gt;Sec-CH-Prefers-Color-Scheme&lt;/code&gt; and &lt;code&gt;Sec-CH-Prefers-Reduced-Motion&lt;/code&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;The list of the client hints is modeled after the
&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#mf-user-preferences&quot; rel=&quot;noopener&quot;&gt;user preference media features&lt;/a&gt; in
&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-scheme&quot; rel=&quot;noopener&quot;&gt;Media Queries Level 5&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Client Hint&lt;/th&gt;
&lt;th&gt;Allowed Values&lt;/th&gt;
&lt;th&gt;Corresponding User Preference Media Feature&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sec-CH-Prefers-Reduced-Motion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-prefers-reduced-motion-no-preference&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;no-preference&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-prefers-reduced-motion-reduce&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;reduce&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sec-CH-Prefers-Reduced-Transparency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-prefers-reduced-transparency-no-preference&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;no-preference&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-prefers-reduced-transparency-reduce&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;reduce&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-transparency&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;prefers-reduced-transparency&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sec-CH-Prefers-Contrast&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-prefers-contrast-no-preference&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;no-preference&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-prefers-contrast-less&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;less&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-prefers-contrast-more&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;more&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-prefers-contrast-custom&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;custom&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#prefers-contrast&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;prefers-contrast&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sec-CH-Forced-Colors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-forced-colors-active&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;active&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-forced-colors-none&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;none&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#forced-colors&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;forced-colors&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sec-CH-Prefers-Color-Scheme&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-prefers-color-scheme-light&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;light&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-prefers-color-scheme-dark&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;dark&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;prefers-color-scheme&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sec-CH-Prefers-Reduced-Data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-prefers-reduced-data-no-preference&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;no-preference&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#valdef-media-prefers-reduced-data-reduce&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;reduce&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-data&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;prefers-reduced-data&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;h2 id=&quot;browser-support&quot;&gt;Browser support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-preference-media-features-headers/#browser-support&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Sec-CH-Prefers-Color-Scheme&lt;/code&gt; client hint header is supported on Chromium 93.
The &lt;code&gt;Sec-CH-Prefers-Reduced-Motion&lt;/code&gt; client hint header is supported on Chromium 108.
Other vendors&#39; feedback, namely &lt;a href=&quot;https://lists.webkit.org/pipermail/webkit-dev/2021-May/031856.html&quot; rel=&quot;noopener&quot;&gt;WebKit&#39;s&lt;/a&gt;
and &lt;a href=&quot;https://github.com/mozilla/standards-positions/issues/526&quot; rel=&quot;noopener&quot;&gt;Mozilla&#39;s&lt;/a&gt;, is pending.&lt;/p&gt;
&lt;h2 id=&quot;demo-of-sec-ch-prefers-color-scheme&quot;&gt;Demo of &lt;code&gt;Sec-CH-Prefers-Color-Scheme&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-preference-media-features-headers/#demo-of-sec-ch-prefers-color-scheme&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Try the &lt;a href=&quot;https://sec-ch-prefers-color-scheme.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt; in Chromium 93 and notice how
the inlined CSS changes according to the user&#39;s preferred color scheme.&lt;/p&gt;
&lt;img alt=&quot;Sec-CH-Prefers-Color-Scheme: dark&quot; decoding=&quot;async&quot; height=&quot;541&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/pKAKyrN18CjhAYUNpJyk.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;img alt=&quot;Sec-CH-Prefers-Color-Scheme: light&quot; decoding=&quot;async&quot; height=&quot;541&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6Xujcgyveo0QY0E3LQOF.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;demo-of-sec-ch-prefers-reduced-motion&quot;&gt;Demo of &lt;code&gt;Sec-CH-Prefers-Reduced-Motion&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-preference-media-features-headers/#demo-of-sec-ch-prefers-reduced-motion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Try the &lt;a href=&quot;https://sec-ch-prefers-reduced-motion.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt; in Chromium 108 and notice how
the inlined CSS changes according to the user&#39;s motion preferences.&lt;/p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How it works &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-preference-media-features-headers/#how-it-works&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;The client makes an initial request to the server.&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;GET / HTTP/2&lt;br /&gt;Host: example.com&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;The server responds, telling the client via &lt;code&gt;Accept-CH&lt;/code&gt; that it accepts the
&lt;code&gt;Sec-CH-Prefers-Color-Scheme&lt;/code&gt; and the &lt;code&gt;Sec-CH-Prefers-Contrast&lt;/code&gt; Client Hints, out of which as per
&lt;code&gt;Critical-CH&lt;/code&gt; it considers &lt;code&gt;Sec-CH-Prefers-Color-Scheme&lt;/code&gt; a Critical Client Hint that it also
varies the response on as conveyed by &lt;code&gt;Vary&lt;/code&gt;.&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;HTTP/2 &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt; OK&lt;br /&gt;Content-Type: text/html&lt;br /&gt;Accept-CH: Sec-CH-Prefers-Color-Scheme, Sec-CH-Prefers-Contrast&lt;br /&gt;Vary: Sec-CH-Prefers-Color-Scheme&lt;br /&gt;Critical-CH: Sec-CH-Prefers-Color-Scheme&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;The client then retries the request, telling the server via &lt;code&gt;Sec-CH-Prefers-Color-Scheme&lt;/code&gt; that it
has a user preference for dark-schemed content.&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;GET / HTTP/2&lt;br /&gt;Host: example.com&lt;br /&gt;Sec-CH-Prefers-Color-Scheme: &lt;span class=&quot;token string&quot;&gt;&quot;dark&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;The server can then tailor the response to the client&#39;s preferences accordingly and, for example,
inline the CSS responsible for the dark theme into the response body.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;nodejs-example&quot;&gt;Node.js example &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-preference-media-features-headers/#nodejs-example&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Node.js example below, written for the popular Express.js framework, shows how
dealing with the &lt;code&gt;Sec-CH-Prefers-Color-Scheme&lt;/code&gt; client hint header could look like in practice.
This code is what actually powers the &lt;a href=&quot;https://web.dev/user-preference-media-features-headers/#demo-of-sec-ch-prefers-color-scheme&quot;&gt;demo&lt;/a&gt; above.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&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 parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;// Tell the client the server accepts the `Sec-CH-Prefers-Color-Scheme` client hint…&lt;/span&gt;&lt;br /&gt;  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Accept-CH&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Sec-CH-Prefers-Color-Scheme&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 comment&quot;&gt;// …and that the server&#39;s response will vary based on its value…&lt;/span&gt;&lt;br /&gt;  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Vary&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Sec-CH-Prefers-Color-Scheme&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 comment&quot;&gt;// …and that the server considers this client hint a _critical_ client hint.&lt;/span&gt;&lt;br /&gt;  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Critical-CH&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Sec-CH-Prefers-Color-Scheme&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 comment&quot;&gt;// Read the user&#39;s preferred color scheme from the headers…&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; prefersColorScheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sec-ch-prefers-color-scheme&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 comment&quot;&gt;// …and send the adequate HTML response with the right CSS inlined.&lt;/span&gt;&lt;br /&gt;  res&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 function&quot;&gt;getHTML&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prefersColorScheme&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;privacy-and-security-considerations&quot;&gt;Privacy and security considerations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-preference-media-features-headers/#privacy-and-security-considerations&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Chromium team designed and implemented User Preference Media Features Client Hints Headers
using the core principles defined in &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/lkgr/docs/security/permissions-for-powerful-web-platform-features.md&quot; rel=&quot;noopener&quot;&gt;Controlling Access to Powerful Web Platform
Features&lt;/a&gt;, including user control, transparency, and ergonomics.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8942#section-4&quot; rel=&quot;noopener&quot;&gt;Security Considerations&lt;/a&gt; of HTTP
Client Hints and the
&lt;a href=&quot;https://tools.ietf.org/html/draft-davidben-http-client-hint-reliability-02#section-5&quot; rel=&quot;noopener&quot;&gt;Security Considerations&lt;/a&gt;
of Client Hint Reliability likewise apply to this proposal.&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;References &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-preference-media-features-headers/#references&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/user-preference-media-features-headers/&quot; rel=&quot;noopener&quot;&gt;Spec draft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/WICG/user-preference-media-features-headers#readme&quot; rel=&quot;noopener&quot;&gt;Explainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chromestatus.com/feature/5642300464037888&quot; rel=&quot;noopener&quot;&gt;Sec-CH-Prefers-Color-Scheme - Chrome Status entry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://crbug.com/1207897&quot; rel=&quot;noopener&quot;&gt;Sec-CH-Prefers-Color-Scheme - Chromium bug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chromestatus.com/feature/5141804190531584&quot; rel=&quot;noopener&quot;&gt;Sec-CH-Prefers-Reduced-Motion - Chrome Status entry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://crbug.com/1361871&quot; rel=&quot;noopener&quot;&gt;Sec-CH-Prefers-Reduced-Motion - Chromium bug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lists.webkit.org/pipermail/webkit-dev/2021-May/031856.html&quot; rel=&quot;noopener&quot;&gt;WebKit thread&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mozilla/standards-positions/issues/526&quot; rel=&quot;noopener&quot;&gt;Mozilla Standards Position&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8942&quot; rel=&quot;noopener&quot;&gt;Client Hints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/draft-davidben-http-client-hint-reliability-02&quot; rel=&quot;noopener&quot;&gt;Client Hint Reliability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-scheme&quot; rel=&quot;noopener&quot;&gt;Media Queries Level 5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-19&quot; rel=&quot;noopener&quot;&gt;Structured Headers for HTTP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/user-preference-media-features-headers/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Many thanks for valuable feedback and advice from
&lt;a href=&quot;https://github.com/yoavweiss&quot; rel=&quot;noopener&quot;&gt;Yoav Weiss&lt;/a&gt;.
Hero image by &lt;a href=&quot;https://commons.wikimedia.org/wiki/User:Tdadamemd&quot; rel=&quot;noopener&quot;&gt;Tdadamemd&lt;/a&gt; on
&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Sun%26Moon_apparent_sizes_(min-max_halved).jpg&quot; rel=&quot;noopener&quot;&gt;Wikimedia Commons&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author><author>
      <name>François Beaufort</name>
    </author>
  </entry>
  
  <entry>
    <title>Excalidraw and Fugu: Improving Core User Journeys
</title>
    <link href="https://web.dev/excalidraw-and-fugu/"/>
    <updated>2021-05-18T00:00:00Z</updated>
    <id>https://web.dev/excalidraw-and-fugu/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This is a write-up of my Google I/O talk. If you prefer watching it, see the video below. &lt;/div&gt;&lt;/aside&gt;
&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;EK1AkxgQwro&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;h2 id=&quot;how-i-came-to-excalidraw&quot;&gt;How I came to Excalidraw &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#how-i-came-to-excalidraw&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I want to start with a story. On January 1st, 2020,
&lt;a href=&quot;https://twitter.com/vjeux&quot; rel=&quot;noopener&quot;&gt;Christopher Chedeau&lt;/a&gt;, a software engineer at Facebook,
&lt;a href=&quot;https://twitter.com/Vjeux/status/1212503324982792193&quot; rel=&quot;noopener&quot;&gt;tweeted&lt;/a&gt; about a small drawing app he had
started to work on. With this tool, you could draw boxes and arrows that feel cartoony and
hand-drawn. The next day, you could also draw ellipses and text, as well as select objects and move
them around. On January 3, the app had gotten its name, Excalidraw, and, like with every good side
project, buying the &lt;a href=&quot;https://excalidraw.com/&quot; rel=&quot;noopener&quot;&gt;domain name&lt;/a&gt; was one of Christopher&#39;s first acts. By
now, you could use colors and export the whole drawing as a PNG.&lt;/p&gt;
&lt;img alt=&quot;Screenshot of the Excalidraw prototype application showing that it supported rectangles, arrows, ellipses, and text.&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/VbicbA7xj5azVcDUBSKt.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;On January 15, Christopher put out a
&lt;a href=&quot;https://blog.vjeux.com/2020/uncategorized/reflections-on-excalidraw.html&quot; rel=&quot;noopener&quot;&gt;blog post&lt;/a&gt; that drew a
lot of attention on Twitter, including mine. The post started with some impressive stats:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;12K unique active users&lt;/li&gt;
&lt;li&gt;1.5K stars on GitHub&lt;/li&gt;
&lt;li&gt;26 contributors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a project that started a mere two weeks ago, that&#39;s not bad at all. But the thing that truly
spiked my interest was further down in the post. Christopher wrote that he tried something new this
time: &lt;em&gt;giving everyone who landed a pull request unconditional commit access.&lt;/em&gt; The same day of
reading the blog post, I had a &lt;a href=&quot;https://github.com/excalidraw/excalidraw/pull/388&quot; rel=&quot;noopener&quot;&gt;pull request&lt;/a&gt; up
that added File System Access API support to Excalidraw, fixing a
&lt;a href=&quot;https://github.com/excalidraw/excalidraw/issues/169&quot; rel=&quot;noopener&quot;&gt;feature request&lt;/a&gt; that someone had filed.&lt;/p&gt;
&lt;img alt=&quot;Screenshot of the tweet where I announce my PR.&quot; decoding=&quot;async&quot; height=&quot;424&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 550px) 550px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/9VJ9EqPzKdzUpxFeM5wH.png?auto=format&amp;w=1100 1100w&quot; width=&quot;550&quot; /&gt;
&lt;p&gt;My pull request was merged a day later and from thereon, I had full commit access. Needless to say,
I didn&#39;t abuse my power. And nor did anybody else from the 149 contributors so far.&lt;/p&gt;
&lt;p&gt;Today, &lt;a href=&quot;https://excalidraw.com/&quot; rel=&quot;noopener&quot;&gt;Excalidraw&lt;/a&gt; is a full-fledged installable progressive web app with
offline support, a stunning dark mode, and yes, the ability to open and save files thanks to the
File System Access API.&lt;/p&gt;
&lt;img alt=&quot;Screenshot of the Excalidraw PWA in today&amp;#x27;s state.&quot; decoding=&quot;async&quot; height=&quot;537&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/Wzz6UELRpcvkKZQtmVmc.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;lipis-on-why-he-dedicates-so-much-of-his-time-to-excalidraw&quot;&gt;Lipis on why he dedicates so much of his time to Excalidraw &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#lipis-on-why-he-dedicates-so-much-of-his-time-to-excalidraw&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So this marks the end of my &amp;quot;how I came to Excalidraw&amp;quot; story, but before I dive into some of
Excalidraw&#39;s amazing features, I have the pleasure to introduce Panayiotis. Panayiotis Lipiridis, on
the Internet simply known as &lt;a href=&quot;https://github.com/lipis&quot; rel=&quot;noopener&quot;&gt;lipis&lt;/a&gt;, is the most prolific contributor to
Excalidraw. I asked lipis what motivates him to dedicate so much of his time to Excalidraw:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Like everyone else I learned about this project from Christopher&#39;s tweet. My first contribution
was to add the &lt;a href=&quot;https://yeun.github.io/open-color/&quot; rel=&quot;noopener&quot;&gt;Open Color library&lt;/a&gt;, the colors that are still
part of Excalidraw today. As the project grew and we had quite many requests, my next big
contribution was to build a backend for storing drawings so users could share them. But what
really drives me to contribute is that whoever tried Excalidraw is looking to find excuses to use
it again.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I fully agree with lipis. Whoever tried Excalidraw is looking to find excuses to use it again.&lt;/p&gt;
&lt;h2 id=&quot;excalidraw-in-action&quot;&gt;Excalidraw in action &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#excalidraw-in-action&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I want to show you now how you can use Excalidraw in practice. I&#39;m not a great artist, but the
Google I/O logo is simple enough, so let me give it a try. A box is the &amp;quot;i&amp;quot;, a line can be the
slash, and the &amp;quot;o&amp;quot; is a circle. I hold down &lt;kbd&gt;shift&lt;/kbd&gt;, so I get a perfect circle. Let me move
the slash a little, so it looks better. Now some color for the &amp;quot;i&amp;quot; and the &amp;quot;o&amp;quot;. Blue is good. Maybe
a different fill style? All solid, or cross-hatch? Nah, hachure looks great. It&#39;s not perfect, but
that&#39;s the idea of Excalidraw, so let me save it.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/wK9jDdHG7A7qT5ViOuEQ.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;p&gt;I click the save icon and enter a file name in the file save dialog. In Chrome, a browser that
supports the File System Access API, this is not a download, but a true save operation, where I can
choose the location and name of the file, and where, if I make edits, I can just save them to the
same file.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/HvKcKNk8Q3bbaVe36E3T.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Let me change the logo and make the &amp;quot;i&amp;quot; red. If I now click save again, my modification is saved to
the same file as before. As a proof, let me clear the canvas and reopen the file. As you can see,
the modified red-blue logo is there again.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/XzlUi88cPDYl8YFAH1J8.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;h2 id=&quot;working-with-files&quot;&gt;Working with files &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#working-with-files&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On browsers that currently don&#39;t support the File System Access API, each save operation is a
download, so when I make changes, I end up with multiple files with an incrementing number in the
filename that fill up my Downloads folder. But despite this downside, I can still save the file.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/1oVPIESBNhoL4AhOSNli.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;h3 id=&quot;opening-files&quot;&gt;Opening files &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#opening-files&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;So what&#39;s the secret? How can opening and saving work on different browsers that may or may not
support the File System Access API? Opening a file in Excalidraw happens in a function called
&lt;code&gt;loadFromJSON)(&lt;/code&gt;), which in turn calls a function called &lt;code&gt;fileOpen()&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;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;loadFromJSON&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;localAppState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AppState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; blob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fileOpen&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;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Excalidraw files&#39;&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;extensions&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 string&quot;&gt;&#39;.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.excalidraw&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.png&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.svg&#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;mimeTypes&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 string&quot;&gt;&#39;application/json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/png&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/svg+xml&#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 keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadFromBlob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; localAppState&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;p&gt;The &lt;code&gt;fileOpen()&lt;/code&gt; function that comes from a small library I wrote called
&lt;a href=&quot;https://github.com/GoogleChromeLabs/browser-fs-access&quot; rel=&quot;noopener&quot;&gt;browser-fs-access&lt;/a&gt; that we use in
Excalidraw. This library provides file system access through the
&lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt; with a legacy fallback, so it can be used in any
browser.&lt;/p&gt;
&lt;p&gt;Let me first show you the implementation for when the API is supported. After negotiating the
accepted MIME types and file extensions, the central piece is calling the File System Access API&#39;s
function &lt;code&gt;showOpenFilePicker()&lt;/code&gt;. This function returns an array of files or a single file, dependent
on whether multiple files are selected. All that&#39;s left then is to put the file handle on the file
object, so it can be retrieved again.&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;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;options &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;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; accept &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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Not shown: deal with extensions and MIME types.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handleOrHandles &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showOpenFilePicker&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;types&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&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;accept&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; accept&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;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;multiple&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;multiple &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 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;const&lt;/span&gt; files &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handleOrHandles&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;getFileWithHandle&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;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;multiple&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; files&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; files&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 keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getFileWithHandle&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFile&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;    file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; handle&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; file&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The fallback implementation relies on an &lt;code&gt;input&lt;/code&gt; element of type &lt;code&gt;&amp;quot;file&amp;quot;&lt;/code&gt;. After the negotiation of
the to-be-accepted MIME types and extensions, the next step is to programmatically click the input
element so the file open dialog shows. On change, that is, when the user has selected one or
multiple files, the promise resolves.&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;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;options &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;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&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 parameter&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; input &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;input&#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;    input&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 string&quot;&gt;&#39;file&#39;&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;const&lt;/span&gt; accept &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 operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mimeTypes &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mimeTypes &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;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;extensions &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;extensions &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;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 function&quot;&gt;join&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;    input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;multiple &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;multiple &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;    input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;accept &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; accept &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;*/*&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    input&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;change&#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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;multiple &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&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 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;    input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&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 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;h3 id=&quot;saving-files&quot;&gt;Saving files &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#saving-files&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now to saving. In Excalidraw, saving happens in a function called &lt;code&gt;saveAsJSON()&lt;/code&gt;. It first
serializes the Excalidraw elements array to JSON, converts the JSON to a blob, and then calls a
function called &lt;code&gt;fileSave()&lt;/code&gt;. This function is likewise provided by the
&lt;a href=&quot;https://github.com/GoogleChromeLabs/browser-fs-access&quot; rel=&quot;noopener&quot;&gt;browser-fs-access&lt;/a&gt; library.&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;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;saveAsJSON&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;elements&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; readonly ExcalidrawElement&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 literal-property property&quot;&gt;appState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AppState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; serialized &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;serializeAsJSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;elements&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; appState&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;const&lt;/span&gt; blob &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;Blob&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;serialized&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 literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/vnd.excalidraw+json&#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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fileHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fileSave&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    blob&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 literal-property property&quot;&gt;fileName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; appState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Excalidraw file&#39;&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;extensions&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 string&quot;&gt;&#39;.excalidraw&#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;    appState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fileHandle&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 keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; fileHandle &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;p&gt;Again let me first look at the implementation for browsers with File System Access API support. The
first couple of lines look a little involved, but all they do is negotiate the MIME types and file
extensions. When I have saved before and already have a file handle, no save dialog needs to be
shown. But if this is the first save, a file dialog gets displayed and the app gets a file handle
back for future use. The rest is then just writing to the file, which happens through a
&lt;a href=&quot;https://web.dev/streams/&quot;&gt;writable stream&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 keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;blob&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options &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;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handle &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&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fileName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fileName &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Untitled&#39;&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;const&lt;/span&gt; accept &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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Not shown: deal with extensions and MIME types.&lt;/span&gt;&lt;br /&gt;  handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;    handle &lt;span class=&quot;token operator&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;await&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showSaveFilePicker&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;suggestedName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fileName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;types&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&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;accept&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; accept&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;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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; writable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWritable&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;await&lt;/span&gt; writable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&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;await&lt;/span&gt; writable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&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; handle&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;h4 id=&quot;the-save-as-feature&quot;&gt;The &amp;quot;save as&amp;quot; feature &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#the-save-as-feature&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;If I decide to ignore an already existing file handle, I can implement a &amp;quot;save as&amp;quot; feature to create
a new file based on an existing file. To show this, let me open an existing file, make some
modification, and then not overwrite the existing file, but create a new file by using the save-as
feature. This leaves the original file intact.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/oTNuosQmoMBP2G7XR8Wb.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;p&gt;The implementation for browsers that don&#39;t support the File System Access API is short, since all it
does is create an anchor element with a &lt;code&gt;download&lt;/code&gt; attribute whose value is the desired filename and
a blob URL as its &lt;code&gt;href&lt;/code&gt; attribute value.&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;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;blob&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options &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;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; a &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;a&#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;  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;download &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fileName &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Untitled&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&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;  a&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 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;=&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;setTimeout&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;=&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&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;30&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;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The anchor element then gets clicked programmatically. To prevent memory leaks, the blob URL needs
to be revoked after use. As this is just a download, no file save dialog gets shown ever, and all
files land in the default &lt;code&gt;Downloads&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/1oVPIESBNhoL4AhOSNli.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;h2 id=&quot;drag-and-drop&quot;&gt;Drag and drop &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#drag-and-drop&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of my favorite system integrations on desktop is drag and drop. In Excalidraw, when I drop an
&lt;code&gt;.excalidraw&lt;/code&gt; file onto the application, it opens right away and I can start editing. On browsers
that support the File System Access API, I can then even immediately save my changes. No need to go
through a file save dialog since the required file handle has been obtained from the drag and drop
operation.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/aOPKhOOe20od8uOzehdy.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;p&gt;The secret for making this happen is by calling &lt;code&gt;getAsFileSystemHandle()&lt;/code&gt; on the
&lt;a href=&quot;https://web.dev/datatransfer/&quot;&gt;data transfer&lt;/a&gt; item when the File System Access API is supported. I then pass this
file handle to &lt;code&gt;loadFromBlob()&lt;/code&gt;, which you may remember from a couple paragraphs above. So many
things you can do with files: opening, saving, over-saving, dragging, dropping. My colleague Pete
and I have documented all these tricks and more in &lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;our article&lt;/a&gt; so you can
catch up in case all this went a little too fast.&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;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;files&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 keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/json&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; file&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.excalidraw&#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 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;setState&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;isLoading&lt;/span&gt;&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;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;// Provided by browser-fs-access.&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;supported&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;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;items&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;      file &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; any&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; item &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; any&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAsFileSystemHandle&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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&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;warn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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 function&quot;&gt;loadFromBlob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&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;state&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;then&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 parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; elements&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; appState &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;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Load from blob&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 function&quot;&gt;catch&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 parameter&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;setState&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;isLoading&lt;/span&gt;&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;span class=&quot;token literal-property property&quot;&gt;errorMessage&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message &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 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;sharing-files&quot;&gt;Sharing files &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#sharing-files&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another system integration currently on Android, ChromeOS, and Windows is through the
&lt;a href=&quot;https://web.dev/web-share-target/&quot;&gt;Web Share Target API&lt;/a&gt;. Here I am in the Files app in my &lt;code&gt;Downloads&lt;/code&gt; folder. I
can see two files, one of them with the non-descript name &lt;code&gt;untitled&lt;/code&gt; and a timestamp. To check what
it contains, I click on the three dots, then share, and one of the options that appears is
Excalidraw. When I tap the icon, I can then see that the file just contains the I/O logo again.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/x93JgKGcp1o8at5P7exv.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;h2 id=&quot;lipis-on-the-deprecated-electron-version&quot;&gt;Lipis on the deprecated Electron version &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#lipis-on-the-deprecated-electron-version&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One thing you can do with files that I haven&#39;t talked about yet is doubleclick them. What typically
happens when you doubleclick a file is that the app that&#39;s associated with the file&#39;s MIME type
opens. For example for &lt;code&gt;.docx&lt;/code&gt; this would be Microsoft Word.&lt;/p&gt;
&lt;p&gt;Excalidraw &lt;a href=&quot;https://web.dev/deprecating-excalidraw-electron/&quot;&gt;used to have an Electron version&lt;/a&gt; of the app that
supported such file type associations, so when you double-clicked an &lt;code&gt;.excalidraw&lt;/code&gt; file, the
Excalidraw Electron app would open. Lipis, whom you have already met before, was both the creator
and the deprecator of Excalidraw Electron. I asked him why he felt it was possible to deprecate the
Electron version:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;People have been asking for an Electron app since the beginning, mainly because they wanted to
open files by double-clicking. We also intended to put the app in app stores. In parallel, someone
suggested creating a PWA instead, so we just did both. Luckily we were introduced to Project Fugu
APIs like file system access, clipboard access, file handling, and more. With a sole click you can
install the app on your desktop or mobile, without the extra weight of Electron. It was an easy
decision to deprecate the Electron version, concentrate just on the web app, and make it the
best-possible PWA. On top, we&#39;re now able to publish PWAs to the Play Store and the Microsoft
Store! That&#39;s huge!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One could say Excalidraw for Electron was not deprecated because Electron is bad, not at all, but
because the web has become good enough. I like this!&lt;/p&gt;
&lt;h2 id=&quot;file-handling&quot;&gt;File handling &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#file-handling&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When I say &amp;quot;the web has become good enough&amp;quot;, it&#39;s because of features like the upcoming File
Handling.&lt;/p&gt;
&lt;p&gt;This is a regular macOS Big Sur installation. Now check out what happens when I right-click an
Excalidraw file. I can choose to open it with Excalidraw, the installed PWA. Of course
double-clicking would work, too, it&#39;s just less dramatic to demonstrate in a screencast.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/Gz1w0Gey1XerN86sIF01.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;p&gt;So how does this work? The first step is to make the file types my application can handle known to
the operating system. I do this in a new field called &lt;code&gt;file_handlers&lt;/code&gt; in the web app manifest. Its
value is an array of objects with an action and an &lt;code&gt;accept&lt;/code&gt; property. The action determines the URL
path the operating system launches your app at and the accept object are key value pairs of MIME
types and the associated file extensions.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Excalidraw&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;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Excalidraw is a whiteboard tool...&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;&quot;start_url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&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;&quot;display&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;standalone&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;&quot;theme_color&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#000000&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;&quot;background_color&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#ffffff&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;&quot;file_handlers&quot;&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;action&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&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;&quot;accept&quot;&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 property&quot;&gt;&quot;application/vnd.excalidraw+json&quot;&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 string&quot;&gt;&quot;.excalidraw&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;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;The next step is to handle the file when the application launches. This happens in the &lt;code&gt;launchQueue&lt;/code&gt;
interface where I need to set a consumer by calling, well, &lt;code&gt;setConsumer()&lt;/code&gt;. The parameter to this
function is an asynchronous function that receives the &lt;code&gt;launchParams&lt;/code&gt;. This &lt;code&gt;launchParams&lt;/code&gt; object
has a field called files that gets me an array of file handles to work with. I only care for the
first one and from this file handle I get a blob that I then pass to our old friend
&lt;code&gt;loadFromBlob()&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;launchQueue&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;LaunchParams&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&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;  window &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; any&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;launchQueue&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setConsumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;launchParams&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 literal-property property&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; any&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&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;launchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&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 keyword&quot;&gt;const&lt;/span&gt; fileHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; launchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&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 keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Blob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFile&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;      blob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;loadFromBlob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&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;state&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;then&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 parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; elements&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; appState &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;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Initialize app state.&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 function&quot;&gt;catch&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 parameter&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;setState&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;isLoading&lt;/span&gt;&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;span class=&quot;token literal-property property&quot;&gt;errorMessage&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message &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 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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Again, if this went too fast, you can read more about the File Handling API in
&lt;a href=&quot;https://web.dev/file-handling/&quot;&gt;my article&lt;/a&gt;. You can enable file handling by setting the experimental web platform
features flag. It&#39;s scheduled to land in Chrome later this year.&lt;/p&gt;
&lt;h2 id=&quot;clipboard-integration&quot;&gt;Clipboard integration &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#clipboard-integration&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another cool feature of Excalidraw is the clipboard integration. I can copy my entire drawing or
just parts of it into the clipboard, maybe adding a watermark if I feel like, and then paste it into
another app. This is a web version of the Windows 95 Paint app by the way.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/EHHQS78y6RJf21J1wD7y.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;p&gt;The way this works is surprisingly simple. All I need is the canvas as a blob, which I then write
onto the clipboard by passing a one-element array with a &lt;code&gt;ClipboardItem&lt;/code&gt; with the blob to the
&lt;code&gt;navigator.clipboard.write()&lt;/code&gt; function. For more information on what you can do with the clipboard
API, See Jason&#39;s and &lt;a href=&quot;https://web.dev/async-clipboard/&quot;&gt;my article&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 keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;copyCanvasToClipboardAsPng&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;canvas&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; HTMLCanvasElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; blob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;canvasToBlob&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;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&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;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ClipboardItem&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 string-property property&quot;&gt;&#39;image/png&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; blob&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;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;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; canvasToBlob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;canvas&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; HTMLCanvasElement&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Promise&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Blob&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&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 parameter&quot;&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;      canvas&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBlob&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 parameter&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;blob&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 function&quot;&gt;reject&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;CanvasError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;canvasError.canvasTooBig&#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 string&quot;&gt;&#39;CANVAS_POSSIBLY_TOO_BIG&#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 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;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&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;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&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 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;h2 id=&quot;collaborating-with-others&quot;&gt;Collaborating with others &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#collaborating-with-others&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;sharing-a-session-url&quot;&gt;Sharing a session URL &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#sharing-a-session-url&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Did you know that Excalidraw also has a collaborative mode? Different people can work together on
the same document. To start a new session, I click on the live collaboration button and then start a
session. I can share the session URL with my collaborators easily thanks to the
&lt;a href=&quot;https://web.dev/web-share/&quot;&gt;Web Share API&lt;/a&gt; that Excalidraw has integrated.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/7tbl5j0jrVZd3ffxhpoX.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;h3 id=&quot;live-collaboration&quot;&gt;Live collaboration &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#live-collaboration&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I have simulated a collaboration session locally by working on the Google I/O logo on my Pixelbook,
my Pixel 3a phone, and my iPad Pro. You can see that changes I make on one device are reflected on
all other devices.&lt;/p&gt;
&lt;p&gt;I can even see all cursors move around. The Pixelbook&#39;s cursor moves steadily, since it&#39;s controlled
by a trackpad, but the Pixel 3a phone&#39;s cursor and the iPad Pro&#39;s tablet cursor jump around, since I
control these devices by tapping with my finger.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/7muh13F0CjvKBntVrUTp.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;h3 id=&quot;seeing-collaborator-statuses&quot;&gt;Seeing collaborator statuses &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#seeing-collaborator-statuses&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To improve the realtime collaboration experience, there is even an idle detection system running.
The cursor of the iPad Pro shows a green dot when I use it. The dot turns black when I switch to a
different browser tab or app. And when I&#39;m in the Excalidraw app, but just not doing anything, the
cursor shows me as idle, symbolized by the three zZZs.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/Y7vEI1qHTDJpHNdXjteS.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Avid readers of our publications might be inclined to think that idle detection is realized through
the &lt;a href=&quot;https://web.dev/idle-detection/&quot;&gt;Idle Detection API&lt;/a&gt;, an early stage proposal that&#39;s been worked on in the
context of Project Fugu. Spoiler alert: it&#39;s not. While we had an implementation based on this API
in Excalidraw, in the end, we decided to go for a more traditional approach based on measuring
pointer movement and page visibility.&lt;/p&gt;
&lt;img alt=&quot;Screenshot of the Idle Detection feedback filed on the WICG Idle Detection repo.&quot; decoding=&quot;async&quot; height=&quot;685&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/SudM7tqa3ZooUJYx7aBB.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;We filed &lt;a href=&quot;https://github.com/WICG/idle-detection/issues/36&quot; rel=&quot;noopener&quot;&gt;feedback&lt;/a&gt; on why the Idle Detection API
wasn&#39;t solving the use case we had. All Project Fugu APIs are being developed in the open, so
everyone can chime in and have their voice heard!&lt;/p&gt;
&lt;h2 id=&quot;lipis-on-what-is-holding-back-excalidraw&quot;&gt;Lipis on what is holding back Excalidraw &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#lipis-on-what-is-holding-back-excalidraw&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Talking of which, I asked lipis one last question regarding what he thinks is missing from the web
platform that holds back Excalidraw:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The File System Access API is great, but you know what? Most files that I care about these days
live in my Dropbox or Google Drive, not on my hard disk. I wish the File System Access API would
include an abstraction layer for remote file systems providers like Dropbox or Google to integrate
with and that developers could code against. Users could then relax and know their files are safe
with the cloud provider they trust.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I fully agree with lipis, I live in the cloud, too. Here&#39;s hoping that this will be implemented
soon.&lt;/p&gt;
&lt;h2 id=&quot;tabbed-application-mode&quot;&gt;Tabbed application mode &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#tabbed-application-mode&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Wow! We have seen a lot of really great API integrations in Excalidraw.
&lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File system&lt;/a&gt;, &lt;a href=&quot;https://web.dev/file-handling/&quot;&gt;file handling&lt;/a&gt;,
&lt;a href=&quot;https://web.dev/excalidraw-and-fugu/%5Casync-clipboard/&quot;&gt;clipboard&lt;/a&gt;, &lt;a href=&quot;https://web.dev/excalidraw-and-fugu/%5Cweb-share/&quot;&gt;web share&lt;/a&gt;, and
&lt;a href=&quot;https://web.dev/web-share-target/&quot;&gt;web share target&lt;/a&gt;. But here is one more thing. Up until now, I could only ever
edit one document at a given time. Not anymore. Please enjoy for the first time an early version of
tabbed application mode in Excalidraw. This is how it looks.&lt;/p&gt;
&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/h8zrwaB8jBXVnQuxglpS.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;&lt;/p&gt;
&lt;p&gt;I have an existing file open in the installed Excalidraw PWA that&#39;s running in standalone mode. Now
I open a new tab in the standalone window. This is not a regular browser tab, but a PWA tab. In this
new tab I can then open a secondary file, and work on them independently from the same app window.&lt;/p&gt;
&lt;p&gt;Tabbed application mode is in its early stages and not everything is set in stone. If you&#39;re
interested, be sure to read up on the current status of this feature in
&lt;a href=&quot;https://web.dev/tabbed-application-mode/&quot;&gt;my article&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;closing&quot;&gt;Closing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/excalidraw-and-fugu/#closing&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To stay in the loop on this and other features, be sure to watch our
&lt;a href=&quot;https://fugu-tracker.web.app/&quot; rel=&quot;noopener&quot;&gt;Fugu API tracker&lt;/a&gt;. We&#39;re super excited to push the web forward and
allow you to do more on the platform. Here&#39;s to an ever improving Excalidraw, and here&#39;s to all the
amazing applications that you will build. Go start creating at
&lt;a href=&quot;https://excalidraw.com/&quot; rel=&quot;noopener&quot;&gt;excalidraw.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I can&#39;t wait to see some of the APIs that I have shown today pop up in your apps. My name is Tom,
you can find me as &lt;a href=&quot;https://twitter.com/tomayac&quot; rel=&quot;noopener&quot;&gt;@tomayac&lt;/a&gt; on Twitter and the internet in general.
Thank you very much for watching, and enjoy the rest of Google I/O.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Customize the window controls overlay of your PWA&#39;s title bar</title>
    <link href="https://web.dev/window-controls-overlay/"/>
    <updated>2021-04-22T00:00:00Z</updated>
    <id>https://web.dev/window-controls-overlay/</id>
    <content type="html" mode="escaped">&lt;p&gt;If you remember my article &lt;a href=&quot;https://web.dev/app-like-pwas/&quot;&gt;Make your PWA feel more like an app&lt;/a&gt;, you may recall
how I mentioned &lt;a href=&quot;https://web.dev/app-like-pwas/#customized-title-bar&quot;&gt;customizing the title bar of your app&lt;/a&gt; as a
strategy for creating a more app-like experience. Here is an example of how this can look
showing the macOS Podcasts app.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A macOS Podcasts app title bar showing media control buttons and metadata about the currently playing podcast.&quot; decoding=&quot;async&quot; height=&quot;63&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/o5gZ3GSKyUZOPhFxX7js.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
     A custom title bar makes your PWA feel more like a platform-specific app.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Now you may be tempted to object by saying that Podcasts is a platform-specific macOS app that does
not run in a browser and therefore can do what it wants without having to play by the browser&#39;s
rules. True, but the good news is that the Window Controls Overlay feature, which is the topic of
this very article, soon lets you create similar user interfaces for your PWA.&lt;/p&gt;
&lt;h2 id=&quot;window-controls-overlay-components&quot;&gt;Window Controls Overlay components &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#window-controls-overlay-components&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Window Controls Overlay consists of four sub-features:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;&amp;quot;window-controls-overlay&amp;quot;&lt;/code&gt; value for the &lt;a href=&quot;https://web.dev/display-override/&quot;&gt;&lt;code&gt;&amp;quot;display_override&amp;quot;&lt;/code&gt;&lt;/a&gt; field in
the web app manifest.&lt;/li&gt;
&lt;li&gt;The CSS environment variables &lt;code&gt;titlebar-area-x&lt;/code&gt;, &lt;code&gt;titlebar-area-y&lt;/code&gt;, &lt;code&gt;titlebar-area-width&lt;/code&gt;, and
&lt;code&gt;titlebar-area-height&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The standardization of the previously proprietary CSS property &lt;code&gt;-webkit-app-region&lt;/code&gt; as the
&lt;code&gt;app-region&lt;/code&gt; property to define draggable regions in web content.&lt;/li&gt;
&lt;li&gt;A mechanism to query for and work around the window controls region via the
&lt;code&gt;windowControlsOverlay&lt;/code&gt; member of &lt;code&gt;window.navigator&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;what-is-window-controls-overlay&quot;&gt;What is Window Controls Overlay &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#what-is-window-controls-overlay&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The title bar area refers to the space to the left or right of the window controls (that is, the
buttons to minimize, maximize, close, etc.) and often contains the title of the application. Window
Controls Overlay lets progressive web applications (PWAs) provide a more app-like feel by swapping
the existing full-width title bar for a small overlay containing the window controls. This allows
developers to place custom content in what was previously the browser-controlled title bar area.&lt;/p&gt;
&lt;h2 id=&quot;status&quot;&gt;Current status &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#status&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt;
&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Create explainer&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/WICG/window-controls-overlay/blob/main/explainer.md&quot; rel=&quot;noopener&quot;&gt;Complete&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Create initial draft of specification&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://wicg.github.io/window-controls-overlay/&quot; rel=&quot;noopener&quot;&gt;Complete&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Gather feedback &amp;amp; iterate on design&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://web.dev/window-controls-overlay/#feedback&quot;&gt;In progress&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Origin trial&lt;/td&gt;
&lt;td&gt;Complete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. &lt;strong&gt;Launch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Complete&lt;/strong&gt; (in Chromium 104)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;how-to-use-window-controls-overlay&quot;&gt;How to use Window Controls Overlay &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#how-to-use-window-controls-overlay&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;adding-window-controls-overlay-to-the-web-app-manifest&quot;&gt;Adding &lt;code&gt;window-controls-overlay&lt;/code&gt; to the web app manifest &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#adding-window-controls-overlay-to-the-web-app-manifest&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A progressive web app can opt-in to the window controls overlay by adding
&lt;code&gt;&amp;quot;window-controls-overlay&amp;quot;&lt;/code&gt; as the primary &lt;code&gt;&amp;quot;display_override&amp;quot;&lt;/code&gt; member in the web app manifest:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;display_override&quot;&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 string&quot;&gt;&quot;window-controls-overlay&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The window controls overlay will be visible only when all of the following conditions are satisfied:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The app is &lt;em&gt;not&lt;/em&gt; opened in the browser, but in a separate PWA window.&lt;/li&gt;
&lt;li&gt;The manifest includes &lt;code&gt;&amp;quot;display_override&amp;quot;: [&amp;quot;window-controls-overlay&amp;quot;]&lt;/code&gt;. (Other values are
allowed thereafter.)&lt;/li&gt;
&lt;li&gt;The PWA is running on a desktop operating system.&lt;/li&gt;
&lt;li&gt;The current origin matches the origin for which the PWA was installed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result of this is an empty title bar area with the regular window controls on the left or the
right, depending on the operating system.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;An app window with an empty titlebar with the window controls on the left.&quot; decoding=&quot;async&quot; height=&quot;182&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/zVuuiMs37fGrDK8J7PXK.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    An empty title bar ready for custom content.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;moving-content-into-the-title-bar&quot;&gt;Moving content into the title bar &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#moving-content-into-the-title-bar&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that there is space in the title bar, you can move something there. For this article, I
built a Wikimedia Featured Content PWA. A useful feature for this app may be a search for words in
the article titles. The HTML for the search feature looks like this:&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;class&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;search&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;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;logo.svg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&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;Wikimedia logo.&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;32&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;32&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;label&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;input&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;search&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;    Search for words in articles&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;label&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;To move this &lt;code&gt;div&lt;/code&gt; up into the title bar, some CSS is needed:&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;.search&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;/* Make sure the `div` stays there, even when scrolling. */&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; fixed&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt;   * Gradient, because why not. Endless opportunities.&lt;br /&gt;   * The gradient ends in `#36c`, which happens to be the app&#39;s&lt;br /&gt;   * `&amp;lt;meta name=&quot;theme-color&quot; content=&quot;#36c&quot;&gt;`.&lt;br /&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;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;90deg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #36c&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #131313&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 33%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #36c&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;/* Use the environment variable for the left anchoring with a fallback. */&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlebar-area-x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&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;/* Use the environment variable for the top anchoring with a fallback. */&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlebar-area-y&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&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;/* Use the environment variable for setting the width with a fallback. */&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlebar-area-width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 100%&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;/* Use the environment variable for setting the height with a fallback. */&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;titlebar-area-height&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 33px&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;You can see the effect of this code in the screenshot below. The title bar is fully responsive. When
you resize the PWA window, the title bar reacts as if it were composed of regular HTML content,
which, in fact, it is.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;An app window with a search bar in the title bar.&quot; decoding=&quot;async&quot; height=&quot;182&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/z59JyFopfQC35WrEyA6g.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The new title bar is active and responsive.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;determining-which-parts-of-the-title-bar-are-draggable&quot;&gt;Determining which parts of the title bar are draggable &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#determining-which-parts-of-the-title-bar-are-draggable&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While the screenshot above suggests that you are done, you are not done quite yet. The PWA window is
no longer draggable (apart from a very small area), since the window controls buttons are not drag
areas, and the rest of the title bar consists of the search widget. Fix this using
the &lt;code&gt;app-region&lt;/code&gt; CSS property with a value of &lt;code&gt;drag&lt;/code&gt;. In the concrete case, it is fine to make
everything besides the &lt;code&gt;input&lt;/code&gt; element draggable.&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 comment&quot;&gt;/* The entire search `div` is draggable… */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.search&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;-webkit-app-region&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; drag&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;app-region&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; drag&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 comment&quot;&gt;/* …except for the `input`. */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;input&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;-webkit-app-region&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; no-drag&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;app-region&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; no-drag&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;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; For now, &lt;code&gt;app-region&lt;/code&gt; has not been standardized, so the plan is to continue using the prefixed &lt;code&gt;-webkit-app-region&lt;/code&gt; until &lt;code&gt;app-region&lt;/code&gt; is standardized. Currently, only &lt;code&gt;-webkit-app-region&lt;/code&gt; is supported in the browser. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;With this CSS in place, the user can drag the app window as usual by dragging the &lt;code&gt;div&lt;/code&gt;, the &lt;code&gt;img&lt;/code&gt;,
or the &lt;code&gt;label&lt;/code&gt;. Only the &lt;code&gt;input&lt;/code&gt; element is interactive so the search query can be entered.&lt;/p&gt;
&lt;h3 id=&quot;feature-detection&quot;&gt;Feature detection &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#feature-detection&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Support for Window Controls Overlay can be detected by testing for the existence of
&lt;code&gt;windowControlsOverlay&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;windowControlsOverlay&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;// Window Controls Overlay is supported.&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;querying-the-window-controls-region-with-windowcontrolsoverlay&quot;&gt;Querying the window controls region with &lt;code&gt;windowControlsOverlay&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#querying-the-window-controls-region-with-windowcontrolsoverlay&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The code so far has one problem: on some platforms the window controls are on the right, on
others they are on the left. To make matters worse, the &amp;quot;three dots&amp;quot; Chrome menu will change
position, too, based on the platform. This means that the linear gradient background image needs to
be dynamically adapted to run from &lt;code&gt;#131313&lt;/code&gt;→&lt;code&gt;maroon&lt;/code&gt; or &lt;code&gt;maroon&lt;/code&gt;→&lt;code&gt;#131313&lt;/code&gt;→&lt;code&gt;maroon&lt;/code&gt;, so that it
blends in with the title bar&#39;s &lt;code&gt;maroon&lt;/code&gt; background color which is determined by
&lt;code&gt;&amp;lt;meta name=&amp;quot;theme-color&amp;quot; content=&amp;quot;maroon&amp;quot;&amp;gt;&lt;/code&gt;. This can be achieved by querying the
&lt;code&gt;getTitlebarAreaRect()&lt;/code&gt; API on the &lt;code&gt;navigator.windowControlsOverlay&lt;/code&gt; property.&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;&lt;span class=&quot;token string&quot;&gt;&#39;windowControlsOverlay&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;const&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; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowControlsOverlay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTitlebarAreaRect&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;// Window controls are on the right (like on Windows).&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Chrome menu is left of the window controls.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// [ windowControlsOverlay___________________ […] [_] [■] [X] ]&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;x &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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    div&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;search-controls-right&#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 comment&quot;&gt;// Window controls are on the left (like on macOS).&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Chrome menu is right of the window controls overlay.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// [ [X] [_] [■] ___________________windowControlsOverlay [⋮] ]&lt;/span&gt;&lt;br /&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;    div&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;search-controls-left&#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;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;// When running in a non-supporting browser tab.&lt;/span&gt;&lt;br /&gt;  div&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;search-controls-right&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Rather than having the background image in the &lt;code&gt;.search&lt;/code&gt; class CSS rules directly (as before), the
modified code now uses two classes that the code above sets dynamically.&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 comment&quot;&gt;/* For macOS: */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.search-controls-left&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;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;90deg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #36c&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 45%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #131313&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 90%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #36c&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 comment&quot;&gt;/* For Windows: */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.search-controls-right&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;linear-gradient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;90deg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #36c&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #131313&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 33%&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; #36c&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;determining-if-the-window-controls-overlay-is-visible&quot;&gt;Determining if the window controls overlay is visible &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#determining-if-the-window-controls-overlay-is-visible&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The window controls overlay will not be visible in the title bar area in all circumstances. While it
will naturally not be there on browsers that do not support the Window Controls Overlay feature, it
will also not be there when the PWA in question runs in a tab. To detect this situation, you can
query the &lt;code&gt;visible&lt;/code&gt; property of the &lt;code&gt;windowControlsOverlay&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowControlsOverlay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visible&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;// The window controls overlay is visible in the title bar area.&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;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; The window controls overlay visibility is not to be confused with the visibility in the CSS sense of whatever HTML content you place next to the window controls overlay. Even if you set &lt;code&gt;display: none&lt;/code&gt; on the &lt;code&gt;div&lt;/code&gt; placed into the window controls overlay, the &lt;code&gt;visible&lt;/code&gt; property of the window controls overlay would still report &lt;code&gt;true&lt;/code&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Alternatively, you can also use the &lt;code&gt;display-mode&lt;/code&gt; media query in JavaScript and/or CSS:&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 query list.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mediaQueryList &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matchMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(display-mode: window-controls-overlay)&#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;// Define a callback function for the event listener.&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;handleDisplayModeChange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;mql&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;// React on display mode changes.&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 comment&quot;&gt;// Run the display mode change handler once.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;handleOrientationChange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mediaQueryList&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;// Add the callback function as a listener to the query list.&lt;/span&gt;&lt;br /&gt;mediaQueryList&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;change&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handleDisplayModeChange&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;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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;display-mode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; window-controls-overlay&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 comment&quot;&gt;/* React on display mode changes. */&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;being-notified-of-geometry-changes&quot;&gt;Being notified of geometry changes &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#being-notified-of-geometry-changes&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Querying the window controls overlay area with &lt;code&gt;getTitlebarAreaRect()&lt;/code&gt; can suffice for one-off
things like setting the correct background image based on where the window controls are, but in
other cases, more fine-grained control is necessary. For example, a possible use case could be to
adapt the window controls overlay based on the available space and to add a joke right in the window
control overlay when there is enough space.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Window controls overlay area on a narrow window with shortened text.&quot; decoding=&quot;async&quot; height=&quot;303&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/uDWjKo827ntEHp5S8tyW.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Title bar controls adapted to a narrow window.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You can be notified of geometry changes by subscribing to
&lt;code&gt;navigator.windowControlsOverlay.ongeometrychange&lt;/code&gt; or by setting up an event listener for the
&lt;code&gt;geometrychange&lt;/code&gt; event. This event will only fire when the window controls overlay is visible, that
is, when &lt;code&gt;navigator.windowControlsOverlay.visible&lt;/code&gt; is &lt;code&gt;true&lt;/code&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; Since this event fires frequently (comparable to how a scroll listener fires), I always recommend you use a &lt;a href=&quot;https://css-tricks.com/the-difference-between-throttling-and-debouncing/#debouncing-enforces-that-a-function-not-be-called-again-until-a-certain-amount-of-time-has-passed-without-it-being-called-as-in-execute-this-function-only-if-100-milliseconds-have-passed-witho&quot;&gt;debounce function&lt;/a&gt; so the event does not fire too often. &lt;/div&gt;&lt;/aside&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;debounce&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 parameter&quot;&gt;func&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; wait&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;let&lt;/span&gt; timeout&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 keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;executedFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;args&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;later&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;span class=&quot;token operator&quot;&gt;=&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;clearTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timeout&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;func&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;args&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;span class=&quot;token function&quot;&gt;clearTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;timeout&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;    timeout &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;later&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; wait&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;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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;windowControlsOverlay&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;  navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowControlsOverlay&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ongeometrychange &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;debounce&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 parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    span&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hidden &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;titlebarAreaRect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;800&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;250&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;Rather than assigning a function to &lt;code&gt;ongeometrychange&lt;/code&gt;, you can also add an event listener to
&lt;code&gt;windowControlsOverlay&lt;/code&gt; as below. You can read about the difference between the two on
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/Guide/Events/Event_handlers&quot; rel=&quot;noopener&quot;&gt;MDN&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;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;windowControlsOverlay&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;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;geometrychange&#39;&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;debounce&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 parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    span&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hidden &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;titlebarAreaRect&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;width &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;800&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;250&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;h3 id=&quot;compatibility-when-running-in-a-tab-and-on-non-supporting-browsers&quot;&gt;Compatibility when running in a tab and on non-supporting browsers &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#compatibility-when-running-in-a-tab-and-on-non-supporting-browsers&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are two possible cases to consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The case where an app is running in a browser that &lt;em&gt;supports&lt;/em&gt; Window Controls Overlay, but
where the app is used in a browser tab.&lt;/li&gt;
&lt;li&gt;The case where an app is running in a browser that &lt;em&gt;does not support&lt;/em&gt; Window Controls Overlay.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In both cases, by default the HTML built for the window controls
overlay will display inline like regular HTML content and the &lt;code&gt;env()&lt;/code&gt; variables&#39; fallback values
will kick in for the positioning. On supporting browsers, you can also decide to not display the
HTML designated for the window controls overlay by checking the overlay&#39;s &lt;code&gt;visible&lt;/code&gt; property, and if
it reports &lt;code&gt;false&lt;/code&gt;, then hiding that HTML content.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A PWA running in a browser tab with the window controls overlay displayed in the body.&quot; decoding=&quot;async&quot; height=&quot;428&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/2rAm7saFWnCnaIzukTlO.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Controls meant for the title bar can be easily displayed in the body on older browsers.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;As a reminder, non-supporting browsers will either not consider the
&lt;a href=&quot;https://web.dev/display-override/&quot;&gt;&lt;code&gt;&amp;quot;display_override&amp;quot;&lt;/code&gt;&lt;/a&gt; web app manifest property at all, or not recognize the
&lt;code&gt;&amp;quot;window-controls-overlay&amp;quot;&lt;/code&gt; and thus use the next possible value according to the fallback chain,
for example, &lt;code&gt;&amp;quot;standalone&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A PWA running in standalone mode with the window controls overlay displayed in the body.&quot; decoding=&quot;async&quot; height=&quot;428&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/1VonSc0jOiOureeSkqtf.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Controls meant for the title bar can be easily displayed in the body on older browsers.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;ui-considerations&quot;&gt;UI considerations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#ui-considerations&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While it may be tempting, creating a classic dropdown menu in the Window Controls Overlay area is not recommended. Doing so would violate the
&lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/macos/menus/menu-bar-menus/&quot; rel=&quot;noopener&quot;&gt;design guidelines on macOS&lt;/a&gt;,
a platform on which users expect menu bars (both system-provided ones and
custom ones) at the top of the screen.&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; Having a proper App Menu API, similar to &lt;a href=&quot;https://www.electronjs.org/docs/latest/api/menu&quot;&gt;&lt;code&gt;Menu&lt;/code&gt;&lt;/a&gt; available to Electron.js apps, is tracked as &lt;a href=&quot;https://crbug.com/1295253&quot;&gt;crbug/1295253&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;If your app provides a fullscreen experience, carefully consider whether it makes sense
for your Window Controls Overlay to be part of the fullscreen view. Potentially you may
want to rearrange your layout when the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Document/onfullscreenchange&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;onfullscreenchange&lt;/code&gt;&lt;/a&gt;
event fires.&lt;/p&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have created a &lt;a href=&quot;https://window-controls-overlay.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt; that you can play with in
different supporting and non-supporting browsers and in the installed and non-installed state. For
the actual Window Controls Overlay experience, you need to install the app and set the
&lt;a href=&quot;https://web.dev/window-controls-overlay/#enabling-via-chrome:flags&quot;&gt;flag&lt;/a&gt;. You can see two screenshots of what to expect below. The
&lt;a href=&quot;https://glitch.com/edit/#!/window-controls-overlay&quot; rel=&quot;noopener&quot;&gt;source code&lt;/a&gt; for the app is available on Glitch.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Wikimedia Featured Content demo app with Window Controls Overlay.&quot; decoding=&quot;async&quot; height=&quot;543&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/g8uxpFCkWhmFUkrIAVrJ.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The demo app is available for experimentation.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The search feature in the window controls overlay is fully functional:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Wikimedia Featured Content demo app with Window Controls Overlay and active search for the term &amp;#x27;cleopa…&amp;#x27; highlighting one of the articles with the matched term &amp;#x27;Cleopatra&amp;#x27;.&quot; decoding=&quot;async&quot; height=&quot;543&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/J3nvcwbrHznRFw5ZxaJS.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    A search feature using the Window Controls Overlay.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;security-considerations&quot;&gt;Security considerations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#security-considerations&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Chromium team designed and implemented the Window Controls Overlay API using the core principles
defined in &lt;a href=&quot;https://chromium.googlesource.com/chromium/src/+/lkgr/docs/security/permissions-for-powerful-web-platform-features.md&quot; rel=&quot;noopener&quot;&gt;Controlling Access to Powerful Web Platform Features&lt;/a&gt;, including user
control, transparency, and ergonomics.&lt;/p&gt;
&lt;h3 id=&quot;spoofing&quot;&gt;Spoofing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#spoofing&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Giving sites partial control of the title bar leaves room for developers to spoof content in what
was previously a trusted, browser-controlled region. Currently, in Chromium browsers, standalone
mode includes a title bar which on initial launch displays the title of the webpage on the left, and
the origin of the page on the right (followed by the &amp;quot;settings and more&amp;quot; button and the window
controls). After a few seconds, the origin text disappears. If the browser is set to a right-to-left
(RTL) language, this layout is flipped such that the origin text is on the left. This opens the
window controls overlay to spoof the origin if there is insufficient padding between the origin and
the right edge of the overlay. For example, the origin &amp;quot;evil.ltd&amp;quot; could be appended with a trusted
site &amp;quot;google.com&amp;quot;, leading users to believe that the source is trustworthy. The plan is to keep this
origin text so that users know what the origin of the app is and can ensure that it matches their
expectations. For RTL configured browsers, there must be enough padding to the right of the origin
text to prevent a malicious website from appending the unsafe origin with a trusted origin.&lt;/p&gt;
&lt;h3 id=&quot;fingerprinting&quot;&gt;Fingerprinting &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#fingerprinting&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Enabling the window controls overlay and draggable regions do not pose
considerable privacy concerns other than feature detection. However, due to
differing sizes and positions of the window control buttons across operating
systems, the
&lt;code&gt;navigator.&lt;wbr /&gt;windowControlsOverlay.&lt;wbr /&gt;getTitlebarAreaRect()&lt;/code&gt;
method returns a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/DOMRect&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;DOMRect&lt;/code&gt;&lt;/a&gt;
whose position and dimensions reveal information about the operating system upon
which the browser is running. Currently, developers can already discover the OS
from the user agent string, but due to fingerprinting concerns, there is
discussion about freezing the UA string and unifying OS versions. There is an
ongoing effort within the browser community to understand how frequently the
size of the window controls overlay changes across platforms, as the current
assumption is that these are fairly stable across OS versions and thus would not
be useful for observing minor OS versions. Although this is a potential
fingerprinting issue, it only applies to installed PWAs that use the custom
title bar feature and does not apply to general browser usage. Additionally, the
&lt;code&gt;navigator.&lt;wbr /&gt;windowControlsOverlay&lt;/code&gt; API will not be available to
iframes embedded inside of a PWA.&lt;/p&gt;
&lt;h3 id=&quot;navigation&quot;&gt;Navigation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#navigation&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Navigating to a different origin within a PWA will cause it to fall back to the normal standalone
title bar, even if it meets the above criteria and is launched with the window controls overlay.
This is to accommodate the black bar that appears on navigation to a different origin. After
navigating back to the original origin, the window controls overlay will be used again.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A black URL bar for out-of-origin navigation.&quot; decoding=&quot;async&quot; height=&quot;543&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oUxysbNURuPZgAodB5hU.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    A black bar is shown when the user navigates to a different origin.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;feedback&quot;&gt;Feedback &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#feedback&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Chromium team wants to hear about your experiences with the Window Controls Overlay API.&lt;/p&gt;
&lt;h3 id=&quot;tell-us-about-the-api-design&quot;&gt;Tell us about the API design &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#tell-us-about-the-api-design&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Is there something about the API that doesn&#39;t work like you expected? Or are there missing methods
or properties that you need to implement your idea? Have a question or comment on the security
model? File a spec issue on the corresponding &lt;a href=&quot;https://github.com/WICG/window-controls-overlay/issues&quot; rel=&quot;noopener&quot;&gt;GitHub repo&lt;/a&gt;, or add your thoughts to an
existing issue.&lt;/p&gt;
&lt;h3 id=&quot;report-a-problem-with-the-implementation&quot;&gt;Report a problem with the implementation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#report-a-problem-with-the-implementation&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Did you find a bug with Chromium&#39;s implementation? Or is the implementation different from the spec?
File a bug at &lt;a href=&quot;https://new.crbug.com/&quot; rel=&quot;noopener&quot;&gt;new.crbug.com&lt;/a&gt;. Be sure to include as much detail as you can,
simple instructions for reproducing, and enter &lt;code&gt;UI&amp;gt;Browser&amp;gt;WebAppInstalls&lt;/code&gt; in the &lt;strong&gt;Components&lt;/strong&gt;
box. &lt;a href=&quot;https://glitch.com/&quot; rel=&quot;noopener&quot;&gt;Glitch&lt;/a&gt; works great for sharing quick and easy repros.&lt;/p&gt;
&lt;h3 id=&quot;show-support-for-the-api&quot;&gt;Show support for the API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#show-support-for-the-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Are you planning to use the Window Controls Overlay API? Your public support helps the Chromium team
to prioritize features and shows other browser vendors how critical it is to support them.&lt;/p&gt;
&lt;p&gt;Send a Tweet to &lt;a href=&quot;https://twitter.com/ChromiumDev&quot; rel=&quot;noopener&quot;&gt;@ChromiumDev&lt;/a&gt; with the
&lt;a href=&quot;https://twitter.com/search?q=%23WindowControlsOverlay&amp;amp;src=recent_search_click&amp;amp;f=live&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;#WindowControlsOverlay&lt;/code&gt;&lt;/a&gt;
hashtag and let us know where and how you&#39;re using it.&lt;/p&gt;
&lt;h2 id=&quot;helpful&quot;&gt;Helpful links &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#helpful&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/WICG/window-controls-overlay/blob/main/explainer.md&quot; rel=&quot;noopener&quot;&gt;Explainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/window-controls-overlay/&quot; rel=&quot;noopener&quot;&gt;Spec draft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://crbug.com/937121&quot; rel=&quot;noopener&quot;&gt;Chromium bug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chromestatus.com/feature/5741247866077184&quot; rel=&quot;noopener&quot;&gt;Chrome Platform Status entry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/w3ctag/design-reviews/issues/481&quot; rel=&quot;noopener&quot;&gt;TAG review&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/experimental-features/#window-controls-overlay-for-installed-desktop-web-apps&quot; rel=&quot;noopener&quot;&gt;Microsoft Edge&#39;s related docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/window-controls-overlay/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Window Controls Overlay was implemented and specified by
&lt;a href=&quot;https://www.linkedin.com/in/amanda-baker-20a2b962/&quot; rel=&quot;noopener&quot;&gt;Amanda Baker&lt;/a&gt; from the Microsoft Edge team.
This article was reviewed by &lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt; and
&lt;a href=&quot;https://github.com/kenchris&quot; rel=&quot;noopener&quot;&gt;Kenneth Rohde Christiansen&lt;/a&gt;. Hero image by
&lt;a href=&quot;https://unsplash.com/@sigmund&quot; rel=&quot;noopener&quot;&gt;Sigmund&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/OV44gxH71DU&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author><author>
      <name>Amanda Baker</name>
    </author>
  </entry>
  
  <entry>
    <title>Breaking down barriers using the DataTransfer API</title>
    <link href="https://web.dev/datatransfer/"/>
    <updated>2021-04-21T00:00:00Z</updated>
    <id>https://web.dev/datatransfer/</id>
    <content type="html" mode="escaped">&lt;p&gt;You might have heard about the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/DataTransfer&quot; rel=&quot;noopener&quot;&gt;DataTransfer API&lt;/a&gt;, which is
part of the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API&quot; rel=&quot;noopener&quot;&gt;HTML5 Drag and Drop API&lt;/a&gt;
and &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Element/copy_event&quot; rel=&quot;noopener&quot;&gt;Clipboard events&lt;/a&gt;. It can
be used to transfer data between source and receiving targets.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 3, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      3
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 3.5, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      3.5
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 12, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      12
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 4, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      4
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/DataTransfer#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;The drag-drop and copy-paste interactions are often used for interactions within a page
to transfer simple text from A to B. But what is oftentimes overlooked is the ability to use
these same interactions to go beyond the browser window.&lt;/p&gt;
&lt;p&gt;Both the browser&#39;s built-in drag-and-drop and the copy-paste interactions can communicate
with other applications, web or otherwise, and are not tied to any origin. The API supports multiple
data entries with different behaviors based on where data is transferred to. Your
web application can send and receive the transferred data when listening to incoming events.&lt;/p&gt;
&lt;p&gt;This capability can change the way we think about sharing and interoperability in web
applications on desktop. Transferring data between applications doesn&#39;t need to rely on
tightly coupled integrations anymore. Instead you can give users full control to transfer
data to wherever they like.&lt;/p&gt;
&lt;figure&gt;
  &lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;EYMgUhn_Zdo&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
  &lt;figcaption&gt;An example of interactions that are possible with the DataTransfer API. (Video does not include sound.)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;transferring-data&quot;&gt;Transferring data &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/datatransfer/#transferring-data&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To get started, you&#39;ll need to implement drag-drop or copy-paste. The examples
below show drag-drop interactions, but the process for copy-paste is similar. If
you are unfamiliar with the Drag and Drop API, there&#39;s a great article
&lt;a href=&quot;https://web.dev/drag-and-drop/&quot;&gt;explaining HTML5 Drag and Drop&lt;/a&gt;, which explains the ins and outs.&lt;/p&gt;
&lt;p&gt;By providing &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/MIME_types&quot; rel=&quot;noopener&quot;&gt;MIME-type&lt;/a&gt; keyed data,
you are able to freely interact with external applications.
Most WYSIWYG editors, text editors, and browsers respond to the &amp;quot;primitive&amp;quot; mime-types used in the
example below.&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;&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;#dragSource&#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 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;dragstart&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;text/plain&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Foo bar&#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;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;text/html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&amp;lt;h1&gt;Foo bar&amp;lt;/h1&gt;&#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;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;text/uri-list&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://example.com&#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;Notice the &lt;code&gt;event.dataTransfer&lt;/code&gt; property. This returns an instance of
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/DataTransfer&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;DataTransfer&lt;/code&gt;&lt;/a&gt;. As
you&#39;ll see, this object is sometimes returned by properties with other names.&lt;/p&gt;
&lt;p&gt;Receiving the data transfer works almost the same as providing it. Listen to the receiving events
(&lt;code&gt;drop&lt;/code&gt;, or &lt;code&gt;paste&lt;/code&gt;) and read the keys. When dragging over an element, the browser only has access
to the &lt;code&gt;type&lt;/code&gt; keys of the data. The data itself can only be accessed after a drop.&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;&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;#dropTarget&#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 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;dragover&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;types&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;// Without this, the drop event won&#39;t fire.&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&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;#dropTarget&#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 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;drop&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;// Log all the transferred data items to the console.&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;let&lt;/span&gt; type &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;types&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 punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;data&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;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&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 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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Three MIME-types are widely supported across applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;text/html&lt;/code&gt;:&lt;/strong&gt; Renders the HTML payload in &lt;code&gt;contentEditable&lt;/code&gt; elements and rich
text (WYSIWYG) editors like Google Docs, Microsoft Word, and others.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;text/plain:&lt;/code&gt;&lt;/strong&gt; Sets the value of input elements, content of code editors, and the fallback
from &lt;code&gt;text/html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;text/uri-list&lt;/code&gt;:&lt;/strong&gt; Navigates to the URL when dropping on the URL bar or browser page. A URL
shortcut will be created when dropping on a directory or the desktop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The widespread adoption of &lt;code&gt;text/html&lt;/code&gt; by WYSIWYG editors makes it very useful. As in HTML
documents, you can embed resources by using
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs&quot; rel=&quot;noopener&quot;&gt;Data URLs&lt;/a&gt; or publicly
accessible URLs. This works well with exporting visuals (for example from a canvas) to editors like
Google Docs.&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;const&lt;/span&gt; redPixel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;data:image/gif;base64,R0lGODdhAQABAPAAAP8AAAAAACwAAAAAAQABAAACAkQBADs=&#39;&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;const&lt;/span&gt; html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&amp;lt;img src=&quot;&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; redPixel &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&quot; width=&quot;100&quot; height=&quot;100&quot; alt=&quot;&quot; /&gt;&#39;&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;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;text/html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; html&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;transfer-using-copy-and-paste&quot;&gt;Transfer using copy and paste &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/datatransfer/#transfer-using-copy-and-paste&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Using the DataTransfer API with copy-paste interactions is shown below. Notice that
the &lt;code&gt;DataTransfer&lt;/code&gt; object is returned by a property called &lt;code&gt;clipboardData&lt;/code&gt; for clipboard events.&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;// Listen to copy-paste events on the document.&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;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;copy&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; copySource &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;#copySource&#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;// Only copy when the `activeElement` (i.e., focused element) is,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// or is within, the `copySource` 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;copySource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeElement&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;    event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboardData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;text/plain&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Foo bar&#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;    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;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;br /&gt;document&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;paste&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; pasteTarget &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;#pasteTarget&#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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pasteTarget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeElement&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;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboardData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;text/plain&#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;    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;data&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;custom-data-formats&quot;&gt;Custom data formats &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/datatransfer/#custom-data-formats&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You are not limited to the primitive MIME types, but can use any key to identify the transferred
data. This can be useful for cross-browser interactions within your application. As shown below, you
can transfer more complex data using the &lt;code&gt;JSON.stringify()&lt;/code&gt; and &lt;code&gt;JSON.parse()&lt;/code&gt; functions.&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;&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;#dragSource&#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 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;dragstart&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; data &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 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 string&quot;&gt;&#39;bar&#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;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my-custom-type&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&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;#dropTarget&#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 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;dragover&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;// Only allow dropping when our custom data is available.&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;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;types&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my-custom-type&#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 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;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;br /&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;#dropTarget&#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 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;drop&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;types&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my-custom-type&#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 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 keyword&quot;&gt;const&lt;/span&gt; dataString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;my-custom-type&#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;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dataString&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;data&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;connecting-the-web&quot;&gt;Connecting the web &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/datatransfer/#connecting-the-web&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While custom formats are great for communication between applications you have in your control, it
also limits the user when transferring data to applications that aren&#39;t using your format. If you want to
connect with third-party applications across the web, you need a universal data format.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://json-ld.org/&quot; rel=&quot;noopener&quot;&gt;JSON-LD&lt;/a&gt; (Linked Data) standard is a great candidate for this. It is
lightweight and easy to read from and write to in JavaScript. &lt;a href=&quot;https://schema.org/&quot; rel=&quot;noopener&quot;&gt;Schema.org&lt;/a&gt; contains many
predefined types that can be used, and custom schema definitions are an option as well.&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;const&lt;/span&gt; data &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 string-property property&quot;&gt;&#39;@context&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://schema.org&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string-property property&quot;&gt;&#39;@type&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ImageObject&#39;&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;contentLocation&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Venice, Italy&#39;&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;contentUrl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;venice.jpg&#39;&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;datePublished&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;2010-08-08&#39;&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;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;I took this picture during our honey moon.&#39;&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;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Canal in Venice&#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;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;application/ld+json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&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;When using the Schema.org types, you can start with the generic &lt;a href=&quot;https://schema.org/Thing&quot; rel=&quot;noopener&quot;&gt;Thing&lt;/a&gt; type,
or use something closer to your use case like &lt;a href=&quot;https://schema.org/Event&quot; rel=&quot;noopener&quot;&gt;Event&lt;/a&gt;, &lt;a href=&quot;https://schema.org/Person&quot; rel=&quot;noopener&quot;&gt;Person&lt;/a&gt;,
&lt;a href=&quot;https://schema.org/MediaObject&quot; rel=&quot;noopener&quot;&gt;MediaObject&lt;/a&gt;, &lt;a href=&quot;https://schema.org/Place&quot; rel=&quot;noopener&quot;&gt;Place&lt;/a&gt;, or even highly-specific types like
&lt;a href=&quot;https://schema.org/MedicalEntity&quot; rel=&quot;noopener&quot;&gt;MedicalEntity&lt;/a&gt; if need be. When you use TypeScript, you can use the
interface definitions from the &lt;a href=&quot;https://github.com/google/schema-dts&quot; rel=&quot;noopener&quot;&gt;schema-dts&lt;/a&gt; type definitions.&lt;/p&gt;
&lt;p&gt;By transmitting and receiving JSON-LD data, you will support a more connected and open web. With
applications speaking the same language, you can create deep integrations with external
applications. There&#39;s no need for complicated API integrations; all the information that&#39;s needed is
included in the transferred data.&lt;/p&gt;
&lt;p&gt;Think of all the possibilities for transferring data between any (web) application with no
restrictions: sharing events from a calendar to your favorite ToDo app, attaching virtual files to
emails, sharing contacts. That would be great, right? This starts with you! 🙌&lt;/p&gt;
&lt;h2 id=&quot;concerns&quot;&gt;Concerns &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/datatransfer/#concerns&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While the DataTransfer API is available today, there are some things to be aware of before integrating.&lt;/p&gt;
&lt;h3 id=&quot;browser-compatibility&quot;&gt;Browser compatibility &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/datatransfer/#browser-compatibility&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Desktop browsers all have great support for the technique described above, while mobile devices do
not. The technique has been tested on all major browsers (Chrome, Edge, Firefox, Safari) and
operating systems (Android, ChromeOS, iOS, macOS, Ubuntu Linux, and Windows), but unfortunately Android
and iOS didn&#39;t pass the test. While browsers continue to develop, for now the technique is limited
to desktop browsers only.&lt;/p&gt;
&lt;h3 id=&quot;discoverability&quot;&gt;Discoverability &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/datatransfer/#discoverability&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Drag-drop and copy-paste are system-level interactions when working on a desktop computer, with
roots back to the first GUIs more than 40 years ago. Think about how many times you have
used these interactions for organizing files. This isn&#39;t yet very common on the web.&lt;/p&gt;
&lt;p&gt;You will need to educate users about this new interaction, and come up with UX patterns to make this
recognizable, especially for people whose experience with computers so far has been confined to mobile devices.&lt;/p&gt;
&lt;h3 id=&quot;accessibility&quot;&gt;Accessibility &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/datatransfer/#accessibility&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Drag-drop is not a very accessible interaction, but the DataTransfer API works with copy-paste, too.
Make sure you listen to copy-paste events. It doesn&#39;t take much extra work, and your users
will be grateful to you for adding it.&lt;/p&gt;
&lt;h3 id=&quot;security-and-privacy&quot;&gt;Security and privacy &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/datatransfer/#security-and-privacy&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are some security and privacy considerations you should be aware of when using the technique.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clipboard data is available to other applications on the user&#39;s device.&lt;/li&gt;
&lt;li&gt;Web applications you are dragging over have access to the type keys, not the data. The data only
becomes available on drop or paste.&lt;/li&gt;
&lt;li&gt;The received data should be treated like any other user input; sanitize and validate before using.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;getting-started-with-the-transmat-helper-library&quot;&gt;Getting started with the Transmat helper library &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/datatransfer/#getting-started-with-the-transmat-helper-library&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Are you excited about using the DataTransfer API in your application? Consider taking a look at the
&lt;a href=&quot;https://google.github.io/transmat&quot; rel=&quot;noopener&quot;&gt;Transmat library on GitHub&lt;/a&gt;. This open-source library aligns browser
differences, provides JSON-LD utilities, contains an observer to respond to transfer events for
highlighting drop-areas, and lets you integrate the data transfer operations among existing drag and drop
implementations.&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;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Transmat&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; TransmatObserver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; addListeners &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;transmat&#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;// Send data on drag/copy.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;addListeners&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;myElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;transmit&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; transmat &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;Transmat&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;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  transmat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setData&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 string-property property&quot;&gt;&#39;text/plain&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Foobar&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string-property property&quot;&gt;&#39;application/json&#39;&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 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 string&quot;&gt;&#39;bar&#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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Receive data on drop/paste.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;addListeners&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;myElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;receive&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; transmat &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;Transmat&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;&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;transmat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;application/json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; transmat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;accept&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 keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transmat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;application/json&#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 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;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Observe transfer events and highlight drop areas.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; obs &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;TransmatObserver&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 parameter&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; entries&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;const&lt;/span&gt; transmat &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;Transmat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;event&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;transmat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasMimeType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;application/json&#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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;drag-over&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isTarget&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;      entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;classList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toggle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;drag-active&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isActive&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;obs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;myElement&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;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/datatransfer/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hero image by &lt;a href=&quot;https://unsplash.com/@ertelier&quot; rel=&quot;noopener&quot;&gt;Luba Ertel&lt;/a&gt; on
&lt;a href=&quot;https://unsplash.com/photos/WlL8aHeMcVM&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Jorik Tangelder</name>
    </author><author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Mainline Menswear implements PWA and sees a 55% conversion rate uplift</title>
    <link href="https://web.dev/mainline-mensware/"/>
    <updated>2021-04-20T00:00:00Z</updated>
    <id>https://web.dev/mainline-mensware/</id>
    <content type="html" mode="escaped">&lt;p&gt;Mainline is an online clothing retailer that offers the biggest designer brand names in fashion. The
UK-based company entrusts its team of in-house experts, blended strategically with key partners, to
provide a frictionless shopping experience for all. With market presence in over 100 countries via
seven custom-built territorial websites and an app, Mainline will continue to ensure the ecommerce
offering is rivalling the competition.&lt;/p&gt;
&lt;h2 id=&quot;challenge&quot;&gt;Challenge &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#challenge&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mainline Menswear&#39;s goal was to complement the current mobile optimized website with progressive
features that would adhere to their &#39;mobile first&#39; vision, focusing on mobile-friendly design and
functionality with a growing smartphone market in mind.&lt;/p&gt;
&lt;h2 id=&quot;solution&quot;&gt;Solution &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#solution&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The objective was to build and launch a PWA that complemented the original mobile friendly version
of the Mainline Menswear website, and then compare the stats to their hybrid mobile app,
which is currently available on Android and iOS.&lt;/p&gt;
&lt;p&gt;Once the app launched and was being used by a small section of Mainline Menswear users, they were able to
determine the difference in key stats between PWA, app, and Web.&lt;/p&gt;
&lt;p&gt;The approach Mainline took when converting their website to a PWA was to make sure that
the framework they selected for their website (Nuxt.js, utilizing Vue.js) would be future-proof
and enable them to take advantage of fast moving web technology.&lt;/p&gt;
&lt;h2 id=&quot;results&quot;&gt;Results &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#results&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;stats&quot;&gt;
  &lt;div class=&quot;stats__item&quot;&gt;
    &lt;p class=&quot;stats__figure&quot;&gt;139&lt;sub&gt;%&lt;/sub&gt;&lt;/p&gt;
    &lt;p&gt;More pages per session in PWA vs. web.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;stats__item&quot;&gt;
    &lt;p class=&quot;stats__figure&quot;&gt;161&lt;sub&gt;%&lt;/sub&gt;&lt;/p&gt;
    &lt;p&gt;Longer session durations in PWA vs. web.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;stats__item&quot;&gt;
    &lt;p class=&quot;stats__figure&quot;&gt;10&lt;sub&gt;%&lt;/sub&gt;&lt;/p&gt;
    &lt;p&gt;Lower bounce rate in PWA vs. web&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;stats&quot;&gt;
  &lt;div class=&quot;stats__item&quot;&gt;
    &lt;p class=&quot;stats__figure&quot;&gt;12.5&lt;sub&gt;%&lt;/sub&gt;&lt;/p&gt;
    &lt;p&gt;Higher average order value in PWA vs. web&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;stats__item&quot;&gt;
    &lt;p class=&quot;stats__figure&quot;&gt;55&lt;sub&gt;%&lt;/sub&gt;&lt;/p&gt;
    &lt;p&gt;Higher conversion rate in PWA vs. web.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;stats__item&quot;&gt;
    &lt;p class=&quot;stats__figure&quot;&gt;243&lt;sub&gt;%&lt;/sub&gt;&lt;/p&gt;
    &lt;p&gt;Higher revenue per session in PWA vs. web.&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;technical-deep-dive&quot;&gt;Technical deep dive &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#technical-deep-dive&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mainlinemenswear.co.uk/&quot; rel=&quot;noopener&quot;&gt;Mainline Menswear&lt;/a&gt; is using the
&lt;a href=&quot;https://nuxtjs.org/&quot; rel=&quot;noopener&quot;&gt;Nuxt.js framework&lt;/a&gt; to bundle and render their site, which is a single page
application (SPA).&lt;/p&gt;
&lt;h3 id=&quot;generating-a-service-worker-file&quot;&gt;Generating a service worker file &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#generating-a-service-worker-file&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For generating the service worker, Mainline Menswear added configuration through a custom
implementation of the &lt;a href=&quot;https://pwa.nuxtjs.org/workbox&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;nuxt/pwa&lt;/code&gt; Workbox module&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The reason they forked the &lt;code&gt;nuxt/pwa&lt;/code&gt; module was to allow the team to add more customizations to the
service worker file that they weren&#39;t able to or had issues with when using the standard version.
One such optimization was around the &lt;a href=&quot;https://web.dev/mainline-mensware/#providing-offline-functionality&quot;&gt;offline functionality&lt;/a&gt; of
the site like, for example, serving a default offline page and gathering analytics while offline.&lt;/p&gt;
&lt;h3 id=&quot;anatomy-of-the-web-app-manifest&quot;&gt;Anatomy of the web app manifest &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#anatomy-of-the-web-app-manifest&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The team generated a manifest with icons for different mobile app icon sizes and other web app
details like &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;theme_color&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Mainline Menswear&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;&quot;short_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;MMW&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;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Shop mens designer clothes with Mainline Menswear. Famous brands including Hugo Boss, Adidas, and Emporio Armani.&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;&quot;icons&quot;&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/_nuxt/icons/icon_512.c2336e.png&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;&quot;sizes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;512x512&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;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/png&quot;&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 property&quot;&gt;&quot;theme_color&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#107cbb&quot;&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 app, once installed, can be launched from the home screen without the browser getting in the
way. This is achieved by adding the &lt;code&gt;display&lt;/code&gt; parameter in the web app manifest file:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;display&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;standalone&quot;&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;Last but not the least, the company is now able to easily track how many users are visiting their
web app from the home screen by simply appending a &lt;code&gt;utm_source&lt;/code&gt; parameter in the &lt;code&gt;start_url&lt;/code&gt; field of
the manifest:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;start_url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/?utm_source=pwa&quot;&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;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; See &lt;a href=&quot;https://web.dev/add-manifest/&quot;&gt;Add a web app manifest&lt;/a&gt; for a more in-depth explanation of all the web app manifest fields. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;runtime-caching-for-faster-navigations&quot;&gt;Runtime caching for faster navigations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#runtime-caching-for-faster-navigations&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Caching for web apps is a must for page speed optimization and for providing a better user
experience for returning users.&lt;/p&gt;
&lt;p&gt;For caching on the web, there are quite a few
&lt;a href=&quot;https://dev.to/jonchen/service-worker-caching-and-http-caching-p82&quot; rel=&quot;noopener&quot;&gt;different approaches&lt;/a&gt;. The team
is using a mix of the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Caching&quot; rel=&quot;noopener&quot;&gt;HTTP cache&lt;/a&gt; and
the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Cache&quot; rel=&quot;noopener&quot;&gt;Cache API&lt;/a&gt; for caching assets on the
client side.&lt;/p&gt;
&lt;p&gt;The Cache API gives Mainline Menswear finer control over the cached assets, allowing them to apply
complex strategies to each file type. While all this sounds complicated and hard to set up and
maintain, &lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox&lt;/a&gt; provides them with an easy
way of declaring such complex strategies and eases the pain of maintenance.&lt;/p&gt;
&lt;h4 id=&quot;caching-css-and-js&quot;&gt;Caching CSS and JS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#caching-css-and-js&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;For CSS and JS files, the team chose to cache them and serve them over the cache using the
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/reference/workbox-strategies/#type-StaleWhileRevalidate&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;StaleWhileRevalidate&lt;/code&gt;&lt;/a&gt;
Workbox strategy. This strategy allows them to serve all Nuxt CSS and JS files fast,
which significantly increases their site&#39;s performance.
At the same time, the files are being updated in the background to the latest version for the next visit:&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;/* sw.js */&lt;/span&gt;&lt;br /&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&lt;span class=&quot;token escape&quot;&gt;\/&lt;/span&gt;_nuxt&lt;span class=&quot;token escape&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;token char-set class-name&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token quantifier number&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token group punctuation&quot;&gt;(?:&lt;/span&gt;js&lt;span class=&quot;token alternation keyword&quot;&gt;|&lt;/span&gt;css&lt;span class=&quot;token group punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token anchor function&quot;&gt;$&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StaleWhileRevalidate&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;cacheName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;css_js&#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;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&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;&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;h4 id=&quot;caching-google-fonts&quot;&gt;Caching Google fonts &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#caching-google-fonts&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The strategy for caching Google Fonts depends on two file types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The stylesheet that contains the &lt;code&gt;@font-face&lt;/code&gt; declarations.&lt;/li&gt;
&lt;li&gt;The underlying font files (requested within the stylesheet mentioned above).&lt;/li&gt;
&lt;/ul&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;// Cache the Google Fonts stylesheets with a stale-while-revalidate strategy.&lt;/span&gt;&lt;br /&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;https:&lt;span class=&quot;token escape&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;token escape&quot;&gt;\/&lt;/span&gt;fonts&lt;span class=&quot;token special-escape escape&quot;&gt;\.&lt;/span&gt;googleapis&lt;span class=&quot;token special-escape escape&quot;&gt;\.&lt;/span&gt;com&lt;span class=&quot;token escape&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;token quantifier number&quot;&gt;*&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StaleWhileRevalidate&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;cacheName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;google_fonts_stylesheets&#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;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&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;&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;// Cache the underlying font files with a cache-first strategy for 1 year.&lt;/span&gt;&lt;br /&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;https:&lt;span class=&quot;token escape&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;token escape&quot;&gt;\/&lt;/span&gt;fonts&lt;span class=&quot;token special-escape escape&quot;&gt;\.&lt;/span&gt;gstatic&lt;span class=&quot;token special-escape escape&quot;&gt;\.&lt;/span&gt;com&lt;span class=&quot;token escape&quot;&gt;\/&lt;/span&gt;&lt;span class=&quot;token quantifier number&quot;&gt;*&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CacheFirst&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;cacheName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;google_fonts_webfonts&#39;&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;plugins&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cacheableResponse&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CacheableResponsePlugin&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;statuses&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&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expiration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ExpirationPlugin&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;maxAgeSeconds&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;365&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1 year&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;maxEntries&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&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;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 string&quot;&gt;&#39;GET&#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;/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; A full example of the common Google Fonts strategy can be found in the &lt;a href=&quot;https://developer.chrome.com/docs/workbox/#use-cases-and-recipes&quot;&gt;Workbox Docs&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;caching-images&quot;&gt;Caching images &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#caching-images&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;For images, Mainline Menswear decided to go with two strategies. The first strategy applies
to all images coming from their CDN, which are usually product images. Their pages are image-heavy so
they are conscious of not taking too much of their users&#39; device storage. So through Workbox, they
added a strategy that is &lt;strong&gt;caching images coming only from their CDN&lt;/strong&gt; with a &lt;strong&gt;maximum
of 60 images&lt;/strong&gt; using the
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/reference/workbox-expiration/#type-ExpirationPlugin&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ExpirationPlugin&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The 61st (newest) image requested, replaces the 1st (oldest) image so that no more than 60 product
images are cached at any point in time.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerRoute&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 parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request &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;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;br /&gt;    url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;https://mainline-menswear-res.cloudinary.com&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;br /&gt;    request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;destination &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image&#39;&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;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StaleWhileRevalidate&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;cacheName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;product_images&#39;&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;plugins&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expiration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ExpirationPlugin&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;// Only cache 60 images.&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;maxEntries&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&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;purgeOnQuotaError&lt;/span&gt;&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 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;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;p&gt;The second image strategy handles the rest of the images being requested by the origin.
These images tend to be very few and small across the whole origin, but to be on the safe side,
the number of these cached images is also limited to 60.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&lt;span class=&quot;token special-escape escape&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;token group punctuation&quot;&gt;(?:&lt;/span&gt;png&lt;span class=&quot;token alternation keyword&quot;&gt;|&lt;/span&gt;gif&lt;span class=&quot;token alternation keyword&quot;&gt;|&lt;/span&gt;jpg&lt;span class=&quot;token alternation keyword&quot;&gt;|&lt;/span&gt;jpeg&lt;span class=&quot;token alternation keyword&quot;&gt;|&lt;/span&gt;svg&lt;span class=&quot;token alternation keyword&quot;&gt;|&lt;/span&gt;webp&lt;span class=&quot;token group punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token anchor function&quot;&gt;$&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StaleWhileRevalidate&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;cacheName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;images&#39;&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;plugins&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expiration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ExpirationPlugin&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;// Only cache 60 images.&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;maxEntries&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&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;purgeOnQuotaError&lt;/span&gt;&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 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;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-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Objective&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Even though the caching strategy is exactly the same as the previous one, by splitting images into two caches (&lt;code&gt;product_images&lt;/code&gt; and &lt;code&gt;images&lt;/code&gt;), it allows for more flexible updates to the strategies or caches. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;providing-offline-functionality&quot;&gt;Providing offline functionality &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#providing-offline-functionality&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The offline page is precached right after the service worker is installed and activated. They do
this by creating a list of all offline dependencies: the offline HTML file and an offline SVG icon.&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;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OFFLINE_HTML&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/offline/offline.html&#39;&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;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PRECACHE&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 punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OFFLINE_HTML&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;revision&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;70f044fda3e9647a98f084763ae2c32a&#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 literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/offline/offline.svg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;revision&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;efe016c546d7ba9f20aefc0afa9fc74a&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The precache list is then fed into Workbox which takes care of all the heavy lifting of adding the
URLs to the cache, checking for any revision mismatch, updating, and serving the
precached files with a &lt;code&gt;CacheFirst&lt;/code&gt; strategy.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;precaching&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;precacheAndRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PRECACHE&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;h4 id=&quot;handling-offline-navigations&quot;&gt;Handling offline navigations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#handling-offline-navigations&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Once the service worker activates and the offline page is precached, it is then used to &lt;strong&gt;respond to
offline navigation requests by the user&lt;/strong&gt;. While Mainline Menswear&#39;s web app is an SPA, the offline
page shows only after the page reloads, the user closes and reopens the browser tab, or when the web
app is launched from the home screen while offline.&lt;/p&gt;
&lt;p&gt;To achieve this, Mainline Menswear provided a fallback to failed
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/reference/workbox-routing/#type-NavigationRoute&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;NavigationRoute&lt;/code&gt;&lt;/a&gt;
requests with the precached offline page:&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;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; htmlHandler &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;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;strategies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NetworkOnly&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; navigationRoute &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;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NavigationRoute&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 parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; event &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;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;// A NavigationRoute matches navigation requests in the browser, i.e. requests for HTML&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; htmlHandler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handle&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; event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request &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;catch&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;=&gt;&lt;/span&gt; caches&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;&lt;span class=&quot;token constant&quot;&gt;OFFLINE_HTML&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;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;ignoreSearch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&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;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;workbox&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;routing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;navigationRoute&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;figure data-size=&quot;full&quot;&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; width=&quot;300&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/8WbTDNrhLsU0El80frMBGE4eMCD3/eJgApjFLpSRFMcMyC4e0.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;Offline page example as seen on www.mainlinemenswear.co.uk.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;reporting-successful-installs&quot;&gt;Reporting successful installs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#reporting-successful-installs&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Apart from the home screen launch tracking (with &lt;code&gt;&amp;quot;start_url&amp;quot;: &amp;quot;/?utm_source=pwa&amp;quot;&lt;/code&gt; in the web
application manifest), the web app also reports successful app installs by listening to the
&lt;code&gt;appinstalled&lt;/code&gt; event on &lt;code&gt;window&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;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;appinstalled&#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 parameter&quot;&gt;evt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;ga&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;send&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;event&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Install&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Success&#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;blockquote&gt;
   Adding PWA capabilities to your website will
  further enhance your customers experience of shopping with you, and will be quicker to market than a
  [platform-specific] app. 
  &lt;cite&gt;Andy Hoyle, Head of Development&lt;/cite&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mainline-mensware/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To learn more about progressive web apps and how to build them, head to the
&lt;a href=&quot;https://web.dev/progressive-web-apps/&quot;&gt;Progressive Web Apps section&lt;/a&gt; on web.dev.&lt;/p&gt;
&lt;p&gt;To read more Progressive Web Apps case studies, browse to the
&lt;a href=&quot;https://web.dev/tags/case-study/&quot;&gt;case studies section&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Harry Theodoulou</name>
    </author><author>
      <name>Natasha Kosoglov</name>
    </author><author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>What are mini apps?</title>
    <link href="https://web.dev/mini-app-about/"/>
    <updated>2021-03-03T00:00:00Z</updated>
    <id>https://web.dev/mini-app-about/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This post is part of an article collection where each article builds upon previous articles. If you just landed here, you may want to start reading from the &lt;a href=&quot;https://web.dev/mini-app-super-apps/&quot;&gt;beginning&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;building-blocks-and-compatibility&quot;&gt;Building blocks and compatibility &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-about/#building-blocks-and-compatibility&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mini apps are small (commonly &lt;a href=&quot;https://www.w3.org/2021/10/MiniApp-Overview-breakout.pdf&quot; rel=&quot;noopener&quot;&gt;2-4 MB&lt;/a&gt;)
apps that require a
&lt;a href=&quot;https://web.dev/mini-app-super-apps/#for-mini-apps-you-need-super-apps&quot;&gt;super app&lt;/a&gt;
to run. What they have in
common, independent of the super app, is that they are built with (&amp;quot;dialects&amp;quot; of) the web
technologies HTML, CSS, and JavaScript. The runtime of a mini app is a
&lt;a href=&quot;https://research.google/pubs/pub46739/&quot; rel=&quot;noopener&quot;&gt;WebView&lt;/a&gt; in the super app, not the underlying operating
system, which makes mini apps cross platform. The same mini app can run
in the same super app, no matter if the super app runs on Android, iOS, or another OS. However, not
all mini apps can run in all super apps, more on this &lt;a href=&quot;https://web.dev/mini-app-standardization/&quot;&gt;later&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;discovery&quot;&gt;Discovery &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-about/#discovery&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mini apps are often discovered &lt;em&gt;ad-hoc&lt;/em&gt; via branded 2D barcodes, which solves an important
offline-to-online challenge, for example, getting from a physical restaurant menu to a payment mini
app, or from a physical e-scooter to a rental mini app. The image below shows an example of such a
branded 2D barcode for
&lt;a href=&quot;https://github.com/wechat-miniprogram/miniprogram-demo&quot; rel=&quot;noopener&quot;&gt;WeChat&#39;s demo mini app&lt;/a&gt;. When the code is
scanned with the WeChat super app, the mini app launches directly.
Other super apps will typically not be able to recognize the barcode.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;WeChat-branded 2d barcode.&quot; decoding=&quot;async&quot; height=&quot;250&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 250px) 250px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SOisfOqKQWr0GZZvUaqn.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SOisfOqKQWr0GZZvUaqn.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SOisfOqKQWr0GZZvUaqn.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SOisfOqKQWr0GZZvUaqn.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SOisfOqKQWr0GZZvUaqn.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SOisfOqKQWr0GZZvUaqn.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SOisfOqKQWr0GZZvUaqn.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SOisfOqKQWr0GZZvUaqn.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SOisfOqKQWr0GZZvUaqn.jpg?auto=format&amp;w=500 500w&quot; width=&quot;250&quot; /&gt;
  &lt;figcaption&gt;
    Scanning this 2d barcode with the WeChat app launches a demo mini app.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Mini apps can also be discovered through regular in-app search in the super app, be shared in chat
messages, or be part of a news item in a news feed. Some super apps have the notion of verified
accounts that can contain mini apps in their profiles. Mini apps can be highlighted when they are
physically geographically close, like the mini app of a business in front of which the user
stands, or virtually close, like when the user gets directions on a map shown in the super
app. Frequently used mini apps are available in an app drawer that in many super apps can be
accessed through a swipe down gesture or through a special section in the super app menu.&lt;/p&gt;
&lt;h2 id=&quot;the-user-experience&quot;&gt;The user experience &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-about/#the-user-experience&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All super apps have more or less the same user interface for mini apps. A themeable top bar with the
mini app&#39;s name, and, in the upper corner of the screen, a close button on the far right preceded by
an action menu that provides access to common features like sharing the app, adding it to a
favorites list or the home screen, reporting abusive apps, providing feedback, and settings. The
screenshot below shows a shopping mini app running in the context of the Alipay super app with the
action menu opened.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Alipay super app running a shopping mini app with highlighted top bar, action menu button, and close button. The action menu is opened.&quot; decoding=&quot;async&quot; height=&quot;649&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PkjzF8AyxDVIAMZmhVrr.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PkjzF8AyxDVIAMZmhVrr.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PkjzF8AyxDVIAMZmhVrr.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PkjzF8AyxDVIAMZmhVrr.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PkjzF8AyxDVIAMZmhVrr.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PkjzF8AyxDVIAMZmhVrr.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PkjzF8AyxDVIAMZmhVrr.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PkjzF8AyxDVIAMZmhVrr.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PkjzF8AyxDVIAMZmhVrr.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PkjzF8AyxDVIAMZmhVrr.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/PkjzF8AyxDVIAMZmhVrr.jpg?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    Opened action menu of a shopping mini app running in the Alipay super app.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;ui-paradigms&quot;&gt;UI paradigms &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-about/#ui-paradigms&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Usually there is a bottom tab bar for the mini app&#39;s main navigation. Most &lt;a href=&quot;https://web.dev/mini-app-components/&quot;&gt;super app providers offer
components&lt;/a&gt; that help developers quickly implement common UI paradigms,
such as carousels, accordions, progress bars, spinners, switches, maps, and so on. This also helps make the
user experience between different mini apps consistent, which is encouraged by
&lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/en/design/&quot; rel=&quot;noopener&quot;&gt;WeChat&#39;s Mini Program Design Guidelines&lt;/a&gt;.
This is similar to what Apple incentivizes with its
&lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/ios/overview/themes/&quot; rel=&quot;noopener&quot;&gt;Apple Human Interface Guidelines&lt;/a&gt;,
and Google with its &lt;a href=&quot;https://developer.android.com/design&quot; rel=&quot;noopener&quot;&gt;Design for Android&lt;/a&gt; recommendations.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Douyin demo mini app showcasing the Douyin slider (carousel) component with toggles for auto-advance, dot indicators, etc.&quot; decoding=&quot;async&quot; height=&quot;617&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nFVCU3HqKERzl7Lops6Q.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nFVCU3HqKERzl7Lops6Q.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nFVCU3HqKERzl7Lops6Q.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nFVCU3HqKERzl7Lops6Q.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nFVCU3HqKERzl7Lops6Q.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nFVCU3HqKERzl7Lops6Q.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nFVCU3HqKERzl7Lops6Q.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nFVCU3HqKERzl7Lops6Q.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nFVCU3HqKERzl7Lops6Q.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nFVCU3HqKERzl7Lops6Q.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nFVCU3HqKERzl7Lops6Q.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    Douyin&#39;s slider (carousel) component with various options.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;serving&quot;&gt;Serving &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-about/#serving&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Rather than being served piece by piece as separate resources, mini apps are served as encrypted
packaged apps, that is, as archives that contain all resources in just one file. Unlike regular web
apps, they are also not served from the particular origin of the mini app creator, but from the
super app provider directly. They can still access APIs from the servers of the mini app creator,
but the core resources (commonly referred to as the app shell), must be served from the super app
provider. Mini apps have to declare the origins they request additional data from.&lt;/p&gt;
&lt;h2 id=&quot;caching,-updates,-and-deep-linking&quot;&gt;Caching, updates, and deep-linking &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-about/#caching,-updates,-and-deep-linking&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mini apps are kept in the cache of the super app, so the next time the user launches a cached mini
app, it loads almost instantly. If there is an update, a new app package is loaded. The version
number can be part of the launch URI (see &lt;a href=&quot;https://web.dev/mini-app-about/#discovery&quot;&gt;Discovery&lt;/a&gt;), so the super app knows early on
if the locally cached version is still current. The launch URI also optionally contains the desired
page of the mini app, so deep-linking into specific pages of mini apps is possible. Via a sitemap,
mini apps can declare which of their pages should be indexable by the super app provider&#39;s mini app
crawler.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;macOS Finder showing a folder containing cached WeChat mini app &amp;#x60;.wxapkg&amp;#x60; files.&quot; decoding=&quot;async&quot; height=&quot;465&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZKLNvNnm3Rr6aBmYbons.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Mini apps are cached as encrypted packaged apps.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;security-and-permissions&quot;&gt;Security and permissions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-about/#security-and-permissions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mini apps are reviewed by the super app provider, which means users perceive them as more secure
than web apps. They need to declare their potentially required permissions beforehand in a manifest
or mini app configuration file, which, for some providers, also requires explanations for why each
permission is needed. Mini apps can of course still lie, but they would have a hard time justifying
why they are, for example,
&lt;a href=&quot;https://twitter.com/search?q=why%20website%20access%20%22motion%20sensors%22%20&amp;amp;src=typed_query&amp;amp;f=live&quot; rel=&quot;noopener&quot;&gt;trying to access motion sensors&lt;/a&gt;
without a reason that is apparent to the user. The incentive to fingerprint the user is notably a
lot lower compared to the web, since the user is typically already logged in to the super app anyway
(see &lt;a href=&quot;https://web.dev/mini-app-about/#identity-payment-social-graph&quot;&gt;Identity, payement, and social graph&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Whenever a mini app performs an operation that requires a special permission, a prompt is shown to
the user that, if enforced by the platform, also includes the usage justification, as stated by the
developer. The screenshot below shows the
&lt;a href=&quot;https://microapp.bytedance.com/docs/zh-CN/mini-app/introduction/plug-in/example/&quot; rel=&quot;noopener&quot;&gt;Douyin demo mini app&lt;/a&gt;
as it asks the user for permission to share their location. In some super apps, there is also an
imperative API that mini apps can leverage to request permissions without immediately using them, or
to only check the status of a permission. This may even include an API to open the central super app
permission settings, which corresponds to
&lt;a href=&quot;about://settings/content/siteDetails?site=https%3A%2F%2Fexample.com%2F&quot; rel=&quot;noopener&quot;&gt;Chrome&#39;s &lt;em&gt;Site Settings&lt;/em&gt;&lt;/a&gt;.
Mini apps also have to declare beforehand the origins of all servers that they potentially will
request data from.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Douyin demo mini app showing a geolocation prompt with two options: &amp;#x27;Not Allowed&amp;#x27; and &amp;#x27;Allowed&amp;#x27;.&quot; decoding=&quot;async&quot; height=&quot;617&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8To8DiUqnP4qqFfpPNqk.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8To8DiUqnP4qqFfpPNqk.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8To8DiUqnP4qqFfpPNqk.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8To8DiUqnP4qqFfpPNqk.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8To8DiUqnP4qqFfpPNqk.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8To8DiUqnP4qqFfpPNqk.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8To8DiUqnP4qqFfpPNqk.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8To8DiUqnP4qqFfpPNqk.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8To8DiUqnP4qqFfpPNqk.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8To8DiUqnP4qqFfpPNqk.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8To8DiUqnP4qqFfpPNqk.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    The Douyin demo mini app asking for the geolocation permission.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;access-to-powerful-features&quot;&gt;Access to powerful features &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-about/#access-to-powerful-features&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The hosting super app offers access to powerful APIs via a JavaScript bridge that gets injected into
the WebView offered by the super app (see
&lt;a href=&quot;https://web.dev/mini-app-about/#building-blocks-and-compatibility&quot;&gt;Building blocks and compatibility&lt;/a&gt;). This JavaScript bridge
provides hooks into the operating system&#39;s APIs. For example, a mini app JavaScript function like
&lt;code&gt;getConnectedWifi()&lt;/code&gt;—the capability of a mini app to obtain the name of the currently active Wi-Fi
network—under the hood is facilitated through Android&#39;s
&lt;a href=&quot;https://developer.android.com/reference/android/net/wifi/WifiManager#getConnectionInfo()&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;getConnectionInfo()&lt;/code&gt;&lt;/a&gt;
API or iOS&#39;
&lt;a href=&quot;https://developer.apple.com/documentation/systemconfiguration/1614126-cncopycurrentnetworkinfo&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;CNCopyCurrentNetworkInfo()&lt;/code&gt;&lt;/a&gt;
API. Other examples of powerful device APIs exposed in common super apps are Bluetooth, NFC,
iBeacon, GPS, system clipboard, orientation sensors, battery information, calendar access, phonebook
access, screen brightness control, file system access, vibration hardware for physical feedback,
camera and microphone access, screen recording and screenshot creation, network status, UDP sockets,
barcode scanning, device memory information, and more.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The WeChat demo mini app showing a slider that controls the screen brightness of the device moved all the way to the maximum.&quot; decoding=&quot;async&quot; height=&quot;649&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HNEKyoLVeq3IUKGXEZEZ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HNEKyoLVeq3IUKGXEZEZ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HNEKyoLVeq3IUKGXEZEZ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HNEKyoLVeq3IUKGXEZEZ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HNEKyoLVeq3IUKGXEZEZ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HNEKyoLVeq3IUKGXEZEZ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HNEKyoLVeq3IUKGXEZEZ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HNEKyoLVeq3IUKGXEZEZ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HNEKyoLVeq3IUKGXEZEZ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HNEKyoLVeq3IUKGXEZEZ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HNEKyoLVeq3IUKGXEZEZ.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    The WeChat demo mini app setting the device&#39;s screen brightness to the maximum.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;access-to-cloud-services&quot;&gt;Access to cloud services &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-about/#access-to-cloud-services&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Many super apps also provide &amp;quot;serverless&amp;quot; access to cloud services of the super app provider that,
apart from raw cloud computing and cloud storage, frequently also include higher-level tasks like
text translation, object detection or content classification in images, speech recognition, or other
Machine Learning tasks. Mini apps can be monetized with ads, which are commonly made available by
super apps providers. Super app platforms usually also provide cloud analytics data, so mini app
developers can better understand how users interact with their apps.&lt;/p&gt;
&lt;h2 id=&quot;identity,-payment,-social-graph&quot;&gt;Identity, payment, social graph &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-about/#identity,-payment,-social-graph&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A very important feature of mini apps is the identity and social graph information that is shared
from the super app. Super apps like Douyin or WeChat started as social networking sites in the
broadest sense, where users have a (sometimes even government-verified) identity, a friend or
follower network, and frequently also payment data stored. For example, a shopping mini app can (or
sometimes even must) process any payments directly through the payment APIs of the super app and,
upon user consent, can obtain user data like their shipping address, phone number, and full name,
all without ever having to force the user to painfully fill out forms. Below, you can see the
Walmart mini app running in WeChat, opened for the very first time, greeting me with a familiar
face.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Walmart mini app showing the author&amp;#x27;s face and name on the &amp;#x27;Me&amp;#x27; tab.&quot; decoding=&quot;async&quot; height=&quot;649&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HzPx6ZSqQWvsUDT04ex2.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HzPx6ZSqQWvsUDT04ex2.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HzPx6ZSqQWvsUDT04ex2.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HzPx6ZSqQWvsUDT04ex2.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HzPx6ZSqQWvsUDT04ex2.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HzPx6ZSqQWvsUDT04ex2.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HzPx6ZSqQWvsUDT04ex2.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HzPx6ZSqQWvsUDT04ex2.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HzPx6ZSqQWvsUDT04ex2.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HzPx6ZSqQWvsUDT04ex2.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/HzPx6ZSqQWvsUDT04ex2.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    The Walmart mini app with a personalized &quot;Me&quot; view on the first visit.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Mini apps can get highly popular by letting people share their achievements like highscores in a
game and challenge their contacts through status updates. The mini app is then only a tap away, so
users can enter into competition without any friction and the mini app thereby grow its reach.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; In the next chapter, you will learn &lt;a href=&quot;https://web.dev/mini-app-what-are-h5-and-quickapp&quot;&gt;what sets mini apps apart from H5 apps and QuickApp&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-about/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://github.com/mihajlija&quot; rel=&quot;noopener&quot;&gt;Milica Mihajlija&lt;/a&gt;,
&lt;a href=&quot;https://github.com/alankent&quot; rel=&quot;noopener&quot;&gt;Alan Kent&lt;/a&gt;,
and Keith Gu.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Other mini app runtime environments</title>
    <link href="https://web.dev/mini-app-alternative-runtime-environments/"/>
    <updated>2021-03-03T00:00:00Z</updated>
    <id>https://web.dev/mini-app-alternative-runtime-environments/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This post is part of an article collection where each article builds upon previous articles. If you just landed here, you may want to start reading from the &lt;a href=&quot;https://web.dev/mini-app-super-apps/&quot;&gt;beginning&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;more-than-just-on-mobile&quot;&gt;More than just on mobile &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-alternative-runtime-environments/#more-than-just-on-mobile&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In countries like China, mini apps have taken the market by storm.
Apart from mobile devices, where mini apps are omnipresent and which are their natural habitat, mini
apps have started to conquer other runtime environments like cars and the classic desktop.&lt;/p&gt;
&lt;h2 id=&quot;mini-apps-in-cars&quot;&gt;Mini apps in cars &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-alternative-runtime-environments/#mini-apps-in-cars&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In July 2020 the German car maker BMW Group
&lt;a href=&quot;https://www.press.bmwgroup.com/china/article/detail/T0313254ZH_CN/%E2%80%9C2020-%E5%AE%9D%E9%A9%AC%E7%A7%91%E6%8A%80%E6%97%A5%E2%80%9D%E5%9C%A8%E7%BA%BF%E5%8F%91%E5%B8%83%E5%A4%9A%E6%AC%BE%E8%BD%A6%E5%86%85%E6%95%B0%E5%AD%97%E4%BA%A7%E5%93%81&quot; rel=&quot;noopener&quot;&gt;announced&lt;/a&gt;
a collaboration with Tencent branded as WeScenario, which,
&lt;a href=&quot;https://www.tencent.com/en-us/articles/2201068.html&quot; rel=&quot;noopener&quot;&gt;according to Tencent&lt;/a&gt;, will be rolled out to &lt;em&gt;&amp;quot;30
leading auto companies in the world, and [bring the WeScenario] ecosystem of social, content and
services to more than 110 mainstream automobile models&amp;quot;&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Dashboard of a Tencent car showing two rows of mini app icons.&quot; decoding=&quot;async&quot; height=&quot;533&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/AX07xQlEHL7MDjPo1U1H.jpg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Landing page of Tencent WeScenario (Source: &lt;a href=&quot;https://www.press.bmwgroup.com/china/article/detail/T0313254ZH_CN/%E2%80%9C2020-%E5%AE%9D%E9%A9%AC%E7%A7%91%E6%8A%80%E6%97%A5%E2%80%9D%E5%9C%A8%E7%BA%BF%E5%8F%91%E5%B8%83%E5%A4%9A%E6%AC%BE%E8%BD%A6%E5%86%85%E6%95%B0%E5%AD%97%E4%BA%A7%E5%93%81&quot;&gt;BMW&lt;/a&gt;).
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;mini-apps-on-the-desktop&quot;&gt;Mini apps on the desktop &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-alternative-runtime-environments/#mini-apps-on-the-desktop&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;mini-apps-in-wechat-desktop&quot;&gt;Mini apps in WeChat Desktop &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-alternative-runtime-environments/#mini-apps-in-wechat-desktop&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Using the WeChat desktop client available for
&lt;a href=&quot;https://dldir1.qq.com/weixin/mac/WeChatMac.dmg&quot; rel=&quot;noopener&quot;&gt;macOS&lt;/a&gt;
and &lt;a href=&quot;https://dldir1.qq.com/weixin/Windows/WeChatSetup.exe&quot; rel=&quot;noopener&quot;&gt;Windows&lt;/a&gt;), it is possible to run WeChat
mini apps on the desktop. (Make sure to &lt;em&gt;not&lt;/em&gt; load the macOS
&lt;a href=&quot;https://itunes.apple.com/cn/app/id836500024&quot; rel=&quot;noopener&quot;&gt;version from the App Store&lt;/a&gt;
if you are doing research and want the full experience, since it is more limited.)&lt;/p&gt;
&lt;p&gt;To test it on macOS, share a mini app from a mobile device with yourself via the &amp;quot;File Transfer&amp;quot;
account. This will result in a message that you can then open on the desktop client. In most cases,
the mini app will then be directly clickable and runnable. In other cases, you have to forward the
chat history to yourself again from a mobile device.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The WeChat macOS desktop client showing a chat with oneself with a shared mini app and a chat history as the two visible messages.&quot; decoding=&quot;async&quot; height=&quot;602&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qLbYBpAoSvbc1Qjc9Ub0.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Sharing a mini app with oneself in the WeChat macOS desktop client.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;On Windows, the workaround to share mini apps with oneself is not necessary, since there is a
dedicated mini apps panel that shows the user&#39;s recently used mini apps and also includes an app
search where new mini apps can be discovered.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The mini app panel in the WeChat Windows client showing the user&amp;#x27;s recently used mini apps.&quot; decoding=&quot;async&quot; height=&quot;531&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NQCoGaAWcbuiO37dCNY5.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The mini app panel in the WeChat Windows client.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The mini app search in the WeChat Windows client showing mini apps listed in various categories like games, business, education, etc.&quot; decoding=&quot;async&quot; height=&quot;576&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/H5nqmnoK9JjLcu8mWDSH.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The mini app search in the WeChat Windows client.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;WeChat mini apps on the desktop naturally integrate with the operating system. On both macOS and
Windows, they get their own entry in the multitasking bar and have their own taskbar icon. While on
macOS, there is an option to be kept in the Dock, the icon disappears the moment the WeChat client
app gets closed. On Windows, mini app icons can be pinned to the taskbar, but cannot be launched. On
macOS, the title of the app is always &amp;quot;WeChat&amp;quot; and not the actual title of the app, whereas the
title is displayed correctly on Windows.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The macOS multitask switcher includes mini apps alongside regular macOS app.&quot; decoding=&quot;async&quot; height=&quot;79&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nrvwtwOWot6eZI40evfp.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The Starbucks app is a mini app and can be multitasked to like any regular macOS app.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Starbucks mini app icon on the macOS Dock with a WeChat title.&quot; decoding=&quot;async&quot; height=&quot;412&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 646px) 646px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fG4cpJgpyXeqvjn4lF3v.png?auto=format&amp;w=1292 1292w&quot; width=&quot;646&quot; /&gt;
  &lt;figcaption&gt;
   Mini apps on macOS have WeChat as their title.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Most mini apps are not optimized for desktop yet and run in a fixed, non-resizable window that
includes the well-known UI affordances and permission prompts as on mobile (see
&lt;a href=&quot;https://web.dev/mini-app-about/#the-user-experience&quot;&gt;The user experience&lt;/a&gt;).&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Starbucks mini app running on macOS asking for the user profile permission which the user can grant via a prompt shown at the bottom.&quot; decoding=&quot;async&quot; height=&quot;484&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uorcciuZIL8sadxTjXbv.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uorcciuZIL8sadxTjXbv.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uorcciuZIL8sadxTjXbv.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uorcciuZIL8sadxTjXbv.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uorcciuZIL8sadxTjXbv.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uorcciuZIL8sadxTjXbv.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uorcciuZIL8sadxTjXbv.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uorcciuZIL8sadxTjXbv.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uorcciuZIL8sadxTjXbv.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uorcciuZIL8sadxTjXbv.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uorcciuZIL8sadxTjXbv.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    The Starbucks mini app running on macOS asking for the user profile permission.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Starbucks mini app running on macOS showing the home screen of the app.&quot; decoding=&quot;async&quot; height=&quot;484&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UGufvRnoaAGm8A3qQG4a.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UGufvRnoaAGm8A3qQG4a.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UGufvRnoaAGm8A3qQG4a.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UGufvRnoaAGm8A3qQG4a.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UGufvRnoaAGm8A3qQG4a.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UGufvRnoaAGm8A3qQG4a.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UGufvRnoaAGm8A3qQG4a.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UGufvRnoaAGm8A3qQG4a.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UGufvRnoaAGm8A3qQG4a.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UGufvRnoaAGm8A3qQG4a.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UGufvRnoaAGm8A3qQG4a.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    The Starbucks mini app running on macOS in a fixed, non-resizable window.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Responsive mini apps that are optimized for the desktop (apart from for mobile) can be displayed in
a wider window that on macOS is currently still fixed, but that on Windows is flexibly resizable.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The WeChat components demo app in a responsive app window that can be resized and that by default is wider than the usual mobile screen.&quot; decoding=&quot;async&quot; height=&quot;620&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cUMqQ75zPeCDNTcCWF5D.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The WeChat components demo app in a responsive app window.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The WeChat components demo app in a narrow window showing three boxes A, B, and C stacked on top of each other.&quot; decoding=&quot;async&quot; height=&quot;614&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rPbojIjBGKbNtDviB0MJ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rPbojIjBGKbNtDviB0MJ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rPbojIjBGKbNtDviB0MJ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rPbojIjBGKbNtDviB0MJ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rPbojIjBGKbNtDviB0MJ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rPbojIjBGKbNtDviB0MJ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rPbojIjBGKbNtDviB0MJ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rPbojIjBGKbNtDviB0MJ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rPbojIjBGKbNtDviB0MJ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rPbojIjBGKbNtDviB0MJ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rPbojIjBGKbNtDviB0MJ.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    The WeChat components demo app in a narrow app window.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The WeChat components demo app in a wide window showing three boxes A, B, and C with A stacked on top of B and C on the side.&quot; decoding=&quot;async&quot; height=&quot;565&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 600px) 600px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xnlacc8Xm1eNMCuajYYL.png?auto=format&amp;w=1200 1200w&quot; width=&quot;600&quot; /&gt;
  &lt;figcaption&gt;
    The WeChat components demo app in a wide app window.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Mini app permission settings on macOS can be changed via the context menu. On Windows, this does not
seem to be possible and the location reported by the demo app appears to be the coarse location that
Windows allows apps to obtain without asking for permission.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The WeChat components demo app running on macOS showing two checkboxes for the location and user info permission.&quot; decoding=&quot;async&quot; height=&quot;384&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 500px) 500px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rHyiTGbMauqbSFWBFlMW.png?auto=format&amp;w=1000 1000w&quot; width=&quot;500&quot; /&gt;
  &lt;figcaption&gt;
    WeChat mini app settings on macOS.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;mini-apps-in-the-360-secure-browser&quot;&gt;Mini apps in the 360 Secure Browser &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-alternative-runtime-environments/#mini-apps-in-the-360-secure-browser&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;360 Secure Browser (360 安全浏览器) is a web browser developed by the company Qihoo. Apart from
&lt;a href=&quot;https://mse.360.cn/&quot; rel=&quot;noopener&quot;&gt;iOS and Android&lt;/a&gt;, the browser is also available for
&lt;a href=&quot;https://browser.360.cn/se/&quot; rel=&quot;noopener&quot;&gt;Windows&lt;/a&gt;, &lt;a href=&quot;https://browser.360.cn/ee/mac/index.html&quot; rel=&quot;noopener&quot;&gt;macOS&lt;/a&gt;, and
&lt;a href=&quot;https://browser.360.cn/se/linux/index.html&quot; rel=&quot;noopener&quot;&gt;Linux&lt;/a&gt;. On Windows, it is capable of running special
&lt;a href=&quot;https://mp.360.cn/#/&quot; rel=&quot;noopener&quot;&gt;360 mini apps&lt;/a&gt;. The
&lt;a href=&quot;https://mp.360.cn/doc/miniprogram/dev/&quot; rel=&quot;noopener&quot;&gt;developer documentation&lt;/a&gt; and the
&lt;a href=&quot;https://mp.360.cn/doc/miniprogram/dev/#/e1487ee88399013ec06eff05007391fc&quot; rel=&quot;noopener&quot;&gt;API&lt;/a&gt; are comparable to
those of other vendors; however, 360 does not offer dedicated DevTools. Instead, developers need to
create their mini apps in an IDE of their own choosing and can then test them in the browser using a
special development mode. Debugging happens through Chrome Dev Tools. A
&lt;a href=&quot;https://mp.360.cn/examples/appdemo.zip&quot; rel=&quot;noopener&quot;&gt;demo app&lt;/a&gt; is available for getting started.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A 360 mini app running in 360 Secure Browser being debugged with Chrome Dev Tools.&quot; decoding=&quot;async&quot; height=&quot;402&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IdHpN4GhVWDZ5gmSZ4Jb.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Debugging a 360 mini app using Chrome Dev Tools.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;360 mini apps can run in fullscreen mode and do appear as separate entries in the multitasking bar.
Via the context menu, a home screen icon can be added that allows for mini apps to be launched from
the desktop.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A 360 video mini app running in fullscreen mode showing various thumbnails of videos to watch.&quot; decoding=&quot;async&quot; height=&quot;402&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M4MPV6TXwsLC6lFn9Jgi.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    360 mini app running in fullscreen mode.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;web-based-mini-apps&quot;&gt;Web-based mini apps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-alternative-runtime-environments/#web-based-mini-apps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are some mini app platforms that are web-based, but that depend on the presence of a special
WebView to unlock their full potential.&lt;/p&gt;
&lt;h3 id=&quot;line&quot;&gt;LINE &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-alternative-runtime-environments/#line&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://line.me/&quot; rel=&quot;noopener&quot;&gt;LINE&lt;/a&gt; is an app for instant communications on electronic devices such as
smartphones, tablet computers, and personal computers. In addition, LINE is a platform providing
various services including digital wallet, news stream, video on demand, and digital comic
distribution. The service is a subsidiary of Korean internet search engine company,
&lt;a href=&quot;http://www.navercorp.com/&quot; rel=&quot;noopener&quot;&gt;Naver Corporation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since LINE &lt;a href=&quot;https://developers.line.biz/en/docs/line-mini-app/quickstart/&quot; rel=&quot;noopener&quot;&gt;mini apps&lt;/a&gt; is
&lt;a href=&quot;https://developers.line.biz/en/docs/line-mini-app/discover/specifications/#supported-platforms-and-versions&quot; rel=&quot;noopener&quot;&gt;essentially just a regular web app&lt;/a&gt;
(see &lt;a href=&quot;https://github.com/line/line-liff-v2-starter&quot; rel=&quot;noopener&quot;&gt;sample app&lt;/a&gt;) that pulls in the
&lt;a href=&quot;https://developers.line.biz/en/docs/liff/developing-liff-apps/#developing-a-liff-app&quot; rel=&quot;noopener&quot;&gt;LINE Front-end Framework&lt;/a&gt;
(LIFF),  it can also be accessed outside of the main LINE app through special
&lt;a href=&quot;https://developers.line.biz/en/docs/line-mini-app/develop/permanent-links/&quot; rel=&quot;noopener&quot;&gt;permanent links&lt;/a&gt;
(&lt;a href=&quot;https://liff.line.me/1653544369-LP5XbPYw&quot; rel=&quot;noopener&quot;&gt;example&lt;/a&gt;). However, not all APIs are available in such
cases. Examples of not available in the browser APIs include the
&lt;a href=&quot;https://developers.line.biz/en/reference/liff/#scan-code&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;liff.scanCode()&lt;/code&gt;&lt;/a&gt; method for reading QR
codes or Bluetooth-related APIs like
&lt;a href=&quot;https://developers.line.biz/en/reference/liff/#bluetooth-get-availability&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;liff.bluetooth.getAvailability()&lt;/code&gt;&lt;/a&gt;.
To get a feel for the platform, you can test the
&lt;a href=&quot;https://liff.line.me/1653544369-LP5XbPYw&quot; rel=&quot;noopener&quot;&gt;LINE Playground app&lt;/a&gt; in the browser and the LINE app if
you have a LINE account.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The LINE Playground demo app running on an iOS device showing &amp;#x60;liff.getOS()&amp;#x60; returning &amp;#x27;ios&amp;#x27;.&quot; decoding=&quot;async&quot; height=&quot;649&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YIlFjXgZhq8ROMrPA1BO.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YIlFjXgZhq8ROMrPA1BO.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YIlFjXgZhq8ROMrPA1BO.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YIlFjXgZhq8ROMrPA1BO.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YIlFjXgZhq8ROMrPA1BO.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YIlFjXgZhq8ROMrPA1BO.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YIlFjXgZhq8ROMrPA1BO.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YIlFjXgZhq8ROMrPA1BO.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YIlFjXgZhq8ROMrPA1BO.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YIlFjXgZhq8ROMrPA1BO.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YIlFjXgZhq8ROMrPA1BO.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
   The LINE Playground demo app running on an iOS device.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The LINE Playground demo app running in the web browser showing &amp;#x60;liff.getOS()&amp;#x60; returning &amp;#x27;web&amp;#x27;.&quot; decoding=&quot;async&quot; height=&quot;510&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/UPwJbVhssGfA4IQ89kEo.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
   The LINE Playground demo app running in the web browser.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;google-spot&quot;&gt;Google Spot &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-alternative-runtime-environments/#google-spot&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://developers.google.com/pay/spot&quot; rel=&quot;noopener&quot;&gt;Google Spot Platform&lt;/a&gt; allows developers to set up a
Spot on &lt;a href=&quot;https://pay.google.com/&quot; rel=&quot;noopener&quot;&gt;Google Pay&lt;/a&gt;—a digital storefront that they can create, brand, and
host however they choose. It is discoverable both online as well as through physical barcodes. Users
can easily share a &amp;quot;Spot&amp;quot; (as the app calls it) on their favorite messaging app or find it on Google Pay. A Spot is built
using HTML and JavaScript, so existing investments into mobile websites or PWAs can be easily
transformed into a Spot by &amp;quot;adding a few lines of JavaScript&amp;quot; according to the announcement post.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Eat.fit mini app running in the Google Pay super app showing the sign-in bottom sheet.&quot; decoding=&quot;async&quot; height=&quot;637&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T4OLZX8PIFjFq3gVzUmo.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T4OLZX8PIFjFq3gVzUmo.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T4OLZX8PIFjFq3gVzUmo.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T4OLZX8PIFjFq3gVzUmo.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T4OLZX8PIFjFq3gVzUmo.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T4OLZX8PIFjFq3gVzUmo.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T4OLZX8PIFjFq3gVzUmo.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T4OLZX8PIFjFq3gVzUmo.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T4OLZX8PIFjFq3gVzUmo.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T4OLZX8PIFjFq3gVzUmo.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T4OLZX8PIFjFq3gVzUmo.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
   The Eat.fit mini app running in the Google Pay super app (Source: &lt;a href=&quot;https://developers.google.com/pay/spot&quot;&gt;Google&lt;/a&gt;).
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;snap-minis&quot;&gt;Snap Minis &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-alternative-runtime-environments/#snap-minis&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://snap.com/&quot; rel=&quot;noopener&quot;&gt;Snap Inc.&lt;/a&gt; is an American camera and social media company most known for its
chat app &lt;a href=&quot;https://www.snapchat.com/&quot; rel=&quot;noopener&quot;&gt;Snapchat&lt;/a&gt;. Snap has announced
&lt;a href=&quot;https://minis.snapchat.com/&quot; rel=&quot;noopener&quot;&gt;Snap Minis&lt;/a&gt;, bite-size utilities made for friends. They are built with
HTML5, so they are easy to develop. Plus, they work for all Snapchatters, on any kind of device,
with no installation required.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Atom Tickets mini app running in Snapchat showing three snapchat users reserving their seats in a movie theater.&quot; decoding=&quot;async&quot; height=&quot;470&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 320px) 320px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vM5d0wK5fCQSV0VyP7HA.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vM5d0wK5fCQSV0VyP7HA.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vM5d0wK5fCQSV0VyP7HA.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vM5d0wK5fCQSV0VyP7HA.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vM5d0wK5fCQSV0VyP7HA.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vM5d0wK5fCQSV0VyP7HA.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vM5d0wK5fCQSV0VyP7HA.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vM5d0wK5fCQSV0VyP7HA.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vM5d0wK5fCQSV0VyP7HA.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vM5d0wK5fCQSV0VyP7HA.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vM5d0wK5fCQSV0VyP7HA.png?auto=format&amp;w=640 640w&quot; width=&quot;320&quot; /&gt;
  &lt;figcaption&gt;
   The Atom Tickets mini app running in Snapchat (Source: &lt;a href=&quot;https://minis.snapchat.com/&quot;&gt;Snap&lt;/a&gt;).
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;vk-mini-apps&quot;&gt;VK Mini Apps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-alternative-runtime-environments/#vk-mini-apps&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Russian social networking platform &lt;a href=&quot;https://vk.com/mini-apps&quot; rel=&quot;noopener&quot;&gt;VK&lt;/a&gt; runs a &lt;a href=&quot;https://vk.com/mini-apps&quot; rel=&quot;noopener&quot;&gt;mini app program&lt;/a&gt;
that allows developers to &lt;a href=&quot;https://dev.vk.com/mini-apps/overview&quot; rel=&quot;noopener&quot;&gt;build&lt;/a&gt; mini apps that tie in deeply with the social network.
VK mini apps work on both VK&#39;s platform-specific mobile apps, as well as on the desktop website.
Apart from several of the brand&#39;s other platforms like &lt;a href=&quot;https://mail.ru/&quot; rel=&quot;noopener&quot;&gt;Mail.ru&lt;/a&gt;, VK mini apps are also integrated in the
&lt;a href=&quot;https://browser.ru/&quot; rel=&quot;noopener&quot;&gt;Atom browser&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Все аптеки mini app running in VK.&quot; decoding=&quot;async&quot; height=&quot;948&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 460px) 460px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/tZJgYpdjurBVfhInqWRo.webp?auto=format&amp;w=920 920w&quot; width=&quot;460&quot; /&gt;
  &lt;figcaption&gt;
    The Все аптеки mini app running in VK (Source: &lt;a href=&quot;https://vk.com/mini-apps&quot;&gt;VK&lt;/a&gt;).
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Read on to see some examples of the &lt;a href=&quot;https://web.dev/mini-app-open-source-projects/&quot;&gt;mini app open source projects&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-alternative-runtime-environments/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://github.com/mihajlija&quot; rel=&quot;noopener&quot;&gt;Milica Mihajlija&lt;/a&gt;,
&lt;a href=&quot;https://github.com/alankent&quot; rel=&quot;noopener&quot;&gt;Alan Kent&lt;/a&gt;,
and Keith Gu.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Concluding thoughts about mini apps from a web developer</title>
    <link href="https://web.dev/mini-app-conclusion/"/>
    <updated>2021-03-03T00:00:00Z</updated>
    <id>https://web.dev/mini-app-conclusion/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This post is part of an article collection where each article builds upon previous articles. If you just landed here, you may want to start reading from the &lt;a href=&quot;https://web.dev/mini-app-super-apps/&quot;&gt;beginning&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;where-does-this-leave-us&quot;&gt;Where does this leave us? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-conclusion/#where-does-this-leave-us&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Writing and researching mini apps has been quite a ride, but one that I do not regret. On the one
hand, the success and the popularity of mini apps have proven their creators right about their approach. On the other
hand, though, this success is geographically concentrated in regions where the few popular super
apps are dominant, at least at the time of writing. What is undoubtedly true is that the ecosystem
is highly fascinating and well worth a look. This collection of articles has provided deep-dives
into many of the aspects that make a difference when using and creating mini apps. From the
&lt;a href=&quot;https://web.dev/mini-app-devtools/&quot;&gt;DevTools&lt;/a&gt; experience to the
&lt;a href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#markup-languages&quot;&gt;mark-up&lt;/a&gt;,
&lt;a href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#styling&quot;&gt;styling&lt;/a&gt;, and
&lt;a href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#scripting&quot;&gt;scripting&lt;/a&gt; approaches, over to the
&lt;a href=&quot;https://web.dev/mini-app-components/&quot;&gt;component model&lt;/a&gt;, and finally to the overall
&lt;a href=&quot;https://web.dev/mini-app-project-structure-lifecycle-and-bundling/&quot;&gt;architecture&lt;/a&gt;; mini apps provide learning and
inspiration opportunities for app developers, and even so for those who purely aim for the web.&lt;/p&gt;
&lt;p&gt;My initial experiments with
&lt;a href=&quot;https://web.dev/mini-app-example-project/&quot;&gt;building a web application the mini app way&lt;/a&gt; were
successful. Future work will show to what extent this model is performant and flexible enough to
cater for the many shapes that web apps can take. My current &lt;em&gt;ad-hoc&lt;/em&gt; approach can be formalized by
packaging up the relevant pieces of code in a dedicated library, &lt;code&gt;mini-app.js&lt;/code&gt; if you will. What is
interesting is that this kind of programming goes back all the way to &lt;code&gt;frameset&lt;/code&gt;. Just that today it
is about applications and not documents.&lt;/p&gt;
&lt;p&gt;I see great potential for improvement with the entire web development experience by taking
inspiration from the various mini apps DevTools. From the easy
&lt;a href=&quot;https://web.dev/mini-app-devtools/#simulator-and-real-device-testing-and-debugging&quot;&gt;(remote) on-device testing feature&lt;/a&gt;
to the packaging and &lt;a href=&quot;https://web.dev/mini-app-project-structure-lifecycle-and-bundling/#the-build-process&quot;&gt;building&lt;/a&gt;
experience; the integration of the &lt;a href=&quot;https://web.dev/mini-app-devtools/#mini-app-ides&quot;&gt;IDE&lt;/a&gt; with the DevTools
environment offers a lot of starting points for making developers&#39; lives easier.&lt;/p&gt;
&lt;h2 id=&quot;closing-thoughts&quot;&gt;Closing thoughts &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-conclusion/#closing-thoughts&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;From a features point of view, the web is becoming more and more powerful
with each release of essentially &lt;em&gt;any&lt;/em&gt; browser. The ever-growing
&lt;a href=&quot;https://developer.chrome.com/blog/fugu-status/&quot; rel=&quot;noopener&quot;&gt;list of capabilities&lt;/a&gt; makes use cases possible on the web that were unthinkable a
mere year ago. At the same time, the need for
&lt;a href=&quot;https://web.dev/mini-app-standardization/&quot;&gt;mini apps standardization&lt;/a&gt; shows that developers are not willing or
able to build the same mini app for each super app. On the horizon maybe there is a desire for an
abstraction layer on the browser level that allows for mini apps to run on the web, while noting that
the web is not immune from fragmentation, especially when looking at different browser vendors and
what they choose to implement and what not. Concluding, I am looking forward to seeing where all
this is headed. Thinking outside of the box and taking input and inspiration from outside of one&#39;s
own bubble can definitely help when building a better future on the web.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Congratulations, you have reached the end of the &lt;a href=&quot;https://web.dev/mini-apps/&quot;&gt;mini apps collection&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-conclusion/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://github.com/mihajlija&quot; rel=&quot;noopener&quot;&gt;Milica Mihajlija&lt;/a&gt;,
&lt;a href=&quot;https://github.com/alankent&quot; rel=&quot;noopener&quot;&gt;Alan Kent&lt;/a&gt;,
and Keith Gu.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Mini app components</title>
    <link href="https://web.dev/mini-app-components/"/>
    <updated>2021-03-03T00:00:00Z</updated>
    <id>https://web.dev/mini-app-components/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This post is part of an article collection where each article builds upon previous articles. If you just landed here, you may want to start reading from the &lt;a href=&quot;https://web.dev/mini-app-super-apps/&quot;&gt;beginning&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;web-components&quot;&gt;Web components &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-components/#web-components&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/Web_Components/&quot; rel=&quot;noopener&quot;&gt;Web components&lt;/a&gt; started with the
promise of letting developers piece them together and build great apps on top of them. Examples of
such atomic components are GitHub&#39;s &lt;a href=&quot;https://github.com/github/time-elements&quot; rel=&quot;noopener&quot;&gt;time-elements&lt;/a&gt;, Stefan
Judis&#39; &lt;a href=&quot;https://github.com/stefanjudis/web-vitals-element&quot; rel=&quot;noopener&quot;&gt;web-vitals-element&lt;/a&gt;, or, shameless plug,
Google&#39;s &lt;a href=&quot;https://github.com/GoogleChromeLabs/dark-mode-toggle/&quot; rel=&quot;noopener&quot;&gt;dark mode toggle&lt;/a&gt;. When it comes to
complete design systems, however, I have observed that people prefer to rely on a coherent set of
components from the same vendor. An incomplete list of examples includes SAP&#39;s
&lt;a href=&quot;https://sap.github.io/ui5-webcomponents/&quot; rel=&quot;noopener&quot;&gt;UI5 Web Components&lt;/a&gt;, the
&lt;a href=&quot;https://www.webcomponents.org/author/PolymerElements&quot; rel=&quot;noopener&quot;&gt;Polymer Elements&lt;/a&gt;,
&lt;a href=&quot;https://www.webcomponents.org/author/vaadin&quot; rel=&quot;noopener&quot;&gt;Vaadin&#39;s elements&lt;/a&gt;, Microsoft&#39;s
&lt;a href=&quot;https://github.com/microsoft/fast&quot; rel=&quot;noopener&quot;&gt;FAST&lt;/a&gt;, the
&lt;a href=&quot;https://github.com/material-components/material-components-web-components&quot; rel=&quot;noopener&quot;&gt;Material Web Components&lt;/a&gt;,
arguably the &lt;a href=&quot;https://amp.dev/documentation/components/&quot; rel=&quot;noopener&quot;&gt;AMP components&lt;/a&gt;, and many more. Due to a
number of factors out of scope for this article, a lot of developers, however, have also flocked to
frameworks like &lt;a href=&quot;https://reactjs.org/&quot; rel=&quot;noopener&quot;&gt;React&lt;/a&gt;, &lt;a href=&quot;https://vuejs.org/&quot; rel=&quot;noopener&quot;&gt;Vue.js&lt;/a&gt;,
&lt;a href=&quot;https://emberjs.com/&quot; rel=&quot;noopener&quot;&gt;Ember.js&lt;/a&gt;, etc. Rather than giving the developer the freedom to choose from
any of these options (or, dependent on your viewpoint, &lt;em&gt;forcing&lt;/em&gt; them to make a technology choice),
super app providers universally supply a set of components that developers must use.&lt;/p&gt;
&lt;h2 id=&quot;components-in-mini-apps&quot;&gt;Components in mini apps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-components/#components-in-mini-apps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can think of these components like any of the component libraries mentioned above. To get an
overview of the available components, you can browse
&lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/en/dev/component/&quot; rel=&quot;noopener&quot;&gt;WeChat&#39;s component library&lt;/a&gt;,
&lt;a href=&quot;https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/component/all&quot; rel=&quot;noopener&quot;&gt;ByteDance&#39;s components&lt;/a&gt;,
&lt;a href=&quot;https://opendocs.alipay.com/mini/component&quot; rel=&quot;noopener&quot;&gt;Alipay&#39;s components&lt;/a&gt;,
&lt;a href=&quot;https://smartprogram.baidu.com/docs/develop/component/component/&quot; rel=&quot;noopener&quot;&gt;Baidu&#39;s&lt;/a&gt;, and
&lt;a href=&quot;https://doc.quickapp.cn/widgets/common-events.html&quot; rel=&quot;noopener&quot;&gt;Quick App components&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Earlier &lt;a href=&quot;https://web.dev/mini-app-devtools/#custom-elements-under-the-hood&quot;&gt;I showed&lt;/a&gt; that while, for example, WeChat&#39;s &lt;code&gt;&amp;lt;image&amp;gt;&lt;/code&gt;
is a web component under the hood, not all of these components are technically web components. Some
components, like &lt;code&gt;&amp;lt;map&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt;, are rendered as
&lt;a href=&quot;https://developers.weixin.qq.com/ebook?action=get_post_info&amp;amp;docid=000caab39b88b06b00863ab085b80a&quot; rel=&quot;noopener&quot;&gt;OS-built-in components&lt;/a&gt;
that get layered over the WebView. For the developer, this implementation detail is not revealed,
they are programmed like any other component.&lt;/p&gt;
&lt;p&gt;As always, the details vary, but the overall programming concepts are the same across all super app
providers. An important concept is data binding, as shown before in
&lt;a href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#markup-languages&quot;&gt;Markup languages&lt;/a&gt;. Generally, components are grouped by function, so finding the
right one for the job is easier. Below is an example from Alipay&#39;s categorization, which is similar
to the component grouping of other vendors.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;View containers
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;view&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;swiper&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scroll-view&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cover-view&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cover-image&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;movable-view&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;movable-area&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Basic content
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;text&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;icon&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;progress&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rich-text&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Form components
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;button&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;form&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;label&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;input&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;textarea&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;radio&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;radio-group&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkbox&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkbox-group&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;switch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;slider&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;picker-view&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;picker&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Navigation
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;navigator&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Media components
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;image&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;video&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Canvas
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;canvas&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Map
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;map&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Open components
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;web-view&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lifestyle&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;contact-button&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Accessibility
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;aria-component&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Below, you can see Alipay&#39;s &lt;a href=&quot;https://opendocs.alipay.com/mini/component/image&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;image&amp;gt;&lt;/code&gt;&lt;/a&gt; used in an
&lt;code&gt;a:for&lt;/code&gt; directive (see &lt;a href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#list-rendering&quot;&gt;List rendering&lt;/a&gt;) that loops over an image data array
provided in &lt;code&gt;index.js&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;/* index.js */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Page&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;data&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 literal-property property&quot;&gt;array&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;scaleToFill&quot;&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;text&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;scaleToFill&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;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;aspectFit&quot;&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;text&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;aspectFit&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;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;src&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://images.example.com/sample.png&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;span class=&quot;token function&quot;&gt;imageError&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;    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;&quot;image&quot;&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;detail&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;errMsg&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;span class=&quot;token function&quot;&gt;onTap&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;    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;&quot;image tap&quot;&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 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;imageLoad&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;    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;&quot;image&quot;&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 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;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;!-- index.axml --&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;view&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;page&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;view&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;page-section&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;a:&lt;/span&gt;for&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;{{array}}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;a:&lt;/span&gt;for-item&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;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;&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;view&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;page-section-demo&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;onTap&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;onTap&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;image&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token attr-name&quot;&gt;class&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;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;mode&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;{{item.mode}}&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;onTap&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;onTap&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;onError&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 javascript language-javascript&quot;&gt;imageError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&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;onLoad&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 javascript language-javascript&quot;&gt;imageLoad&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&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;{{src}}&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;lazy-load&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;true&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;default-source&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;https://images.example.com/loading.png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&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;view&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;view&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;view&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 the data binding of the &lt;code&gt;item.mode&lt;/code&gt; to the &lt;code&gt;mode&lt;/code&gt; attribute, the &lt;code&gt;src&lt;/code&gt; to the &lt;code&gt;src&lt;/code&gt; attribute,
and the three event handlers &lt;code&gt;onTap&lt;/code&gt;, &lt;code&gt;onError&lt;/code&gt;, and &lt;code&gt;onLoad&lt;/code&gt; to the functions of the same name. As
shown &lt;a href=&quot;https://web.dev/mini-app-devtools/#custom-elements-under-the-hood&quot;&gt;before&lt;/a&gt;, the &lt;code&gt;&amp;lt;image&amp;gt;&lt;/code&gt; tag internally gets converted into a
&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with a placeholder of the image&#39;s final dimensions, optional lazy loading, a default source,
etc.&lt;/p&gt;
&lt;p&gt;The available configuration options of the component are all listed in the
&lt;a href=&quot;https://opendocs.alipay.com/mini/component/image&quot; rel=&quot;noopener&quot;&gt;documentation&lt;/a&gt;. An embedded-in-the-docs
&lt;a href=&quot;https://herbox-embed.alipay.com/s/doc-image?chInfo=openhome-doc&amp;amp;theme=light&quot; rel=&quot;noopener&quot;&gt;component preview with simulator&lt;/a&gt;
makes the code immediately tangible.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Alipay component documentation with embedded component preview, showing a code editor with simulator that shows the component rendered on a simulated iPhone 6.&quot; decoding=&quot;async&quot; height=&quot;510&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/t2Y6WWsRhp6LjThxROUo.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Alipay component documentation with embedded component preview.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Alipay component preview running in a separate browser tab showing a code editor with simulator that shows the component rendered on a simulated iPhone 6.&quot; decoding=&quot;async&quot; height=&quot;514&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xu2F51XL9Z0M3Z9n6n2O.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Alipay component preview popped out into its own tab.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Each component also has a QR code that can be scanned with the Alipay app that opens the component
example in a self-contained minimal example.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Alipay&amp;#x27;s &amp;#x60;image&amp;#x60; component previewed on a real device after scanning a QR code in the documentation.&quot; decoding=&quot;async&quot; height=&quot;649&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gaWqLG5GqeqqfbhWz8D1.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gaWqLG5GqeqqfbhWz8D1.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gaWqLG5GqeqqfbhWz8D1.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gaWqLG5GqeqqfbhWz8D1.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gaWqLG5GqeqqfbhWz8D1.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gaWqLG5GqeqqfbhWz8D1.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gaWqLG5GqeqqfbhWz8D1.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gaWqLG5GqeqqfbhWz8D1.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gaWqLG5GqeqqfbhWz8D1.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gaWqLG5GqeqqfbhWz8D1.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gaWqLG5GqeqqfbhWz8D1.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    Preview of the Alipay &lt;code&gt;&amp;lt;image&amp;gt;&lt;/code&gt; component on a real device after following a &lt;a href=&quot;https://qr.alipay.com/s6x01278ucjhjyknjd5ow53&quot;&gt;QR code link&lt;/a&gt; from the docs.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Developers can jump from the documentation straight into Alipay DevTools IDE by leveraging a
proprietary URI scheme &lt;code&gt;antdevtool-tiny://&lt;/code&gt;. This allows the documentation to link directly into a
to-be-imported mini app project, so developers can get started with the component immediately.&lt;/p&gt;
&lt;h2 id=&quot;custom-components&quot;&gt;Custom components &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-components/#custom-components&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Apart from using the vendor-provided components, developers can also create custom components. The
concept exists for
&lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/en/dev/framework/custom-component/&quot; rel=&quot;noopener&quot;&gt;WeChat&lt;/a&gt;,
&lt;a href=&quot;https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/guide/custom-component/custom-component&quot; rel=&quot;noopener&quot;&gt;ByteDance&lt;/a&gt;,
&lt;a href=&quot;https://opendocs.alipay.com/mini/framework/component_configuration&quot; rel=&quot;noopener&quot;&gt;Alipay&lt;/a&gt;, and
&lt;a href=&quot;https://smartprogram.baidu.com/docs/develop/framework/custom-component/&quot; rel=&quot;noopener&quot;&gt;Baidu&lt;/a&gt;, as well as
&lt;a href=&quot;https://doc.quickapp.cn/tutorial/framework/parent-child-component-communication.html#%E7%BB%84%E4%BB%B6%E8%87%AA%E5%AE%9A%E4%B9%89&quot; rel=&quot;noopener&quot;&gt;Quick App&lt;/a&gt;.
For example, a Baidu custom component consists of four files that must reside in the same folder:
&lt;code&gt;custom.swan&lt;/code&gt;, &lt;code&gt;custom.css&lt;/code&gt;, &lt;code&gt;custom.js&lt;/code&gt;, and &lt;code&gt;custom.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The file &lt;code&gt;custom.json&lt;/code&gt; denotes the folder contents as a custom component.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;component&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&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 file &lt;code&gt;custom.swan&lt;/code&gt; contains the markup and &lt;code&gt;custom.css&lt;/code&gt; the CSS.&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;view&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;name&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;bindtap&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;tap&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;{{name}} {{age}}&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;view&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;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;.name&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The file &lt;code&gt;custom.js&lt;/code&gt; contains the logic. The component lifecycle functions are &lt;code&gt;attached()&lt;/code&gt;,
&lt;code&gt;detached()&lt;/code&gt;, &lt;code&gt;created()&lt;/code&gt;, and &lt;code&gt;ready()&lt;/code&gt;. The component can additionally also react on
&lt;a href=&quot;https://web.dev/mini-app-project-structure-lifecycle-and-bundling/#mini-app-lifecycle&quot;&gt;page lifecycle events&lt;/a&gt;, namely &lt;code&gt;show()&lt;/code&gt; and &lt;code&gt;hide()&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 function&quot;&gt;Component&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;properties&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 literal-property property&quot;&gt;name&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 literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; String&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&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 string&quot;&gt;&quot;swan&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;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;data&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 literal-property property&quot;&gt;age&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;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;methods&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;tap&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 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;span class=&quot;token literal-property property&quot;&gt;lifetimes&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;attached&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 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-variable function&quot;&gt;detached&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 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-variable function&quot;&gt;created&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 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-variable function&quot;&gt;ready&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 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;span class=&quot;token literal-property property&quot;&gt;pageLifetimes&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;show&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 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-variable function&quot;&gt;hide&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 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;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 custom component can then be imported in &lt;code&gt;index.json&lt;/code&gt;, the key of the import determines the name
(here: &lt;code&gt;&amp;quot;custom&amp;quot;&lt;/code&gt;) that the custom component can then be used with in &lt;code&gt;index.swan&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;usingComponents&quot;&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 property&quot;&gt;&quot;custom&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/components/custom/custom&quot;&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;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;view&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;custom&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;swanapp&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 tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;custom&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;view&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;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Continue reading to learn about the &lt;a href=&quot;https://web.dev/mini-app-project-structure-lifecycle-and-bundling/&quot;&gt;project structure, lifecycle, and bundling&lt;/a&gt; of mini apps. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-components/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://github.com/mihajlija&quot; rel=&quot;noopener&quot;&gt;Milica Mihajlija&lt;/a&gt;,
&lt;a href=&quot;https://github.com/alankent&quot; rel=&quot;noopener&quot;&gt;Alan Kent&lt;/a&gt;,
and Keith Gu.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Mini app DevTools</title>
    <link href="https://web.dev/mini-app-devtools/"/>
    <updated>2021-03-03T00:00:00Z</updated>
    <id>https://web.dev/mini-app-devtools/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This post is part of an article collection where each article builds upon previous articles. If you just landed here, you may want to start reading from the &lt;a href=&quot;https://web.dev/mini-app-super-apps/&quot;&gt;beginning&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;the-developer-experience&quot;&gt;The developer experience &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-devtools/#the-developer-experience&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that I have covered mini apps &lt;em&gt;per se&lt;/em&gt;, I want to focus on the developer experience for the
various super app platforms. Mini app development on all platforms happens in IDEs that are provided
for free by the super app platforms. While there are more, I want to focus on the four most popular
ones, and a fifth for Quick App for comparison.&lt;/p&gt;
&lt;h2 id=&quot;mini-app-ides&quot;&gt;Mini app IDEs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-devtools/#mini-app-ides&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like the super apps, the majority of the IDEs are available only in Chinese. You actually want to
make sure that you install the Chinese version and not a sometimes available English (or overseas)
version, since it might not be up-to-date. If you are a macOS developer, be aware that not all IDEs
are signed, which means macOS refuses to run the installer. You can, &lt;strong&gt;at your own risk&lt;/strong&gt;, bypass
this as
&lt;a href=&quot;https://support.apple.com/guide/mac-help/open-a-mac-app-from-an-unidentified-developer-mh40616/mac&quot; rel=&quot;noopener&quot;&gt;outlined by Apple help&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html&quot; rel=&quot;noopener&quot;&gt;WeChat DevTools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://render.alipay.com/p/f/fd-jwq8nu2a/pages/home/index.html&quot; rel=&quot;noopener&quot;&gt;Alipay DevTools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://smartprogram.baidu.com/docs/develop/devtools/history/&quot; rel=&quot;noopener&quot;&gt;Baidu DevTools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/developer-instrument/developer-instrument-update-and-download&quot; rel=&quot;noopener&quot;&gt;ByteDance DevTools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.quickapp.cn/docCenter/IDEPublicity&quot; rel=&quot;noopener&quot;&gt;Quick App DevTools&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;mini-app-starter-projects&quot;&gt;Mini app starter projects &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-devtools/#mini-app-starter-projects&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To get started quickly with mini app development, all super app providers offer demo apps
that can be downloaded and tested immediately, and that are sometimes also integrated in the &amp;quot;New
Project&amp;quot; wizards of the various IDEs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/wechat-miniprogram/miniprogram-demo&quot; rel=&quot;noopener&quot;&gt;WeChat demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://opendocs.alipay.com/mini/introduce/demo&quot; rel=&quot;noopener&quot;&gt;Alipay demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://smartprogram.baidu.com/docs/develop/tutorial/demo/&quot; rel=&quot;noopener&quot;&gt;Baidu demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://microapp.bytedance.com/docs/zh-CN/mini-app/introduction/plug-in/example&quot; rel=&quot;noopener&quot;&gt;ByteDance demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/quickappcn/sample&quot; rel=&quot;noopener&quot;&gt;Quick App demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;development-flow&quot;&gt;Development flow &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-devtools/#development-flow&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After launching the IDE and loading or creating a (demo) mini app, the first step is always to log
in. Usually you just need to scan a QR code with the super app (where you are already logged in)
that is generated by the IDE. Very rarely do you have to enter a password. Once you are logged in,
the IDE knows your identity and lets you start programming, debugging, testing, and submitting your
app for review. In the following, you can see screenshots of the five IDEs mentioned in the
paragraph above.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;WeChat DevTools application window showing simulator, code editor, and debugger.&quot; decoding=&quot;async&quot; height=&quot;463&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YvjvlB82SfPqHBl56Rz4.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    WeChat DevTools with simulator, code editor, and debugger.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Alipay DevTools application window showing code editor, simulator, and debugger.&quot; decoding=&quot;async&quot; height=&quot;454&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iLbYZFZ9ec245segsIKk.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Alipay DevTools with code editor, simulator, and debugger.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Baidu DevTools application window showing simulator, code editor, and debugger.&quot; decoding=&quot;async&quot; height=&quot;540&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/svoq0p6GO1PCT0k0bdba.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Baidu DevTools with simulator, code editor, and debugger.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;ByteDance DevTools application window showing simulator, code editor, and debugger.&quot; decoding=&quot;async&quot; height=&quot;561&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/myapOcgYOwsEumeFbL3A.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    ByteDance DevTools with simulator, code editor, and debugger.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Quick App DevTools application window showing code editor, simulator, and debugger.&quot; decoding=&quot;async&quot; height=&quot;485&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/dUsfozY82E2YhtlxNJz9.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Quick App DevTools with code editor, simulator, and debugger.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;As you can see, the fundamental components of all IDEs are very similar. You always have a code
editor based on the &lt;a href=&quot;https://microsoft.github.io/monaco-editor/&quot; rel=&quot;noopener&quot;&gt;Monaco Editor&lt;/a&gt;, the same project
that also powers &lt;a href=&quot;https://github.com/Microsoft/vscode&quot; rel=&quot;noopener&quot;&gt;VS Code&lt;/a&gt;. In all IDEs, there is a debugger
based on the &lt;a href=&quot;https://github.com/ChromeDevTools/devtools-frontend&quot; rel=&quot;noopener&quot;&gt;Chrome DevTools frontend&lt;/a&gt; with
some modifications, more on those later (see &lt;a href=&quot;https://web.dev/mini-app-devtools/#debugger&quot;&gt;Debugger&lt;/a&gt;). The IDEs &lt;em&gt;per se&lt;/em&gt; are
implemented either as &lt;a href=&quot;https://nwjs.io/&quot; rel=&quot;noopener&quot;&gt;NW.js&lt;/a&gt; or as &lt;a href=&quot;https://www.electronjs.org/&quot; rel=&quot;noopener&quot;&gt;Electron&lt;/a&gt; apps,
the simulators in the IDEs are realized as an
&lt;a href=&quot;https://docs.nwjs.io/en/latest/References/webview%20Tag/&quot; rel=&quot;noopener&quot;&gt;NW.js &lt;code&gt;&amp;lt;webview&amp;gt;&lt;/code&gt; tag&lt;/a&gt; or
&lt;a href=&quot;https://www.electronjs.org/docs/api/webview-tag&quot; rel=&quot;noopener&quot;&gt;Electron &lt;code&gt;&amp;lt;webview&amp;gt;&lt;/code&gt; tag&lt;/a&gt;, which in turn are based on
a &lt;a href=&quot;https://www.electronjs.org/docs/api/webview-tag&quot; rel=&quot;noopener&quot;&gt;Chromium &lt;code&gt;&amp;lt;webview&amp;gt;&lt;/code&gt; tag&lt;/a&gt;. If you are interested in
the IDE internals, you can oftentimes simply inspect them with Chrome DevTools with the keyboard
shortcut &lt;kbd&gt;Control&lt;/kbd&gt;+&lt;kbd&gt;Alt&lt;/kbd&gt;+&lt;kbd&gt;I&lt;/kbd&gt; (or
&lt;kbd&gt;Command&lt;/kbd&gt;+&lt;kbd&gt;Option&lt;/kbd&gt;+&lt;kbd&gt;I&lt;/kbd&gt; on Mac).&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Chrome DevTools used to inspect Baidu&amp;#x27;s DevTools showing the simulator&amp;#x27;s webview tag in the Chrome DevTools&amp;#x27; Elements panel.&quot; decoding=&quot;async&quot; height=&quot;504&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yXcGmOhbi3xrcMIDhw1t.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Inspecting Baidu DevTools with Chrome DevTools reveals that the simulator is realized as an Electron &lt;code&gt;&amp;lt;webview&amp;gt;&lt;/code&gt; tag.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;simulator-and-real-device-testing-and-debugging&quot;&gt;Simulator and real device testing and debugging &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-devtools/#simulator-and-real-device-testing-and-debugging&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The simulator is comparable with what you might know from Chrome DevTools&#39;
&lt;a href=&quot;https://developer.chrome.com/docs/devtools/device-mode/&quot; rel=&quot;noopener&quot;&gt;device mode&lt;/a&gt;. You can simulate
different Android and iOS devices, change the scale and device orientation, but also simulate
various network states, memory pressure, a barcode reading event, unexpected termination, and dark
mode.&lt;/p&gt;
&lt;p&gt;While the built-in simulator suffices to get a rough feeling for how the app behaves, on-device
testing, like with regular web apps, is irreplaceable. Testing an in-development mini app is just a
QR code scan away. For example, in ByteDance DevTools, scanning a QR code dynamically generated by the IDE
with a real device leads to a cloud-hosted version of the mini app that can then immediately be
tested on the device. The way this works for ByteDance is that the URL behind the QR code
(&lt;a href=&quot;https://t.zijieimg.com/JMvE5kM/?a=b&quot; rel=&quot;noopener&quot;&gt;example&lt;/a&gt;) redirects to a hosted page
(&lt;a href=&quot;https://s.pstatp.com/toutiao/resource/tma_c_reveal_fe/static/redirect.html?version=v2&amp;amp;app_id=ttb3d2c56f2ce8e78c&amp;amp;scene=0&amp;amp;version_type=preview&amp;amp;token=3605997583095982&amp;amp;start_page=pages%2Fcomponent%2Findex&amp;amp;url=%7B%22id%22%3A%22ttb3d2c56f2ce8e78c%22%2C%22name%22%3A%22%E5%90%8D%E7%A7%B0%E9%87%8D%E7%BD%AEttb3d2c56f2ce8e78c%22%2C%22icon%22%3A%22%22%2C%22url%22%3A%22https%3A%2F%2Fsf1-ttcdn-tos.pstatp.com%2Fobj%2Fdeveloper%2Fapp%2Fttb3d2c56f2ce8e78c%2Fpreview%2F%22%2C%22orientation%22%3A0%2C%22ttid%22%3A%226857810517176942605%22%2C%22state%22%3A1%2C%22type%22%3A1%2C%22tech_type%22%3A1%2C%22version%22%3A%22undefined%22%7D&amp;amp;tech_type=1&amp;amp;bdpsum=281c864&quot; rel=&quot;noopener&quot;&gt;example&lt;/a&gt;),
that contains links with special URI schemes like, for example, &lt;code&gt;snssdk1128://&lt;/code&gt;, to preview the mini
app on the various ByteDance super apps like Douyin or Toutiao
(here is an &lt;a href=&quot;snssdk1128://microapp?version=v2&amp;amp;app_id=ttb3d2c56f2ce8e78c&amp;amp;scene=0&amp;amp;version_type=preview&amp;amp;token=3605997583095982&amp;amp;start_page=pages%2Fcomponent%2Findex&amp;amp;url=%7B%22id%22%3A%22ttb3d2c56f2ce8e78c%22%2C%22name%22%3A%22%E5%90%8D%E7%A7%B0%E9%87%8D%E7%BD%AEttb3d2c56f2ce8e78c%22%2C%22icon%22%3A%22%22%2C%22url%22%3A%22https%3A%2F%2Fsf1-ttcdn-tos.pstatp.com%2Fobj%2Fdeveloper%2Fapp%2Fttb3d2c56f2ce8e78c%2Fpreview%2F%22%2C%22orientation%22%3A0%2C%22ttid%22%3A%226857810517176942605%22%2C%22state%22%3A1%2C%22type%22%3A1%2C%22tech_type%22%3A1%2C%22version%22%3A%22undefined%22%7D&amp;amp;tech_type=1&amp;amp;bdpsum=281c864&quot; rel=&quot;noopener&quot;&gt;example&lt;/a&gt;).
Other super app providers do not go through an intermediate page, but open the preview directly.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;ByteDance DevTools showing a QR code that the user can scan with the Douyin app to see the current mini app on their device.&quot; decoding=&quot;async&quot; height=&quot;551&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/p0CrYvh00oAV9AUzeoU6.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    ByteDance DevTools showing a QR code that the user can scan with the Douyin app for immediate on-device testing.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Intermediate landing page for previewing a ByteDance mini app in various of the company&amp;#x27;s super apps, opened on a regular desktop browser for reverse-engineering the process.&quot; decoding=&quot;async&quot; height=&quot;433&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LNOqaa8z2Oo4CZPa48Bw.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
     Intermediate ByteDance landing page for previewing a mini app (opened on a desktop browser to show the flow).
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;An even more compelling feature is cloud-based preview remote debugging. After simply scanning a
special likewise IDE-generated QR code, the mini app opens on the physical device, with a Chrome
DevTools window running on the computer for remote debugging.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A mobile phone running a mini app with parts of the UI highlighted by the ByteDance DevTools debugger running on a laptop inspecting it.&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/N5Crv3ryZ3bCNFMv7Lir.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
   Wirelessly remote-debugging a mini app on a real device with ByteDance DevTools.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;debugger&quot;&gt;Debugger &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-devtools/#debugger&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;elements-debugging&quot;&gt;Elements debugging &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-devtools/#elements-debugging&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The mini app debugging experience is very familiar to anyone who has ever worked with Chrome
DevTools. There are some important differences, though, that make the workflow tailored to mini
apps. Instead of the Chrome DevTools&#39;
&lt;a href=&quot;https://developer.chrome.com/docs/devtools/#elements&quot; rel=&quot;noopener&quot;&gt;Elements panel&lt;/a&gt;, mini app IDEs
have a customized panel that is tailored to their particular dialect of HTML. For example, in the
case of WeChat, the panel is called
&lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/en/dev/framework/view/wxml/&quot; rel=&quot;noopener&quot;&gt;Wxml&lt;/a&gt;, which stands for
WeiXin Markup Language. In Baidu DevTools, it&#39;s called
&lt;a href=&quot;https://smartprogram.baidu.com/docs/develop/framework/dev/&quot; rel=&quot;noopener&quot;&gt;Swan Element&lt;/a&gt;. ByteDance DevTools calls
it
&lt;a href=&quot;https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/guide/mini-app-framework/view/ttml&quot; rel=&quot;noopener&quot;&gt;Bxml&lt;/a&gt;.
Alipay names it &lt;a href=&quot;https://opendocs.alipay.com/mini/framework/axml&quot; rel=&quot;noopener&quot;&gt;AXML&lt;/a&gt;, and Quick App references
the panel simply as &lt;a href=&quot;https://doc.quickapp.cn/tutorial/framework/for.html&quot; rel=&quot;noopener&quot;&gt;UX&lt;/a&gt;. I will dive into
these markup languages &lt;a href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#markup-languages&quot;&gt;later&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Inspecting an image with WeChat DevTools&amp;#x27; &amp;#x27;Wxml&amp;#x27; panel. It shows that the tag in use is an &amp;#x60;image&amp;#x60; tag.&quot; decoding=&quot;async&quot; height=&quot;572&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/k1FO68wZhzpRNvQl2bN3.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Inspecting an &lt;code&gt;&amp;lt;image&amp;gt;&lt;/code&gt; element with WeChat DevTools.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;custom-elements-under-the-hood&quot;&gt;Custom elements under the hood &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-devtools/#custom-elements-under-the-hood&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Inspecting the WebView on a real device via &lt;a href=&quot;about://inspect/#devices&quot; rel=&quot;noopener&quot;&gt;about://inspect/#devices&lt;/a&gt;
reveals that WeChat DevTools was deliberately hiding the truth. Where WeChat DevTools showed an
&lt;code&gt;&amp;lt;image&amp;gt;&lt;/code&gt;, the actual thing I am looking at is a custom element called &lt;code&gt;&amp;lt;wx-image&amp;gt;&lt;/code&gt; with a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;
as its only child. It is interesting to note that this custom element does not use
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/Web_Components/Using_shadow_DOM&quot; rel=&quot;noopener&quot;&gt;Shadow DOM&lt;/a&gt;. More on
these components &lt;a href=&quot;https://web.dev/mini-app-components/&quot;&gt;later&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Inspecting a WeChat mini app running on a real device with Chrome DevTools. Where WeChat DevTools reported I am looking at an &amp;#x60;image&amp;#x60; tag, Chrome DevTools reveals I am actually dealing with a &amp;#x60;wx-image&amp;#x60; custom element.&quot; decoding=&quot;async&quot; height=&quot;385&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/on9Ty46RyteTI6QTc2vk.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Inspecting an &lt;code&gt;&amp;lt;image&amp;gt;&lt;/code&gt; element with WeChat DevTools reveals that it is actually a &lt;code&gt;&amp;lt;wx-image&amp;gt;&lt;/code&gt; custom element.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;css-debugging&quot;&gt;CSS debugging &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-devtools/#css-debugging&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Another difference is the new length unit &lt;code&gt;rpx&lt;/code&gt; for responsive pixel in the various dialects of CSS
(more on this unit &lt;a href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#styling&quot;&gt;later&lt;/a&gt;). WeChat DevTools uses device-independent CSS length units to
make developing for different device sizes more intuitive.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Inspecting a view with a specified top and bottom padding of &amp;#x60;200rpx&amp;#x60; in WeChat DevTools.&quot; decoding=&quot;async&quot; height=&quot;486&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bf4YBcscBzbmQtJig7Ij.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Inspecting the padding specified in responsive pixels (&lt;code&gt;200rpx 0&lt;/code&gt;) of a view with WeChat DevTools.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;performance-auditing&quot;&gt;Performance auditing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-devtools/#performance-auditing&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Performance is front and center for mini apps, so it is no surprise that WeChat DevTools and some
other DevTools have an integrated Lighthouse-inspired auditing tool. The focus areas of the audits
are Total, Performance, Experience, and Best Practice. The view of the IDE can be customized. In the
screenshot below I have temporarily hidden the code editor to have more screen real estate for the
audit tool.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Running a performance audit with the built-in audit tool. The scores show Total, Performance, Experience, and Best Practice, each 100 out of 100 points.&quot; decoding=&quot;async&quot; height=&quot;485&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/q8Rze6pR9mpDXw9VCaEs.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The built-in Audit tool in WeChat DevTools showing Total, Performance, Experience, and Best Practice.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;api-mocking&quot;&gt;API mocking &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-devtools/#api-mocking&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Rather than requiring the developer to set up a separate service, mocking API responses is directly
part of WeChat DevTools. Via an easy-to-use interface the developer can set up API endpoints and the
desired mock responses.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Setting up a mock response for an API endpoint in WeChat DevTools.&quot; decoding=&quot;async&quot; height=&quot;485&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/olJmRvdl0zkoWZiiOkiP.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    WeChat DevTools&#39; integrated API response mocking feature.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Read on to learn about the &lt;a href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/&quot;&gt;mark-up, styling, and scripting of mini apps&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-devtools/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://github.com/mihajlija&quot; rel=&quot;noopener&quot;&gt;Milica Mihajlija&lt;/a&gt;,
&lt;a href=&quot;https://github.com/alankent&quot; rel=&quot;noopener&quot;&gt;Alan Kent&lt;/a&gt;,
and Keith Gu.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Applying the mini app programming principles to an example project</title>
    <link href="https://web.dev/mini-app-example-project/"/>
    <updated>2021-03-03T00:00:00Z</updated>
    <id>https://web.dev/mini-app-example-project/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This post is part of an article collection where each article builds upon previous articles. If you just landed here, you may want to start reading from the &lt;a href=&quot;https://web.dev/mini-app-super-apps/&quot;&gt;beginning&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;the-app-domain&quot;&gt;The app domain &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-example-project/#the-app-domain&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To show the &lt;a href=&quot;https://web.dev/mini-app-programming-way/&quot;&gt;mini app way of programming&lt;/a&gt;
applied to a web app, I needed a small but complete enough app idea.
&lt;a href=&quot;https://en.wikipedia.org/wiki/High-intensity_interval_training&quot; rel=&quot;noopener&quot;&gt;High-intensity interval training&lt;/a&gt; (HIIT)
is a cardiovascular exercise strategy of alternating sets of short periods of intense anaerobic exercise with less intense recovery periods.
Many HIIT trainings use HIIT timers, for example, this &lt;a href=&quot;https://www.youtube.com/watch?v=tXOZS3AKKOw&quot; rel=&quot;noopener&quot;&gt;30 minute online session&lt;/a&gt;
from &lt;a href=&quot;https://www.youtube.com/user/thebodycoach1&quot; rel=&quot;noopener&quot;&gt;The Body Coach TV&lt;/a&gt; YouTube channel.&lt;/p&gt;
&lt;div class=&quot;switcher&quot;&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;HIIT training online session with green high intensity timer.&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tUl2jNm2bFqGBAFF5s63.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;
      Active period.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;HIIT training online session with red low intensity timer.&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FMUgX3WJ4ZfQX38zHP75.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;
      Resting period.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;h2 id=&quot;hiit-time-example-app&quot;&gt;HIIT Time example app &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-example-project/#hiit-time-example-app&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For this chapter, I have built a basic example of such a HIIT timer application aptly named
&amp;quot;HIIT Time&amp;quot; that lets the user define and manage various timers,
always consisting of a high and a low intensity interval,
and then select one of them for a training session.
It is a responsive app with a navbar, a tabbar, and three pages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Workout:&lt;/strong&gt; The active page during a workout. It lets the user select one of the timers
and features three progress rings: the number of sets, the active period, and the resting period.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timers:&lt;/strong&gt; Manages existing timers and lets the user create new ones.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Preferences:&lt;/strong&gt; Allows toggling sound effects and speech output and selecting language and theme.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following screenshots give an impression of the application.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;HIIT Time example app in portrait mode.&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9RYkQ17tlEy79NlAIFfP.svg&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    HIIT Time &quot;Workout&quot; tab in portrait mode.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;HIIT Time example app in landscape mode.&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SNHMWFvHtCYHEfC9SHPl.svg&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    HIIT Time &quot;Workout&quot; tab in landscape mode.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;HIIT Time example app showing management of a timer.&quot; decoding=&quot;async&quot; height=&quot;450&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/f7uqTk1PNMVaHob7FDzA.svg&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    HIIT Time timer management.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;app-structure&quot;&gt;App structure &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-example-project/#app-structure&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As outlined above, the app consists of a navbar, a tabbar, and three pages, arranged in a grid.
Navbar and tabbar are realized as iframes with a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; container in between them with three more iframes
for the pages, out of which one is always visible and dependent on the active selection in the tabbar.
A final iframe pointing to &lt;code&gt;about:blank&lt;/code&gt; serves for dynamically created in-app pages, which are needed for modifying existing
timers or creating new ones.
I call this pattern multi-page single-page app (MPSPA).&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Chrome DevTools view of the HTML structure of the app showing that it consists of six iframes: one for the navbar, one for the tabbar, and three grouped ones for each page of the app, with a final placeholder iframe for dynamic pages.&quot; decoding=&quot;async&quot; height=&quot;244&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rv14TNs1kU0bpW5kv5bq.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The app consists of six iframes.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;components-based-lit-html-markup&quot;&gt;Components-based lit-html markup &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-example-project/#components-based-lit-html-markup&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The structure of each page is realized as &lt;a href=&quot;https://lit-html.polymer-project.org/&quot; rel=&quot;noopener&quot;&gt;lit-html&lt;/a&gt; scaffold
that gets dynamically evaluated at runtime.
For a background on lit-html, it is an efficient, expressive, extensible HTML templating library for JavaScript.
By using it directly in the HTML files, the mental programming model is directly output-oriented.
As a programmer, you write a template of what the final output will look like,
and lit-html then fills the gaps dynamically based on your data and hooks up the event listeners.
The app makes use of third-party custom elements like &lt;a href=&quot;https://shoelace.style/&quot; rel=&quot;noopener&quot;&gt;Shoelace&lt;/a&gt;&#39;s &lt;a href=&quot;https://shoelace.style/components/progress-ring&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;sl-progress-ring&amp;gt;&lt;/code&gt;&lt;/a&gt; or a self-implemented custom element called &lt;code&gt;&amp;lt;human-duration&amp;gt;&lt;/code&gt;.
Since custom elements have a declarative API (for example, the &lt;code&gt;percentage&lt;/code&gt; attribute of the progress ring),
they work well together with lit-html, as you can see in the listing below.&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 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;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;start&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;@click&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;${eventHandlers.start}&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;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;br /&gt;    ${strings.START}&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 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;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;pause&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;@click&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;${eventHandlers.pause}&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;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;br /&gt;    ${strings.PAUSE}&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 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;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;reset&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;@click&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;${eventHandlers.reset}&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;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;br /&gt;    ${strings.RESET}&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 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;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;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;progress-rings&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;sl-progress-ring&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token attr-name&quot;&gt;class&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;sets&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;percentage&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;${Math.floor(data.sets/data.activeTimer.sets*100)}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&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 attr-name&quot;&gt;class&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;progress-ring-caption&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;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;${strings.SETS}&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;span&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;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;${data.sets}&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;span&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;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;sl-progress-ring&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;figure&gt;
  &lt;img alt=&quot;Three buttons and a progress ring.&quot; decoding=&quot;async&quot; height=&quot;244&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Toz6JmkCQVt7WLscSnlP.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Toz6JmkCQVt7WLscSnlP.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Toz6JmkCQVt7WLscSnlP.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Toz6JmkCQVt7WLscSnlP.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Toz6JmkCQVt7WLscSnlP.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Toz6JmkCQVt7WLscSnlP.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Toz6JmkCQVt7WLscSnlP.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Toz6JmkCQVt7WLscSnlP.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Toz6JmkCQVt7WLscSnlP.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Toz6JmkCQVt7WLscSnlP.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Toz6JmkCQVt7WLscSnlP.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    Rendered section of the page corresponding to the mark-up above.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;programming-model&quot;&gt;Programming model &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-example-project/#programming-model&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Each page has a corresponding &lt;code&gt;Page&lt;/code&gt; class that fills the lit-html markup with life by providing implementations
of the event handlers and providing the data for each page.
This class also supports lifecycle methods like &lt;code&gt;onShow()&lt;/code&gt;, &lt;code&gt;onHide()&lt;/code&gt;, &lt;code&gt;onLoad()&lt;/code&gt;, and &lt;code&gt;onUnload()&lt;/code&gt;.
Pages have access to a data store that serves for sharing optionally persisted per-page state and global state.
All strings are centrally managed, so internationalization is built in.
Routing is handled by the browser essentially for free, since all the app does is toggle iframe visibility and
for dynamically created pages change the &lt;code&gt;src&lt;/code&gt; attribute of the placeholder iframe.
The example below shows the code for closing a dynamically created page.&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;import&lt;/span&gt; Page &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;../page.js&#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 keyword&quot;&gt;const&lt;/span&gt; page &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;Page&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;eventHandlers&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;back&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 parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      e&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;      window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;top&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;history&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;back&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;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;figure&gt;
  &lt;img alt=&quot;In-app page realized as an iframe.&quot; decoding=&quot;async&quot; height=&quot;272&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 500px) 500px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/y82LVHSxUVAehgQlDbsb.png?auto=format&amp;w=1000 1000w&quot; width=&quot;500&quot; /&gt;
  &lt;figcaption&gt;
    Navigation happens from iframe to iframe.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;styling&quot;&gt;Styling &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-example-project/#styling&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Styling of pages happens per-page in its own scoped CSS file.
This means elements can usually just be directly addressed by their element names,
since no clashes with other pages can occur.
Global styles are added to each page, so central settings like the &lt;code&gt;font-family&lt;/code&gt; or the &lt;code&gt;box-sizing&lt;/code&gt;
do not need to be declared repeatedly.
This is also where the themes and dark mode options are defined.
The listing below shows the rules for the Preferences page that lays out the various form elements
on a grid.&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;main&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;max-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 600px&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 selector&quot;&gt;form&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; grid&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;grid-template-columns&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; auto 1fr&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;grid-gap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.5rem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;margin-block-end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1rem&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 selector&quot;&gt;label&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;text-align&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; end&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;grid-column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1 / 2&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 selector&quot;&gt;input,&lt;br /&gt;select&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;grid-column&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 2 / 3&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;figure&gt;
  &lt;img alt=&quot;HIIT Time app preferences page showing a form in grid layout.&quot; decoding=&quot;async&quot; height=&quot;312&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 500px) 500px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/z3Op4O7OM5NQ1zz8Uah8.png?auto=format&amp;w=1000 1000w&quot; width=&quot;500&quot; /&gt;
  &lt;figcaption&gt;
    Every page is its own world. Styling happens directly with the element names.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;screen-wake-lock&quot;&gt;Screen wake lock &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-example-project/#screen-wake-lock&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;During a workout, the screen should not turn off.
On browsers that support it, HIIT Time realizes this through a &lt;a href=&quot;https://web.dev/wake-lock/&quot;&gt;screen wake lock&lt;/a&gt;.
The snippet below shows how it is done.&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;&lt;span class=&quot;token string&quot;&gt;&#39;wakeLock&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;requestWakeLock&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;      page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shared&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wakeLock &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wakeLock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;screen&#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;      page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shared&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wakeLock&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;release&#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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;// Nothing.&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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Request a screen wake lock…&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestWakeLock&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;// …and re-request it when the page becomes visible.&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;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;visibilitychange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;br /&gt;      page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shared&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wakeLock &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 operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;br /&gt;      document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visibilityState &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;visible&#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;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestWakeLock&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 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;testing-the-application&quot;&gt;Testing the application &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-example-project/#testing-the-application&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The HIIT Time application is available on &lt;a href=&quot;https://github.com/tomayac/hiit-time&quot; rel=&quot;noopener&quot;&gt;GitHub&lt;/a&gt;.
You can play with the &lt;a href=&quot;https://tomayac.github.io/hiit-time/&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt; in a new window,
or right in the iframe embed below, which simulates a mobile device.&lt;/p&gt;
&lt;iframe src=&quot;https://tomayac.github.io/hiit-time/#workout&quot; width=&quot;411&quot; height=&quot;731&quot; loading=&quot;lazy&quot; frameborder=&quot;0&quot; allow=&quot;screen-wake-lock&quot;&gt;&lt;/iframe&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The final chapter ends this collection on mini apps with a &lt;a href=&quot;https://web.dev/mini-app-conclusion&quot;&gt;conclusion&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-example-project/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://github.com/mihajlija&quot; rel=&quot;noopener&quot;&gt;Milica Mihajlija&lt;/a&gt;,
&lt;a href=&quot;https://github.com/alankent&quot; rel=&quot;noopener&quot;&gt;Alan Kent&lt;/a&gt;,
and Keith Gu.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Mini app markup, styling, and scripting</title>
    <link href="https://web.dev/mini-app-markup-styling-and-scripting/"/>
    <updated>2021-03-03T00:00:00Z</updated>
    <id>https://web.dev/mini-app-markup-styling-and-scripting/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This post is part of an article collection where each article builds upon previous articles. If you just landed here, you may want to start reading from the &lt;a href=&quot;https://web.dev/mini-app-super-apps/&quot;&gt;beginning&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;markup-languages&quot;&gt;Markup languages &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#markup-languages&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As outlined before, rather than with plain HTML, mini apps are written with dialects of HTML. If you
have ever dealt with &lt;a href=&quot;https://vuejs.org/&quot; rel=&quot;noopener&quot;&gt;Vue.js&lt;/a&gt; text interpolation and directives, you will feel
immediately at home, but similar concepts existed way before that in XML Transformations
(&lt;a href=&quot;https://www.w3.org/TR/xslt-30/&quot; rel=&quot;noopener&quot;&gt;XSLT&lt;/a&gt;). Below, you can see code samples from WeChat&#39;s
&lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/en/dev/framework/view/wxml/&quot; rel=&quot;noopener&quot;&gt;WXML&lt;/a&gt;, but the concept is
the same for all mini apps platforms, namely Alipay&#39;s
&lt;a href=&quot;https://opendocs.alipay.com/mini/framework/axml&quot; rel=&quot;noopener&quot;&gt;AXML&lt;/a&gt;, Baidu&#39;s
&lt;a href=&quot;https://smartprogram.baidu.com/docs/develop/framework/dev/&quot; rel=&quot;noopener&quot;&gt;Swan Element&lt;/a&gt;, ByteDance&#39;s
&lt;a href=&quot;https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/guide/mini-app-framework/view/ttml&quot; rel=&quot;noopener&quot;&gt;TTML&lt;/a&gt;
(despite the DevTools calling it Bxml), and Quick App&#39;s
&lt;a href=&quot;https://doc.quickapp.cn/tutorial/framework/for.html&quot; rel=&quot;noopener&quot;&gt;HTML&lt;/a&gt;. Just like with Vue.js, the underlying
mini app programming concept is the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel&quot; rel=&quot;noopener&quot;&gt;model-view-viewmodel&lt;/a&gt; (MVVM).&lt;/p&gt;
&lt;h3 id=&quot;data-binding&quot;&gt;Data binding &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#data-binding&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Data binding corresponds to Vue.js&#39;
&lt;a href=&quot;https://vuejs.org/v2/guide/syntax.html#Text&quot; rel=&quot;noopener&quot;&gt;text interpolation&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 comment&quot;&gt;&amp;lt;!-- wxml --&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;view&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{message}}&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;view&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;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;// page.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Page&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;data&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 literal-property property&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello World!&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;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;h3 id=&quot;list-rendering&quot;&gt;List rendering &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#list-rendering&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;List rendering works like Vue.js &lt;a href=&quot;https://vuejs.org/v2/guide/list.html&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;v-for&lt;/code&gt; directive&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 comment&quot;&gt;&amp;lt;!-- wxml --&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;view&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;wx:&lt;/span&gt;for&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;{{array}}&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;{{item}}&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;view&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;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;// page.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Page&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;data&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 literal-property property&quot;&gt;array&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;1&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 punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&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;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;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;h3 id=&quot;conditional-rendering&quot;&gt;Conditional rendering &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#conditional-rendering&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Conditional rendering works like Vue.js&#39;
&lt;a href=&quot;https://vuejs.org/v2/guide/conditional.html&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;v-if&lt;/code&gt; directive&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 comment&quot;&gt;&amp;lt;!-- wxml --&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;view&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;wx:&lt;/span&gt;if&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;{{view == &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;one&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;}}&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;One&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;view&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;view&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;wx:&lt;/span&gt;elif&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;{{view == &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;two&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;}}&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;Two&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;view&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;view&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;wx:&lt;/span&gt;else&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;{{view == &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;three&lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;}}&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;Three&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;view&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;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;// page.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Page&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;data&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 literal-property property&quot;&gt;view&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;three&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;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;h3 id=&quot;templates&quot;&gt;Templates &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#templates&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Rather than requiring the imperative
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLTemplateElement/content&quot; rel=&quot;noopener&quot;&gt;cloning of the &lt;code&gt;content&lt;/code&gt; of an HTML template&lt;/a&gt;,
WXML templates can be used declaratively via the &lt;code&gt;is&lt;/code&gt; attribute that links to a template definition.&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;!-- wxml --&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;template&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;person&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;view&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    First Name: {{firstName}}, Last Name: {{lastName}}&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;view&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;template&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;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;template&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;person&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data&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;{{...personA}}&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 tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&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;template&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;person&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data&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;{{...personB}}&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 tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&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;template&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;person&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data&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;{{...personC}}&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 tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;template&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;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;// page.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Page&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;data&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 literal-property property&quot;&gt;personA&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 literal-property property&quot;&gt;firstName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;lastName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Foo&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 literal-property property&quot;&gt;personB&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 literal-property property&quot;&gt;firstName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Bob&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;lastName&lt;/span&gt;&lt;span class=&quot;token operator&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;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;personC&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 literal-property property&quot;&gt;firstName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Charly&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;lastName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Baz&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;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;h2 id=&quot;styling&quot;&gt;Styling &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#styling&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Styling happens with dialects of CSS. WeChat&#39;s is named
&lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/en/dev/framework/quickstart/code.html#WXSS-Style&quot; rel=&quot;noopener&quot;&gt;WXSS&lt;/a&gt;.
For Alipay, theirs is called &lt;a href=&quot;https://opendocs.alipay.com/mini/framework/acss&quot; rel=&quot;noopener&quot;&gt;ACSS&lt;/a&gt;, Baidu&#39;s simply
&lt;a href=&quot;https://smartprogram.baidu.com/docs/develop/framework/view_css/&quot; rel=&quot;noopener&quot;&gt;CSS&lt;/a&gt;, and for ByteDance, their
dialect is referred to as
&lt;a href=&quot;https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/guide/mini-app-framework/view/ttss&quot; rel=&quot;noopener&quot;&gt;TTSS&lt;/a&gt;.
What they have in common is that they extend CSS with responsive pixels. When writing regular CSS,
developers need to convert all pixel units to adapt to different mobile device screens with
different widths and pixel ratios. TTSS supports the &lt;code&gt;rpx&lt;/code&gt; unit as its underlying layer, which means
the mini app takes over the job from the developer and converts the units on their behalf, based on
a specified screen width of &lt;code&gt;750rpx&lt;/code&gt;. For example, on a Pixel 3a phone with a screen width of
&lt;code&gt;393px&lt;/code&gt; (and a device pixel ratio of &lt;code&gt;2.75&lt;/code&gt;), responsive &lt;code&gt;200rpx&lt;/code&gt; become &lt;code&gt;104px&lt;/code&gt; on the real device
when inspected with Chrome DevTools (393px / 750rpx * 200rpx ≈ 104px). In Android, the same concept
is called
&lt;a href=&quot;https://developer.android.com/training/multiscreen/screendensities#TaskUseDP&quot; rel=&quot;noopener&quot;&gt;density-independent pixel&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Inspecting a view with Chrome DevTools whose responsive pixel padding was specified with &amp;#x60;200rpx&amp;#x60; shows that it is actually &amp;#x60;104px&amp;#x60; on a Pixel 3a device.&quot; decoding=&quot;async&quot; height=&quot;385&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/n26ptkMoSfiTDanfFh5F.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Inspecting the actual padding on a Pixel 3a device with Chrome DevTools.
  &lt;/figcaption&gt;
&lt;/figure&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 comment&quot;&gt;/* app.wxss */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.container&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;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&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 property&quot;&gt;flex-direction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; column&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;align-items&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;justify-content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; space-between&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; 200rpx 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* ← responsive pixels */&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;box-sizing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; border-box&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;Since components (see &lt;a href=&quot;https://web.dev/mini-app-components/&quot;&gt;later&lt;/a&gt;) do not use shadow DOM, styles declared on a page reach
into all components. The common stylesheet file organization is to have one root stylesheet for
global styles, and individual per-page stylesheets specific to each page of the mini app. Styles can
be imported with an &lt;code&gt;@import&lt;/code&gt; rule that behaves like the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/@import&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;@import&lt;/code&gt;&lt;/a&gt; CSS at-rule. Like in HTML,
styles can also be declared inline, including dynamic text interpolation (see
&lt;a href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#data-binding&quot;&gt;before&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;view&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 selector&quot;&gt;color:&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;color&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&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;h2 id=&quot;scripting&quot;&gt;Scripting &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#scripting&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mini apps support a &amp;quot;safe subset&amp;quot; of JavaScript that includes support for modules using varying
syntaxes that remind of &lt;a href=&quot;http://www.commonjs.org/&quot; rel=&quot;noopener&quot;&gt;CommonJS&lt;/a&gt; or &lt;a href=&quot;https://requirejs.org/&quot; rel=&quot;noopener&quot;&gt;RequireJS&lt;/a&gt;.
JavaScript code cannot be executed via &lt;code&gt;eval()&lt;/code&gt; and no functions can be created with
&lt;code&gt;new Function()&lt;/code&gt;. The scripting execution context is &lt;a href=&quot;https://v8.dev/&quot; rel=&quot;noopener&quot;&gt;V8&lt;/a&gt; or
&lt;a href=&quot;https://developer.apple.com/documentation/javascriptcore&quot; rel=&quot;noopener&quot;&gt;JavaScriptCore&lt;/a&gt; on devices, and V8 or
&lt;a href=&quot;https://nwjs.io/&quot; rel=&quot;noopener&quot;&gt;NW.js&lt;/a&gt; in the simulator. Coding with ES6 or newer syntax is usually possible,
since the particular DevTools automatically transpile the code to ES5 if the build target is an
operating system with an older WebView implementation (see &lt;a href=&quot;https://web.dev/mini-app-project-structure-lifecycle-and-bundling/#the-build-process&quot;&gt;later&lt;/a&gt;). The
documentation of the super app providers explicitly mentions that their scripting languages are not
to be confused with and are distinct from JavaScript. This statement, however, refers mostly just to
the way modules work, that is, that they do not support standard
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Guide/Modules&quot; rel=&quot;noopener&quot;&gt;ES Modules&lt;/a&gt; yet.&lt;/p&gt;
&lt;p&gt;As mentioned &lt;a href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#markup-languages&quot;&gt;before&lt;/a&gt;, the mini app programming concept is the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel&quot; rel=&quot;noopener&quot;&gt;model-view-viewmodel&lt;/a&gt; (MVVM).
The logic layer and the view layer run on different threads, which means the user interface does not
get blocked by long-running operations. In web terms, you can think of scripts running in a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Web_Workers_API/Using_web_workers&quot; rel=&quot;noopener&quot;&gt;Web Worker&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;WeChat&#39;s scripting language is called
&lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/en/dev/reference/wxs/&quot; rel=&quot;noopener&quot;&gt;WXS&lt;/a&gt;, Alipay&#39;s
&lt;a href=&quot;https://opendocs.alipay.com/mini/framework/sjs&quot; rel=&quot;noopener&quot;&gt;SJS&lt;/a&gt;, ByteDance&#39;s likewise
&lt;a href=&quot;https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/framework/sjs-syntax-reference/sjs-introduction/&quot; rel=&quot;noopener&quot;&gt;SJS&lt;/a&gt;.
Baidu speaks of &lt;a href=&quot;https://smartprogram.baidu.com/docs/develop/framework/devjs/&quot; rel=&quot;noopener&quot;&gt;JS&lt;/a&gt; when referencing
theirs. These scripts need to be included using a special kind of tag, for example, &lt;code&gt;&amp;lt;wxs&amp;gt;&lt;/code&gt; in
WeChat. In contrast, Quick App uses regular &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags and the ES6
&lt;a href=&quot;https://doc.quickapp.cn/framework/script.html&quot; rel=&quot;noopener&quot;&gt;JS&lt;/a&gt; syntax.&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;wxs&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;module&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;m1&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;  var msg = &quot;hello world&quot;;&lt;br /&gt;  module.exports.message = msg;&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;wxs&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;view&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{m1.message}}&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;view&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;Modules can also be loaded via a &lt;code&gt;src&lt;/code&gt; attribute, or imported via &lt;code&gt;require()&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;// /pages/tools.wxs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; foo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&#39;hello world&#39; from tools.wxs&quot;&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; &lt;span class=&quot;token function-variable function&quot;&gt;bar&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;d&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; d&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;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &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 constant&quot;&gt;FOO&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; foo&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; bar&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;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;msg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;some msg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;!-- page/index/index.wxml --&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;wxs&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;./../tools.wxs&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;module&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;tools&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;view&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{tools.msg}}&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;view&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;view&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{tools.bar(tools.FOO)}}&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;view&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;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;// /pages/logic.wxs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; tools &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./tools.wxs&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;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;tools&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;FOO&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;tools&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;logic.wxs&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 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;tools&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;msg&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;javascript-bridge-api&quot;&gt;JavaScript bridge API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#javascript-bridge-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The JavaScript bridge that connects mini apps with the operating system makes it possible
to use OS capabilities (see &lt;a href=&quot;https://web.dev/mini-app-about/#access-to-powerful-features&quot;&gt;Access to powerful features&lt;/a&gt;. It
also offers a number of convenience methods. For an overview, you can check out the different APIs
of &lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/en/dev/api/&quot; rel=&quot;noopener&quot;&gt;WeChat&lt;/a&gt;,
&lt;a href=&quot;https://opendocs.alipay.com/mini/api&quot; rel=&quot;noopener&quot;&gt;Alipay&lt;/a&gt;,
&lt;a href=&quot;https://smartprogram.baidu.com/docs/develop/api/apilist/&quot; rel=&quot;noopener&quot;&gt;Baidu&lt;/a&gt;,
&lt;a href=&quot;https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/api/foundation/tt-can-i-use&quot; rel=&quot;noopener&quot;&gt;ByteDance&lt;/a&gt;,
and &lt;a href=&quot;https://doc.quickapp.cn/features/&quot; rel=&quot;noopener&quot;&gt;Quick App&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Feature detection is straightforward, since all platforms provide a (literally called like this)
&lt;code&gt;canIUse()&lt;/code&gt; method whose name seems inspired by the website &lt;a href=&quot;https://caniuse.com/&quot; rel=&quot;noopener&quot;&gt;caniuse.com&lt;/a&gt;. For
example, ByteDance&#39;s
&lt;a href=&quot;https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/api/foundation/tt-can-i-use&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;tt.canIUse()&lt;/code&gt;&lt;/a&gt;
allows for support checks for APIs, methods, parameters, options, components, and attributes.&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;// Testing if the `&amp;lt;swiper&gt;` component is supported.&lt;/span&gt;&lt;br /&gt;tt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;canIUse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;swiper&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 comment&quot;&gt;// Testing if a particular field is supported.&lt;/span&gt;&lt;br /&gt;tt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;canIUse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;request.success.data&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The next chapter introduces &lt;a href=&quot;https://web.dev/mini-app-components/&quot;&gt;mini app components&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://github.com/mihajlija&quot; rel=&quot;noopener&quot;&gt;Milica Mihajlija&lt;/a&gt;,
&lt;a href=&quot;https://github.com/alankent&quot; rel=&quot;noopener&quot;&gt;Alan Kent&lt;/a&gt;,
and Keith Gu.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Mini app open source projects</title>
    <link href="https://web.dev/mini-app-open-source-projects/"/>
    <updated>2021-03-03T00:00:00Z</updated>
    <id>https://web.dev/mini-app-open-source-projects/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This post is part of an article collection where each article builds upon previous articles. If you just landed here, you may want to start reading from the &lt;a href=&quot;https://web.dev/mini-app-super-apps/&quot;&gt;beginning&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;kbone&quot;&gt;kbone &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-open-source-projects/#kbone&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://wechat-miniprogram.github.io/kbone/docs/&quot; rel=&quot;noopener&quot;&gt;kbone&lt;/a&gt; project
(&lt;a href=&quot;https://github.com/Tencent/kbone&quot; rel=&quot;noopener&quot;&gt;open source on GitHub&lt;/a&gt;) implements an adapter that simulates a
browser environment in the adaptation layer, so that code written for the web can run without
changes in a mini app. Several starter templates (among them
&lt;a href=&quot;https://github.com/wechat-miniprogram/kbone-template-vue&quot; rel=&quot;noopener&quot;&gt;Vue&lt;/a&gt;,
&lt;a href=&quot;https://github.com/wechat-miniprogram/kbone-template-react&quot; rel=&quot;noopener&quot;&gt;React&lt;/a&gt;, and
&lt;a href=&quot;https://github.com/wechat-miniprogram/kbone-template-preact&quot; rel=&quot;noopener&quot;&gt;Preact&lt;/a&gt;) exist that make the
onboarding experience for web developers coming from these frameworks easier.&lt;/p&gt;
&lt;p&gt;A new project can be created with the &lt;code&gt;kbone-cli&lt;/code&gt; tool. A wizard asks what framework to initiate the
project with. The code snippet below shows the Preact demo. In the code snippet below, the &lt;code&gt;mp&lt;/code&gt;
command builds the mini app, the &lt;code&gt;web&lt;/code&gt; command builds the web app, and &lt;code&gt;build&lt;/code&gt; creates the
production web app.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx kbone-cli init my-app&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; my-app&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run mp&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run web&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run build&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The code snippet below shows a simple counter component that then gets isomorphically rendered in a
mini app and a web app. The overhead of the mini app is significant, purely judging from the DOM
structure.&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;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; h&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Component &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;preact&quot;&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;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./index.css&quot;&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;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Counter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  state &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 literal-property property&quot;&gt;count&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token function-variable function&quot;&gt;sub&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;span class=&quot;token operator&quot;&gt;=&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;setState&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 parameter&quot;&gt;prevState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;span class=&quot;token literal-property property&quot;&gt;count&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;prevState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count &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;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token function-variable function&quot;&gt;add&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;span class=&quot;token operator&quot;&gt;=&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;setState&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 parameter&quot;&gt;prevState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;span class=&quot;token literal-property property&quot;&gt;count&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;prevState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count &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;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token function-variable function&quot;&gt;clickHandle&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;span class=&quot;token operator&quot;&gt;=&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 string&quot;&gt;&quot;undefined&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; wx &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; wx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getSystemInfoSync&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;      wx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;navigateTo&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;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../log/index?id=1&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;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;      location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;log.html&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;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 function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&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; count &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;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 operator&quot;&gt;&amp;lt;&lt;/span&gt;div&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;button onClick&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 keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sub&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&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;button&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;span&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;count&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;span&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;button onClick&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 keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;add&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&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;button&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;div onClick&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 keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clickHandle&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&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;div&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;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;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;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; Counter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;figure&gt;
  &lt;img alt=&quot;The Preact kbone template demo app opened in WeChat DevTools. Inspecting the DOM structure shows a significant overhead compared to the web app.&quot; decoding=&quot;async&quot; height=&quot;664&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DDreycxzclMP8IiUplyO.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
   The Preact kbone template demo app opened in WeChat DevTools. Note the complex DOM structure and how the plus and minus buttons are actually not &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; elements.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Preact kbone template demo app opened in the web browser. Inspecting the DOM structure shows the to-be-expected markup based on the Preact component code.&quot; decoding=&quot;async&quot; height=&quot;496&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rerYiCnwc92DP7pM7WPc.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
   The Preact kbone template demo app opened in the web browser. Note the DOM structure.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;kbone-ui&quot;&gt;kbone-ui &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-open-source-projects/#kbone-ui&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://wechat-miniprogram.github.io/kbone/docs/ui/intro/&quot; rel=&quot;noopener&quot;&gt;kbone-ui&lt;/a&gt; project
(&lt;a href=&quot;https://github.com/wechat-miniprogram/kbone-ui&quot; rel=&quot;noopener&quot;&gt;open source on GitHub&lt;/a&gt;) is a UI framework that
facilitates both mini app development as well as Vue.js development with kbone. The kbone-ui
components emulate the look and feel of
&lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/dev/component/&quot; rel=&quot;noopener&quot;&gt;WeChat&#39;s built-in mini app components&lt;/a&gt;
(also see &lt;a href=&quot;https://web.dev/mini-app-components/&quot;&gt;Components&lt;/a&gt; above). A
&lt;a href=&quot;https://wechat-miniprogram.github.io/kboneui/ui/#/&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt; that runs directly in the browser lets
you explore the available components.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Demo of the kbone-ui framework showing form-related components like radio buttons, switches, inputs, and labels.&quot; decoding=&quot;async&quot; height=&quot;438&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 320px) 320px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wEA4Hr2JyVfKgdHnpb5o.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wEA4Hr2JyVfKgdHnpb5o.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wEA4Hr2JyVfKgdHnpb5o.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wEA4Hr2JyVfKgdHnpb5o.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wEA4Hr2JyVfKgdHnpb5o.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wEA4Hr2JyVfKgdHnpb5o.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wEA4Hr2JyVfKgdHnpb5o.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wEA4Hr2JyVfKgdHnpb5o.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wEA4Hr2JyVfKgdHnpb5o.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wEA4Hr2JyVfKgdHnpb5o.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wEA4Hr2JyVfKgdHnpb5o.png?auto=format&amp;w=640 640w&quot; width=&quot;320&quot; /&gt;
  &lt;figcaption&gt;
   The kbone-ui demo showing form-related components.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;weui&quot;&gt;WeUI &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-open-source-projects/#weui&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Tencent/weui&quot; rel=&quot;noopener&quot;&gt;WeUI&lt;/a&gt; is a set of basic style libraries consistent with WeChat&#39;s
default visual experience. The official WeChat design team has tailored designs for WeChat internal
web pages and WeChat mini apps to make users&#39; perception of use more uniform. It includes components
such as &lt;code&gt;button&lt;/code&gt;, &lt;code&gt;cell&lt;/code&gt;, &lt;code&gt;dialog&lt;/code&gt;, &lt;code&gt;progress&lt;/code&gt;, &lt;code&gt;toast&lt;/code&gt;, &lt;code&gt;article&lt;/code&gt;, &lt;code&gt;actionsheet&lt;/code&gt;, and &lt;code&gt;icon&lt;/code&gt;. There
are different versions of WeUI available like &lt;a href=&quot;https://github.com/Tencent/weui-wxss/&quot; rel=&quot;noopener&quot;&gt;weui-wxss&lt;/a&gt; for
WeChat mini apps styled with WXSS (see &lt;a href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#styling&quot;&gt;Styling&lt;/a&gt; above),
&lt;a href=&quot;https://github.com/weui/weui.js/&quot; rel=&quot;noopener&quot;&gt;weui.js&lt;/a&gt; for web apps, and
&lt;a href=&quot;https://github.com/weui/react-weui/&quot; rel=&quot;noopener&quot;&gt;react-weui&lt;/a&gt; for WeChat React components.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Demo of the WeUI framework showing form-related components, namely switches.&quot; decoding=&quot;async&quot; height=&quot;395&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 450px) 450px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/V0xswCD3MJltrxALmy8n.png?auto=format&amp;w=900 900w&quot; width=&quot;450&quot; /&gt;
  &lt;figcaption&gt;
   The WeUI demo app showing switches.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;omi&quot;&gt;Omi &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-open-source-projects/#omi&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://tencent.github.io/omi/&quot; rel=&quot;noopener&quot;&gt;Omi&lt;/a&gt; is a self-proclaimed frontend cross-frameworks framework
(&lt;a href=&quot;https://github.com/Tencent/omi&quot; rel=&quot;noopener&quot;&gt;open source on GitHub&lt;/a&gt;. It merges Web Components, JSX, Virtual
DOM, functional style, observer or Proxy into one framework with tiny size and high performance. Its
aim is to let developers write components once and use them everywhere, such as Omi, React, Preact,
Vue.js, or Angular. Writing Omi components is very intuitive and free of almost all boilerplate.&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;import&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; WeElement&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; define &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;omi&quot;&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 function&quot;&gt;define&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;my-counter&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;extends&lt;/span&gt; WeElement &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  data &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 literal-property property&quot;&gt;count&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;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;static&lt;/span&gt; css &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&lt;br /&gt;    span{&lt;br /&gt;        color: red;&lt;br /&gt;    }&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&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 function-variable function&quot;&gt;sub&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;span class=&quot;token operator&quot;&gt;=&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;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&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 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;update&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;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token function-variable function&quot;&gt;add&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;span class=&quot;token operator&quot;&gt;=&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;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&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 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;update&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;br /&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;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 operator&quot;&gt;&amp;lt;&lt;/span&gt;div&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;button onClick&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 keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sub&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&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;button&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;span&lt;span class=&quot;token operator&quot;&gt;&gt;&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;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count&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;span&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;button onClick&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 keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;add&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token operator&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;button&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;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;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;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 operator&quot;&gt;&amp;lt;&lt;/span&gt;my&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;counter &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;body&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;omiu&quot;&gt;Omiu &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-open-source-projects/#omiu&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://tencent.github.io/omi/components/docs/&quot; rel=&quot;noopener&quot;&gt;Omiu&lt;/a&gt; is a cross framework UI component library
(&lt;a href=&quot;https://github.com/Tencent/omi#omiu&quot; rel=&quot;noopener&quot;&gt;open source on GitHub&lt;/a&gt;) developed based on Omi, which outputs
custom elements of standard web components.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Demo of the Omiu framework showing form-related components, namely switches.&quot; decoding=&quot;async&quot; height=&quot;939&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/eoNhik827CE4TfaT9ZaV.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
   The Omiu demo app showing switches.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;wepy&quot;&gt;WePY &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-open-source-projects/#wepy&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://wepyjs.github.io/wepy-docs/&quot; rel=&quot;noopener&quot;&gt;WePY&lt;/a&gt; is a framework that allows mini apps to support
componentized development. Through pre-compilation, developers can choose their favorite development
style to develop mini apps. The detailed optimization of the framework and the introduction of
Promises and async functions all make the development of mini app projects easier and more
efficient. At the same time, WePY is also a growing framework, which has largely absorbed some
optimized frontend tools and framework design concepts and ideas, mostly from Vue.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 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 attr-name&quot;&gt;lang&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;less&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 style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #4d926f&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;.num&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; &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&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;template&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 attr-name&quot;&gt;class&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;container&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;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;num&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;@tap&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;num++&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;{{num}}&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;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;custom-component&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;custom-component&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;vendor-component&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;vendor-component&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;{{text}}&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;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;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;v-model&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;text&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;div&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;template&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;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  { usingComponents: { customComponent: &#39;@/components/customComponent&#39;, vendorComponent:&lt;br /&gt;  &#39;module:vendorComponent&#39; } }&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;config&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 punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; wepy &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@wepy/core&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  wepy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;page&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;data&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 literal-property property&quot;&gt;num&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 literal-property property&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello World&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;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&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;figure&gt;
  &lt;img alt=&quot;WePY &amp;#x27;getting started&amp;#x27; documentation page showing the first steps to get going.&quot; decoding=&quot;async&quot; height=&quot;505&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ooxZuXzVY9aHXmha36vM.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
   WePY &quot;getting started&quot; documentation.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;vconsole&quot;&gt;vConsole &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-open-source-projects/#vconsole&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/Tencent/vConsole&quot; rel=&quot;noopener&quot;&gt;vConsole&lt;/a&gt; project provides a lightweight, extendable
frontend developer tool for mobile web pages. It offers a DevTools-like debugger that can be
injected directly into web apps and mini apps. A
&lt;a href=&quot;http://wechatfe.github.io/vconsole/demo.html&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt; showcases the opportunities. The vConsole
includes tabs for logs, system, network, elements, and storage.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;vConsole demo app. The vConsole opens at the bottom and has tabs for logs, system, network, elements, and storage.&quot; decoding=&quot;async&quot; height=&quot;505&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ow6FfaoI7fMCJDN1819t.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
   vConsole demo app showing the elements explorer.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;weweb&quot;&gt;weweb &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-open-source-projects/#weweb&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://weidian-inc.github.io/hera/#/&quot; rel=&quot;noopener&quot;&gt;weweb&lt;/a&gt; project
(&lt;a href=&quot;https://github.com/wdfe/weweb&quot; rel=&quot;noopener&quot;&gt;open source on GitHub&lt;/a&gt;) is the underlying frontend framework of
the &lt;a href=&quot;https://weidian-inc.github.io/hera/#/&quot; rel=&quot;noopener&quot;&gt;Hera&lt;/a&gt; mini app framework that claims to be compatible
with the syntax of WeChat mini apps, so you can write web applications in the way of mini apps. The
documentation promises that if you already have a mini app, you can run it in the browser thanks to
weweb. In my experiments, this did not work reliably for current mini apps, most probably because
the project has not seen updates recently leading its compiler to miss changes in the
WeChat platform.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Documentation of the Hera mini app framework listing the WeChat APIs that it supports like &amp;#x60;wx.request&amp;#x60;, &amp;#x60;wx.uploadFile&amp;#x60;, etc.&quot; decoding=&quot;async&quot; height=&quot;505&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/jU9Od9IDqFlmuYzAtirn.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
   Hera mini app framework documentation showing the list of supported WeChat APIs.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The weweb demo mini app compiled with weweb to run in the browser showing form elements.&quot; decoding=&quot;async&quot; height=&quot;429&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/C7ZpPrFE4geW0dylRpZ2.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/C7ZpPrFE4geW0dylRpZ2.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/C7ZpPrFE4geW0dylRpZ2.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/C7ZpPrFE4geW0dylRpZ2.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/C7ZpPrFE4geW0dylRpZ2.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/C7ZpPrFE4geW0dylRpZ2.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/C7ZpPrFE4geW0dylRpZ2.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/C7ZpPrFE4geW0dylRpZ2.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/C7ZpPrFE4geW0dylRpZ2.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/C7ZpPrFE4geW0dylRpZ2.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/C7ZpPrFE4geW0dylRpZ2.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    The weweb demo mini app compiled with weweb to run in the browser.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; In the next chapter, you will learn how to &lt;a href=&quot;https://web.dev/mini-app-programming-way/&quot;&gt;program the mini app way&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-open-source-projects/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://github.com/mihajlija&quot; rel=&quot;noopener&quot;&gt;Milica Mihajlija&lt;/a&gt;,
&lt;a href=&quot;https://github.com/alankent&quot; rel=&quot;noopener&quot;&gt;Alan Kent&lt;/a&gt;,
and Keith Gu.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Programming the mini app way</title>
    <link href="https://web.dev/mini-app-programming-way/"/>
    <updated>2021-03-03T00:00:00Z</updated>
    <id>https://web.dev/mini-app-programming-way/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This post is part of an article collection where each article builds upon previous articles. If you just landed here, you may want to start reading from the &lt;a href=&quot;https://web.dev/mini-app-super-apps/&quot;&gt;beginning&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;what-has-worked-well-for-mini-apps&quot;&gt;What has worked well for mini apps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-programming-way/#what-has-worked-well-for-mini-apps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this chapter, I want to look at lessons I learned from researching mini apps from a web
developer&#39;s point of view, or answer the question what does it mean to develop the mini app way.&lt;/p&gt;
&lt;h2 id=&quot;components&quot;&gt;Components &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-programming-way/#components&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Rather than reinvent the wheel and make developers build yet another implementation of common UI paradigms like tabs,
accordions, carousels, etc., mini apps just ship with a default selection of components that is extensible in case you need more.
On the web, there are likewise many options, some of which I have listed in the &lt;a href=&quot;https://web.dev/mini-app-components/#web-components&quot;&gt;chapter on mini app components&lt;/a&gt;.
In an ideal world, component libraries on the web were built in a way that you could mix them freely.
In practice, too many times, there is a certain lock-in regarding a design system you need to buy in to when you use a component,
or the component library is distributed in a way that it is all or nothing, but no individual components can be easily added to a project.
There are, however, atomic components that you can use in isolation, or libraries like &lt;a href=&quot;https://github.com/thepassle/generic-components&quot; rel=&quot;noopener&quot;&gt;generic-components&lt;/a&gt;
that are unstyled on purpose.
Finding an using those seems like a good idea.&lt;/p&gt;
&lt;h2 id=&quot;model-view-viewmodel&quot;&gt;Model-view-viewmodel &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-programming-way/#model-view-viewmodel&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#markup-languages&quot;&gt;model–view–viewmodel&lt;/a&gt; (MVVM) architectural pattern—that facilitates the
separation of the development of the graphical user interface (the view) via a markup language from
the development of the back-end logic (the model)—means the view is not dependent on any specific model platform.
While there are some documented &lt;a href=&quot;https://docs.microsoft.com/en-us/archive/blogs/johngossman/advantages-and-disadvantages-of-m-v-vm&quot; rel=&quot;noopener&quot;&gt;disadvantages&lt;/a&gt; of the pattern, in general it works really well for applications of the complexity of mini apps.
It can shine especially with rich templating libraries (see &lt;a href=&quot;https://web.dev/mini-app-example-project/&quot;&gt;next chapter&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id=&quot;page-wise-thinking&quot;&gt;Page-wise thinking &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-programming-way/#page-wise-thinking&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Debugging mini apps shows that they are essentially multi-page applications (MPA).
This has many advantages, like, for example, it allows for trivial routing and conflict-free per-page styling.
People have &lt;a href=&quot;https://medium.com/elemefe/upgrading-ele-me-to-progressive-web-app-2a446832e509&quot; rel=&quot;noopener&quot;&gt;successfully applied MPA architectures&lt;/a&gt; to Progressive Web Apps.
Thinking in pages also helps manage resources like each page&#39;s CSS and JavaScript files, and other assets like images and videos.
Most importantly, building this way means you get route-based code splitting for free
if you do not load anything else.
In that case, each page by definition strictly only loads what it needs to function.&lt;/p&gt;
&lt;h2 id=&quot;build-process&quot;&gt;Build process &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-programming-way/#build-process&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mini apps have &lt;a href=&quot;https://web.dev/mini-app-project-structure-lifecycle-and-bundling/#the-build-process&quot;&gt;no visible build process&lt;/a&gt;.
On the web, modern build tools like &lt;a href=&quot;https://www.snowpack.dev/&quot; rel=&quot;noopener&quot;&gt;Snowpack&lt;/a&gt; leverage JavaScript&#39;s built-in
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import&quot; rel=&quot;noopener&quot;&gt;module system&lt;/a&gt;
(known as ESM) to avoid unnecessary work and stay fast no matter how big a project grows.
While it is early days for technologies like &lt;a href=&quot;https://web.dev/web-bundles/&quot;&gt;Web Bundles&lt;/a&gt;, it is something that can be easily added
to the build process.&lt;/p&gt;
&lt;h2 id=&quot;powerful-capabilities&quot;&gt;Powerful capabilities &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-programming-way/#powerful-capabilities&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The web platform has gained many &lt;a href=&quot;https://web.dev/tags/capabilities/&quot;&gt;new capabilities&lt;/a&gt; recently.
Access to &lt;a href=&quot;https://web.dev/tags/devices/&quot;&gt;devices&lt;/a&gt; via &lt;a href=&quot;https://web.dev/bluetooth/&quot;&gt;Bluetooth&lt;/a&gt;, &lt;a href=&quot;https://web.dev/usb/&quot;&gt;USB&lt;/a&gt;, &lt;a href=&quot;https://web.dev/hid/&quot;&gt;HID&lt;/a&gt;, &lt;a href=&quot;https://web.dev/serial/&quot;&gt;serial&lt;/a&gt;,
and &lt;a href=&quot;https://web.dev/nfc/&quot;&gt;NFC&lt;/a&gt; is all possible now.
Where mini apps run in WebViews and depend on a &lt;a href=&quot;https://web.dev/mini-app-markup-styling-and-scripting/#javascript-bridge-api&quot;&gt;JavaScript bridge&lt;/a&gt;,
on the web, these powerful capabilities are available directly,
so you do not program against an API provided by the JavaScript bridge,
but against the browser API without an intermediate actor.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Read on to see an &lt;a href=&quot;https://web.dev/mini-app-example-project/&quot;&gt;example project&lt;/a&gt; that puts this way of programming into practice. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-programming-way/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://github.com/mihajlija&quot; rel=&quot;noopener&quot;&gt;Milica Mihajlija&lt;/a&gt;,
&lt;a href=&quot;https://github.com/alankent&quot; rel=&quot;noopener&quot;&gt;Alan Kent&lt;/a&gt;,
and Keith Gu.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Project structure, lifecycle, and bundling</title>
    <link href="https://web.dev/mini-app-project-structure-lifecycle-and-bundling/"/>
    <updated>2021-03-03T00:00:00Z</updated>
    <id>https://web.dev/mini-app-project-structure-lifecycle-and-bundling/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This post is part of an article collection where each article builds upon previous articles. If you just landed here, you may want to start reading from the &lt;a href=&quot;https://web.dev/mini-app-super-apps/&quot;&gt;beginning&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;mini-app-project-structure&quot;&gt;Mini app project structure &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-project-structure-lifecycle-and-bundling/#mini-app-project-structure&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As before with the markup languages, the styling languages, and the components; with the mini app
project structure, too, the details like the file extensions or the default names vary. The
overall idea, though, is the same for all super app providers. The project structure always consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A root file &lt;code&gt;app.js&lt;/code&gt; that initializes the mini app.&lt;/li&gt;
&lt;li&gt;A configuration file &lt;code&gt;app.json&lt;/code&gt; that &lt;em&gt;roughly&lt;/em&gt; corresponds to a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/Manifest&quot; rel=&quot;noopener&quot;&gt;web app manifest&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;An optional common style sheet file &lt;code&gt;app.css&lt;/code&gt; with shared default styles.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;project.config.json&lt;/code&gt; file that contains build information.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All the pages are stored in individual subfolders in a
&lt;code&gt;pages&lt;/code&gt; folder. Each page subfolder contains a CSS file, a JS file, an HTML file, and an optional
configuration JSON file. All files must be named like their containing folder, apart from the file
extensions. Like that, the mini app just needs a pointer to the directory in the &lt;code&gt;app.json&lt;/code&gt; file
(the manifest-like file), and can find all subresources dynamically. From the perspective of a web
developer, mini apps are thus multi page apps.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;├── app.js               &lt;span class=&quot;token comment&quot;&gt;# Initialization logic&lt;/span&gt;&lt;br /&gt;├── app.json             &lt;span class=&quot;token comment&quot;&gt;# Common configuration&lt;/span&gt;&lt;br /&gt;├── app.css              &lt;span class=&quot;token comment&quot;&gt;# Common style sheet&lt;/span&gt;&lt;br /&gt;├── project.config.json  &lt;span class=&quot;token comment&quot;&gt;# Project configuration&lt;/span&gt;&lt;br /&gt;└── pages                &lt;span class=&quot;token comment&quot;&gt;# List of pages&lt;/span&gt;&lt;br /&gt;   ├── index               &lt;span class=&quot;token comment&quot;&gt;# Home page&lt;/span&gt;&lt;br /&gt;   │   ├── index.css         &lt;span class=&quot;token comment&quot;&gt;# Page style sheet&lt;/span&gt;&lt;br /&gt;   │   ├── index.js          &lt;span class=&quot;token comment&quot;&gt;# Page logic&lt;/span&gt;&lt;br /&gt;   │   ├── index.json        &lt;span class=&quot;token comment&quot;&gt;# Page configuration&lt;/span&gt;&lt;br /&gt;   │   └── index.html        &lt;span class=&quot;token comment&quot;&gt;# Page markup&lt;/span&gt;&lt;br /&gt;   └── other               &lt;span class=&quot;token comment&quot;&gt;# Other page&lt;/span&gt;&lt;br /&gt;       ├── other.css         &lt;span class=&quot;token comment&quot;&gt;# Page style sheet&lt;/span&gt;&lt;br /&gt;       ├── other.js          &lt;span class=&quot;token comment&quot;&gt;# Page logic&lt;/span&gt;&lt;br /&gt;       ├── other.json        &lt;span class=&quot;token comment&quot;&gt;# Page configuration&lt;/span&gt;&lt;br /&gt;       └── other.html        &lt;span class=&quot;token comment&quot;&gt;# Page markup&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;mini-app-lifecycle&quot;&gt;Mini app lifecycle &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-project-structure-lifecycle-and-bundling/#mini-app-lifecycle&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A mini app must be registered with the super app by calling the globally defined &lt;code&gt;App()&lt;/code&gt; method.
Referring to the project structure outlined &lt;a href=&quot;https://web.dev/mini-app-project-structure-lifecycle-and-bundling/#mini-app-project-structure&quot;&gt;before&lt;/a&gt;, this happens in
&lt;code&gt;app.js&lt;/code&gt;. The mini app lifecycle essentially consists of four events: &lt;code&gt;launch&lt;/code&gt;, &lt;code&gt;show&lt;/code&gt;, &lt;code&gt;hide&lt;/code&gt;, and
&lt;code&gt;error&lt;/code&gt;. Handlers for these events can be passed to the &lt;code&gt;App()&lt;/code&gt; method in the form of a
configuration object, which can also contain a &lt;code&gt;globalData&lt;/code&gt; property that holds data that should be
globally available across all pages.&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;/* app.js */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;App&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;onLaunch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;options&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;// Do something when the app is launched initially.&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 function&quot;&gt;onShow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;options&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;// Do something when the app is shown.&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 function&quot;&gt;onHide&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;// Do something when the app is hidden.&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 function&quot;&gt;onError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;msg&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;msg&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;span class=&quot;token literal-property property&quot;&gt;globalData&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;I am global data&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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As usual, the individual details vary, but the concept is the same for
&lt;a href=&quot;https://developers.weixin.qq.com/miniprogram/en/dev/reference/api/App.html&quot; rel=&quot;noopener&quot;&gt;WeChat&lt;/a&gt;,
&lt;a href=&quot;https://opendocs.alipay.com/mini/framework/app-detail&quot; rel=&quot;noopener&quot;&gt;Alipay&lt;/a&gt;,
&lt;a href=&quot;https://smartprogram.baidu.com/docs/develop/framework/app_service_register/&quot; rel=&quot;noopener&quot;&gt;Baidu&lt;/a&gt;,
&lt;a href=&quot;https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/framework/logic-layer/start-app/&quot; rel=&quot;noopener&quot;&gt;ByteDance&lt;/a&gt;,
and also
&lt;a href=&quot;https://doc.quickapp.cn/tutorial/framework/lifecycle.html#app-%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F&quot; rel=&quot;noopener&quot;&gt;Quick App&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;page-lifecycle&quot;&gt;Page lifecycle &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-project-structure-lifecycle-and-bundling/#page-lifecycle&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Similar to the app lifecycle, the page lifecycle, too, has lifecycle events that the developer can
listen for and react upon. These core events are &lt;code&gt;load&lt;/code&gt;, &lt;code&gt;show&lt;/code&gt;, &lt;code&gt;ready&lt;/code&gt;, &lt;code&gt;hide&lt;/code&gt;, and &lt;code&gt;unload&lt;/code&gt;. Some
platforms offer additional events like &lt;code&gt;pulldownrefresh&lt;/code&gt;. Setting up the event handlers happens in
the &lt;code&gt;Page()&lt;/code&gt; method that is defined for each page. For the &lt;code&gt;index&lt;/code&gt; or the &lt;code&gt;other&lt;/code&gt; pages from the
project structure &lt;a href=&quot;https://web.dev/mini-app-project-structure-lifecycle-and-bundling/#mini-app-project-structure&quot;&gt;before&lt;/a&gt;, this would happen in &lt;code&gt;index.js&lt;/code&gt; or
&lt;code&gt;other.js&lt;/code&gt; respectively.&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;/* index.js */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Page&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;data&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 literal-property property&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;This is page data.&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;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 parameter&quot;&gt;options&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;// Do something when the page is initially loaded.&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 function-variable function&quot;&gt;onShow&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;// Do something when the page is shown.&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 function-variable function&quot;&gt;onReady&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;// Do something when the page is ready.&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 function-variable function&quot;&gt;onHide&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;// Do something when the page is hidden.&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 function-variable function&quot;&gt;onUnload&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;// Do something when the page is closed.&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 function-variable function&quot;&gt;onPullDownRefresh&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;// Do something when the user pulls down to refresh.&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 function-variable function&quot;&gt;onReachBottom&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;// Do something when the user scrolls to the bottom.&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 function-variable function&quot;&gt;onShareAppMessage&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;// Do something when the user shares the page.&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 function-variable function&quot;&gt;onPageScroll&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;// Do something when the user scrolls the page.&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 function-variable function&quot;&gt;onResize&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;// Do something when the user resizes the page.&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 function&quot;&gt;onTabItemTap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;item&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;// Do something when the user taps the page&#39;s tab.&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;customData&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 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 string&quot;&gt;&quot;bar&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;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;h2 id=&quot;the-build-process&quot;&gt;The build process &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-project-structure-lifecycle-and-bundling/#the-build-process&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The build process of mini apps is abstracted away from the developer. Under the hood, it is using
industry tools like the &lt;a href=&quot;https://babeljs.io/&quot; rel=&quot;noopener&quot;&gt;Babel&lt;/a&gt; compiler for transpilation and minification and
the &lt;a href=&quot;https://postcss.org/&quot; rel=&quot;noopener&quot;&gt;postcss&lt;/a&gt; CSS transformer. The build experience is comparable to that of
&lt;a href=&quot;https://nextjs.org/&quot; rel=&quot;noopener&quot;&gt;Next.js&lt;/a&gt; or
&lt;a href=&quot;https://reactjs.org/docs/create-a-new-react-app.html&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;create-react-app&lt;/code&gt;&lt;/a&gt;, where developers, if
they explicitly choose not to, never touch the build parameters. The resulting processed files
are finally signed, encrypted, and packaged in one or several (sub)packages that then get uploaded
to the servers of the super app providers. Subpackages are meant for lazy loading, so a mini app
does not have to be downloaded all at once. The packaging details are meant to be private and are
not documented, but some package formats like WeChat&#39;s &lt;code&gt;wxapkg&lt;/code&gt; format have been
&lt;a href=&quot;https://github.com/sjatsh/unwxapkg&quot; rel=&quot;noopener&quot;&gt;reverse-engineered&lt;/a&gt;.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The next chapter provides insights on the &lt;a href=&quot;https://web.dev/mini-app-standardization/&quot;&gt;mini app standardization effort&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-project-structure-lifecycle-and-bundling/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://github.com/mihajlija&quot; rel=&quot;noopener&quot;&gt;Milica Mihajlija&lt;/a&gt;,
&lt;a href=&quot;https://github.com/alankent&quot; rel=&quot;noopener&quot;&gt;Alan Kent&lt;/a&gt;,
and Keith Gu.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Mini app standardization</title>
    <link href="https://web.dev/mini-app-standardization/"/>
    <updated>2021-03-03T00:00:00Z</updated>
    <id>https://web.dev/mini-app-standardization/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This post is part of an article collection where each article builds upon previous articles. If you just landed here, you may want to start reading from the &lt;a href=&quot;https://web.dev/mini-app-super-apps/&quot;&gt;beginning&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;mini-app-popularity&quot;&gt;Mini app popularity &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-standardization/#mini-app-popularity&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Mini apps have seen tremendous growth. WeChat mini apps as of June 2020 have reached
&lt;a href=&quot;https://www.questmobile.com.cn/research/report-new/122&quot; rel=&quot;noopener&quot;&gt;830 million active users&lt;/a&gt;, Alipay mini apps
&lt;a href=&quot;https://kr-asia.com/the-mau-of-wechat-alipay-and-baidus-mini-programs-now-add-up-to-more-than-1-billion&quot; rel=&quot;noopener&quot;&gt;401 million active users&lt;/a&gt;
as of April 2019, and Baidu mini apps in the same month
&lt;a href=&quot;https://kr-asia.com/the-mau-of-wechat-alipay-and-baidus-mini-programs-now-add-up-to-more-than-1-billion&quot; rel=&quot;noopener&quot;&gt;115 million active users&lt;/a&gt;.
Effectively companies have traded building apps for the two operating systems iOS and Android and
additionally the web for building apps for three or more
&lt;a href=&quot;https://web.dev/mini-app-super-apps/#for-mini-apps-you-need-super-apps&quot;&gt;super apps platforms&lt;/a&gt;.
The differences
between each super app platform may not be as big as the differences between Android, iOS, and the
web, but nevertheless they exist. Where on Android, iOS, and the web, we have seen cross-platform
approaches like &lt;a href=&quot;https://flutter.dev/&quot; rel=&quot;noopener&quot;&gt;Flutter&lt;/a&gt;, &lt;a href=&quot;https://ionicframework.com/&quot; rel=&quot;noopener&quot;&gt;Ionic&lt;/a&gt;, and
&lt;a href=&quot;https://reactnative.dev/&quot; rel=&quot;noopener&quot;&gt;React Native&lt;/a&gt; (&lt;a href=&quot;https://github.com/necolas/react-native-web&quot; rel=&quot;noopener&quot;&gt;for Web&lt;/a&gt;)
gain popularity, in the mini apps ecosystem, we can see an effort led by the
&lt;a href=&quot;https://www.w3.org/community/miniapps/&quot; rel=&quot;noopener&quot;&gt;MiniApps Ecosystem Community Group&lt;/a&gt; with
&lt;a href=&quot;https://www.w3.org/community/miniapps/participants&quot; rel=&quot;noopener&quot;&gt;members&lt;/a&gt; from, among others, Alibaba, Baidu,
ByteDance, Huawei, Intel, Xiaomi, China Mobile, Facebook, and Google to standardize aspects of mini
apps.&lt;/p&gt;
&lt;h2 id=&quot;publications&quot;&gt;Publications &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-standardization/#publications&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Notable publications of the group so far include a
&lt;a href=&quot;https://w3c.github.io/miniapp/white-paper/&quot; rel=&quot;noopener&quot;&gt;whitepaper&lt;/a&gt;, a
&lt;a href=&quot;https://www.w3.org/TR/mini-app-white-paper/comparison.html&quot; rel=&quot;noopener&quot;&gt;Comparison of APIs in MiniApps, W3C specs, and PWAs&lt;/a&gt;,
and specifications and explainers on the following aspects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URI Scheme: &lt;a href=&quot;https://w3c.github.io/miniapp/specs/uri/&quot; rel=&quot;noopener&quot;&gt;spec&lt;/a&gt;,
&lt;a href=&quot;https://github.com/w3c/miniapp/blob/gh-pages/specs/uri/docs/explainer.md&quot; rel=&quot;noopener&quot;&gt;explainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Lifecycle: &lt;a href=&quot;https://w3c.github.io/miniapp/specs/lifecycle/&quot; rel=&quot;noopener&quot;&gt;spec&lt;/a&gt;,
&lt;a href=&quot;https://github.com/w3c/miniapp/blob/gh-pages/specs/lifecycle/docs/explainer.md&quot; rel=&quot;noopener&quot;&gt;explainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Manifest: &lt;a href=&quot;https://w3c.github.io/miniapp/specs/manifest/&quot; rel=&quot;noopener&quot;&gt;spec&lt;/a&gt;,
&lt;a href=&quot;https://github.com/w3c/miniapp/blob/gh-pages/specs/manifest/docs/explainer.md&quot; rel=&quot;noopener&quot;&gt;explainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Packaging: &lt;a href=&quot;https://w3c.github.io/miniapp/specs/packaging/&quot; rel=&quot;noopener&quot;&gt;spec&lt;/a&gt;,
&lt;a href=&quot;https://github.com/w3c/miniapp/blob/gh-pages/specs/packaging/docs/explainer.md&quot; rel=&quot;noopener&quot;&gt;explainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;An exploration of &lt;a href=&quot;https://w3c.github.io/miniapp/specs/widget-req/&quot; rel=&quot;noopener&quot;&gt;widget requirements&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;W3C member and group participant Fuqiao Xue (W3C) has further published a
&lt;a href=&quot;https://xfq.github.io/miniapp-comparison/&quot; rel=&quot;noopener&quot;&gt;Comparison of MiniApps and web apps&lt;/a&gt; on his own behalf,
that is, not as an official group publication, but nonetheless worth the read.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The header of the MiniApp Standardization White Paper in a browser window.&quot; decoding=&quot;async&quot; height=&quot;540&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/NW2O3YZ3kxJPPeFr4Jw6.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The MiniApp Standardization White Paper.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;formal-launch-of-the-w3c-miniapps-working-group&quot;&gt;Formal launch of the W3C MiniApps Working Group &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-standardization/#formal-launch-of-the-w3c-miniapps-working-group&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On January 19, 2021, the &lt;a href=&quot;https://www.w3.org/2021/miniapps/&quot; rel=&quot;noopener&quot;&gt;MiniApps Working Group&lt;/a&gt; was &lt;a href=&quot;https://www.w3.org/blog/2021/01/w3c-launches-the-miniapps-working-group/&quot; rel=&quot;noopener&quot;&gt;formally launched&lt;/a&gt; in the W3C.
The group uses the spelling and capitalization &amp;quot;MiniApps&amp;quot; to distinguish the standardization effort from the technology.
You can read the group&#39;s &lt;a href=&quot;https://www.w3.org/2021/01/miniapps-wg-charter.html&quot; rel=&quot;noopener&quot;&gt;charter&lt;/a&gt; to get a feel for the planned work.
Leaders of the group introduced the effort as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Currently, there are many variants of MiniApps developed by different vendors with different APIs.
To enhance the interoperability between MiniApp platforms, mainstream MiniApp vendors including
Alibaba, Baidu, Huawei, and Xiaomi have been working together in the &lt;a href=&quot;https://www.w3.org/2018/chinese-web-ig/index.html&quot; rel=&quot;noopener&quot;&gt;W3C Chinese Web Interest Group&lt;/a&gt;
since May 2019 and published a &lt;a href=&quot;https://www.w3.org/TR/mini-app-white-paper/&quot; rel=&quot;noopener&quot;&gt;MiniApp Standardization White Paper&lt;/a&gt; in September 2019 as the initial standardization exploration for MiniApp technologies. As more global companies get interested in joining the MiniApp related discussion, the MiniApps Ecosystem Community Group launched during TPAC 2019 so that the global Web community can join the discussion.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Based on extensive standardization requirements, W3C today announced the formal establishment of the MiniApps Working Group,
dedicated to in-depth exploration and coordination of the diverse MiniApp ecosystem with W3C members and the public,
and enhancing the interoperability of different MiniApp platforms, thereby maximizing the integration of MiniApps and the Web,
reducing technical fragmentation and the learning cost of developers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;&amp;quot;Maximizing the integration of MiniApps and the Web&amp;quot;&lt;/em&gt; in particular sounds very interesting.
As a curious member of the group, I look forward to seeing where this endeavor is headed.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The following chapter looks at &lt;a href=&quot;https://web.dev/mini-app-alternative-runtime-environments/&quot;&gt;alternative mini app runtime environments&lt;/a&gt; apart from mobile devices. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-standardization/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://github.com/mihajlija&quot; rel=&quot;noopener&quot;&gt;Milica Mihajlija&lt;/a&gt;,
&lt;a href=&quot;https://github.com/alankent&quot; rel=&quot;noopener&quot;&gt;Alan Kent&lt;/a&gt;,
and Keith Gu.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Mini apps and super apps</title>
    <link href="https://web.dev/mini-app-super-apps/"/>
    <updated>2021-03-03T00:00:00Z</updated>
    <id>https://web.dev/mini-app-super-apps/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;welcome-to-mini-apps&quot;&gt;Welcome to mini apps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-super-apps/#welcome-to-mini-apps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you look at applications on your phone, you probably have specific apps for specific tasks.
You might have a banking app. You might have an app for buying public transit tickets. Likely you
have an app for getting directions, and many more specialized apps. This post introduces you to the
concept of a different kind of apps—mini apps—sometimes also called mini programs or applets.
You will first learn about the background of various mini app platforms and their developer experience, and
then focus on things the web can learn from mini apps. But before learning about mini apps, you first
need to learn about super apps.&lt;/p&gt;
&lt;h2 id=&quot;what-are-super-apps&quot;&gt;What are super apps? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-super-apps/#what-are-super-apps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Super apps serve as hosts to other apps that run within them: the so-called mini apps. Popular super apps are
&lt;a href=&quot;https://weixin.qq.com/&quot; rel=&quot;noopener&quot;&gt;WeChat&lt;/a&gt; (微信) by Tencent, &lt;a href=&quot;https://www.alipay.com/&quot; rel=&quot;noopener&quot;&gt;Alipay&lt;/a&gt; (支付宝) by Ant Group
(an affiliate company of the Chinese Alibaba Group), the app of the search engine &lt;a href=&quot;https://baidu.com/&quot; rel=&quot;noopener&quot;&gt;Baidu&lt;/a&gt; (百度),
as well as ByteDance&#39;s &lt;a href=&quot;https://www.douyin.com/&quot; rel=&quot;noopener&quot;&gt;Douyin&lt;/a&gt; (抖音), which you might know as TikTok (蒂克托克).
The first three are commonly also referred to as BAT, derived from &lt;strong&gt;B&lt;/strong&gt;(aidu)&lt;strong&gt;A&lt;/strong&gt;(libaba)&lt;strong&gt;T&lt;/strong&gt;(encent).
Super apps have taken the Chinese market by storm, which is why a lot of the examples in this article are Chinese.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;List of recently launched mini apps in the WeChat super app.&quot; decoding=&quot;async&quot; height=&quot;649&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/UKmUgG231MtQ2nEo1P0K.PNG?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/UKmUgG231MtQ2nEo1P0K.PNG?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/UKmUgG231MtQ2nEo1P0K.PNG?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/UKmUgG231MtQ2nEo1P0K.PNG?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/UKmUgG231MtQ2nEo1P0K.PNG?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/UKmUgG231MtQ2nEo1P0K.PNG?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/UKmUgG231MtQ2nEo1P0K.PNG?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/UKmUgG231MtQ2nEo1P0K.PNG?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/UKmUgG231MtQ2nEo1P0K.PNG?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/UKmUgG231MtQ2nEo1P0K.PNG?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/UKmUgG231MtQ2nEo1P0K.PNG?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;The WeChat super app showing recently launched mini apps.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;a-few-words-about-super-app-platforms&quot;&gt;A few words about super app platforms &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-super-apps/#a-few-words-about-super-app-platforms&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;WeChat aims to make itself a one-stop shop to meet almost any need users might have in their daily
lives. Alipay builds its platforms on top of its payment system, focusing on retail and financial
services, including credit, loan, insurance, installment, and local life services. Baidu strives to
transform its search engine from solely connecting people, services, and information into
information-as-a-service through mini programs for travel, retail, ads, payment, and more. Last but
not least Douyin wants to boost itself as a hub for social e-commerce and transform to more of an
entertainment and shopping platform.&lt;/p&gt;
&lt;h3 id=&quot;installing-super-apps&quot;&gt;Installing super apps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-super-apps/#installing-super-apps&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Super apps are available on multiple operating systems. Note that the versions available in
the official app stores may not always contain all features or be available in all locales. The
links below point to links that work universally, but that may require loading from untrusted
sources, so download and install the apps &lt;strong&gt;at your own risk&lt;/strong&gt;. You typically need to create an
account, which involves revealing your phone number. You might want to consider getting a burner
phone. Be advised that many super apps only allow you to create a so-called overseas account, which
does not have all features of a domestic account.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WeChat:&lt;/strong&gt; &lt;a href=&quot;https://apps.apple.com/us/app/wechat/id414478124&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;,
&lt;a href=&quot;https://weixin.qq.com/cgi-bin/readtemplate?uin=&amp;amp;stype=&amp;amp;promote=&amp;amp;fr=&amp;amp;lang=zh_CN&amp;amp;ADTAG=&amp;amp;check=false&amp;amp;t=weixin_download_method&amp;amp;sys=android&amp;amp;loc=weixin,android,web,0&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt;,
&lt;a href=&quot;https://mac.weixin.qq.com/&quot; rel=&quot;noopener&quot;&gt;macOS&lt;/a&gt;, &lt;a href=&quot;https://pc.weixin.qq.com/&quot; rel=&quot;noopener&quot;&gt;Windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Baidu:&lt;/strong&gt; &lt;a href=&quot;https://apps.apple.com/us/app/%E7%99%BE%E5%BA%A6/id382201985&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;,
&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.baidu.searchbox&amp;amp;hl=en&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alipay:&lt;/strong&gt; &lt;a href=&quot;https://itunes.apple.com/app/id333206289?mt=8&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;,
&lt;a href=&quot;https://t.alipayobjects.com/L1/71/100/and/alipay_wap_main.apk&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Douyin:&lt;/strong&gt;
&lt;a href=&quot;https://itunes.apple.com/cn/app/%E6%8A%96%E9%9F%B3%E7%9F%AD%E8%A7%86%E9%A2%91/id1142110895?l=zh&amp;amp;ls=1&amp;amp;mt=8&quot; rel=&quot;noopener&quot;&gt;iOS&lt;/a&gt;
(CN-only), &lt;a href=&quot;http://s.toutiao.com/UsMYE/&quot; rel=&quot;noopener&quot;&gt;Android&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Since the user interface of many super apps is Chinese-only, use the &lt;a href=&quot;https://translate.google.com/intl/en/about/#!#speak-with-the-world&quot;&gt;Google Translate app&lt;/a&gt; in camera mode with a secondary phone (given you have one) to understand what is going on if you do not speak Chinese. &lt;/div&gt;&lt;/aside&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A secondary phone running Google Translate in camera mode live-translating the user interface of a Chinese mini app running on the primary phone.&quot; decoding=&quot;async&quot; height=&quot;520&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSLjHjkFgscBC2j2d6j9.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSLjHjkFgscBC2j2d6j9.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSLjHjkFgscBC2j2d6j9.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSLjHjkFgscBC2j2d6j9.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSLjHjkFgscBC2j2d6j9.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSLjHjkFgscBC2j2d6j9.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSLjHjkFgscBC2j2d6j9.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSLjHjkFgscBC2j2d6j9.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSLjHjkFgscBC2j2d6j9.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSLjHjkFgscBC2j2d6j9.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSLjHjkFgscBC2j2d6j9.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    Using Google Translate in camera mode to live-translate a Chinese mini app.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Read on to &lt;a href=&quot;https://web.dev/mini-app-about/&quot;&gt;learn more about mini apps&lt;/a&gt; in the next chapter. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-super-apps/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://github.com/mihajlija&quot; rel=&quot;noopener&quot;&gt;Milica Mihajlija&lt;/a&gt;,
&lt;a href=&quot;https://github.com/alankent&quot; rel=&quot;noopener&quot;&gt;Alan Kent&lt;/a&gt;,
and Keith Gu.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>What are H5 and QuickApp?</title>
    <link href="https://web.dev/mini-app-what-are-h5-and-quickapp/"/>
    <updated>2021-03-03T00:00:00Z</updated>
    <id>https://web.dev/mini-app-what-are-h5-and-quickapp/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This post is part of an article collection where each article builds upon previous articles. If you just landed here, you may want to start reading from the &lt;a href=&quot;https://web.dev/mini-app-super-apps/&quot;&gt;beginning&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;what-mini-apps-are-not&quot;&gt;What mini apps are not &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-what-are-h5-and-quickapp/#what-mini-apps-are-not&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before I go into more detail on the developer experience of mini apps, I want to briefly mention and
set apart two technologies that come up in the context of mini apps, H5 and Quick App.&lt;/p&gt;
&lt;h2 id=&quot;h5&quot;&gt;H5 &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-what-are-h5-and-quickapp/#h5&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;H5 apps (or pages) are commonly seen as the predecessor of mini apps. What people mean by H5 is
essentially a well-designed mobile web app (or page) that can be shared easily on chat applications.
H5 is a reference to the HTML5 umbrella of technologies that includes responsive design, snappy CSS
animations, multimedia content, etc. The Chinese Wikipedia actually
&lt;a href=&quot;https://zh.wikipedia.org/wiki/H5&quot; rel=&quot;noopener&quot;&gt;redirects&lt;/a&gt; from H5 to HTML5. A good example of a representative
H5 page experience is the &lt;a href=&quot;https://panteng.github.io/wechat-h5-boilerplate/&quot; rel=&quot;noopener&quot;&gt;WeChat H5 boilerplate&lt;/a&gt;
project&#39;s demo.&lt;/p&gt;
&lt;h2 id=&quot;quick-app&quot;&gt;Quick App &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-what-are-h5-and-quickapp/#quick-app&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.quickapp.cn/&quot; rel=&quot;noopener&quot;&gt;Quick App&lt;/a&gt; is an industry alliance consisting of the following members:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.vivo.com.cn/&quot; rel=&quot;noopener&quot;&gt;vivo open platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://developer.huawei.com/cn/consumer&quot; rel=&quot;noopener&quot;&gt;Huawei Developer Alliance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://open.oppomobile.com/&quot; rel=&quot;noopener&quot;&gt;OPPO open platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mi.com/console/app/newapp.html&quot; rel=&quot;noopener&quot;&gt;Xiaomi Open Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://open.lenovo.com/developer/&quot; rel=&quot;noopener&quot;&gt;Lenovo Open Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://devquickapp.gionee.com/&quot; rel=&quot;noopener&quot;&gt;Gionee Open Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://open.flyme.cn/&quot; rel=&quot;noopener&quot;&gt;Meizu Open Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.ztems.com/&quot; rel=&quot;noopener&quot;&gt;ZTE Developer Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://developer.nubia.com/developer/view/index.html&quot; rel=&quot;noopener&quot;&gt;Nubian Open Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.oneplus.cn/&quot; rel=&quot;noopener&quot;&gt;OnePlus Open Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://dev.hismarttv.com/&quot; rel=&quot;noopener&quot;&gt;Hisense Open Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chinamobileltd.com/tc/global/home.php&quot; rel=&quot;noopener&quot;&gt;China Mobile Terminal Corporation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the technology of Quick App is comparable to &amp;quot;regular&amp;quot; mini apps (see
&lt;a href=&quot;https://web.dev/mini-app-about/#building-blocks-and-compatibility&quot;&gt;Building blocks and compatibility&lt;/a&gt;), the discovery of Quick App
is different. They are meant to be listed in stores, which come pre-installed on devices of the
manufacturers in the alliance, but can also be shared by means of a deep link (see the
&lt;a href=&quot;https://www.quickapp.cn/quickAppShow&quot; rel=&quot;noopener&quot;&gt;Quick App showcase&lt;/a&gt;). They do not run in the context of a
super app, but launch as seemingly self-contained full screen applications that feel deeply integrated into
device. What happens in the background is that they are opened in a full screen view rendered by the
operating system that provides the JavaScript bridge.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The next chapter covers the &lt;a href=&quot;https://web.dev/mini-app-devtools/&quot;&gt;developer experience of mini apps&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/mini-app-what-are-h5-and-quickapp/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://github.com/mihajlija&quot; rel=&quot;noopener&quot;&gt;Milica Mihajlija&lt;/a&gt;,
&lt;a href=&quot;https://github.com/alankent&quot; rel=&quot;noopener&quot;&gt;Alan Kent&lt;/a&gt;,
and Keith Gu.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Tabbed application mode for PWAs</title>
    <link href="https://web.dev/tabbed-application-mode/"/>
    <updated>2021-02-25T00:00:00Z</updated>
    <id>https://web.dev/tabbed-application-mode/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Tabbed application mode is part of the &lt;a href=&quot;https://developer.chrome.com/blog/fugu-status/&quot;&gt;capabilities project&lt;/a&gt; and is currently in development. This post will be updated as the implementation progresses. Tabbed application mode is an early-stage exploration of the Chrome team. It is not ready for production yet. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;In the world of computing, the &lt;a href=&quot;https://en.wikipedia.org/wiki/Desktop_metaphor&quot; rel=&quot;noopener&quot;&gt;desktop metaphor&lt;/a&gt; is
an interface metaphor that is a set of unifying concepts used by graphical user interfaces (GUI) to
help users interact more easily with the computer. In keeping with the desktop metaphor, GUI tabs
are modeled after traditional card tabs inserted in books, paper files, or card indexes. A &lt;em&gt;tabbed
document interface&lt;/em&gt; (TDI) or tab is a graphical control element that allows multiple documents or
panels to be contained within a single window, using tabs as a navigational widget for switching
between sets of documents.&lt;/p&gt;
&lt;p&gt;Progressive Web Apps can run in &lt;a href=&quot;https://web.dev/add-manifest/#display&quot;&gt;various display modes&lt;/a&gt; determined by the
&lt;code&gt;display&lt;/code&gt; property in the web app manifest. Examples are &lt;code&gt;fullscreen&lt;/code&gt;, &lt;code&gt;standalone&lt;/code&gt;, &lt;code&gt;minimal-ui&lt;/code&gt;,
and &lt;code&gt;browser&lt;/code&gt;. These display modes follow a
&lt;a href=&quot;https://w3c.github.io/manifest/#dfn-fallback-display-mode&quot; rel=&quot;noopener&quot;&gt;well-defined fallback chain&lt;/a&gt;
(&lt;code&gt;&amp;quot;fullscreen&amp;quot;&lt;/code&gt; → &lt;code&gt;&amp;quot;standalone&amp;quot;&lt;/code&gt; → &lt;code&gt;&amp;quot;minimal-ui&amp;quot;&lt;/code&gt; → &lt;code&gt;&amp;quot;browser&amp;quot;&lt;/code&gt;). If a browser does not support a
given mode, it falls back to the next display mode in the chain. Via the
&lt;a href=&quot;https://web.dev/display-override/&quot;&gt;&lt;code&gt;&amp;quot;display_override&amp;quot;&lt;/code&gt;&lt;/a&gt; property, developers can specify their own fallback chain
if they need to.&lt;/p&gt;
&lt;h2 id=&quot;what-is-tabbed-application-mode&quot;&gt;What is tabbed application mode &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#what-is-tabbed-application-mode&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Something that has been missing from the platform so far is a way to let PWA developers offer their
users a tabbed document interface, for example, to enable editing different files in the same PWA
window. Tabbed application mode closes this gap.&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; This feature is about having a standalone app window with multiple tabs (containing separate documents inside the app scope) inside it. It is not to be confused with the existing &lt;code&gt;&amp;quot;display&amp;quot;: &amp;quot;browser&amp;quot;&lt;/code&gt;, which has a separate meaning (specifically, that the app is opened in a regular browser tab). &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;suggested-use-cases-for-tabbed-application-mode&quot;&gt;Suggested use cases for tabbed application mode &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#suggested-use-cases-for-tabbed-application-mode&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Examples of sites that may use tabbed application mode include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Productivity apps that let the user edit more than one document (or file) at the same time.&lt;/li&gt;
&lt;li&gt;Communication apps that let the user have conversations in different rooms per tab.&lt;/li&gt;
&lt;li&gt;Reading apps that open article links in new in-app tabs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;differences-to-developer-built-tabs&quot;&gt;Differences to developer-built tabs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#differences-to-developer-built-tabs&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Having documents in separate browser tabs comes with resource isolation for free, which is not
possible using the web today. Developer-built tabs would not scale acceptably to hundreds of tabs
like browser tabs do. Browser affordances such as navigation history, &amp;quot;Copy this page URL&amp;quot;, &amp;quot;Cast
this tab&amp;quot; or &amp;quot;Open this page in a web browser&amp;quot; would be applied to the developer-built tabbed
interface page, but not the currently selected document page.&lt;/p&gt;
&lt;h3 id=&quot;differences-to-display-browser&quot;&gt;Differences to &lt;code&gt;&amp;quot;display&amp;quot;: &amp;quot;browser&amp;quot;&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#differences-to-display-browser&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The current &lt;code&gt;&amp;quot;display&amp;quot;: &amp;quot;browser&amp;quot;&lt;/code&gt; already has a
&lt;a href=&quot;https://w3c.github.io/manifest/#dom-displaymodetype-browser&quot; rel=&quot;noopener&quot;&gt;specific meaning&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Opens the web application using the platform-specific convention for opening hyperlinks in the
user agent (e.g., in a browser tab or a new window).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While browsers can do whatever they want regarding UI, it would clearly be a pretty big subversion
of developer expectations if &lt;code&gt;&amp;quot;display&amp;quot;: &amp;quot;browser&amp;quot;&lt;/code&gt; suddenly meant &amp;quot;run in a separate
application-specific window with no browser affordances, but a tabbed document interface&amp;quot;.&lt;/p&gt;
&lt;p&gt;Setting &lt;code&gt;&amp;quot;display&amp;quot;: &amp;quot;browser&amp;quot;&lt;/code&gt; is effectively the way you &lt;em&gt;opt out&lt;/em&gt; of being put into an application
window.&lt;/p&gt;
&lt;h2 id=&quot;current-status&quot;&gt;Current status &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#current-status&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt;
&lt;div class=&quot;table-wrapper scrollbar&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Create explainer&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/WICG/manifest-incubations/blob/gh-pages/tabbed-mode-explainer.md&quot; rel=&quot;noopener&quot;&gt;Completed&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Create initial draft of specification&lt;/td&gt;
&lt;td&gt;Not started&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Gather feedback &amp;amp; iterate on design&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://web.dev/tabbed-application-mode/#feedback&quot;&gt;In progress&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Origin trial&lt;/td&gt;
&lt;td&gt;Not started&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Launch&lt;/td&gt;
&lt;td&gt;Not started&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;using-tabbed-application-mode&quot;&gt;Using tabbed application mode &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#using-tabbed-application-mode&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To use tabbed application mode, developers need to opt their apps in by setting a specific
&lt;a href=&quot;https://web.dev/display-override/&quot;&gt;&lt;code&gt;&amp;quot;display_override&amp;quot;&lt;/code&gt;&lt;/a&gt; mode value in the web app manifest.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;display&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;standalone&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;&quot;display_override&quot;&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 string&quot;&gt;&quot;tabbed&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Next, the property &lt;code&gt;&amp;quot;tab_strip&amp;quot;&lt;/code&gt; can optionally be used to fine-tune the tab behavior. It has two
allowed sub-properties, &lt;code&gt;&amp;quot;home_tab&amp;quot;&lt;/code&gt; and &lt;code&gt;&amp;quot;new_tab_button&amp;quot;&lt;/code&gt;. If the &lt;code&gt;&amp;quot;tab_strip&amp;quot;&lt;/code&gt; property is not
present, the particular properties&#39; &lt;code&gt;&amp;quot;auto&amp;quot;&lt;/code&gt; values are used. The browser determines what values to
use for &lt;code&gt;&amp;quot;auto&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;home-tab&quot;&gt;Home tab &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#home-tab&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The home tab is a pinned tab that, if enabled for an app, should always be present when the app is
open. This tab should never navigate. Links clicked from this tab should open in a new app tab. Apps
can choose to customize the URL this tab is locked to and the icon displayed on the tab.&lt;/p&gt;
&lt;p&gt;The allowed values for &lt;code&gt;&amp;quot;home_tab&amp;quot;&lt;/code&gt; are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;auto&amp;quot;&lt;/code&gt; to let the browser determine what to do.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;absent&amp;quot;&lt;/code&gt; to tell the browser to not show a home tab.&lt;/li&gt;
&lt;li&gt;An object with two sub-properties:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;url&amp;quot;&lt;/code&gt; with the allowed values of &lt;code&gt;&amp;quot;auto&amp;quot;&lt;/code&gt; or a URL to lock the home tab to.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;icons&amp;quot;&lt;/code&gt; with the allowed values of &lt;code&gt;&amp;quot;auto&amp;quot;&lt;/code&gt; or an array of icons as in the main
&lt;a href=&quot;https://web.dev/add-manifest/#icons&quot;&gt;&lt;code&gt;&amp;quot;icons&amp;quot;&lt;/code&gt; property&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;new-tab-button&quot;&gt;New tab button &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#new-tab-button&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The new tab button, if present, should open a new tab at a URL that is within the scope of the app.
The app may choose to set a custom URL and icon for this button. Browsers can decide how to handle
dragging these tabs around to create new windows or combine with browser tabs.&lt;/p&gt;
&lt;p&gt;The allowed values for &lt;code&gt;&amp;quot;new_tab_button&amp;quot;&lt;/code&gt; are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;auto&amp;quot;&lt;/code&gt; to let the browser determine what to do.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;absent&amp;quot;&lt;/code&gt; to tell the browser to not show a new tab button.&lt;/li&gt;
&lt;li&gt;An object with two sub-properties:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;url&amp;quot;&lt;/code&gt; with the allowed values of &lt;code&gt;&amp;quot;auto&amp;quot;&lt;/code&gt; or an in-scope URL to open new tabs on.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;icons&amp;quot;&lt;/code&gt; with the allowed values of &lt;code&gt;&amp;quot;auto&amp;quot;&lt;/code&gt; or an array of icons as in the main
&lt;a href=&quot;https://web.dev/add-manifest/#icons&quot;&gt;&lt;code&gt;&amp;quot;icons&amp;quot;&lt;/code&gt; property&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;complete-example&quot;&gt;Complete example &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#complete-example&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A complete example to configure the behavior of an app with a tabbed interface may look as follows:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;display_override&quot;&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 string&quot;&gt;&quot;tabbed&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 property&quot;&gt;&quot;tab_strip&quot;&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 property&quot;&gt;&quot;home_tab&quot;&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 property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./home/&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;&quot;icons&quot;&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./home.svg&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;&quot;sizes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;any&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;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/svg+xml&quot;&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 property&quot;&gt;&quot;new_tab_button&quot;&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 property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./new-tab/&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;&quot;icons&quot;&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./new-tab.svg&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;&quot;sizes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;any&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;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/svg+xml&quot;&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;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;h3 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#demo&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can try tabbed application mode by setting a browser flag:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set the &lt;code&gt;#enable-desktop-pwas-tab-strip&lt;/code&gt; flag.&lt;/li&gt;
&lt;li&gt;Install the app &lt;a href=&quot;https://tabbed-application-mode.glitch.me/&quot; rel=&quot;noopener&quot;&gt;tabbed-application-mode.glitch.me&lt;/a&gt;
(&lt;a href=&quot;https://glitch.com/edit/#!/tabbed-application-mode?path=manifest.json&quot; rel=&quot;noopener&quot;&gt;source code&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Click the different links on the different tabs.&lt;/li&gt;
&lt;/ol&gt;
&lt;img alt=&quot;Screenshot of the tabbed application mode demo at tabbed-application-mode.glitch.me.&quot; decoding=&quot;async&quot; height=&quot;706&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/piz5g2gsqkkQMNmgJ1db.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;feedback&quot;&gt;Feedback &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#feedback&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Chrome team wants to hear about your experiences with tabbed application mode.&lt;/p&gt;
&lt;h3 id=&quot;tell-us-about-the-api-design&quot;&gt;Tell us about the API design &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#tell-us-about-the-api-design&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Is there something about tabbed application mode that does not work like you expected? Comment on
the &lt;a href=&quot;https://github.com/w3c/manifest/issues/737&quot; rel=&quot;noopener&quot;&gt;web app manifest Issue&lt;/a&gt; that we have created.&lt;/p&gt;
&lt;h3 id=&quot;report-a-problem-with-the-implementation&quot;&gt;Report a problem with the implementation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#report-a-problem-with-the-implementation&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Did you find a bug with Chrome&#39;s implementation? File a bug at
&lt;a href=&quot;https://new.crbug.com/&quot; rel=&quot;noopener&quot;&gt;new.crbug.com&lt;/a&gt;. Be sure to include as much detail as you can, simple
instructions for reproducing, and enter &lt;code&gt;UI&amp;gt;Browser&amp;gt;WebAppInstalls&lt;/code&gt; in the &lt;strong&gt;Components&lt;/strong&gt; box.
&lt;a href=&quot;https://glitch.com/&quot; rel=&quot;noopener&quot;&gt;Glitch&lt;/a&gt; works great for sharing quick and easy reproduction cases.&lt;/p&gt;
&lt;h3 id=&quot;show-support-for-the-api&quot;&gt;Show support for the API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#show-support-for-the-api&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Are you planning to use tabbed application mode? Your public support helps the Chrome team
prioritize features and shows other browser vendors how critical it is to support them.&lt;/p&gt;
&lt;p&gt;Send a tweet to &lt;a href=&quot;https://twitter.com/ChromiumDev&quot; rel=&quot;noopener&quot;&gt;@ChromiumDev&lt;/a&gt; using the hashtag
&lt;a href=&quot;https://twitter.com/search?q=%23TabbedApplicationMode&amp;amp;src=typed_query&amp;amp;f=live&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;#TabbedApplicationMode&lt;/code&gt;&lt;/a&gt;
and let us know where and how you are using it.&lt;/p&gt;
&lt;h2 id=&quot;useful-links&quot;&gt;Useful links &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#useful-links&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/WICG/manifest-incubations/blob/gh-pages/tabbed-mode-explainer.md&quot; rel=&quot;noopener&quot;&gt;Explainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/w3c/manifest/issues/737&quot; rel=&quot;noopener&quot;&gt;Web app manifest spec issue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://crbug.com/897314&quot; rel=&quot;noopener&quot;&gt;Chromium bug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Blink Component: &lt;a href=&quot;https://chromestatus.com/features#component%3ABlink%3EUI%3EBrowser%3EWebAppInstalls&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;UI&amp;gt;Browser&amp;gt;WebAppInstalls&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/tabbed-application-mode/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Tabbed application mode was explored by &lt;a href=&quot;https://github.com/mgiuca&quot; rel=&quot;noopener&quot;&gt;Matt Giuca&lt;/a&gt;. The experimental
implementation in Chrome was the work of &lt;a href=&quot;https://github.com/alancutter&quot; rel=&quot;noopener&quot;&gt;Alan Cutter&lt;/a&gt;. This article
was reviewed by &lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;. Hero image by
&lt;a href=&quot;https://commons.wikimedia.org/wiki/User:Till.niermann&quot; rel=&quot;noopener&quot;&gt;Till Niermann&lt;/a&gt; on
&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Dictionary_indents_headon.jpg&quot; rel=&quot;noopener&quot;&gt;Wikimedia Commons&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Streams—The definitive guide</title>
    <link href="https://web.dev/streams/"/>
    <updated>2021-02-19T00:00:00Z</updated>
    <id>https://web.dev/streams/</id>
    <content type="html" mode="escaped">&lt;p&gt;The Streams API allows you to programmatically access streams of data received over the network
or created by whatever means locally and
process them with JavaScript. Streaming involves breaking down a resource that you want to receive, send, or transform
into small chunks, and then processing these chunks bit by bit. While streaming is something
browsers do anyway when receiving assets like HTML or videos to be shown on webpages, this
capability has never been available to JavaScript before &lt;code&gt;fetch&lt;/code&gt; with streams was introduced in 2015.&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; Streaming was technically possible with &lt;code&gt;XMLHttpRequest&lt;/code&gt;, but it &lt;a href=&quot;https://gist.github.com/igrigorik/5736866&quot;&gt;really was not pretty&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Previously, if you wanted to process a resource of some kind (be it a video, or a text file, etc.),
you would have to download the entire file, wait for it to be deserialized into a suitable format,
and then process it. With streams being available to
JavaScript, this all changes. You can now process raw data with JavaScript progressively as
soon as it is available on the client, without needing to generate a buffer, string, or blob.
This unlocks a number of use cases, some of which I list below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Video effects:&lt;/strong&gt; piping a readable video stream through a transform stream that applies effects
in real time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data (de)compression:&lt;/strong&gt; piping a file stream through a transform stream that selectively
(de)compresses it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Image decoding:&lt;/strong&gt; piping an HTTP response stream through a transform stream that decodes bytes
into bitmap data, and then through another transform stream that translates bitmaps into PNGs. If
installed inside the &lt;code&gt;fetch&lt;/code&gt; handler of a service worker, this allows you to transparently polyfill
new image formats like AVIF.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;browser-support&quot;&gt;Browser support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#browser-support&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;readablestream-and-writablestream&quot;&gt;ReadableStream and WritableStream &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#readablestream-and-writablestream&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 43, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      43
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 65, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      65
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 14, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      14
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 10.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      10.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStream#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id=&quot;transformstream&quot;&gt;TransformStream &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#transformstream&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 67, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      67
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 102, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      102
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      79
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 14.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      14.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/TransformStream#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&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; For additional support, there is a &lt;a href=&quot;https://github.com/MattiasBuelens/web-streams-polyfill&quot;&gt;polyfill&lt;/a&gt; available. If possible, load the polyfill conditionally and only if the built-in feature is not available. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;core-concepts&quot;&gt;Core concepts &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#core-concepts&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before I go into details on the various types of streams, let me introduce some core concepts.&lt;/p&gt;
&lt;h3 id=&quot;chunks&quot;&gt;Chunks &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#chunks&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A chunk is a &lt;strong&gt;single piece of data&lt;/strong&gt; that is written to or read from a stream. It can be of any
type; streams can even contain chunks of different types. Most of the time, a chunk will not be the most atomic
unit of data for a given stream. For example, a byte stream might contain chunks consisting of 16
KiB &lt;code&gt;Uint8Array&lt;/code&gt; units, instead of single bytes.&lt;/p&gt;
&lt;h3 id=&quot;readable-streams&quot;&gt;Readable streams &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#readable-streams&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A readable stream represents a source of data from which you can read. In other words, data &lt;strong&gt;comes
out&lt;/strong&gt; of a readable stream. Concretely, a readable stream is an instance of the &lt;code&gt;ReadableStream&lt;/code&gt;
class.&lt;/p&gt;
&lt;h3 id=&quot;writable-streams&quot;&gt;Writable streams &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#writable-streams&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A writable stream represents a destination for data into which you can write. In other words, data
&lt;strong&gt;goes in&lt;/strong&gt; to a writable stream. Concretely, a writable stream is an instance of the
&lt;code&gt;WritableStream&lt;/code&gt; class.&lt;/p&gt;
&lt;h3 id=&quot;transform-streams&quot;&gt;Transform streams &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#transform-streams&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A transform stream consists of a &lt;strong&gt;pair of streams&lt;/strong&gt;: a writable stream, known as its writable side,
and a readable stream, known as its readable side.
A real-world metaphor for this would be a
&lt;a href=&quot;https://en.wikipedia.org/wiki/Simultaneous_interpretation&quot; rel=&quot;noopener&quot;&gt;simultaneous interpreter&lt;/a&gt;
who translates from one language to another on-the-fly.
In a manner specific to the transform stream, writing
to the writable side results in new data being made available for reading from the
readable side. Concretely, any object with a &lt;code&gt;writable&lt;/code&gt; property and a &lt;code&gt;readable&lt;/code&gt; property can serve
as a transform stream. However, the standard &lt;code&gt;TransformStream&lt;/code&gt; class makes it easier to create
such a pair that is properly entangled.&lt;/p&gt;
&lt;h3 id=&quot;pipe-chains&quot;&gt;Pipe chains &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#pipe-chains&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Streams are primarily used by &lt;strong&gt;piping&lt;/strong&gt; them to each other. A readable stream can be piped directly
to a writable stream, using the readable stream&#39;s &lt;code&gt;pipeTo()&lt;/code&gt; method, or it can be piped through one
or more transform streams first, using the readable stream&#39;s &lt;code&gt;pipeThrough()&lt;/code&gt; method. A &lt;strong&gt;set of
streams piped together&lt;/strong&gt; in this way is referred to as a pipe chain.&lt;/p&gt;
&lt;h3 id=&quot;backpressure&quot;&gt;Backpressure &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#backpressure&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Once a pipe chain is constructed, it will propagate signals regarding how fast chunks should flow
through it. If any step in the chain cannot yet accept chunks, it propagates a signal backwards
through the pipe chain, until eventually the original source is told to stop producing chunks so
fast. This process of &lt;strong&gt;normalizing flow&lt;/strong&gt; is called backpressure.&lt;/p&gt;
&lt;h3 id=&quot;teeing&quot;&gt;Teeing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#teeing&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A readable stream can be teed (named after the shape of an uppercase &#39;T&#39;) using its &lt;code&gt;tee()&lt;/code&gt; method.
This will &lt;strong&gt;lock&lt;/strong&gt; the stream, that is, make it no longer directly usable; however, it will create &lt;strong&gt;two new
streams&lt;/strong&gt;, called branches, which can be consumed independently.
Teeing also is important because streams cannot be rewound or restarted, more about this later.&lt;/p&gt;
&lt;figure&gt;
  &lt;!-- Original source file located at https://drive.google.com/file/d/17apgoyo6E5RAA_nwwM5Xg4FGiMr8y3mk/view?usp=sharing --&gt;
  &lt;img alt=&quot;Diagram of a pipe chain consisting of a readable stream coming from a call to the fetch API that is then piped through a transform stream whose output is teed and then sent to the browser for the first resulting readable stream and to the service worker cache for the second resulting readable stream.&quot; decoding=&quot;async&quot; height=&quot;430&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/M70SLIvXhMkYfxDm5b98.svg&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;A pipe chain.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-mechanics-of-a-readable-stream&quot;&gt;The mechanics of a readable stream &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-mechanics-of-a-readable-stream&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A readable stream is a data source represented in JavaScript by a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStream&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ReadableStream&lt;/code&gt;&lt;/a&gt; object that
flows from an underlying source. The
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStream/ReadableStream&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ReadableStream()&lt;/code&gt;&lt;/a&gt;
constructor creates and returns a readable stream object from the given handlers. There are two
types of underlying source:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Push sources&lt;/strong&gt; constantly push data at you when you have accessed them, and it is up to you to
start, pause, or cancel access to the stream. Examples include live video streams, server-sent events,
or WebSockets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pull sources&lt;/strong&gt; require you to explicitly request data from them once connected to. Examples
include HTTP operations via &lt;code&gt;fetch()&lt;/code&gt; or &lt;code&gt;XMLHttpRequest&lt;/code&gt; calls.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stream data is read sequentially in small pieces called &lt;strong&gt;chunks&lt;/strong&gt;.
The chunks placed in a stream are said to be &lt;strong&gt;enqueued&lt;/strong&gt;. This means they are waiting in a queue
ready to be read. An &lt;strong&gt;internal queue&lt;/strong&gt; keeps track of the chunks that have not yet been read.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;queuing strategy&lt;/strong&gt; is an object that determines how a stream should signal backpressure based on
the state of its internal queue. The queuing strategy assigns a size to each chunk, and compares the
total size of all chunks in the queue to a specified number, known as the &lt;strong&gt;high water mark&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The chunks inside the stream are read by a &lt;strong&gt;reader&lt;/strong&gt;. This reader retrieves the data one chunk at a
time, allowing you to do whatever kind of operation you want to do on it. The reader plus the other
processing code that goes along with it is called a &lt;strong&gt;consumer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The next construct in this context is called a &lt;strong&gt;controller&lt;/strong&gt;. Each readable stream has an associated
controller that, as the name suggests, allows you to control the stream.&lt;/p&gt;
&lt;p&gt;Only one reader can read a stream at a time; when a reader is created and starts reading a stream
(that is, becomes an &lt;strong&gt;active reader&lt;/strong&gt;), it is &lt;strong&gt;locked&lt;/strong&gt; to it. If you want another reader to take over
reading your stream, you typically need to &lt;strong&gt;release&lt;/strong&gt; the first reader before you do anything else
(although you can &lt;strong&gt;tee&lt;/strong&gt; streams).&lt;/p&gt;
&lt;h3 id=&quot;creating-a-readable-stream&quot;&gt;Creating a readable stream &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#creating-a-readable-stream&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You create a readable stream by calling its constructor
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStream/ReadableStream&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ReadableStream()&lt;/code&gt;&lt;/a&gt;.
The constructor has an optional argument &lt;code&gt;underlyingSource&lt;/code&gt;, which represents an object
with methods and properties that define how the constructed stream instance will behave.&lt;/p&gt;
&lt;h4 id=&quot;the-underlyingsource&quot;&gt;The &lt;code&gt;underlyingSource&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-underlyingsource&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;This can use the following optional, developer-defined methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;start(controller)&lt;/code&gt;: Called immediately when the object is constructed. The
method can access the stream source, and do anything else
required to set up the stream functionality. If this process is to be done asynchronously, the method can
return a promise to signal success or failure. The &lt;code&gt;controller&lt;/code&gt; parameter passed to this method is
a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ReadableStreamDefaultController&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pull(controller)&lt;/code&gt;: Can be used to control the stream as more chunks are fetched. It
is called repeatedly as long as the stream&#39;s internal queue of chunks is not full, up until the queue
reaches its high water mark. If the result of calling &lt;code&gt;pull()&lt;/code&gt; is a promise,
&lt;code&gt;pull()&lt;/code&gt; will not be called again until said  promise fulfills.
If the promise rejects, the stream will become errored.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cancel(reason)&lt;/code&gt;: Called when the stream consumer cancels the stream.&lt;/li&gt;
&lt;/ul&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;const&lt;/span&gt; readableStream &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;ReadableStream&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;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;/* … */&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 function&quot;&gt;pull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;/* … */&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 function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;reason&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;/* … */&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 &lt;code&gt;ReadableStreamDefaultController&lt;/code&gt; supports the following methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/close&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ReadableStreamDefaultController.close()&lt;/code&gt;&lt;/a&gt;
closes the associated stream.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/enqueue&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ReadableStreamDefaultController.enqueue()&lt;/code&gt;&lt;/a&gt;
enqueues a given chunk in the associated stream.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/error&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ReadableStreamDefaultController.error()&lt;/code&gt;&lt;/a&gt;
causes any future interactions with the associated stream to error.&lt;/li&gt;
&lt;/ul&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;/* … */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;  controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;The first chunk!&#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;span class=&quot;token comment&quot;&gt;/* … */&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h4 id=&quot;the-queuingstrategy&quot;&gt;The &lt;code&gt;queuingStrategy&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-queuingstrategy&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The second, likewise optional, argument of the &lt;code&gt;ReadableStream()&lt;/code&gt; constructor is &lt;code&gt;queuingStrategy&lt;/code&gt;.
It is an object that optionally defines a queuing strategy for the stream, which takes two
parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;highWaterMark&lt;/code&gt;: A non-negative number indicating the high water mark of the stream using this queuing strategy.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size(chunk)&lt;/code&gt;: A function that computes and returns the finite non-negative size of the given chunk value.
The result is used to determine backpressure, manifesting via the appropriate &lt;code&gt;ReadableStreamDefaultController.desiredSize&lt;/code&gt; property.
It also governs when the underlying source&#39;s &lt;code&gt;pull()&lt;/code&gt; method is called.&lt;/li&gt;
&lt;/ul&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;const&lt;/span&gt; readableStream &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;ReadableStream&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;/* … */&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;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;highWaterMark&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&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;size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;chunk&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; chunk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&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;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; You could define your own custom &lt;code&gt;queuingStrategy&lt;/code&gt;, or use an instance of &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy&quot;&gt;&lt;code&gt;ByteLengthQueuingStrategy&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy&quot;&gt;&lt;code&gt;CountQueuingStrategy&lt;/code&gt;&lt;/a&gt; for this object&#39;s value. If no &lt;code&gt;queuingStrategy&lt;/code&gt; is supplied, the default used is the same as a &lt;code&gt;CountQueuingStrategy&lt;/code&gt; with a &lt;code&gt;highWaterMark&lt;/code&gt; of &lt;code&gt;1&lt;/code&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;the-getreader-and-read-methods&quot;&gt;The &lt;code&gt;getReader()&lt;/code&gt; and &lt;code&gt;read()&lt;/code&gt; methods &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-getreader-and-read-methods&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To read from a readable stream, you need a reader, which will be a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ReadableStreamDefaultReader&lt;/code&gt;&lt;/a&gt;.
The &lt;code&gt;getReader()&lt;/code&gt; method of the &lt;code&gt;ReadableStream&lt;/code&gt; interface creates a reader and locks the stream to
it. While the stream is locked, no other reader can be acquired until this one is released.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/read&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;read()&lt;/code&gt;&lt;/a&gt;
method of the &lt;code&gt;ReadableStreamDefaultReader&lt;/code&gt; interface returns a promise providing access to the next
chunk in the stream&#39;s internal queue. It fulfills or rejects with a result depending on the state of
the stream. The different possibilities are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If a chunk is available, the promise will be fulfilled with an object of the form&lt;br /&gt;
&lt;code&gt;{ value: chunk, done: false }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the stream becomes closed, the promise will be fulfilled with an object of the form&lt;br /&gt;
&lt;code&gt;{ value: undefined, done: true }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the stream becomes errored, the promise will be rejected with the relevant error.&lt;/li&gt;
&lt;/ul&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;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readableStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReader&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;while&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;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; done&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 operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;done&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;The stream is done.&#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;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;  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;Just read a chunk:&#39;&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 punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h4 id=&quot;the-locked-property&quot;&gt;The &lt;code&gt;locked&lt;/code&gt; property &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-locked-property&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You can check if a readable stream is locked by accessing its
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStream/locked&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ReadableStream.locked&lt;/code&gt;&lt;/a&gt;
property.&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;const&lt;/span&gt; locked &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readableStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;locked&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 template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;The stream is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;locked &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;indeed&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;not&#39;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; locked.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;h3 id=&quot;readable-stream-code-samples&quot;&gt;Readable stream code samples &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#readable-stream-code-samples&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The code sample below shows all the steps in action. You first create a &lt;code&gt;ReadableStream&lt;/code&gt; that in its
&lt;code&gt;underlyingSource&lt;/code&gt; argument (that is, the &lt;code&gt;TimestampSource&lt;/code&gt; class) defines a &lt;code&gt;start()&lt;/code&gt; method.
This method tells the stream&#39;s &lt;code&gt;controller&lt;/code&gt; to
&lt;code&gt;enqueue()&lt;/code&gt; a timestamp every second during ten seconds.
Finally, it tells the controller to &lt;code&gt;close()&lt;/code&gt; the stream. You consume this
stream by creating a reader via the &lt;code&gt;getReader()&lt;/code&gt; method and calling &lt;code&gt;read()&lt;/code&gt; until the stream is
&lt;code&gt;done&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;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TimestampSource&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  #interval&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;#interval &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 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;=&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;const&lt;/span&gt; string &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;Date&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;toLocaleTimeString&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;// Add the string to the stream.&lt;/span&gt;&lt;br /&gt;      controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;string&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 template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Enqueued &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;string&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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 number&quot;&gt;1_000&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 function&quot;&gt;setTimeout&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;=&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;clearInterval&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;#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 comment&quot;&gt;// Close the stream after 10s.&lt;/span&gt;&lt;br /&gt;      controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&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 number&quot;&gt;10_000&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 function&quot;&gt;cancel&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;// This is called if the reader cancels.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;clearInterval&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;#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;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;const&lt;/span&gt; stream &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;ReadableStream&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;TimestampSource&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;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;concatStringStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;stream&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;let&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&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;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; stream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReader&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;while&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;    &lt;span class=&quot;token comment&quot;&gt;// The `read()` method returns a promise that&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// resolves when a value has been received.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; done&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 operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;// Result objects contain two properties:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// `done`  - `true` if the stream has already given you all its data.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// `value` - Some data. Always `undefined` when `done` is `true`.&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;done&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    result &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; value&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 template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Read &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; characters so far&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;    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 template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Most recently read chunk: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;value&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;br /&gt;&lt;span class=&quot;token function&quot;&gt;concatStringStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stream&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;then&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 parameter&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&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;Stream complete&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&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;h3 id=&quot;asynchronous-iteration&quot;&gt;Asynchronous iteration &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#asynchronous-iteration&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Checking upon each &lt;code&gt;read()&lt;/code&gt; loop iteration if the stream is &lt;code&gt;done&lt;/code&gt; may not be the most convenient API.
Luckily there will soon be a better way to do this: asynchronous iteration.&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 keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; chunk &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; stream&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;chunk&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;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Asynchronous iteration is not yet implemented in any browser. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;A workaround to use asynchronous iteration today is to implement the behavior with a polyfill.&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;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ReadableStream&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;Symbol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;asyncIterator&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 class-name&quot;&gt;ReadableStream&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;Symbol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;asyncIterator&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;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&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;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reader &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;getReader&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;      &lt;span class=&quot;token keyword&quot;&gt;while&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;        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;done&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 operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;done&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 keyword&quot;&gt;yield&lt;/span&gt; value&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 keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;releaseLock&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;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;teeing-a-readable-stream&quot;&gt;Teeing a readable stream &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#teeing-a-readable-stream&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStream/tee&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;tee()&lt;/code&gt;&lt;/a&gt; method of the
&lt;code&gt;ReadableStream&lt;/code&gt; interface tees the current readable stream, returning a two-element array
containing the two resulting branches as new &lt;code&gt;ReadableStream&lt;/code&gt; instances. This allows
two readers to read a stream simultaneously. You might do this, for example, in a service worker if
you want to fetch a response from the server and stream it to the browser, but also stream it to the
service worker cache. Since a response body cannot be consumed more than once, you need two copies
to do this. To cancel the stream, you then need to cancel both resulting branches. Teeing a stream
will generally lock it for the duration, preventing other readers from locking it.&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;const&lt;/span&gt; readableStream &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;ReadableStream&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;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;// Called by constructor.&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;[start]&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;a&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;b&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;c&#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;span class=&quot;token function&quot;&gt;pull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;// Called `read()` when the controller&#39;s queue is empty.&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;[pull]&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;d&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&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;br /&gt;  &lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;reason&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;// Called when the stream is canceled.&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;[cancel]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reason&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;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;// Create two `ReadableStream`s.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;streamA&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; streamB&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readableStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tee&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;// Read streamA iteratively one by one. Typically, you&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// would not do it this way, but you certainly can.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; readerA &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; streamA&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReader&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;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;[A]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; readerA&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;span class=&quot;token comment&quot;&gt;//=&gt; {value: &quot;a&quot;, done: false}&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;[A]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; readerA&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;span class=&quot;token comment&quot;&gt;//=&gt; {value: &quot;b&quot;, done: false}&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;[A]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; readerA&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;span class=&quot;token comment&quot;&gt;//=&gt; {value: &quot;c&quot;, done: false}&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;[A]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; readerA&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;span class=&quot;token comment&quot;&gt;//=&gt; {value: &quot;d&quot;, done: false}&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;[A]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; readerA&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;span class=&quot;token comment&quot;&gt;//=&gt; {value: undefined, done: true}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Read streamB in a loop. This is the more common way&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// to read data from the stream.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; readerB &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; streamB&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReader&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;while&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;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; readerB&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;done&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&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;  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;[B]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&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;readable-byte-streams&quot;&gt;Readable byte streams &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#readable-byte-streams&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For streams representing bytes, an extended version of the readable stream is provided to handle
bytes efficiently, in particular by minimizing copies. Byte streams allow for bring-your-own-buffer
(BYOB) readers to be acquired. The default implementation can give a range of different outputs such
as strings or array buffers in the case of WebSockets, whereas byte streams guarantee byte output.
In addition, BYOB readers have stability benefits. This is
because if a buffer detaches, it can guarantee that one does not write into the same buffer twice,
hence avoiding race conditions. BYOB readers can reduce the number of times the browser needs to run
garbage collection, because it can reuse buffers.&lt;/p&gt;
&lt;h3 id=&quot;creating-a-readable-byte-stream&quot;&gt;Creating a readable byte stream &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#creating-a-readable-byte-stream&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can create a readable byte stream by passing an additional &lt;code&gt;type&lt;/code&gt; parameter to the
&lt;code&gt;ReadableStream()&lt;/code&gt; constructor.&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;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReadableStream&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;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;bytes&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h4 id=&quot;the-underlyingsource-2&quot;&gt;The &lt;code&gt;underlyingSource&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-underlyingsource-2&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The underlying source of a readable byte stream is given a &lt;code&gt;ReadableByteStreamController&lt;/code&gt; to
manipulate. Its &lt;code&gt;ReadableByteStreamController.enqueue()&lt;/code&gt; method takes a &lt;code&gt;chunk&lt;/code&gt; argument whose value
is an &lt;code&gt;ArrayBufferView&lt;/code&gt;. The property &lt;code&gt;ReadableByteStreamController.byobRequest&lt;/code&gt; returns the current
BYOB pull request, or null if there is none. Finally, the &lt;code&gt;ReadableByteStreamController.desiredSize&lt;/code&gt;
property returns the desired size to fill the controlled stream&#39;s internal queue.&lt;/p&gt;
&lt;h3 id=&quot;the-queuingstrategy-2&quot;&gt;The &lt;code&gt;queuingStrategy&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-queuingstrategy-2&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The second, likewise optional, argument of the &lt;code&gt;ReadableStream()&lt;/code&gt; constructor is &lt;code&gt;queuingStrategy&lt;/code&gt;.
It is an object that optionally defines a queuing strategy for the stream, which takes one
parameter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;highWaterMark&lt;/code&gt;: A non-negative number of bytes indicating the high water mark of the stream using this queuing strategy.
This is used to determine backpressure, manifesting via the appropriate &lt;code&gt;ReadableByteStreamController.desiredSize&lt;/code&gt; property.
It also governs when the underlying source&#39;s &lt;code&gt;pull()&lt;/code&gt; method is called.&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Unlike queuing strategies for other stream types, a queuing strategy for a readable byte stream does not have a &lt;code&gt;size(chunk)&lt;/code&gt; function. The size of each chunk is always determined by its &lt;code&gt;byteLength&lt;/code&gt; property. &lt;/div&gt;&lt;/aside&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 no &lt;code&gt;queuingStrategy&lt;/code&gt; is supplied, the default used is one with a &lt;code&gt;highWaterMark&lt;/code&gt; of &lt;code&gt;0&lt;/code&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;the-getreader-and-read-methods-2&quot;&gt;The &lt;code&gt;getReader()&lt;/code&gt; and &lt;code&gt;read()&lt;/code&gt; methods &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-getreader-and-read-methods-2&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You can then get access to a &lt;code&gt;ReadableStreamBYOBReader&lt;/code&gt; by setting the &lt;code&gt;mode&lt;/code&gt; parameter accordingly:
&lt;code&gt;ReadableStream.getReader({ mode: &amp;quot;byob&amp;quot; })&lt;/code&gt;. This allows for more precise control over buffer
allocation in order to avoid copies. To read from the byte stream, you need to call
&lt;code&gt;ReadableStreamBYOBReader.read(view)&lt;/code&gt;, where &lt;code&gt;view&lt;/code&gt; is an
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ArrayBufferView&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ArrayBufferView&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;readable-byte-stream-code-sample&quot;&gt;Readable byte stream code sample &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#readable-byte-stream-code-sample&quot;&gt;#&lt;/a&gt;&lt;/h4&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;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readableStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReader&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;mode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;byob&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; startingAB &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;ArrayBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1_024&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;const&lt;/span&gt; buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readInto&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;startingAB&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;&quot;The first 1024 bytes, or less:&quot;&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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readInto&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;let&lt;/span&gt; offset &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;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;offset &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;byteLength&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;const&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; view&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; done &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;Uint8Array&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; offset&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;byteLength &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; offset&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;    buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; view&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 keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;done&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;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;    offset &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; view&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;byteLength&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;return&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The following function returns readable byte streams that allow for efficient zero-copy reading of a
randomly generated array. Instead of using a predetermined chunk size of 1,024, it attempts to fill
the developer-supplied buffer, allowing for full control.&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;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_CHUNK_SIZE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1_024&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;makeReadableByteStream&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReadableStream&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;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;bytes&#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 function&quot;&gt;pull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;// Even when the consumer is using the default reader,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// the auto-allocation feature allocates a buffer and&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// passes it to us via `byobRequest`.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; view &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;byobRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;view&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      view &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRandomValues&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;view&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;      controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;byobRequest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;respond&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;view&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;byteLength&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 literal-property property&quot;&gt;autoAllocateChunkSize&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT_CHUNK_SIZE&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;the-mechanics-of-a-writable-stream&quot;&gt;The mechanics of a writable stream &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-mechanics-of-a-writable-stream&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A writable stream is a destination into which you can write data, represented in JavaScript by a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WritableStream&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;WritableStream&lt;/code&gt;&lt;/a&gt; object. This
serves as an abstraction over the top of an &lt;strong&gt;underlying sink&lt;/strong&gt;—a lower-level I/O sink into which
raw data is written.&lt;/p&gt;
&lt;p&gt;The data is written to the stream via a &lt;strong&gt;writer&lt;/strong&gt;, one chunk at a time. A chunk can take a
multitude of forms, just like the chunks in a reader. You can use whatever code you like to produce
the chunks ready for writing; the writer plus the associated code is called a &lt;strong&gt;producer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When a writer is created and starts writing to a stream (an &lt;strong&gt;active writer&lt;/strong&gt;), it is said to be
&lt;strong&gt;locked&lt;/strong&gt; to it. Only one writer can write to a writable stream at one time. If you want another
writer to start writing to your stream, you typically need to release it, before you then attach
another writer to it.&lt;/p&gt;
&lt;p&gt;An &lt;strong&gt;internal queue&lt;/strong&gt; keeps track of the chunks that have been written to the stream but not yet
been processed by the underlying sink.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;queuing strategy&lt;/strong&gt; is an object that determines how a stream should signal backpressure based on
the state of its internal queue. The queuing strategy assigns a size to each chunk, and compares the
total size of all chunks in the queue to a specified number, known as the &lt;strong&gt;high water mark&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The final construct is called a &lt;strong&gt;controller&lt;/strong&gt;. Each writable stream has an associated controller that
allows you to control the stream (for example, to abort it).&lt;/p&gt;
&lt;h3 id=&quot;creating-a-writable-stream&quot;&gt;Creating a writable stream &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#creating-a-writable-stream&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WritableStream&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;WritableStream&lt;/code&gt;&lt;/a&gt; interface of
the Streams API provides a standard abstraction for writing streaming data to a destination, known
as a sink. This object comes with built-in backpressure and queuing. You create a writable stream by
calling its constructor
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WritableStream/WritableStream&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;WritableStream()&lt;/code&gt;&lt;/a&gt;.
It has an optional &lt;code&gt;underlyingSink&lt;/code&gt; parameter, which represents an object
with methods and properties that define how the constructed stream instance will behave.&lt;/p&gt;
&lt;h4 id=&quot;the-underlyingsink&quot;&gt;The &lt;code&gt;underlyingSink&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-underlyingsink&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;underlyingSink&lt;/code&gt; can include the following optional, developer-defined methods. The &lt;code&gt;controller&lt;/code&gt;
parameter passed to some of the methods is a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;WritableStreamDefaultController&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;start(controller)&lt;/code&gt;: This method is called immediately when the object is constructed. The
contents of this method should aim to get access to the underlying sink. If this process is to be
done asynchronously, it can return a promise to signal success or failure.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;write(chunk, controller)&lt;/code&gt;: This method will be called when a new chunk of data (specified in the
&lt;code&gt;chunk&lt;/code&gt; parameter) is ready to be written to the underlying sink. It can return a promise to
signal success or failure of the write operation. This method will be called only after previous
writes have succeeded, and never after the stream is closed or aborted.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;close(controller)&lt;/code&gt;: This method will be called if the app signals that it has finished writing
chunks to the stream. The contents should do whatever is necessary to finalize writes to the
underlying sink, and release access to it. If this process is asynchronous, it can return a
promise to signal success or failure. This method will be called only after all queued-up writes
have succeeded.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;abort(reason)&lt;/code&gt;: This method will be called if the app signals that it wishes to abruptly close
the stream and put it in an errored state. It can clean up any held resources, much like
&lt;code&gt;close()&lt;/code&gt;, but &lt;code&gt;abort()&lt;/code&gt; will be called even if writes are queued up. Those chunks will be thrown
away. If this process is asynchronous, it can return a promise to signal success or failure. The
&lt;code&gt;reason&lt;/code&gt; parameter contains a &lt;code&gt;DOMString&lt;/code&gt; describing why the stream was aborted.&lt;/li&gt;
&lt;/ul&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;const&lt;/span&gt; writableStream &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;WritableStream&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;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;/* … */&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 function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;chunk&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; controller&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;/* … */&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 function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;/* … */&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 function&quot;&gt;abort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;reason&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;/* … */&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
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;WritableStreamDefaultController&lt;/code&gt;&lt;/a&gt;
interface of the Streams API represents a controller allowing control of a &lt;code&gt;WritableStream&lt;/code&gt;&#39;s state
during set up, as more chunks are submitted for writing, or at the end of writing. When constructing
a &lt;code&gt;WritableStream&lt;/code&gt;, the underlying sink is given a corresponding &lt;code&gt;WritableStreamDefaultController&lt;/code&gt;
instance to manipulate. The &lt;code&gt;WritableStreamDefaultController&lt;/code&gt; has only one method:
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/error&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;WritableStreamDefaultController.error()&lt;/code&gt;&lt;/a&gt;,
which causes any future interactions with the associated stream to error.
&lt;code&gt;WritableStreamDefaultController&lt;/code&gt; also supports a &lt;code&gt;signal&lt;/code&gt; property which returns an instance of
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/AbortSignal&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;AbortSignal&lt;/code&gt;&lt;/a&gt;,
allowing a &lt;code&gt;WritableStream&lt;/code&gt; operation to be stopped if needed.&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;/* … */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;chunk&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; controller&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;    &lt;span class=&quot;token comment&quot;&gt;// Try to do something dangerous with `chunk`.&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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* … */&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h4 id=&quot;the-queuingstrategy-3&quot;&gt;The &lt;code&gt;queuingStrategy&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-queuingstrategy-3&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The second, likewise optional, argument of the &lt;code&gt;WritableStream()&lt;/code&gt; constructor is &lt;code&gt;queuingStrategy&lt;/code&gt;.
It is an object that optionally defines a queuing strategy for the stream, which takes two
parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;highWaterMark&lt;/code&gt;: A non-negative number indicating the high water mark of the stream using this queuing strategy.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size(chunk)&lt;/code&gt;: A function that computes and returns the finite non-negative size of the given chunk value.
The result is used to determine backpressure, manifesting via the appropriate &lt;code&gt;WritableStreamDefaultWriter.desiredSize&lt;/code&gt; property.&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; You could define your own custom &lt;code&gt;queuingStrategy&lt;/code&gt;, or use an instance of &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy&quot;&gt;&lt;code&gt;ByteLengthQueuingStrategy&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy&quot;&gt;&lt;code&gt;CountQueuingStrategy&lt;/code&gt;&lt;/a&gt; for this object value. If no &lt;code&gt;queuingStrategy&lt;/code&gt; is supplied, the default used is the same as a &lt;code&gt;CountQueuingStrategy&lt;/code&gt; with a &lt;code&gt;highWaterMark&lt;/code&gt; of &lt;code&gt;1&lt;/code&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;the-getwriter-and-write-methods&quot;&gt;The &lt;code&gt;getWriter()&lt;/code&gt; and &lt;code&gt;write()&lt;/code&gt; methods &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-getwriter-and-write-methods&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To write to a writable stream, you need a writer, which will be a
&lt;code&gt;WritableStreamDefaultWriter&lt;/code&gt;. The &lt;code&gt;getWriter()&lt;/code&gt; method of the &lt;code&gt;WritableStream&lt;/code&gt; interface returns a
new instance of &lt;code&gt;WritableStreamDefaultWriter&lt;/code&gt; and locks the stream to that instance. While the
stream is locked, no other writer can be acquired until the current one is released.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/write&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;write()&lt;/code&gt;&lt;/a&gt;
method of the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;WritableStreamDefaultWriter&lt;/code&gt;&lt;/a&gt;
interface writes a passed chunk of data to a &lt;code&gt;WritableStream&lt;/code&gt; and its underlying sink, then returns
a promise that resolves to indicate the success or failure of the write operation. Note that what
&amp;quot;success&amp;quot; means is up to the underlying sink; it might indicate that the chunk has been accepted,
and not necessarily that it is safely saved to its ultimate 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 keyword&quot;&gt;const&lt;/span&gt; writer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; writableStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getWriter&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;const&lt;/span&gt; resultPromise &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; writer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;The first chunk!&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h4 id=&quot;the-locked-property-2&quot;&gt;The &lt;code&gt;locked&lt;/code&gt; property &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-locked-property-2&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You can check if a writable stream is locked by accessing its
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WritableStream/locked&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;WritableStream.locked&lt;/code&gt;&lt;/a&gt;
property.&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;const&lt;/span&gt; locked &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; writableStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;locked&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 template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;The stream is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;locked &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;indeed&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;not&#39;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; locked.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;h3 id=&quot;writable-stream-code-sample&quot;&gt;Writable stream code sample &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#writable-stream-code-sample&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The code sample below shows all steps in action.&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;const&lt;/span&gt; writableStream &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;WritableStream&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;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;[start]&#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;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;chunk&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; controller&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;[write]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; chunk&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;// Wait for next write.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&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 parameter&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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 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;=&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;textContent &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; chunk&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;resolve&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 number&quot;&gt;1_000&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;br /&gt;  &lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;[close]&#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;span class=&quot;token function&quot;&gt;abort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;reason&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;[abort]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reason&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;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;const&lt;/span&gt; writer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; writableStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getWriter&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;const&lt;/span&gt; start &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;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; char &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;abcdefghijklmnopqrstuvwxyz&#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;// Wait to add to the write queue.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; writer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ready&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;[ready]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&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; start&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ms&#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;// The Promise is resolved after the write finishes.&lt;/span&gt;&lt;br /&gt;  writer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;char&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;await&lt;/span&gt; writer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&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;h3 id=&quot;piping-a-readable-stream-to-a-writable-stream&quot;&gt;Piping a readable stream to a writable stream &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#piping-a-readable-stream-to-a-writable-stream&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A readable stream can be piped to a writable stream through the readable stream&#39;s
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeTo&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;pipeTo()&lt;/code&gt;&lt;/a&gt; method.
&lt;code&gt;ReadableStream.pipeTo()&lt;/code&gt; pipes the current &lt;code&gt;ReadableStream&lt;/code&gt;to a given &lt;code&gt;WritableStream&lt;/code&gt; and returns a
promise that fulfills when the piping process completes successfully, or rejects if any errors were
encountered.&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;const&lt;/span&gt; readableStream &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;ReadableStream&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;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;// Called by constructor.&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;[start readable]&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;a&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;b&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;c&#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;span class=&quot;token function&quot;&gt;pull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;// Called when controller&#39;s queue is empty.&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;[pull]&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;d&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&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;br /&gt;  &lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;reason&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;// Called when the stream is canceled.&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;[cancel]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reason&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;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;const&lt;/span&gt; writableStream &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;WritableStream&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;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;// Called by constructor&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;[start writable]&#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;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;chunk&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; controller&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;// Called upon writer.write()&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;[write]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; chunk&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;// Wait for next write.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&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 parameter&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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 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;=&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;textContent &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; chunk&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;resolve&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 number&quot;&gt;1_000&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;br /&gt;  &lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;[close]&#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;span class=&quot;token function&quot;&gt;abort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;reason&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;[abort]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reason&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;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;await&lt;/span&gt; readableStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipeTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;writableStream&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;[finished]&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;creating-a-transform-stream&quot;&gt;Creating a transform stream &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#creating-a-transform-stream&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;TransformStream&lt;/code&gt; interface of the Streams API represents a set of transformable data. You
create a transform stream by calling its constructor &lt;code&gt;TransformStream()&lt;/code&gt;, which creates and returns
a transform stream object from the given handlers. The &lt;code&gt;TransformStream()&lt;/code&gt; constructor accepts as
its first argument an optional JavaScript object representing the &lt;code&gt;transformer&lt;/code&gt;. Such objects can
contain any of the following methods:&lt;/p&gt;
&lt;h3 id=&quot;the-transformer&quot;&gt;The &lt;code&gt;transformer&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-transformer&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;start(controller)&lt;/code&gt;: This method is called immediately when the object is constructed. Typically
this is used to enqueue prefix chunks, using &lt;code&gt;controller.enqueue()&lt;/code&gt;. Those chunks will be read
from the readable side but do not depend on any writes to the writable side. If this initial
process is asynchronous, for example because it takes some effort to acquire the prefix chunks,
the function can return a promise to signal success or failure; a rejected promise will error the
stream. Any thrown exceptions will be re-thrown by the &lt;code&gt;TransformStream()&lt;/code&gt; constructor.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;transform(chunk, controller)&lt;/code&gt;: This method is called when a new chunk originally written to the
writable side is ready to be transformed. The stream implementation guarantees that this function
will be called only after previous transforms have succeeded, and never before &lt;code&gt;start()&lt;/code&gt; has
completed or after &lt;code&gt;flush()&lt;/code&gt; has been called. This function performs the actual transformation
work of the transform stream. It can enqueue the results using &lt;code&gt;controller.enqueue()&lt;/code&gt;. This
permits a single chunk written to the writable side to result in zero or multiple chunks on the
readable side, depending on how many times &lt;code&gt;controller.enqueue()&lt;/code&gt; is called. If the process of
transforming is asynchronous, this function can return a promise to signal success or failure of
the transformation. A rejected promise will error both the readable and writable sides of the
transform stream. If no &lt;code&gt;transform()&lt;/code&gt; method is supplied, the identity transform is used, which
enqueues chunks unchanged from the writable side to the readable side.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flush(controller)&lt;/code&gt;: This method is called after all chunks written to the writable side have been
transformed by successfully passing through &lt;code&gt;transform()&lt;/code&gt;, and the writable side is about to be
closed. Typically this is used to enqueue suffix chunks to the readable side, before that too
becomes closed. If the flushing process is asynchronous, the function can return a promise to
signal success or failure; the result will be communicated to the caller of
&lt;code&gt;stream.writable.write()&lt;/code&gt;. Additionally, a rejected promise will error both the readable and
writable sides of the stream. Throwing an exception is treated the same as returning a rejected
promise.&lt;/li&gt;
&lt;/ul&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;const&lt;/span&gt; transformStream &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;TransformStream&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;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;/* … */&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 function&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;chunk&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; controller&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;/* … */&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 function&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;/* … */&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;h3 id=&quot;the-writablestrategy-and-readablestrategy-queueing-strategies&quot;&gt;The &lt;code&gt;writableStrategy&lt;/code&gt; and &lt;code&gt;readableStrategy&lt;/code&gt; queueing strategies &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#the-writablestrategy-and-readablestrategy-queueing-strategies&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The second and third optional parameters of the &lt;code&gt;TransformStream()&lt;/code&gt; constructor are optional
&lt;code&gt;writableStrategy&lt;/code&gt; and &lt;code&gt;readableStrategy&lt;/code&gt; queueing strategies. They are defined as outlined in the
&lt;a href=&quot;https://web.dev/streams/#the-queuingstrategy&quot;&gt;readable&lt;/a&gt; and the &lt;a href=&quot;https://web.dev/streams/#the-queuingstrategy-3&quot;&gt;writable&lt;/a&gt; stream
sections respectively.&lt;/p&gt;
&lt;h3 id=&quot;transform-stream-code-sample&quot;&gt;Transform stream code sample &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#transform-stream-code-sample&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The following code sample shows a simple transform stream in action.&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;// Note that `TextEncoderStream` and `TextDecoderStream` exist now.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// This example shows how you would have done it before.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; textEncoderStream &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;TransformStream&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;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;chunk&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; controller&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;[transform]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; chunk&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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&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;TextEncoder&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;encode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;chunk&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;br /&gt;  &lt;span class=&quot;token function&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;[flush]&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;terminate&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;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;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;const&lt;/span&gt; readStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; textEncoderStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;readable&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; writeStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; textEncoderStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;writable&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;const&lt;/span&gt; writer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; writeStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getWriter&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;const&lt;/span&gt; char &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;abc&#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;    writer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;char&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;  writer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&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;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReader&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;let&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;done&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;    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;[value]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&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 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;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;piping-a-readable-stream-through-a-transform-stream&quot;&gt;Piping a readable stream through a transform stream &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#piping-a-readable-stream-through-a-transform-stream&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeThrough&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;pipeThrough()&lt;/code&gt;&lt;/a&gt;
method of the &lt;code&gt;ReadableStream&lt;/code&gt; interface provides a chainable way of piping the current stream
through a transform stream or any other writable/readable pair. Piping a stream will generally lock
it for the duration of the pipe, preventing other readers from locking it.&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;const&lt;/span&gt; transformStream &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;TransformStream&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;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;chunk&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; controller&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;[transform]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; chunk&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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&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;TextEncoder&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;encode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;chunk&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;br /&gt;  &lt;span class=&quot;token function&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;[flush]&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;terminate&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;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;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; readableStream &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;ReadableStream&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;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;// called by constructor&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;[start]&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;a&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;b&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;c&#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;span class=&quot;token function&quot;&gt;pull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;controller&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;// called read when controller&#39;s queue is empty&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;[pull]&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;d&#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;    controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&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;// or controller.error();&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 function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;reason&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;// called when rs.cancel(reason)&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;[cancel]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reason&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;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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readableStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipeThrough&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transformStream&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;getReader&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;let&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;done&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;    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;[value]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&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 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;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 next code sample (a bit contrived) shows how you could implement a &amp;quot;shouting&amp;quot; version of &lt;code&gt;fetch()&lt;/code&gt;
that uppercases all text by consuming the returned response promise
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Streams_API/Using_readable_streams#consuming_a_fetch_as_a_stream&quot; rel=&quot;noopener&quot;&gt;as a stream&lt;/a&gt;
and uppercasing chunk by chunk. The advantage of this approach is that you do not need to wait for
the whole document to be downloaded, which can make a huge difference when dealing with large files.&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;upperCaseStream&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TransformStream&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;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;chunk&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; controller&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;      controller&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;chunk&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toUpperCase&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 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;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;appendToDOMStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;el&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WritableStream&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;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;chunk&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;      el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;chunk&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 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 function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./lorem-ipsum.txt&#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 function&quot;&gt;then&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 parameter&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;br /&gt;  response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipeThrough&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;TextDecoderStream&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 function&quot;&gt;pipeThrough&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;upperCaseStream&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 function&quot;&gt;pipeTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendToDOMStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&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 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;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The demo below shows readable, writable, and transform streams in action. It also includes examples
of &lt;code&gt;pipeThrough()&lt;/code&gt; and &lt;code&gt;pipeTo()&lt;/code&gt; pipe chains, and also demonstrates &lt;code&gt;tee()&lt;/code&gt;. You can optionally run
the &lt;a href=&quot;https://streams-demo.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt; in its own window or view the
&lt;a href=&quot;https://glitch.com/edit/#!/streams-demo?path=script.js&quot; rel=&quot;noopener&quot;&gt;source code&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 420px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/streams-demo?attributionHidden=true&amp;sidebarCollapsed=true&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;streams-demo on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;useful-streams-available-in-the-browser&quot;&gt;Useful streams available in the browser &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#useful-streams-available-in-the-browser&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are a number of useful streams built right into the browser. You can easily create a
&lt;code&gt;ReadableStream&lt;/code&gt; from a blob. The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Blob&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Blob&lt;/code&gt;&lt;/a&gt;
interface&#39;s &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Blob/stream&quot; rel=&quot;noopener&quot;&gt;stream()&lt;/a&gt; method returns
a &lt;code&gt;ReadableStream&lt;/code&gt; which upon reading returns the data contained within the blob. Also recall that a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/File&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;File&lt;/code&gt;&lt;/a&gt; object is a specific kind of a
&lt;code&gt;Blob&lt;/code&gt;, and can be used in any context that a blob can.&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;const&lt;/span&gt; readableStream &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;Blob&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 string&quot;&gt;&#39;hello world&#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 punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text/plain&#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 punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&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 streaming variants of &lt;code&gt;TextDecoder.decode()&lt;/code&gt; and &lt;code&gt;TextEncoder.encode()&lt;/code&gt; are called
&lt;a href=&quot;https://encoding.spec.whatwg.org/#interface-textdecoderstream&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;TextDecoderStream&lt;/code&gt;&lt;/a&gt; and
&lt;a href=&quot;https://encoding.spec.whatwg.org/#interface-textencoderstream&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;TextEncoderStream&lt;/code&gt;&lt;/a&gt; respectively.&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;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://streams.spec.whatwg.org/&#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;const&lt;/span&gt; decodedStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&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;pipeThrough&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;TextDecoderStream&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;Compressing or decompressing a file is easy with the
&lt;a href=&quot;https://wicg.github.io/compression/#compression-stream&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;CompressionStream&lt;/code&gt;&lt;/a&gt; and
&lt;a href=&quot;https://wicg.github.io/compression/#decompression-stream&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;DecompressionStream&lt;/code&gt;&lt;/a&gt; transform streams
respectively. The code sample below shows how you can download the Streams spec, compress (gzip) it
right in the browser, and write the compressed file directly to disk.&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;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://streams.spec.whatwg.org/&#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;const&lt;/span&gt; readableStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; compressedStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readableStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipeThrough&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;CompressionStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;gzip&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fileHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;showSaveFilePicker&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;const&lt;/span&gt; writableStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; fileHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWritable&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;compressedStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipeTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;writableStream&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 &lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt;&#39;s
&lt;a href=&quot;https://wicg.github.io/file-system-access/#filesystemwritablefilestream&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileSystemWritableFileStream&lt;/code&gt;&lt;/a&gt;
and the experimental &lt;a href=&quot;https://web.dev/fetch-upload-streaming/#writable-streams&quot;&gt;&lt;code&gt;fetch()&lt;/code&gt; request streams&lt;/a&gt; are
examples of writable streams in the wild.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/serial/&quot;&gt;Serial API&lt;/a&gt; makes heavy use of both readable and writable streams.&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;// Prompt user to select any serial port.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; port &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestPort&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;// Wait for the serial port to open.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; port&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 punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;baudRate&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9_600&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;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; port&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;readable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReader&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;// Listen to data coming from the serial device.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;while&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;  &lt;span class=&quot;token keyword&quot;&gt;const&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; done &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;await&lt;/span&gt; reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;done&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;// Allow the serial port to be closed later.&lt;/span&gt;&lt;br /&gt;    reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;releaseLock&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;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 comment&quot;&gt;// value is a Uint8Array.&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;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 punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Write to the serial port.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; writer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; port&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;writable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getWriter&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;const&lt;/span&gt; data &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;Uint8Array&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;104&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;101&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;108&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;111&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;// hello&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; writer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&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;// Allow the serial port to be closed later.&lt;/span&gt;&lt;br /&gt;writer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;releaseLock&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;Finally, the &lt;a href=&quot;https://web.dev/websocketstream/&quot;&gt;&lt;code&gt;WebSocketStream&lt;/code&gt;&lt;/a&gt; API integrates streams with the WebSocket API.&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;const&lt;/span&gt; wss &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;WebSocketStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;WSS_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;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; readable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; writable &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;await&lt;/span&gt; wss&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReader&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;const&lt;/span&gt; writer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; writable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getWriter&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;while&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;  &lt;span class=&quot;token keyword&quot;&gt;const&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; done &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;await&lt;/span&gt; reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;done&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;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 keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;process&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 keyword&quot;&gt;await&lt;/span&gt; writer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&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;useful-resources&quot;&gt;Useful resources &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#useful-resources&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://streams.spec.whatwg.org/&quot; rel=&quot;noopener&quot;&gt;Streams specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://streams.spec.whatwg.org/demos/&quot; rel=&quot;noopener&quot;&gt;Accompanying demos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/MattiasBuelens/web-streams-polyfill&quot; rel=&quot;noopener&quot;&gt;Streams polyfill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jakearchibald.com/2016/streams-ftw/&quot; rel=&quot;noopener&quot;&gt;2016—the year of web streams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jakearchibald.com/2017/async-iterators-and-generators/&quot; rel=&quot;noopener&quot;&gt;Async iterators and generators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://surma.dev/lab/whatwg-stream-visualizer/lab.html&quot; rel=&quot;noopener&quot;&gt;Stream Visualizer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/streams/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://jakearchibald.com/&quot; rel=&quot;noopener&quot;&gt;Jake Archibald&lt;/a&gt;,
&lt;a href=&quot;https://github.com/beaufortfrancois&quot; rel=&quot;noopener&quot;&gt;François Beaufort&lt;/a&gt;,
&lt;a href=&quot;https://samdutton.com/&quot; rel=&quot;noopener&quot;&gt;Sam Dutton&lt;/a&gt;,
&lt;a href=&quot;https://github.com/MattiasBuelens&quot; rel=&quot;noopener&quot;&gt;Mattias Buelens&lt;/a&gt;,
&lt;a href=&quot;https://surma.dev/&quot; rel=&quot;noopener&quot;&gt;Surma&lt;/a&gt;,
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;, and
&lt;a href=&quot;https://github.com/ricea&quot; rel=&quot;noopener&quot;&gt;Adam Rice&lt;/a&gt;.
&lt;a href=&quot;https://jakearchibald.com/&quot; rel=&quot;noopener&quot;&gt;Jake Archibald&lt;/a&gt;&#39;s blog posts have helped me a lot in understanding
streams. Some of the code samples are inspired by GitHub user
&lt;a href=&quot;https://gist.github.com/bellbind/f6a7ba88e9f1a9d749fec4c9289163ac&quot; rel=&quot;noopener&quot;&gt;@bellbind&lt;/a&gt;&#39;s explorations and
parts of the prose build heavily on the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Streams_API&quot; rel=&quot;noopener&quot;&gt;MDN Web Docs on Streams&lt;/a&gt;. The
&lt;a href=&quot;https://streams.spec.whatwg.org/&quot; rel=&quot;noopener&quot;&gt;Streams Standard&lt;/a&gt;&#39;s
&lt;a href=&quot;https://github.com/whatwg/streams/graphs/contributors&quot; rel=&quot;noopener&quot;&gt;authors&lt;/a&gt; have done a tremendous job on
writing this spec. Hero image by &lt;a href=&quot;https://unsplash.com/@ryanlara&quot; rel=&quot;noopener&quot;&gt;Ryan Lara&lt;/a&gt; on
&lt;a href=&quot;https://unsplash.com/&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Deprecating Excalidraw Electron in favor of the web version</title>
    <link href="https://web.dev/deprecating-excalidraw-electron/"/>
    <updated>2021-01-07T00:00:00Z</updated>
    <id>https://web.dev/deprecating-excalidraw-electron/</id>
    <content type="html" mode="escaped">&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; &lt;a href=&quot;https://excalidraw.com/&quot;&gt;Excalidraw&lt;/a&gt; is a virtual collaborative whiteboard that lets you easily sketch diagrams that feel hand-drawn. This article was cross-posted to and first appeared on the &lt;a href=&quot;https://blog.excalidraw.com/deprecating-excalidraw-electron/&quot;&gt;Excalidraw blog&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;On the &lt;a href=&quot;https://github.com/excalidraw&quot; rel=&quot;noopener&quot;&gt;Excalidraw project&lt;/a&gt;, we have decided to
deprecate &lt;a href=&quot;https://github.com/excalidraw/excalidraw-desktop&quot; rel=&quot;noopener&quot;&gt;Excalidraw Desktop&lt;/a&gt;, an
&lt;a href=&quot;https://www.electronjs.org/&quot; rel=&quot;noopener&quot;&gt;Electron&lt;/a&gt; wrapper for Excalidraw, in favor of the web version that you
can—and always could—find at &lt;a href=&quot;https://excalidraw.com/&quot; rel=&quot;noopener&quot;&gt;excalidraw.com&lt;/a&gt;. After a careful analysis, we
have decided that &lt;a href=&quot;https://web.dev/pwa/&quot;&gt;Progressive Web App&lt;/a&gt; (PWA) is the future we want to build
upon. Read on to learn why.&lt;/p&gt;
&lt;h2 id=&quot;how-excalidraw-desktop-came-into-being&quot;&gt;How Excalidraw Desktop came into being &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/deprecating-excalidraw-electron/#how-excalidraw-desktop-came-into-being&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Soon after &lt;a href=&quot;https://twitter.com/vjeux&quot; rel=&quot;noopener&quot;&gt;@vjeux&lt;/a&gt; created the initial version of Excalidraw in
January 2020 and &lt;a href=&quot;https://blog.excalidraw.com/reflections-on-excalidraw/&quot; rel=&quot;noopener&quot;&gt;blogged about it&lt;/a&gt;, he proposed the following in
&lt;a href=&quot;https://github.com/excalidraw/excalidraw/issues/561#issue-555138343&quot; rel=&quot;noopener&quot;&gt;Issue #561&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Would be great to wrap Excalidraw within Electron (or equivalent) and publish it as a [platform-specific]
application to the various app stores.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The immediate reaction by &lt;a href=&quot;https://github.com/voluntadpear&quot; rel=&quot;noopener&quot;&gt;@voluntadpear&lt;/a&gt; was to suggest:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What about making it a PWA instead? Android currently supports adding them to the Play Store as
Trusted Web Activities and hopefully iOS will do the same soon. On Desktop, Chrome lets you
download a desktop shortcut to a PWA.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The decision that &lt;a href=&quot;https://github.com/vjeux&quot; rel=&quot;noopener&quot;&gt;@vjeux&lt;/a&gt; took in the end was simple:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We should do both :)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While work on converting the version of Excalidraw into a PWA was started by
&lt;a href=&quot;https://github.com/voluntadpear&quot; rel=&quot;noopener&quot;&gt;@voluntadpear&lt;/a&gt; and later others,
&lt;a href=&quot;https://github.com/lipis&quot; rel=&quot;noopener&quot;&gt;@lipis&lt;/a&gt; independently
&lt;a href=&quot;https://github.com/excalidraw/excalidraw/issues/561#issuecomment-579573783&quot; rel=&quot;noopener&quot;&gt;went ahead&lt;/a&gt; and created
a &lt;a href=&quot;https://github.com/excalidraw/excalidraw-desktop&quot; rel=&quot;noopener&quot;&gt;separate repo&lt;/a&gt; for Excalidraw Desktop.&lt;/p&gt;
&lt;p&gt;To this day, the initial goal set by &lt;a href=&quot;https://github.com/vjeux&quot; rel=&quot;noopener&quot;&gt;@vjeux&lt;/a&gt;, that is, to submit
Excalidraw to the various app stores, has not been reached yet. Honestly, no one has even started
the submission process to any of the stores. But why is that? Before I answer, let&#39;s
look at Electron, the platform.&lt;/p&gt;
&lt;h2 id=&quot;what-is-electron&quot;&gt;What is Electron? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/deprecating-excalidraw-electron/#what-is-electron&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The unique selling point of &lt;a href=&quot;https://www.electronjs.org/&quot; rel=&quot;noopener&quot;&gt;Electron&lt;/a&gt; is that it allows you to &lt;em&gt;&amp;quot;build
cross-platform desktop apps with JavaScript, HTML, and CSS&amp;quot;&lt;/em&gt;. Apps built with Electron are
&lt;em&gt;&amp;quot;compatible with Mac, Windows, and Linux&amp;quot;&lt;/em&gt;, that is, &lt;em&gt;&amp;quot;Electron apps build and run on three
platforms&amp;quot;&lt;/em&gt;. According to the homepage, the hard parts that Electron makes easy are
&lt;a href=&quot;https://www.electronjs.org/docs/api/auto-updater&quot; rel=&quot;noopener&quot;&gt;automatic updates&lt;/a&gt;,
&lt;a href=&quot;https://www.electronjs.org/docs/api/menu&quot; rel=&quot;noopener&quot;&gt;system-level menus and notifications&lt;/a&gt;,
&lt;a href=&quot;https://www.electronjs.org/docs/api/crash-reporter&quot; rel=&quot;noopener&quot;&gt;crash reporting&lt;/a&gt;,
&lt;a href=&quot;https://www.electronjs.org/docs/api/content-tracing&quot; rel=&quot;noopener&quot;&gt;debugging and profiling&lt;/a&gt;, and
&lt;a href=&quot;https://www.electronjs.org/docs/api/auto-updater#windows&quot; rel=&quot;noopener&quot;&gt;Windows installers&lt;/a&gt;. Turns out, some of
the promised features need a detailed look at the small print.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;For example, automatic updates &lt;em&gt;&amp;quot;are [currently] only [supported] on macOS and Windows. There is
no built-in support for auto-updater on Linux, so it is recommended to use the distribution&#39;s
package manager to update your app&amp;quot;&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Developers can create system-level menus by calling &lt;code&gt;Menu.setApplicationMenu(menu)&lt;/code&gt;. On Windows and
Linux, the menu will be set as each window&#39;s top menu, while on macOS there are many
system-defined standard menus, like the
&lt;a href=&quot;https://developer.apple.com/documentation/appkit/nsapplication/1428608-servicesmenu?language=objc&quot; rel=&quot;noopener&quot;&gt;Services&lt;/a&gt;
menu. To make one&#39;s menus a standard menu, developers should set their menu&#39;s &lt;code&gt;role&lt;/code&gt; accordingly,
and Electron will recognize them and make them become standard menus. This means that a lot of
menu-related code will use the following platform check:
&lt;code&gt;const isMac = process.platform === &#39;darwin&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Windows installers can be made with
&lt;a href=&quot;https://github.com/electron/windows-installer&quot; rel=&quot;noopener&quot;&gt;windows-installer&lt;/a&gt;. The README of the project
highlights that &lt;em&gt;&amp;quot;for a production app you need to sign your application. Internet Explorer&#39;s
SmartScreen filter will block your app from being downloaded, and many anti-virus vendors will
consider your app as malware unless you obtain a valid cert&amp;quot;&lt;/em&gt; [sic].&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Looking at just these three examples, it is clear that Electron is far from &amp;quot;write once, run
everywhere&amp;quot;. Distributing an app on app stores requires
&lt;a href=&quot;https://www.electronjs.org/docs/tutorial/code-signing&quot; rel=&quot;noopener&quot;&gt;code signing&lt;/a&gt;, a security technology for
certifying app ownership. Packaging an app requires using tools like
&lt;a href=&quot;https://github.com/electron-userland/electron-forge&quot; rel=&quot;noopener&quot;&gt;electron-forge&lt;/a&gt; and thinking about where to
host packages for app updates. It gets complex relatively quickly, especially when the objective
truly is cross platform support. I want to note that it is &lt;em&gt;absolutely&lt;/em&gt; possible to create stunning
Electron apps with enough effort and dedication. For Excalidraw Desktop, we were not there.&lt;/p&gt;
&lt;h2 id=&quot;where-excalidraw-desktop-left-off&quot;&gt;Where Excalidraw Desktop left off &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/deprecating-excalidraw-electron/#where-excalidraw-desktop-left-off&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Excalidraw Desktop so far is basically the Excalidraw web app bundled as an
&lt;a href=&quot;https://github.com/electron/asar&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;.asar&lt;/code&gt;&lt;/a&gt; file with an added &lt;strong&gt;About Excalidraw&lt;/strong&gt; window. The look
and feel of the application is almost identical to the web version.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Excalidraw Desktop application running in an Electron wrapper.&quot; decoding=&quot;async&quot; height=&quot;601&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/oR9usELiRYTSu8V7i7vj.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Excalidraw Desktop is almost indistinguishable from the web version&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Excalidraw Desktop &amp;#x27;About&amp;#x27; window displaying the version of the Electron wrapper and the web app.&quot; decoding=&quot;async&quot; height=&quot;330&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/y9d4nWR3p0VjvHcnP0iq.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/y9d4nWR3p0VjvHcnP0iq.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/y9d4nWR3p0VjvHcnP0iq.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/y9d4nWR3p0VjvHcnP0iq.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/y9d4nWR3p0VjvHcnP0iq.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/y9d4nWR3p0VjvHcnP0iq.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/y9d4nWR3p0VjvHcnP0iq.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/y9d4nWR3p0VjvHcnP0iq.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/y9d4nWR3p0VjvHcnP0iq.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/y9d4nWR3p0VjvHcnP0iq.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/y9d4nWR3p0VjvHcnP0iq.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/y9d4nWR3p0VjvHcnP0iq.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/y9d4nWR3p0VjvHcnP0iq.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
  &lt;figcaption&gt;The &lt;strong&gt;About Excalibur&lt;/strong&gt; menu providing insights into the versions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;On macOS, there is now a system-level menu at the top of the application, but since none of the menu
actions—apart from &lt;strong&gt;Close Window&lt;/strong&gt; and &lt;strong&gt;About Excalidraw&lt;/strong&gt;—are hooked up to anything, the menu
is, in its current state, pretty useless. Meanwhile, all actions can of course be performed via the
regular Excalidraw toolbars and the context menu.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Excalidraw Desktop menu bar on macOS with the &amp;#x27;File&amp;#x27;, &amp;#x27;Close Window&amp;#x27; menu item selected.&quot; decoding=&quot;async&quot; height=&quot;138&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 736px) 736px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/akQQgmMKo66quqeVDdAH.png?auto=format&amp;w=1472 1472w&quot; width=&quot;736&quot; /&gt;
  &lt;figcaption&gt;The menu bar of Excalidraw Desktop on macOS&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We use &lt;a href=&quot;https://github.com/electron-userland/electron-builder&quot; rel=&quot;noopener&quot;&gt;electron-builder&lt;/a&gt;, which supports
&lt;a href=&quot;https://www.electron.build/configuration/configuration#PlatformSpecificBuildOptions-fileAssociations&quot; rel=&quot;noopener&quot;&gt;file type associations&lt;/a&gt;.
By double-clicking an &lt;code&gt;.excalidraw&lt;/code&gt; file, ideally the Excalidraw Desktop app should open. The
relevant excerpt of our &lt;code&gt;electron-builder.json&lt;/code&gt; file looks like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;fileAssociations&quot;&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;ext&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;excalidraw&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;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Excalidraw&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;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Excalidraw file&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;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Editor&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;&quot;mimeType&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;application/json&quot;&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;Unfortunately, in practice, this does not always work as intended, since, depending on the
installation type (for the current user, for all users), apps on Windows 10 do not have the
rights to associate a file type to themselves.&lt;/p&gt;
&lt;p&gt;These shortcomings and the pending work to make the experience truly app-like on &lt;em&gt;all&lt;/em&gt; platforms
(which, again, with enough effort &lt;em&gt;is&lt;/em&gt; possible) were a strong argument for us to reconsider our
investment in Excalidraw Desktop. The way bigger argument for us, though, was that we foresee that
for &lt;em&gt;our&lt;/em&gt; use case, we do not need all the features Electron offers. The grown and still growing set
of capabilities of the web serves us equally well, if not better.&lt;/p&gt;
&lt;h2 id=&quot;how-the-web-serves-us-today-and-in-the-future&quot;&gt;How the web serves us today and in the future &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/deprecating-excalidraw-electron/#how-the-web-serves-us-today-and-in-the-future&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Even in 2020, &lt;a href=&quot;https://jquery.com/&quot; rel=&quot;noopener&quot;&gt;jQuery&lt;/a&gt; is still
&lt;a href=&quot;https://almanac.httparchive.org/en/2020/javascript#libraries&quot; rel=&quot;noopener&quot;&gt;incredibly popular&lt;/a&gt;. For many
developers it has become a habit to use it, despite the fact that today they
&lt;a href=&quot;http://youmightnotneedjquery.com/&quot; rel=&quot;noopener&quot;&gt;might not need jQuery&lt;/a&gt;. There is a similar resource for
Electron, aptly called &lt;a href=&quot;https://youmightnotneedelectron.com/&quot; rel=&quot;noopener&quot;&gt;You Might Not Need Electron&lt;/a&gt;. Let me
outline why we think we do not need Electron.&lt;/p&gt;
&lt;h3 id=&quot;installable-progressive-web-app&quot;&gt;Installable Progressive Web App &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/deprecating-excalidraw-electron/#installable-progressive-web-app&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Excalidraw today is an &lt;a href=&quot;https://web.dev/installable/&quot;&gt;installable&lt;/a&gt; Progressive Web App with a
&lt;a href=&quot;https://excalidraw.com/service-worker.js&quot; rel=&quot;noopener&quot;&gt;service worker&lt;/a&gt; and a
&lt;a href=&quot;https://excalidraw.com/manifest.json&quot; rel=&quot;noopener&quot;&gt;web app manifest&lt;/a&gt;. It caches all its resources in two caches,
one for fonts and font-related CSS, and one for everything else.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Chrome DevTools Application tab showing the two Excalidraw caches.&quot; decoding=&quot;async&quot; height=&quot;569&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/tTo7miHIREZRySv8aoBd.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Excalidraw&#39;s cache contents&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This means the application is fully offline-capable and can run without a network connection.
Chromium-based browsers on both desktop and mobile prompt the user to install the app.
You can see the installation prompt in the screenshot below.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Excalidraw prompting the user to install the app in Chrome on macOS.&quot; decoding=&quot;async&quot; height=&quot;258&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/be3EQLezj3776w6SHLPi.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/be3EQLezj3776w6SHLPi.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/be3EQLezj3776w6SHLPi.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/be3EQLezj3776w6SHLPi.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/be3EQLezj3776w6SHLPi.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/be3EQLezj3776w6SHLPi.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/be3EQLezj3776w6SHLPi.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/be3EQLezj3776w6SHLPi.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/be3EQLezj3776w6SHLPi.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/be3EQLezj3776w6SHLPi.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/be3EQLezj3776w6SHLPi.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/be3EQLezj3776w6SHLPi.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/be3EQLezj3776w6SHLPi.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
  &lt;figcaption&gt;The Excalidraw install dialog in Chrome&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Excalidraw is configured to run as a standalone application, so when you install it, you get an app
that runs in its own window. It is fully integrated in the operating system&#39;s multitasking UI and
gets its own app icon on the home screen, Dock, or task bar; depending on the platform where you install
it.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Excalidraw running in its own window.&quot; decoding=&quot;async&quot; height=&quot;584&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/MbMgQlGSBeNcX7Y362jV.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;The Excalidraw PWA in a standalone window&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Excalidraw icon on the macOS Dock.&quot; decoding=&quot;async&quot; height=&quot;167&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/7ncf98ZQZcg4g3UP2s7F.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/7ncf98ZQZcg4g3UP2s7F.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/7ncf98ZQZcg4g3UP2s7F.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/7ncf98ZQZcg4g3UP2s7F.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/7ncf98ZQZcg4g3UP2s7F.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/7ncf98ZQZcg4g3UP2s7F.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/7ncf98ZQZcg4g3UP2s7F.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/7ncf98ZQZcg4g3UP2s7F.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/7ncf98ZQZcg4g3UP2s7F.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/7ncf98ZQZcg4g3UP2s7F.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/7ncf98ZQZcg4g3UP2s7F.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/7ncf98ZQZcg4g3UP2s7F.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/7ncf98ZQZcg4g3UP2s7F.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
  &lt;figcaption&gt;The Excalidraw icon on the macOS Dock&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;file-system-access&quot;&gt;File system access &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/deprecating-excalidraw-electron/#file-system-access&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Excalidraw uses &lt;a href=&quot;https://github.com/GoogleChromeLabs/browser-fs-access&quot; rel=&quot;noopener&quot;&gt;browser-fs-access&lt;/a&gt; for
accessing the file system of the operating system. On supporting browsers, this allows for a true
open→edit→save workflow and actual over-saving and &amp;quot;save as&amp;quot;, with a transparent fallback for
other browsers. You can learn more about this feature in my blog post
&lt;a href=&quot;https://web.dev/browser-fs-access/&quot;&gt;Reading and writing files and directories with the browser-fs-access library&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;drag-and-drop-support&quot;&gt;Drag and drop support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/deprecating-excalidraw-electron/#drag-and-drop-support&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Files can be dragged and dropped onto the Excalidraw window just as in platform-specific applications. On a
browser that supports the &lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt;, a dropped
file can be immediately edited and the modifications be saved to the original file. This is so
intuitive that you sometimes forget that you are dealing with a web app.&lt;/p&gt;
&lt;h3 id=&quot;clipboard-access&quot;&gt;Clipboard access &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/deprecating-excalidraw-electron/#clipboard-access&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Excalidraw works well with the operating system&#39;s clipboard. Entire Excalidraw drawings or also just
individual objects can be copied and pasted in &lt;code&gt;image/png&lt;/code&gt; and &lt;code&gt;image/svg+xml&lt;/code&gt; formats, allowing for
an easy integration with other platform-specific tools like &lt;a href=&quot;https://inkscape.org/&quot; rel=&quot;noopener&quot;&gt;Inkscape&lt;/a&gt; or web-based
tools like &lt;a href=&quot;https://jakearchibald.github.io/svgomg/&quot; rel=&quot;noopener&quot;&gt;SVGOMG&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Excalidraw context menu showing the &amp;#x27;copy to clipboard as SVG&amp;#x27; and &amp;#x27;copy to clipboard as PNG&amp;#x27; menu items.&quot; decoding=&quot;async&quot; height=&quot;746&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/90gLbYTtkKtDfun4fiRM.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;The Excalidraw context menu offering clipboard actions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;file-handling&quot;&gt;File handling &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/deprecating-excalidraw-electron/#file-handling&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Excalidraw already supports the experimental &lt;a href=&quot;https://web.dev/file-handling/&quot;&gt;File Handling API&lt;/a&gt;,
which means &lt;code&gt;.excalidraw&lt;/code&gt; files can be double-clicked in the operating system&#39;s file manager and
open directly in the Excalidraw app, since Excalidraw registers as a file handler for &lt;code&gt;.excalidraw&lt;/code&gt;
files in the operating system.&lt;/p&gt;
&lt;h3 id=&quot;declarative-link-capturing&quot;&gt;Declarative link capturing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/deprecating-excalidraw-electron/#declarative-link-capturing&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Excalidraw drawings can be shared by link. Here is an
&lt;a href=&quot;https://excalidraw.com/#json=4646308765761536,jwZJW8JsOM75vdhqG2nBgA&quot; rel=&quot;noopener&quot;&gt;example&lt;/a&gt;. In the future, if
people have Excalidraw installed as a PWA, such links will not open in a browser tab, but launch a
new standalone window. Pending implementation, this will work thanks to
&lt;a href=&quot;https://github.com/WICG/sw-launch/blob/master/declarative_link_capturing.md&quot; rel=&quot;noopener&quot;&gt;declarative link capturing&lt;/a&gt;,
an, at the time of writing, bleeding-edge proposal for a new web platform feature.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/deprecating-excalidraw-electron/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The web has come a long way, with more and more features landing in browsers that only a couple of
years or even months ago were unthinkable on the web and exclusive to platform-specific applications.
Excalidraw is at the forefront of what is possible in the browser, all while acknowledging that not
all browsers on all platforms support each feature we use. By betting on a progressive
enhancement strategy, we enjoy the latest and greatest wherever possible, but without leaving anyone
behind. Best viewed in &lt;em&gt;any&lt;/em&gt; browser.&lt;/p&gt;
&lt;p&gt;Electron has served us well, but in 2020 and beyond, we can live without it. Oh, and for that
objective of &lt;a href=&quot;https://github.com/vjeux&quot; rel=&quot;noopener&quot;&gt;@vjeux&lt;/a&gt;: since the Android Play Store now accepts PWAs in a
container format called &lt;a href=&quot;https://web.dev/using-a-pwa-in-your-android-app/&quot;&gt;Trusted Web Activity&lt;/a&gt; and
since the
&lt;a href=&quot;https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-edgehtml/microsoft-store&quot; rel=&quot;noopener&quot;&gt;Microsoft Store supports PWAs&lt;/a&gt;,
too, you can expect Excalidraw in these stores in the not too distant future. Meanwhile, you can
always use and install &lt;a href=&quot;https://excalidraw.com/&quot; rel=&quot;noopener&quot;&gt;Excalidraw in and from the browser&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/deprecating-excalidraw-electron/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by &lt;a href=&quot;https://github.com/lipis&quot; rel=&quot;noopener&quot;&gt;@lipis&lt;/a&gt;,
&lt;a href=&quot;https://github.com/dwelle&quot; rel=&quot;noopener&quot;&gt;@dwelle&lt;/a&gt;, and
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Play the Chrome dino game with your gamepad</title>
    <link href="https://web.dev/gamepad/"/>
    <updated>2020-11-03T00:00:00Z</updated>
    <id>https://web.dev/gamepad/</id>
    <content type="html" mode="escaped">&lt;p&gt;Chrome&#39;s offline page easter egg is one of the worst-kept secrets in history (&lt;code&gt;[citation needed]&lt;/code&gt;,
but claim made for the dramatic effect). If you press the &lt;kbd&gt;space&lt;/kbd&gt; key or, on mobile
devices, tap the dinosaur, the offline page becomes a playable arcade game. You might be aware that
you do not actually have to go offline when you feel like playing: in Chrome, you can just navigate
to &lt;code&gt;about://dino&lt;/code&gt;, or, for the geek in you, browse to &lt;code&gt;about://network-error/-106&lt;/code&gt;. But did you know
that there are currently
&lt;a href=&quot;https://www.blog.google/products/chrome/chrome-dino#jump-content:~:text=There%20are%20currently%20270%20million%20games%20played%20every%20month&quot; rel=&quot;noopener&quot;&gt;270 million Chrome dino games played every month&lt;/a&gt;?&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Chrome&amp;#x27;s offline page with the Chrome dino game.&quot; decoding=&quot;async&quot; height=&quot;647&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BQ9zVNGfI0PjH6LTwxj5.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Press the space bar to play!
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Another fact that arguably is more useful to know and that you might not be aware of is that in
arcade mode you can play the game with a gamepad. Gamepad support was added roughly one year ago as
of the time of this writing in a
&lt;a href=&quot;https://github.com/chromium/chromium/commit/fcafd36b23c535e307da4213b7d639f8c13b8da2&quot; rel=&quot;noopener&quot;&gt;commit&lt;/a&gt; by
&lt;a href=&quot;https://github.com/reillyeon&quot; rel=&quot;noopener&quot;&gt;Reilly Grant&lt;/a&gt;. As you can see, the game, just like the rest of the
Chromium project, is fully
&lt;a href=&quot;https://github.com/chromium/chromium/tree/master/components/neterror/resources&quot; rel=&quot;noopener&quot;&gt;open source&lt;/a&gt;. In
this post, I want to show you how to use the Gamepad API.&lt;/p&gt;
&lt;h2 id=&quot;using-the-gamepad-api&quot;&gt;Using the Gamepad API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/gamepad/#using-the-gamepad-api&quot;&gt;#&lt;/a&gt;&lt;/h2&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;a href=&quot;https://w3c.github.io/gamepad/&quot;&gt;Gamepad API&lt;/a&gt; has been around for a long time. This post disregards all the legacy features and vendor prefixes. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;feature-detection-and-browser-support&quot;&gt;Feature detection and browser support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/gamepad/#feature-detection-and-browser-support&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Gamepad API has universally great &lt;a href=&quot;https://caniuse.com/gamepad&quot; rel=&quot;noopener&quot;&gt;browser support&lt;/a&gt; across both
desktop and mobile. You can detect if the Gamepad API is supported using the snippet below:&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;&lt;span class=&quot;token string&quot;&gt;&#39;getGamepads&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;// The API is supported!&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;how-the-browser-represents-a-gamepad&quot;&gt;How the browser represents a gamepad &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/gamepad/#how-the-browser-represents-a-gamepad&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The browser represents gamepads as &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Gamepad&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Gamepad&lt;/code&gt;&lt;/a&gt;
objects. A &lt;code&gt;Gamepad&lt;/code&gt; has the following properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt;: An identification string for the gamepad. This string identifies the brand or style of
connected gamepad device.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;displayId&lt;/code&gt;: The
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/VRDisplay/displayId&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;VRDisplay.displayId&lt;/code&gt;&lt;/a&gt; of an
associated &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/VRDisplay&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;VRDisplay&lt;/code&gt;&lt;/a&gt; (if relevant).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;index&lt;/code&gt;: The index of the gamepad in the navigator.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;connected&lt;/code&gt;: Indicates whether the gamepad is still connected to the system.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hand&lt;/code&gt;: An enum defining what hand the controller is being held in, or is most likely to be held
in.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timestamp&lt;/code&gt;: The last time the data for this gamepad was updated.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mapping&lt;/code&gt;: The button and axes mapping in use for this device, either &lt;code&gt;&amp;quot;standard&amp;quot;&lt;/code&gt; or
&lt;code&gt;&amp;quot;xr-standard&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pose&lt;/code&gt;: A &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/GamepadPose&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;GamepadPose&lt;/code&gt;&lt;/a&gt; object
representing the pose information associated with a WebVR controller.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;axes&lt;/code&gt;: An array of values for all axes of the gamepad, linearly normalized to the range of
&lt;code&gt;-1.0&lt;/code&gt;–&lt;code&gt;1.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;buttons&lt;/code&gt;: An array of button states for all buttons of the gamepad.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that buttons can be digital (pressed or not pressed) or analog (for example, 78% pressed). This
is why buttons are reported as &lt;code&gt;GamepadButton&lt;/code&gt; objects, with the following attributes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pressed&lt;/code&gt;: The pressed state of the button (&lt;code&gt;true&lt;/code&gt; if the button is currently pressed, and &lt;code&gt;false&lt;/code&gt;
if it is not pressed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;touched&lt;/code&gt;: The touched state of the button. If the button is capable of detecting touch, this
property is &lt;code&gt;true&lt;/code&gt; if the button is currently being touched, and &lt;code&gt;false&lt;/code&gt; otherwise.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;value&lt;/code&gt;: For buttons that have an analog sensor, this property represents the amount by which the
button has been pressed, linearly normalized to the range of &lt;code&gt;0.0&lt;/code&gt;–&lt;code&gt;1.0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hapticActuators&lt;/code&gt;: An array containing
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/GamepadHapticActuator&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;GamepadHapticActuator&lt;/code&gt;&lt;/a&gt;
objects, each of which represents haptic feedback hardware available on the controller.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One additional thing that you might encounter, depending on your browser and the gamepad you have,
is a &lt;code&gt;vibrationActuator&lt;/code&gt; property. This field is currently implemented in Chrome and earmarked for
&lt;a href=&quot;https://github.com/w3c/gamepad/pull/68&quot; rel=&quot;noopener&quot;&gt;merging&lt;/a&gt; into the
&lt;a href=&quot;https://w3c.github.io/gamepad/extensions.html&quot; rel=&quot;noopener&quot;&gt;Gamepad Extensions&lt;/a&gt; spec.&lt;/p&gt;
&lt;p&gt;The schematic overview below, taken
&lt;a href=&quot;https://w3c.github.io/gamepad/#fig-visual-representation-of-a-standard-gamepad-layout:~:text=Figure%201%20Visual%20representation%20of%20a%20standard%20gamepad%20layout.&quot; rel=&quot;noopener&quot;&gt;straight from the spec&lt;/a&gt;,
shows the mapping and the arrangement of the buttons and axes on a generic gamepad.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Schematic overview of the button and axes mappings of a common gamepad.&quot; decoding=&quot;async&quot; height=&quot;566&quot; loading=&quot;lazy&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qy6OxKmPAE5dpfLCMhZt.svg&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Visual representation of a standard gamepad layout
    (&lt;a href=&quot;https://w3c.github.io/gamepad/#fig-visual-representation-of-a-standard-gamepad-layout:~:text=Figure%201%20Visual%20representation%20of%20a%20standard%20gamepad%20layout.&quot;&gt;Source&lt;/a&gt;).
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;being-notified-when-a-gamepad-gets-connected&quot;&gt;Being notified when a gamepad gets connected &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/gamepad/#being-notified-when-a-gamepad-gets-connected&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To learn when a gamepad is connected, listen for the &lt;code&gt;gamepadconnected&lt;/code&gt; event that triggers on the
&lt;code&gt;window&lt;/code&gt; object. When the user connects a gamepad, which can either happen via USB or via Bluetooth,
a &lt;code&gt;GamepadEvent&lt;/code&gt; is fired that has the gamepad&#39;s details in an aptly named &lt;code&gt;gamepad&lt;/code&gt; property.
Below, you can see an example from an Xbox 360 controller that I had lying around (yes, I am into
retro gaming).&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;&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;gamepadconnected&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;✅ 🎮 A gamepad was connected:&#39;&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;gamepad&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;/*&lt;br /&gt;    gamepad: Gamepad&lt;br /&gt;    axes: (4) [0, 0, 0, 0]&lt;br /&gt;    buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]&lt;br /&gt;    connected: true&lt;br /&gt;    id: &quot;Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)&quot;&lt;br /&gt;    index: 0&lt;br /&gt;    mapping: &quot;standard&quot;&lt;br /&gt;    timestamp: 6563054.284999998&lt;br /&gt;    vibrationActuator: GamepadHapticActuator {type: &quot;dual-rumble&quot;}&lt;br /&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;h3 id=&quot;being-notified-when-a-gamepad-gets-disconnected&quot;&gt;Being notified when a gamepad gets disconnected &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/gamepad/#being-notified-when-a-gamepad-gets-disconnected&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Being notified of gamepad disconnections happens analogously to the way connections are detected.
This time the app listens for the &lt;code&gt;gamepaddisconnected&lt;/code&gt; event. Note how in the example below
&lt;code&gt;connected&lt;/code&gt; is now &lt;code&gt;false&lt;/code&gt; when I unplug the Xbox 360 controller.&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;highlight-line&quot;&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;gamepaddisconnected&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&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;❌ 🎮 A gamepad was disconnected:&#39;&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;gamepad&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;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;/*&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    gamepad: Gamepad&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    axes: (4) [0, 0, 0, 0]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    buttons: (17) [GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton, GamepadButton]&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    connected: false&lt;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    id: &quot;Xbox 360 Controller (STANDARD GAMEPAD Vendor: 045e Product: 028e)&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    index: 0&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    mapping: &quot;standard&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    timestamp: 6563054.284999998&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    vibrationActuator: null&lt;/span&gt;&lt;br /&gt;  */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&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&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;the-gamepad-in-your-game-loop&quot;&gt;The gamepad in your game loop &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/gamepad/#the-gamepad-in-your-game-loop&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Getting ahold of a gamepad starts with a call to &lt;code&gt;navigator.getGamepads()&lt;/code&gt;, which returns an array
with &lt;code&gt;Gamepad&lt;/code&gt; items. The array in Chrome &lt;em&gt;always&lt;/em&gt; has a fixed length of four items. If zero or less
than four gamepads are connected, an item may just be &lt;code&gt;null&lt;/code&gt;. Always be sure to check all items of
the array and be aware that gamepads &amp;quot;remember&amp;quot; their slot and may not always be present at the
first available slot.&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;// When no gamepads are connected:&lt;/span&gt;&lt;br /&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getGamepads&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;// (4) [null, null, null, null]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;If one or several gamepads are connected, but &lt;code&gt;navigator.getGamepads()&lt;/code&gt; still reports &lt;code&gt;null&lt;/code&gt; items,
you may need to &amp;quot;wake&amp;quot; each gamepad by pressing any of its buttons. You can then poll the gamepad
states in your game loop as shown below.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;pollGamepads&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;span class=&quot;token operator&quot;&gt;=&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;// Always call `navigator.getGamepads()` inside of&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// the game loop, not outside.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; gamepads &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getGamepads&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;const&lt;/span&gt; gamepad &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; gamepads&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;// Disregard empty slots.&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;gamepad&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;continue&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;// Process the gamepad state.&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;gamepad&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 comment&quot;&gt;// Call yourself upon the next animation frame.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// (Typically this happens every 60 times per second.)&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;requestAnimationFrame&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pollGamepads&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;span class=&quot;token comment&quot;&gt;// Kick off the initial game loop iteration.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;pollGamepads&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;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Do not store a lasting reference to the &lt;code&gt;navigator.getGamepads()&lt;/code&gt; array &lt;em&gt;outside&lt;/em&gt; of the game loop, since the method returns a static snapshot, not a live object. Call &lt;code&gt;navigator.getGamepads()&lt;/code&gt; each time anew &lt;em&gt;in your game loop&lt;/em&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;using-the-vibration-actuator&quot;&gt;Using the vibration actuator &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/gamepad/#using-the-vibration-actuator&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;vibrationActuator&lt;/code&gt; property returns a &lt;code&gt;GamepadHapticActuator&lt;/code&gt; object, which corresponds to a
configuration of motors or other actuators that can apply a force for the purposes of haptic
feedback. Haptic effects can be played by calling &lt;code&gt;Gamepad.vibrationActuator.playEffect()&lt;/code&gt;. The only
currently valid effect type is &lt;code&gt;&#39;dual-rumble&#39;&lt;/code&gt;. Dual-rumble describes a haptic configuration with an
eccentric rotating mass vibration motor in each handle of a standard gamepad. In this configuration,
either motor is capable of vibrating the whole gamepad. The two masses are unequal so that the
effects of each can be combined to create more complex haptic effects. Dual-rumble effects are
defined by four parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;duration&lt;/code&gt;: Sets the duration of the vibration effect in milliseconds.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;startDelay&lt;/code&gt;: Sets the duration of the delay until the vibration is started.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strongMagnitude&lt;/code&gt; and &lt;code&gt;weakMagnitude&lt;/code&gt;: Set the vibration intensity levels for the heavier and
lighter eccentric rotating mass motors, normalized to the range &lt;code&gt;0.0&lt;/code&gt;–&lt;code&gt;1.0&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&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;// This assumes a `Gamepad` as the value of the `gamepad` variable.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;vibrate&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 parameter&quot;&gt;gamepad&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; delay &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; duration &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; weak &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; strong &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;vibrationActuator&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; gamepad&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  gamepad&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;vibrationActuator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;playEffect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;dual-rumble&#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;// Start delay in ms.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;startDelay&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; delay&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Duration is ms.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;token operator&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 comment&quot;&gt;// The magnitude of the weak actuator (between 0 and 1).&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;weakMagnitude&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; weak&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// The magnitude of the strong actuator (between 0 and 1).&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;strongMagnitude&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; strong&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;h3 id=&quot;integration-with-permissions-policy&quot;&gt;Integration with Permissions Policy &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/gamepad/#integration-with-permissions-policy&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Gamepad API spec defines a
&lt;a href=&quot;https://developer.chrome.com/docs/privacy-sandbox/permissions-policy/&quot; rel=&quot;noopener&quot;&gt;policy-controlled feature&lt;/a&gt; identified by the
string &lt;code&gt;&amp;quot;gamepad&amp;quot;&lt;/code&gt;. Its default &lt;code&gt;allowlist&lt;/code&gt; is &lt;code&gt;&amp;quot;self&amp;quot;&lt;/code&gt;. A document&#39;s permissions policy determines
whether any content in that document is allowed to access &lt;code&gt;navigator.getGamepads()&lt;/code&gt;. If disabled in
any document, no content in the document will be allowed to use &lt;code&gt;navigator.getGamepads()&lt;/code&gt;, nor will
the &lt;code&gt;gamepadconnected&lt;/code&gt; and &lt;code&gt;gamepaddisconnected&lt;/code&gt; events fire.&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;iframe&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;index.html&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;allow&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;gamepad&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 tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;iframe&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;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/gamepad/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A simple &lt;a href=&quot;https://gamepad-demo.glitch.me/&quot; rel=&quot;noopener&quot;&gt;gamepad tester demo&lt;/a&gt; is embedded below. The source code
is available &lt;a href=&quot;https://glitch.com/edit/#!/gamepad-demo&quot; rel=&quot;noopener&quot;&gt;on Glitch&lt;/a&gt;. Try the demo by connecting a
gamepad via USB or Bluetooth and pressing any of its buttons or moving any of its axis.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 1000px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi; gamepad&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/gamepad-demo?attributionHidden=true&amp;sidebarCollapsed=true&amp;path=script.js&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;gamepad-demo on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;bonus-play-chrome-dino-on-webdev&quot;&gt;Bonus: play Chrome dino on web.dev &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/gamepad/#bonus-play-chrome-dino-on-webdev&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can play &lt;a href=&quot;https://tomayac.github.io/chrome-dino-gamepad/&quot; rel=&quot;noopener&quot;&gt;Chrome dino&lt;/a&gt; with your gamepad on this
very site. The source code is available &lt;a href=&quot;https://github.com/tomayac/chrome-dino-gamepad&quot; rel=&quot;noopener&quot;&gt;on GitHub&lt;/a&gt;.
Check out the gamepad polling implementation in
&lt;a href=&quot;https://github.com/tomayac/chrome-dino-gamepad/blob/885eb6134805345bf31eeb9971830adeb84747ab/trex-runner.js#L529-L571&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;trex-runner.js&lt;/code&gt;&lt;/a&gt;
and note how it is emulating key presses.&lt;/p&gt;
&lt;div style=&quot;height: 420px; width: 100%;&quot;&gt;
  &lt;iframe src=&quot;https://tomayac.github.io/chrome-dino-gamepad/&quot; title=&quot;Chrome dino gamepad&quot; allow=&quot;gamepad; fullscreen&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; loading=&quot;lazy&quot;&gt;
  &lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;For the &lt;a href=&quot;https://tomayac.github.io/chrome-dino-gamepad/&quot; rel=&quot;noopener&quot;&gt;Chrome dino gamepad&lt;/a&gt; demo to work, I have
ripped out the Chrome dino game from the core Chromium project (updating an
&lt;a href=&quot;https://github.com/arnellebalane/trex-runner&quot; rel=&quot;noopener&quot;&gt;earlier effort&lt;/a&gt; by
&lt;a href=&quot;https://arnellebalane.com/&quot; rel=&quot;noopener&quot;&gt;Arnelle Ballane&lt;/a&gt;), placed it on a standalone site, extended the
existing gamepad API implementation by adding ducking and vibration effects, created a full screen
mode, and &lt;a href=&quot;https://github.com/mehulsatardekar&quot; rel=&quot;noopener&quot;&gt;Mehul Satardekar&lt;/a&gt; contributed a dark mode
implementation. Happy gaming!&lt;/p&gt;
&lt;h2 id=&quot;useful-links&quot;&gt;Useful links &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/gamepad/#useful-links&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/gamepad/&quot; rel=&quot;noopener&quot;&gt;Gamepad API spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://w3c.github.io/gamepad/extensions.html&quot; rel=&quot;noopener&quot;&gt;Gamepad API extensions spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/w3c/gamepad/&quot; rel=&quot;noopener&quot;&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/gamepad/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by &lt;a href=&quot;https://github.com/beaufortfrancois&quot; rel=&quot;noopener&quot;&gt;François Beaufort&lt;/a&gt; and
&lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;. The Gamepad API spec is currently edited by
&lt;a href=&quot;https://github.com/sagoston&quot; rel=&quot;noopener&quot;&gt;Steve Agoston&lt;/a&gt;,
&lt;a href=&quot;https://www.linkedin.com/in/james-hollyer-981110a3/&quot; rel=&quot;noopener&quot;&gt;James Hollyer&lt;/a&gt;, and
&lt;a href=&quot;https://github.com/nondebug&quot; rel=&quot;noopener&quot;&gt;Matt Reynolds&lt;/a&gt;. The former spec editors are
&lt;a href=&quot;https://blog.tojicode.com/&quot; rel=&quot;noopener&quot;&gt;Brandon Jones&lt;/a&gt;, &lt;a href=&quot;https://h4ck3r.net/&quot; rel=&quot;noopener&quot;&gt;Scott Graham&lt;/a&gt;, and
&lt;a href=&quot;http://ted.mielczarek.org/&quot; rel=&quot;noopener&quot;&gt;Ted Mielczarek&lt;/a&gt;. The Gamepad Extensions spec is edited by
&lt;a href=&quot;https://blog.tojicode.com/&quot; rel=&quot;noopener&quot;&gt;Brandon Jones&lt;/a&gt;. Hero image by Laura Torrent Puig.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Create an offline fallback page</title>
    <link href="https://web.dev/offline-fallback-page/"/>
    <updated>2020-09-24T00:00:00Z</updated>
    <id>https://web.dev/offline-fallback-page/</id>
    <content type="html" mode="escaped">&lt;p&gt;What do the Google Assistant app, the Slack app, the Zoom app, and almost
any other platform-specific app on your phone or computer have in common? Right, they always at least give you &lt;em&gt;something&lt;/em&gt;.
Even when you do not have a network connection, you can still open the Assistant app, or enter
Slack, or launch Zoom. You might not get anything particularly meaningful or even
be unable to achieve what you wanted to achieve, but at least you get &lt;em&gt;something&lt;/em&gt; and the app is in
control.&lt;/p&gt;
&lt;figure role=&quot;group&quot; aria-labelledby=&quot;fig-apps-wrapper&quot;&gt;
  &lt;figure role=&quot;group&quot; aria-labelledby=&quot;fig-assistant&quot; style=&quot;display: inline-block&quot;&gt;
    &lt;img alt=&quot;Google Assistant mobile app while offline.&quot; decoding=&quot;async&quot; height=&quot;1344&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 621px) 621px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gr49coayhLfP1UVJ2EeR.jpg?auto=format&amp;w=1242 1242w&quot; width=&quot;621&quot; /&gt;
    &lt;figcaption id=&quot;fig-assistant&quot;&gt;
      Google Assistant.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure role=&quot;group&quot; aria-labelledby=&quot;fig-slack&quot; style=&quot;display: inline-block&quot;&gt;
    &lt;img alt=&quot;Slack mobile app while offline.&quot; decoding=&quot;async&quot; height=&quot;1344&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 621px) 621px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D4P00CQ15IE0plUEY3di.jpg?auto=format&amp;w=1242 1242w&quot; width=&quot;621&quot; /&gt;
    &lt;figcaption id=&quot;fig-slack&quot;&gt;
      Slack.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure role=&quot;group&quot; aria-labelledby=&quot;fig-zoom&quot; style=&quot;display: inline-block&quot;&gt;
    &lt;img alt=&quot;Zoom mobile app while offline.&quot; decoding=&quot;async&quot; height=&quot;1344&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 621px) 621px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gw1LQG4JNYUDxQ2NOJHC.jpg?auto=format&amp;w=1242 1242w&quot; width=&quot;621&quot; /&gt;
    &lt;figcaption id=&quot;fig-zoom&quot;&gt;
      Zoom.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figcaption id=&quot;fig-apps-wrapper&quot;&gt;
    With platform-specific apps, even when you do not have a network connection, you never get nothing.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In contrast, on the Web, traditionally you get nothing when you are offline. Chrome gives you
the &lt;a href=&quot;https://www.blog.google/products/chrome/chrome-dino/&quot; rel=&quot;noopener&quot;&gt;offline dino game&lt;/a&gt;, but that is it.&lt;/p&gt;
&lt;figure role=&quot;group&quot; aria-labelledby=&quot;fig-offline-wrapper&quot;&gt;
  &lt;figure role=&quot;group&quot; aria-labelledby=&quot;fig-chrome-ios&quot; style=&quot;display: inline-block&quot;&gt;
    &lt;img alt=&quot;Google Chrome mobile app showing the offline dino game.&quot; decoding=&quot;async&quot; height=&quot;1731&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yEf0wzIQ1hIf85xtUwse.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption id=&quot;fig-chrome-ios&quot;&gt;
      Google Chrome for iOS.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure role=&quot;group&quot; aria-labelledby=&quot;fig-chrome&quot; style=&quot;display: inline-block&quot;&gt;
    &lt;img alt=&quot;Google Chrome desktop app showing the offline dino game.&quot; decoding=&quot;async&quot; height=&quot;607&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/vrqfLVP132LcydIWcYbh.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption id=&quot;fig-chrome&quot;&gt;
      Google Chrome for macOS.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figcaption id=&quot;fig-offline-wrapper&quot;&gt;
    On the Web, when you do not have a network connection, by default you get nothing.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;an-offline-fallback-page-with-a-custom-service-worker&quot;&gt;An offline fallback page with a custom service worker &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-fallback-page/#an-offline-fallback-page-with-a-custom-service-worker&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It does not have to be this way, though. Thanks to
&lt;a href=&quot;https://web.dev/service-workers-cache-storage/&quot;&gt;service workers and the Cache Storage API&lt;/a&gt;, you can provide a
customized offline experience for your users. This can be a simple branded page with the information
that the user is currently offline, but it can just as well be a more creative solution, like, for
example, the famous &lt;a href=&quot;https://www.trivago.com/offline&quot; rel=&quot;noopener&quot;&gt;trivago offline maze game&lt;/a&gt; with a manual
&lt;strong&gt;Reconnect&lt;/strong&gt; button and an automatic reconnection attempt countdown.&lt;/p&gt;
&lt;figure&gt;
    &lt;img alt=&quot;The trivago offline page with the trivago offline maze.&quot; decoding=&quot;async&quot; height=&quot;616&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0yvun9EV5758sRO9wSgY.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;
      The trivago offline maze.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;h3 id=&quot;registering-the-service-worker&quot;&gt;Registering the service worker &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-fallback-page/#registering-the-service-worker&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The way to make this happen is through a service worker. You can register a service worker
from your main page as in the code sample below. Usually you do this once
your app has loaded.&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;&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;&quot;load&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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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 string&quot;&gt;&quot;serviceWorker&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;    navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;service-worker.js&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;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;h3 id=&quot;the-service-worker-code&quot;&gt;The service worker code &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-fallback-page/#the-service-worker-code&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The contents of the actual service worker file may seem a little involved at first sight, but the
comments in the sample below should clear things up. The core idea is to pre-cache a file named
&lt;code&gt;offline.html&lt;/code&gt; that only gets served on &lt;em&gt;failing&lt;/em&gt; navigation requests, and to let the browser handle
all other cases:&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;/*&lt;br /&gt;Copyright 2015, 2019, 2020, 2021 Google LLC. All Rights Reserved.&lt;br /&gt; Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&lt;br /&gt; you may not use this file except in compliance with the License.&lt;br /&gt; You may obtain a copy of the License at&lt;br /&gt; http://www.apache.org/licenses/LICENSE-2.0&lt;br /&gt; Unless required by applicable law or agreed to in writing, software&lt;br /&gt; distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&lt;br /&gt; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&lt;br /&gt; See the License for the specific language governing permissions and&lt;br /&gt; limitations under the License.&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Incrementing OFFLINE_VERSION will kick off the install event and force&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// previously cached resources to be updated from the network.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// This variable is intentionally declared and unused.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Add a comment for your linter if you want:&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// eslint-disable-next-line no-unused-vars&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OFFLINE_VERSION&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;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CACHE_NAME&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;offline&quot;&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;// Customize this with a different URL if needed.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;OFFLINE_URL&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;offline.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;self&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;&quot;install&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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;waitUntil&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;async&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;=&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;const&lt;/span&gt; cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&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 constant&quot;&gt;CACHE_NAME&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;// Setting {cache: &#39;reload&#39;} in the new request ensures that the&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// response isn&#39;t fulfilled from the HTTP cache; i.e., it will be&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// from the network.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; cache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&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;Request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;OFFLINE_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;span class=&quot;token literal-property property&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;reload&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 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;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 comment&quot;&gt;// Force the waiting service worker to become the active service worker.&lt;/span&gt;&lt;br /&gt;  self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;skipWaiting&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;self&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;&quot;activate&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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;waitUntil&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;async&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;=&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;// Enable navigation preload if it&#39;s supported.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// See https://developers.google.com/web/updates/2017/02/navigation-preload&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 string&quot;&gt;&quot;navigationPreload&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;registration&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;await&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;navigationPreload&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enable&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 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;// Tell the active service worker to take control of the page immediately.&lt;/span&gt;&lt;br /&gt;  self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clients&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;claim&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;self&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;&quot;fetch&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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;// Only call event.respondWith() if this is a navigation request&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// for an HTML page.&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;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mode &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;navigate&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;    event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;respondWith&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;async&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;=&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;          &lt;span class=&quot;token comment&quot;&gt;// First, try to use the navigation preload response if it&#39;s&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token comment&quot;&gt;// supported.&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; preloadResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;preloadResponse&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;preloadResponse&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; preloadResponse&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 comment&quot;&gt;// Always try the network first.&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; networkResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&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;request&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; networkResponse&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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&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;// catch is only triggered if an exception is thrown, which is&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token comment&quot;&gt;// likely due to a network error.&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token comment&quot;&gt;// If fetch() returns a valid HTTP response with a response code in&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token comment&quot;&gt;// the 4xx or 5xx range, the catch() will NOT be called.&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;&quot;Fetch failed; returning offline page instead.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&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;const&lt;/span&gt; cache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; caches&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 constant&quot;&gt;CACHE_NAME&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;const&lt;/span&gt; cachedResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; cache&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;&lt;span class=&quot;token constant&quot;&gt;OFFLINE_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;return&lt;/span&gt; cachedResponse&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;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;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// If our if() condition is false, then this fetch handler won&#39;t&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// intercept the request. If there are any other fetch handlers&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// registered, they will get a chance to call event.respondWith().&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// If no fetch handlers call event.respondWith(), the request&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// will be handled by the browser as if there were no service&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// worker involvement.&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;h3 id=&quot;the-offline-fallback-page&quot;&gt;The offline fallback page &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-fallback-page/#the-offline-fallback-page&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;offline.html&lt;/code&gt; file is where you can get creative and adapt it to your needs and add your
branding. The example below shows the bare minimum of what is possible.
It demonstrates both manual reload based on a button press as well as automatic reload
based on the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/online_event&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;online&lt;/code&gt; event&lt;/a&gt;
and regular server polling.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; You need to cache all resources required by your offline page. One way to deal with this is to inline everything, so the offline page is self-contained. This is what I do in the example below. &lt;/div&gt;&lt;/aside&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;html&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;lang&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;en&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;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;charset&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;utf-8&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;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;http-equiv&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-UA-Compatible&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;IE=edge&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;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, initial-scale=1&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;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;You are offline&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;!-- Inline the page&#39;s stylesheet. --&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;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;body&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;font-family&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; helvetica&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; arial&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sans-serif&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; 2em&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 selector&quot;&gt;h1&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;font-style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; italic&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; #373fff&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 selector&quot;&gt;p&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;margin-block&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1rem&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 selector&quot;&gt;button&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; 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;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;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;You are offline&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;h1&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;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Click the button below to try reloading.&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;&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;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;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;⤾ Reload&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 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;!-- Inline the page&#39;s JavaScript file. --&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 punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Manual reload feature.&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;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;button&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 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;&quot;click&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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &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;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reload&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Listen to changes in the network state, reload when online.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// This handles the case when the device is completely offline.&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;online&#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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &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;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reload&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// Check if the server is responding and reload the page if it is.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// This handles the case when the device is online, but the server&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;// is offline or misbehaving.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkNetworkAndReload&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;          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.&#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;// Verify we get a valid response from the server&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;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&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;            window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reload&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 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;catch&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;// Unable to connect to the server, ignore.&lt;/span&gt;&lt;br /&gt;        &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;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;checkNetworkAndReload&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2500&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 function&quot;&gt;checkNetworkAndReload&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&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;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;html&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;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-fallback-page/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can see the offline fallback page in action in the
&lt;a href=&quot;https://offline-fallback-demo.glitch.me/index.html&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt; embedded below. If you are
interested, you can explore the &lt;a href=&quot;https://glitch.com/edit/#!/offline-fallback-demo&quot; rel=&quot;noopener&quot;&gt;source code&lt;/a&gt; on
Glitch.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 420px; width: 100%;&quot;&gt;
  &lt;iframe src=&quot;https://glitch.com/embed/#!/embed/offline-fallback-demo?path=offline.html&amp;previewSize=100&quot; title=&quot;offline-fallback-demo on Glitch&quot; allow=&quot;geolocation; microphone; camera; midi; vr; encrypted-media&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;
  &lt;/iframe&gt;
&lt;/div&gt;
&lt;h3 id=&quot;side-note-on-making-your-app-installable&quot;&gt;Side note on making your app installable &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-fallback-page/#side-note-on-making-your-app-installable&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that your site has an offline fallback page, you might wonder about next steps. To make
your app installable, you need to add a &lt;a href=&quot;https://web.dev/add-manifest/&quot;&gt;web app manifest&lt;/a&gt; and optionally come up
with an &lt;a href=&quot;https://web.dev/define-install-strategy/&quot;&gt;install strategy&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;side-note-on-serving-an-offline-fallback-page-with-workboxjs&quot;&gt;Side note on serving an offline fallback page with Workbox.js &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-fallback-page/#side-note-on-serving-an-offline-fallback-page-with-workboxjs&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You may have heard of &lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot; rel=&quot;noopener&quot;&gt;Workbox&lt;/a&gt;.
Workbox is a set of JavaScript libraries for adding offline support to web apps. If you prefer to
write less service worker code yourself, you can use the Workbox recipe for an
&lt;a href=&quot;https://developer.chrome.com/docs/workbox/managing-fallback-responses/#offline-page-only&quot; rel=&quot;noopener&quot;&gt;offline page only&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Up next, learn &lt;a href=&quot;https://web.dev/define-install-strategy/&quot;&gt;how to define an install strategy&lt;/a&gt; for your app.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author><author>
      <name>Pete LePage</name>
    </author>
  </entry>
  
  <entry>
    <title>Unblocking clipboard access</title>
    <link href="https://web.dev/async-clipboard/"/>
    <updated>2020-07-31T00:00:00Z</updated>
    <id>https://web.dev/async-clipboard/</id>
    <content type="html" mode="escaped">&lt;p&gt;The traditional way of getting access to the system clipboard was via
&lt;a href=&quot;https://developer.chrome.com/blog/cut-and-copy-commands/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;document.execCommand()&lt;/code&gt;&lt;/a&gt;
for clipboard interactions. Though widely supported, this method of cutting and
pasting came at a cost: clipboard access was synchronous, and could only read
and write to the DOM.&lt;/p&gt;
&lt;p&gt;That&#39;s fine for small bits of text, but there are many cases where blocking the
page for clipboard transfer is a poor experience. Time consuming sanitization or
image decoding might be needed before content can be safely pasted. The browser
may need to load or inline linked resources from a pasted document. That would
block the page while waiting on the disk or network. Imagine adding permissions
into the mix, requiring that the browser block the page while requesting
clipboard access. At the same time, the permissions put in place around
&lt;code&gt;document.execCommand()&lt;/code&gt; for clipboard interaction are loosely defined and vary
between browsers.&lt;/p&gt;
&lt;p&gt;The
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Clipboard_API&quot; rel=&quot;noopener&quot;&gt;Async Clipboard API&lt;/a&gt;
addresses these issues, providing a well-defined permissions model that doesn&#39;t
block the page. The Async Clipboard API is limited to handling text and images
on most browsers, but support varies. Be sure to carefully study the browser
compatibility overview for each of the following sections.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Chrome as of version 104 supports &lt;a href=&quot;https://developer.chrome.com/blog/web-custom-formats-for-the-async-clipboard-api/&quot;&gt;web custom formats&lt;/a&gt; that let developers write arbitrary data to the clipboard. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;copy-writing-data-to-the-clipboard&quot;&gt;Copy: writing data to the clipboard &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#copy-writing-data-to-the-clipboard&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;writetext&quot;&gt;writeText() &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#writetext&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To copy text to the clipboard call &lt;code&gt;writeText()&lt;/code&gt;. Since this API is
asynchronous, the &lt;code&gt;writeText()&lt;/code&gt; function returns a Promise that resolves or
rejects depending on whether the passed text is copied successfully:&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;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;copyPageUrl&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;    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&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;Page URL copied to clipboard&#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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Failed to copy: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&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;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 66, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      66
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 63, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      63
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      79
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 13.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      13.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Clipboard/writeText#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id=&quot;write&quot;&gt;write() &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#write&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Actually, &lt;code&gt;writeText()&lt;/code&gt; is just a convenience method for the generic &lt;code&gt;write()&lt;/code&gt;
method, which also lets you copy images to the clipboard. Like &lt;code&gt;writeText()&lt;/code&gt;, it
is asynchronous and returns a Promise.&lt;/p&gt;
&lt;p&gt;To write an image to the clipboard, you need the image as a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/blob&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;blob&lt;/code&gt;&lt;/a&gt;. One way to do
this is by requesting the image from a server using &lt;code&gt;fetch()&lt;/code&gt;, then calling
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Body/blob&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;blob()&lt;/code&gt;&lt;/a&gt; on the
response.&lt;/p&gt;
&lt;p&gt;Requesting an image from the server may not be desirable or possible for a
variety of reasons. Fortunately, you can also draw the image to a canvas and
call the canvas&#39;
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLCanvasElement/toBlob&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;toBlob()&lt;/code&gt;&lt;/a&gt;
method.&lt;/p&gt;
&lt;p&gt;Next, pass an array of &lt;code&gt;ClipboardItem&lt;/code&gt; objects as a parameter to the &lt;code&gt;write()&lt;/code&gt;
method. Currently you can only pass one image at a time, but we hope to add
support for multiple images in the future. &lt;code&gt;ClipboardItem&lt;/code&gt; takes an object with
the MIME type of the image as the key and the blob as the value. For blob
objects obtained from &lt;code&gt;fetch()&lt;/code&gt; or &lt;code&gt;canvas.toBlob()&lt;/code&gt;, the &lt;code&gt;blob.type&lt;/code&gt; property
automatically contains the correct MIME type for an image.&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;try&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;const&lt;/span&gt; imgURL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/images/generic/file.png&#39;&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;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imgURL&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;const&lt;/span&gt; blob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;blob&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;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&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;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClipboardItem&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;// The key is determined dynamically based on the blob&#39;s type.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;blob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; blob&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;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;Image copied.&#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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;Alternatively, you can write a promise to the &lt;code&gt;ClipboardItem&lt;/code&gt; object.
For this pattern, you need to know the MIME type of the data beforehand.&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;try&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;const&lt;/span&gt; imgURL &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/images/generic/file.png&#39;&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;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&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;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClipboardItem&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 key beforehand and write a promise as the value.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token string-property property&quot;&gt;&#39;image/png&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imgURL&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;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;blob&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 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;  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;Image copied.&#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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;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; Safari (WebKit) treats user activation differently than Chromium (Blink) (see &lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=222262&quot;&gt;WebKit bug #222262&lt;/a&gt;). For Safari, run all asynchronous operations in a promise whose result you assign to the &lt;code&gt;ClipboardItem&lt;/code&gt;:  &lt;code&gt;js   new ClipboardItem({     &#39;foo/bar&#39;: new Promise(async (resolve) =&amp;gt; {         // Prepare `blobValue` of type `foo/bar`         resolve(new Blob([blobValue], { type: &#39;foo/bar&#39; }));       }),     })   &lt;/code&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 66, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      66
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 87, Behind a flag&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;flag&quot; title=&quot;Behind a flag&quot; aria-label=&quot;Behind a flag&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
79
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari 13.1, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
13.1
&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Clipboard/write#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id=&quot;the-copy-event&quot;&gt;The copy event &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#the-copy-event&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the case where a user initiates a clipboard copy
and does &lt;em&gt;not&lt;/em&gt; call &lt;code&gt;preventDefault()&lt;/code&gt;, the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Document/copy_event&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;copy&lt;/code&gt; event&lt;/a&gt;
includes a &lt;code&gt;clipboardData&lt;/code&gt; property with the items already in the right format.
If you want to implement your own logic, you need to call &lt;code&gt;preventDefault()&lt;/code&gt; to
prevent the default behavior in favor of your own implementation.
In this case, &lt;code&gt;clipboardData&lt;/code&gt; will be empty.
Consider a page with text and an image, and when the user selects all and
initiates a clipboard copy, your custom solution should discard text and only
copy the image. You can achieve this as shown in the code sample below.
What&#39;s not covered in this example is how to fall back to earlier
APIs when the Clipboard API isn&#39;t supported.&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;!-- The image we want on the clipboard. --&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;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;kitten.webp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&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;Cute kitten.&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 comment&quot;&gt;&amp;lt;!-- Some text we&#39;re not interested in. --&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;Lorem ipsum&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;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;&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;&quot;copy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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 operator&quot;&gt;=&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;// Prevent the default behavior.&lt;/span&gt;&lt;br /&gt;  e&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 keyword&quot;&gt;try&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;// Prepare an array for the clipboard items.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; clipboardItems &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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// Assume `blob` is the blob representation of `kitten.webp`.&lt;/span&gt;&lt;br /&gt;    clipboardItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&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;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClipboardItem&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;blob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; blob&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;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;clipboardItems&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;&quot;Image copied, text ignored.&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;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;For the &lt;code&gt;copy&lt;/code&gt; event:&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      1
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 22, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      22
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 12, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      12
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 3, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      3
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Element/copy_event#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;For &lt;code&gt;ClipboardItem&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 76, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      76
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 87, Behind a flag&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;flag&quot; title=&quot;Behind a flag&quot; aria-label=&quot;Behind a flag&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
79
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari 13.1, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
13.1
&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/ClipboardItem#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;paste-reading-data-from-clipboard&quot;&gt;Paste: reading data from clipboard &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#paste-reading-data-from-clipboard&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;readtext&quot;&gt;readText() &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#readtext&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To read text from the clipboard, call &lt;code&gt;navigator.clipboard.readText()&lt;/code&gt; and wait
for the returned promise to resolve:&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;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getClipboardContents&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;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readText&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;    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;Pasted content: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; text&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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Failed to read clipboard contents: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&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;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 66, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      66
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
79
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari 13.1, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
13.1
&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Clipboard/readText#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id=&quot;read&quot;&gt;read() &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#read&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;navigator.clipboard.read()&lt;/code&gt; method is also asynchronous and returns a
promise. To read an image from the clipboard, obtain a list of
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/ClipboardItem&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ClipboardItem&lt;/code&gt;&lt;/a&gt;
objects, then iterate over them.&lt;/p&gt;
&lt;p&gt;Each &lt;code&gt;ClipboardItem&lt;/code&gt; can hold its contents in different types, so you&#39;ll need to
iterate over the list of types, again using a &lt;code&gt;for...of&lt;/code&gt; loop. For each type,
call the &lt;code&gt;getType()&lt;/code&gt; method with the current type as an argument to obtain the
corresponding blob. As before, this code is not tied to images, and will
work with other future file types.&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;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getClipboardContents&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;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; clipboardItems &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;const&lt;/span&gt; clipboardItem &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; clipboardItems&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;const&lt;/span&gt; type &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; clipboardItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;types&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;const&lt;/span&gt; blob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; clipboardItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&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 constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&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;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 86, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      86
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 90, Behind a flag&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;flag&quot; title=&quot;Behind a flag&quot; aria-label=&quot;Behind a flag&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
79
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari 13.1, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
13.1
&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Clipboard/read#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id=&quot;working-with-pasted-files&quot;&gt;Working with pasted files &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#working-with-pasted-files&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It is useful for users to be able to use clipboard keyboard shortcuts such as
&lt;kbd&gt;ctrl&lt;/kbd&gt;+&lt;kbd&gt;c&lt;/kbd&gt; and &lt;kbd&gt;ctrl&lt;/kbd&gt;+&lt;kbd&gt;v&lt;/kbd&gt;.
Chromium exposes &lt;em&gt;read-only&lt;/em&gt; files on the clipboard as outlined below.
This triggers when the user hits the operating system&#39;s default paste shortcut
or when the user clicks &lt;strong&gt;Edit&lt;/strong&gt; then &lt;strong&gt;Paste&lt;/strong&gt; in the browser&#39;s menu bar.
No further plumbing code is needed.&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;&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;&quot;paste&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  e&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 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;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboardData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&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 keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboardData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&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;// Read the file&#39;s contents, assuming it&#39;s a text file.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// There is no way to write back to it.&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 keyword&quot;&gt;await&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&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 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;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 3, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      3
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 3.6, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      3.6
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 12, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      12
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 4, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      4
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/DataTransfer/files#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id=&quot;the-paste-event&quot;&gt;The paste event &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#the-paste-event&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As noted before, there are plans to introduce events to work with the Clipboard API,
but for now you can use the existing &lt;code&gt;paste&lt;/code&gt; event. It works nicely with the new
asynchronous methods for reading clipboard text. As with the &lt;code&gt;copy&lt;/code&gt; event, don&#39;t
forget to call &lt;code&gt;preventDefault()&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;document&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;paste&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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 operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  e&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 keyword&quot;&gt;const&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readText&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;  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;Pasted text: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; text&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;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      1
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 22, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      22
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 12, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      12
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 3, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      3
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/API/Element/copy_event#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;handling-multiple-mime-types&quot;&gt;Handling multiple MIME types &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#handling-multiple-mime-types&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most implementations put multiple data formats on the clipboard for a single cut
or copy operation. There are two reasons for this: as an app developer, you have
no way of knowing the capabilities of the app that a user wants to copy text or images to,
and many applications support pasting structured data as plain text. This is typically
presented to users with an &lt;strong&gt;Edit&lt;/strong&gt; menu item with a name such as &lt;strong&gt;Paste and
match style&lt;/strong&gt; or &lt;strong&gt;Paste without formatting&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The following example shows how to do this. This example uses &lt;code&gt;fetch()&lt;/code&gt; to obtain
image data, but it could also come from a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/canvas&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;&lt;/a&gt;
or the &lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&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 keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;copy&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;const&lt;/span&gt; image &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;kitten.png&#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 function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;blob&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 keyword&quot;&gt;const&lt;/span&gt; text &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;Blob&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 string&quot;&gt;&#39;Cute sleeping kitten&#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 punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text/plain&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; item &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;ClipboardItem&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 string-property property&quot;&gt;&#39;text/plain&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string-property property&quot;&gt;&#39;image/png&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; image&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 keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&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;item&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;h2 id=&quot;security-and-permissions&quot;&gt;Security and permissions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#security-and-permissions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Clipboard access has always presented a security concern for browsers. Without
proper permissions, a page could silently copy all manner of malicious content
to a user&#39;s clipboard that would produce catastrophic results when pasted.
Imagine a web page that silently copies &lt;code&gt;rm -rf /&lt;/code&gt; or a
&lt;a href=&quot;http://www.aerasec.de/security/advisories/decompression-bomb-vulnerability.html&quot; rel=&quot;noopener&quot;&gt;decompression bomb image&lt;/a&gt;
to your clipboard.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Browser prompt asking the user for the clipboard permission.&quot; decoding=&quot;async&quot; height=&quot;338&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Dt4QpuEuik9ja970Zos1.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The permission prompt for the Clipboard API.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Giving web pages unfettered read access to the clipboard is even more
troublesome. Users routinely copy sensitive information like passwords and
personal details to the clipboard, which could then be read by any page without
the user&#39;s knowledge.&lt;/p&gt;
&lt;p&gt;As with many new APIs, the Clipboard API is only supported for pages served over
HTTPS. To help prevent abuse, clipboard access is only allowed when a page is
the active tab. Pages in active tabs can write to the clipboard without
requesting permission, but reading from the clipboard always requires
permission.&lt;/p&gt;
&lt;p&gt;Permissions for copy and paste have been added to the
&lt;a href=&quot;https://developer.chrome.com/blog/permissions-api-for-the-web/&quot; rel=&quot;noopener&quot;&gt;Permissions API&lt;/a&gt;.
The &lt;code&gt;clipboard-write&lt;/code&gt; permission is granted automatically to pages when they are
the active tab. The &lt;code&gt;clipboard-read&lt;/code&gt; permission must be requested, which you can
do by trying to read data from the clipboard. The code below shows the latter:&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;const&lt;/span&gt; queryOpts &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 literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;clipboard-read&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;allowWithoutGesture&lt;/span&gt;&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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; permissionStatus &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;permissions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;queryOpts&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;// Will be &#39;granted&#39;, &#39;denied&#39; or &#39;prompt&#39;:&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;permissionStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&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;// Listen for changes to the permission state&lt;/span&gt;&lt;br /&gt;permissionStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onchange&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;span class=&quot;token operator&quot;&gt;=&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;permissionStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&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;p&gt;You can also control whether a user gesture is required to invoke cutting or
pasting using the &lt;code&gt;allowWithoutGesture&lt;/code&gt; option. The default for this value
varies by browser, so you should always include it.&lt;/p&gt;
&lt;p&gt;Here&#39;s where the asynchronous nature of the Clipboard API really comes in handy:
attempting to read or write clipboard data automatically prompts the user for
permission if it hasn&#39;t already been granted. Since the API is promise-based,
this is completely transparent, and a user denying clipboard permission causes
the promise to reject so the page can respond appropriately.&lt;/p&gt;
&lt;p&gt;Because browsers only allow clipboard access when a page is the active tab,
you&#39;ll find that some of the examples here don&#39;t run if pasted directly into
the browser&#39;s console, since the developer tools themselves are the active tab. There&#39;s a trick: defer
clipboard access using &lt;code&gt;setTimeout()&lt;/code&gt;, then quickly click inside the page to
focus it before the functions are called:&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 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;async&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;=&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;const&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readText&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;  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;text&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;2000&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;permissions-policy-integration&quot;&gt;Permissions policy integration &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#permissions-policy-integration&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To use the API in iframes, you need to enable it with
&lt;a href=&quot;https://developer.chrome.com/docs/privacy-sandbox/permissions-policy/&quot; rel=&quot;noopener&quot;&gt;Permissions Policy&lt;/a&gt;,
which defines a mechanism that allows for selectively enabling and
disabling various browser features and APIs. Concretely, you need to pass either
or both of &lt;code&gt;clipboard-read&lt;/code&gt; or &lt;code&gt;clipboard-write&lt;/code&gt;, depending on the needs of your app.&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;iframe&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&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;index.html&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token attr-name&quot;&gt;allow&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;clipboard-read; clipboard-write&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&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;iframe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The values of the &lt;code&gt;allow&lt;/code&gt; attribute need to be separated by semicolon. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;feature-detection&quot;&gt;Feature detection &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#feature-detection&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To use the Async Clipboard API while supporting all browsers, test for
&lt;code&gt;navigator.clipboard&lt;/code&gt; and fall back to earlier methods. For example, here&#39;s how
you might implement pasting to include other browsers.&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;&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;paste&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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 operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  e&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 keyword&quot;&gt;let&lt;/span&gt; text&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;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&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;    text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readText&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;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboardData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;text/plain&#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;  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;Got pasted text: &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; text&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;That&#39;s not the whole story. Before the Async Clipboard API, there were a mix of
different copy and paste implementations across web browsers. In most browsers,
the browser&#39;s own copy and paste can be triggered using
&lt;code&gt;document.execCommand(&#39;copy&#39;)&lt;/code&gt; and &lt;code&gt;document.execCommand(&#39;paste&#39;)&lt;/code&gt;. If the text
to be copied is a string not present in the DOM, it must be injected into the
DOM and selected:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;button&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 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 operator&quot;&gt;=&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;const&lt;/span&gt; input &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;input&#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;  input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;display &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;none&#39;&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;input&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;  input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;focus&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;  input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&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;const&lt;/span&gt; result &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;execCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;copy&#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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;unsuccessful&#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;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Failed to copy text.&#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;  input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;demos&quot;&gt;Demos &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#demos&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can play with the Async Clipboard API in the demos below. On Glitch you
can remix &lt;a href=&quot;https://glitch.com/edit/#!/async-clipboard-text&quot; rel=&quot;noopener&quot;&gt;the text demo&lt;/a&gt;
or &lt;a href=&quot;https://glitch.com/edit/#!/async-clipboard-api&quot; rel=&quot;noopener&quot;&gt;the image demo&lt;/a&gt; to
experiment with them.&lt;/p&gt;
&lt;p&gt;The first example demonstrates moving text on and off the clipboard.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 500px; width: 100%;&quot;&gt;
  &lt;iframe src=&quot;https://async-clipboard-text.glitch.me/&quot; title=&quot;async-clipboard-text on Glitch&quot; allow=&quot;clipboard-read; clipboard-write&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;
  &lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;To try the API with images, use this demo. Recall that only PNGs are supported
and only in
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Clipboard_API#browser_compatibility&quot; rel=&quot;noopener&quot;&gt;a few browsers&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 500px; width: 100%;&quot;&gt;
  &lt;iframe src=&quot;https://async-clipboard-api.glitch.me/&quot; title=&quot;async-clipboard-api on Glitch&quot; allow=&quot;clipboard-read; clipboard-write&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot;&gt;
  &lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;related-links&quot;&gt;Related links &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#related-links&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Clipboard_API&quot; rel=&quot;noopener&quot;&gt;MDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/blog/web-custom-formats-for-the-async-clipboard-api/&quot; rel=&quot;noopener&quot;&gt;Web custom formats&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/async-clipboard/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Asynchronous Clipboard API was implemented by &lt;a href=&quot;https://www.linkedin.com/in/darwinhuang/&quot; rel=&quot;noopener&quot;&gt;Darwin
Huang&lt;/a&gt; and &lt;a href=&quot;https://www.linkedin.com/in/garykac/&quot; rel=&quot;noopener&quot;&gt;Gary
Kačmarčík&lt;/a&gt;. Darwin also provided the demo.
Thanks to &lt;a href=&quot;https://github.com/kyarik&quot; rel=&quot;noopener&quot;&gt;Kyarik&lt;/a&gt; and again Gary Kačmarčík for
reviewing parts of this article.&lt;/p&gt;
&lt;p&gt;Hero image by &lt;a href=&quot;https://unsplash.com/@markuswinkler&quot; rel=&quot;noopener&quot;&gt;Markus Winkler&lt;/a&gt; on
&lt;a href=&quot;https://unsplash.com/photos/7iSEHWsxPLw&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Jason Miller</name>
    </author><author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Perform efficient per-video-frame operations on video with `requestVideoFrameCallback()`
</title>
    <link href="https://web.dev/requestvideoframecallback-rvfc/"/>
    <updated>2020-06-29T00:00:00Z</updated>
    <id>https://web.dev/requestvideoframecallback-rvfc/</id>
    <content type="html" mode="escaped">&lt;p&gt;There&#39;s a new Web API on the block, defined in the
&lt;a href=&quot;https://wicg.github.io/video-rvfc/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;HTMLVideoElement.requestVideoFrameCallback()&lt;/code&gt;&lt;/a&gt;
specification.
The &lt;code&gt;requestVideoFrameCallback()&lt;/code&gt; method allows web authors to register a callback
that runs in the rendering steps when a new video frame is sent to the compositor.
This is intended to allow developers to perform efficient per-video-frame operations on video,
such as video processing and painting to a canvas, video analysis,
or synchronization with external audio sources.&lt;/p&gt;
&lt;h2 id=&quot;difference-with-requestanimationframe&quot;&gt;Difference with requestAnimationFrame() &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/requestvideoframecallback-rvfc/#difference-with-requestanimationframe&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Operations like drawing a video frame to a canvas via
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/drawImage&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;drawImage()&lt;/code&gt;&lt;/a&gt;
made through this API will be synchronized as a best effort
with the frame rate of the video playing on screen.
Different from
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;window.requestAnimationFrame()&lt;/code&gt;&lt;/a&gt;,
which usually fires about 60 times per second,
&lt;code&gt;requestVideoFrameCallback()&lt;/code&gt; is bound to the actual video frame rate—with an important
&lt;a href=&quot;https://wicg.github.io/video-rvfc/#ref-for-update-the-rendering%E2%91%A2:~:text=Note%3A%20The%20effective%20rate%20at%20which,browser%20would%20fire%20callbacks%20at%2060hz.&quot; rel=&quot;noopener&quot;&gt;exception&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The effective rate at which callbacks are run is the lesser rate between the video&#39;s rate
and the browser&#39;s rate.
This means a 25fps video playing in a browser that paints at 60Hz
would fire callbacks at 25Hz.
A 120fps video in that same 60Hz browser would fire callbacks at 60Hz.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;whats-in-a-name&quot;&gt;What&#39;s in a name? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/requestvideoframecallback-rvfc/#whats-in-a-name&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Due to its similarity with &lt;code&gt;window.requestAnimationFrame()&lt;/code&gt;, the method initially
was &lt;a href=&quot;https://discourse.wicg.io/t/proposal-video-requestanimationframe/3691&quot; rel=&quot;noopener&quot;&gt;proposed as &lt;code&gt;video.requestAnimationFrame()&lt;/code&gt;&lt;/a&gt;,
but I&#39;m happy with the new name,
&lt;code&gt;requestVideoFrameCallback()&lt;/code&gt;, which was agreed on
after a &lt;a href=&quot;https://github.com/WICG/video-rvfc/issues/44&quot; rel=&quot;noopener&quot;&gt;lengthy discussion&lt;/a&gt;.
Yay, &lt;a href=&quot;https://css-tricks.com/what-is-bikeshedding/&quot; rel=&quot;noopener&quot;&gt;bikeshedding&lt;/a&gt; for the win!&lt;/p&gt;
&lt;h2 id=&quot;feature-detection&quot;&gt;Feature detection &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/requestvideoframecallback-rvfc/#feature-detection&quot;&gt;#&lt;/a&gt;&lt;/h2&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;&lt;span class=&quot;token string&quot;&gt;&#39;requestVideoFrameCallback&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLVideoElement&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 comment&quot;&gt;// The API is supported! &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;browser-support&quot;&gt;Browser support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/requestvideoframecallback-rvfc/#browser-support&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 83, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      83
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 83, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
83
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari 15.4, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
15.4
&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id=&quot;polyfill&quot;&gt;Polyfill &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/requestvideoframecallback-rvfc/#polyfill&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A &lt;a href=&quot;https://github.com/ThaUnknown/rvfc-polyfill&quot; rel=&quot;noopener&quot;&gt;polyfill for the &lt;code&gt;requestVideoFrameCallback()&lt;/code&gt; method&lt;/a&gt;
based on
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Window.requestAnimationFrame()&lt;/code&gt;&lt;/a&gt;
and
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLVideoElement/getVideoPlaybackQuality&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;HTMLVideoElement.getVideoPlaybackQuality()&lt;/code&gt;&lt;/a&gt;
is available. Before using this, be aware of the
&lt;a href=&quot;https://github.com/ThaUnknown/rvfc-polyfill#requestvideoframecallback-polyfill&quot; rel=&quot;noopener&quot;&gt;limitations mentioned in the &lt;code&gt;README&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;using-the-requestvideoframecallback-method&quot;&gt;Using the requestVideoFrameCallback() method &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/requestvideoframecallback-rvfc/#using-the-requestvideoframecallback-method&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you have ever used the &lt;code&gt;requestAnimationFrame()&lt;/code&gt; method, you will immediately feel at home with the &lt;code&gt;requestVideoFrameCallback()&lt;/code&gt; method.
You register an initial callback once, and then re-register whenever the callback fires.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;doSomethingWithTheFrame&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 parameter&quot;&gt;now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;// Do something with the frame.&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;now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metadata&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;// Re-register the callback to be notified about the next frame.&lt;/span&gt;&lt;br /&gt;  video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestVideoFrameCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doSomethingWithTheFrame&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;span class=&quot;token comment&quot;&gt;// Initially register the callback to be notified about the first frame.&lt;/span&gt;&lt;br /&gt;video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestVideoFrameCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doSomethingWithTheFrame&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 the callback, &lt;code&gt;now&lt;/code&gt; is a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/DOMHighResTimeStamp&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;DOMHighResTimeStamp&lt;/code&gt;&lt;/a&gt;
and &lt;code&gt;metadata&lt;/code&gt; is a &lt;a href=&quot;https://wicg.github.io/video-rvfc/#dictdef-videoframemetadata&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;VideoFrameMetadata&lt;/code&gt;&lt;/a&gt;
dictionary with the following properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;presentationTime&lt;/code&gt;, of type &lt;code&gt;DOMHighResTimeStamp&lt;/code&gt;:
The time at which the user agent submitted the frame for composition.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;expectedDisplayTime&lt;/code&gt;, of type &lt;code&gt;DOMHighResTimeStamp&lt;/code&gt;:
The time at which the user agent expects the frame to be visible.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;width&lt;/code&gt;, of type &lt;code&gt;unsigned long&lt;/code&gt;:
The width of the video frame, in media pixels.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;height&lt;/code&gt;, of type &lt;code&gt;unsigned long&lt;/code&gt;:
The height of the video frame, in media pixels.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mediaTime&lt;/code&gt;, of type &lt;code&gt;double&lt;/code&gt;:
The media presentation timestamp (PTS) in seconds of the frame presented (e.g., its timestamp on the &lt;code&gt;video.currentTime&lt;/code&gt; timeline).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;presentedFrames&lt;/code&gt;, of type &lt;code&gt;unsigned long&lt;/code&gt;:
A count of the number of frames submitted for composition. Allows clients to determine if frames were missed between instances of &lt;code&gt;VideoFrameRequestCallback&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;processingDuration&lt;/code&gt;, of type &lt;code&gt;double&lt;/code&gt;:
The elapsed duration in seconds from submission of the encoded packet with the same presentation timestamp (PTS) as this frame (e.g., same as the &lt;code&gt;mediaTime&lt;/code&gt;) to the decoder until the decoded frame was ready for presentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For WebRTC applications, additional properties may appear:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;captureTime&lt;/code&gt;, of type &lt;code&gt;DOMHighResTimeStamp&lt;/code&gt;:
For video frames coming from either a local or remote source, this is the time at which the frame was captured by the camera.
For a remote source, the capture time is estimated using clock synchronization and RTCP sender reports
to convert RTP timestamps to capture time.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;receiveTime&lt;/code&gt;, of type &lt;code&gt;DOMHighResTimeStamp&lt;/code&gt;:
For video frames coming from a remote source, this is the time the encoded frame was received
by the platform, i.e., the time at which the last packet belonging to this frame was received over the network.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rtpTimestamp&lt;/code&gt;, of type &lt;code&gt;unsigned long&lt;/code&gt;:
The RTP timestamp associated with this video frame.&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Note that &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; might differ from &lt;code&gt;videoWidth&lt;/code&gt; and &lt;code&gt;videoHeight&lt;/code&gt; in certain cases (e.g., an anamorphic video might have rectangular pixels). &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Of special interest in this list is &lt;code&gt;mediaTime&lt;/code&gt;.
In Chromium&#39;s implementation, we use the audio clock as the time source that backs &lt;code&gt;video.currentTime&lt;/code&gt;,
whereas the &lt;code&gt;mediaTime&lt;/code&gt; is directly populated by the &lt;code&gt;presentationTimestamp&lt;/code&gt; of the frame.
The &lt;code&gt;mediaTime&lt;/code&gt; is what you should use if you want to exactly identify frames in a reproducible way,
including to identify exactly which frames you missed.&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; Unfortunately, the video element does not guarantee frame-accurate &lt;em&gt;seeking&lt;/em&gt;. This has been an ongoing &lt;a href=&quot;https://github.com/w3c/media-and-entertainment/issues/4&quot;&gt;subject of discussion&lt;/a&gt;. &lt;a href=&quot;https://github.com/WICG/web-codecs&quot;&gt;WebCodecs&lt;/a&gt; will eventually allow for frame accurate applications. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;if-things-seem-one-frame-off&quot;&gt;If things seem one frame off… &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/requestvideoframecallback-rvfc/#if-things-seem-one-frame-off&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Vertical synchronization (or just vsync), is a graphics technology that synchronizes the frame rate of a video and the refresh rate of a monitor.
Since &lt;code&gt;requestVideoFrameCallback()&lt;/code&gt; runs on the main thread, but, under the hood, video compositing happens on the compositor thread,
everything from this API is a best effort, and we do not offer any strict guarantees.
What may be happening is that the API can be one vsync late relative to when a video frame is rendered.
It takes one vsync for changes made to the web page through the API to appear on screen (same as &lt;code&gt;window.requestAnimationFrame()&lt;/code&gt;).
So if you keep updating the &lt;code&gt;mediaTime&lt;/code&gt; or frame number on your web page and compare that
against the numbered video frames, eventually the video will look like it is one frame ahead.&lt;/p&gt;
&lt;p&gt;What is really happening is that the frame is ready at vsync x, the callback is fired and the frame is rendered at vsync x+1,
and changes made in the callback are rendered at vsync x+2.
You can check whether the callback is a vsync late (and the frame is already rendered on screen)
by checking whether the &lt;code&gt;metadata.expectedDisplayTime&lt;/code&gt; is roughly &lt;code&gt;now&lt;/code&gt; or one vsync in the future.
If it is within about five to ten microseconds of &lt;code&gt;now&lt;/code&gt;, the frame is already rendered;
if the &lt;code&gt;expectedDisplayTime&lt;/code&gt; is approximately sixteen milliseconds in the future (assuming your browser/screen is refreshing at 60Hz),
then you are in sync with the frame.&lt;/p&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/requestvideoframecallback-rvfc/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have created a small
&lt;a href=&quot;https://requestvideoframecallback.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo on Glitch&lt;/a&gt;
that shows how frames are drawn on a canvas at exactly
the frame rate of the video and
where the frame metadata is logged for debugging purposes.
The core logic is just a couple of lines of JavaScript.&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;let&lt;/span&gt; paintCount &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 keyword&quot;&gt;let&lt;/span&gt; startTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.0&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;updateCanvas&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 parameter&quot;&gt;now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;startTime &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.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;    startTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; now&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;  ctx&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;video&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;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; elapsed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; startTime&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.0&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;const&lt;/span&gt; fps &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 operator&quot;&gt;++&lt;/span&gt;paintCount &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; elapsed&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;toFixed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&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;  fpsInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerText &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;video fps: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;fps&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  metadataInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerText &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&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;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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestVideoFrameCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;updateCanvas&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;video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestVideoFrameCallback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;updateCanvas&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;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 1200px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/requestvideoframecallback?attributionHidden=true&amp;sidebarCollapsed=true&amp;path=script.js&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;requestvideoframecallback on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/requestvideoframecallback-rvfc/#conclusions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have done frame-level processing for a long time—without having access to the actual frames,
only based on &lt;code&gt;video.currentTime&lt;/code&gt;.
I implemented video shot segmentation in JavaScript
in a rough-and-ready manner; you can still read the accompanying
&lt;a href=&quot;https://www2012.universite-lyon.fr/proceedings/nocompanion/DevTrack_028.pdf&quot; rel=&quot;noopener&quot;&gt;research paper&lt;/a&gt;.
Had the &lt;code&gt;requestVideoFrameCallback()&lt;/code&gt; existed back then, my life would have been much simpler…&lt;/p&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/requestvideoframecallback-rvfc/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;requestVideoFrameCallback&lt;/code&gt; API was specified and implemented by
&lt;a href=&quot;https://github.com/tguilbert-google&quot; rel=&quot;noopener&quot;&gt;Thomas Guilbert&lt;/a&gt;.
This article was reviewed by &lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;
and &lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;.
&lt;a href=&quot;https://unsplash.com/photos/tV80374iytg&quot; rel=&quot;noopener&quot;&gt;Hero image&lt;/a&gt; by
&lt;a href=&quot;https://unsplash.com/@dmjdenise&quot; rel=&quot;noopener&quot;&gt;Denise Jans&lt;/a&gt; on Unsplash.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Progressively enhance your Progressive Web App</title>
    <link href="https://web.dev/progressively-enhance-your-pwa/"/>
    <updated>2020-06-29T00:00:00Z</updated>
    <id>https://web.dev/progressively-enhance-your-pwa/</id>
    <content type="html" mode="escaped">&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;NXCT3htg9nk&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;p&gt;Back in March 2003, &lt;a href=&quot;https://twitter.com/nickf&quot; rel=&quot;noopener&quot;&gt;Nick Finck&lt;/a&gt; and
&lt;a href=&quot;https://twitter.com/schampeo&quot; rel=&quot;noopener&quot;&gt;Steve Champeon&lt;/a&gt; stunned the web design world
with the concept of
&lt;a href=&quot;http://www.hesketh.com/publications/inclusive_web_design_for_the_future/&quot; rel=&quot;noopener&quot;&gt;progressive enhancement&lt;/a&gt;,
a strategy for web design that emphasizes loading core web page content first,
and that then progressively adds more nuanced
and technically rigorous layers of presentation and features on top of the content.
While in 2003, progressive enhancement was about using—at the time—modern
CSS features, unobtrusive JavaScript, and even just Scalable Vector Graphics.
Progressive enhancement in 2020 and beyond is about using
&lt;a href=&quot;https://developer.chrome.com/blog/fugu-status/&quot; rel=&quot;noopener&quot;&gt;modern browser capabilities&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Inclusive web design for the future with progressive enhancement. Title slide from Finck and Champeon&amp;#x27;s original presentation.&quot; decoding=&quot;async&quot; height=&quot;597&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IEOd4MT9BqnbeXQ7z0vC.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Slide: Inclusive Web Design for the Future With Progressive Enhancement.
    (&lt;a href=&quot;http://www.hesketh.com/publications/inclusive_web_design_for_the_future/&quot;&gt;Source&lt;/a&gt;)
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;modern-javascript&quot;&gt;Modern JavaScript &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#modern-javascript&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Speaking of JavaScript, the browser support situation for the latest core ES 2015 JavaScript
features is great.
The new standard includes promises, modules, classes, template literals, arrow functions, &lt;code&gt;let&lt;/code&gt; and &lt;code&gt;const&lt;/code&gt;,
default parameters, generators, the destructuring assignment, rest and spread, &lt;code&gt;Map&lt;/code&gt;/&lt;code&gt;Set&lt;/code&gt;,
&lt;code&gt;WeakMap&lt;/code&gt;/&lt;code&gt;WeakSet&lt;/code&gt;, and many more.
&lt;a href=&quot;https://caniuse.com/#feat=es6&quot; rel=&quot;noopener&quot;&gt;All are supported&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The CanIUse support table for ES6 features showing support across all major browsers.&quot; decoding=&quot;async&quot; height=&quot;296&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sYcABrEPMr01C2ilp4B0.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The ECMAScript 2015 (ES6) browser support table. (&lt;a href=&quot;https://caniuse.com/#feat=es6&quot;&gt;Source&lt;/a&gt;)
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Async functions, an ES 2017 feature and one of my personal favorites,
&lt;a href=&quot;https://caniuse.com/#feat=async-functions&quot; rel=&quot;noopener&quot;&gt;can be used&lt;/a&gt;
in all major browsers.
The &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt; keywords enable asynchronous, promise-based behavior
to be written in a cleaner style, avoiding the need to explicitly configure promise chains.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The CanIUse support table for async functions showing support across all major browsers.&quot; decoding=&quot;async&quot; height=&quot;247&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/0WFlQTFFTlqXKvpROMu9.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The Async functions browser support table. (&lt;a href=&quot;https://caniuse.com/#feat=async-functions&quot;&gt;Source&lt;/a&gt;)
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;And even super recent ES 2020 language additions like
&lt;a href=&quot;https://caniuse.com/#feat=mdn-javascript_operators_optional_chaining&quot; rel=&quot;noopener&quot;&gt;optional chaining&lt;/a&gt; and
&lt;a href=&quot;https://caniuse.com/#feat=mdn-javascript_operators_nullish_coalescing&quot; rel=&quot;noopener&quot;&gt;nullish coalescing&lt;/a&gt;
have reached support really quickly. You can see a code sample below.
When it comes to core JavaScript features, the grass couldn&#39;t be much greener than it
is today.&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;const&lt;/span&gt; adventurer &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 literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Alice&#39;&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;cat&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 literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Dinah&#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;&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;adventurer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dog&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;name&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;// Expected output: undefined&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 number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&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;// Expected output: 0&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;figure data-size=&quot;full&quot;&gt;
  &lt;img alt=&quot;The iconic Windows XP green grass background image.&quot; decoding=&quot;async&quot; height=&quot;500&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/v1nhcTV9aaqPKd9oRvYz.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The grass is green when it comes to core JavaScript features.
    (Microsoft product screenshot, used with
    &lt;a href=&quot;https://www.microsoft.com/en-us/legal/intellectualproperty/permissions/default&quot;&gt;permission&lt;/a&gt;.)
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-sample-app-fugu-greetings&quot;&gt;The sample app: Fugu Greetings &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#the-sample-app-fugu-greetings&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For this article, I work with a simple PWA, called
&lt;a href=&quot;https://tomayac.github.io/fugu-greetings/public/&quot; rel=&quot;noopener&quot;&gt;Fugu Greetings&lt;/a&gt;
(&lt;a href=&quot;https://github.com/tomayac/fugu-greetings&quot; rel=&quot;noopener&quot;&gt;GitHub&lt;/a&gt;).
The name of this app is a tip of the hat to Project Fugu 🐡, an effort to give the web all
the powers of Android/iOS/desktop applications.
You can read more about the project on its
&lt;a href=&quot;https://developer.chrome.com/blog/fugu-status&quot; rel=&quot;noopener&quot;&gt;landing page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Fugu Greetings is a drawing app that lets you create virtual greeting cards, and send
them to your loved ones. It exemplifies
&lt;a href=&quot;https://web.dev/progressive-web-apps/&quot;&gt;PWA&#39;s core concepts&lt;/a&gt;. It&#39;s
&lt;a href=&quot;https://web.dev/reliable/&quot;&gt;reliable&lt;/a&gt; and fully offline enabled, so even if you don&#39;t
have a network, you can still use it. It&#39;s also &lt;a href=&quot;https://web.dev/install-criteria/&quot;&gt;Installable&lt;/a&gt;
to a device&#39;s home screen and integrates seamlessly with the operating system
as a stand-alone application.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Fugu Greetings PWA with a drawing that resembles the PWA community logo.&quot; decoding=&quot;async&quot; height=&quot;543&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w0id013BKBF2z7m70TrX.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The &lt;a href=&quot;https://tomayac.github.io/fugu-greetings/public/&quot;&gt;Fugu Greetings&lt;/a&gt; sample app.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;progressive-enhancement&quot;&gt;Progressive enhancement &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#progressive-enhancement&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With this out of the way, it&#39;s time to talk about &lt;em&gt;progressive enhancement&lt;/em&gt;.
The MDN Web Docs Glossary &lt;a href=&quot;https://developer.mozilla.org/docs/Glossary/Progressive_Enhancement&quot; rel=&quot;noopener&quot;&gt;defines&lt;/a&gt;
the concept as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Progressive enhancement is a design philosophy that provides a baseline of
essential content and functionality to as many users as possible, while
delivering the best possible experience only to users of the most modern
browsers that can run all the required code.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection&quot; rel=&quot;noopener&quot;&gt;Feature detection&lt;/a&gt;
is generally used to determine whether browsers can handle more modern functionality,
while &lt;a href=&quot;https://developer.mozilla.org/docs/Glossary/Polyfill&quot; rel=&quot;noopener&quot;&gt;polyfills&lt;/a&gt;
are often used to add missing features with JavaScript.&lt;/p&gt;
&lt;p&gt;[…]&lt;/p&gt;
&lt;p&gt;Progressive enhancement is a useful technique that allows web developers to focus
on developing the best possible websites while making those websites work
on multiple unknown user agents.
&lt;a href=&quot;https://developer.mozilla.org/docs/Glossary/Graceful_degradation&quot; rel=&quot;noopener&quot;&gt;Graceful degradation&lt;/a&gt;
is related, but is not the same thing and is often seen as going in the opposite direction
to progressive enhancement.
In reality, both approaches are valid and can often complement one another.&lt;/p&gt;
&lt;p&gt;&lt;cite&gt;MDN contributors&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; This is not an introductory article on progressive enhancement, but assumes you are familiar with the concept. For a solid foundation, I recommend Steve Champeon&#39;s article &lt;a href=&quot;http://www.hesketh.com/progressive_enhancement_and_the_future_of_web_design.html&quot;&gt;Progressive Enhancement and the Future of Web Design&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Starting each greeting card from scratch can be really cumbersome.
So why not have a feature that allows users to import an image, and start from there?
With a traditional approach, you&#39;d have used an
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/input/file&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;input type=file&amp;gt;&lt;/code&gt;&lt;/a&gt;
element to make this happen.
First, you&#39;d create the element, set its &lt;code&gt;type&lt;/code&gt; to &lt;code&gt;&#39;file&#39;&lt;/code&gt; and add MIME types to the &lt;code&gt;accept&lt;/code&gt; property,
and then programmatically &amp;quot;click&amp;quot; it and listen for changes.
When you select an image, it is imported straight onto the canvas.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;importImage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&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 parameter&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; input &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;input&#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;    input&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 string&quot;&gt;&#39;file&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;accept &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image/*&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    input&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;change&#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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&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 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;    input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&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 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;When there&#39;s an &lt;em&gt;import&lt;/em&gt; feature, there probably should be an &lt;em&gt;export&lt;/em&gt; feature
so users can save their greeting cards locally.
The traditional way of saving files is to create an anchor link
with a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/a#download&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;download&lt;/code&gt;&lt;/a&gt;
attribute and with a blob URL as its &lt;code&gt;href&lt;/code&gt;.
You&#39;d also programmatically &amp;quot;click&amp;quot; it to trigger the download,
and, to prevent memory leaks, hopefully not forget to revoke the blob object URL.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;exportImage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; a &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;a&#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;  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;download &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;fugu-greeting.png&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&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;  a&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 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 operator&quot;&gt;=&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;setTimeout&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;=&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;revokeObjectURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&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;30&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;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;But wait a minute. Mentally, you haven&#39;t &amp;quot;downloaded&amp;quot; a greeting card, you have
&amp;quot;saved&amp;quot; it.
Rather than showing you a &amp;quot;save&amp;quot; dialog that lets you choose where to put the file,
the browser has directly downloaded the greeting card without user interaction
and has put it straight into your Downloads folder. This isn&#39;t great.&lt;/p&gt;
&lt;p&gt;What if there were a better way?
What if you could just open a local file, edit it, and then save the modifications,
either to a new file, or back to the original file that you had initially opened?
Turns out there is. The &lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt;
allows you to open and create files and
directories, as well as modify and save them .&lt;/p&gt;
&lt;p&gt;So how do I feature-detect an API?
The File System Access API exposes a new method &lt;code&gt;window.chooseFileSystemEntries()&lt;/code&gt;.
Consequently, I need to conditionally load different import and export modules depending on whether this method is available. I&#39;ve shown how to do this below.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;loadImportAndExport&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;span class=&quot;token operator&quot;&gt;=&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 string&quot;&gt;&#39;chooseFileSystemEntries&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&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;    Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./import_image.mjs&#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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./export_image.mjs&#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 keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./import_image_legacy.mjs&#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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./export_image_legacy.mjs&#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;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;But before I dive into the File System Access API details,
let me just quickly highlight the progressive enhancement pattern here.
On browsers that currently don&#39;t support the File System Access API, I load the legacy scripts.
You can see the network tabs of Firefox and Safari below.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Safari Web Inspector showing the legacy files getting loaded.&quot; decoding=&quot;async&quot; height=&quot;114&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rnPL8xIMt6HJEUbDfrez.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Safari Web Inspector network tab.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Firefox Developer Tools showing the legacy files getting loaded.&quot; decoding=&quot;async&quot; height=&quot;166&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bui4rcv0jvlVLHI3jBoo.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Firefox Developer Tools network tab.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;However, on Chrome, a browser that supports the API, only the new scripts are loaded.
This is made elegantly possible thanks to
&lt;a href=&quot;https://v8.dev/features/dynamic-import&quot; rel=&quot;noopener&quot;&gt;dynamic &lt;code&gt;import()&lt;/code&gt;&lt;/a&gt;, which all modern browsers
&lt;a href=&quot;https://caniuse.com/#feat=es6-module-dynamic-import&quot; rel=&quot;noopener&quot;&gt;support&lt;/a&gt;.
As I said earlier, the grass is pretty green these days.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Chrome DevTools showing the modern files getting loaded.&quot; decoding=&quot;async&quot; height=&quot;241&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/plf6kGtFE8g9Fjogv8ia.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Chrome DevTools network tab.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-file-system-access-api&quot;&gt;The File System Access API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#the-file-system-access-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So now that I have addressed this, it&#39;s time to look at the actual implementation based on the File System Access API.
For importing an image, I call &lt;code&gt;window.chooseFileSystemEntries()&lt;/code&gt;
and pass it an &lt;code&gt;accepts&lt;/code&gt; property where I say I want image files.
Both file extensions as well as MIME types are supported.
This results in a file handle, from which I can get the actual file by calling &lt;code&gt;getFile()&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;importImage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;chooseFileSystemEntries&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;accepts&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Image files&#39;&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;mimeTypes&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 string&quot;&gt;&#39;image/*&#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;extensions&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 string&quot;&gt;&#39;jpg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;jpeg&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;png&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;webp&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;svg&#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;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 keyword&quot;&gt;return&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFile&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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;Exporting an image is almost the same, but this time
I need to pass a type parameter of &lt;code&gt;&#39;save-file&#39;&lt;/code&gt; to the &lt;code&gt;chooseFileSystemEntries()&lt;/code&gt; method.
From this I get a file save dialog.
With file open, this wasn&#39;t necessary since &lt;code&gt;&#39;open-file&#39;&lt;/code&gt; is the default.
I set the &lt;code&gt;accepts&lt;/code&gt; parameter similarly to before, but this time limited to just PNG images.
Again I get back a file handle, but rather than getting the file,
this time I create a writable stream by calling &lt;code&gt;createWritable()&lt;/code&gt;.
Next, I write the blob, which is my greeting card image, to the file.
Finally, I close the writable stream.&lt;/p&gt;
&lt;p&gt;Everything can always fail: The disk could be out of space,
there could be a write or read error, or maybe simply the user cancels the file dialog.
This is why I always wrap the calls in a &lt;code&gt;try...catch&lt;/code&gt; statement.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;exportImage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;chooseFileSystemEntries&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;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;save-file&#39;&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;accepts&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token literal-property property&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Image file&#39;&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;extensions&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 string&quot;&gt;&#39;png&#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;mimeTypes&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 string&quot;&gt;&#39;image/png&#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;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 keyword&quot;&gt;const&lt;/span&gt; writable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWritable&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;await&lt;/span&gt; writable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&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;await&lt;/span&gt; writable&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;Using progressive enhancement with the File System Access API,
I can open a file as before.
The imported file is drawn right onto the canvas.
I can make my edits and finally save them with a real save dialog box
where I can choose the name and storage location of the file.
Now the file is ready to be preserved for eternity.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Fugu Greetings app with a file open dialog.&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TEKHbetFMURVWh4QPRJw.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The file open dialog.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Fugu Greetings app now with an imported image.&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/tls52mkxDB513SzzcfNj.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The imported image.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Fugu Greetings app with the modified image.&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3LwHvtROaN1bHJN1El5D.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Saving the modified image to a new file.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-web-share-and-web-share-target-apis&quot;&gt;The Web Share and Web Share Target APIs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#the-web-share-and-web-share-target-apis&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Apart from storing for eternity, maybe I actually want to share my greeting card.
This is something that the &lt;a href=&quot;https://web.dev/web-share/&quot;&gt;Web Share API&lt;/a&gt; and
&lt;a href=&quot;https://web.dev/web-share-target/&quot;&gt;Web Share Target API&lt;/a&gt; allow me to do.
Mobile, and more recently desktop operating systems have gained built-in sharing
mechanisms.
For example, below is desktop Safari&#39;s share sheet on macOS triggered from an article on
my &lt;a href=&quot;https://blog.tomayac.com/&quot; rel=&quot;noopener&quot;&gt;blog&lt;/a&gt;.
When you click the &lt;strong&gt;Share Article&lt;/strong&gt; button, you can share a link to the article with a friend, for
example, via the macOS Messages app.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Desktop Safari&amp;#x27;s share sheet on macOS triggered from an article&amp;#x27;s Share button&quot; decoding=&quot;async&quot; height=&quot;423&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/sinRHRHFgSgwAC7x8dIZ.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Web Share API on desktop Safari on macOS.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The code to make this happen is pretty straightforward. I call &lt;code&gt;navigator.share()&lt;/code&gt; and
pass it an optional &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt;, and &lt;code&gt;url&lt;/code&gt; in an object.
But what if I want to attach an image? Level 1 of the Web Share API doesn&#39;t support this yet.
The good news is that Web Share Level 2 has added file sharing capabilities.&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;try&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;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;share&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;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Check out this article:&#39;&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;text&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; by @tomayac:&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&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 literal-property property&quot;&gt;url&lt;/span&gt;&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;link[rel=canonical]&#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;href&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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;warn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;Let me show you how to make this work with the Fugu Greeting card application.
First, I need to prepare a &lt;code&gt;data&lt;/code&gt; object with a &lt;code&gt;files&lt;/code&gt; array consisting of one blob, and then
a &lt;code&gt;title&lt;/code&gt; and a &lt;code&gt;text&lt;/code&gt;. Next, as a best practice, I use the new &lt;code&gt;navigator.canShare()&lt;/code&gt; method which does
what its name suggests:
It tells me if the &lt;code&gt;data&lt;/code&gt; object I&#39;m trying to share can technically be shared by the browser.
If &lt;code&gt;navigator.canShare()&lt;/code&gt; tells me the data can be shared, I&#39;m ready to
call &lt;code&gt;navigator.share()&lt;/code&gt; as before.
Because everything can fail, I&#39;m again using a &lt;code&gt;try...catch&lt;/code&gt; block.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;share&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; blob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; data &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 literal-property property&quot;&gt;files&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 keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;File&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;blob&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 string&quot;&gt;&#39;fugu-greeting.png&#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;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; blob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&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;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; text&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 keyword&quot;&gt;try&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 punctuation&quot;&gt;(&lt;/span&gt;navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;canShare&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&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 keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Can&#39;t share data.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&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;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;share&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;As before, I use progressive enhancement.
If both &lt;code&gt;&#39;share&#39;&lt;/code&gt; and &lt;code&gt;&#39;canShare&#39;&lt;/code&gt; exist on the &lt;code&gt;navigator&lt;/code&gt; object, only then I go forward and
load &lt;code&gt;share.mjs&lt;/code&gt; via dynamic &lt;code&gt;import()&lt;/code&gt;.
On browsers like mobile Safari that only fulfill one of the two conditions, I don&#39;t load
the functionality.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;loadShare&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;span class=&quot;token operator&quot;&gt;=&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 string&quot;&gt;&#39;share&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;canShare&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./share.mjs&#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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In Fugu Greetings, if I tap the &lt;strong&gt;Share&lt;/strong&gt; button on a supporting browser like Chrome on Android,
the built-in share sheet opens.
I can, for example, choose Gmail, and the email composer widget pops up with the
image attached.&lt;/p&gt;
&lt;div class=&quot;switcher&quot;&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;OS-level share sheet showing various apps to share the image to.&quot; decoding=&quot;async&quot; height=&quot;1645&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/szInJQl908kv9GEU8EJf.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;
      Choosing an app to share the file to.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;Gmail&amp;#x27;s email compose widget with the image attached.&quot; decoding=&quot;async&quot; height=&quot;1645&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mKmdg1OMGpWDukmfSSOl.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;
      The file gets attached to a new email in Gmail&#39;s composer.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-contact-picker-api&quot;&gt;The Contact Picker API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#the-contact-picker-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Next, I want to talk about contacts, meaning a device&#39;s address book
or contacts manager app.
When you write a greeting card, it may not always be easy to correctly write
someone&#39;s name.
For example, I have a friend Sergey who prefers his name to be spelled in Cyrillic letters. I&#39;m
using a German QWERTZ keyboard and have no idea how to type their name.
This is a problem that the &lt;a href=&quot;https://web.dev/contact-picker/&quot;&gt;Contact Picker API&lt;/a&gt; can solve.
Since I have my friend stored in my phone&#39;s contacts app,
via the Contacts Picker API, I can tap into my contacts from the web.&lt;/p&gt;
&lt;p&gt;First, I need to specify the list of properties I want to access.
In this case, I only want the names,
but for other use cases I might be interested in telephone numbers, emails, avatar
icons, or physical addresses.
Next, I configure an &lt;code&gt;options&lt;/code&gt; object and set &lt;code&gt;multiple&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;, so that I can select more
than one entry.
Finally, I can call &lt;code&gt;navigator.contacts.select()&lt;/code&gt;, which returns the desired properties
for the user-selected contacts.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getContacts&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;const&lt;/span&gt; properties &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 string&quot;&gt;&#39;name&#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;const&lt;/span&gt; options &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 literal-property property&quot;&gt;multiple&lt;/span&gt;&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;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;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contacts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;properties&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;And by now you&#39;ve probably learned the pattern:
I only load the file when the API is actually supported.&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;&lt;span class=&quot;token string&quot;&gt;&#39;contacts&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./contacts.mjs&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In Fugu Greeting, when I tap the &lt;strong&gt;Contacts&lt;/strong&gt; button and select my two best pals,
&lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%91%D1%80%D0%B8%D0%BD,_%D0%A1%D0%B5%D1%80%D0%B3%D0%B5%D0%B9_%D0%9C%D0%B8%D1%85%D0%B0%D0%B9%D0%BB%D0%BE%D0%B2%D0%B8%D1%87&quot; rel=&quot;noopener&quot;&gt;Сергей Михайлович Брин&lt;/a&gt; and &lt;a href=&quot;https://zh.wikipedia.org/wiki/%E6%8B%89%E9%87%8C%C2%B7%E4%BD%A9%E5%A5%87&quot; rel=&quot;noopener&quot;&gt;劳伦斯·爱德华·&amp;quot;拉里&amp;quot;·佩奇&lt;/a&gt;,
you can see how the
contacts picker is limited to only show their names,
but not their email addresses, or other information like their phone numbers.
Their names are then drawn onto my greeting card.&lt;/p&gt;
&lt;div class=&quot;switcher&quot;&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;Contacts picker showing the names of two contacts in the address book.&quot; decoding=&quot;async&quot; height=&quot;1645&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/T5gmSr1XGiVIV9Pw1HbC.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;
      Selecting two names with the contact picker from the address book.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;The names of the two previously picked contacts drawn on the greeting card.&quot; decoding=&quot;async&quot; height=&quot;1644&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ioMOCEHwvwdyzS7DX5L8.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;
      The two names then get drawn onto the greeting card.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;h2 id=&quot;the-asynchronous-clipboard-api&quot;&gt;The Asynchronous Clipboard API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#the-asynchronous-clipboard-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Up next is copying and pasting.
One of our favorite operations as software developers is copy and paste.
As a greeting card author, at times, I may want to do the same.
I may want to either paste an image into a greeting card I&#39;m working on,
or copy my greeting card so I can continue editing it from
somewhere else.
The &lt;a href=&quot;https://web.dev/image-support-for-async-clipboard/&quot;&gt;Async Clipboard API&lt;/a&gt;,
supports both text and images.
Let me walk you through how I added copy and paste support to the Fugu
Greetings app.&lt;/p&gt;
&lt;p&gt;In order to copy something onto the system&#39;s clipboard, I need to write to it.
The &lt;code&gt;navigator.clipboard.write()&lt;/code&gt; method takes an array of clipboard items as a
parameter.
Each clipboard item is essentially an object with a blob as a value, and the blob&#39;s type
as the key.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;copy&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&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;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClipboardItem&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;blob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; blob&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;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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;To paste, I need to loop over the clipboard items that I obtain by calling
&lt;code&gt;navigator.clipboard.read()&lt;/code&gt;.
The reason for this is that multiple clipboard items might be on the clipboard in
different representations.
Each clipboard item has a &lt;code&gt;types&lt;/code&gt; field that tells me the MIME types of the available
resources.
I call the clipboard item&#39;s &lt;code&gt;getType()&lt;/code&gt; method, passing the
MIME type I obtained before.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;paste&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; clipboardItems &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&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;const&lt;/span&gt; clipboardItem &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; clipboardItems&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;        &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;const&lt;/span&gt; type &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; clipboardItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;types&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;const&lt;/span&gt; blob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; clipboardItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&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; blob&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;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;And it&#39;s almost needless to say by now. I only do this on supporting browsers.&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;&lt;span class=&quot;token string&quot;&gt;&#39;clipboard&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;write&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clipboard&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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./clipboard.mjs&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;So how does this work in practice? I have an image open in the macOS Preview app and
copy it to the clipboard.
When I click &lt;strong&gt;Paste&lt;/strong&gt;, the Fugu Greetings app then asks me
whether I want to allow the app to see text and images on the clipboard.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Fugu Greetings app showing the clipboard permission prompt.&quot; decoding=&quot;async&quot; height=&quot;543&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EO9BemEDnDtO3SvLl8u5.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The clipboard permission prompt.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Finally, after accepting the permission, the image is then pasted into the application.
The other way round works, too.
Let me copy a greeting card to the clipboard.
When I then open Preview and click &lt;strong&gt;File&lt;/strong&gt; and then &lt;strong&gt;New from Clipboard&lt;/strong&gt;,
the greeting card gets pasted into a new untitled image.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The macOS Preview app with an untitled, just pasted image.&quot; decoding=&quot;async&quot; height=&quot;464&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/VAkvEYWsQsJ0VJ8IjEs1.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    An image pasted into the macOS Preview app.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-badging-api&quot;&gt;The Badging API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#the-badging-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another useful API is the &lt;a href=&quot;https://web.dev/badging-api/&quot;&gt;Badging API&lt;/a&gt;.
As an installable PWA, Fugu Greetings of course does have an app icon
that users can place on the app dock or the home screen.
A fun and easy way to demonstrate the API is to (ab)use it in Fugu Greetings
as a pen strokes counter.
I have added an event listener that increments the pen strokes counter whenever the &lt;code&gt;pointerdown&lt;/code&gt; event occurs
and then sets the updated icon badge.
Whenever the canvas gets cleared, the counter resets, and the badge is removed.&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;let&lt;/span&gt; strokes &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;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;pointerdown&#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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAppBadge&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;strokes&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;br /&gt;clearButton&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 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;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  strokes &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;  navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAppBadge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strokes&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;This feature is a progressive enhancement, so the loading logic is as usual.&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;&lt;span class=&quot;token string&quot;&gt;&#39;setAppBadge&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./badge.mjs&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In this example, I have drawn the numbers from one to seven, using one pen stroke
per number.
The badge counter on the icon is now at seven.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The numbers from one to seven drawn onto the greeting card, each with just one pen stroke.&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uKurKxYeRlLCLXJYhX9I.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Drawing the numbers from 1 to 7, using seven pen strokes.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Badge icon on the Fugu Greetings app showing the number 7.&quot; decoding=&quot;async&quot; height=&quot;448&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 742px) 742px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5uOcN2MdjKVnWXRyTmCG.png?auto=format&amp;w=1484 1484w&quot; width=&quot;742&quot; /&gt;
  &lt;figcaption&gt;
    The pen strokes counter in the form of the app icon badge.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-periodic-background-sync-api&quot;&gt;The Periodic Background Sync API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#the-periodic-background-sync-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Want to start each day fresh with something new?
A neat feature of the Fugu Greetings app is that it can inspire you each morning
with a new background image to start your greeting card.
The app uses the &lt;a href=&quot;https://web.dev/periodic-background-sync/&quot;&gt;Periodic Background Sync API&lt;/a&gt;
to achieve this.&lt;/p&gt;
&lt;p&gt;The first step is to &lt;em&gt;register&lt;/em&gt; a periodic sync event in the service worker registration.
It listens for a sync tag called &lt;code&gt;&#39;image-of-the-day&#39;&lt;/code&gt;
and has a minimum interval of one day,
so the user can get a new background image every 24 hours.&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;registerPeriodicBackgroundSync&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;const&lt;/span&gt; registration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ready&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;    registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;periodicSync&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;image-of-the-day-sync&#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;// An interval of one day.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;minInterval&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;24&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60&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;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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&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;The second step is to &lt;em&gt;listen&lt;/em&gt; for the &lt;code&gt;periodicsync&lt;/code&gt; event in the service worker.
If the event tag is &lt;code&gt;&#39;image-of-the-day&#39;&lt;/code&gt;, that is, the one that was registered before,
the image of the day is retrieved via the &lt;code&gt;getImageOfTheDay()&lt;/code&gt; function,
and the result propagated to all clients, so they can update their canvases and
caches.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;self&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;periodicsync&#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 parameter&quot;&gt;syncEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;syncEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image-of-the-day-sync&#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;    syncEvent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;waitUntil&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;async&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;=&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;const&lt;/span&gt; blob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getImageOfTheDay&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;const&lt;/span&gt; clients &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clients&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matchAll&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;        clients&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&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 parameter&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postMessage&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;image&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; blob&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;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;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;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;Again this is truly a progressive enhancement, so the code is only loaded when the
API is supported by the browser.
This applies to both the client code and the service worker code.
On non-supporting browsers, neither of them is loaded.
Note how in the service worker, instead of a dynamic &lt;code&gt;import()&lt;/code&gt;
(that isn&#39;t supported in a service worker context
&lt;a href=&quot;https://github.com/w3c/ServiceWorker/issues/1356#issuecomment-433411852&quot; rel=&quot;noopener&quot;&gt;yet&lt;/a&gt;),
I use the classic
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/WorkerGlobalScope/importScripts&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;importScripts()&lt;/code&gt;&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;// In the client:&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; registration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ready&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;registration &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;periodicSync&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; registration&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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./periodic_background_sync.mjs&#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;/code&gt;&lt;/pre&gt;
&lt;/div&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;// In the service worker:&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 string&quot;&gt;&#39;periodicSync&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;registration&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;importScripts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./image_of_the_day.mjs&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In Fugu Greetings, pressing the &lt;strong&gt;Wallpaper&lt;/strong&gt; button reveals the greeting card image of the day
that is updated every day via the Periodic Background Sync API.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Fugu Greetings app with a new greeting card image of the day.&quot; decoding=&quot;async&quot; height=&quot;481&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/YdSHSI4pZcTPyVv8CVx8.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Pressing the &lt;strong&gt;Wallpaper&lt;/strong&gt; button displays the image of the day.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;notification-triggers-api&quot;&gt;Notification Triggers API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#notification-triggers-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Sometimes even with a lot of inspiration, you need a nudge to finish a started greeting
card.
This is a feature that is enabled by the &lt;a href=&quot;https://web.dev/notification-triggers/&quot;&gt;Notification Triggers API&lt;/a&gt;.
As a user, I can enter a time when I want to be nudged to finish my greeting card.
When that time comes, I will get a notification that my greeting card is waiting.&lt;/p&gt;
&lt;p&gt;After prompting for the target time,
the application schedules the notification with a &lt;code&gt;showTrigger&lt;/code&gt;.
This can be a &lt;code&gt;TimestampTrigger&lt;/code&gt; with the previously selected target date.
The reminder notification will be triggered locally, no network or server side is needed.&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;const&lt;/span&gt; targetDate &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;promptTargetDate&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;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;targetDate&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;const&lt;/span&gt; registration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serviceWorker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ready&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  registration&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showNotification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Reminder&#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;tag&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;reminder&#39;&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;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;It&#39;s time to finish your greeting card!&quot;&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;showTrigger&lt;/span&gt;&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;TimestampTrigger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;targetDate&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As with everything else I have shown so far, this is a progressive enhancement,
so the code is only conditionally loaded.&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;&lt;span class=&quot;token string&quot;&gt;&#39;Notification&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;showTrigger&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Notification&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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./notification_triggers.mjs&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;When I check the &lt;strong&gt;Reminder&lt;/strong&gt; checkbox in Fugu Greetings, a prompt asks
me when I want to be reminded to finish my greeting card.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Fugu Greetings app with a prompt asking the user when they want to be reminded to finish their greeting card.&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xtD7PtIIBO0Yn1ISFSyz.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Scheduling a local notification to be reminded to finish a greeting card.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When a scheduled notification triggers in Fugu Greetings,
it is shown just like any other notification, but as I wrote before,
it didn&#39;t require a network connection.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;macOS Notification Center showing a triggered notification from Fugu Greetings.&quot; decoding=&quot;async&quot; height=&quot;172&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 300px) 300px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e1FJA11UE3lrL1d4mCCo.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e1FJA11UE3lrL1d4mCCo.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e1FJA11UE3lrL1d4mCCo.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e1FJA11UE3lrL1d4mCCo.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e1FJA11UE3lrL1d4mCCo.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e1FJA11UE3lrL1d4mCCo.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e1FJA11UE3lrL1d4mCCo.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e1FJA11UE3lrL1d4mCCo.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e1FJA11UE3lrL1d4mCCo.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e1FJA11UE3lrL1d4mCCo.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e1FJA11UE3lrL1d4mCCo.png?auto=format&amp;w=600 600w&quot; width=&quot;300&quot; /&gt;
  &lt;figcaption&gt;
    The triggered notification appears in the macOS Notification Center.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-wake-lock-api&quot;&gt;The Wake Lock API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#the-wake-lock-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I also want to include the &lt;a href=&quot;https://web.dev/wakelock/&quot;&gt;Wake Lock API&lt;/a&gt;.
Sometimes you just need to stare long enough at the screen until inspiration
kisses you.
The worst that can happen then is for the screen to turn off.
The Wake Lock API can prevent this from happening.&lt;/p&gt;
&lt;p&gt;The first step is to obtain a wake lock with the &lt;code&gt;navigator.wakelock.request method()&lt;/code&gt;.
I pass it the string &lt;code&gt;&#39;screen&#39;&lt;/code&gt; to obtain a screen wake lock.
I then add an event listener to be informed when the wake lock is released.
This can happen, for example, when the tab visibility changes.
If this happens, I can, when the tab becomes visible again, re-obtain the wake lock.&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;let&lt;/span&gt; wakeLock &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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;requestWakeLock&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  wakeLock &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wakeLock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;screen&#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;  wakeLock&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;release&#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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;Wake Lock was released&#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;  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;Wake Lock is active&#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 keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;handleVisibilityChange&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;span class=&quot;token operator&quot;&gt;=&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;wakeLock &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 operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visibilityState &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;visible&#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;requestWakeLock&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;br /&gt;&lt;br /&gt;document&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;visibilitychange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handleVisibilityChange&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;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fullscreenchange&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; handleVisibilityChange&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;Yes, this is a progressive enhancement, so I only need to load it when the browser
supports the API.&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;&lt;span class=&quot;token string&quot;&gt;&#39;wakeLock&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;request&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wakeLock&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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./wake_lock.mjs&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In Fugu Greetings, there&#39;s an &lt;strong&gt;Insomnia&lt;/strong&gt; checkbox that, when checked, keeps the
screen awake.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The insomnia checkbox, if checked, keeps the screen awake.&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kB5LkdV3cVKaJ0Xze76v.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The &lt;strong&gt;Insomnia&lt;/strong&gt; checkbox keeps app awake.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-idle-detection-api&quot;&gt;The Idle Detection API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#the-idle-detection-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At times, even if you stare at the screen for hours,
it&#39;s just useless and you can&#39;t come up with the slightest idea what to do with your greeting card.
The &lt;a href=&quot;https://web.dev/idle-detection/&quot;&gt;Idle Detection API&lt;/a&gt; allows the app to detect user idle time.
If the user is idle for too long, the app resets to the initial state
and clears the canvas.
This API is currently gated behind the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Notification/requestPermission&quot; rel=&quot;noopener&quot;&gt;notifications permission&lt;/a&gt;,
since a lot of production use cases of idle detection are notifications-related,
for example, to only send a notification to a device the user is currently actively using.&lt;/p&gt;
&lt;p&gt;After making sure that the notifications permission is granted, I then instantiate the
idle detector.
I register an event listener that listens for idle changes, which includes the user and
the screen state.
The user can be active or idle,
and the screen can be unlocked or locked.
If the user is idle, the canvas clears.
I give the idle detector a threshold of 60 seconds.&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;const&lt;/span&gt; idleDetector &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;IdleDetector&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;idleDetector&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;change&#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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; userState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; idleDetector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userState&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; screenState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; idleDetector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;screenState&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 template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Idle change: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;userState&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;screenState&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;userState &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;idle&#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;clearCanvas&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; idleDetector&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&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;threshold&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  signal&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;And as always, I only load this code when the browser supports it.&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;&lt;span class=&quot;token string&quot;&gt;&#39;IdleDetector&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; window&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;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./idle_detection.mjs&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In the Fugu Greetings app, the canvas clears when the &lt;strong&gt;Ephemeral&lt;/strong&gt; checkbox is
checked and the user is idle for for too long.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Fugu Greetings app with a cleared canvas after the user has been idle for too long.&quot; decoding=&quot;async&quot; height=&quot;480&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yq3IsOqp01AZTw7nvfnB.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    When the &lt;strong&gt;Ephemeral&lt;/strong&gt; checkbox is checked and the user has been idle for too long, the canvas is cleared.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;closing&quot;&gt;Closing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#closing&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Phew, what a ride. So many APIs in just one sample app.
And, remember, I never make the user pay the download cost
for a feature that their browser doesn&#39;t support.
By using progressive enhancement, I make sure only the relevant code gets loaded.
And since with HTTP/2, requests are cheap, this pattern should work well for a lot of
applications,
although you might want to consider a bundler for really large apps.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Chrome DevTools Network panel showing only requests for files with code that the current browser supports.&quot; decoding=&quot;async&quot; height=&quot;566&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/DOnuk7CHPsnbTdlqOHXM.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Chrome DevTools Network tab showing only requests for files with code that the current browser supports.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The app may look a little different on each browser since not all platforms support all features,
but the core functionality is always there—progressively enhanced according to the particular browser&#39;s capabilities.
Note that these capabilities may change even in one and the same browser,
depending on whether the app is running as an installed app or in a browser tab.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Fugu Greetings running on Android Chrome, showing many available features.&quot; decoding=&quot;async&quot; height=&quot;243&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 500px) 500px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/LmUW0CZpH5eXIoHTZ6kH.png?auto=format&amp;w=1000 1000w&quot; width=&quot;500&quot; /&gt;
  &lt;figcaption&gt;
    &lt;a href=&quot;https://github.com/tomayac/fugu-greetings&quot;&gt;Fugu Greetings&lt;/a&gt; running on Android Chrome.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Fugu Greetings running on desktop Safari, showing fewer available features.&quot; decoding=&quot;async&quot; height=&quot;403&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 500px) 500px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/BOcbAW4FCi10d9cGdeNW.png?auto=format&amp;w=1000 1000w&quot; width=&quot;500&quot; /&gt;
  &lt;figcaption&gt;
    &lt;a href=&quot;https://github.com/tomayac/fugu-greetings&quot;&gt;Fugu Greetings&lt;/a&gt; running on desktop Safari.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Fugu Greetings running on desktop Chrome, showing many available features.&quot; decoding=&quot;async&quot; height=&quot;348&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 500px) 500px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/7zT4BEUzxTkjg8e08OJU.png?auto=format&amp;w=1000 1000w&quot; width=&quot;500&quot; /&gt;
  &lt;figcaption&gt;
    &lt;a href=&quot;https://github.com/tomayac/fugu-greetings&quot;&gt;Fugu Greetings&lt;/a&gt; running on desktop Chrome.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If you&#39;re interested in the &lt;a href=&quot;https://tomayac.github.io/fugu-greetings/public/&quot; rel=&quot;noopener&quot;&gt;Fugu Greetings&lt;/a&gt; app,
go find and &lt;a href=&quot;https://github.com/tomayac/fugu-greetings&quot; rel=&quot;noopener&quot;&gt;fork it on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Fugu Greetings repo on GitHub.&quot; decoding=&quot;async&quot; height=&quot;490&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l260mtBzRi8OxdV8gXSg.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    &lt;a href=&quot;https://github.com/tomayac/fugu-greetings&quot;&gt;Fugu Greetings&lt;/a&gt; app on GitHub.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The Chromium team is working hard on making the grass greener when it comes to advanced Fugu APIs.
By applying progressive enhancement in the development of my app,
I make sure that everybody gets a good, solid baseline experience,
but that people using browsers that support more Web platform APIs get an even better experience.
I&#39;m looking forward to seeing what you do with progressive enhancement in your apps.&lt;/p&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/progressively-enhance-your-pwa/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;m grateful to &lt;a href=&quot;https://christianliebel.com/&quot; rel=&quot;noopener&quot;&gt;Christian Liebel&lt;/a&gt; and
&lt;a href=&quot;https://h3manth.com/&quot; rel=&quot;noopener&quot;&gt;Hemanth HM&lt;/a&gt; who both have contributed to Fugu Greetings.
This article was reviewed by &lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt; and
&lt;a href=&quot;https://github.com/kaycebasques&quot; rel=&quot;noopener&quot;&gt;Kayce Basques&lt;/a&gt;.
&lt;a href=&quot;https://github.com/jakearchibald/&quot; rel=&quot;noopener&quot;&gt;Jake Archibald&lt;/a&gt; helped me find out the situation
with dynamic &lt;code&gt;import()&lt;/code&gt; in a service worker context.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Boldly link where no one has linked before: Text Fragments
</title>
    <link href="https://web.dev/text-fragments/"/>
    <updated>2020-06-17T00:00:00Z</updated>
    <id>https://web.dev/text-fragments/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;fragment-identifiers&quot;&gt;Fragment Identifiers &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#fragment-identifiers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Chrome 80 was a big release. It contained a number of highly anticipated features like
&lt;a href=&quot;https://web.dev/module-workers/&quot;&gt;ECMAScript Modules in Web Workers&lt;/a&gt;,
&lt;a href=&quot;https://v8.dev/features/nullish-coalescing&quot; rel=&quot;noopener&quot;&gt;nullish coalescing&lt;/a&gt;,
&lt;a href=&quot;https://v8.dev/features/optional-chaining&quot; rel=&quot;noopener&quot;&gt;optional chaining&lt;/a&gt;, and more. The release was, as usual,
announced through a
&lt;a href=&quot;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html&quot; rel=&quot;noopener&quot;&gt;blog post&lt;/a&gt; on the
Chromium blog. You can see an excerpt of the blog post in the screenshot below.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;628&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/egsW6tkKWYI8IHE6JyMZ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/egsW6tkKWYI8IHE6JyMZ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/egsW6tkKWYI8IHE6JyMZ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/egsW6tkKWYI8IHE6JyMZ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/egsW6tkKWYI8IHE6JyMZ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/egsW6tkKWYI8IHE6JyMZ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/egsW6tkKWYI8IHE6JyMZ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/egsW6tkKWYI8IHE6JyMZ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/egsW6tkKWYI8IHE6JyMZ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/egsW6tkKWYI8IHE6JyMZ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/egsW6tkKWYI8IHE6JyMZ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/egsW6tkKWYI8IHE6JyMZ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/egsW6tkKWYI8IHE6JyMZ.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
  &lt;figcaption&gt;Chromium blog post with red boxes around elements with an &lt;code&gt;id&lt;/code&gt; attribute.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You are probably asking yourself what all the red boxes mean. They are the result of running the
following snippet in DevTools. It highlights all elements that have an &lt;code&gt;id&lt;/code&gt; attribute.&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;&lt;span class=&quot;token function&quot;&gt;querySelectorAll&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;span class=&quot;token function&quot;&gt;forEach&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 parameter&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;border &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;solid 2px red&#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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;I can place a deep link to any element highlighted with a red box thanks to the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Identifying_resources_on_the_Web#Fragment&quot; rel=&quot;noopener&quot;&gt;fragment identifier&lt;/a&gt;
which I then use in the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/URL/hash&quot; rel=&quot;noopener&quot;&gt;hash&lt;/a&gt; of the
page&#39;s URL. Assuming I wanted to deep link to the &lt;em&gt;Give us feedback in our
&lt;a href=&quot;http://support.google.com/bin/static.py?hl=en&amp;amp;page=portal_groups.cs&quot; rel=&quot;noopener&quot;&gt;Product Forums&lt;/a&gt;&lt;/em&gt; box in the
aside, I could do so by handcrafting the URL
&lt;a href=&quot;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1&quot;&gt;&lt;code&gt;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;#HTML1&lt;/mark&gt;&lt;/code&gt;&lt;/a&gt;.
As you can see in the Elements panel of the Developer Tools, the element in question has an &lt;code&gt;id&lt;/code&gt;
attribute with the value &lt;code&gt;HTML1&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;97&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 600px) 600px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/whVXhhrYwA55S3i4J3l5.png?auto=format&amp;w=1200 1200w&quot; width=&quot;600&quot; /&gt;
  &lt;figcaption&gt;Dev Tools showing the &lt;code&gt;id&lt;/code&gt; of an element.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If I parse this URL with JavaScript&#39;s &lt;code&gt;URL()&lt;/code&gt; constructor, the different components are revealed.
Notice the &lt;code&gt;hash&lt;/code&gt; property with the value &lt;code&gt;#HTML1&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;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1&#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&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Creates a new `URL` object&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;URL {&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  hash: &quot;#HTML1&quot;&lt;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  host: &quot;blog.chromium.org&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  hostname: &quot;blog.chromium.org&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  href: &quot;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  origin: &quot;https://blog.chromium.org&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  password: &quot;&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  pathname: &quot;/2019/12/chrome-80-content-indexing-es-modules.html&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  port: &quot;&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  protocol: &quot;https:&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  search: &quot;&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  searchParams: URLSearchParams {}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  username: &quot;&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;}&lt;/span&gt;&lt;br /&gt;*/&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;The fact though that I had to open the Developer Tools to find the &lt;code&gt;id&lt;/code&gt; of an element speaks volumes
about the probability this particular section of the page was meant to be linked to by the author of
the blog post.&lt;/p&gt;
&lt;p&gt;What if I want to link to something without an &lt;code&gt;id&lt;/code&gt;? Say I want to link to the &lt;em&gt;ECMAScript Modules
in Web Workers&lt;/em&gt; heading. As you can see in the screenshot below, the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; in question does not
have an &lt;code&gt;id&lt;/code&gt; attribute, meaning there is no way I can link to this heading. This is the problem that
Text Fragments solve.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;71&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 600px) 600px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/1g4rTS1q5LKHEHnDoF9o.png?auto=format&amp;w=1200 1200w&quot; width=&quot;600&quot; /&gt;
  &lt;figcaption&gt;Dev Tools showing a heading without an &lt;code&gt;id&lt;/code&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;text-fragments&quot;&gt;Text Fragments &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#text-fragments&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://wicg.github.io/ScrollToTextFragment/&quot; rel=&quot;noopener&quot;&gt;Text Fragments&lt;/a&gt; proposal adds support for
specifying a text snippet in the URL hash. When navigating to a URL with such a text fragment, the
user agent can emphasize and/or bring it to the user&#39;s attention.&lt;/p&gt;
&lt;h3 id=&quot;browser-compatibility&quot;&gt;Browser compatibility &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#browser-compatibility&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 89, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      89
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 89, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
89
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/CSS/::target-text#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;aside class=&quot;aside flow bg-state-good-bg color-state-good-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; height=&quot;24&quot; width=&quot;24&quot; fill=&quot;currentColor&quot; role=&quot;img&quot; aria-label=&quot;Check&quot;&gt;   &lt;path d=&quot;M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Success&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; These links used to not work when served across &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Redirections#Alternative_way_of_specifying_redirections&quot;&gt;client-side redirects&lt;/a&gt; that some common services like Twitter use. This issue was tracked as &lt;a href=&quot;https://crbug.com/1055455&quot;&gt;crbug.com/1055455&lt;/a&gt; and is now fixed. Regular &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Redirections#Principle&quot;&gt;HTTP redirects&lt;/a&gt; always worked fine. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;For &lt;a href=&quot;https://web.dev/text-fragments/#security&quot;&gt;security&lt;/a&gt; reasons, the feature requires links to be opened in a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Link_types/noopener&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;noopener&lt;/code&gt;&lt;/a&gt; context.
Therefore, make sure to include
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/a#attr-rel&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;rel=&amp;quot;noopener&amp;quot;&lt;/code&gt;&lt;/a&gt; in your
&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; anchor markup or add
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Window/open#noopener&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;noopener&lt;/code&gt;&lt;/a&gt; to your
&lt;code&gt;Window.open()&lt;/code&gt; list of window functionality features.&lt;/p&gt;
&lt;h3 id=&quot;start&quot;&gt;&lt;code&gt;start&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#start&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In its simplest form, the syntax of Text Fragments is as follows: The hash symbol &lt;code&gt;#&lt;/code&gt; followed by
&lt;code&gt;:~:text=&lt;/code&gt; and finally &lt;code&gt;start&lt;/code&gt;, which represents the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent&quot; rel=&quot;noopener&quot;&gt;percent-encoded&lt;/a&gt;
text I want to link to.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#:~:text=start&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;For example, say that I want to link to the &lt;em&gt;ECMAScript Modules in Web Workers&lt;/em&gt; heading in the
&lt;a href=&quot;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html&quot; rel=&quot;noopener&quot;&gt;blog post announcing features in Chrome 80&lt;/a&gt;,
the URL in this case would be:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript%20Modules%20in%20Web%20Workers&quot;&gt;&lt;code&gt;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;#:~:text=ECMAScript%20Modules%20in%20Web%20Workers&lt;/mark&gt;&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The text fragment is emphasized &lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;like this&lt;/mark&gt;.
If you click the link in a supporting browser like Chrome, the text fragment is highlighted and
scrolls into view:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;208&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D3jwPrJlvN3FmJo3pADt.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D3jwPrJlvN3FmJo3pADt.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D3jwPrJlvN3FmJo3pADt.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D3jwPrJlvN3FmJo3pADt.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D3jwPrJlvN3FmJo3pADt.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D3jwPrJlvN3FmJo3pADt.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D3jwPrJlvN3FmJo3pADt.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D3jwPrJlvN3FmJo3pADt.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D3jwPrJlvN3FmJo3pADt.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D3jwPrJlvN3FmJo3pADt.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D3jwPrJlvN3FmJo3pADt.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D3jwPrJlvN3FmJo3pADt.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/D3jwPrJlvN3FmJo3pADt.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
  &lt;figcaption&gt;Text fragment scrolled into view and highlighted.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;start-and-end&quot;&gt;&lt;code&gt;start&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#start-and-end&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now what if I want to link to the entire &lt;em&gt;section&lt;/em&gt; titled &lt;em&gt;ECMAScript Modules in Web Workers&lt;/em&gt;, not
just its heading? Percent-encoding the entire text of the section would make the resulting URL
impracticably long.&lt;/p&gt;
&lt;p&gt;Luckily there is a better way. Rather than the entire text, I can frame the desired text using the
&lt;code&gt;start,end&lt;/code&gt; syntax. Therefore, I specify a couple of percent-encoded words at the beginning
of the desired text, and a couple of percent-encoded words at the end of the desired text, separated
by a comma &lt;code&gt;,&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That looks like this:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript%20Modules%20in%20Web%20Workers,ES%20Modules%20in%20Web%20Workers.&quot;&gt;&lt;code&gt;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;#:~:text=ECMAScript%20Modules%20in%20Web%20Workers,ES%20Modules%20in%20Web%20Workers.&lt;/mark&gt;&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;start&lt;/code&gt;, I have &lt;code&gt;ECMAScript%20Modules%20in%20Web%20Workers&lt;/code&gt;, then a comma &lt;code&gt;,&lt;/code&gt; followed
by &lt;code&gt;ES%20Modules%20in%20Web%20Workers.&lt;/code&gt; as &lt;code&gt;end&lt;/code&gt;. When you click through on a supporting browser
like Chrome, the whole section is highlighted and scrolled into view:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;343&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2yTYmKnjHTnqXkcmHF1F.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2yTYmKnjHTnqXkcmHF1F.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2yTYmKnjHTnqXkcmHF1F.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2yTYmKnjHTnqXkcmHF1F.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2yTYmKnjHTnqXkcmHF1F.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2yTYmKnjHTnqXkcmHF1F.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2yTYmKnjHTnqXkcmHF1F.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2yTYmKnjHTnqXkcmHF1F.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2yTYmKnjHTnqXkcmHF1F.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2yTYmKnjHTnqXkcmHF1F.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2yTYmKnjHTnqXkcmHF1F.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2yTYmKnjHTnqXkcmHF1F.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/2yTYmKnjHTnqXkcmHF1F.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
  &lt;figcaption&gt;Text fragment scrolled into view and highlighted.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Now you may wonder about my choice of &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt;. Actually, the slightly shorter URL
&lt;a href=&quot;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript%20Modules,Web%20Workers.&quot;&gt;&lt;code&gt;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;#:~:text=ECMAScript%20Modules,Web%20Workers.&lt;/mark&gt;&lt;/code&gt;&lt;/a&gt;
with only two words on each side would have worked, too. Compare &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt; with the
previous values.&lt;/p&gt;
&lt;p&gt;If I take it one step further and now use only one word for both &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt;, you can
see that I am in trouble. The URL
&lt;a href=&quot;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript,Workers.&quot;&gt;&lt;code&gt;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;#:~:text=ECMAScript,Workers.&lt;/mark&gt;&lt;/code&gt;&lt;/a&gt;
is even shorter now, but the highlighted text fragment is no longer the originally desired one. The
highlighting stops at the first occurrence of the word &lt;code&gt;Workers.&lt;/code&gt;, which is correct, but not what I
intended to highlight. The problem is that the desired section is not uniquely identified by the
current one-word &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt; values:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;342&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GGbbtHBpsoFyubnISyZw.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GGbbtHBpsoFyubnISyZw.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GGbbtHBpsoFyubnISyZw.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GGbbtHBpsoFyubnISyZw.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GGbbtHBpsoFyubnISyZw.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GGbbtHBpsoFyubnISyZw.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GGbbtHBpsoFyubnISyZw.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GGbbtHBpsoFyubnISyZw.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GGbbtHBpsoFyubnISyZw.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GGbbtHBpsoFyubnISyZw.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GGbbtHBpsoFyubnISyZw.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GGbbtHBpsoFyubnISyZw.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GGbbtHBpsoFyubnISyZw.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
  &lt;figcaption&gt;Non-intended text fragment scrolled into view and highlighted.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;prefix-and-suffix&quot;&gt;&lt;code&gt;prefix-&lt;/code&gt; and &lt;code&gt;-suffix&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#prefix-and-suffix&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Using long enough values for &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt; is one solution for obtaining a unique link.
In some situations, however, this is not possible. On a side note, why did I choose the
Chrome 80 release blog post as my example? The answer is that in this release Text Fragments
were introduced:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Blog post text: Text URL Fragments. Users or authors can now link to a specific portion of a page using a text fragment provided in a URL. When the page is loaded, the browser highlights the text and scrolls the fragment into view. For example, the URL below loads a wiki page for &amp;#x27;Cat&amp;#x27; and scrolls to the content listed in the &amp;#x60;text&amp;#x60; parameter.&quot; decoding=&quot;async&quot; height=&quot;200&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/yA1p3CijeDbTRwMys9Hq.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Text Fragments announcement blog post excerpt.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Notice how in the screenshot above the word &amp;quot;text&amp;quot; appears four times. The forth occurrence is
written in a green code font. If I wanted to link to this particular word, I would set &lt;code&gt;start&lt;/code&gt;
to &lt;code&gt;text&lt;/code&gt;. Since the word &amp;quot;text&amp;quot; is, well, only one word, there cannot be a &lt;code&gt;end&lt;/code&gt;. What now? The
URL
&lt;a href=&quot;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=text&quot;&gt;&lt;code&gt;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;#:~:text=text&lt;/mark&gt;&lt;/code&gt;&lt;/a&gt;
matches at the first occurrence of the word &amp;quot;Text&amp;quot; already in the heading:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;209&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nXxCskUwdCxwxejPSSZW.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Text Fragment matching at the first occurrence of &quot;Text&quot;.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Note that text fragment matching is case-insensitive. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Luckily there is a solution. In cases like this, I can specify a &lt;code&gt;prefix​-&lt;/code&gt; and a &lt;code&gt;-suffix&lt;/code&gt;. The
word before the green code font &amp;quot;text&amp;quot; is &amp;quot;the&amp;quot;, and the word after is &amp;quot;parameter&amp;quot;. None of the
other three occurrences of the word &amp;quot;text&amp;quot; has the same surrounding words. Armed with this
knowledge, I can tweak the previous URL and add the &lt;code&gt;prefix-&lt;/code&gt; and the &lt;code&gt;-suffix&lt;/code&gt;. Like the other
parameters, they, too, need to be percent-encoded and can contain more than one word.
&lt;a href=&quot;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=the-,text,-parameter&quot;&gt;&lt;code&gt;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;#:~:text=the-,text,-parameter&lt;/mark&gt;&lt;/code&gt;&lt;/a&gt;.
To allow the parser to clearly identify the &lt;code&gt;prefix-&lt;/code&gt; and the &lt;code&gt;-suffix&lt;/code&gt;, they need to be separated
from the &lt;code&gt;start&lt;/code&gt; and the optional &lt;code&gt;end&lt;/code&gt; with a dash &lt;code&gt;-&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;203&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/J3L5BVSMmzGY6xdkabP6.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Text Fragment matching at the desired occurrence of &quot;text&quot;.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;the-full-syntax&quot;&gt;The full syntax &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#the-full-syntax&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The full syntax of Text Fragments is shown below. (Square brackets indicate an optional parameter.)
The values for all parameters need to be percent-encoded. This is especially important for the dash
&lt;code&gt;-&lt;/code&gt;, ampersand &lt;code&gt;&amp;amp;&lt;/code&gt;, and comma &lt;code&gt;,&lt;/code&gt; characters, so they are not being interpreted as part of the text
directive syntax.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#:~:text=[prefix-,]start[,end][,-suffix]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Each of &lt;code&gt;prefix-&lt;/code&gt;, &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;end&lt;/code&gt;, and &lt;code&gt;-suffix&lt;/code&gt; will only match text within a single
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Block-level_elements#Elements&quot; rel=&quot;noopener&quot;&gt;block-level element&lt;/a&gt;,
but full &lt;code&gt;start,end&lt;/code&gt; ranges &lt;em&gt;can&lt;/em&gt; span multiple blocks. For example,
&lt;code&gt;:~:text=The quick,lazy dog&lt;/code&gt; will fail to match in the following example, because the starting
string &amp;quot;The quick&amp;quot; does not appear within a single, uninterrupted block-level element:&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 punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  The&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;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;br /&gt;  quick brown fox&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;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;jumped over the lazy dog&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;It does, however, match in this 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;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;The quick brown fox&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;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;jumped over the lazy dog&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;h3 id=&quot;creating-text-fragment-urls-with-a-browser-extension&quot;&gt;Creating Text Fragment URLs with a browser extension &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#creating-text-fragment-urls-with-a-browser-extension&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Creating Text Fragments URLs by hand is tedious, especially when it comes to making sure they are
unique. If you really want to, the specification has some tips and lists the exact
&lt;a href=&quot;https://wicg.github.io/ScrollToTextFragment/#generating-text-fragment-directives&quot; rel=&quot;noopener&quot;&gt;steps for generating Text Fragment URLs&lt;/a&gt;.
We provide an open-source browser extension called
&lt;a href=&quot;https://github.com/GoogleChromeLabs/link-to-text-fragment&quot; rel=&quot;noopener&quot;&gt;Link to Text Fragment&lt;/a&gt; that lets you
link to any text by selecting it, and then clicking &amp;quot;Copy Link to Selected Text&amp;quot; in the context
menu. This extension is available for the following browsers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/link-to-text-fragment/pbcodcjpfjdpcineamnnmbkkmkdpajjg&quot; rel=&quot;noopener&quot;&gt;Link to Text Fragment for Google Chrome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://microsoftedge.microsoft.com/addons/detail/link-to-text-fragment/pmdldpbcbobaamgkpkghjigngamlolag&quot; rel=&quot;noopener&quot;&gt;Link to Text Fragment for Microsoft Edge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://addons.mozilla.org/firefox/addon/link-to-text-fragment/&quot; rel=&quot;noopener&quot;&gt;Link to Text Fragment for Mozilla Firefox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apps.apple.com/app/link-to-text-fragment/id1532224396&quot; rel=&quot;noopener&quot;&gt;Link to Text Fragment for Apple Safari&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
  &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;500&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ASLtFCPoHvyTKrAtKAv4.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    &lt;a href=&quot;https://github.com/GoogleChromeLabs/link-to-text-fragment&quot;&gt;
      Link to Text Fragment
    &lt;/a&gt;
    browser extension.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;multiple-text-fragments-in-one-url&quot;&gt;Multiple text fragments in one URL &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#multiple-text-fragments-in-one-url&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Note that multiple text fragments can appear in one URL. The particular text fragments need to be
separated by an ampersand character &lt;code&gt;&amp;amp;&lt;/code&gt;. Here is an example link with three text fragments:
&lt;a href=&quot;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=Text%20URL%20Fragments&amp;text=text,-parameter&amp;text=:~:text=On%20islands,%20birds%20can%20contribute%20as%20much%20as%2060%25%20of%20a%20cat&#39;s%20diet&quot;&gt;&lt;code&gt;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;text=Text%20URL%20Fragments&lt;/mark&gt;&amp;amp;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;text=text,-parameter&lt;/mark&gt;&amp;amp;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;text=:~:text=On%20islands,%20birds%20can%20contribute%20as%20much%20as%2060%25%20of%20a%20cat&#39;s%20diet&lt;/mark&gt;&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;324&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ffsq7aoSoVd9q6r5cquY.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Three text fragments in one URL.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;mixing-element-and-text-fragments&quot;&gt;Mixing element and text fragments &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#mixing-element-and-text-fragments&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Traditional element fragments can be combined with text fragments. It is perfectly fine to have both
in the same URL, for example, to provide a meaningful fallback in case the original text on the page
changes, so that the text fragment does not match anymore. The URL
&lt;a href=&quot;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1:~:text=Give%20us%20feedback%20in%20our%20Product%20Forums.&quot;&gt;&lt;code&gt;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;#HTML1:~:text=Give%20us%20feedback%20in%20our%20Product%20Forums.&lt;/mark&gt;&lt;/code&gt;&lt;/a&gt;
linking to the &lt;em&gt;Give us feedback in our
&lt;a href=&quot;http://support.google.com/bin/static.py?hl=en&amp;amp;page=portal_groups.cs&quot; rel=&quot;noopener&quot;&gt;Product Forums&lt;/a&gt;&lt;/em&gt; section
contains both an element fragment (&lt;code&gt;HTML1&lt;/code&gt;), as well as a text fragment
(&lt;code&gt;text=Give%20us%20feedback%20in%20our%20Product%20Forums.&lt;/code&gt;):&lt;/p&gt;
&lt;figure&gt;
   &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;121&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 237px) 237px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/JRKCM6Ihrq8sgRZRiymr.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/JRKCM6Ihrq8sgRZRiymr.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/JRKCM6Ihrq8sgRZRiymr.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/JRKCM6Ihrq8sgRZRiymr.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/JRKCM6Ihrq8sgRZRiymr.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/JRKCM6Ihrq8sgRZRiymr.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/JRKCM6Ihrq8sgRZRiymr.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/JRKCM6Ihrq8sgRZRiymr.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/JRKCM6Ihrq8sgRZRiymr.png?auto=format&amp;w=474 474w&quot; width=&quot;237&quot; /&gt;
  &lt;figcaption&gt;Linking with both element fragment and text fragment.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;the-fragment-directive&quot;&gt;The fragment directive &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#the-fragment-directive&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There is one element of the syntax I have not explained yet: the fragment directive &lt;code&gt;:~:&lt;/code&gt;. To avoid
compatibility issues with existing URL element fragments as shown above, the
&lt;a href=&quot;https://wicg.github.io/ScrollToTextFragment/&quot; rel=&quot;noopener&quot;&gt;Text Fragments specification&lt;/a&gt; introduces the fragment
directive. The fragment directive is a portion of the URL fragment delimited by the code sequence
&lt;code&gt;:~:&lt;/code&gt;. It is reserved for user agent instructions, such as &lt;code&gt;text=&lt;/code&gt;, and is stripped from the URL
during loading so that author scripts cannot directly interact with it. User agent instructions are
also called &lt;em&gt;directives&lt;/em&gt;. In the concrete case, &lt;code&gt;text=&lt;/code&gt; is therefore called a &lt;em&gt;text directive&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&quot;feature-detection&quot;&gt;Feature detection &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#feature-detection&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To detect support, test for the read-only &lt;code&gt;fragmentDirective&lt;/code&gt; property on &lt;code&gt;document&lt;/code&gt;. The fragment
directive is a mechanism for URLs to specify instructions directed to the browser rather than the
document. It is meant to avoid direct interaction with author script, so that future user agent
instructions can be added without fear of introducing breaking changes to existing content. One
potential example of such future additions could be translation hints.&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;&lt;span class=&quot;token string&quot;&gt;&#39;fragmentDirective&#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;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Text Fragments is supported.&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;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; From Chrome 80 to Chrome 85, the &lt;code&gt;fragmentDirective&lt;/code&gt; property was defined on &lt;code&gt;Location.prototype&lt;/code&gt;. For details on this change, see &lt;a href=&quot;https://github.com/WICG/scroll-to-text-fragment/issues/130&quot;&gt;WICG/scroll-to-text-fragment#130&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Feature detection is mainly intended for cases where links are dynamically generated (for example by
search engines) to avoid serving text fragments links to browsers that do not support them.&lt;/p&gt;
&lt;h3 id=&quot;styling-text-fragments&quot;&gt;Styling text fragments &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#styling-text-fragments&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;By default, browsers style text fragments the same way they style
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/mark&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;mark&lt;/code&gt;&lt;/a&gt; (typically black on yellow,
the CSS &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/color_value#system_colors&quot; rel=&quot;noopener&quot;&gt;system colors&lt;/a&gt;
for &lt;code&gt;mark&lt;/code&gt;). The user-agent stylesheet contains CSS that looks like this:&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;:root::target-text&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; MarkText&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; Mark&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 you can see, the browser exposes a pseudo selector
&lt;a href=&quot;https://drafts.csswg.org/css-pseudo/#selectordef-target-text&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;::target-text&lt;/code&gt;&lt;/a&gt; that you can use to
customize the applied highlighting. For example, you could design your text fragments to be black
text on a red background. As always, be sure to
&lt;a href=&quot;https://developer.chrome.com/docs/devtools/accessibility/reference/#contrast&quot; rel=&quot;noopener&quot;&gt;check the color contrast&lt;/a&gt;
so your override styling does not cause accessibility issues and make sure the highlighting actually
visually stands out from the rest of the content.&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;:root::target-text&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; black&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;background-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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;polyfillability&quot;&gt;Polyfillability &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#polyfillability&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Text Fragments feature can be polyfilled to some extent. We provide a
&lt;a href=&quot;https://github.com/GoogleChromeLabs/text-fragments-polyfill&quot; rel=&quot;noopener&quot;&gt;polyfill&lt;/a&gt;, which is used internally by
the &lt;a href=&quot;https://github.com/GoogleChromeLabs/link-to-text-fragment&quot; rel=&quot;noopener&quot;&gt;extension&lt;/a&gt;, for browsers that do not
provide built-in support for Text Fragments where the functionality is implemented in JavaScript.&lt;/p&gt;
&lt;h3 id=&quot;programmatic-text-fragment-link-generation&quot;&gt;Programmatic Text Fragment link generation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#programmatic-text-fragment-link-generation&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/GoogleChromeLabs/text-fragments-polyfill&quot; rel=&quot;noopener&quot;&gt;polyfill&lt;/a&gt; contains a file
&lt;code&gt;fragment-generation-utils.js&lt;/code&gt; that you can import and use to generate Text Fragment links. This is
outlined in the code sample below:&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;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; generateFragment &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;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://unpkg.com/text-fragments-polyfill/dist/fragment-generation-utils.js&#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;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateFragment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getSelection&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 keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;search&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&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 keyword&quot;&gt;const&lt;/span&gt; fragment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fragment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; prefix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fragment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prefix &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fragment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prefix&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-,&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&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;const&lt;/span&gt; suffix &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fragment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;suffix &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;,-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fragment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;suffix&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&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;const&lt;/span&gt; start &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fragment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textStart&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;const&lt;/span&gt; end &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fragment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textEnd &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fragment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;textEnd&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  url &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;#:~:text=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;prefix&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;start&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;end&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;suffix&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;url&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;obtaining-text-fragments-for-analytics-purposes&quot;&gt;Obtaining Text Fragments for analytics purposes &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#obtaining-text-fragments-for-analytics-purposes&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Plenty of sites use the fragment for routing, which is why browsers strip out Text Fragments
so as to not break those pages. There is an
&lt;a href=&quot;https://github.com/WICG/scroll-to-text-fragment/issues/128&quot; rel=&quot;noopener&quot;&gt;acknowledged need&lt;/a&gt;
to expose Text Fragments links to pages, for example, for analytics purposes,
but the proposed solution is not implemented yet.
As a workaround for now, you can use the code below to extract
the desired information.&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;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;performance&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getEntries&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;find&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 parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type &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;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;navigate&#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;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hash&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;security&quot;&gt;Security &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#security&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Text fragment directives are invoked only on full (non-same-page) navigations that are the result of
a
&lt;a href=&quot;https://html.spec.whatwg.org/multipage/interaction.html#tracking-user-activation&quot; rel=&quot;noopener&quot;&gt;user activation&lt;/a&gt;.
Additionally, navigations originating from a different origin than the destination will require the
navigation to take place in a
&lt;a href=&quot;https://html.spec.whatwg.org/multipage/links.html#link-type-noopener&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;noopener&lt;/code&gt;&lt;/a&gt; context, such
that the destination page is known to be sufficiently isolated. Text fragment directives are only
applied to the main frame. This means that text will not be searched inside iframes, and iframe
navigation will not invoke a text fragment.&lt;/p&gt;
&lt;h3 id=&quot;privacy&quot;&gt;Privacy &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#privacy&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It is important that implementations of the Text Fragments specification do not leak whether a text
fragment was found on a page or not. While element fragments are fully under the control of the
original page author, text fragments can be created by anyone. Remember how in my example above
there was no way to link to the &lt;em&gt;ECMAScript Modules in Web Workers&lt;/em&gt; heading, since the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; did
not have an &lt;code&gt;id&lt;/code&gt;, but how anyone, including me, could just link to anywhere by carefully crafting
the text fragment?&lt;/p&gt;
&lt;p&gt;Imagine I ran an evil ad network &lt;code&gt;evil-ads.example.com&lt;/code&gt;. Further imagine that in one of my ad
iframes I dynamically created a hidden cross-origin iframe to &lt;code&gt;dating.example.com&lt;/code&gt; with a Text
Fragment URL
&lt;code&gt;dating.example.com&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;#:~:text=Log%20Out&lt;/mark&gt;&lt;/code&gt;
once the user interacts with the ad. If the text &amp;quot;Log Out&amp;quot; is found, I know the victim is currently
logged in to &lt;code&gt;dating.example.com&lt;/code&gt;, which I could use for user profiling. Since a naive Text
Fragments implementation might decide that a successful match should cause a focus switch, on
&lt;code&gt;evil-ads.example.com&lt;/code&gt; I could listen for the &lt;code&gt;blur&lt;/code&gt; event and thus know when a match occurred. In
Chrome, we have implemented Text Fragments in such a way that the above scenario cannot happen.&lt;/p&gt;
&lt;p&gt;Another attack might be to exploit network traffic based on scroll position. Assume I had access to
network traffic logs of my victim, like as the admin of a company intranet. Now imagine there
existed a long human resources document &lt;em&gt;What to Do If You Suffer From…&lt;/em&gt; and then a list of
conditions like &lt;em&gt;burn out&lt;/em&gt;, &lt;em&gt;anxiety&lt;/em&gt;, etc. I could place a tracking pixel next to each item on the
list. If I then determine that loading the document temporally co-occurs with the loading of the
tracking pixel next to, say, the &lt;em&gt;burn out&lt;/em&gt; item, I can then, as the intranet admin, determine that
an employee has clicked through on a text fragment link with &lt;code&gt;:~:text=burn%20out&lt;/code&gt; that the employee
may have assumed was confidential and not visible to anyone. Since this example is somewhat
contrived to begin with and since its exploitation requires &lt;em&gt;very&lt;/em&gt; specific preconditions to be met,
the Chrome security team evaluated the risk of implementing scroll on navigation to be manageable.
Other user agents may decide to show a manual scroll UI element instead.&lt;/p&gt;
&lt;p&gt;For sites that wish to opt-out, Chromium supports a
&lt;a href=&quot;https://wicg.github.io/document-policy/&quot; rel=&quot;noopener&quot;&gt;Document Policy&lt;/a&gt;
header value that they can send so user agents will not process Text Fragment URLs.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Document-Policy: force-load-at-top&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;disabling-text-fragments&quot;&gt;Disabling text fragments &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#disabling-text-fragments&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The easiest way for disabling the feature is by using an extension that can inject HTTP response
headers, for example,
&lt;a href=&quot;https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj&quot; rel=&quot;noopener&quot;&gt;ModHeader&lt;/a&gt;
(not a Google product), to insert a response (&lt;em&gt;not&lt;/em&gt; request) header as follows:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Document-Policy: force-load-at-top&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Another, more involved, way to opt out is by using the enterprise setting
&lt;a href=&quot;https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ScrollToTextFragmentEnabled&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ScrollToTextFragmentEnabled&lt;/code&gt;&lt;/a&gt;.
To do this on macOS, paste the command below in the terminal.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;defaults &lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt; com.google.Chrome ScrollToTextFragmentEnabled -bool &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;On Windows, follow the documentation on the
&lt;a href=&quot;https://support.google.com/chrome/a/answer/9131254?hl=en&quot; rel=&quot;noopener&quot;&gt;Google Chrome Enterprise Help&lt;/a&gt; support
site.&lt;/p&gt;
&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; Only try this when you know what you are doing. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;text-fragments-in-web-search&quot;&gt;Text fragments in web search &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#text-fragments-in-web-search&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For some searches, the search engine Google provides a quick answer or summary with a content
snippet from a relevant website. These &lt;em&gt;featured snippets&lt;/em&gt; are most likely to show up when a search
is in the form of a question. Clicking a featured snippet takes the user directly to the featured
snippet text on the source web page. This works thanks to automatically created Text Fragments URLs.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;451&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KbZgnGxZOOymLxYPZyGH.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Google search engine results page showing a featured snippet. The status bar shows the Text Fragments URL.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;&quot; decoding=&quot;async&quot; height=&quot;451&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/4Q7zk9xBnb2uw8GRaLnU.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;After clicking through, the relevant section of the page is scrolled into view.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Text Fragments URL is a powerful feature to link to arbitrary text on webpages. The scholarly
community can use it to provide highly accurate citation or reference links. Search engines can use
it to deeplink to text results on pages. Social networking sites can use it to let users share
specific passages of a webpage rather than inaccessible screenshots. I hope you start
&lt;a href=&quot;https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=Text%20URL%20Fragments&amp;amp;text=text,-parameter&amp;amp;text=:~:text=On%20islands,%20birds%20can%20contribute%20as%20much%20as%2060%25%20of%20a%20cat&#39;s%20diet&quot; rel=&quot;noopener&quot;&gt;using Text Fragment URLs&lt;/a&gt;
and find them as useful as I do. Be sure to install the
&lt;a href=&quot;https://github.com/GoogleChromeLabs/link-to-text-fragment&quot; rel=&quot;noopener&quot;&gt;Link to Text Fragment&lt;/a&gt; browser
extension.&lt;/p&gt;
&lt;h2 id=&quot;related-links&quot;&gt;Related links &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#related-links&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wicg.github.io/scroll-to-text-fragment/&quot; rel=&quot;noopener&quot;&gt;Spec draft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/w3ctag/design-reviews/issues/392&quot; rel=&quot;noopener&quot;&gt;TAG Review&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chromestatus.com/feature/4733392803332096&quot; rel=&quot;noopener&quot;&gt;Chrome Platform Status entry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://crbug.com/919204&quot; rel=&quot;noopener&quot;&gt;Chrome tracking bug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://groups.google.com/a/chromium.org/d/topic/blink-dev/zlLSxQ9BA8Y/discussion&quot; rel=&quot;noopener&quot;&gt;Intent to Ship thread&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lists.webkit.org/pipermail/webkit-dev/2019-December/030978.html&quot; rel=&quot;noopener&quot;&gt;WebKit-Dev thread&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mozilla/standards-positions/issues/194&quot; rel=&quot;noopener&quot;&gt;Mozilla standards position thread&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/text-fragments/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Text Fragments was implemented and specified by &lt;a href=&quot;https://github.com/nickburris&quot; rel=&quot;noopener&quot;&gt;Nick Burris&lt;/a&gt; and
&lt;a href=&quot;https://github.com/bokand&quot; rel=&quot;noopener&quot;&gt;David Bokan&lt;/a&gt;, with contributions from
&lt;a href=&quot;https://github.com/grantjwang&quot; rel=&quot;noopener&quot;&gt;Grant Wang&lt;/a&gt;. Thanks to &lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt; for
the thorough review of this article. Hero image by &lt;a href=&quot;https://unsplash.com/@grakozy&quot; rel=&quot;noopener&quot;&gt;Greg Rakozy&lt;/a&gt; on
&lt;a href=&quot;https://unsplash.com/photos/oMpAz-DN-9I&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Make your PWA feel more like an app</title>
    <link href="https://web.dev/app-like-pwas/"/>
    <updated>2020-06-15T00:00:00Z</updated>
    <id>https://web.dev/app-like-pwas/</id>
    <content type="html" mode="escaped">&lt;p&gt;When you play Progressive Web App buzzword bingo, it is a safe bet to set on &amp;quot;PWAs are just websites&amp;quot;. Microsoft&#39;s PWA documentation &lt;a href=&quot;https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/#progressive-web-apps-on-windows:~:text=PWAs%20are%20just%20websites&quot; rel=&quot;noopener&quot;&gt;agrees&lt;/a&gt;, we &lt;a href=&quot;https://web.dev/progressive-web-apps/#content:~:text=Progressive%20Web%20Apps,Websites&quot;&gt;say it&lt;/a&gt; on this very site, and even PWA nominators Frances Berriman and Alex Russell &lt;a href=&quot;https://infrequently.org/2015/06/progressive-apps-escaping-tabs-without-losing-our-soul/#post-2263:~:text=they%E2%80%99re%20just%20websites&quot; rel=&quot;noopener&quot;&gt;write so&lt;/a&gt;, too. Yes, PWAs are just websites, but they are also way more than that. If done right, a PWA will not feel like a website, but like a &amp;quot;real&amp;quot; app. Now what does it mean to feel like a real app?&lt;/p&gt;
&lt;p&gt;To answer this question, let me use the Apple &lt;a href=&quot;https://support.apple.com/en-us/HT201859&quot; rel=&quot;noopener&quot;&gt;Podcasts&lt;/a&gt; app as an example.
It is available on macOS on desktop and on iOS (and iPadOS respectively) on mobile.
While Podcasts is a media application, the core ideas I illustrate with its help apply to other categories of apps, too.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;An iPhone and a MacBook side by side, both running the Podcasts application.&quot; decoding=&quot;async&quot; height=&quot;617&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/aNYiT2EkVkjNplAIKbLU.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Apple Podcasts on iPhone and on macOS (&lt;a href=&quot;https://support.apple.com/en-us/HT201859&quot;&gt;Source&lt;/a&gt;).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; Each iOS/Android/desktop app feature that is listed below has a &lt;strong&gt;How to do this on the web&lt;/strong&gt; component that you can open for more details. Note that not all browsers on the various operating systems support all the listed APIs or functionalities. Be sure to carefully review the compatibility notes in the linked articles. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;capable-of-running-offline&quot;&gt;Capable of running offline &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#capable-of-running-offline&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you take a step back and think of the platform-specific applications you have on your mobile phone or desktop computer, one thing clearly stands out: you never get nothing. In the Podcasts app, even if I am offline, there is always something. When there is no network connection, the app naturally still opens. The &lt;strong&gt;Top Charts&lt;/strong&gt; section does not show any content, but instead falls back to a &lt;strong&gt;Can&#39;t connect right now&lt;/strong&gt; message paired with a &lt;strong&gt;Retry&lt;/strong&gt; button. It may not be the most welcoming experience, but I get something.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Podcasts app showing a &amp;#x27;Cannot connect right now.&amp;#x27; info message when no network connection is available.&quot; decoding=&quot;async&quot; height=&quot;440&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TMbGLQkbLROxmUMdxLET.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Podcasts app without network connection.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  The Podcasts app follows the so-called app shell model. All the static content that is needed to show the core app is cached locally,
  including decorative images like the left-hand menu icons and the core player UI icons.
  Dynamic content like the &lt;b&gt;Top Charts&lt;/b&gt; data is only loaded on demand, with locally cached fallback content available should the loading fail.
  Read the  article &lt;a href=&quot;https://developers.google.com/web/fundamentals/architecture/app-shell&quot;&gt;The App Shell Model&lt;/a&gt;
  to learn how to apply this architectural model to your web app.
&lt;/details&gt;
&lt;h2 id=&quot;offline-content-available-and-media-playable&quot;&gt;Offline content available and media playable &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#offline-content-available-and-media-playable&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While offline, via the left-hand drawer, I can still navigate to the &lt;strong&gt;Downloaded&lt;/strong&gt; section and enjoy downloaded podcast episodes that are ready to be played
and are displayed with all metadata like artwork and descriptions.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Podcasts app with a downloaded episode of a podcast playing.&quot; decoding=&quot;async&quot; height=&quot;440&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/onUIDiaFNNHOmnwXzRh1.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Downloaded podcast episodes can be played even without network.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  Previously downloaded media content can be served from the cache, for example using the
  &lt;a href=&quot;https://developer.chrome.com/docs/workbox/serving-cached-audio-and-video/&quot;&gt;Serve cached audio and video&lt;/a&gt;
  recipe from the &lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot;&gt;Workbox&lt;/a&gt; library.
  Other content can always be stored in the cache, or in IndexedDB. Read the article &lt;a href=&quot;https://web.dev/storage-for-the-web/&quot;&gt;Storage for the web&lt;/a&gt;
  for all details and to know when to use what storage technology.
  If you have data that should be persistently stored without the risk of being purged when the
  available amount of memory gets low, you can use the
  &lt;a href=&quot;https://web.dev/persistent-storage/&quot;&gt;Persistent Storage API&lt;/a&gt;.
&lt;/details&gt;
&lt;h2 id=&quot;proactive-background-downloading&quot;&gt;Proactive background downloading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#proactive-background-downloading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When I am back online, I can of course search for content with a query like &lt;code&gt;http 203&lt;/code&gt;, and when I decide to subscribe to the search result, the &lt;a href=&quot;https://web.dev/podcasts/&quot;&gt;HTTP 203 podcast&lt;/a&gt;, the latest episode of the series is immediately downloaded, no questions asked.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Podcasts app downloading the latest episode of a podcast immediately after subscribing.&quot; decoding=&quot;async&quot; height=&quot;658&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WbCk4nPpBS3zwkPVRGuo.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;After subscribing to a podcast, the latest episode is immediately downloaded.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  Downloading a podcast episode is an operation that potentially can take longer. The &lt;a href=&quot;https://developers.google.com/web/updates/2018/12/background-fetch&quot;&gt;Background Fetch API&lt;/a&gt; lets you delegate downloads to the browser, which takes care of them in the background.
  On Android, the browser in turn can even delegate these downloads further on to the operating system, so the browser does not need to be continuously running.
  Once the download has completed, your app&#39;s service worker gets woken up and you can decide what to do with the response.
&lt;/details&gt;
&lt;h2 id=&quot;sharing-to-and-interacting-with-other-applications&quot;&gt;Sharing to and interacting with other applications &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#sharing-to-and-interacting-with-other-applications&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Podcasts app integrates naturally with other applications. For example, when I right-click an episode that I like, I can share it to other apps on my device, like the Messages app. It also naturally integrates with the system clipboard. I can right-click any episode and copy a link to it.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Podcasts app&amp;#x27;s context menu invoked on a podcast episode with the &amp;#x27;Share Episode &amp;gt; Messages&amp;#x27; option selected.&quot; decoding=&quot;async&quot; height=&quot;392&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/gKeFGOAZ2muuYeDNFbBW.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Sharing a podcast episode to the Messages app.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  The &lt;a href=&quot;https://web.dev/web-share/&quot;&gt;Web Share API&lt;/a&gt; and the &lt;a href=&quot;https://web.dev/web-share-target/&quot;&gt;Web Share Target API&lt;/a&gt;
  allow your app to share and receive texts, files, and links to and from other applications on the device.
  Although it is not yet possible for a web app to add menu items to the operating system&#39;s built-in right-click menu, there are lots of other ways to link to and from other apps on the device.
  With the &lt;a href=&quot;https://web.dev/image-support-for-async-clipboard/&quot;&gt;Async Clipboard API&lt;/a&gt;, you can programmatically read and write
  text and image data (PNG images) to the system clipboard.
  On Android, you can use the &lt;a href=&quot;https://web.dev/contact-picker/&quot;&gt;Contact Picker API&lt;/a&gt; to select entries from the device&#39;s contacts manager.
  If you offer both a platform-specific app and a PWA, you can use the &lt;a href=&quot;https://web.dev/get-installed-related-apps/&quot;&gt;Get Installed Related Apps API&lt;/a&gt;
  to check if the platform-specific app is installed, in which case you do not need to encourage the user to install the PWA or accept web push notifications.
&lt;/details&gt;
&lt;h2 id=&quot;background-app-refreshing&quot;&gt;Background app refreshing &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#background-app-refreshing&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the Podcasts app&#39;s settings, I can configure the app to download new episodes automatically. Like that, I do not even have to think about it, updated content will always just be there. Magic.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Podcasts app&amp;#x27;s settings menu in the &amp;#x27;General&amp;#x27; section where the &amp;#x27;Refresh Podcasts&amp;#x27; option is set to &amp;#x27;Every Hour&amp;#x27;.&quot; decoding=&quot;async&quot; height=&quot;465&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/iTKgVVjX0EM0RQS3ap4X.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Podcasts configured to check for new podcast episode every hour.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  The &lt;a href=&quot;https://web.dev/periodic-background-sync/&quot;&gt;Periodic Background Sync API&lt;/a&gt;
  allows your app to refresh its content regularly in the background without the need for it to be running.
  This means new content is proactively available, so your users can start delving into it right away whenever they decide.
&lt;/details&gt;
&lt;h2 id=&quot;state-synchronized-over-the-cloud&quot;&gt;State synchronized over the cloud &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#state-synchronized-over-the-cloud&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At the same time, my subscriptions are synchronized across all devices I own. In a seamless world, I do not have to worry about manually keeping my podcast subscriptions in sync. Likewise, I do not have to be afraid that my mobile device&#39;s memory will be consumed by episodes I have already listened to on my desktop. The play state is kept in sync, and listened-to episodes are automatically deleted.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Podcasts app&amp;#x27;s settings menu in the &amp;#x27;Advanced&amp;#x27; section where the &amp;#x27;Sync subscriptions across devices&amp;#x27; option is activated.&quot; decoding=&quot;async&quot; height=&quot;525&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/uVJJ40Zxi5jx1AP1jd9U.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;State is synchronized over the cloud.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  Syncing app state data is a task that you can delegate to the &lt;a href=&quot;https://developers.google.com/web/updates/2015/12/background-sync&quot;&gt;Background Sync API&lt;/a&gt;. The sync operation itself does not have to happen immediately, just &lt;em&gt;eventually&lt;/em&gt;, and maybe even when the user has closed the app again already.
&lt;/details&gt;
&lt;h2 id=&quot;hardware-media-key-controls&quot;&gt;Hardware media key controls &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#hardware-media-key-controls&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When I am busy with another application, say, reading a news page in the Chrome browser, I can still control the Podcasts app with the media keys on my laptop.
There is no need to switch to the app just to skip forward or backward.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Apple MacBook Pro Magic Keyboard with annotated media keys.&quot; decoding=&quot;async&quot; height=&quot;406&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/TqRtzNtfhahjX93hI1P6.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;The media keys allow for controlling the Podcasts app (&lt;a href=&quot;https://support.apple.com/guide/macbook-pro/magic-keyboard-apdd0116a6a2/mac&quot;&gt;Source&lt;/a&gt;).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  Media keys are supported by the &lt;a href=&quot;https://web.dev/media-session/&quot;&gt;Media Session API&lt;/a&gt;.
  Like that, users can make use of the hardware media keys on their physical keyboards, headphones, or even control the web app
  from the software media keys on their smartwatches.
  An additional idea to smooth seeking operations is to send a
  &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Vibration_API&quot;&gt;vibration pattern&lt;/a&gt;
  when the user seeks over a significant part of the content, for example, passing the opening credits or a chapter boundary.
&lt;/details&gt;
&lt;h2 id=&quot;multitasking-and-app-shortcut&quot;&gt;Multitasking and app shortcut &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#multitasking-and-app-shortcut&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Of course I can always multitask back to the Podcasts app from anywhere. The app has a clearly distinguishable icon that I can also put on my desktop or application dock so Podcasts can be launched immediately when I feel like it.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The macOS task switcher with a number of app icons to choose from, one of them the Podcasts app.&quot; decoding=&quot;async&quot; height=&quot;630&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/l5EzElV5BGweYXLAqF4u.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Multitasking back to the Podcasts app.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  Progressive Web Apps on both desktop and mobile can be installed to the home screen, start menu, or application dock.
  Installation can happen based on a proactive prompt, or fully controlled by the app developer.
  The article &lt;a href=&quot;https://web.dev/install-criteria/&quot;&gt;What does it take to be installable?&lt;/a&gt; covers everything you need to know.
  When multitasking, PWAs appear independent from the browser.
&lt;/details&gt;
&lt;h2 id=&quot;quick-actions-in-context-menu&quot;&gt;Quick actions in context menu &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#quick-actions-in-context-menu&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The most common app actions, &lt;strong&gt;Search&lt;/strong&gt; for new content and &lt;strong&gt;Check for New Episodes&lt;/strong&gt;, are available right from the context menu of the app in the Dock. Via the &lt;strong&gt;Options&lt;/strong&gt; menu, I can also decide to open the app at login time.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Podcasts app icon context menu showing the &amp;#x27;Search&amp;#x27; and &amp;#x27;Check for New Episodes&amp;#x27; options.&quot; decoding=&quot;async&quot; height=&quot;736&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 534px) 534px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/SnA6Thz5xaopuTWRzWgQ.png?auto=format&amp;w=1068 1068w&quot; width=&quot;534&quot; /&gt;
  &lt;figcaption&gt;Quick actions are immediately available right from the app icon.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  By specifying &lt;a href=&quot;https://web.dev/app-shortcuts/&quot;&gt;app icon shortcuts&lt;/a&gt;
  in the PWA&#39;s web app manifest, you can register quick routes to common tasks that users can reach directly from the app icon.
  On operating systems like macOS, users can also right-click the app icon and set the app to launch at login time.
  There is ongoing work on a proposal for &lt;a href=&quot;https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/RunOnLogin/Explainer.md&quot;&gt;run on login&lt;/a&gt;.
&lt;/details&gt;
&lt;h2 id=&quot;act-as-default-app&quot;&gt;Act as default app &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#act-as-default-app&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Other iOS applications and even websites or emails can integrate with the Podcasts app by leveraging the &lt;code&gt;podcasts://&lt;/code&gt; URL scheme. If I follow a link like &lt;a href=&quot;podcasts://podcasts.apple.com/podcast/the-css-podcast/id1042283903&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;podcasts://podcasts.apple.com/podcast/the-css-podcast/id1042283903&lt;/code&gt;&lt;/a&gt; while in the browser, I am brought right into the Podcasts app and can decide to subscribe or listen to the podcast.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Chrome browser showing a confirmation dialog asking the user whether they want to open the Podcasts app.&quot; decoding=&quot;async&quot; height=&quot;492&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/x8mjOWiMO4CVigvtV8Kg.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;The Podcasts app can be opened right from the browser.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  Handling fully custom URL schemes is not yet possible, but there is ongoing work on a proposal for
  &lt;a href=&quot;https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/URLProtocolHandler/explainer.md&quot;&gt;URL Protocol Handling&lt;/a&gt;
  for PWAs. Currently, &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Navigator/registerProtocolHandler&quot;&gt;&lt;code&gt;registerProtocolHandler()&lt;/code&gt;&lt;/a&gt; with a &lt;code&gt;web+&lt;/code&gt; scheme prefix is the best alternative.
&lt;/details&gt;
&lt;h2 id=&quot;local-file-system-integration&quot;&gt;Local file system integration &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#local-file-system-integration&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You may not immediately think of it, but the Podcasts app naturally integrates with the local file system. When I download a podcast episode, on my laptop it is stored in &lt;code&gt;~/Library/Group Containers/243LU875E5.groups.com.apple.podcasts/Library/Cache&lt;/code&gt;. Unlike, say &lt;code&gt;~/Documents&lt;/code&gt;, this directory is of course not meant to be accessed directly by regular users, but it is there.
Other storage mechanisms than files are referenced in the &lt;a href=&quot;https://web.dev/app-like-pwas/#offline-content-available-and-media-playable&quot;&gt;offline content&lt;/a&gt; section.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The macOS Finder navigated to the Podcasts app&amp;#x27;s system directory.&quot; decoding=&quot;async&quot; height=&quot;337&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Og60tp5kB9lVZsi3Prdt.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Podcast episodes are stored in a special system app folder.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  The &lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt; enables developers to get access to the local file system
  of the device. You can use it directly or via the &lt;a href=&quot;https://github.com/GoogleChromeLabs/browser-fs-access&quot;&gt;browser-fs-access&lt;/a&gt;
  support library that transparently provides a fallback for browsers that do not support the API.
  For security reasons, system directories are not web-accessible.
&lt;/details&gt;
&lt;h2 id=&quot;platform-look-and-feel&quot;&gt;Platform look and feel &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#platform-look-and-feel&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There is a more subtle thing that is self-evident for an iOS application like Podcasts: none of the text labels are selectable and all text blends in with the system font of the machine. Also my choice of system color theme (dark mode) is respected.&lt;/p&gt;
&lt;div class=&quot;switcher&quot;&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;The Podcasts app in dark mode.&quot; decoding=&quot;async&quot; height=&quot;463&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/OApP9uGUje6CkS7cKcZh.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;The Podcasts app supports light and dark mode.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;The Podcasts app in light mode.&quot; decoding=&quot;async&quot; height=&quot;463&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cnVihfFR2anSBlIVfCSW.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;The app uses the default system font.&lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  By leveraging the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/user-select&quot;&gt;&lt;code&gt;user-select&lt;/code&gt;&lt;/a&gt;
  CSS property with the value of &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/user-select#Syntax:~:text=none,-The&quot;&gt;&lt;code&gt;none&lt;/code&gt;&lt;/a&gt;,
  you can protect UI elements from being accidentally selected.
  Be sure, though, to not abuse this property for making &lt;em&gt;app contents&lt;/em&gt; unselectable.
  It should only be used for UI elements like button texts, etc.
  The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/font-family#%3Cgeneric-name%3E:~:text=system%2Dui,-Glyphs&quot;&gt;&lt;code&gt;system-ui&lt;/code&gt;&lt;/a&gt;
  value for the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/font-family&quot;&gt;&lt;code&gt;font-family&lt;/code&gt;&lt;/a&gt; CSS property allows you to
  specify the default UI font of the system to be used for your app.
  Finally, your app can obey to the user&#39;s color scheme preference by respecting their &lt;a href=&quot;https://web.dev/prefers-color-scheme/&quot;&gt;&lt;code&gt;prefers-color-scheme&lt;/code&gt;&lt;/a&gt; choice, with an optional &lt;a href=&quot;https://github.com/GoogleChromeLabs/dark-mode-toggle&quot;&gt;dark mode toggle&lt;/a&gt;
  to override it.
  Another thing to decide on might be what the browser should do when reaching
  the boundary of a scrolling area, for example, to implement custom &lt;em&gt;pull to refresh&lt;/em&gt;.
  This is possible with the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/overscroll-behavior&quot;&gt;&lt;code&gt;overscroll-behavior&lt;/code&gt;&lt;/a&gt; CSS property.
&lt;/details&gt;
&lt;h2 id=&quot;customized-title-bar&quot;&gt;Customized title bar &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#customized-title-bar&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you look at the Podcasts app window, you notice that it does not have a classic integrated title bar and toolbar, like, for example, the Safari browser window, but a customized experience that looks like a sidebar docked to the main player window.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Safari browser&amp;#x27;s integrated tile bar and toolbar.&quot; decoding=&quot;async&quot; height=&quot;40&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/cB7G2e31JXU71EfvhG3i.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Podcasts app&amp;#x27;s customized split customized title bar.&quot; decoding=&quot;async&quot; height=&quot;43&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/mFvLbyQ90wsDPQ9l86s3.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Customized title bars of Safari and Podcasts.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  While not currently possible, &lt;a href=&quot;https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/TitleBarCustomization/explainer.md&quot;&gt;title bar customization&lt;/a&gt; is being worked on at the moment.
  You can (and should), however, specify the &lt;a href=&quot;https://web.dev/add-manifest/#display&quot;&gt;&lt;code&gt;display&lt;/code&gt;&lt;/a&gt; and the
  &lt;a href=&quot;https://web.dev/add-manifest/#theme-color&quot;&gt;&lt;code&gt;theme-color&lt;/code&gt;&lt;/a&gt; properties of the web app manifest to
  determine the look and feel of your application window and to decide which default browser controls—potentially none of them—should be shown.
&lt;/details&gt;
&lt;h2 id=&quot;snappy-animations&quot;&gt;Snappy animations &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#snappy-animations&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In-app animations are snappy and smooth in Podcasts. For example, when I open the &lt;strong&gt;Episode Notes&lt;/strong&gt; drawer on the right, it elegantly slides in. When I remove one episode from my downloads, the remaining episodes float up and consume the screen real estate that was freed by the deleted episode.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The Podcasts app with the &amp;#x27;Episode Notes&amp;#x27; drawer expanded.&quot; decoding=&quot;async&quot; height=&quot;463&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ucob9t4Ga3jMK20RVvSD.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;In-app animations like when opening a drawer are snappy.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  Performant animations on the web are certainly possible if you take into account a number of best practices
  outlined in the article &lt;a href=&quot;https://developers.google.com/web/fundamentals/design-and-ux/animations/animations-and-performance&quot;&gt;Animations and Performance&lt;/a&gt;.
  Scroll animations as commonly seen in paginated content or media carousels can be massively improved by using the &lt;a href=&quot;https://developers.google.com/web/updates/2018/07/css-scroll-snap&quot;&gt;CSS Scroll Snap&lt;/a&gt; feature.
  For full control, you can use the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Web_Animations_API&quot;&gt;Web Animations API&lt;/a&gt;.
&lt;/details&gt;
&lt;h2 id=&quot;content-surfaced-outside-of-app&quot;&gt;Content surfaced outside of app &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#content-surfaced-outside-of-app&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Podcasts app on iOS can surface content in other locations than the actual application, for example, in the system&#39;s Widgets view, or in the form of a Siri Suggestion. Having proactive, usage-based calls-to-action that just require a tap to interact with can greatly increase the re-engagement rate of an app like Podcasts.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;iOS Widget view showing the Podcasts app suggesting a new episode of a podcast.&quot; decoding=&quot;async&quot; height=&quot;1511&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 751px) 751px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/w8zhRHcKzRfgjXZu7y4h.png?auto=format&amp;w=1502 1502w&quot; width=&quot;751&quot; /&gt;
  &lt;figcaption&gt;App content is surfaced outside of the main Podcasts app.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  The &lt;a href=&quot;https://web.dev/content-indexing-api/&quot;&gt;Content Index API&lt;/a&gt; allows your application
  to tell the browser which content of the PWA is available offline.
  This allows the browser to surface this content outside of the main app.
  By marking up interesting content in your app as suitable for &lt;a href=&quot;https://developers.google.com/search/docs/data-types/speakable&quot;&gt;speakable&lt;/a&gt;
  audio playback and by using &lt;a href=&quot;https://developers.google.com/search/docs/guides/search-gallery&quot;&gt;structured markup&lt;/a&gt; in general,
  you can help search engines and virtual assistants like the Google Assistant present your offerings in an ideal light.
&lt;/details&gt;
&lt;h2 id=&quot;lock-screen-media-control-widget&quot;&gt;Lock screen media control widget &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#lock-screen-media-control-widget&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When a podcast episode is playing, the Podcasts app shows a beautiful control widget on the lock screen that features metadata like the episode artwork, the episode title, and the podcast name.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;iOS media playback widget on the lock screen showing a podcast episode with rich metadata.&quot; decoding=&quot;async&quot; height=&quot;1511&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 751px) 751px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Lr9R2zpjDEgHtyJ7hjHf.png?auto=format&amp;w=1502 1502w&quot; width=&quot;751&quot; /&gt;
  &lt;figcaption&gt;Media playing in the app can be controlled from the lock screen.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  The &lt;a href=&quot;https://web.dev/media-session/&quot;&gt;Media Session API&lt;/a&gt; lets you specify metadata like artwork, track titles, etc.
  that then gets displayed on the lock screen, smartwatches, or other media widgets in the browser.
&lt;/details&gt;
&lt;h2 id=&quot;push-notifications&quot;&gt;Push notifications &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#push-notifications&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Push notifications have become a bit of an annoyance on the web
(albeit &lt;a href=&quot;https://blog.chromium.org/2020/01/introducing-quieter-permission-ui-for.html&quot; rel=&quot;noopener&quot;&gt;notification prompts are a lot quieter&lt;/a&gt; now).
But if used properly, they can add a lot of value.
For example, the iOS Podcasts app can optionally notify me of new episodes of podcasts I am subscribed to or recommend new ones, as well as alert me of new app features.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;iOS Podcasts app in the &amp;#x27;Notifications&amp;#x27; settings screen showing the &amp;#x27;New Episodes&amp;#x27; notifications toggle activated.&quot; decoding=&quot;async&quot; height=&quot;1511&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 751px) 751px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IFnNRo6BnHL6BxDmiqF7.png?auto=format&amp;w=1502 1502w&quot; width=&quot;751&quot; /&gt;
  &lt;figcaption&gt;Apps can send push notifications to inform the user about new content.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  The &lt;a href=&quot;https://developers.google.com/web/fundamentals/push-notifications&quot;&gt;Push API&lt;/a&gt;
  allows your app to receive push notifications so you can notify your users about noteworthy events around your PWA.
  For notifications that should fire at a known time in the future and that do not require a network connection,
  you can use the &lt;a href=&quot;https://web.dev/notification-triggers/&quot;&gt;Notification Triggers API&lt;/a&gt;.
&lt;/details&gt;
&lt;h2 id=&quot;app-icon-badging&quot;&gt;App icon badging &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#app-icon-badging&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Whenever there are new episodes available for one of the podcasts I am subscribed to, an app icon badge on the Podcasts home screen icon appears, again encouraging me to re-engage with the app in a way that is not intrusive.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;iOS settings screen showing the &amp;#x27;Badges&amp;#x27; toggle activated.&quot; decoding=&quot;async&quot; height=&quot;1511&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 751px) 751px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3smO2sJz5oMwy4RYpQoF.png?auto=format&amp;w=1502 1502w&quot; width=&quot;751&quot; /&gt;
  &lt;figcaption&gt;Badges are a subtle way for applications to inform users about new content.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  You can set app icon badges with the &lt;a href=&quot;https://web.dev/badging-api/&quot;&gt;Badging API&lt;/a&gt;.
  This is especially useful when your PWA has some notion of &quot;unread&quot; items or when you need a means
  to unobtrusively draw the user&#39;s attention back to the app.
&lt;/details&gt;
&lt;h2 id=&quot;media-playback-takes-precedence-over-energy-saver-settings&quot;&gt;Media playback takes precedence over energy saver settings &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#media-playback-takes-precedence-over-energy-saver-settings&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When podcast media is playing, the screen may turn off, but the system will not enter standby mode.
Apps can optionally keep the screen awake, too, for example to display lyrics or captions.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;macOS Preferences in the &amp;#x27;Energy Saver&amp;#x27; section.&quot; decoding=&quot;async&quot; height=&quot;573&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CRkipfmdkLJrND83qvQw.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Apps can keep the screen awake.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  The &lt;a href=&quot;https://web.dev/wakelock/&quot;&gt;Screen Wake Lock API&lt;/a&gt; allows you to prevent the screen from turning off.
  Media playback on the web automatically prevents the system from entering standby mode.
&lt;/details&gt;
&lt;h2 id=&quot;app-discovery-through-an-app-store&quot;&gt;App discovery through an app store &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#app-discovery-through-an-app-store&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While the Podcasts app is part of the macOS desktop experience, on iOS it needs to be installed from the App Store.
A quick search for &lt;code&gt;podcast&lt;/code&gt;, &lt;code&gt;podcasts&lt;/code&gt;, or &lt;code&gt;apple podcasts&lt;/code&gt; immediately turns the app up in the App Store.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;iOS App Store search for &amp;#x27;podcasts&amp;#x27; reveals the Podcasts app.&quot; decoding=&quot;async&quot; height=&quot;1511&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 751px) 751px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZLr5quaQWA9VJGAHNrLd.png?auto=format&amp;w=1502 1502w&quot; width=&quot;751&quot; /&gt;
  &lt;figcaption&gt;Users have learned to discover apps in app stores.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;details&gt;
&lt;summary&gt;
  How to do this on the web
&lt;/summary&gt;
  While Apple does not allow PWAs on the App Store, on Android, you can submit your PWA
  &lt;a href=&quot;https://web.dev/using-a-pwa-in-your-android-app/&quot;&gt;wrapped in a Trusted Web Activity&lt;/a&gt;.
  The &lt;a href=&quot;https://github.com/GoogleChromeLabs/bubblewrap&quot;&gt;&lt;code&gt;bubblewrap&lt;/code&gt;&lt;/a&gt; script makes this a painless operation.
  This script is also what internally powers &lt;a href=&quot;https://www.pwabuilder.com/&quot;&gt;PWABuilder&lt;/a&gt;&#39;s Android app export feature,
  which you can use without touching the command line.
&lt;/details&gt;
&lt;h2 id=&quot;feature-summary&quot;&gt;Feature summary &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#feature-summary&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The table below shows a compact overview of all features and provides a list of useful resources for realizing them on the web.&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Feature&lt;/th&gt;
        &lt;th&gt;Useful resources for doing this on the web&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#capable-of-running-offline&quot;&gt;Capable of running offline&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://developers.google.com/web/fundamentals/architecture/app-shell&quot;&gt;App shell model&lt;/a&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#offline-content-available-and-media-playable&quot;&gt;Offline content available and media playable&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://developer.chrome.com/docs/workbox/serving-cached-audio-and-video/&quot;&gt;Serve cached audio and video&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/docs/workbox/&quot;&gt;Workbox library&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/storage-for-the-web/&quot;&gt;Storage API&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/persistent-storage/&quot;&gt;Persistent Storage API&lt;/a&gt;&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#proactive-background-downloading&quot;&gt;Proactive background downloading&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://developers.google.com/web/updates/2018/12/background-fetch&quot;&gt;Background Fetch API&lt;/a&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#sharing-to-and-interacting-with-other-applications&quot;&gt;Sharing to and interacting with other applications&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/web-share/&quot;&gt;Web Share API&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/web-share-target/&quot;&gt;Web Share Target API&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://web.dev/image-support-for-async-clipboard/&quot;&gt;Async Clipboard API&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/contact-picker/&quot;&gt;Contact Picker API&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://web.dev/get-installed-related-apps/&quot;&gt;Get Installed Related Apps API&lt;/a&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#background-app-refreshing&quot;&gt;Background app refreshing&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://web.dev/periodic-background-sync/&quot;&gt;Periodic Background Sync API&lt;/a&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#state-synchronized-over-the-cloud&quot;&gt;State synchronized over the cloud&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://developers.google.com/web/updates/2015/12/background-sync&quot;&gt;Background Sync API&lt;/a&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#hardware-media-key-controls&quot;&gt;Hardware media key controls&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/media-session/&quot;&gt;Media Session API&lt;/a&gt;&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#multitasking-and-app-shortcut&quot;&gt;Multitasking and app shortcut&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/install-criteria/&quot;&gt;Installability criteria&lt;/a&gt;&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#quick-actions-in-context-menu&quot;&gt;Quick actions in context menu&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/app-shortcuts/&quot;&gt;App icon shortcuts&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://github.com/MicrosoftEdge/MSEdgeExplainers/tree/master/RunOnLogin&quot;&gt;Run on login&lt;/a&gt; (early stage)
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#act-as-default-app&quot;&gt;Act as default app&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/URLProtocolHandler/explainer.md&quot;&gt;URL protocol handling&lt;/a&gt; (early stage)
            &lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Navigator/registerProtocolHandler&quot;&gt;&lt;code&gt;registerProtocolHandler()&lt;/code&gt;&lt;/a&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#local-file-system-integration&quot;&gt;Local file system integration&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://github.com/GoogleChromeLabs/browser-fs-access&quot;&gt;browser-fs-access library&lt;/a&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#platform-look-and-feel&quot;&gt;Platform look and feel&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/user-select#Syntax:~:text=none,-The&quot;&gt;&lt;code&gt;user-select: none&lt;/code&gt;&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/font-family&quot;&gt;&lt;code&gt;font-family: system-ui&lt;/code&gt;&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://web.dev/prefers-color-scheme/&quot;&gt;&lt;code&gt;prefers-color-scheme&lt;/code&gt;&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://github.com/GoogleChromeLabs/dark-mode-toggle&quot;&gt;Dark mode toggle&lt;/a&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#customized-title-bar&quot;&gt;Customized title bar&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/TitleBarCustomization/explainer.md&quot;&gt;Title bar customization&lt;/a&gt; (early stage)
            &lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/add-manifest/#display&quot;&gt;Display mode&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/add-manifest/#theme-color&quot;&gt;Theme color&lt;/a&gt;&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#snappy-animations&quot;&gt;Snappy animations&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://developers.google.com/web/fundamentals/design-and-ux/animations/animations-and-performance&quot;&gt;Animations and performance tips&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://developers.google.com/web/updates/2018/07/css-scroll-snap&quot;&gt;CSS Scroll Snap&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Web_Animations_API&quot;&gt;Web Animations API&lt;/a&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#content-surfaced-outside-of-app&quot;&gt;Content surfaced outside of app&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/content-indexing-api/&quot;&gt;Content Index API&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://developers.google.com/search/docs/data-types/speakable&quot;&gt;Speakable content&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://developers.google.com/search/docs/guides/search-gallery&quot;&gt;Structured markup&lt;/a&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#lock-screen-media-control-widget&quot;&gt;Lock screen media control widget&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/media-session/&quot;&gt;Media Session API&lt;/a&gt;&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#push-notifications&quot;&gt;Push notifications&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://developers.google.com/web/fundamentals/push-notifications&quot;&gt;Push API&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/notification-triggers/&quot;&gt;Notification Triggers API&lt;/a&gt;&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#app-icon-badging&quot;&gt;App icon badging&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/badging-api/&quot;&gt;Badging API&lt;/a&gt;&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#media-playback-takes-precedence-over-energy-saver-settings&quot;&gt;Media playback trumps energy saver settings&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;&lt;a href=&quot;https://web.dev/wakelock/&quot;&gt;Screen Wake Lock API&lt;/a&gt;&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;a href=&quot;https://web.dev/app-like-pwas/#app-discovery-through-an-app-store&quot;&gt;App discovery through an app store&lt;/a&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://web.dev/using-a-pwa-in-your-android-app/&quot;&gt;Trusted Web Activity&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;a href=&quot;https://github.com/GoogleChromeLabs/bubblewrap&quot;&gt;&lt;code&gt;bubblewrap&lt;/code&gt; library&lt;/a&gt;
            &lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;https://www.pwabuilder.com/&quot;&gt;PWABuilder tool&lt;/a&gt;&lt;/li&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;PWAs have come a long way since their introduction in 2015.
In the context of &lt;a href=&quot;https://developer.chrome.com/blog/fugu-status&quot; rel=&quot;noopener&quot;&gt;Project Fugu 🐡&lt;/a&gt;, the cross-company Chromium team is working on closing the last remaining gaps.
By following even only some of the pieces of advice in this article,
you can piece by piece get closer to that app-like feeling and make your users forget
that they are dealing with &amp;quot;just a website&amp;quot;, because, honestly, most of them do not care
how your app is built (and why should they?), as long as it feels like a &lt;em&gt;real&lt;/em&gt; app.&lt;/p&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/app-like-pwas/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by
&lt;a href=&quot;https://web.dev/authors/kaycebasques/&quot;&gt;Kayce Basques&lt;/a&gt;,
&lt;a href=&quot;https://web.dev/authors/joemedley/&quot;&gt;Joe Medley&lt;/a&gt;,
&lt;a href=&quot;https://github.com/inexorabletash&quot; rel=&quot;noopener&quot;&gt;Joshua Bell&lt;/a&gt;,
&lt;a href=&quot;https://blog.almaer.com/&quot; rel=&quot;noopener&quot;&gt;Dion Almaer&lt;/a&gt;,
&lt;a href=&quot;https://blog.oshineye.com/&quot; rel=&quot;noopener&quot;&gt;Ade Oshineye&lt;/a&gt;,
&lt;a href=&quot;https://web.dev/authors/petelepage/&quot;&gt;Pete LePage&lt;/a&gt;,
&lt;a href=&quot;https://web.dev/authors/samthor/&quot;&gt;Sam Thorogood&lt;/a&gt;,
&lt;a href=&quot;https://github.com/reillyeon&quot; rel=&quot;noopener&quot;&gt;Reilly Grant&lt;/a&gt;,
and &lt;a href=&quot;https://github.com/jyasskin&quot; rel=&quot;noopener&quot;&gt;Jeffrey Yasskin&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Wake Lock API case study: 300% increase in purchase intent indicators on BettyCrocker.com
</title>
    <link href="https://web.dev/betty-crocker/"/>
    <updated>2020-05-19T00:00:00Z</updated>
    <id>https://web.dev/betty-crocker/</id>
    <content type="html" mode="escaped">&lt;p&gt;For nearly a century, Betty Crocker has been America&#39;s source for modern cooking instruction
and trusted recipe development.
Launched in 1997, their site &lt;a href=&quot;https://www.bettycrocker.com/&quot; rel=&quot;noopener&quot;&gt;BettyCrocker.com&lt;/a&gt;
today receives more than 12 million visitors per month.
After they &lt;strong&gt;implemented the Wake Lock API&lt;/strong&gt;, their
&lt;strong&gt;indicators of
&lt;a href=&quot;https://www.igi-global.com/dictionary/technology-and-sharing-economy-based-business-models-for-marketing-to-connected-consumers/24144&quot; rel=&quot;noopener&quot;&gt;purchase intent&lt;/a&gt;
were about 300% higher&lt;/strong&gt; for wake lock users compared to all users.&lt;/p&gt;
&lt;h2 id=&quot;the-retired-ios-and-android-apps&quot;&gt;The retired iOS and Android apps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/betty-crocker/#the-retired-ios-and-android-apps&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Released to &lt;a href=&quot;https://consumergoods.com/betty-crocker-launches-cookbook-app&quot; rel=&quot;noopener&quot;&gt;much fanfare&lt;/a&gt; in 2014,
Betty Crocker recently took their apps out of the Apple App Store and the Google Play Store
after they had been deprioritized.
For a long time, the Betty Crocker team has preferred adding new features to the mobile site
instead of the iOS/Android apps.
The technical platform the iOS/Android apps were created on was outdated,
and the business did not have the resources
to support updating and maintaining the apps moving forward.
The web app also was objectively way bigger traffic-wise,
more modern, and easier to enhance.&lt;/p&gt;
&lt;p&gt;The iOS/Android apps did have one &lt;em&gt;killer feature&lt;/em&gt;, though, that their users loved:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Millennial cooking pro tip: the &lt;a href=&quot;https://twitter.com/BettyCrocker&quot; rel=&quot;noopener&quot;&gt;@BettyCrocker&lt;/a&gt; mobile app
doesn&#39;t dim or lock when you&#39;re following a recipe.
—&lt;a href=&quot;https://twitter.com/AvaBeilke/status/996746473168670720&quot; rel=&quot;noopener&quot;&gt;@AvaBeilke&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;80% of people cook with a device in the kitchen, but screen dimming and locking is a problem.
What did &lt;a href=&quot;https://twitter.com/BettyCrocker&quot; rel=&quot;noopener&quot;&gt;@BettyCrocker&lt;/a&gt; do?
Updated their app to NOT dim when users are in a recipe.
—&lt;a href=&quot;https://twitter.com/Katie_Tweedy_/status/996746567762763776&quot; rel=&quot;noopener&quot;&gt;@Katie_Tweedy_&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;bringing-the-killer-feature-to-the-web-with-the-wake-lock-api&quot;&gt;Bringing the killer feature to the web with the Wake Lock API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/betty-crocker/#bringing-the-killer-feature-to-the-web-with-the-wake-lock-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When cooking with a device, there is nothing more frustrating
than having to touch the screen with messy hands or even your nose when the screen turns off.
Betty Crocker asked themselves how they could port the killer feature of their iOS/Android apps
over to the web app.
This is when they learned about
&lt;a href=&quot;https://developer.chrome.com/blog/capabilities/&quot; rel=&quot;noopener&quot;&gt;Project Fugu&lt;/a&gt; and the
&lt;a href=&quot;https://web.dev/wakelock/&quot;&gt;Wake Lock API&lt;/a&gt;.&lt;/p&gt;
&lt;img alt=&quot;A person kneading dough on a kitchen table covered in flour&quot; decoding=&quot;async&quot; height=&quot;533&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/Yoj65m20XpoPdaL8ejAv.jpg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;p&gt;The Wake Lock API provides a way to prevent the device
from dimming or locking the screen.
This capability enables new experiences that, until now, required an iOS/Android app.
The Wake Lock API reduces the need for hacky and potentially power-hungry workarounds.&lt;/p&gt;
&lt;h3 id=&quot;requesting-a-wake-lock&quot;&gt;Requesting a wake lock &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/betty-crocker/#requesting-a-wake-lock&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To request a wake lock, you need to call the &lt;code&gt;navigator.wakeLock.request()&lt;/code&gt; method
that returns a &lt;code&gt;WakeLockSentinel&lt;/code&gt; object. You will use this object as a
&lt;a href=&quot;https://en.wikipedia.org/wiki/Sentinel_value&quot; rel=&quot;noopener&quot;&gt;sentinel value&lt;/a&gt;.
The browser can refuse the request for various reasons
(for example, because the battery is too low),
so it is a good practice to wrap the call in a &lt;code&gt;try…catch&lt;/code&gt; statement.&lt;/p&gt;
&lt;h3 id=&quot;releasing-a-wake-lock&quot;&gt;Releasing a wake lock &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/betty-crocker/#releasing-a-wake-lock&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You also need a way to release a wake lock,
which is achieved by calling the &lt;code&gt;release()&lt;/code&gt; method of the &lt;code&gt;WakeLockSentinel&lt;/code&gt; object.
If you want to automatically release the wake lock after a certain period of time has passed,
you can use &lt;code&gt;window.setTimeout()&lt;/code&gt; to call &lt;code&gt;release()&lt;/code&gt;, as shown in the example below.&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;// The wake lock sentinel.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; wakeLock &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;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Function that attempts to request a wake lock.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;requestWakeLock&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;=&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;    wakeLock &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wakeLock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;screen&#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;    wakeLock&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;release&#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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;Wake Lock was released&#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;    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;Wake Lock is active&#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 keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&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;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Request a wake lock…&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestWakeLock&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;// …and release it again after 5s.&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;setTimeout&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;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  wakeLock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;release&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;  wakeLock &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 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;5000&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;the-implementation&quot;&gt;The implementation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/betty-crocker/#the-implementation&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the new web app, users should be enabled to easily navigate through a recipe,
complete steps, and even walk away without the screen locking.
To achieve this goal, the team first built a quick front-end prototype
as a proof of concept and to gather UX input.&lt;/p&gt;
&lt;p&gt;After the prototype proved useful, they designed a
&lt;a href=&quot;https://vuejs.org/v2/guide/components.html&quot; rel=&quot;noopener&quot;&gt;Vue.js component&lt;/a&gt;
that could be shared across all their brands (&lt;a href=&quot;https://www.bettycrocker.com/&quot; rel=&quot;noopener&quot;&gt;BettyCrocker&lt;/a&gt;,
&lt;a href=&quot;https://www.pillsbury.com/&quot; rel=&quot;noopener&quot;&gt;Pillsbury&lt;/a&gt;, and &lt;a href=&quot;https://www.tablespoon.com/&quot; rel=&quot;noopener&quot;&gt;Tablespoon&lt;/a&gt;).
Even though only Betty Crocker had iOS and Android apps,
the three sites do have a shared code base,
so they were able to implement the component once, and deploy it everywhere,
as shown in the screenshots below.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;BettyCrocker.com wake lock toggle&quot; decoding=&quot;async&quot; height=&quot;170&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 600px) 600px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/I9y4AIPEK9P4V0JFn4y1.png?auto=format&amp;w=1200 1200w&quot; width=&quot;600&quot; /&gt;
  &lt;figcaption&gt;BettyCrocker.com wake lock toggle.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Pillsbury.com wake lock toggle&quot; decoding=&quot;async&quot; height=&quot;152&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 600px) 600px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/PXS7bnWxYiLKtmLekulr.png?auto=format&amp;w=1200 1200w&quot; width=&quot;600&quot; /&gt;
  &lt;figcaption&gt;Pillsbury.com wake lock toggle.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Tablespoon.com wake lock toggle&quot; decoding=&quot;async&quot; height=&quot;152&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 600px) 600px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/S5NQabO9qJTwlidx2eZo.png?auto=format&amp;w=1200 1200w&quot; width=&quot;600&quot; /&gt;
  &lt;figcaption&gt;Tablespoon.com wake lock toggle.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When developing the component based on the new site&#39;s modernized framework,
there was a strong focus on the
&lt;a href=&quot;https://012.vuejs.org/guide/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ViewModel&lt;/code&gt; layer of the MVVM pattern&lt;/a&gt;.
The team also programmed with interoperability in mind
to enable functionality on legacy and new frameworks of the site.&lt;/p&gt;
&lt;p&gt;To keep track of viewability and usability, Betty Crocker integrated analytics tracking
for core events in the wake lock lifecycle.
The team utilized feature management to deploy the wake lock component
to a single site for initial production rollout,
and then deployed the feature to the rest of the sites after monitoring usage and page health.
They continue to monitor analytics data based on the usage of this component.&lt;/p&gt;
&lt;p&gt;As a failsafe for users, the team created a forced timeout
to disable the wake lock after one hour of inactivity.
The final implementation they settled on
was in the short-term a toggle switch on all recipe pages across their sites.
In the long-term, they envision a revamped recipe page view.&lt;/p&gt;
&lt;h3 id=&quot;the-wake-lock-container&quot;&gt;The wake lock container &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/betty-crocker/#the-wake-lock-container&quot;&gt;#&lt;/a&gt;&lt;/h3&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; &lt;span class=&quot;token function-variable function&quot;&gt;wakeLockControl&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;span class=&quot;token operator&quot;&gt;=&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 keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;/* webpackChunkName: &#39;wakeLock&#39; */&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./wakeLock&#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 keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&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;components&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 literal-property property&quot;&gt;wakeLockControl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; wakeLockControl&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 function&quot;&gt;data&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;config&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;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;wakeLockComponent&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#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;  &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;methods&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;init&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;config&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;config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config &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;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 string&quot;&gt;&#39;wakeLock&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;request&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wakeLock&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;wakeLockComponent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;wakeLockControl&#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 keyword&quot;&gt;else&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;Browser not supported&#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;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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;the-wake-lock-component&quot;&gt;The wake lock component &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/betty-crocker/#the-wake-lock-component&quot;&gt;#&lt;/a&gt;&lt;/h3&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;template&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 attr-name&quot;&gt;class&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;wakeLock&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;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;textAbove&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 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;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;label&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;switch&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;:aria-label&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;settingsInternal.textAbove&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;input&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;checkbox&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;@change&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;onChange()&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;v-model&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;isChecked&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;span&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;slider round&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 tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&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;label&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;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;template&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;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;text/javascript&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 class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; debounce &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;lodash.debounce&#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 keyword&quot;&gt;const&lt;/span&gt; scrollDebounceMs &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;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&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;props&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 literal-property property&quot;&gt;settings&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 literal-property property&quot;&gt;type&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 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 function&quot;&gt;data&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;settingsInternal&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;settings &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;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;isChecked&lt;/span&gt;&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 literal-property property&quot;&gt;wakeLock&lt;/span&gt;&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 literal-property property&quot;&gt;timerId&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 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;span class=&quot;token function&quot;&gt;created&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;$_raiseAnalyticsEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Wake Lock Toggle Available&#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;span class=&quot;token literal-property property&quot;&gt;methods&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;onChange&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;if&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;isChecked&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;$_requestWakeLock&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 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;$_releaseWakeLock&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;br /&gt;      &lt;span class=&quot;token function-variable function&quot;&gt;$_requestWakeLock&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&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;try&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;wakeLock &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wakeLock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;screen&#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;//Start new timer&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;$_handleAbortTimer&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;//Only add event listeners after wake lock is successfully enabled&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;addEventListener&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;visibilitychange&#39;&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;$_handleVisibilityChange&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;          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;br /&gt;            &lt;span class=&quot;token string&quot;&gt;&#39;scroll&#39;&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;debounce&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;$_handleAbortTimer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scrollDebounceMs&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;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;$_raiseAnalyticsEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Wake Lock Toggle Enabled&#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 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 keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isChecked &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 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 function-variable function&quot;&gt;$_releaseWakeLock&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;try&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;wakeLock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;release&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;wakeLock &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 comment&quot;&gt;//Clear timer&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;$_handleAbortTimer&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;//Clean up event listeners&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;removeEventListener&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;visibilitychange&#39;&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;$_handleVisibilityChange&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;          window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEventListener&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;scroll&#39;&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;debounce&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;$_handleAbortTimer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; scrollDebounceMs&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;span class=&quot;token punctuation&quot;&gt;}&lt;/span&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;          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 template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Wake Lock Release Error: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;br /&gt;      &lt;span class=&quot;token function-variable function&quot;&gt;$_handleAbortTimer&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;//If there is an existing timer then clear it and set to zero&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 keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timerId &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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token function&quot;&gt;clearTimeout&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;timerId&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;timerId &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;//Start new timer; Will be triggered from toggle enabled or scroll event&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 keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isChecked&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;timerId &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;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;$_releaseWakeLock&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;settingsInternal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timeoutDurationMs&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;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 function-variable function&quot;&gt;$_handleVisibilityChange&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;//Handle navigating away from page/tab&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 keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isChecked&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;$_releaseWakeLock&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;isChecked &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 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 function-variable function&quot;&gt;$_raiseAnalyticsEvent&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;eventType&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;let&lt;/span&gt; eventParams &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 literal-property property&quot;&gt;EventType&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; eventType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token literal-property property&quot;&gt;Position&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#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;        Analytics&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;raiseEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eventParams&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;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;/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;script&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;results&quot;&gt;Results &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/betty-crocker/#results&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The Vue.js component has been deployed on all three sites and delivered great results.
During the period from December 10th, 2019 to January 10th, 2020,
BettyCrocker.com reported the following metrics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Of all Betty Crocker users with a browser compatible with the Wake Lock API,
3.5% of them enabled the feature immediately, making it a top-5 action.&lt;/li&gt;
&lt;li&gt;The session duration for users who enabled the wake lock was 3.1× longer
than for users who did not.&lt;/li&gt;
&lt;li&gt;The bounce rate for users who enabled the wake lock was 50% lower
than for those not using the wake lock feature.&lt;/li&gt;
&lt;li&gt;Indicators of purchase intent were about 300% higher for wake lock users compared to all users.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;stats&quot;&gt;
  &lt;div class=&quot;stats__item&quot;&gt;
    &lt;p class=&quot;stats__figure&quot;&gt;3.1&lt;sub&gt;×&lt;/sub&gt;&lt;/p&gt;
    &lt;p&gt;Longer session duration&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;stats__item&quot;&gt;
    &lt;p class=&quot;stats__figure&quot;&gt;50&lt;sub&gt;%&lt;/sub&gt;&lt;/p&gt;
    &lt;p&gt;Lower bounce rate&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;stats__item&quot;&gt;
    &lt;p class=&quot;stats__figure&quot;&gt;300&lt;sub&gt;%&lt;/sub&gt;&lt;/p&gt;
    &lt;p&gt;Higher purchase intent indicators&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/betty-crocker/#conclusions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Betty Crocker has seen fantastic results using the Wake Lock API.
You can test the feature yourself by searching for your favorite recipe on any of their sites
(&lt;a href=&quot;https://www.bettycrocker.com/&quot; rel=&quot;noopener&quot;&gt;BettyCrocker&lt;/a&gt;,
&lt;a href=&quot;https://www.pillsbury.com/&quot; rel=&quot;noopener&quot;&gt;Pillsbury&lt;/a&gt;, or &lt;a href=&quot;https://www.tablespoon.com/&quot; rel=&quot;noopener&quot;&gt;Tablespoon&lt;/a&gt;)
and enabling the &lt;strong&gt;Prevent your screen from going dark while you cook&lt;/strong&gt; toggle.&lt;/p&gt;
&lt;p&gt;Use cases for wake locks do not stop at recipe sites.
Other examples are boarding pass or ticket apps that need to keep the screen on
until the barcode has been scanned, kiosk-style apps that keep the screen on continuously,
or web-based presentation apps that prevent the screen from sleeping during a presentation.&lt;/p&gt;
&lt;p&gt;We have compiled &lt;a href=&quot;https://web.dev/wakelock/&quot;&gt;everything you need to know about the Wake Lock API&lt;/a&gt;
in a comprehensive article on this very site.
Happy reading, and happy cooking!&lt;/p&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/betty-crocker/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;em&gt;person kneading dough&lt;/em&gt; photo courtesy of
&lt;a href=&quot;https://unsplash.com/@julianhochgesang&quot; rel=&quot;noopener&quot;&gt;Julian Hochgesang&lt;/a&gt;
on &lt;a href=&quot;https://unsplash.com/photos/huepD-06_pQ&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author><author>
      <name>Phillip Kriegel</name>
    </author>
  </entry>
  
  <entry>
    <title>Improved dark mode default styling with the `color-scheme` CSS property and the corresponding meta tag</title>
    <link href="https://web.dev/color-scheme/"/>
    <updated>2020-04-08T00:00:00Z</updated>
    <id>https://web.dev/color-scheme/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;background&quot;&gt;Background &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/color-scheme/#background&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;the-prefers-color-scheme-user-preference-media-feature&quot;&gt;The &lt;code&gt;prefers-color-scheme&lt;/code&gt; user preference media feature &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/color-scheme/#the-prefers-color-scheme-user-preference-media-feature&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/@media/prefers-color-scheme&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;prefers-color-scheme&lt;/code&gt;&lt;/a&gt;
user preference media feature gives developers full control over their pages&#39; appearances.
If you are unfamiliar with it, please read my article
&lt;a href=&quot;https://web.dev/prefers-color-scheme/&quot;&gt;&lt;code&gt;prefers-color-scheme&lt;/code&gt;: Hello darkness, my old friend&lt;/a&gt;,
where I documented everything I know about creating amazing dark mode experiences.&lt;/p&gt;
&lt;p&gt;One puzzle piece that was only mentioned briefly in the article is
the &lt;code&gt;color-scheme&lt;/code&gt; CSS property and the corresponding meta tag of the same name.
They both make your life as a developer easier
by allowing you to opt your page in to theme-specific defaults of the user agent stylesheet,
such as, for example, form controls, scroll bars, as well as CSS system colors.
At the same time, this feature prevents browsers from applying any transformations on their own.&lt;/p&gt;
&lt;h3 id=&quot;browser-support&quot;&gt;Browser support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/color-scheme/#browser-support&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;prefers-color-scheme&quot;&gt;&lt;code&gt;prefers-color-scheme&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/color-scheme/#prefers-color-scheme&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 76, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      76
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 67, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      67
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      79
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 12.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      12.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/CSS/@media/prefers-color-scheme#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h4 id=&quot;color-scheme&quot;&gt;&lt;code&gt;color-scheme&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/color-scheme/#color-scheme&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 81, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      81
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 96, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      96
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 81, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      81
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 13, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      13
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/CSS/color-scheme#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h3 id=&quot;the-user-agent-stylesheet&quot;&gt;The user agent stylesheet &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/color-scheme/#the-user-agent-stylesheet&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Before I continue, let me briefly describe what a user agent stylesheet is.
Most of the time, you can think of the word &lt;em&gt;user agent&lt;/em&gt; (UA)
as a fancy way to say &lt;em&gt;browser&lt;/em&gt;.
The UA stylesheet determines the default look and feel of a page.
As the name suggests, a UA stylesheet is something that depends on the UA in question.
You can have a look at
&lt;a href=&quot;https://chromium.googlesource.com/chromium/blink/+/master/Source/core/css/html.css&quot; rel=&quot;noopener&quot;&gt;Chrome&#39;s&lt;/a&gt;
(and Chromium&#39;s) UA stylesheet and compare it to
&lt;a href=&quot;https://dxr.mozilla.org/mozilla-central/source/layout/style/res/html.css&quot; rel=&quot;noopener&quot;&gt;Firefox&#39;s&lt;/a&gt; or
&lt;a href=&quot;https://trac.webkit.org/browser/trunk/Source/WebCore/css/html.css&quot; rel=&quot;noopener&quot;&gt;Safari&#39;s&lt;/a&gt; (and WebKit&#39;s).
Typically, UA stylesheets agree on the majority of things.
For example, they all make links blue, general text black, and background color white,
but there are also important (and sometimes annoying) differences,
for instance, how they style form controls.&lt;/p&gt;
&lt;p&gt;Have a closer look at
&lt;a href=&quot;https://trac.webkit.org/browser/trunk/Source/WebCore/css/html.css&quot; rel=&quot;noopener&quot;&gt;WebKit&#39;s UA stylesheet&lt;/a&gt;
and what it does regarding dark mode.
(Do a full text search for &amp;quot;dark&amp;quot; in the stylesheet.)
The default provided by the stylesheet changes based on whether dark mode is on or off.
To illustrate this, here is one such CSS rule using the
&lt;a href=&quot;https://css-tricks.com/almanac/selectors/m/matches/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;:matches&lt;/code&gt;&lt;/a&gt;
pseudo class and WebKit-internal variables like &lt;code&gt;-apple-system-control-background&lt;/code&gt;,
as well as the WebKit-internal preprocessor directive &lt;code&gt;#if defined&lt;/code&gt;:&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;input,&lt;br /&gt;input:matches([type=&quot;password&quot;], [type=&quot;search&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;-webkit-appearance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; textfield&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  #if &lt;span class=&quot;token function&quot;&gt;defined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;HAVE_OS_DARK_MODE_SUPPORT&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &amp;amp;&amp;amp;&lt;br /&gt;      HAVE_OS_DARK_MODE_SUPPORT&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; text&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; -apple-system-control-background&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  #else&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;background-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;  #endif&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;/* snip */&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;You will notice some non-standard values for the &lt;code&gt;color&lt;/code&gt; and &lt;code&gt;background-color&lt;/code&gt; properties above.
Neither &lt;code&gt;text&lt;/code&gt; nor &lt;code&gt;-apple-system-control-background&lt;/code&gt; are valid CSS colors.
They are WebKit-internal &lt;em&gt;semantic&lt;/em&gt; colors.&lt;/p&gt;
&lt;p&gt;Turns out, CSS has standardized semantic system colors.
They are specified in
&lt;a href=&quot;https://drafts.csswg.org/css-color/#css-system-colors&quot; rel=&quot;noopener&quot;&gt;CSS Color Module Level 4&lt;/a&gt;.
For example,
&lt;a href=&quot;https://drafts.csswg.org/css-color/#valdef-system-color-canvas&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;Canvas&lt;/code&gt;&lt;/a&gt;
(not to be confused with the &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; tag)
is for the background of application content or documents,
whereas
&lt;a href=&quot;https://drafts.csswg.org/css-color/#valdef-system-color-canvastext&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;CanvasText&lt;/code&gt;&lt;/a&gt;
is for text in application content or documents.
The two go together and should not be used in isolation.&lt;/p&gt;
&lt;p&gt;UA stylesheets can use either their own proprietary or the standardized semantic system colors,
to determine how HTML elements should be rendered by default.
If the operating system is set to dark mode or uses a dark theme,
&lt;code&gt;CanvasText&lt;/code&gt; (or &lt;code&gt;text&lt;/code&gt;) would be conditionally set to white,
and &lt;code&gt;Canvas&lt;/code&gt; (or &lt;code&gt;-apple-system-control-background&lt;/code&gt;) would be set to black.
The UA stylesheet then assigns the following CSS only once, and covers both light and dark mode.&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 comment&quot;&gt;/**&lt;br /&gt;  Not actual UA stylesheet code.&lt;br /&gt;  For illustrative purposes only.&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;body&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; CanvasText&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Canvas&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;the-color-scheme-css-property&quot;&gt;The &lt;code&gt;color-scheme&lt;/code&gt; CSS property &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/color-scheme/#the-color-scheme-css-property&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://drafts.csswg.org/css-color-adjust/&quot; rel=&quot;noopener&quot;&gt;CSS Color Adjustment Module Level 1&lt;/a&gt;
specification introduces a model and controls
over automatic color adjustment by the user agent
with the objective of handling user preferences
such as dark mode, contrast adjustment, or specific desired color schemes.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://drafts.csswg.org/css-color-adjust/#color-scheme-prop&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;color-scheme&lt;/code&gt;&lt;/a&gt;
property defined therein allows an element to indicate
which color schemes it is comfortable being rendered with.
These values are negotiated with the user&#39;s preferences, resulting in a chosen color scheme
that affects user interface (UI) things such as the default colors of form controls
and scroll bars, as well as the used values of the CSS system colors.
The following values are currently supported:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;&lt;code&gt;normal&lt;/code&gt;&lt;/em&gt; Indicates that the element is not aware of color schemes at all,
and so the element should be rendered with the browser&#39;s default color scheme.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;&lt;code&gt;[ light | dark ]+&lt;/code&gt;&lt;/em&gt; Indicates that the element is aware of and can handle
the listed color schemes, and expresses an ordered preference between them.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; Providing both keywords indicates that the first scheme is preferred (by the author), but the second is also acceptable if the user prefers it instead. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;In this list, &lt;code&gt;light&lt;/code&gt; represents a light color scheme,
with light background colors and dark foreground colors,
whereas &lt;code&gt;dark&lt;/code&gt; represents the opposite, with dark background colors and light foreground colors.&lt;/p&gt;
&lt;p&gt;For all elements, rendering with a color scheme should cause the colors used
in all browser-provided UI for the element to match with the intent of the color scheme.
Examples are scroll bars, spellcheck underlines, form controls, etc.&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;color-scheme&lt;/code&gt; CSS property can be used on both the &lt;code&gt;:root&lt;/code&gt; level, as well as on an individual per-element level. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;On the &lt;code&gt;:root&lt;/code&gt; element, rendering with a color scheme
additionally must affect the surface color of the canvas (that is, the global background color),
the initial value of the &lt;code&gt;color&lt;/code&gt; property, and the used values of the system colors,
and should also affect the viewport&#39;s scroll bars.&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 comment&quot;&gt;/*&lt;br /&gt;  The page supports both dark and light color schemes,&lt;br /&gt;  and the page author prefers dark.&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;:root&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-scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dark light&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;the-color-scheme-meta-tag&quot;&gt;The &lt;code&gt;color-scheme&lt;/code&gt; meta tag &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/color-scheme/#the-color-scheme-meta-tag&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Honoring the &lt;code&gt;color-scheme&lt;/code&gt; CSS property requires the CSS to be first downloaded
(if it is referenced via &lt;code&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot;&amp;gt;&lt;/code&gt;) and to be parsed.
To aid user agents in rendering the page background with the desired color scheme &lt;em&gt;immediately&lt;/em&gt;,
a &lt;code&gt;color-scheme&lt;/code&gt; value can also be provided in a
&lt;a href=&quot;https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;meta name=&amp;quot;color-scheme&amp;quot;&amp;gt;&lt;/code&gt;&lt;/a&gt;
element.&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;!--&lt;br /&gt;  The page supports both dark and light color schemes,&lt;br /&gt;  and the page author prefers dark.&lt;br /&gt;--&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;color-scheme&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;dark light&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;h2 id=&quot;combining-color-scheme-and-prefers-color-scheme&quot;&gt;Combining &lt;code&gt;color-scheme&lt;/code&gt; and &lt;code&gt;prefers-color-scheme&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/color-scheme/#combining-color-scheme-and-prefers-color-scheme&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since both the meta tag and the CSS property (if applied to the &lt;code&gt;:root&lt;/code&gt; element)
eventually result in the same behavior, I always recommend specifying the color scheme
via the meta tag, so the browser can adopt to the preferred scheme faster.&lt;/p&gt;
&lt;p&gt;While for absolute baseline pages no additional CSS rules are necessary,
in the general case you should always combine &lt;code&gt;color-scheme&lt;/code&gt; with &lt;code&gt;prefers-color-scheme&lt;/code&gt;.
For example, the proprietary WebKit CSS color &lt;code&gt;-webkit-link&lt;/code&gt;, used by WebKit and Chrome
for the classic link blue &lt;code&gt;rgb(0,0,238)&lt;/code&gt;,
has an insufficient contrast ratio of 2.23:1 on a black background and
&lt;a href=&quot;https://webaim.org/resources/contrastchecker/?fcolor=0000EE&amp;amp;bcolor=000000&quot; rel=&quot;noopener&quot;&gt;fails&lt;/a&gt;
both the WCAG AA as well as the WCAG AAA
&lt;a href=&quot;https://www.w3.org/WAI/WCAG21/Understanding/conformance#levels&quot; rel=&quot;noopener&quot;&gt;requirements&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have opened bugs for &lt;a href=&quot;https://crbug.com/1066811&quot; rel=&quot;noopener&quot;&gt;Chrome&lt;/a&gt;,
&lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=209851&quot; rel=&quot;noopener&quot;&gt;WebKit&lt;/a&gt;, and
&lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=1626560&quot; rel=&quot;noopener&quot;&gt;Firefox&lt;/a&gt;
as well as a &lt;a href=&quot;https://github.com/whatwg/html/issues/5426&quot; rel=&quot;noopener&quot;&gt;meta issue in the HTML Standard&lt;/a&gt;
to get this fixed.&lt;/p&gt;
&lt;h2 id=&quot;interplay-with-prefers-color-scheme&quot;&gt;Interplay with &lt;code&gt;prefers-color-scheme&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/color-scheme/#interplay-with-prefers-color-scheme&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The interplay of the &lt;code&gt;color-scheme&lt;/code&gt; CSS property and the corresponding meta tag
with the &lt;code&gt;prefers-color-scheme&lt;/code&gt; user preference media feature may seem confusing at first.
In fact, they play together really well.
The most important thing to understand is that &lt;code&gt;color-scheme&lt;/code&gt;
exclusively determines the default appearance,
whereas &lt;code&gt;prefers-color-scheme&lt;/code&gt; determines the stylable appearance.
To make this clearer, assume the following page:&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;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;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;color-scheme&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;dark light&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;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;fieldset&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-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gainsboro&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 atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@media&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;prefers-color-scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; dark&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;fieldset&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-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; darkslategray&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&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;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;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    Lorem ipsum dolor sit amet, legere ancillae ne vis.&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;&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;form&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;fieldset&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;legend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Lorem ipsum&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;legend&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;button&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;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;Lorem ipsum&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 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;fieldset&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;form&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;The inline CSS code on the page
sets the &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; element&#39;s &lt;code&gt;background-color&lt;/code&gt; to &lt;code&gt;gainsboro&lt;/code&gt; in the general case,
and to &lt;code&gt;darkslategray&lt;/code&gt; if the user prefers a &lt;code&gt;dark&lt;/code&gt; color scheme
according to the &lt;code&gt;prefers-color-scheme&lt;/code&gt; user preference media feature.&lt;/p&gt;
&lt;p&gt;Via the &lt;code&gt;&amp;lt;meta name=&amp;quot;color-scheme&amp;quot; content=&amp;quot;dark light&amp;quot;&amp;gt;&lt;/code&gt; element,
the page tells the browser that it supports a dark and a light theme,
with a preference for a dark theme.&lt;/p&gt;
&lt;p&gt;Depending on whether the operating system is set to dark or light mode,
the whole page appears light on dark, or vice versa, based on the user agent stylesheet.
There is &lt;em&gt;no&lt;/em&gt; additional developer-provided CSS involved to change the paragraph text
or the background color of the page.&lt;/p&gt;
&lt;p&gt;Note how the &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; element&#39;s &lt;code&gt;background-color&lt;/code&gt; changes
based on whether dark mode is enabled, following the rules
in the developer-provided inline stylesheet on the page.
It is either &lt;code&gt;gainsboro&lt;/code&gt; or &lt;code&gt;darkslategray&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A page in light mode.&quot; decoding=&quot;async&quot; height=&quot;322&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/kSgOIiGRqjw2PvRlVCaV.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    &lt;strong&gt;Light mode:&lt;/strong&gt; Styles specified by the developer and the user agent.
    The text is black and the background is white as per the user agent stylesheet.
    The &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; element&#39;s &lt;code&gt;background-color&lt;/code&gt; is &lt;code&gt;gainsboro&lt;/code&gt;
    as per the inlined developer stylesheet.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A page in dark mode.&quot; decoding=&quot;async&quot; height=&quot;322&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qqkHz83kerktbDIGCJeG.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    &lt;strong&gt;Dark mode:&lt;/strong&gt; Styles specified by the developer and the user agent.
    The text is white and the background is black as per the user agent stylesheet.
    The &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; element&#39;s &lt;code&gt;background-color&lt;/code&gt; is &lt;code&gt;darkslategray&lt;/code&gt;
    as per the inlined developer stylesheet.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element&#39;s appearance is controlled by the user agent stylesheet.
Its &lt;code&gt;color&lt;/code&gt; is set to the
&lt;a href=&quot;https://drafts.csswg.org/css-color/#valdef-system-color-buttontext&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ButtonText&lt;/code&gt;&lt;/a&gt;
system color, and its &lt;code&gt;background-color&lt;/code&gt; and the four &lt;code&gt;border-color&lt;/code&gt;s are set to the system color
&lt;a href=&quot;https://drafts.csswg.org/css-color/#valdef-system-color-buttonface&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;ButtonFace&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A light mode page that uses the ButtonFace property.&quot; decoding=&quot;async&quot; height=&quot;322&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lSNFROIe1P94DlhoVtoV.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    &lt;strong&gt;Light mode:&lt;/strong&gt; The &lt;code&gt;background-color&lt;/code&gt; and the various
    &lt;code&gt;border-color&lt;/code&gt;s are set to the &lt;a href=&quot;https://drafts.csswg.org/css-color/#valdef-system-color-buttonface&quot;&gt;ButtonFace&lt;/a&gt;
    system color.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Now note how the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element&#39;s &lt;code&gt;border-color&lt;/code&gt; changes.
The &lt;em&gt;computed&lt;/em&gt; value for the &lt;code&gt;border-top-color&lt;/code&gt; and the &lt;code&gt;border-bottom-color&lt;/code&gt;
switches from &lt;code&gt;rgba(0, 0, 0, 0.847)&lt;/code&gt; (blackish) to &lt;code&gt;rgba(255, 255, 255, 0.847)&lt;/code&gt; (whitish),
since the user agent updates &lt;code&gt;ButtonFace&lt;/code&gt; dynamically based on the color scheme.
The same applies for the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element&#39;s &lt;code&gt;color&lt;/code&gt;
that is set to the corresponding system color &lt;code&gt;ButtonText&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Showing that the computed color values match ButtonFace.&quot; decoding=&quot;async&quot; height=&quot;322&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/IogmyIzUhokJgnnxUkPi.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    &lt;strong&gt;Light mode:&lt;/strong&gt; The computed values of the &lt;code&gt;border-top-color&lt;/code&gt;
    and the &lt;code&gt;border-bottom-color&lt;/code&gt; that are both set to &lt;code&gt;ButtonFace&lt;/code&gt;
    in the user agent stylesheet are now &lt;code&gt;rgba(0, 0, 0, 0.847)&lt;/code&gt;.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Showing that the computed color values still match ButtonFace while in dark mode.&quot; decoding=&quot;async&quot; height=&quot;322&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/3sU1uZyt3zNhEgw3gpZJ.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    &lt;strong&gt;Dark mode:&lt;/strong&gt; The computed values of the &lt;code&gt;border-top-color&lt;/code&gt;
    and the &lt;code&gt;border-bottom-color&lt;/code&gt; that are both set to &lt;code&gt;ButtonFace&lt;/code&gt;
    in the user agent stylesheet are now &lt;code&gt;rgba(255, 255, 255, 0.847)&lt;/code&gt;.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/color-scheme/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can see the effects of &lt;code&gt;color-scheme&lt;/code&gt; applied to a large number of HTML elements
in a &lt;a href=&quot;https://color-scheme-demo.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo on Glitch&lt;/a&gt;.
The demo &lt;em&gt;deliberately&lt;/em&gt; shows the WCAG AA and WCAG AAA
&lt;a href=&quot;https://webaim.org/resources/contrastchecker/?fcolor=0000EE&amp;amp;bcolor=000000&quot; rel=&quot;noopener&quot;&gt;violation&lt;/a&gt;
with the link colors mentioned in the
&lt;a href=&quot;https://web.dev/color-scheme/#using-color-scheme-in-practice&quot;&gt;warning above&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The demo while in light mode.&quot; decoding=&quot;async&quot; height=&quot;982&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/bqXapQKcNbyE3uwEOELO.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The &lt;a href=&quot;https://color-scheme-demo.glitch.me/&quot;&gt;demo&lt;/a&gt;
    toggled to &lt;code&gt;color-scheme: light&lt;/code&gt;.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The demo while in dark mode.&quot; decoding=&quot;async&quot; height=&quot;982&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/9G4hFdtSSwPLOm57zedD.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The &lt;a href=&quot;https://color-scheme-demo.glitch.me/&quot;&gt;demo&lt;/a&gt;
    toggled to &lt;code&gt;color-scheme: dark&lt;/code&gt;.
    Note the WCAG&amp;nbsp;AA and WCAG&amp;nbsp;AAA
    &lt;a href=&quot;https://webaim.org/resources/contrastchecker/?fcolor=0000EE&amp;bcolor=000000&quot;&gt;
      violation
    &lt;/a&gt;
    with the link colors.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/color-scheme/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;color-scheme&lt;/code&gt; CSS property and the corresponding meta tag were implemented by
&lt;a href=&quot;https://github.com/lilles&quot; rel=&quot;noopener&quot;&gt;Rune Lillesveen&lt;/a&gt;.
Rune is also a co-editor of the CSS Color Adjustment Module Level 1 specification.
Hero image by
&lt;a href=&quot;https://unsplash.com/@philinit?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot; rel=&quot;noopener&quot;&gt;Philippe Leone&lt;/a&gt;
on &lt;a href=&quot;https://unsplash.com/photos/dbFfEBOCrkU&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Adaptive icon support in PWAs with maskable icons</title>
    <link href="https://web.dev/maskable-icon/"/>
    <updated>2019-12-19T00:00:00Z</updated>
    <id>https://web.dev/maskable-icon/</id>
    <content type="html" mode="escaped">&lt;h2 id=&quot;what&quot;&gt;What are maskable icons? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/maskable-icon/#what&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;ve installed a Progressive Web App on a recent Android phone, you might notice the icon shows
up with a white background. Android Oreo introduced adaptive icons, which display app icons in a
variety of shapes across different device models. Icons that don&#39;t follow this new format are given
white backgrounds.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;PWA icons in white circles on Android&quot; decoding=&quot;async&quot; height=&quot;100&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/jzjx6dGkXN9EdqnUzAeg.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/jzjx6dGkXN9EdqnUzAeg.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/jzjx6dGkXN9EdqnUzAeg.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/jzjx6dGkXN9EdqnUzAeg.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/jzjx6dGkXN9EdqnUzAeg.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/jzjx6dGkXN9EdqnUzAeg.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/jzjx6dGkXN9EdqnUzAeg.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/jzjx6dGkXN9EdqnUzAeg.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/jzjx6dGkXN9EdqnUzAeg.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/jzjx6dGkXN9EdqnUzAeg.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/jzjx6dGkXN9EdqnUzAeg.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/jzjx6dGkXN9EdqnUzAeg.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/jzjx6dGkXN9EdqnUzAeg.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
  &lt;figcaption&gt;Transparent PWA icons appear inside white circles on Android.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Maskable icons are a new icon format that give you more control and let your Progressive Web App use
adaptive icons. If you supply a maskable icon, your icon can fill up the entire shape and look great
on all Android devices. Firefox and Chrome have recently added support for this new format, and you
can adopt it in your apps.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;PWA icons covering the entire circle on Android&quot; decoding=&quot;async&quot; height=&quot;100&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 400px) 400px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/J7gkg9ylP2ANlFawblze.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/J7gkg9ylP2ANlFawblze.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/J7gkg9ylP2ANlFawblze.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/J7gkg9ylP2ANlFawblze.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/J7gkg9ylP2ANlFawblze.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/J7gkg9ylP2ANlFawblze.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/J7gkg9ylP2ANlFawblze.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/J7gkg9ylP2ANlFawblze.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/J7gkg9ylP2ANlFawblze.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/J7gkg9ylP2ANlFawblze.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/J7gkg9ylP2ANlFawblze.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/J7gkg9ylP2ANlFawblze.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/J7gkg9ylP2ANlFawblze.png?auto=format&amp;w=800 800w&quot; width=&quot;400&quot; /&gt;
  &lt;figcaption&gt;Maskable icons cover the entire circle instead.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;are-my-current-icons-ready&quot;&gt;Are my current icons ready? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/maskable-icon/#are-my-current-icons-ready&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since maskable icons need to support a variety of shapes, you supply an opaque image with some
padding that the browser can crop to the required shape and size. It&#39;s best not to rely on
any particular shape, since the ultimate shape varies by browser and platform.&lt;/p&gt;
&lt;figure data-float=&quot;right&quot;&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/tcFciHGuF3MxnTr1y5ue01OGLBn2/mx1PEstODUy6b5TXjo4S.webm&quot; type=&quot;video/webm&quot; /&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/tcFciHGuF3MxnTr1y5ue01OGLBn2/tw7QbXq9SBjGL3UYW0Fq.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    Different platform-specific shapes.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Luckily, there&#39;s a well-defined and &lt;a href=&quot;https://w3c.github.io/manifest/#icon-masks&quot; rel=&quot;noopener&quot;&gt;standardized&lt;/a&gt;
&amp;quot;minimum safe zone&amp;quot; that all platforms respect. The important parts of your icon, such as your logo,
should be within a circular area in the center of the icon with a radius equal to 40% of the icon
width. The outer 10% edge may be cropped.&lt;/p&gt;
&lt;p&gt;You can check which parts of your icons land within the safe zone with Chrome DevTools. With your
Progressive Web App open, launch DevTools and navigate to the &lt;strong&gt;Application&lt;/strong&gt; panel. In the
&lt;strong&gt;Icons&lt;/strong&gt; section, you can choose to &lt;strong&gt;Show only the minimum safe area for maskable icons&lt;/strong&gt;. Your
icons will be trimmed so that only the safe area is visible. If your logo is visible within this
safe area, you&#39;re good to go.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Applications panel in DevTools displaying PWA icons with edges cropped&quot; decoding=&quot;async&quot; height=&quot;423&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 762px) 762px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/UeKTJM2SE0SQhgnnyaQG.png?auto=format&amp;w=1524 1524w&quot; width=&quot;762&quot; /&gt;
  &lt;figcaption&gt;The Applications panel.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To test your maskable icon with the variety of Android shapes, use the
&lt;a href=&quot;https://maskable.app/&quot; rel=&quot;noopener&quot;&gt;Maskable.app&lt;/a&gt; tool Tiger created.
Open an icon, then Maskable.app will let you
try various shapes and sizes, and you can share the preview with others on your team.&lt;/p&gt;
&lt;h2 id=&quot;how-do-i-adopt-maskable-icons&quot;&gt;How do I adopt maskable icons? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/maskable-icon/#how-do-i-adopt-maskable-icons&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you want to create a maskable icon based on an existing icon, you can use the
&lt;a href=&quot;https://maskable.app/editor&quot; rel=&quot;noopener&quot;&gt;Maskable.app Editor&lt;/a&gt;. Upload your icon, adjust the color and size,
then export the image.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Maskable.app Editor screenshot&quot; decoding=&quot;async&quot; height=&quot;569&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 670px) 670px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/MDXDwH3RWyj4po6daeXw.png?auto=format&amp;w=1340 1340w&quot; width=&quot;670&quot; /&gt;
  &lt;figcaption&gt;Creating icons in the Maskable.app Editor.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Once you&#39;ve created a maskable icon and tested it in DevTools, you&#39;ll need to update your
&lt;a href=&quot;https://web.dev/add-manifest/&quot;&gt;web app manifest&lt;/a&gt; to point to the
new asset. The web app manifest provides information about your web app in a JSON file, and
includes an &lt;a href=&quot;https://web.dev/add-manifest/#icons&quot;&gt;&lt;code&gt;icons&lt;/code&gt; array&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the inclusion of maskable icons, the &lt;code&gt;purpose&lt;/code&gt; field tells the browser how your icon
should be used. By default, icons will have a purpose of &lt;code&gt;&amp;quot;any&amp;quot;&lt;/code&gt;. These icons will be
resized on top of a white background on Android.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  …&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;icons&quot;&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    …&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;path/to/regular_icon.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;sizes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;196x196&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;purpose&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;any&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;path/to/maskable_icon.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;sizes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;196x196&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;purpose&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;maskable&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &amp;lt;-- New property value `&quot;maskable&quot;`&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    …&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  …&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Maskable icons should use a different value for &lt;code&gt;purpose&lt;/code&gt;: &lt;code&gt;&amp;quot;maskable&amp;quot;&lt;/code&gt;. This indicates
that an image is meant to be used with icon masks, giving you more control over the result.
This way, your icons will not have a white background. You can also specify multiple
space-separated purposes (for example, &lt;code&gt;&amp;quot;any maskable&amp;quot;&lt;/code&gt;), if you want your maskable icon to
be used without a mask on other devices.&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; While you &lt;em&gt;can&lt;/em&gt; specify multiple space-separated purposes such as &lt;code&gt;&amp;quot;any maskable&amp;quot;&lt;/code&gt;, in practice you &lt;em&gt;shouldn&#39;t&lt;/em&gt;. Using &lt;code&gt;&amp;quot;maskable&amp;quot;&lt;/code&gt; icons as &lt;code&gt;&amp;quot;any&amp;quot;&lt;/code&gt; icons is suboptimal as the icon is going to be used as-is, resulting in excess padding, making the core icon content smaller. Ideally, icons for the &lt;code&gt;&amp;quot;any&amp;quot;&lt;/code&gt; purpose should have transparent regions and no extra padding, like your site&#39;s favicons, since the browser isn&#39;t going to add that for them. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;With this, you can go forth and create your own maskable icons, making sure your app looks great
edge-to-edge (and for what it&#39;s worth, circle-to-circle, oval-to-oval 😄).&lt;/p&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/maskable-icon/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article was reviewed by &lt;a href=&quot;https://github.com/jpmedley&quot; rel=&quot;noopener&quot;&gt;Joe Medley&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Tiger Oakes</name>
    </author><author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>prefers-color-scheme: Hello darkness, my old friend</title>
    <link href="https://web.dev/prefers-color-scheme/"/>
    <updated>2019-06-27T00:00:00Z</updated>
    <id>https://web.dev/prefers-color-scheme/</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/prefers-color-scheme/#introduction&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; I have done a lot of background research on the history and theory of dark mode, if you are only interested in working with dark mode, feel free to &lt;a href=&quot;https://web.dev/prefers-color-scheme/#activating-dark-mode-in-the-operating-system&quot;&gt;skip the introduction&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;dark-mode-before-dark-mode&quot;&gt;Dark mode before &lt;em&gt;Dark Mode&lt;/em&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#dark-mode-before-dark-mode&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;figure data-float=&quot;right&quot;&gt;
  &lt;img alt=&quot;Green screen computer monitor&quot; decoding=&quot;async&quot; height=&quot;175&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 233px) 233px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/fmdRPm6K5SXiIRLgyz4y.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/fmdRPm6K5SXiIRLgyz4y.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/fmdRPm6K5SXiIRLgyz4y.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/fmdRPm6K5SXiIRLgyz4y.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/fmdRPm6K5SXiIRLgyz4y.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/fmdRPm6K5SXiIRLgyz4y.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/fmdRPm6K5SXiIRLgyz4y.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/fmdRPm6K5SXiIRLgyz4y.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/fmdRPm6K5SXiIRLgyz4y.jpg?auto=format&amp;w=466 466w&quot; width=&quot;233&quot; /&gt;
  &lt;figcaption&gt;Green screen (&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Compaq_Portable_and_Wordperfect.JPG&quot;&gt;Source&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We have gone full circle with dark mode.
In the dawn of personal computing, dark mode wasn&#39;t a matter of choice,
but a matter of fact:
Monochrome &lt;abbr title=&quot;Cathode-Ray Tube&quot;&gt;CRT&lt;/abbr&gt; computer monitors worked by firing electron beams
on a phosphorescent screen and the phosphor used in early CRTs was green.
Because text was displayed in green and the rest of the screen was black, these models were often referred to as
&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Schneider_CPC6128_with_green_monitor_GT65,_start_screen.jpg&quot; rel=&quot;noopener&quot;&gt;green screens&lt;/a&gt;.&lt;/p&gt;
&lt;figure data-float=&quot;left&quot;&gt;
  &lt;img alt=&quot;Dark-on-white word processing&quot; decoding=&quot;async&quot; height=&quot;175&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 222px) 222px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/l9oDlIO59oyJiXVegxIV.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/l9oDlIO59oyJiXVegxIV.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/l9oDlIO59oyJiXVegxIV.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/l9oDlIO59oyJiXVegxIV.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/l9oDlIO59oyJiXVegxIV.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/l9oDlIO59oyJiXVegxIV.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/l9oDlIO59oyJiXVegxIV.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/l9oDlIO59oyJiXVegxIV.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/l9oDlIO59oyJiXVegxIV.jpg?auto=format&amp;w=444 444w&quot; width=&quot;222&quot; /&gt;
  &lt;figcaption&gt;Dark-on-white (&lt;a href=&quot;https://www.youtube.com/watch?v=qKkABzt0Zqg&quot;&gt;Source&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The subsequently introduced Color CRTs displayed multiple colors
through the use of red, green, and blue phosphors.
They created white by activating all three phosphors simultaneously.
With the advent of more sophisticated &lt;abbr title=&quot;What You See Is What You Get&quot;&gt;WYSIWYG&lt;/abbr&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Desktop_publishing&quot; rel=&quot;noopener&quot;&gt;desktop publishing&lt;/a&gt;,
the idea of making the virtual document resemble a physical sheet of paper became popular.&lt;/p&gt;
&lt;figure data-float=&quot;right&quot;&gt;
  &lt;img alt=&quot;Dark-on-white webpage in the WorldWideWeb browser&quot; decoding=&quot;async&quot; height=&quot;175&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 233px) 233px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/lnuLLcQzIF7r08lt479k.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/lnuLLcQzIF7r08lt479k.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/lnuLLcQzIF7r08lt479k.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/lnuLLcQzIF7r08lt479k.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/lnuLLcQzIF7r08lt479k.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/lnuLLcQzIF7r08lt479k.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/lnuLLcQzIF7r08lt479k.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/lnuLLcQzIF7r08lt479k.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/lnuLLcQzIF7r08lt479k.png?auto=format&amp;w=466 466w&quot; width=&quot;233&quot; /&gt;
  &lt;figcaption&gt;The WorldWideWeb browser (&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:WorldWideWeb_FSF_GNU.png&quot;&gt;Source&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This is where &lt;em&gt;dark-on-white&lt;/em&gt; as a design trend started,
and this trend was carried over to the
&lt;a href=&quot;http://info.cern.ch/hypertext/WWW/TheProject.html&quot; rel=&quot;noopener&quot;&gt;early document-based web&lt;/a&gt;.
The first ever browser,
&lt;a href=&quot;https://en.wikipedia.org/wiki/WorldWideWeb&quot; rel=&quot;noopener&quot;&gt;WorldWideWeb&lt;/a&gt;
(remember,
&lt;a href=&quot;https://en.wikipedia.org/wiki/Cascading_Style_Sheets#History&quot; rel=&quot;noopener&quot;&gt;CSS wasn&#39;t even invented&lt;/a&gt; yet),
&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:WorldWideWeb_FSF_GNU.png&quot; rel=&quot;noopener&quot;&gt;displayed webpages&lt;/a&gt; this way.
Fun fact: the second ever browser,
&lt;a href=&quot;https://en.wikipedia.org/wiki/Line_Mode_Browser&quot; rel=&quot;noopener&quot;&gt;Line Mode Browser&lt;/a&gt;—a terminal-based browser—was
green on dark.
These days, web pages and web apps are typically designed with dark text
on a light background, a baseline assumption that is also hard-coded in user agent stylesheets, including
&lt;a href=&quot;https://chromium.googlesource.com/chromium/blink/+/master/Source/core/css/html.css&quot; rel=&quot;noopener&quot;&gt;Chrome&#39;s&lt;/a&gt;.&lt;/p&gt;
&lt;figure data-float=&quot;left&quot;&gt;
  &lt;img alt=&quot;Smartphone used while lying in bed&quot; decoding=&quot;async&quot; height=&quot;175&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 262px) 262px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/zCdyRdnAnbrB7aAB0TQi.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/zCdyRdnAnbrB7aAB0TQi.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/zCdyRdnAnbrB7aAB0TQi.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/zCdyRdnAnbrB7aAB0TQi.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/zCdyRdnAnbrB7aAB0TQi.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/zCdyRdnAnbrB7aAB0TQi.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/zCdyRdnAnbrB7aAB0TQi.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/zCdyRdnAnbrB7aAB0TQi.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/zCdyRdnAnbrB7aAB0TQi.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/zCdyRdnAnbrB7aAB0TQi.jpg?auto=format&amp;w=524 524w&quot; width=&quot;262&quot; /&gt;
  &lt;figcaption&gt;Smartphone used in bed (Source: Unsplash)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The days of CRTs are long over.
Content consumption and creation has shifted to mobile devices
that use backlit &lt;abbr title=&quot;Liquid Crystal Display&quot;&gt;LCD&lt;/abbr&gt;
or energy-saving &lt;abbr title=&quot;Active-Matrix Organic Light-Emitting Diode&quot;&gt;AMOLED&lt;/abbr&gt; screens.
Smaller and more transportable computers, tablets, and smartphones led to new usage patterns.
Leisure tasks like web browsing, coding for fun, and high-end gaming
frequently happen after-hours in dim environments.
People even enjoy their devices in their beds at night-time.
The more people use their devices in the dark,
the more the idea of going back to the roots of &lt;em&gt;light-on-dark&lt;/em&gt; becomes popular.&lt;/p&gt;
&lt;h3 id=&quot;why-dark-mode&quot;&gt;Why dark mode &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#why-dark-mode&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;dark-mode-for-aesthetic-reasons&quot;&gt;Dark mode for aesthetic reasons &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#dark-mode-for-aesthetic-reasons&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When people get asked
&lt;a href=&quot;https://medium.com/dev-channel/let-there-be-darkness-maybe-9facd9c3023d&quot; rel=&quot;noopener&quot;&gt;why they like or want dark mode&lt;/a&gt;,
the most popular response is that &lt;em&gt;&amp;quot;it&#39;s easier on the eyes,&amp;quot;&lt;/em&gt;
followed by &lt;em&gt;&amp;quot;it&#39;s elegant and beautiful.&amp;quot;&lt;/em&gt;
Apple in their
&lt;a href=&quot;https://developer.apple.com/documentation/appkit/supporting_dark_mode_in_your_interface&quot; rel=&quot;noopener&quot;&gt;Dark Mode developer documentation&lt;/a&gt;
explicitly writes: &lt;em&gt;&amp;quot;The choice of whether to enable a light or dark appearance
is an aesthetic one for most users, and might not relate to ambient lighting conditions.&amp;quot;&lt;/em&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; Read up more on &lt;a href=&quot;https://medium.com/dev-channel/let-there-be-darkness-maybe-9facd9c3023d&quot;&gt;user research regarding why people want dark mode and how they use it&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;figure data-float=&quot;right&quot;&gt;
  &lt;img alt=&quot;CloseView in Mac OS System 7 with &amp;quot;White on Black&amp;quot; mode&quot; decoding=&quot;async&quot; height=&quot;225&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 193px) 193px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/WZ9I5g1YGG6S1TjygEIq.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/WZ9I5g1YGG6S1TjygEIq.png?auto=format&amp;w=193 193w, https://web-dev.imgix.net/image/admin/WZ9I5g1YGG6S1TjygEIq.png?auto=format&amp;w=220 220w, https://web-dev.imgix.net/image/admin/WZ9I5g1YGG6S1TjygEIq.png?auto=format&amp;w=251 251w, https://web-dev.imgix.net/image/admin/WZ9I5g1YGG6S1TjygEIq.png?auto=format&amp;w=286 286w, https://web-dev.imgix.net/image/admin/WZ9I5g1YGG6S1TjygEIq.png?auto=format&amp;w=326 326w, https://web-dev.imgix.net/image/admin/WZ9I5g1YGG6S1TjygEIq.png?auto=format&amp;w=372 372w, https://web-dev.imgix.net/image/admin/WZ9I5g1YGG6S1TjygEIq.png?auto=format&amp;w=386 386w&quot; width=&quot;193&quot; /&gt;
  &lt;figcaption&gt;System&amp;nbsp;7 CloseView (&lt;a href=&quot;https://archive.org/details/mac_Macintosh_System_7_at_your_Fingertips_1992&quot;&gt;Source&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id=&quot;dark-mode-as-an-accessibility-tool&quot;&gt;Dark mode as an accessibility tool &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#dark-mode-as-an-accessibility-tool&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;There are also people who actually &lt;em&gt;need&lt;/em&gt; dark mode and use it as another accessibility tool,
for example, users with low vision.
The earliest occurrence of such an accessibility tool I could find is
&lt;a href=&quot;https://en.wikipedia.org/wiki/System_7&quot; rel=&quot;noopener&quot;&gt;System 7&lt;/a&gt;&#39;s &lt;em&gt;CloseView&lt;/em&gt; feature, which had a toggle for
&lt;em&gt;Black on White&lt;/em&gt; and &lt;em&gt;White on Black&lt;/em&gt;.
While System 7 supported color, the default user interface was still black-and-white.&lt;/p&gt;
&lt;p&gt;These inversion-based implementations demonstrated their weaknesses once color was introduced.
User research by Szpiro &lt;em&gt;et al.&lt;/em&gt; on
&lt;a href=&quot;https://dl.acm.org/citation.cfm?id=2982168&quot; rel=&quot;noopener&quot;&gt;how people with low vision access computing devices&lt;/a&gt;
showed that all interviewed users disliked inverted images,
but that many preferred light text on a dark background.
Apple accommodates for this user preference with a feature called
&lt;a href=&quot;https://www.apple.com//accessibility/iphone/vision/&quot; rel=&quot;noopener&quot;&gt;Smart Invert&lt;/a&gt;,
which reverses the colors on the display, except for images, media,
and some apps that use dark color styles.&lt;/p&gt;
&lt;p&gt;A special form of low vision is Computer Vision Syndrome, also known as Digital Eye Strain, which is
&lt;a href=&quot;https://onlinelibrary.wiley.com/doi/full/10.1111/j.1475-1313.2011.00834.x&quot; rel=&quot;noopener&quot;&gt;defined&lt;/a&gt;
as &lt;em&gt;&amp;quot;the combination of eye and vision problems associated with the use of computers
(including desktop, laptop, and tablets) and other electronic displays (e.g.
smartphones and electronic reading devices).&amp;quot;&lt;/em&gt;
It has been &lt;a href=&quot;https://bmjopen.bmj.com/content/5/1/e006748&quot; rel=&quot;noopener&quot;&gt;proposed&lt;/a&gt;
that the use of electronic devices by adolescents, particularly at night time,
leads to an increased risk of shorter sleep duration,
longer sleep-onset latency, and increased sleep deficiency.
Additionally, exposure to blue light has been widely
&lt;a href=&quot;https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4254760/&quot; rel=&quot;noopener&quot;&gt;reported&lt;/a&gt;
to be involved in the regulation of
&lt;a href=&quot;https://en.wikipedia.org/wiki/Circadian_rhythm&quot; rel=&quot;noopener&quot;&gt;circadian rhythm&lt;/a&gt;
and the sleep cycle,
and irregular light environments may lead to sleep deprivation,
possibly affecting mood and task performance, according to
&lt;a href=&quot;https://www.college-optometrists.org/oip-resource/computer-vision-syndrome--a-k-a--digital-eye-strain.html&quot; rel=&quot;noopener&quot;&gt;research by Rosenfield&lt;/a&gt;.
To limit these negative effects, reducing blue light by adjusting the display color temperature
through features like iOS&#39; &lt;a href=&quot;https://support.apple.com/en-us/HT207570&quot; rel=&quot;noopener&quot;&gt;Night Shift&lt;/a&gt; or Android&#39;s
&lt;a href=&quot;https://support.google.com/pixelphone/answer/7169926?&quot; rel=&quot;noopener&quot;&gt;Night Light&lt;/a&gt; can help,
as well as avoiding bright lights or irregular lights in general through dark themes or dark modes.&lt;/p&gt;
&lt;h4 id=&quot;dark-mode-power-savings-on-amoled-screens&quot;&gt;Dark mode power savings on AMOLED screens &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#dark-mode-power-savings-on-amoled-screens&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Finally, dark mode is known to save a &lt;em&gt;lot&lt;/em&gt; of energy on
&lt;abbr title=&quot;Active-Matrix Organic Light-Emitting Diode&quot;&gt;AMOLED&lt;/abbr&gt; screens.
Android case studies that focused on popular Google apps
like YouTube have shown that the power savings can be up to 60%.
The video below has more details on these case studies and the power savings per app.&lt;/p&gt;
&lt;figure data-size=&quot;full&quot;&gt;
  &lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;N_6sPd0Jd3g&quot; videoStartAt=&quot;305&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;activating-dark-mode-in-the-operating-system&quot;&gt;Activating dark mode in the operating system &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#activating-dark-mode-in-the-operating-system&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that I have covered the background of why dark mode is such a big deal for many users,
let&#39;s review how you can support it.&lt;/p&gt;
&lt;figure data-float=&quot;left&quot;&gt;
  &lt;img alt=&quot;Android Q dark mode settings&quot; decoding=&quot;async&quot; height=&quot;250&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 218px) 218px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/Yh6SEoWDK1SbqcGjlL6d.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/Yh6SEoWDK1SbqcGjlL6d.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/Yh6SEoWDK1SbqcGjlL6d.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/Yh6SEoWDK1SbqcGjlL6d.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/Yh6SEoWDK1SbqcGjlL6d.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/Yh6SEoWDK1SbqcGjlL6d.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/Yh6SEoWDK1SbqcGjlL6d.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/Yh6SEoWDK1SbqcGjlL6d.png?auto=format&amp;w=436 436w&quot; width=&quot;218&quot; /&gt;
  &lt;figcaption&gt;Android&amp;nbsp;Q dark theme settings&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Operating systems that support a dark mode or dark theme
typically have an option to activate it somewhere in the settings.
On macOS X, it&#39;s in the system preference&#39;s &lt;em&gt;General&lt;/em&gt; section and called &lt;em&gt;Appearance&lt;/em&gt; (&lt;a href=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/lUAnDhiGiZxigDbCqfn1.png?auto=format&quot;&gt;screenshot&lt;/a&gt;),
and on Windows 10, it&#39;s in the &lt;em&gt;Colors&lt;/em&gt; section and called &lt;em&gt;Choose your color&lt;/em&gt; (&lt;a href=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ahr8nkFttRPCe4RH8IEk.png?auto=format&quot;&gt;screenshot&lt;/a&gt;).
For Android Q, you can find it under &lt;em&gt;Display&lt;/em&gt; as a &lt;em&gt;Dark Theme&lt;/em&gt; toggle switch (&lt;a href=&quot;https://web-dev.imgix.net/image/admin/Yh6SEoWDK1SbqcGjlL6d.png?auto=format&quot;&gt;screenshot&lt;/a&gt;),
and on iOS 13, you can change the &lt;em&gt;Appearance&lt;/em&gt; in the &lt;em&gt;Display &amp;amp; Brightness&lt;/em&gt;
section of the settings (&lt;a href=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/K0QTu4Elw1ETabtoJjZ1.jpg?auto=format&quot;&gt;screenshot&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id=&quot;the-prefers-color-scheme-media-query&quot;&gt;The &lt;code&gt;prefers-color-scheme&lt;/code&gt; media query &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#the-prefers-color-scheme-media-query&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One last bit of theory before I get going.
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/Media_Queries/Using_media_queries&quot; rel=&quot;noopener&quot;&gt;Media queries&lt;/a&gt;
allow authors to test and query values or features of the user agent or display device,
independent of the document being rendered.
They are used in the CSS &lt;code&gt;@media&lt;/code&gt; rule to conditionally apply styles to a document,
and in various other contexts and languages, such as HTML and JavaScript.
&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/&quot; rel=&quot;noopener&quot;&gt;Media Queries Level 5&lt;/a&gt;
introduces so-called user preference media features, that is,
a way for sites to detect the user&#39;s preferred way to display content.&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; ☝️ An established user preference media feature is &lt;code&gt;prefers-reduced-motion&lt;/code&gt; that lets you detect the desire for less motion on a page. I have &lt;a href=&quot;https://web.dev/prefers-reduced-motion/&quot;&gt;written about &lt;code&gt;prefers-reduced-motion&lt;/code&gt;&lt;/a&gt; before. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;The &lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;prefers-color-scheme&lt;/code&gt;&lt;/a&gt;
media feature is used to detect
if the user has requested the page to use a light or dark color theme.
It works with the following values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;light&lt;/code&gt;:
Indicates that the user has notified the system that they prefer a page that has a light theme
(dark text on light background).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dark&lt;/code&gt;:
Indicates that the user has notified the system that they prefer a page that has a dark theme
(light text on dark background).&lt;/li&gt;
&lt;/ul&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; An earlier version of the spec included a third value, &lt;code&gt;no-preference&lt;/code&gt;. It was meant to indicate that the user has made no preference known to the system. Since no browser ever implemented it, the value was &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/3857#issuecomment-634779976&quot;&gt;removed&lt;/a&gt; from the spec. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;supporting-dark-mode&quot;&gt;Supporting dark mode &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#supporting-dark-mode&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;finding-out-if-dark-mode-is-supported-by-the-browser&quot;&gt;Finding out if dark mode is supported by the browser &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#finding-out-if-dark-mode-is-supported-by-the-browser&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As dark mode is reported through a media query, you can easily check if the current browser
supports dark mode by checking if the media query &lt;code&gt;prefers-color-scheme&lt;/code&gt; matches at all.
Note how I don&#39;t include any value, but purely check if the media query alone matches.&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;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matchMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(prefers-color-scheme)&#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;media &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;not all&#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;  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;🎉 Dark mode is supported&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;At the time of writing, &lt;code&gt;prefers-color-scheme&lt;/code&gt; is supported on both desktop and mobile (where available)
by Chrome and Edge as of version 76, Firefox as of version 67,
and Safari as of version 12.1 on macOS and as of version 13 on iOS.
For all other browsers, you can check the &lt;a href=&quot;https://caniuse.com/#feat=prefers-color-scheme&quot; rel=&quot;noopener&quot;&gt;Can I use support tables&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; There is a custom element &lt;a href=&quot;https://github.com/GoogleChromeLabs/dark-mode-toggle&quot;&gt;&lt;code&gt;&amp;lt;dark-mode-toggle&amp;gt;&lt;/code&gt;&lt;/a&gt; available that adds dark mode support to older browsers. I write about it &lt;a href=&quot;https://web.dev/prefers-color-scheme/#the-lessdark-mode-togglegreater-custom-element&quot;&gt;further down in this article&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;h3 id=&quot;learning-about-a-users-preferences-at-request-time&quot;&gt;Learning about a user&#39;s preferences at request time &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#learning-about-a-users-preferences-at-request-time&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/user-preference-media-features-headers/&quot;&gt;&lt;code&gt;Sec-CH-Prefers-Color-Scheme&lt;/code&gt;&lt;/a&gt; client hint header
allows sites to obtain the user&#39;s color scheme preferences optionally at request time,
allowing servers to inline the right CSS and therefore avoid a flash of incorrect color theme.&lt;/p&gt;
&lt;h3 id=&quot;dark-mode-in-practice&quot;&gt;Dark mode in practice &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#dark-mode-in-practice&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let&#39;s finally see how supporting dark mode looks like in practice.
Just like with the &lt;a href=&quot;https://en.wikipedia.org/wiki/Highlander_(film)&quot; rel=&quot;noopener&quot;&gt;Highlander&lt;/a&gt;,
with dark mode &lt;em&gt;there can be only one&lt;/em&gt;: dark or light, but never both!
Why do I mention this? Because this fact should have an impact on the loading strategy.
&lt;strong&gt;Please don&#39;t force users to download CSS in the critical rendering path
that is for a mode they don&#39;t currently use.&lt;/strong&gt;
To optimize load speed, I have therefore split my CSS for the example app
that shows the following recommendations in practice
into three parts in order to &lt;a href=&quot;https://web.dev/defer-non-critical-css/&quot;&gt;defer non-critical CSS&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;style.css&lt;/code&gt; that contains generic rules that are used universally on the site.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dark.css&lt;/code&gt; that contains only the rules needed for dark mode.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;light.css&lt;/code&gt; that contains only the rules needed for light mode.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;loading-strategy&quot;&gt;Loading strategy &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#loading-strategy&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The two latter ones, &lt;code&gt;light.css&lt;/code&gt; and &lt;code&gt;dark.css&lt;/code&gt;,
are loaded conditionally with a &lt;code&gt;&amp;lt;link media&amp;gt;&lt;/code&gt; query.
Initially,
&lt;a href=&quot;https://caniuse.com/#feat=prefers-color-scheme&quot; rel=&quot;noopener&quot;&gt;not all browsers will support &lt;code&gt;prefers-color-scheme&lt;/code&gt;&lt;/a&gt;
(detectable using the &lt;a href=&quot;https://web.dev/prefers-color-scheme/#finding-out-if-dark-mode-is-supported-by-the-browser&quot;&gt;pattern above&lt;/a&gt;),
which I deal with dynamically by loading the default &lt;code&gt;light.css&lt;/code&gt; file
via a conditionally inserted &lt;code&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot;&amp;gt;&lt;/code&gt; element in a minuscule inline script
(light is an arbitrary choice, I could also have made dark the default fallback experience).
To avoid a &lt;a href=&quot;https://en.wikipedia.org/wiki/Flash_of_unstyled_content&quot; rel=&quot;noopener&quot;&gt;flash of unstyled content&lt;/a&gt;,
I hide the content of the page until &lt;code&gt;light.css&lt;/code&gt; has loaded.&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;script&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 class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// If `prefers-color-scheme` is not supported, fall back to light mode.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// In this case, light.css will be downloaded with `highest` priority.&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;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matchMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(prefers-color-scheme: dark)&#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;media &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;not all&#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;documentElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;display &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;none&#39;&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;head&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertAdjacentHTML&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;beforeend&#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;&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;/light.css&quot; onload=&quot;document.documentElement.style.display = \&#39;\&#39;&quot;&gt;&#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;  &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;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 comment&quot;&gt;&amp;lt;!--&lt;br /&gt;  Conditionally either load the light or the dark stylesheet. The matching file&lt;br /&gt;  will be downloaded with `highest`, the non-matching file with `lowest`&lt;br /&gt;  priority. If the browser doesn&#39;t support `prefers-color-scheme`, the media&lt;br /&gt;  query is unknown and the files are downloaded with `lowest` priority (but&lt;br /&gt;  above I already force `highest` priority for my default light experience).&lt;br /&gt;--&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;stylesheet&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;/dark.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&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;(prefers-color-scheme: dark)&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;br /&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;stylesheet&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;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;/light.css&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;(prefers-color-scheme: light)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- The main stylesheet --&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;stylesheet&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;/style.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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;stylesheet-architecture&quot;&gt;Stylesheet architecture &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#stylesheet-architecture&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I make maximum use of &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/var&quot; rel=&quot;noopener&quot;&gt;CSS variables&lt;/a&gt;,
this allows my generic &lt;code&gt;style.css&lt;/code&gt; to be, well, generic,
and all the light or dark mode customization happens in the two other files &lt;code&gt;dark.css&lt;/code&gt; and &lt;code&gt;light.css&lt;/code&gt;.
Below you can see an excerpt of the actual styles, but it should suffice to convey the overall idea.
I declare two variables, &lt;code&gt;-⁠-⁠color&lt;/code&gt; and &lt;code&gt;-⁠-⁠background-color&lt;/code&gt;
that essentially create a &lt;em&gt;dark-on-light&lt;/em&gt; and a &lt;em&gt;light-on-dark&lt;/em&gt; baseline theme.&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 comment&quot;&gt;/* light.css: 👉 dark-on-light */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;:root&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; &lt;span class=&quot;token function&quot;&gt;rgb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;5&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 5&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 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 property&quot;&gt;--background-color&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;250&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 250&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 250&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;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* dark.css: 👉 light-on-dark */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;:root&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; &lt;span class=&quot;token function&quot;&gt;rgb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;250&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 250&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 250&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;--background-color&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;5&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 5&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;In my &lt;code&gt;style.css&lt;/code&gt;, I then use these variables in the &lt;code&gt;body { … }&lt;/code&gt; rule.
As they are defined on the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/:root&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;:root&lt;/code&gt; CSS pseudo-class&lt;/a&gt;—a
selector that in HTML represents the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; element
and is identical to the selector &lt;code&gt;html&lt;/code&gt;, except that its specificity is
higher—they cascade down, which serves me for declaring global CSS variables.&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 comment&quot;&gt;/* style.css */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;:root&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-scheme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; light dark&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 selector&quot;&gt;body&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; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--color&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;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--background-color&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;In the code sample above, you will probably have noticed a property
&lt;a href=&quot;https://drafts.csswg.org/css-color-adjust-1/#propdef-color-scheme&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;color-scheme&lt;/code&gt;&lt;/a&gt;
with the space-separated value &lt;code&gt;light dark&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This tells the browser which color themes my app supports
and allows it to activate special variants of the user agent stylesheet,
which is useful to, for example, let the browser render form fields
with a dark background and light text, adjust the scroll bars,
or to enable a theme-aware highlight color.
The exact details of &lt;code&gt;color-scheme&lt;/code&gt; are specified in
&lt;a href=&quot;https://drafts.csswg.org/css-color-adjust-1/&quot; rel=&quot;noopener&quot;&gt;CSS Color Adjustment Module Level 1&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; 🌒 Read up more on &lt;a href=&quot;https://web.dev/color-scheme/&quot;&gt;what &lt;code&gt;color-scheme&lt;/code&gt; actually does&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Everything else is then just a matter of defining CSS variables
for things that matter on my site.
Semantically organizing styles helps a lot when working with dark mode.
For example, rather than &lt;code&gt;-⁠-⁠highlight-yellow&lt;/code&gt;, consider calling the variable
&lt;code&gt;-⁠-⁠accent-color&lt;/code&gt;, as &amp;quot;yellow&amp;quot; may actually not be yellow in dark mode or vice versa.
Below is an example of some more variables that I use in my 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 comment&quot;&gt;/* dark.css */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;:root&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; &lt;span class=&quot;token function&quot;&gt;rgb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;250&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 250&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 250&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;--background-color&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;5&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 5&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 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 property&quot;&gt;--link-color&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;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 188&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 212&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;--main-headline-color&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;233&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 30&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 99&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;--accent-background-color&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;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 188&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 212&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;--accent-color&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;5&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 5&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 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;/code&gt;&lt;/pre&gt;
&lt;/div&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 comment&quot;&gt;/* light.css */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;:root&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; &lt;span class=&quot;token function&quot;&gt;rgb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;5&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 5&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 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 property&quot;&gt;--background-color&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;250&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 250&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 250&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;--link-color&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;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 238&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;--main-headline-color&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;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 192&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;--accent-background-color&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;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 238&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;--accent-color&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;250&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 250&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 250&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;full-example&quot;&gt;Full example &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#full-example&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the following &lt;a href=&quot;https://dark-mode-baseline.glitch.me/&quot; rel=&quot;noopener&quot;&gt;Glitch&lt;/a&gt; embed,
you can see the complete example that puts the concepts from above into practice.
Try toggling dark mode in your particular &lt;a href=&quot;https://web.dev/prefers-color-scheme/#activating-dark-mode-in-the-operating-system&quot;&gt;operating system&#39;s settings&lt;/a&gt;
and see how the page reacts.&lt;/p&gt;
&lt;div style=&quot;height: 900px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;geolocation; microphone; camera; midi; vr; encrypted-media&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/dark-mode-baseline?path=style.css&amp;previewSize=100&amp;attributionHidden=true&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;IFrame content&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h3 id=&quot;loading-impact&quot;&gt;Loading impact &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#loading-impact&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When you play with this example, you can see
why I load my &lt;code&gt;dark.css&lt;/code&gt; and &lt;code&gt;light.css&lt;/code&gt; via media queries.
Try toggling dark mode and reload the page:
the particular currently non-matching stylesheets are still loaded, but with the lowest priority,
so that they never compete with resources that are needed by the site right now.&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; 😲 Read up more on &lt;a href=&quot;https://blog.tomayac.com/2018/11/08/why-browsers-download-stylesheets-with-non-matching-media-queries-180513&quot;&gt;why browsers download stylesheets with non-matching media queries&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Network loading diagram showing how in light mode the dark mode CSS gets loaded with lowest priority&quot; decoding=&quot;async&quot; height=&quot;417&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/flTdLliru6GmqqlOKjNx.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Site in light mode loads the dark mode CSS with lowest priority.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Network loading diagram showing how in dark mode the light mode CSS gets loaded with lowest priority&quot; decoding=&quot;async&quot; height=&quot;417&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/IDs6Le0VBhHu9QEDdxL6.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Site in dark mode loads the light mode CSS with lowest priority.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Network loading diagram showing how in default light mode the dark mode CSS gets loaded with lowest priority&quot; decoding=&quot;async&quot; height=&quot;417&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/zJqu5k3TIgcZf1OHWWIq.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;Site in default light mode on a browser that doesn&#39;t support &lt;code&gt;prefers-color-scheme&lt;/code&gt; loads the dark mode CSS with lowest priority.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;reacting-on-dark-mode-changes&quot;&gt;Reacting on dark mode changes &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#reacting-on-dark-mode-changes&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Like any other media query change, dark mode changes can be subscribed to via JavaScript.
You can use this to, for example, dynamically change the
&lt;a href=&quot;https://developers.google.com/web/fundamentals/design-and-ux/browser-customization/#provide_great_icons_tiles&quot; rel=&quot;noopener&quot;&gt;favicon&lt;/a&gt;
of a page or change the
&lt;a href=&quot;https://developers.google.com/web/fundamentals/design-and-ux/browser-customization/#meta_theme_color_for_chrome_and_opera&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;meta name=&amp;quot;theme-color&amp;quot;&amp;gt;&lt;/code&gt;&lt;/a&gt;
that determines the color of the URL bar in Chrome.
The &lt;a href=&quot;https://web.dev/prefers-color-scheme/#full-example&quot;&gt;full example&lt;/a&gt; above shows this in action,
in order to see the theme color and favicon changes, open the
&lt;a href=&quot;https://dark-mode-baseline.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo in a separate tab&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 keyword&quot;&gt;const&lt;/span&gt; darkModeMediaQuery &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matchMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(prefers-color-scheme: dark)&#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;darkModeMediaQuery&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;change&#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 parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; darkModeOn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matches&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 template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Dark mode is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;darkModeOn &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;🌒 on&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;☀️ off&#39;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As of Chromium 93 and Safari 15, you can adjust the color based on a
media query with the &lt;code&gt;media&lt;/code&gt; attribute of the &lt;code&gt;meta&lt;/code&gt; theme color element. The
first one that matches will be picked. For example, you could have one color for
light mode and another one for dark mode. At the time of writing, you can&#39;t
define those in your manifest. See &lt;a href=&quot;https://github.com/w3c/manifest/issues/975&quot; rel=&quot;noopener&quot;&gt;w3c/manifest#975 GitHub
issue&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;meta&lt;/span&gt;&lt;br /&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;theme-color&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;(prefers-color-scheme: light)&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;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;white&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&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;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;theme-color&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&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;(prefers-color-scheme: dark)&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;black&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;h2 id=&quot;debugging-and-testing-dark-mode&quot;&gt;Debugging and testing dark mode &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#debugging-and-testing-dark-mode&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;emulating-prefers-color-scheme-in-devtools&quot;&gt;Emulating &lt;code&gt;prefers-color-scheme&lt;/code&gt; in DevTools &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#emulating-prefers-color-scheme-in-devtools&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Switching the entire operating system&#39;s color scheme can get annoying real quick,
so Chrome DevTools now allows you to emulate the user&#39;s preferred color scheme
in a way that only affects the currently visible tab.
Open the &lt;a href=&quot;https://developer.chrome.com/docs/devtools/command-menu/&quot; rel=&quot;noopener&quot;&gt;Command Menu&lt;/a&gt;, start typing &lt;code&gt;Rendering&lt;/code&gt;, run the &lt;code&gt;Show Rendering&lt;/code&gt; command, and then change the &lt;strong&gt;Emulate CSS media feature prefers-color-scheme&lt;/strong&gt; option.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A screenshot of the &amp;#x27;Emulate CSS media feature prefers-color-scheme&amp;#x27; option that is located in the Rendering tab of Chrome DevTools&quot; decoding=&quot;async&quot; height=&quot;552&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/RIq2z6Ja1zSzfNTHic5z.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;screenshotting-prefers-color-scheme-with-puppeteer&quot;&gt;Screenshotting &lt;code&gt;prefers-color-scheme&lt;/code&gt; with Puppeteer &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#screenshotting-prefers-color-scheme-with-puppeteer&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/GoogleChrome/puppeteer/&quot; rel=&quot;noopener&quot;&gt;Puppeteer&lt;/a&gt; is a Node.js library
that provides a high-level API to control Chrome or Chromium over the
&lt;a href=&quot;https://chromedevtools.github.io/devtools-protocol/&quot; rel=&quot;noopener&quot;&gt;DevTools Protocol&lt;/a&gt;.
With &lt;a href=&quot;https://www.npmjs.com/package/dark-mode-screenshot&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;dark-mode-screenshot&lt;/code&gt;&lt;/a&gt;, we provide
a Puppeteer script that lets you create screenshots of your pages in both dark and light mode.
You can run this script as a one-off, or alternatively make it part of your
Continuous Integration (CI) test suite.&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx dark-mode-screenshot --url https://googlechromelabs.github.io/dark-mode-toggle/demo/ --output screenshot --fullPage --pause &lt;span class=&quot;token number&quot;&gt;750&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;dark-mode-best-practices&quot;&gt;Dark mode best practices &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#dark-mode-best-practices&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;avoid-pure-white&quot;&gt;Avoid pure white &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#avoid-pure-white&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A small detail you may have noticed is that I don&#39;t use pure white.
Instead, to prevent glowing and bleeding against the surrounding dark content,
I choose a slightly darker white. Something like &lt;code&gt;rgb(250, 250, 250)&lt;/code&gt; works well.&lt;/p&gt;
&lt;h3 id=&quot;re-colorize-and-darken-photographic-images&quot;&gt;Re-colorize and darken photographic images &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#re-colorize-and-darken-photographic-images&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you compare the two screenshots below, you will notice that not only the core theme has changed
from &lt;em&gt;dark-on-light&lt;/em&gt; to &lt;em&gt;light-on-dark&lt;/em&gt;, but that also the hero image looks slightly different.
My &lt;a href=&quot;https://medium.com/dev-channel/re-colorization-for-dark-mode-19e2e17b584b&quot; rel=&quot;noopener&quot;&gt;user research&lt;/a&gt;
has shown that the majority of the surveyed people
prefer slightly less vibrant and brilliant images when dark mode is active.
I refer to this as &lt;em&gt;re-colorization&lt;/em&gt;.&lt;/p&gt;
&lt;div class=&quot;switcher&quot;&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;Hero image slightly darkened in dark mode.&quot; decoding=&quot;async&quot; height=&quot;618&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/qzzYCKNSwoJr9BBEQlR7.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;
      Hero image slightly darkened in dark mode.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;Regular hero image in light mode.&quot; decoding=&quot;async&quot; height=&quot;618&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/41RbLRZ5wzkoVnIRJkNl.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
    &lt;figcaption&gt;
      Regular hero image in light mode.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Re-colorization can be achieved through a CSS filter on my images.
I use a CSS selector that matches all images that don&#39;t have &lt;code&gt;.svg&lt;/code&gt; in their URL,
the idea being that I can give vector graphics (icons) a different re-colorization treatment
than my images (photos), more about this in the &lt;a href=&quot;https://web.dev/prefers-color-scheme/#vector-graphics-and-icons&quot;&gt;next paragraph&lt;/a&gt;.
Note how I again use a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/var&quot; rel=&quot;noopener&quot;&gt;CSS variable&lt;/a&gt;,
so I can later on flexibly change my filter.&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; 🎨 Read up more on &lt;a href=&quot;https://medium.com/dev-channel/re-colorization-for-dark-mode-19e2e17b584b&quot;&gt;user research regarding re-colorization preferences with dark mode&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;As re-colorization is only needed in dark mode, that is, when &lt;code&gt;dark.css&lt;/code&gt; is active,
there are no corresponding rules in &lt;code&gt;light.css&lt;/code&gt;.&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 comment&quot;&gt;/* dark.css */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token property&quot;&gt;--image-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grayscale&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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;img:not([src*=&#39;.svg&#39;])&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;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--image-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;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h4 id=&quot;customizing-dark-mode-re-colorization-intensities-with-javascript&quot;&gt;Customizing dark mode re-colorization intensities with JavaScript &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#customizing-dark-mode-re-colorization-intensities-with-javascript&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Not everyone is the same and people have different dark mode needs.
By sticking to the re-colorization method described above,
I can easily make the grayscale intensity a user preference that I can
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/Using_CSS_custom_properties#Values_in_JavaScript&quot; rel=&quot;noopener&quot;&gt;change via JavaScript&lt;/a&gt;,
and by setting a value of &lt;code&gt;0%&lt;/code&gt;, I can also disable re-colorization completely.
Note that &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Document/documentElement&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;document.documentElement&lt;/code&gt;&lt;/a&gt;
provides a reference to the root element of the document,
that is, the same element I can reference with the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/:root&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;:root&lt;/code&gt; CSS pseudo-class&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 keyword&quot;&gt;const&lt;/span&gt; filter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;grayscale(70%)&#39;&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;documentElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setProperty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;--image-filter&#39;&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h3 id=&quot;invert-vector-graphics-and-icons&quot;&gt;Invert vector graphics and icons &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#invert-vector-graphics-and-icons&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For vector graphics—that in my case are used as icons that I reference via &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements—I
use a different re-colorization method.
While &lt;a href=&quot;https://dl.acm.org/citation.cfm?id=2982168&quot; rel=&quot;noopener&quot;&gt;research&lt;/a&gt; has shown
that people don&#39;t like inversion for photos, it does work very well for most icons.
Again I use CSS variables to determine the inversion amount
in the regular and in the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/:hover&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;:hover&lt;/code&gt;&lt;/a&gt; state.&lt;/p&gt;
&lt;div class=&quot;switcher&quot;&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;Icons are inverted in dark mode.&quot; decoding=&quot;async&quot; height=&quot;48&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 744px) 744px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/JGYFpAPi4233HrEKTQZp.png?auto=format&amp;w=1488 1488w&quot; width=&quot;744&quot; /&gt;
    &lt;figcaption&gt;
      Icons are inverted in dark mode.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;Regular icons in light mode.&quot; decoding=&quot;async&quot; height=&quot;48&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 744px) 744px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/W8AWbuqWthI6CfFsYunk.png?auto=format&amp;w=1488 1488w&quot; width=&quot;744&quot; /&gt;
    &lt;figcaption&gt;
      Regular icons in light mode.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Note how again I only invert icons in &lt;code&gt;dark.css&lt;/code&gt; but not in &lt;code&gt;light.css&lt;/code&gt;, and how &lt;code&gt;:hover&lt;/code&gt;
gets a different inversion intensity in the two cases to make the icon appear
slightly darker or slightly brighter, dependent on the mode the user has selected.&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 comment&quot;&gt;/* dark.css */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token property&quot;&gt;--icon-filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;100%&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;--icon-filter_hover&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;40%&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 selector&quot;&gt;img[src*=&#39;.svg&#39;]&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;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--icon-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;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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 comment&quot;&gt;/* light.css */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token property&quot;&gt;--icon-filter_hover&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;invert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;60%&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;div&gt;&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* style.css */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;img[src*=&#39;.svg&#39;]: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;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--icon-filter_hover&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;use-currentcolor-for-inline-svgs&quot;&gt;Use &lt;code&gt;currentColor&lt;/code&gt; for inline SVGs &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#use-currentcolor-for-inline-svgs&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For &lt;em&gt;inline&lt;/em&gt; SVG images, instead of &lt;a href=&quot;https://web.dev/prefers-color-scheme/#invert-vector-graphics-and-icons&quot;&gt;using inversion filters&lt;/a&gt;,
you can leverage the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/CSS/color_value#currentColor_keyword&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;currentColor&lt;/code&gt;&lt;/a&gt;
CSS keyword that represents the value of an element&#39;s &lt;code&gt;color&lt;/code&gt; property.
This lets you use the &lt;code&gt;color&lt;/code&gt; value on properties that do not receive it by default.
Conveniently, if &lt;code&gt;currentColor&lt;/code&gt; is used as the value of the SVG
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/SVG/Tutorial/Fills_and_Strokes#Fill_and_Stroke_Attributes&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;fill&lt;/code&gt; or &lt;code&gt;stroke&lt;/code&gt; attributes&lt;/a&gt;,
it instead takes its value from the inherited value of the color property.
Even better: this also works for
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/SVG/Element/use&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;svg&amp;gt;&amp;lt;use href=&amp;quot;…&amp;quot;&amp;gt;&amp;lt;/svg&amp;gt;&lt;/code&gt;&lt;/a&gt;,
so you can have separate resources
and &lt;code&gt;currentColor&lt;/code&gt; will still be applied in context.
Please note that this only works for &lt;em&gt;inline&lt;/em&gt; or &lt;code&gt;&amp;lt;use href=&amp;quot;…&amp;quot;&amp;gt;&lt;/code&gt; SVGs,
but not SVGs that are referenced as the &lt;code&gt;src&lt;/code&gt; of an image or somehow via CSS.
You can see this applied in the demo below.&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;highlight-line&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Some inline SVG --&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;svg&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;xmlns&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://www.w3.org/2000/svg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token attr-name&quot;&gt;stroke&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;currentColor&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  […]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&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;svg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;div style=&quot;height: 950px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;geolocation; microphone; camera; midi; vr; encrypted-media&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/dark-mode-currentcolor?path=light.css&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;dark-mode-currentcolor on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h3 id=&quot;smooth-transitions-between-modes&quot;&gt;Smooth transitions between modes &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#smooth-transitions-between-modes&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Switching from dark mode to light mode or vice versa can be smoothed thanks to the fact
that both &lt;code&gt;color&lt;/code&gt; and &lt;code&gt;background-color&lt;/code&gt; are
&lt;a href=&quot;https://www.quackit.com/css/css3/animations/animatable_properties/&quot; rel=&quot;noopener&quot;&gt;animatable CSS properties&lt;/a&gt;.
Creating the animation is as easy as declaring two &lt;code&gt;transition&lt;/code&gt;s for the two properties.
The example below illustrates the overall idea, you can experience it live in the
&lt;a href=&quot;https://dark-mode-baseline.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt;.&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;body&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;--duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0.5s&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;--timing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ease&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&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; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--color&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;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--background-color&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 property&quot;&gt;transition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; color &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--duration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--timing&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; background-color &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        --duration&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--timing&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;art-direction-with-dark-mode&quot;&gt;Art direction with dark mode &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#art-direction-with-dark-mode&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While for loading performance reasons in general I recommend to exclusively work with &lt;code&gt;prefers-color-scheme&lt;/code&gt;
in the &lt;code&gt;media&lt;/code&gt; attribute of &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; elements (rather than inline in stylesheets),
there are situations where you actually may want to work with &lt;code&gt;prefers-color-scheme&lt;/code&gt; directly inline in your HTML code.
Art direction is such a situation.
On the web, art direction deals with the overall visual appearance of a page and how it communicates visually,
stimulates moods, contrasts features, and psychologically appeals to a target audience.&lt;/p&gt;
&lt;p&gt;With dark mode, it&#39;s up to the judgment of the designer to decide what is the best image at a particular mode
and whether &lt;a href=&quot;https://web.dev/prefers-color-scheme/#photographic-images&quot;&gt;re-colorization of images&lt;/a&gt; is maybe &lt;em&gt;not&lt;/em&gt; good enough.
If used with the &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element, the &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; of the image to be shown can be made dependent on the &lt;code&gt;media&lt;/code&gt; attribute.
In the example below, I show the Western hemisphere for dark mode, and the Eastern hemisphere for light mode
or when no preference is given, defaulting to the Eastern hemisphere in all other cases.
This is of course purely for illustrative purposes.
Toggle dark mode on your device to see the difference.&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;picture&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;source&lt;/span&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;western.webp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&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;(prefers-color-scheme: dark)&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;source&lt;/span&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;eastern.webp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&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;(prefers-color-scheme: light)&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;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;eastern.webp&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;picture&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;div style=&quot;height: 600px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;geolocation; microphone; camera; midi; vr; encrypted-media&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/dark-mode-picture?path=index.html&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;dark-mode-picture on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h3 id=&quot;dark-mode,-but-add-an-opt-out&quot;&gt;Dark mode, but add an opt-out &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#dark-mode,-but-add-an-opt-out&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As mentioned in the &lt;a href=&quot;https://web.dev/prefers-color-scheme/#why-dark-mode&quot;&gt;why dark mode&lt;/a&gt; section above,
dark mode is an aesthetic choice for most users.
In consequence, some users may actually like to have their operating system UI
in dark, but still prefer to see their webpages the way they are used to seeing them.
A great pattern is to initially adhere to the signal the browser sends through
&lt;code&gt;prefers-color-scheme&lt;/code&gt;, but to then optionally allow users to override their system-level setting.&lt;/p&gt;
&lt;h4 id=&quot;the-lessdark-mode-togglegreater-custom-element&quot;&gt;The &lt;code&gt;&amp;lt;dark-mode-toggle&amp;gt;&lt;/code&gt; custom element &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#the-lessdark-mode-togglegreater-custom-element&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You can of course create the code for this yourself, but you can also just use
a ready-made custom element (web component) that I have created right for this purpose.
It&#39;s called &lt;a href=&quot;https://github.com/GoogleChromeLabs/dark-mode-toggle&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;dark-mode-toggle&amp;gt;&lt;/code&gt;&lt;/a&gt;
and it adds a toggle (dark mode: on/off) or
a theme switcher (theme: light/dark) to your page that you can fully customize.
The demo below shows the element in action
(oh, and I have also 🤫 silently snuck it in all of the
&lt;a href=&quot;https://dark-mode-baseline.glitch.me/&quot; rel=&quot;noopener&quot;&gt;other&lt;/a&gt;
&lt;a href=&quot;https://dark-mode-currentcolor.glitch.me/&quot; rel=&quot;noopener&quot;&gt;examples&lt;/a&gt;
&lt;a href=&quot;https://dark-mode-picture.glitch.me/&quot; rel=&quot;noopener&quot;&gt;above&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;dark-mode-toggle&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token attr-name&quot;&gt;legend&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;Theme Switcher&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;appearance&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;switch&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;dark&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;Dark&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;light&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;Light&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;remember&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;Remember this&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&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;dark-mode-toggle&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;div class=&quot;switcher&quot;&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;&amp;lt;dark-mode-toggle&amp;gt; in light mode.&quot; decoding=&quot;async&quot; height=&quot;76&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 140px) 140px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/Xy3uus69HnrkRPO4EuRu.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/Xy3uus69HnrkRPO4EuRu.png?auto=format&amp;w=140 140w, https://web-dev.imgix.net/image/admin/Xy3uus69HnrkRPO4EuRu.png?auto=format&amp;w=160 160w, https://web-dev.imgix.net/image/admin/Xy3uus69HnrkRPO4EuRu.png?auto=format&amp;w=182 182w, https://web-dev.imgix.net/image/admin/Xy3uus69HnrkRPO4EuRu.png?auto=format&amp;w=207 207w, https://web-dev.imgix.net/image/admin/Xy3uus69HnrkRPO4EuRu.png?auto=format&amp;w=236 236w, https://web-dev.imgix.net/image/admin/Xy3uus69HnrkRPO4EuRu.png?auto=format&amp;w=270 270w, https://web-dev.imgix.net/image/admin/Xy3uus69HnrkRPO4EuRu.png?auto=format&amp;w=280 280w&quot; width=&quot;140&quot; /&gt;
    &lt;figcaption&gt;
      &lt;code&gt;&amp;lt;dark-mode-toggle&amp;gt;&lt;/code&gt; in light mode.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;&amp;lt;dark-mode-toggle&amp;gt; in light mode.&quot; decoding=&quot;async&quot; height=&quot;76&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 140px) 140px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/glRVRJpQ9hMip6MbqY9N.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/glRVRJpQ9hMip6MbqY9N.png?auto=format&amp;w=140 140w, https://web-dev.imgix.net/image/admin/glRVRJpQ9hMip6MbqY9N.png?auto=format&amp;w=160 160w, https://web-dev.imgix.net/image/admin/glRVRJpQ9hMip6MbqY9N.png?auto=format&amp;w=182 182w, https://web-dev.imgix.net/image/admin/glRVRJpQ9hMip6MbqY9N.png?auto=format&amp;w=207 207w, https://web-dev.imgix.net/image/admin/glRVRJpQ9hMip6MbqY9N.png?auto=format&amp;w=236 236w, https://web-dev.imgix.net/image/admin/glRVRJpQ9hMip6MbqY9N.png?auto=format&amp;w=270 270w, https://web-dev.imgix.net/image/admin/glRVRJpQ9hMip6MbqY9N.png?auto=format&amp;w=280 280w&quot; width=&quot;140&quot; /&gt;
    &lt;figcaption&gt;
      &lt;code&gt;&amp;lt;dark-mode-toggle&amp;gt;&lt;/code&gt; in dark mode.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Try clicking or tapping the dark mode controls in the upper right corner in the demo below.
If you check the checkbox in the third and the fourth control, see how your mode selection
is remembered even when you reload the page.
This allows your visitors to keep their operating system in dark mode,
but enjoy your site in light mode or vice versa.&lt;/p&gt;
&lt;div style=&quot;height: 800px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;geolocation; microphone; camera; midi; vr; encrypted-media&quot; loading=&quot;lazy&quot; src=&quot;https://googlechromelabs.github.io/dark-mode-toggle/demo/index.html&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;IFrame content&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#conclusions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Working with and supporting dark mode is fun and opens up new design avenues.
For some of your visitors it can be the difference between not being able to handle your site
and being a happy user.
There are some pitfalls and careful testing is definitely required,
but dark mode is definitely a great opportunity for you to show that you care about all of your users.
The best practices mentioned in this post and helpers like the
&lt;a href=&quot;https://github.com/GoogleChromeLabs/dark-mode-toggle&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;dark-mode-toggle&amp;gt;&lt;/code&gt;&lt;/a&gt; custom element
should make you confident in your ability to create an amazing dark mode experience.
&lt;a href=&quot;https://twitter.com/tomayac&quot; rel=&quot;noopener&quot;&gt;Let me know on Twitter&lt;/a&gt; what you create and if this post was useful
or also suggestions for improving it.
Thanks for reading! 🌒&lt;/p&gt;
&lt;h2 id=&quot;related-links&quot;&gt;Related links &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#related-links&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Resources for the &lt;code&gt;prefers-color-scheme&lt;/code&gt; media query:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chromestatus.com/feature/5109758977638400&quot; rel=&quot;noopener&quot;&gt;Chrome Platform Status page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://crbug.com/889087&quot; rel=&quot;noopener&quot;&gt;Chromium bug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme&quot; rel=&quot;noopener&quot;&gt;Media Queries Level 5 spec&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Resources for the &lt;code&gt;color-scheme&lt;/code&gt; meta tag and CSS property:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/color-scheme/&quot;&gt;The &lt;code&gt;color-scheme&lt;/code&gt; CSS property and meta tag&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chromestatus.com/feature/5330651267989504&quot; rel=&quot;noopener&quot;&gt;Chrome Platform Status page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://crbug.com/925935&quot; rel=&quot;noopener&quot;&gt;Chromium bug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://drafts.csswg.org/css-color-adjust-1/&quot; rel=&quot;noopener&quot;&gt;CSS Color Adjustment Module Level 1 spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/3299&quot; rel=&quot;noopener&quot;&gt;CSS WG GitHub Issue for the meta tag and the CSS property&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/whatwg/html/issues/4504&quot; rel=&quot;noopener&quot;&gt;HTML WHATWG GitHub Issue for the meta tag&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;General dark mode links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://material.io/design/color/dark-theme.html&quot; rel=&quot;noopener&quot;&gt;Material Design—Dark Theme&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webkit.org/blog/8892/dark-mode-in-web-inspector/&quot; rel=&quot;noopener&quot;&gt;Dark Mode in Web Inspector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://webkit.org/blog/8840/dark-mode-support-in-webkit/&quot; rel=&quot;noopener&quot;&gt;Dark Mode Support in WebKit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/macos/visual-design/dark-mode/&quot; rel=&quot;noopener&quot;&gt;Apple Human Interface Guidelines—Dark Mode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Background research articles for this post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/dev-channel/what-does-dark-modes-supported-color-schemes-actually-do-69c2eacdfa1d&quot; rel=&quot;noopener&quot;&gt;What Does Dark Mode&#39;s &amp;quot;supported-color-schemes&amp;quot; Actually Do? 🤔&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/dev-channel/let-there-be-darkness-maybe-9facd9c3023d&quot; rel=&quot;noopener&quot;&gt;Let there be darkness! 🌚 Maybe…&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/dev-channel/re-colorization-for-dark-mode-19e2e17b584b&quot; rel=&quot;noopener&quot;&gt;Re-Colorization for Dark Mode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-color-scheme/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;prefers-color-scheme&lt;/code&gt; media feature, the &lt;code&gt;color-scheme&lt;/code&gt; CSS property,
and the related meta tag are the implementation work of 👏 &lt;a href=&quot;https://twitter.com/runeli&quot; rel=&quot;noopener&quot;&gt;Rune Lillesveen&lt;/a&gt;.
Rune is also a co-editor of the &lt;a href=&quot;https://drafts.csswg.org/css-color-adjust-1/&quot; rel=&quot;noopener&quot;&gt;CSS Color Adjustment Module Level 1&lt;/a&gt; spec.
I would like to 🙏 thank &lt;a href=&quot;https://www.linkedin.com/in/lukasz-zbylut/&quot; rel=&quot;noopener&quot;&gt;Lukasz Zbylut&lt;/a&gt;,
&lt;a href=&quot;https://twitter.com/rowan_m&quot; rel=&quot;noopener&quot;&gt;Rowan Merewood&lt;/a&gt;,
&lt;a href=&quot;https://www.linkedin.com/in/chiragd/&quot; rel=&quot;noopener&quot;&gt;Chirag Desai&lt;/a&gt;,
and &lt;a href=&quot;https://twitter.com/rob_dodson&quot; rel=&quot;noopener&quot;&gt;Rob Dodson&lt;/a&gt;
for their thorough reviews of this article.
The &lt;a href=&quot;https://web.dev/prefers-color-scheme/#loading-strategy&quot;&gt;loading strategy&lt;/a&gt; is the brainchild of &lt;a href=&quot;https://twitter.com/jaffathecake&quot; rel=&quot;noopener&quot;&gt;Jake Archibald&lt;/a&gt;.
&lt;a href=&quot;https://twitter.com/ecbos_&quot; rel=&quot;noopener&quot;&gt;Emilio Cobos Álvarez&lt;/a&gt; has pointed me to the correct &lt;code&gt;prefers-color-scheme&lt;/code&gt; detection method.
The tip with referenced SVGs and &lt;code&gt;currentColor&lt;/code&gt; came from
&lt;a href=&quot;https://twitter.com/xeenon&quot; rel=&quot;noopener&quot;&gt;Timothy Hatcher&lt;/a&gt;.
Finally, I am thankful to the many anonymous participants of the various user studies
that have helped shape the recommendations in this article.
Hero image by &lt;a href=&quot;https://unsplash.com/photos/kujXUuh1X0o&quot; rel=&quot;noopener&quot;&gt;Nathan Anderson&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>prefers-reduced-motion: Sometimes less movement is more</title>
    <link href="https://web.dev/prefers-reduced-motion/"/>
    <updated>2019-03-11T00:00:00Z</updated>
    <id>https://web.dev/prefers-reduced-motion/</id>
    <content type="html" mode="escaped">&lt;p&gt;Not everyone likes decorative animations or transitions, and some users outright experience motion
sickness when faced with parallax scrolling, zooming effects, and so on. The user preference media
query &lt;code&gt;prefers-reduced-motion&lt;/code&gt; lets you design a motion-reduced variant of your site for users who
have expressed this preference.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 74, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      74
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox 63, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      63
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      79
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Safari 10.1, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      10.1
    &lt;/span&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/CSS/@media/prefers-reduced-motion#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;too-much-motion-in-real-life-and-on-the-web&quot;&gt;Too much motion in real life and on the web &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-reduced-motion/#too-much-motion-in-real-life-and-on-the-web&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The other day, I was ice skating with my kids. It was a lovely day, the sun was shining, and the ice
rink was crammed with people ⛸. The only issue with that: I don&#39;t cope with crowds well. With so
many moving targets, I fail to focus on anything, and end up lost and with a feeling of complete
visual overload, almost like staring at an anthill 🐜.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Throng of feet of ice skating people.&quot; decoding=&quot;async&quot; height=&quot;320&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 580px) 580px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/JA5v1s8gSBk70eJBB8xW.jpg?auto=format&amp;w=1160 1160w&quot; width=&quot;580&quot; /&gt;
  &lt;figcaption&gt;Visual overload in real life.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Occasionally, the same can happen on the web: with flashing ads, fancy parallax effects, surprising
reveal animations, autoplaying videos, and so on, &lt;em&gt;the web sometimes can be quite overwhelming&lt;/em&gt;…
Happily, unlike in real life, there is a solution to that. The CSS media query
&lt;code&gt;prefers-reduced-motion&lt;/code&gt; lets developers create a variant of a page for users who, well, prefer
reduced motion. This can comprise anything from refraining from having autoplaying videos to
disabling certain purely decorative effects, to completely redesigning a page for certain users.&lt;/p&gt;
&lt;p&gt;Before I dive into the feature, let&#39;s take one step back and think of what animations are used for
on the web. If you want, you can also skip the background information and
&lt;a href=&quot;https://web.dev/prefers-reduced-motion/#working-with-the-media-query&quot;&gt;jump right into the technical details&lt;/a&gt; below.&lt;/p&gt;
&lt;h2 id=&quot;animation-on-the-web&quot;&gt;Animation on the web &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-reduced-motion/#animation-on-the-web&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Animation is oftentimes used to provide &lt;em&gt;feedback&lt;/em&gt; to the user, for example, to let them know that
an action was received and is being processed. For example, on a shopping website, a product could
be animated to &amp;quot;fly&amp;quot; into a virtual shopping cart, depicted as an icon in the top-right corner of
the site.&lt;/p&gt;
&lt;p&gt;Another use case involves using motion to
&lt;a href=&quot;https://medium.com/dev-channel/hacking-user-perception-to-make-your-websites-and-apps-feel-faster-922636b620e3&quot; rel=&quot;noopener&quot;&gt;hack user perception&lt;/a&gt;
by using a mixture of skeleton screens, contextual metadata, and low quality image previews to
occupy a lot of the user&#39;s time and make the whole experience &lt;em&gt;feel faster&lt;/em&gt;. The idea is to give
context to the user of what&#39;s coming and meanwhile load in things as quickly as possible.&lt;/p&gt;
&lt;p&gt;Finally, there are &lt;em&gt;decorative&lt;/em&gt; effects like animated gradients, parallax scrolling, background
videos, and several others. While many users enjoy such animations, some users dislike them because
they feel distracted or slowed down by them. In the worst case, users may even suffer from motion
sickness as if it were a real life experience, so for these users reducing animations is a &lt;em&gt;medical
necessity&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;motion-triggered-vestibular-spectrum-disorder&quot;&gt;Motion-triggered vestibular spectrum disorder &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-reduced-motion/#motion-triggered-vestibular-spectrum-disorder&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Some users experience
&lt;a href=&quot;https://www.w3.org/WAI/WCAG21/Understanding/animation-from-interactions.html&quot; rel=&quot;noopener&quot;&gt;distraction or nausea from animated content&lt;/a&gt;.
For example, scrolling animations can cause vestibular disorders when elements other than the main
element associated with the scrolling move around a lot. For example, parallax scrolling animations
can cause vestibular disorders because background elements move at a different rate than foreground
elements. Vestibular (inner ear) disorder reactions include dizziness, nausea, and migraine
headaches, and sometimes require bed rest to recover.&lt;/p&gt;
&lt;h2 id=&quot;remove-motion-on-operating-systems&quot;&gt;Remove motion on operating systems &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-reduced-motion/#remove-motion-on-operating-systems&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Many operating systems have had accessibility settings for specifying a preference for reduced
motion for a long time. The screenshots below show macOS Mojave&#39;s &lt;strong&gt;Reduce motion&lt;/strong&gt; preference and
Android Pie&#39;s &lt;strong&gt;Remove animations&lt;/strong&gt; preference. When checked, these preferences cause the operating
system to not use decorative effects like app launching animations. Applications themselves can and
should honor this setting, too, and remove all unnecessary animations.&lt;/p&gt;
&lt;div class=&quot;switcher&quot;&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;A screenshot of the macOS settings screen with the &amp;#x27;Reduce motion&amp;#x27; checkbox checked.&quot; decoding=&quot;async&quot; height=&quot;300&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 398px) 398px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KwuLNPefeDzUfR17EUtr.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KwuLNPefeDzUfR17EUtr.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KwuLNPefeDzUfR17EUtr.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KwuLNPefeDzUfR17EUtr.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KwuLNPefeDzUfR17EUtr.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KwuLNPefeDzUfR17EUtr.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KwuLNPefeDzUfR17EUtr.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KwuLNPefeDzUfR17EUtr.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KwuLNPefeDzUfR17EUtr.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KwuLNPefeDzUfR17EUtr.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KwuLNPefeDzUfR17EUtr.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KwuLNPefeDzUfR17EUtr.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KwuLNPefeDzUfR17EUtr.png?auto=format&amp;w=796 796w&quot; width=&quot;398&quot; /&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;img alt=&quot;A screenshot of the Android settings screen with the &amp;#x27;Remove animations&amp;#x27; checkbox checked.&quot; decoding=&quot;async&quot; height=&quot;300&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 287px) 287px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qed7yE6FKVQ5YXHn0TbJ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qed7yE6FKVQ5YXHn0TbJ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qed7yE6FKVQ5YXHn0TbJ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qed7yE6FKVQ5YXHn0TbJ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qed7yE6FKVQ5YXHn0TbJ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qed7yE6FKVQ5YXHn0TbJ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qed7yE6FKVQ5YXHn0TbJ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qed7yE6FKVQ5YXHn0TbJ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qed7yE6FKVQ5YXHn0TbJ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qed7yE6FKVQ5YXHn0TbJ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/qed7yE6FKVQ5YXHn0TbJ.png?auto=format&amp;w=574 574w&quot; width=&quot;287&quot; /&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;h2 id=&quot;remove-motion-on-the-web&quot;&gt;Remove motion on the web &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-reduced-motion/#remove-motion-on-the-web&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/&quot; rel=&quot;noopener&quot;&gt;Media Queries Level 5&lt;/a&gt; brings the reduced motion
user preference to the web as well. Media queries allow authors to test and query values or features
of the user agent or display device independent of the document being rendered. The media query
&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt;&lt;/a&gt; is used
to detect if the user has set an operating system preference to minimize the amount of animation or
motion it uses. It can take two possible values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;no-preference&lt;/code&gt;: Indicates that the user has made no preference in the underlying operating
system. This keyword value evaluates as &lt;code&gt;false&lt;/code&gt; in the boolean context.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reduce&lt;/code&gt;: Indicates that the user has set an operating system preference indicating that
interfaces should minimize movement or animation, preferably to the point where all non-essential
movement is removed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;working-with-the-media-query-from-css-and-javascript-contexts&quot;&gt;Working with the media query from CSS and JavaScript contexts &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-reduced-motion/#working-with-the-media-query-from-css-and-javascript-contexts&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As with all media queries, &lt;code&gt;prefers-reduced-motion&lt;/code&gt; can be checked from a CSS context and from a
JavaScript context.&lt;/p&gt;
&lt;p&gt;To illustrate both, let&#39;s say I have an important sign-up button that I want the user to click. I
could define an attention-catching &amp;quot;vibrate&amp;quot; animation, but as a good web citizen I will only play
it for those users who are explicitly OK with animations, but not everyone else, which can be users
who have opted out of animations, or users on browsers that don&#39;t understand the media query.&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 comment&quot;&gt;/*&lt;br /&gt;  If the user has expressed their preference for&lt;br /&gt;  reduced motion, then don&#39;t use animations on buttons.&lt;br /&gt;*/&lt;/span&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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; reduce&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;button&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;animation&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 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 comment&quot;&gt;/*&lt;br /&gt;  If the browser understands the media query and the user&lt;br /&gt;  explicitly hasn&#39;t set a preference, then use animations on buttons.&lt;br /&gt;*/&lt;/span&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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; no-preference&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;button&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;/* `vibrate` keyframes are defined elsewhere */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;animation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; vibrate 0.3s linear infinite both&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;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; If you have a lot of animation-related CSS, you can spare your opted-out users from downloading it by outsourcing all animation-related CSS into a separate stylesheet that you only load conditionally via the &lt;code&gt;media&lt;/code&gt; attribute on the &lt;code&gt;link&lt;/code&gt; element 😎: &lt;code&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;animations.css&amp;quot; media=&amp;quot;(prefers-reduced-motion: no-preference)&amp;quot;&amp;gt;&lt;/code&gt; &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;To illustrate how to work with &lt;code&gt;prefers-reduced-motion&lt;/code&gt; with JavaScript, let&#39;s imagine I have
defined a complex animation with the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Web_Animations_API&quot; rel=&quot;noopener&quot;&gt;Web Animations API&lt;/a&gt;. While CSS rules
will be dynamically triggered by the browser when the user preference changes, for JavaScript
animations I have to listen for changes myself, and then manually stop my potentially in-flight
animations (or restart them if the user lets me):&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;const&lt;/span&gt; mediaQuery &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matchMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(prefers-reduced-motion: reduce)&#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;mediaQuery&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;change&#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 punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;mediaQuery&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;media&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; mediaQuery&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matches&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;// Stop JavaScript-based animations.&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;Note that the parentheses around the actual media query are obligatory:&lt;/p&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;worse&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Don&#39;t&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;&lt;span class=&quot;token function&quot;&gt;matchMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;prefers-reduced-motion: reduce&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;better&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Do&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;&lt;span class=&quot;token function&quot;&gt;matchMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(prefers-reduced-motion: reduce)&#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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;h2 id=&quot;working-with-the-media-query-from-lesspicturegreater-contexts&quot;&gt;Working with the media query from &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; contexts &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-reduced-motion/#working-with-the-media-query-from-lesspicturegreater-contexts&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;An interesting use case is to make playing of an animated AVIF, WebP, or GIF dependent on the
&lt;code&gt;media&lt;/code&gt; attribute. If &lt;code&gt;(prefers-reduced-motion: no-preference)&lt;/code&gt; evaluates to &lt;code&gt;true&lt;/code&gt;, it&#39;s safe to
display the animated version, else the static version:&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;picture&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 comment&quot;&gt;&amp;lt;!-- Animated 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;source&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;nyancat.avifs&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;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;image/avif&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;(prefers-reduced-motion: no-preference)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&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;source&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;nyancat.gif&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;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;image/gif&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;(prefers-reduced-motion: no-preference)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Static 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;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;nyancat.png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&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;Nyan cat&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;250&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;250&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;picture&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 can see the example below. Try toggling your device&#39;s motion preferences to see the difference.&lt;/p&gt;
&lt;picture&gt;
  &lt;source srcset=&quot;https://storage.googleapis.com/web-dev-uploads/file/8WbTDNrhLsU0El80frMBGE4eMCD3/ZhnJj0x2s7oQ10vydXDy.avifs&quot; type=&quot;image/avif&quot; media=&quot;(prefers-reduced-motion: no-preference)&quot; /&gt;
  &lt;source srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/5K63Q5tcQ3vLWAJWQfCp.gif&quot; type=&quot;image/gif&quot; media=&quot;(prefers-reduced-motion: no-preference)&quot; /&gt;
  &lt;img src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/ipZALS4nnkUlleymYJXe.png&quot; alt=&quot;Nyan cat&quot; width=&quot;250&quot; height=&quot;250&quot; /&gt;
&lt;/picture&gt;
&lt;h2 id=&quot;discover-the-users-preferences-at-request-time&quot;&gt;Discover the user&#39;s preferences at request time &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-reduced-motion/#discover-the-users-preferences-at-request-time&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://web.dev/user-preference-media-features-headers/&quot;&gt;&lt;code&gt;Sec-CH-Prefers-Reduced-Motion&lt;/code&gt;&lt;/a&gt; client hint header
allows sites to obtain the user&#39;s motion preferences optionally at request time,
allowing servers to inline the right CSS for performance reasons.&lt;/p&gt;
&lt;h2 id=&quot;demo&quot;&gt;Demo &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-reduced-motion/#demo&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have created a little demo based on Rogério Vicente&#39;s amazing
&lt;a href=&quot;https://http.cat/&quot; rel=&quot;noopener&quot;&gt;🐈 HTTP status cats&lt;/a&gt;. First, take a moment to appreciate the joke, it&#39;s
hilarious and I&#39;ll wait. Now that you&#39;re back, let me introduce the
&lt;a href=&quot;https://prefers-reduced-motion.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt;. When you scroll down, each HTTP status cat
alternatingly appears from either the right or the left side. It&#39;s a buttery smooth 60 FPS
animation, but as outlined above, some users may dislike it or even get motion sick by it, so the
demo is programmed to respect &lt;code&gt;prefers-reduced-motion&lt;/code&gt;. This even works dynamically, so users can
change their preference on-the-fly, no reload required. If a user prefers reduced motion, the
non-necessary reveal animations are gone, and just the regular scrolling motion is left. The
screencast below shows the demo in action:&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; poster=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/CQAw3Ee43Dcv0JOsm9fl.png?auto=format&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/tcFciHGuF3MxnTr1y5ue01OGLBn2/zWs45QPPI9C8CjF813Zx.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;Video of the
    &lt;a href=&quot;https://prefers-reduced-motion.glitch.me/&quot;&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt; demo&lt;/a&gt;
    app
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-reduced-motion/#conclusions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Respecting user preferences is key for modern websites, and browsers are exposing more and more
features to enable web developers to do so. Another launched example is
&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;prefers-color-scheme&lt;/code&gt;&lt;/a&gt;, which
detects if the user prefers a light or dark color scheme. You can read everything about
&lt;code&gt;prefers-color-scheme&lt;/code&gt; in my article &lt;a href=&quot;https://web.dev/prefers-color-scheme&quot;&gt;Hello Darkness, My Old Friend&lt;/a&gt; 🌒.&lt;/p&gt;
&lt;p&gt;The CSS Working Group is currently standardizing more
&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#mf-user-preferences&quot; rel=&quot;noopener&quot;&gt;user preference media queries&lt;/a&gt; like
&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-transparency&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;prefers-reduced-transparency&lt;/code&gt;&lt;/a&gt;
(detects if the user prefers reduced transparency),
&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#prefers-contrast&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;prefers-contrast&lt;/code&gt;&lt;/a&gt; (detects if the user
has requested the system to increase or decrease the amount of contrast between adjacent colors),
and &lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#inverted&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;inverted-colors&lt;/code&gt;&lt;/a&gt; (detects if the user
prefers inverted colors).&lt;/p&gt;
&lt;h2 id=&quot;bonus-forcing-reduced-motion-on-all-websites&quot;&gt;(Bonus) Forcing reduced motion on all websites &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-reduced-motion/#bonus-forcing-reduced-motion-on-all-websites&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Not every site will use &lt;code&gt;prefers-reduced-motion&lt;/code&gt;, or maybe not significantly enough for your taste.
If you, for whatever reason, want to stop motion on all websites, you actually can. One way to make
this happen is to inject a stylesheet with the following CSS into every web page you visit. There
are several
&lt;a href=&quot;https://chrome.google.com/webstore/search/user%20stylesheets?_category=extensions&quot; rel=&quot;noopener&quot;&gt;browser extensions&lt;/a&gt;
out there (use at your own risk!) that allow for this.&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 punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; reduce&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;*,&lt;br /&gt;  ::before,&lt;br /&gt;  ::after&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;animation-delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; -1ms &lt;span class=&quot;token important&quot;&gt;!important&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;animation-duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1ms &lt;span class=&quot;token important&quot;&gt;!important&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;animation-iteration-count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1 &lt;span class=&quot;token important&quot;&gt;!important&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-attachment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; initial &lt;span class=&quot;token important&quot;&gt;!important&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;scroll-behavior&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; auto &lt;span class=&quot;token important&quot;&gt;!important&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-duration&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0s &lt;span class=&quot;token important&quot;&gt;!important&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-delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 0s &lt;span class=&quot;token important&quot;&gt;!important&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;The way this works is that the CSS above overrides the durations of all animations and transitions
to such a short time that they are not noticeable anymore. As some websites depend on an animation
to be run in order to work correctly (maybe because a certain step depends on the firing of the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLElement/animationend_event&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;animationend&lt;/code&gt; event&lt;/a&gt;),
the more radical &lt;code&gt;animation: none !important;&lt;/code&gt; approach wouldn&#39;t work. Even the above hack is not
guaranteed to succeed on all websites (for example, it can&#39;t stop motion that was initiated via the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Web_Animations_API&quot; rel=&quot;noopener&quot;&gt;Web Animations API&lt;/a&gt;), so be sure to
deactivate it when you notice breakage.&lt;/p&gt;
&lt;h2 id=&quot;related-links&quot;&gt;Related Links &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-reduced-motion/#related-links&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Latest Editor&#39;s Draft of the
&lt;a href=&quot;https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion&quot; rel=&quot;noopener&quot;&gt;Media Queries Level 5&lt;/a&gt;
spec.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt; on
&lt;a href=&quot;https://www.chromestatus.com/feature/5597964353404928&quot; rel=&quot;noopener&quot;&gt;Chrome Platform Status&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt; &lt;a href=&quot;http://crbug.com/722548&quot; rel=&quot;noopener&quot;&gt;Chromium bug&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Blink
&lt;a href=&quot;https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/NZ3c9d4ivA8/BIHFbOj6DAAJ&quot; rel=&quot;noopener&quot;&gt;Intent to Implement posting&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/prefers-reduced-motion/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Massive shout-out to &lt;a href=&quot;https://github.com/stephenmcgruer&quot; rel=&quot;noopener&quot;&gt;Stephen McGruer&lt;/a&gt; who has implemented
&lt;code&gt;prefers-reduced-motion&lt;/code&gt; in Chrome and—together with
&lt;a href=&quot;https://twitter.com/rob_dodson&quot; rel=&quot;noopener&quot;&gt;Rob Dodson&lt;/a&gt;—has also reviewed this article.
&lt;a href=&quot;https://unsplash.com/photos/im7Tiw1OY7c&quot; rel=&quot;noopener&quot;&gt;Hero image&lt;/a&gt; by Hannah Cauhepe on Unsplash.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Trust is good, observation is better: Intersection Observer v2</title>
    <link href="https://web.dev/intersectionobserver-v2/"/>
    <updated>2019-02-15T00:00:00Z</updated>
    <id>https://web.dev/intersectionobserver-v2/</id>
    <content type="html" mode="escaped">&lt;p&gt;Intersection Observer v1 is one of those APIs that&#39;s probably universally loved, and, now that
&lt;a href=&quot;https://webkit.org/blog/8582/intersectionobserver-in-webkit/&quot; rel=&quot;noopener&quot;&gt;Safari supports it&lt;/a&gt; as well,
it&#39;s also finally universally usable in all major browsers. For a quick refresher of the API,
I recommend watching &lt;a href=&quot;https://web.dev/authors/surma/&quot;&gt;Surma&lt;/a&gt;&#39;s
&lt;a href=&quot;https://www.youtube.com/watch?v=kW_atFXMG98&quot; rel=&quot;noopener&quot;&gt;Supercharged Microtip&lt;/a&gt; on Intersection
Observer v1 that is embedded below.
You can also read Surma&#39;s in-depth
&lt;a href=&quot;https://developer.chrome.com/blog/intersectionobserver/&quot; rel=&quot;noopener&quot;&gt;article&lt;/a&gt;.
People have used Intersection Observer v1 for a wide range of use cases like
&lt;a href=&quot;https://web.dev/fast/#lazy-load-images-and-video&quot;&gt;lazy loading of images and videos&lt;/a&gt;,
&lt;a href=&quot;https://developer.chrome.com/blog/sticky-headers&quot; rel=&quot;noopener&quot;&gt;being notified when elements reach &lt;code&gt;position: sticky&lt;/code&gt;&lt;/a&gt;,
&lt;a href=&quot;https://github.com/ampproject/amphtml/blob/master/extensions/amp-analytics/0.1/visibility-manager.js&quot; rel=&quot;noopener&quot;&gt;firing analytics events&lt;/a&gt;,
and many more.&lt;/p&gt;
&lt;div class=&quot;youtube&quot;&gt;  &lt;lite-youtube videoid=&quot;kW_atFXMG98&quot;&gt;  &lt;/lite-youtube&gt;&lt;/div&gt;
&lt;p&gt;For the full details, check out the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Intersection_Observer_API&quot; rel=&quot;noopener&quot;&gt;Intersection Observer docs on MDN&lt;/a&gt;,
but as a short reminder, this is what the Intersection Observer v1 API looks like in the most
basic case:&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;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;onIntersection&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 parameter&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; entry &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; entries&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;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isIntersecting&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;entry&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;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; observer &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;IntersectionObserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;onIntersection&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;observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&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;#some-target&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;whats-challenging-with-intersection-observer-v1&quot;&gt;What&#39;s challenging with Intersection Observer v1? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/intersectionobserver-v2/#whats-challenging-with-intersection-observer-v1&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To be clear, Intersection Observer v1 is great, but it&#39;s not perfect. There are
some corner cases where the API falls short. Let&#39;s have a closer look!
The Intersection Observer v1 API can tell you when an element is scrolled into the
window&#39;s viewport, but it &lt;em&gt;doesn&#39;t&lt;/em&gt; tell you whether the element is covered
by any other page content (that is, when the element is occluded) or whether
the element&#39;s visual display has been modified by visual effects like &lt;code&gt;transform&lt;/code&gt;, &lt;code&gt;opacity&lt;/code&gt;,
&lt;code&gt;filter&lt;/code&gt;, etc., which &lt;em&gt;effectively&lt;/em&gt; can make it invisible.&lt;/p&gt;
&lt;p&gt;For an element in the top-level document, this information can be determined by analyzing
the DOM via JavaScript, for example via
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/DocumentOrShadowRoot/elementFromPoint&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;DocumentOrShadowRoot.elementFromPoint()&lt;/code&gt;&lt;/a&gt;
and then digging deeper.
In contrast, the same information cannot be obtained if the element in question is
located in a third-party iframe.&lt;/p&gt;
&lt;h2 id=&quot;why-is-actual-visibility-such-a-big-deal&quot;&gt;Why is actual visibility such a big deal? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/intersectionobserver-v2/#why-is-actual-visibility-such-a-big-deal&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Internet is, unfortunately, a place that attracts bad actors with worse intentions.
For example, a shady publisher that serves pay-per-click ads on a content site might be incentivized
to trick people into clicking their ads to increase the publisher&#39;s ad payout (at least
for a short period, until the ad network catches them).
Typically, such ads are served in iframes.
Now if the publisher wanted to get users to click such ads, they could make the ad iframes
completely transparent by applying a CSS rule &lt;code&gt;iframe { opacity: 0; }&lt;/code&gt; and overlaying the iframes
on top of something attractive, like a cute cat video that users would actually want to click.
This is called &lt;em&gt;clickjacking&lt;/em&gt;.
You can see such a clickjacking attack in action in the upper section of this
&lt;a href=&quot;https://trick-ad-click.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt; (try &amp;quot;watching&amp;quot; the cat video
and activate &amp;quot;trick mode&amp;quot;).
You will notice that the ad in the iframe &amp;quot;thinks&amp;quot; it received legitimate clicks, even if it was
completely transparent when you (pretend-involuntarily) clicked it.&lt;/p&gt;
&lt;img alt=&quot;Tricking a user into clicking an ad by styling it transparent and overlaying it on top of something attractive.&quot; decoding=&quot;async&quot; height=&quot;533&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/oSQhudX6lenD4Pld7PJD.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
&lt;h2 id=&quot;how-does-intersection-observer-v2-fix-this&quot;&gt;How does Intersection Observer v2 fix this? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/intersectionobserver-v2/#how-does-intersection-observer-v2-fix-this&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Intersection Observer v2 introduces the concept of tracking the actual &amp;quot;visibility&amp;quot; of a target
element as a human being would define it.
By setting an option in the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IntersectionObserver/IntersectionObserver&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;IntersectionObserver&lt;/code&gt; constructor&lt;/a&gt;,
intersecting
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/IntersectionObserverEntry&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;IntersectionObserverEntry&lt;/code&gt;&lt;/a&gt;
instances will then contain a new boolean field named &lt;code&gt;isVisible&lt;/code&gt;.
A &lt;code&gt;true&lt;/code&gt; value for &lt;code&gt;isVisible&lt;/code&gt; is a strong guarantee from the underlying implementation
that the target element is completely unoccluded by other content
and has no visual effects applied that would alter or distort its display on screen.
In contrast, a &lt;code&gt;false&lt;/code&gt; value means that the implementation cannot make that guarantee.&lt;/p&gt;
&lt;p&gt;An important detail of the
&lt;a href=&quot;https://w3c.github.io/IntersectionObserver/v2/#calculate-visibility-algo&quot; rel=&quot;noopener&quot;&gt;spec&lt;/a&gt;
is that the implementation &lt;em&gt;is permitted&lt;/em&gt; to report &lt;em&gt;false negatives&lt;/em&gt; (that is, setting &lt;code&gt;isVisible&lt;/code&gt;
to &lt;code&gt;false&lt;/code&gt; even when the target element is completely visible and unmodified).
For performance or other reasons, browsers limit themselves to working with bounding
boxes and rectilinear geometry; they don&#39;t try to achieve pixel-perfect results for
modifications like &lt;code&gt;border-radius&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That said, &lt;em&gt;false positives&lt;/em&gt; are &lt;em&gt;not permitted&lt;/em&gt; under any circumstances (that is, setting
&lt;code&gt;isVisible&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; when the target element is not completely visible and unmodified).&lt;/p&gt;
&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; Visibility is &lt;em&gt;much more expensive&lt;/em&gt; to compute than intersection. For that reason, Intersection Observer v2 is &lt;em&gt;not intended to be used broadly&lt;/em&gt; in the way that Intersection Observer v1 is. Intersection Observer v2 is focused on combatting fraud and should be used only when the visibility information is needed and when Intersection Observer v1 functionality is therefore insufficient. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;what-does-the-new-code-look-like-in-practice&quot;&gt;What does the new code look like in practice? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/intersectionobserver-v2/#what-does-the-new-code-look-like-in-practice&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;IntersectionObserver&lt;/code&gt; constructor now takes two additional configuration properties: &lt;code&gt;delay&lt;/code&gt;
and &lt;code&gt;trackVisibility&lt;/code&gt;.
The &lt;code&gt;delay&lt;/code&gt; is a number indicating the minimum delay in milliseconds between notifications from
the observer for a given target.
The &lt;code&gt;trackVisibility&lt;/code&gt; is a boolean indicating whether the observer will track changes in a target&#39;s
visibility.&lt;/p&gt;
&lt;p&gt;It&#39;s important to note here that when &lt;code&gt;trackVisibility&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;delay&lt;/code&gt; is required to be at
least &lt;code&gt;100&lt;/code&gt; (that is, no more than one notification every 100ms).
As noted before, visibility is expensive to calculate, and this requirement is a precaution against
performance degradation and battery consumption. The responsible developer will use the
&lt;em&gt;largest tolerable value&lt;/em&gt; for delay.&lt;/p&gt;
&lt;p&gt;According to the current
&lt;a href=&quot;https://w3c.github.io/IntersectionObserver/v2/#calculate-visibility-algo&quot; rel=&quot;noopener&quot;&gt;spec&lt;/a&gt;, visibility is
calculated as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If the observer&#39;s &lt;code&gt;trackVisibility&lt;/code&gt; attribute is &lt;code&gt;false&lt;/code&gt;, then the target is considered visible.
This corresponds to the current v1 behavior.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the target has an effective transformation matrix other than a 2D translation
or proportional 2D upscaling, then the target is considered invisible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the target, or any element in its containing block chain, has an effective opacity other than
1.0, then the target is considered invisible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the target, or any element in its containing block chain, has any filters applied,
then the target is considered invisible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the implementation cannot guarantee that the target is completely unoccluded by other page
content, then the target is considered invisible.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This means current implementations are pretty conservative with guaranteeing visibility.
For example, applying an almost unnoticeable grayscale filter like &lt;code&gt;filter: grayscale(0.01%)&lt;/code&gt;
or setting an almost invisible transparency with &lt;code&gt;opacity: 0.99&lt;/code&gt; would all render the element
invisible.&lt;/p&gt;
&lt;p&gt;Below is a short code sample that illustrates the new API features. You can see its click tracking
logic in action in the second section of the &lt;a href=&quot;https://trick-ad-click.glitch.me/&quot; rel=&quot;noopener&quot;&gt;demo&lt;/a&gt;
(but now, try &amp;quot;watching&amp;quot; the puppy video). Be sure to activate &amp;quot;trick mode&amp;quot; again to immediately
convert yourself into a shady publisher and see how Intersection Observer v2 prevents
non-legitimate ad clicks from being tracked.
This time, Intersection Observer v2 has our back! 🎉&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; In contrast to typical lazy-loading code, if you use Intersection Observer to prevent this kind of clickjacking attack, you &lt;em&gt;must not&lt;/em&gt; &lt;code&gt;unobserve&lt;/code&gt; the element after the first intersection. &lt;/div&gt;&lt;/aside&gt;
&lt;img alt=&quot;Intersection Observer v2 preventing an unintended click on an ad.&quot; decoding=&quot;async&quot; height=&quot;876&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 612px) 612px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/8WbTDNrhLsU0El80frMBGE4eMCD3/6n5a6quLhExtwlFoNVdr.png?auto=format&amp;w=1224 1224w&quot; width=&quot;612&quot; /&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 comment&quot;&gt;&amp;lt;!-- This is the ad running in the iframe --&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;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;callToActionButton&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;Buy now!&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 punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;// This is code running in the iframe.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// The iframe must be visible for at least 800ms prior to an input event&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// for the input event to be considered valid.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; minimumVisibleDuration &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;800&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;// Keep track of when the button transitioned to a visible state.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; visibleSince &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;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; button &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;#callToActionButton&#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;button&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 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 operator&quot;&gt;=&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 punctuation&quot;&gt;(&lt;/span&gt;visibleSince &lt;span class=&quot;token operator&quot;&gt;&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 operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;performance&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; visibleSince &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; minimumVisibleDuration&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;trackAdClick&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 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 function&quot;&gt;rejectAdClick&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 punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; observer &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;IntersectionObserver&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 parameter&quot;&gt;changes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; change &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; changes&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;// ⚠️ Feature detection&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 keyword&quot;&gt;typeof&lt;/span&gt; change&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isVisible &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;undefined&#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;// The browser doesn&#39;t support Intersection Observer v2, falling back to v1 behavior.&lt;/span&gt;&lt;br /&gt;      change&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isVisible &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 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;change&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isIntersecting &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; change&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isVisible&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;      visibleSince &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; change&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;time&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;      visibleSince &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 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 literal-property property&quot;&gt;threshold&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;1.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;// 🆕 Track the actual visibility of the element&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;trackVisibility&lt;/span&gt;&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;// 🆕 Set a minimum delay between notifications&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Require that the entire iframe be visible.&lt;/span&gt;&lt;br /&gt;observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&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;#ad&#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 punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;h2 id=&quot;related-links&quot;&gt;Related Links &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/intersectionobserver-v2/#related-links&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Latest Editor&#39;s Draft of the
&lt;a href=&quot;https://w3c.github.io/IntersectionObserver/v2/&quot; rel=&quot;noopener&quot;&gt;Intersection Observer spec&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Intersection Observer v2 on
&lt;a href=&quot;https://www.chromestatus.com/feature/5878481493688320&quot; rel=&quot;noopener&quot;&gt;Chrome Platform Status&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Intersection Observer v2 &lt;a href=&quot;https://crbug.com/827639&quot; rel=&quot;noopener&quot;&gt;Chromium bug&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Blink
&lt;a href=&quot;https://groups.google.com/a/chromium.org/d/msg/blink-dev/tudxAHN9-AY/vz91o_aNDwAJ&quot; rel=&quot;noopener&quot;&gt;Intent to Implement posting&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/intersectionobserver-v2/#acknowledgements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;https://twitter.com/dotproto&quot; rel=&quot;noopener&quot;&gt;Simeon Vincent&lt;/a&gt;,
&lt;a href=&quot;https://twitter.com/yoavweiss&quot; rel=&quot;noopener&quot;&gt;Yoav Weiss&lt;/a&gt;, and &lt;a href=&quot;https://twitter.com/mathias&quot; rel=&quot;noopener&quot;&gt;Mathias Bynens&lt;/a&gt;
for reviewing this article, as well as &lt;a href=&quot;https://twitter.com/stefanzager&quot; rel=&quot;noopener&quot;&gt;Stefan Zager&lt;/a&gt; likewise
for reviewing and for implementing the feature in Chrome.
Hero image by Sergey Semin on &lt;a href=&quot;https://unsplash.com/photos/ZuXDaoIx_Bc&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Add a web app manifest</title>
    <link href="https://web.dev/add-manifest/"/>
    <updated>2018-11-05T00:00:00Z</updated>
    <id>https://web.dev/add-manifest/</id>
    <content type="html" mode="escaped">&lt;p&gt;The web app manifest is a JSON file that tells the browser about your
Progressive Web App and how it should behave when installed on the user&#39;s
desktop or mobile device. A typical manifest file includes the app name, the
icons the app should use, and the URL that should be opened when the
app is launched, among other things.&lt;/p&gt;
&lt;div class=&quot;wdi-browser-compat&quot;&gt;
  &lt;span class=&quot;wdi-browser-compat__label&quot;&gt;Browser support&lt;/span&gt;
  &lt;ul class=&quot;wdi-browser-compat__items&quot;&gt;
    &lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;chrome&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Chrome 39, Supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
      39
    &lt;/span&gt;
    &lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
    &lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;firefox&quot;&gt;
      &lt;span class=&quot;visually-hidden&quot;&gt;Firefox, Not supported&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;edge&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Edge 79, Supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;yes&quot; title=&quot;Supported&quot; aria-label=&quot;Supported&quot;&gt;
79
&lt;/span&gt;
&lt;/li&gt;&lt;li class=&quot;wdi-browser-compat__item&quot;&gt;
&lt;span class=&quot;wdi-browser-compat__icon&quot; data-browser=&quot;safari&quot;&gt;
&lt;span class=&quot;visually-hidden&quot;&gt;Safari, Not supported&lt;/span&gt;
&lt;/span&gt;
&lt;span class=&quot;wdi-browser-compat__version&quot; data-compat=&quot;no&quot; title=&quot;Not supported&quot; aria-label=&quot;Not supported&quot;&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;/span&gt;
&lt;/li&gt;&lt;p&gt;&lt;/p&gt;
  &lt;/ul&gt;
  &lt;a class=&quot;wdi-browser-compat__link&quot; href=&quot;https://developer.mozilla.org/docs/Web/Manifest/name#browser_compatibility&quot; target=&quot;_blank&quot;&gt;Source&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id=&quot;create&quot;&gt;Create the manifest file &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#create&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The manifest file can have any name, but is commonly named &lt;code&gt;manifest.json&lt;/code&gt; and
served from the root (your website&#39;s top-level directory). The specification
suggests the extension should be &lt;code&gt;.webmanifest&lt;/code&gt;, but browsers also support
&lt;code&gt;.json&lt;/code&gt; extensions, which may be easier for developers to understand.&lt;/p&gt;
&lt;p&gt;A typical manifest looks something like this:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;short_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Weather&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;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Weather: Do I need an umbrella?&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;&quot;icons&quot;&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/images/icons-vector.svg&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;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/svg+xml&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;&quot;sizes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;512x512&quot;&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;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/images/icons-192.png&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;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/png&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;&quot;sizes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;192x192&quot;&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;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/images/icons-512.png&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;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/png&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;&quot;sizes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;512x512&quot;&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 property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/?source=pwa&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;&quot;start_url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/?source=pwa&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;&quot;background_color&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#3367D6&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;&quot;display&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;standalone&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;&quot;scope&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&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;&quot;theme_color&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#3367D6&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;&quot;shortcuts&quot;&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;How&#39;s weather today?&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;&quot;short_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Today&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;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;View weather information for today&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;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/today?source=pwa&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;&quot;icons&quot;&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;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/images/today.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;sizes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;192x192&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;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 property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;How&#39;s weather tomorrow?&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;&quot;short_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Tomorrow&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;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;View weather information for tomorrow&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;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/tomorrow?source=pwa&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;&quot;icons&quot;&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;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/images/tomorrow.png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;sizes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;192x192&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;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 property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Weather forecast information&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;&quot;screenshots&quot;&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 punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/images/screenshot1.png&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;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/png&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;&quot;sizes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;540x720&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;&quot;form_factor&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;narrow&quot;&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;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/images/screenshot2.jpg&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;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/jpg&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;&quot;sizes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;720x540&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;&quot;form_factor&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;wide&quot;&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;h3 id=&quot;manifest-properties&quot;&gt;Key manifest properties &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#manifest-properties&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id=&quot;name&quot;&gt;&lt;code&gt;short_name&lt;/code&gt; and/or &lt;code&gt;name&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#name&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You must provide at least the &lt;code&gt;short_name&lt;/code&gt; or &lt;code&gt;name&lt;/code&gt; property. If both are
provided, &lt;code&gt;short_name&lt;/code&gt; is used on the user&#39;s home screen, launcher, or other
places where space may be limited. &lt;code&gt;name&lt;/code&gt; is used when the app is installed.&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; Operating systems usually expect to have a title for each app window. This title is displayed in various window-switching surfaces such as &lt;kbd&gt;alt&lt;/kbd&gt;+&lt;kbd&gt;tab&lt;/kbd&gt;, overview mode, and the shelf window list.  For PWAs running in standalone mode, Chromium prepends the &lt;code&gt;short_name&lt;/code&gt; (or, if it&#39;s not available, the &lt;code&gt;name&lt;/code&gt;) to what is specified in the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; of the HTML document to prevent disguise attacks where standalone apps might try to be mistaken, for example, for operating system dialogs.  In consequence, developers should &lt;em&gt;not&lt;/em&gt; repeat the application name in the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; when the app is running in standalone mode. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;icons&quot;&gt;&lt;code&gt;icons&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#icons&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When a user installs your PWA, you can define a set of icons for the browser
to use on the home screen, app launcher, task switcher, splash screen, and so on.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;icons&lt;/code&gt; property is an array of image objects. Each object must
include the &lt;code&gt;src&lt;/code&gt;, a &lt;code&gt;sizes&lt;/code&gt; property, and the &lt;code&gt;type&lt;/code&gt; of image. To use
&lt;a href=&quot;https://web.dev/maskable-icon/&quot;&gt;maskable icons&lt;/a&gt;, sometimes referred to as adaptive
icons on Android, you&#39;ll also need to add &lt;code&gt;&amp;quot;purpose&amp;quot;: &amp;quot;any maskable&amp;quot;&lt;/code&gt; to the
&lt;code&gt;icon&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;For Chromium, you must provide at least a 192x192 pixel icon, and a 512x512
pixel icon. If only those two icon sizes are provided, Chrome
automatically scales the icons to fit the device. If you&#39;d prefer to scale your
own icons, and adjust them for pixel-perfection, provide icons in increments
of 48dp.&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; Chromium-based browsers also support SVG icons which can be scaled arbitrarily without looking pixelated and that support advanced features like &lt;a href=&quot;https://blog.tomayac.com/2021/07/21/dark-mode-web-app-manifest-app-icons/&quot;&gt;responsiveness to &lt;code&gt;prefers-color-scheme&lt;/code&gt;&lt;/a&gt;, with the caveat that the icons do not update live, but remain in the state they were in at install time.  To use SVG icons safely, you should always specify a rasterized icon as a fallback for browsers that do not support SVG icons. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;id&quot;&gt;&lt;code&gt;id&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#id&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;id&lt;/code&gt; property allows you to explicitly define the identifier used for your application. Adding the &lt;code&gt;id&lt;/code&gt; property to the manifest removes the dependency on the &lt;code&gt;start_url&lt;/code&gt; or the location of the manifest, and makes it possible for them to be updated in the future. For more information, see &lt;a href=&quot;https://developer.chrome.com/blog/pwa-manifest-id/&quot; rel=&quot;noopener&quot;&gt;Uniquely identifying PWAs with the web app manifest id property&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;start-url&quot;&gt;&lt;code&gt;start_url&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#start-url&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;start_url&lt;/code&gt; is required and tells the browser where your application
should start when it is launched, and prevents the app from starting on
whatever page the user was on when they added your app to their home screen.&lt;/p&gt;
&lt;p&gt;Your &lt;code&gt;start_url&lt;/code&gt; should direct the user straight into your app, rather than
a product landing page. Think about what the user will want to do once
they open your app, and place them there.&lt;/p&gt;
&lt;h4 id=&quot;background-color&quot;&gt;&lt;code&gt;background_color&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#background-color&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;background_color&lt;/code&gt; property is used on the splash screen when the
application is first launched on mobile.&lt;/p&gt;
&lt;h4 id=&quot;display&quot;&gt;&lt;code&gt;display&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#display&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You can customize what browser UI is shown when your app is launched. For
example, you can hide the address bar and browser
user interface elements. Games can even
be made to launch full screen. The &lt;code&gt;display&lt;/code&gt; property takes one of the following values:&lt;/p&gt;
&lt;div class=&quot;table-wrapper&quot;&gt;
  &lt;table id=&quot;display-params&quot;&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;&lt;strong&gt;Property&lt;/strong&gt;&lt;/th&gt;
        &lt;th&gt;&lt;strong&gt;Use&lt;/strong&gt;&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code&gt;fullscreen&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;
          Opens the web application without any browser UI and takes
          up the entirety of the available display area.
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code&gt;standalone&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;
          Opens the web app to look and feel like a standalone
          app. The app runs in its own window, separate from the browser, and
          hides standard browser UI elements such as the URL bar.
          &lt;figure&gt;
            &lt;img alt=&quot;An example of a PWA window with standalone display.&quot; decoding=&quot;async&quot; height=&quot;196&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/XdBsDeRZozIyXyiXA59n.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
          &lt;/figure&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code&gt;minimal-ui&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;
          This mode is similar to &lt;code&gt;standalone&lt;/code&gt;, but provides the
          user a minimal set of UI elements for controlling navigation (such
          as back and reload).
          &lt;figure&gt;
            &lt;img alt=&quot;An example of a PWA window with minimal-ui display.&quot; decoding=&quot;async&quot; height=&quot;196&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/trPwjcMio7tBKGBNoT9u.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
          &lt;/figure&gt;
        &lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code&gt;browser&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;A standard browser experience.&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;h4 id=&quot;display-override&quot;&gt;&lt;code&gt;display_override&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#display-override&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Web apps can choose how they are displayed by setting a &lt;code&gt;display&lt;/code&gt; mode in their manifest as
&lt;a href=&quot;https://web.dev/add-manifest/#display&quot;&gt;explained above&lt;/a&gt;. Browsers are &lt;em&gt;not&lt;/em&gt; required to support all display modes, but they
&lt;em&gt;are&lt;/em&gt; required to support the
&lt;a href=&quot;https://w3c.github.io/manifest/#dfn-fallback-display-mode&quot; rel=&quot;noopener&quot;&gt;spec-defined fallback chain&lt;/a&gt;
(&lt;code&gt;&amp;quot;fullscreen&amp;quot;&lt;/code&gt; → &lt;code&gt;&amp;quot;standalone&amp;quot;&lt;/code&gt; → &lt;code&gt;&amp;quot;minimal-ui&amp;quot;&lt;/code&gt; → &lt;code&gt;&amp;quot;browser&amp;quot;&lt;/code&gt;). If they don&#39;t support a given
mode, they fall back to the next display mode in the chain. This inflexible behavior can be
problematic in rare cases. For example, a developer cannot request &lt;code&gt;&amp;quot;minimal-ui&amp;quot;&lt;/code&gt; without being
forced back into the &lt;code&gt;&amp;quot;browser&amp;quot;&lt;/code&gt; display mode when &lt;code&gt;&amp;quot;minimal-ui&amp;quot;&lt;/code&gt; is not supported.
Another problem is that the current behavior makes it impossible to introduce new display
modes in a backward compatible way, since explorations like tabbed application mode don&#39;t have a
natural place in the fallback chain.&lt;/p&gt;
&lt;p&gt;These problems are solved by the &lt;code&gt;display_override&lt;/code&gt; property, which the browser considers &lt;em&gt;before&lt;/em&gt;
the &lt;code&gt;display&lt;/code&gt; property. Its value is a sequence of strings that are considered in the listed order, and the
first supported display mode is applied. If none are supported, the browser falls back to evaluating
the &lt;code&gt;display&lt;/code&gt; field.&lt;/p&gt;
&lt;p&gt;Consider the example below. (The details of
&lt;a href=&quot;https://web.dev/window-controls-overlay/&quot;&gt;&lt;code&gt;&amp;quot;window-control-overlay&amp;quot;&lt;/code&gt;&lt;/a&gt; are out-of-scope for
this article.)&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;display_override&quot;&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 string&quot;&gt;&quot;window-control-overlay&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;minimal-ui&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 property&quot;&gt;&quot;display&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;standalone&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;As stated, the browser will look at &lt;code&gt;display_override&lt;/code&gt; first.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;window-control-overlay&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;minimal-ui&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If neither option is available, it falls back to &lt;code&gt;display&lt;/code&gt;. If &lt;code&gt;&amp;quot;standalone&amp;quot;&lt;/code&gt; is
not available, it resumes spec-defined fallabck chain from that point.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;standalone&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;minimal-ui&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;browser&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&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 browser will not consider &lt;code&gt;display_override&lt;/code&gt; unless &lt;code&gt;display&lt;/code&gt; is also present. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;scope&quot;&gt;&lt;code&gt;scope&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#scope&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;scope&lt;/code&gt; defines the set of URLs that the browser considers to be within your
app, and is used to decide when the user has left the app. The &lt;code&gt;scope&lt;/code&gt;
controls the URL structure that encompasses all the entry and exit points in
your web app. Your &lt;code&gt;start_url&lt;/code&gt; must reside within the &lt;code&gt;scope&lt;/code&gt;.&lt;/p&gt;
&lt;aside class=&quot;aside flow bg-state-bad-bg color-state-bad-text&quot;&gt;&lt;p class=&quot;cluster color-state-bad-text&quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Error sign&quot;&gt;   &lt;path fill-rule=&quot;evenodd&quot; clip-rule=&quot;evenodd&quot; d=&quot;M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15v-2h2v2h-2zm0-10v6h2V7h-2z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Caution&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; If the user clicks a link in your app that navigates outside of the &lt;code&gt;scope&lt;/code&gt;, the link opens and renders within the existing PWA window. If you want the link to open in a browser tab, you must add &lt;code&gt;target=&amp;quot;_blank&amp;quot;&lt;/code&gt; to the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag. On Android, links with &lt;code&gt;target=&amp;quot;_blank&amp;quot;&lt;/code&gt; open in a &lt;a href=&quot;https://developer.chrome.com/multidevice/android/customtabs&quot;&gt;Chrome Custom Tab&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;A few other notes on &lt;code&gt;scope&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you don&#39;t include a &lt;code&gt;scope&lt;/code&gt; in your manifest, then the default implied
&lt;code&gt;scope&lt;/code&gt; is the directory that your web app manifest is served from.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;scope&lt;/code&gt; attribute can be a relative path (&lt;code&gt;../&lt;/code&gt;), or any higher level
path (&lt;code&gt;/&lt;/code&gt;) which would allow for an increase in coverage of navigations
in your web app.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;start_url&lt;/code&gt; must be in the scope.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;start_url&lt;/code&gt; is relative to the path defined in the &lt;code&gt;scope&lt;/code&gt; attribute.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;start_url&lt;/code&gt; starting with &lt;code&gt;/&lt;/code&gt; will always be the root of the origin.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;theme-color&quot;&gt;&lt;code&gt;theme_color&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#theme-color&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;theme_color&lt;/code&gt; sets the color of the tool bar, and may be reflected in
the app&#39;s preview in task switchers. The &lt;code&gt;theme_color&lt;/code&gt; should match the
&lt;code&gt;meta&lt;/code&gt; theme color specified in your document head.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;An example of a PWA window with custom theme_color.&quot; decoding=&quot;async&quot; height=&quot;196&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/8mkBdT3O0FZLo0PUppvv.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    An example of a PWA window with custom theme_color.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;As of Chromium 93 and Safari 15, you can adjust this color in a
media query with the &lt;code&gt;media&lt;/code&gt; attribute of the &lt;code&gt;meta&lt;/code&gt; theme color element. The
first one that matches will be picked. For example, you could have one color for
light mode and another one for dark mode. At the time of writing, you can&#39;t
define those in your manifest. See &lt;a href=&quot;https://github.com/w3c/manifest/issues/975&quot; rel=&quot;noopener&quot;&gt;w3c/manifest#975 GitHub
issue&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;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;theme-color&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&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;(prefers-color-scheme: light)&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;white&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;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;theme-color&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&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;(prefers-color-scheme: dark)&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;black&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;shortcuts&quot;&gt;&lt;code&gt;shortcuts&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#shortcuts&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;shortcuts&lt;/code&gt; property is an array of &lt;a href=&quot;https://web.dev/app-shortcuts&quot;&gt;app shortcut&lt;/a&gt; objects
whose goal is to provide quick access to key tasks within your app. Each member
is a dictionary that contains at least a &lt;code&gt;name&lt;/code&gt; and a &lt;code&gt;url&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;description&quot;&gt;&lt;code&gt;description&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#description&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;description&lt;/code&gt; property describes the purpose of your app.&lt;/p&gt;
&lt;h4 id=&quot;screenshots&quot;&gt;&lt;code&gt;screenshots&lt;/code&gt; &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#screenshots&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;screenshots&lt;/code&gt; property is an array of image objects representing your app
in common usage scenarios. Each object must include the &lt;code&gt;src&lt;/code&gt;, a &lt;code&gt;sizes&lt;/code&gt;
property, and the &lt;code&gt;type&lt;/code&gt; of image.  The &lt;code&gt;form_factor&lt;/code&gt; property is optional.
You can set it either to &lt;code&gt;&amp;quot;wide&amp;quot;&lt;/code&gt; for screenshots applicable to wide screens
only or &lt;code&gt;&amp;quot;narrow&amp;quot;&lt;/code&gt; for narrow screenshots. You should only use it when the
layout varies by screen size.&lt;/p&gt;
&lt;p&gt;In Chrome, the image must respond to certain criteria:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Width and height must be at least 320px and at most 3840px.&lt;/li&gt;
&lt;li&gt;The maximum dimension can&#39;t be more than 2.3 times as long as the minimum
dimension.&lt;/li&gt;
&lt;li&gt;All screenshots matching the appropriate form factor must have the same
aspect ratio.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Screenshots of richer installation UI on desktop and mobile&quot; decoding=&quot;async&quot; height=&quot;386&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/vvhSqZboQoZZN9wBvoXq72wzGAf1/5SlCnibmZHqkXdGVgPZY.jpeg?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Richer installation UI on desktop and mobile.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;link-manifest&quot;&gt;Add the web app manifest to your pages &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#link-manifest&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After creating the manifest, add a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag to all the pages of your
Progressive Web App. For 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;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;manifest&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;/manifest.json&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;aside class=&quot;aside flow bg-tertiary-box-bg color-tertiary-box-text&quot;&gt;&lt;p class=&quot;cluster &quot;&gt;&lt;span class=&quot;aside__icon box-block &quot;&gt;&lt;svg width=&quot;24&quot; height=&quot;24&quot; viewBox=&quot;0 0 24 24&quot; role=&quot;img&quot; aria-label=&quot;Lightbulb&quot; fill=&quot;currentColor&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;   &lt;path d=&quot;M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6A4.997 4.997 0 017 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z&quot;&gt;&lt;/path&gt; &lt;/svg&gt;&lt;/span&gt;&lt;strong&gt;Gotchas&lt;/strong&gt;&lt;/p&gt;&lt;div class=&quot; flow&quot;&gt; The request for the manifest is made &lt;strong&gt;without&lt;/strong&gt; credentials (even if it&#39;s on the same domain), thus if the manifest requires credentials, you must include &lt;code&gt;crossorigin=&amp;quot;use-credentials&amp;quot;&lt;/code&gt; in the manifest tag. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;test-manifest&quot;&gt;Test your manifest &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#test-manifest&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To verify your manifest is setup correctly, use the &lt;strong&gt;Manifest&lt;/strong&gt; pane in the
&lt;strong&gt;Application&lt;/strong&gt; panel of Chrome DevTools.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The application panel in Chrome Devtools with the manifest tab selected.&quot; decoding=&quot;async&quot; height=&quot;601&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/FpIOY0Ak6FAA5xMuB9IT.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Test your manifest in DevTools.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure&gt;
&lt;/figure&gt;
&lt;p&gt;This pane provides a human-readable version of many of your manifest&#39;s
properties, and makes it easy to verify that all of the images are loading
properly.&lt;/p&gt;
&lt;h2 id=&quot;splash-screen&quot;&gt;Splash screens on mobile &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#splash-screen&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When your app first launches on mobile, it can take a moment for the browser
to spin up, and the initial content to begin rendering. Instead of showing a
white screen that may look to the user like the app is stalled, the browser
will show a splash screen until the first paint.&lt;/p&gt;
&lt;p&gt;Chrome automatically creates the splash screen from the manifest
properties, specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;background_color&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;icons&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;background_color&lt;/code&gt; should be the same color as the load page, to provide
a smooth transition from the splash screen to your app.&lt;/p&gt;
&lt;p&gt;Chrome will choose the icon that closely matches the device resolution for the
device. Providing 192px and 512px icons is sufficient for most cases, but
you can provide additional icons for pixel perfection.&lt;/p&gt;
&lt;h2 id=&quot;further-reading&quot;&gt;Further reading &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/add-manifest/#further-reading&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are several additional properties that can be added to the web app
manifest. Refer to the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/Manifest&quot; rel=&quot;noopener&quot;&gt;MDN Web App Manifest documentation&lt;/a&gt;
for more information.&lt;/p&gt;
</content>
    <author>
      <name>Pete LePage</name>
    </author><author>
      <name>François Beaufort</name>
    </author><author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Introduction to variable fonts on the web</title>
    <link href="https://web.dev/variable-fonts/"/>
    <updated>2018-02-19T00:00:00Z</updated>
    <id>https://web.dev/variable-fonts/</id>
    <content type="html" mode="escaped">&lt;p&gt;In this article, we will look at what variable fonts are, the benefits they
offer, and how we can use them in our work. First, let&#39;s review how typography
works on the web, and what innovations variable fonts bring.&lt;/p&gt;
&lt;h2 id=&quot;browser-compatibility&quot;&gt;Browser compatibility &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#browser-compatibility&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As of May 2020 variable fonts are supported in most browsers. See
&lt;a href=&quot;https://caniuse.com/#feat=variable-fonts&quot; rel=&quot;noopener&quot;&gt;Can I use variable fonts?&lt;/a&gt;
and &lt;a href=&quot;https://web.dev/variable-fonts/#fallbacks&quot;&gt;Fallbacks&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;introduction&quot;&gt;Introduction &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#introduction&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The terms font and typeface are often used interchangeably by developers.
However there is a difference: A typeface is the underlying visual design that
can exist in many different typesetting technologies, and a font is one of these
implementations, in a digital file format. In other words, a typeface is what
you &lt;em&gt;see&lt;/em&gt;, and the font is what you &lt;em&gt;use&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Another concept that is often overlooked is the distinction between a style, and
a family. A style is a single and specific typeface, such as Bold Italic, and a
family is the complete set of styles.&lt;/p&gt;
&lt;p&gt;Before variable fonts, each style was implemented as a separate font file. With
variable fonts, all styles can be contained in a single file.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A specimen composition and list of different styles of the Roboto family&quot; decoding=&quot;async&quot; height=&quot;600&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/admin/RbhgXwS81Y9PVRJnTjPX.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Left: a specimen of the Roboto typeface family. Right: named styles within the family.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;challenges-for-the-designer-and-developer&quot;&gt;Challenges for the designer and developer &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#challenges-for-the-designer-and-developer&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When a designer creates a print project they face some constraints, such as the
physical size of the page layout, the number of colors they can use (which is
determined by the kind of printing press that will be used), and so on. But they
can use as many typeface styles as they like. This means that the typography of
print media is often rich and sophisticated, so that the reading experience is
truly delightful. Think of the last time you enjoyed browsing an excellent
magazine.&lt;/p&gt;
&lt;p&gt;Web designers and developers have different constraints than print designers,
and an important one is the associated bandwidth costs of our designs. This
has been a sticking point for richer typographic experiences, as they come at
a cost. With traditional web fonts, each style used in our designs requires
users to download a separate font file, which increases latency and page
rendering time. Only including the Regular and Bold styles, plus their italic
counterparts, can amount to 500 KB or more of font data. This is even before
we have dealt with how the fonts are rendered, the fallback patterns we need
to use, or undesirable side-effects such as &lt;a href=&quot;https://www.zachleat.com/web/fout-vs-foit/&quot; rel=&quot;noopener&quot;&gt;FOIT and
FOUT&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Many font families offer a much wider range of styles, from Thin to Black
weights, narrow and wide widths, a variety of stylistic details, and even
size-specific designs (optimized for large or small text sizes.) Since you&#39;d
have to load a new font file for every style (or combinations of styles), many
web developers choose not to use these capabilities, reducing the reading
experience of their users.&lt;/p&gt;
&lt;h2 id=&quot;anatomy-of-a-variable-font&quot;&gt;Anatomy of a variable font &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#anatomy-of-a-variable-font&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Variable fonts address these challenges, by packing styles into a single file.&lt;/p&gt;
&lt;p&gt;This works by starting with a central or &#39;default&#39; style, usually the
&#39;Regular&#39;–an upright roman design with the most typical weight and width that
is most suitable for plain text. This is then connected to other styles in a
continuous range, called an &#39;axis.&#39; The most common axis is &lt;strong&gt;Weight&lt;/strong&gt;, which
can connect the default style through to a Bold style. Any individual style
can be located along an axis, and is called an &#39;instance&#39; of the variable
font. Some instances are named by the font developer, for example Weight axis
location 600 is called SemiBold.&lt;/p&gt;
&lt;p&gt;The variable font &lt;a href=&quot;https://github.com/TypeNetwork/Roboto-Flex&quot; rel=&quot;noopener&quot;&gt;Roboto Flex&lt;/a&gt;
has three styles for its &lt;strong&gt;Weight&lt;/strong&gt; axis. The Regular style is at the center,
and there are two styles at the opposite ends of the axis, one lighter and
the other heavier. Between these, you can choose from 900 instances:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The letter &amp;#x27;A&amp;#x27; shown in different weights&quot; decoding=&quot;async&quot; height=&quot;218&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Ecr5godvTKunVXP7W8aU.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Above: Illustrated anatomy of the Weight axis for the typeface Roboto.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The font developer can offer a set of different axes. You can combine them
because they all share the same default styles. Roboto has three styles in a
Width axis: the Regular is at the center of the axis, and two styles, narrower
and wider, are at each end. These provide all the widths of the Regular style,
and combine with the Weight axis to provide all the widths for every weight.&lt;/p&gt;
&lt;figure data-size=&quot;full&quot;&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/variable-fonts/roboto-dance.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    Roboto Flex in random combinations of Width and Weight
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This means there are thousands of styles! This may seem like massive overkill,
but the quality of the reading experience can be remarkably enhanced by this
diversity of type styles. And, if it is without performance penalty, web
developers can use a few or as many styles as they wish–it&#39;s up to their design.&lt;/p&gt;
&lt;h3 id=&quot;italics&quot;&gt;Italics &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#italics&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The way that Italics are handled in variable fonts is interesting, as there are
two difference approaches. Typefaces like Helvetica or Roboto have interpolation
compatible contours, so their Roman and Italic styles can be interpolated
between and the &lt;strong&gt;Slant&lt;/strong&gt; axis can be used to get from Roman to Italic.&lt;/p&gt;
&lt;p&gt;Other typefaces (such as Garamond, Baskerville, or Bodoni) have Roman and Italic
glyph contours that are not interpolation compatible. For example, the contours
that typically define a Roman lowercase &amp;quot;n&amp;quot; do not match the contours used to
define an Italic lowercase &amp;quot;n&amp;quot;. Instead of interpolating one contour to the
other, the &lt;strong&gt;Italic&lt;/strong&gt; axis toggles from Roman to Italic contours.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Example of the Weight Axes for the typeface Amstelvar&quot; decoding=&quot;async&quot; height=&quot;520&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/EySl1LIfX1QIrGq654PO.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Amstelvar&#39;s &quot;n&quot; contours in Italic (12 point, regular weight, normal width),
    and in Roman. Image supplied by David Berlow, type designer and typographer
    at Font Bureau.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;After the switch to Italic, the axes available to the user should be the same as
those for the Roman, just as the character set should be the same.&lt;/p&gt;
&lt;p&gt;A glyph substitution capability can also be seen for individual glyphs, and used
anywhere in the design space of a variable font. For example, a dollar sign
design with two vertical bars works best at larger point sizes, but at smaller
point sizes a design with only one bar is better. When we have fewer pixels for
rendering the glyph, a two bar design can become illegible. To combat this, much
like the Italic axis, a glyph substitution of one glyph for another can occur
along the &lt;strong&gt;Optical Size&lt;/strong&gt; axis at a point decided by the type designer.&lt;/p&gt;
&lt;p&gt;In summary, where the contours allow for it, type designers can create fonts
that interpolate between various styles in a multi-dimensional design space.
This gives you granular control over your typography, and a great deal of power.&lt;/p&gt;
&lt;h2 id=&quot;axes-definitions&quot;&gt;Axes definitions &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#axes-definitions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are five &lt;a href=&quot;https://docs.microsoft.com/en-us/typography/opentype/spec/dvaraxisreg#registered-axis-tags&quot; rel=&quot;noopener&quot;&gt;registered
axes&lt;/a&gt;,
which control known, predictable features of the font: weight, width, optical
size, slant and italics. Besides those, a font can contain custom axes. These
can control any design aspect of the font the type designer wishes: the size of
serifs, the length of swashes, the height of ascenders or the size of the dot on
the i.&lt;/p&gt;
&lt;p&gt;Even though axes can control the same feature, they might use different values.
For example, in the Oswald and Hepta Slab variable fonts there is only one axis
available, Weight, but the ranges are different–Oswald has the same range as
before it was upgraded to be variable, 200 to 700, but Hepta Slab has an extreme
hairline weight at 1 that goes all the up to 900.&lt;/p&gt;
&lt;p&gt;The five registered axes have 4-character lowercase tags that are used to
set their values in CSS:&lt;/p&gt;
&lt;table&gt;
	&lt;tbody&gt;
		&lt;tr&gt;
			&lt;th colspan=&quot;2&quot;&gt;Axis names and CSS values&lt;/th&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				Weight
			&lt;/td&gt;
			&lt;td&gt;
				&lt;code&gt;wght&lt;/code&gt;
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				Width
			&lt;/td&gt;
			&lt;td&gt;
				&lt;code&gt;wdth&lt;/code&gt;
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				Slant
			&lt;/td&gt;
			&lt;td&gt;
				&lt;code&gt;slnt&lt;/code&gt;
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				Optical Size
			&lt;/td&gt;
			&lt;td&gt;
				&lt;code&gt;opsz&lt;/code&gt;
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				Italics
			&lt;/td&gt;
			&lt;td&gt;
				&lt;code&gt;ital&lt;/code&gt;
			&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Since the font developer defines which axes are available in a variable font,
and which values they can have, it is essential to find out what each font
offers. The font&#39;s documentation should provide this, or you can inspect the
font using a tool like &lt;a href=&quot;https://wakamaifondue.com/&quot; rel=&quot;noopener&quot;&gt;Wakamai Fondue&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;use-cases-and-benefits&quot;&gt;Use cases and benefits &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#use-cases-and-benefits&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Setting the axes values comes down to personal taste and applying typographic
best practices. The danger with any new technology is possible misuse, and
settings that are overly artistic or exploratory could also decrease legibility
of the actual text. For titles, exploring different axes to create great
artistic designs are exciting, but for body copy this risks making the text
illegible.&lt;/p&gt;
&lt;h3 id=&quot;exciting-expression&quot;&gt;Exciting expression &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#exciting-expression&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;figure&gt;
  &lt;img alt=&quot;Grass example by Mandy Michael&quot; decoding=&quot;async&quot; height=&quot;174&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 495px) 495px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Rh7wLaBLauEF02D2dqMC.png?auto=format&amp;w=990 990w&quot; width=&quot;495&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;One great example of artistic expression is shown above, an exploration of the
typeface
&lt;a href=&quot;https://www.typenetwork.com/brochure/decovar-a-decorative-variable-font-by-david-berlow&quot; rel=&quot;noopener&quot;&gt;Decovar&lt;/a&gt;
by Mandy Michael.&lt;/p&gt;
&lt;p&gt;You can view the working example and source code for the above sample
&lt;a href=&quot;https://codepen.io/mandymichael/pen/YYaWop&quot; rel=&quot;noopener&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;animation&quot;&gt;Animation &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#animation&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;figure&gt;
  &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;      &lt;source src=&quot;https://storage.googleapis.com/web-dev-uploads/video/vgdbNJBYHma2o62ZqYmcnkq3j0o1/2Du2L0Ii5nUqz8n6S3Vz.mp4&quot; type=&quot;video/mp4&quot; /&gt;    &lt;/video&gt;
  &lt;figcaption&gt;
    Typeface Zycon, designed for animation by David Berlow, type designer and
    typographer at Font Bureau.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;There is also a possibility to explore animating characters with variable fonts.
Above is an example of different axes being used with the typeface Zycon. See
the live &lt;a href=&quot;https://www.axis-praxis.org/specimens/zycon&quot; rel=&quot;noopener&quot;&gt;animation example on Axis
Praxis&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://typogram.github.io/Anicons&quot; rel=&quot;noopener&quot;&gt;Anicons&lt;/a&gt; is the world&#39;s first animated
color icon font, based on Material Design Icons. Anicons is an experiment that
combines two cutting edge font technologies: variable fonts and color fonts.&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/variable-fonts/anicons-animation.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    A few examples of hover animations from Anicon&#39;s color icon font
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;finesse&quot;&gt;Finesse &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#finesse&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/variable-fonts/larger-widths.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    Amstelvar using little bits of XTRA in opposite directions so the words&#39; widths are evened out
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/TypeNetwork/Roboto-Flex&quot; rel=&quot;noopener&quot;&gt;Roboto Flex&lt;/a&gt; and
&lt;a href=&quot;https://github.com/TypeNetwork/Amstelvar&quot; rel=&quot;noopener&quot;&gt;Amstelvar&lt;/a&gt; offer a set of
&amp;quot;Parametric Axes.&amp;quot; In these axes, the letters are deconstructed into 4
fundamental aspects of form: black or positive shapes, white or negative
shapes, and the x and y dimensions. In the same way that primary colors can be
blended with any other color to adjust it, these 4 aspects can be used to fine
tune any other axis.&lt;/p&gt;
&lt;p&gt;The XTRA axis in Amstelvar allows you to adjust the &amp;quot;white&amp;quot; per mille value,
as shown above. Using little bits of XTRA in opposite directions, the words&#39;
widths are evened out.&lt;/p&gt;
&lt;h2 id=&quot;variable-fonts-in-css&quot;&gt;Variable fonts in CSS &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#variable-fonts-in-css&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;loading-variable-font-files&quot;&gt;Loading variable font files &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#loading-variable-font-files&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Variable fonts are loaded though the same &lt;code&gt;@font-face&lt;/code&gt; mechanism as traditional
static web fonts, but with two new enhancements:&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;@font-face&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;font-family&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Roboto Flex&#39;&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;src&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;&lt;span class=&quot;token string url&quot;&gt;&#39;RobotoFlex-VF.woff2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;woff2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tech&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;variations&#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 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;&lt;span class=&quot;token string url&quot;&gt;&#39;RobotoFlex-VF.woff2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;woff2-variations&#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 property&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100 1000&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token property&quot;&gt;font-stretch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 25% 151%&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;&lt;strong&gt;1. Source Formats:&lt;/strong&gt; We don&#39;t want the browser to download the font if it
doesn&#39;t support variable fonts, so we add &lt;code&gt;format&lt;/code&gt; and &lt;code&gt;tech&lt;/code&gt; descriptions: once in the
&lt;a href=&quot;https://www.w3.org/TR/css-fonts-4/#font-face-src-parsing&quot; rel=&quot;noopener&quot;&gt;future
syntax&lt;/a&gt;
(&lt;code&gt;format(&#39;woff2&#39;) tech(&#39;variations&#39;)&lt;/code&gt;), once in the deprecated but supported among browsers
syntax (&lt;code&gt;format(&#39;woff2-variations&#39;)&lt;/code&gt;). If the browser supports variable fonts and supports
the upcoming syntax, it will use the first declaration. If it supports variable
fonts and the current syntax, it will use the second declaration. They both
point to the same font file.&lt;/p&gt;
&lt;!-- TODO 2021 Q1 revisit this, based on progress in
     https://www.w3.org/TR/css-fonts-4/#font-face-src-requirement-types
     to allow removing the 2nd src --&gt;
&lt;p&gt;&lt;strong&gt;2. Style Ranges:&lt;/strong&gt; You&#39;ll notice we&#39;re supplying two values for &lt;code&gt;font-weight&lt;/code&gt;
and &lt;code&gt;font-stretch&lt;/code&gt;. Instead of telling the browser which specific weight this
font provides (for example &lt;code&gt;font-weight: 500;&lt;/code&gt;), we now give it the &lt;strong&gt;range&lt;/strong&gt; of
weights supported by the font. For Roboto Flex, the Weight axis ranges from 100
to 1000, and CSS directly maps the axis range to the &lt;code&gt;font-weight&lt;/code&gt; style
property. By specifying the range in &lt;code&gt;@font-face&lt;/code&gt;, any value outside this range
will be &amp;quot;capped&amp;quot; to the nearest valid value. The Width axis range is mapped in
the same way to the &lt;code&gt;font-stretch&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;If you&#39;re using the Google Fonts API, this will all be taken care of. Not only
will the CSS contain the proper source formats and ranges, Google Fonts will
also send static fallback fonts in case variable fonts aren&#39;t supported.&lt;/p&gt;
&lt;h3 id=&quot;using-weights-and-widths&quot;&gt;Using weights and widths &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#using-weights-and-widths&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Currently, the axes you can reliably set from CSS are the &lt;code&gt;wght&lt;/code&gt; axis through
&lt;code&gt;font-weight&lt;/code&gt;, and the &lt;code&gt;wdth&lt;/code&gt; axis through &lt;code&gt;font-stretch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Traditionally, you would set &lt;code&gt;font-weight&lt;/code&gt; as a keyword (&lt;code&gt;light&lt;/code&gt;, &lt;code&gt;bold&lt;/code&gt;) or as
a numerical value between 100 and 900, in steps of 100. With variable fonts, you
can set any value within the font&#39;s Width range:&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;.kinda-light&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;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 125&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 selector&quot;&gt;.super-heavy&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;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1000&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;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/variable-fonts/roboto-flex-weight.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    Roboto Flex&#39; Weight axis being changed from its minimum to its maximum.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Likewise, we can set &lt;code&gt;font-stretch&lt;/code&gt; with keywords (&lt;code&gt;condensed&lt;/code&gt;,
&lt;code&gt;ultra-expanded&lt;/code&gt;) or with percentage values:&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;.kinda-narrow&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;font-stretch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 33.3%&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 selector&quot;&gt;.super-wide&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;font-stretch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 151%&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;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/variable-fonts/roboto-flex-width.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    Roboto Flex&#39; Width axis being changed from its minimum to its maximum.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;using-italics-and-obliques&quot;&gt;Using italics and obliques &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#using-italics-and-obliques&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;ital&lt;/code&gt; axis is intended for fonts that contain both a regular style, and an
italic style. The axis is meant to be an on/off switch: value &lt;code&gt;0&lt;/code&gt; is off and
will show the regular style, value &lt;code&gt;1&lt;/code&gt; will show the italics. Unlike other axes,
there&#39;s no transition. A value of &lt;code&gt;0.5&lt;/code&gt; won&#39;t give you &amp;quot;half italics&amp;quot;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;slnt&lt;/code&gt; axis is different from italics in that it&#39;s not a new &lt;em&gt;style&lt;/em&gt;, but
just slants the regular style. By default its value is &lt;code&gt;0&lt;/code&gt;, which means the
default upright letter shapes. Roboto Flex has a maximum slant of -10 degrees,
meaning the letters will lean to the right when going from 0 to -10.&lt;/p&gt;
&lt;p&gt;It would be intuitive to set these axis through the &lt;code&gt;font-style&lt;/code&gt; property, but
as of April 2020, how to exactly do this is &lt;a href=&quot;https://github.com/w3c/csswg-drafts/issues/3125&quot; rel=&quot;noopener&quot;&gt;still being worked
out&lt;/a&gt;. So for now, you should
treat these as custom axes, and set them through &lt;code&gt;font-variation-settings&lt;/code&gt;:&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;i, em, .italic&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;/* Should be font-style: italic; */&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token property&quot;&gt;font-variation-settings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ital&#39;&lt;/span&gt; 1&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 selector&quot;&gt;.slanted&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;/* Should be font-style: oblique 10deg; */&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token property&quot;&gt;font-variation-settings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;slnt&#39;&lt;/span&gt; 10&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;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/variable-fonts/roboto-flex-slant.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    Roboto Flex&#39; Slant axis being changed from its minimum to its maximum.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;using-optical-sizes&quot;&gt;Using optical sizes &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#using-optical-sizes&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A typeface can be rendered very small (a 12px footnote) or very large (a 80px
headline). Fonts can respond to these size changes by changing its letter shapes
to better suit its size. A small size might be better off without fine details,
while a large size might benefit from more details and thinner strokes.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;The letter &amp;#x27;a&amp;#x27; shown at different optical sizes&quot; decoding=&quot;async&quot; height=&quot;147&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wrVCGSQNaGWhNp97BoRS.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    The letter &#39;a&#39; in Roboto Flex at different pixel sizes, then scaled to be the same size,
    shows the differences in design.
    &lt;a href=&quot;https://codepen.io/RoelN/pen/PoPvdeV&quot;&gt;Try it yourself on Codepen&lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A new CSS property has been introduced for this axis: &lt;code&gt;font-optical-sizing&lt;/code&gt;. By
default it&#39;s set to &lt;code&gt;auto&lt;/code&gt;, which makes the browser set the axis value based on
the &lt;code&gt;font-size&lt;/code&gt;. This means the browser will pick the best optical size
automatically, but if you wish to turn this off you can set
&lt;code&gt;font-optical-sizing&lt;/code&gt; to &lt;code&gt;none&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can also set a custom value for the &lt;code&gt;opsz&lt;/code&gt; axis, if you deliberately want an
optical size that doesn&#39;t match the font size. The following CSS would cause
text to be displayed at a large size, but at an optical size as if it were
printed in &lt;code&gt;8pt&lt;/code&gt;:&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;.small-yet-large&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;font-size&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;font-variation-settings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;opsz&#39;&lt;/span&gt; 8&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;using-custom-axes&quot;&gt;Using custom axes &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#using-custom-axes&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unlike registered axes, custom axes will not be mapped to an existing CSS
property, so you will always have to set them through &lt;code&gt;font-variation-settings&lt;/code&gt;.
Tags for custom axes are always in uppercase, to distinguish them from
registered axes.&lt;/p&gt;
&lt;p&gt;Roboto Flex offers a few custom axes, and the most important is Grade (&lt;code&gt;GRAD&lt;/code&gt;).
A Grade axis is interesting as it changes the weight of the font without
changing the widths, so line breaks do not change. By playing with a Grade axis,
you can avoid being forced to fiddle with changes to Weight axis that affects
the overall width, and then changes to the Width axis that affect the overall
weight.&lt;/p&gt;
&lt;figure&gt;
  &lt;video controls=&quot;&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
    &lt;source src=&quot;https://storage.googleapis.com/web-dev-assets/variable-fonts/roboto-flex-grade.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;
    Roboto Flex&#39; Grade axis being changed from its minimum to its maximum.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;As &lt;code&gt;GRAD&lt;/code&gt; is a custom axis, with a range of -200 to 150 in Roboto Flex.
We need to address it with &lt;code&gt;font-variation-settings&lt;/code&gt;:&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;.grade-light&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;font-variation-settings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; `GRAD` -200&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 selector&quot;&gt;.grade-normal&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;font-variation-settings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; `GRAD` 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;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.grade-heavy&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;font-variation-settings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; `GRAD` 150&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;variable-fonts-on-google-fonts&quot;&gt;Variable fonts on Google Fonts &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#variable-fonts-on-google-fonts&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Google Fonts has expanded their catalog with &lt;a href=&quot;https://fonts.google.com/?vfonly=true&quot; rel=&quot;noopener&quot;&gt;variable
fonts&lt;/a&gt;, and adding new ones regularly.
The interface is currently aimed at picking single instances from the font:
you select the variation you want, click &amp;quot;Select this style&amp;quot;, and it will be
added to the &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; element that fetches the CSS and fonts from Google Fonts.&lt;/p&gt;
&lt;p&gt;To use all the available axes, or ranges of values, you will have to &lt;a href=&quot;https://developers.google.com/fonts/docs/css2&quot; rel=&quot;noopener&quot;&gt;manually
compose&lt;/a&gt; the URL to the Google
Fonts API. The &lt;a href=&quot;https://fonts.google.com/variablefonts&quot; rel=&quot;noopener&quot;&gt;variable fonts overview&lt;/a&gt; lists all axes and values.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/RoelN/google-variable-fonts-links&quot; rel=&quot;noopener&quot;&gt;Google Variable Fonts Links&lt;/a&gt;
tool can also give you the latest URLs for the full variable fonts.&lt;/p&gt;
&lt;h2 id=&quot;font-variation-settings-inheritance&quot;&gt;Font-variation-settings inheritance &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#font-variation-settings-inheritance&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While all registered axes will soon be supported through existing CSS
properties, for the time being you might need to rely on
&lt;code&gt;font-variation-settings&lt;/code&gt; as a fallback. And if your font has custom axes,
you&#39;ll need &lt;code&gt;font-variation-settings&lt;/code&gt; too.&lt;/p&gt;
&lt;p&gt;However, here&#39;s a little gotcha with &lt;code&gt;font-variation-settings&lt;/code&gt;. Every property
you &lt;em&gt;don&#39;t explicitly set&lt;/em&gt; will automatically be reset to its default.
Previously set values aren&#39;t inherited! This means the following will not work
as expected:&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;span&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&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;slanted grade-light&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;	I should be slanted and have a light grade&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;span&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;First the browser will apply &lt;code&gt;font-variation-settings: &#39;slnt&#39; 10&lt;/code&gt; from the
&lt;code&gt;.slanted&lt;/code&gt; class. Then it will apply &lt;code&gt;font-variation-settings: &#39;GRAD&#39; -200&lt;/code&gt; from
the &lt;code&gt;.grade-light&lt;/code&gt; class. But this will reset the &lt;code&gt;slnt&lt;/code&gt; back to its default of
0! The result will be text in a light grade, but not slanted.&lt;/p&gt;
&lt;p&gt;Luckily, we can work around this by using CSS variables:&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 comment&quot;&gt;/* Set the default values */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;:root&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;--slnt&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 property&quot;&gt;--GRAD&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;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Change value for these elements and their children */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.slanted&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;--slnt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 10&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 selector&quot;&gt;.grade-light&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;--GRAD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; -200&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 selector&quot;&gt;.grade-normal&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;--GRAD&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;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.grade-heavy&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;--GRAD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 150&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 comment&quot;&gt;/* Apply whatever value is kept in the CSS variables */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.slanted,&lt;br /&gt;.grade-light,&lt;br /&gt;.grade-normal,&lt;br /&gt;.grade-heavy&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;font-variation-settings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;slnt&#39;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--slnt&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 string&quot;&gt;&#39;GRAD&#39;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--GRAD&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;CSS variables will cascade, so if an element (or one of its parents) will have
set the &lt;code&gt;slnt&lt;/code&gt; to &lt;code&gt;10&lt;/code&gt;, it will keep that value, even if you set &lt;code&gt;GRAD&lt;/code&gt; to
something else. See
&lt;a href=&quot;https://pixelambacht.nl/2019/fixing-variable-font-inheritance/&quot; rel=&quot;noopener&quot;&gt;Fixing variable font inheritance&lt;/a&gt;
for an in-depth explanation of this technique.&lt;/p&gt;
&lt;p&gt;Note that animating CSS variables doesn&#39;t work (by design), so something like
this doesn&#39;t work:&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;@keyframes&lt;/span&gt; width-animation&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;from&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;--wdth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 25&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 selector&quot;&gt;to&lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;--wdth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 151&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;These animations will have to happen directly on &lt;code&gt;font-variation-settings&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;performance-gains&quot;&gt;Performance gains &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#performance-gains&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OpenType variable fonts allow us to store multiple variations of a type family
into a single font file.
&lt;a href=&quot;https://medium.com/@monotype/part-2-from-truetype-gx-to-variable-fonts-4c28b16997c3&quot; rel=&quot;noopener&quot;&gt;Monotype&lt;/a&gt;
ran an experiment by combining 12 input fonts to generate eight weights,
across three widths, across both the Italic and Roman styles. Storing 48
individual fonts in a single variable font file meant a &lt;em&gt;88% reduction in file
size&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;However, if you are using a single font such as Roboto Regular and nothing
else, you might not see a net gain in font size if you were to switch to a
variable font with many axes. As always, it depends on your use-case.&lt;/p&gt;
&lt;p&gt;On the flip side, animating the font between settings may cause performance
issues. Although this will improve once variable font support in browsers gets
more mature, the problem can be reduced somewhat by only animating fonts that are
currently on screen. This handy snippet by
&lt;a href=&quot;https://abcdinamo.com/news/using-variable-fonts-on-the-web&quot; rel=&quot;noopener&quot;&gt;Dinamo&lt;/a&gt; pauses
animations in elements with the class &lt;code&gt;vf-animation&lt;/code&gt;, when they&#39;re not on
screen:&lt;/p&gt;
&lt;div&gt;&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; observer &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;IntersectionObserver&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;entries&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; observer&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;  entries&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&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;entry&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;// Pause/Play the animation&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;entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isIntersecting&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;animationPlayState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;running&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; entry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;style&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;animationPlayState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;paused&quot;&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;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; variableTexts &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;querySelectorAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.vf-animation&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;variableTexts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&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;el&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; observer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;observe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;el&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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;If your font responds to user interaction, it&#39;s a good idea to &lt;a href=&quot;https://css-tricks.com/debouncing-throttling-explained-examples/&quot; rel=&quot;noopener&quot;&gt;throttle or
debounce&lt;/a&gt;
input events. This will prevent the browser from rendering instances of the
variable font that changed so little from the previous instance the human eye
wouldn&#39;t see the difference.&lt;/p&gt;
&lt;p&gt;If you&#39;re using Google Fonts, it&#39;s a good idea to
&lt;a href=&quot;https://web.dev/preconnect-and-dns-prefetch/&quot;&gt;preconnect&lt;/a&gt; to &lt;code&gt;https://fonts.gstatic.com&lt;/code&gt;,
the domain where Google&#39;s fonts are hosted. This will make sure the browser
knows early on where to get the fonts when it comes across them in the CSS:&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;preconnect&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;https://fonts.gstatic.com&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;This tip works for other CDNs as well: the sooner you let the browser set up a
network connection, the sooner it can download your fonts.&lt;/p&gt;
&lt;p&gt;Find more performance tips for loading Google Fonts in &lt;a href=&quot;https://csswizardry.com/2020/05/the-fastest-google-fonts/&quot; rel=&quot;noopener&quot;&gt;The Fastest
Google Fonts&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;fallbacks&quot;&gt;Fallbacks and browser support &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#fallbacks&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All modern browsers &lt;a href=&quot;https://caniuse.com/#feat=variable-fonts&quot; rel=&quot;noopener&quot;&gt;support variable
fonts&lt;/a&gt;. In case you need to support
older browsers, you can choose to build your site with static fonts, and use
variable fonts as progressive enhancement:&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 comment&quot;&gt;/* Set up Roboto for old browsers, only regular + bold */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@supports&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;font-variation-settings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; normal&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 atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@font-face&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;font-family&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Roboto&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;src&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;&lt;span class=&quot;token string url&quot;&gt;&#39;Roboto-Regular.woff2&#39;&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;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; normal&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 atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@font-face&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;font-family&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Roboto&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;src&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;&lt;span class=&quot;token string url&quot;&gt;&#39;Roboto-Bold.woff2&#39;&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;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bold&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 selector&quot;&gt;body&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;font-family&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Roboto&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 selector&quot;&gt;.super-bold&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;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; bold&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;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Set up Roboto for modern browsers, all weights */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@supports&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;font-variation-settings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; normal&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 atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@font-face&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;font-family&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Roboto&#39;&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;src&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;&lt;span class=&quot;token string url&quot;&gt;&#39;RobotoFlex-VF.woff2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;woff2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tech&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;variations&#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 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;&lt;span class=&quot;token string url&quot;&gt;&#39;RobotoFlex-VF.woff2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;woff2-variations&#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 property&quot;&gt;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100 1000&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;font-stretch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 25% 151%&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 selector&quot;&gt;.super-bold&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;font-weight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1000&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 browsers, text with the class &lt;code&gt;.super-bold&lt;/code&gt; will get rendered in the
normal bold, as that&#39;s the only bold font we have available. When variable fonts
are supported, we can actually use the heaviest weight of 1000.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;@supports&lt;/code&gt; rule is not supported by Internet Explorer, so this browser would
not show any styling. If this is a problem, you could always use one of the
&lt;a href=&quot;https://stackoverflow.com/a/20541859/6255000&quot; rel=&quot;noopener&quot;&gt;oldschool hacks&lt;/a&gt; to target relevant
older browsers.&lt;/p&gt;
&lt;p&gt;If you are using the Google Fonts API, it will take care of loading the proper
fonts for your visitor&#39;s browsers. Say you request the font Oswald in weight
range 200 to 700, like so:&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;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;https://fonts.googleapis.com/css2?family=Oswald:wght@200..700&amp;amp;display=swap&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&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;stylesheet&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;Modern browsers that can handle variable fonts will get the variable font, and
will have every weight between 200 and 700 available. Older browsers will get
served individual static fonts for every weight. In this case, this means
they&#39;ll download 6 font files: one for weight 200, one for weight 300, and so
on.&lt;/p&gt;
&lt;h2 id=&quot;thanks&quot;&gt;Thanks &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/variable-fonts/#thanks&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This article would have only been made possible with the help of the following
people:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/mustafa_x&quot; rel=&quot;noopener&quot;&gt;Mustafa Kurtuldu&lt;/a&gt;, UX designer and design advocate at Google&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/PixelAmbacht&quot; rel=&quot;noopener&quot;&gt;Roel Nieskens&lt;/a&gt;, UX designer/developer and typography expert at &lt;a href=&quot;https://kabisa.nl/&quot; rel=&quot;noopener&quot;&gt;Kabisa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/davelab6&quot; rel=&quot;noopener&quot;&gt;Dave Crossland&lt;/a&gt;, Program Manager, Google Fonts&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/dberlow&quot; rel=&quot;noopener&quot;&gt;David Berlow&lt;/a&gt;, type designer and typographer at
&lt;a href=&quot;https://fontbureau.typenetwork.com/&quot; rel=&quot;noopener&quot;&gt;Font Bureau&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/lorp&quot; rel=&quot;noopener&quot;&gt;Laurence Penney&lt;/a&gt;, developer of &lt;a href=&quot;https://axis-praxis.org/&quot; rel=&quot;noopener&quot;&gt;axis-praxis.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/Mandy_Kerr&quot; rel=&quot;noopener&quot;&gt;Mandy Michael&lt;/a&gt;, developer of &lt;a href=&quot;https://variablefonts.dev/&quot; rel=&quot;noopener&quot;&gt;variablefonts.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hero image by &lt;a href=&quot;https://unsplash.com/@brunus&quot; rel=&quot;noopener&quot;&gt;Bruno Martins&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/OhJmwB4XWLE&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;
</content>
    <author>
      <name>Mustafa Kurtuldu</name>
    </author><author>
      <name>Thomas Steiner</name>
    </author><author>
      <name>Dave Crossland</name>
    </author><author>
      <name>Roel Nieskens</name>
    </author>
  </entry>
  
  <entry>
    <title>Offline UX design guidelines</title>
    <link href="https://web.dev/offline-ux-design-guidelines/"/>
    <updated>2016-11-10T00:00:00Z</updated>
    <id>https://web.dev/offline-ux-design-guidelines/</id>
    <content type="html" mode="escaped">&lt;p&gt;This article provides design guidelines on how to create a great experience
on both slow networks and offline.&lt;/p&gt;
&lt;p&gt;The quality of a network connection can be affected by a number of factors such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Poor coverage of a provider.&lt;/li&gt;
&lt;li&gt;Extreme weather conditions.&lt;/li&gt;
&lt;li&gt;Power outages.&lt;/li&gt;
&lt;li&gt;Entering into permanent &amp;quot;dead zones&amp;quot; such as in buildings with walls that block network connections.&lt;/li&gt;
&lt;li&gt;Entering into temporary &amp;quot;dead zones&amp;quot; such as when traveling on a train and going through a tunnel.&lt;/li&gt;
&lt;li&gt;Time-boxed internet connections such as those in airports or hotels.&lt;/li&gt;
&lt;li&gt;Cultural practices that require limited or no internet access at specific times or days.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your goal is to provide a good experience that lessens the impact of changes in connectivity.&lt;/p&gt;
&lt;h2 id=&quot;decide-what-to-show-your-users-when-they-have-a-bad-network-connection&quot;&gt;Decide what to show your users when they have a bad network connection &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#decide-what-to-show-your-users-when-they-have-a-bad-network-connection&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first question that must be asked is what does success and failure of a network connection look
like? A successful connection is your app&#39;s normal online experience. The failure of a connection,
however, can be both the offline state of your app as well how the app behaves when there is a laggy
network.&lt;/p&gt;
&lt;p&gt;When thinking about the success or failure of a network connection you need to ask yourself these
important UX questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How long do you wait to determine the success or failure of a connection?&lt;/li&gt;
&lt;li&gt;What can you do while success or failure is being determined?&lt;/li&gt;
&lt;li&gt;What should you do in the event of failure?&lt;/li&gt;
&lt;li&gt;How do you inform the user of the above?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;inform-users-of-their-current-state-and-change-of-state&quot;&gt;Inform users of their current state and change of state &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#inform-users-of-their-current-state-and-change-of-state&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Inform the user of both the actions they can still take when they have a network failure and the
current state of the application. For example, a notification could say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You seem to have a bad network connection. Not to worry! Messages will be sent when the
network is restored.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;The Emojoy emoji messaging app informing the user when a change in state occurs.&quot; decoding=&quot;async&quot; height=&quot;601&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 335px) 335px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/roxoXuJ9x7qUHFVWMgXZ.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/roxoXuJ9x7qUHFVWMgXZ.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/roxoXuJ9x7qUHFVWMgXZ.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/roxoXuJ9x7qUHFVWMgXZ.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/roxoXuJ9x7qUHFVWMgXZ.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/roxoXuJ9x7qUHFVWMgXZ.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/roxoXuJ9x7qUHFVWMgXZ.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/roxoXuJ9x7qUHFVWMgXZ.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/roxoXuJ9x7qUHFVWMgXZ.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/roxoXuJ9x7qUHFVWMgXZ.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/roxoXuJ9x7qUHFVWMgXZ.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/roxoXuJ9x7qUHFVWMgXZ.png?auto=format&amp;w=670 670w&quot; width=&quot;335&quot; /&gt;
  &lt;figcaption&gt;
    Clearly inform the user when a change in state occurs as soon as possible.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;The I/O 2016 app informing the user when a change in state occurs.&quot; decoding=&quot;async&quot; height=&quot;601&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 335px) 335px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GvE07BeSsnTyxnRbkZhz.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GvE07BeSsnTyxnRbkZhz.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GvE07BeSsnTyxnRbkZhz.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GvE07BeSsnTyxnRbkZhz.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GvE07BeSsnTyxnRbkZhz.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GvE07BeSsnTyxnRbkZhz.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GvE07BeSsnTyxnRbkZhz.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GvE07BeSsnTyxnRbkZhz.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GvE07BeSsnTyxnRbkZhz.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GvE07BeSsnTyxnRbkZhz.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GvE07BeSsnTyxnRbkZhz.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/GvE07BeSsnTyxnRbkZhz.png?auto=format&amp;w=670 670w&quot; width=&quot;335&quot; /&gt;
  &lt;figcaption&gt;
    The Google I/O app used a &quot;toast&quot; to let the user know when they were offline.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;inform-users-when-the-network-connection-improves-or-is-restored&quot;&gt;Inform users when the network connection improves or is restored &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#inform-users-when-the-network-connection-improves-or-is-restored&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;How you inform the user that their network connection has improved depends
on your application. Apps such as a stock market app that prioritize current information
should auto-update and notify the user as soon as possible.&lt;/p&gt;
&lt;p&gt;It is recommended that you let the user know that your web app has been updated &amp;quot;in the background&amp;quot;
by using a visual cue such as, for example, a material design toast element. This involves detecting both the
the presence of a service worker and an update to its managed
content. You can see a code example of this &lt;a href=&quot;https://github.com/GoogleChrome/sw-precache/blob/master/demo/app/js/service-worker-registration.js#L29&quot;&gt;function
at work here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One example of this would be &lt;a href=&quot;https://chromestatus.com/&quot; rel=&quot;noopener&quot;&gt;Chrome Platform Status&lt;/a&gt;
which posts a note to the user when the app has been updated.&lt;/p&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;An example weather app.&quot; decoding=&quot;async&quot; height=&quot;598&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 324px) 324px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ikam7evJEVSicAnVxvWA.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ikam7evJEVSicAnVxvWA.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ikam7evJEVSicAnVxvWA.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ikam7evJEVSicAnVxvWA.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ikam7evJEVSicAnVxvWA.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ikam7evJEVSicAnVxvWA.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ikam7evJEVSicAnVxvWA.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ikam7evJEVSicAnVxvWA.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ikam7evJEVSicAnVxvWA.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ikam7evJEVSicAnVxvWA.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ikam7evJEVSicAnVxvWA.png?auto=format&amp;w=648 648w&quot; width=&quot;324&quot; /&gt;
  &lt;figcaption&gt;
    Some apps, such as the weather app, need to auto-update because old data
    is not useful.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;Chrome Status uses a toast&quot; decoding=&quot;async&quot; height=&quot;598&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 336px) 336px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5TtIkRCPsuxAOajX8LPF.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5TtIkRCPsuxAOajX8LPF.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5TtIkRCPsuxAOajX8LPF.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5TtIkRCPsuxAOajX8LPF.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5TtIkRCPsuxAOajX8LPF.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5TtIkRCPsuxAOajX8LPF.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5TtIkRCPsuxAOajX8LPF.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5TtIkRCPsuxAOajX8LPF.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5TtIkRCPsuxAOajX8LPF.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5TtIkRCPsuxAOajX8LPF.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5TtIkRCPsuxAOajX8LPF.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/5TtIkRCPsuxAOajX8LPF.png?auto=format&amp;w=672 672w&quot; width=&quot;336&quot; /&gt;
  &lt;figcaption&gt;
    Apps such as Chrome Status let the user know
    when content has been updated via a toast.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You may also show the last time the app was updated at all times in a prominent space. This would
be useful for a currency converter app, for example.&lt;/p&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;The Material Money app being out-of-date.&quot; decoding=&quot;async&quot; height=&quot;598&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 324px) 324px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nK4V7aUvmLvaNJgF1S2I.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nK4V7aUvmLvaNJgF1S2I.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nK4V7aUvmLvaNJgF1S2I.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nK4V7aUvmLvaNJgF1S2I.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nK4V7aUvmLvaNJgF1S2I.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nK4V7aUvmLvaNJgF1S2I.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nK4V7aUvmLvaNJgF1S2I.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nK4V7aUvmLvaNJgF1S2I.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nK4V7aUvmLvaNJgF1S2I.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nK4V7aUvmLvaNJgF1S2I.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/nK4V7aUvmLvaNJgF1S2I.png?auto=format&amp;w=648 648w&quot; width=&quot;324&quot; /&gt;
  &lt;figcaption&gt;
    Material Money caches rates…
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;Material money has been updated&quot; decoding=&quot;async&quot; height=&quot;598&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 324px) 324px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wqaFuHzeAC2wR0D3pt7R.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wqaFuHzeAC2wR0D3pt7R.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wqaFuHzeAC2wR0D3pt7R.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wqaFuHzeAC2wR0D3pt7R.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wqaFuHzeAC2wR0D3pt7R.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wqaFuHzeAC2wR0D3pt7R.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wqaFuHzeAC2wR0D3pt7R.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wqaFuHzeAC2wR0D3pt7R.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wqaFuHzeAC2wR0D3pt7R.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wqaFuHzeAC2wR0D3pt7R.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/wqaFuHzeAC2wR0D3pt7R.png?auto=format&amp;w=648 648w&quot; width=&quot;324&quot; /&gt;
  &lt;figcaption&gt;
    …and notifies the user when the app has been updated.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Applications such as news apps could show a simple tap-to-update notification
informing the user of new content. Auto-updating would cause users to lose
their place.&lt;/p&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;An example news app, Tailpiece in its normal state&quot; decoding=&quot;async&quot; height=&quot;665&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 360px) 360px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=720 720w&quot; width=&quot;360&quot; /&gt;
  &lt;figcaption&gt;
    Tailpiece, an online newspaper, will auto-download the latest news…
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;Example news app, Tailpiece when its ready to be updated&quot; decoding=&quot;async&quot; height=&quot;665&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 360px) 360px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WNIMNF14cSF29fntl1Lc.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WNIMNF14cSF29fntl1Lc.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WNIMNF14cSF29fntl1Lc.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WNIMNF14cSF29fntl1Lc.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WNIMNF14cSF29fntl1Lc.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WNIMNF14cSF29fntl1Lc.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WNIMNF14cSF29fntl1Lc.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WNIMNF14cSF29fntl1Lc.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WNIMNF14cSF29fntl1Lc.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WNIMNF14cSF29fntl1Lc.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WNIMNF14cSF29fntl1Lc.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/WNIMNF14cSF29fntl1Lc.png?auto=format&amp;w=720 720w&quot; width=&quot;360&quot; /&gt;
  &lt;figcaption&gt;
    …but allow users to refresh manually so they do not lose their place in an article.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;update-the-ui-to-reflect-the-current-contextual-state&quot;&gt;Update the UI to reflect the current contextual state &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#update-the-ui-to-reflect-the-current-contextual-state&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Each bit of UI may have its own context and functionality that will change depending on if it
requires a successful connection. One example would be an e‑commerce site that can be browsed
offline. The Buy button and pricing would be disabled until a connection is reestablished.&lt;/p&gt;
&lt;p&gt;Other forms of contextual states could include data. For example, the financial application
Robinhood allows users to buy stock and uses color and graphics to notify the user when the market
is open. The whole interface turns white and then grays out when the market closes. When the value
of stock increases or decreases, each individual stock widget turns green or red depending on its
state.&lt;/p&gt;
&lt;h3 id=&quot;educate-the-user-so-they-understand-what-the-offline-model-is&quot;&gt;Educate the user so they understand what the offline model is &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#educate-the-user-so-they-understand-what-the-offline-model-is&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Offline is a new mental model for everyone. You need to educate your users about what changes will
occur when they don&#39;t have a connection. Inform them of where large data is saved and give them
settings to change the default behavior. Make sure you use multiple UI design components such as
informative language, icons, notifications, color, and imagery to convey these ideas collectively
rather than relying on a single design choice, such as an icon on its own, to tell the whole story.&lt;/p&gt;
&lt;h2 id=&quot;provide-an-offline-experience-by-default&quot;&gt;Provide an offline experience by default &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#provide-an-offline-experience-by-default&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If your app doesn&#39;t require much data, then cache that data by default. Users can become
increasingly frustrated if they can only access their data with a network connection. Try to make
the experience as stable as possible. An unstable connection will make your app feel untrustworthy,
where an app that lessens the impact of a network failure will feel magical to the user.&lt;/p&gt;
&lt;p&gt;News sites could benefit from auto-downloading and auto-saving the latest news so a
user could read today&#39;s news without a connection, perhaps downloading the text without the article
images. Also, adapt to the user&#39;s behavior. For example, if the sports section is what they typically view,
make that the priority download.&lt;/p&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;Tailpiece informs the user that they are offline with various design widgets.&quot; decoding=&quot;async&quot; height=&quot;665&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 360px) 360px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M39yiHQYpXacVII6d7zX.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M39yiHQYpXacVII6d7zX.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M39yiHQYpXacVII6d7zX.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M39yiHQYpXacVII6d7zX.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M39yiHQYpXacVII6d7zX.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M39yiHQYpXacVII6d7zX.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M39yiHQYpXacVII6d7zX.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M39yiHQYpXacVII6d7zX.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M39yiHQYpXacVII6d7zX.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M39yiHQYpXacVII6d7zX.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M39yiHQYpXacVII6d7zX.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/M39yiHQYpXacVII6d7zX.png?auto=format&amp;w=720 720w&quot; width=&quot;360&quot; /&gt;
  &lt;figcaption&gt;
    If the device is offline, Tailpiece will notify the user with a status message…
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;Tailpiece has a visual indicator that shows what sections are ready for offline use.&quot; decoding=&quot;async&quot; height=&quot;665&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 360px) 360px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KpkzjYNoCWquWKXTvM28.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KpkzjYNoCWquWKXTvM28.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KpkzjYNoCWquWKXTvM28.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KpkzjYNoCWquWKXTvM28.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KpkzjYNoCWquWKXTvM28.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KpkzjYNoCWquWKXTvM28.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KpkzjYNoCWquWKXTvM28.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KpkzjYNoCWquWKXTvM28.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KpkzjYNoCWquWKXTvM28.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KpkzjYNoCWquWKXTvM28.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KpkzjYNoCWquWKXTvM28.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/KpkzjYNoCWquWKXTvM28.png?auto=format&amp;w=720 720w&quot; width=&quot;360&quot; /&gt;
  &lt;figcaption&gt;
    …letting them know that they can at least partially still use the app.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;aside class=&quot;aside flow bg-state-info-bg color-state-info-text&quot;&gt;&lt;div class=&quot; flow&quot;&gt; When it comes to communicating an app&#39;s status, saying &amp;quot;The network is down&amp;quot; sends the message that the app&#39;s network is experiencing problems, whereas &amp;quot;You are disconnected&amp;quot; makes it clearer to the user that the problem is on their end. &lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;inform-the-user-when-the-app-is-ready-for-offline-consumption&quot;&gt;Inform the user when the app is ready for offline consumption &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#inform-the-user-when-the-app-is-ready-for-offline-consumption&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When a web app first loads you need to indicate to the user if it is ready for offline use. Do this
with a
&lt;a href=&quot;https://material.io/components/snackbars&quot; rel=&quot;noopener&quot;&gt;widget that provides brief feedback&lt;/a&gt;
about an operation through a message at the bottom of the screen such as, for example, when a section has
been synced or a data file has downloaded.&lt;/p&gt;
&lt;p&gt;Again think of the language you are using to make sure it is fit for your audience. Ensure the
messaging is the same in all instances where it&#39;s used. The term offline is generally
misunderstood by a non-technical audience so use action-based language that your audience can relate
to.&lt;/p&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;I/O app offline.&quot; decoding=&quot;async&quot; height=&quot;664&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 360px) 360px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fNOute6xBzFcDUNMeXDe.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fNOute6xBzFcDUNMeXDe.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fNOute6xBzFcDUNMeXDe.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fNOute6xBzFcDUNMeXDe.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fNOute6xBzFcDUNMeXDe.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fNOute6xBzFcDUNMeXDe.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fNOute6xBzFcDUNMeXDe.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fNOute6xBzFcDUNMeXDe.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fNOute6xBzFcDUNMeXDe.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fNOute6xBzFcDUNMeXDe.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fNOute6xBzFcDUNMeXDe.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/fNOute6xBzFcDUNMeXDe.png?auto=format&amp;w=720 720w&quot; width=&quot;360&quot; /&gt;
  &lt;figcaption&gt;
    The Google I/O 2016 app notified the user when
    the app is ready for offline use…
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;Chrome Status site is offline.&quot; decoding=&quot;async&quot; height=&quot;664&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 360px) 360px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Od6jUnazP8n7CMe18G2C.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Od6jUnazP8n7CMe18G2C.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Od6jUnazP8n7CMe18G2C.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Od6jUnazP8n7CMe18G2C.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Od6jUnazP8n7CMe18G2C.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Od6jUnazP8n7CMe18G2C.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Od6jUnazP8n7CMe18G2C.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Od6jUnazP8n7CMe18G2C.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Od6jUnazP8n7CMe18G2C.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Od6jUnazP8n7CMe18G2C.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Od6jUnazP8n7CMe18G2C.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Od6jUnazP8n7CMe18G2C.png?auto=format&amp;w=720 720w&quot; width=&quot;360&quot; /&gt;
  &lt;figcaption&gt;
    …and so does the Chrome Platform Status site, which includes information about the occupied storage.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;make-save-for-offline-an-obvious-part-of-the-interface-for-data-heavy-apps&quot;&gt;Make &#39;save for offline&#39; an obvious part of the interface for data-heavy apps &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#make-save-for-offline-an-obvious-part-of-the-interface-for-data-heavy-apps&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If an application uses large amounts of data, make sure that there is a switch or pin to add an item
for offline use rather than auto-downloading, unless a user has specifically asked for this behavior
via a settings menu. Make sure that the pin or download UI is not obscured by other UI elements and
that the feature is obvious to the user.&lt;/p&gt;
&lt;p&gt;One example would be a music player that requires large data files. The user is aware of the
associated data cost, but may also want to use the player offline.
Downloading music for later use requires the user to plan ahead, so education about this may be
required during onboarding.&lt;/p&gt;
&lt;h3 id=&quot;clarify-what-is-available-offline&quot;&gt;Clarify what is available offline &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#clarify-what-is-available-offline&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Be clear as to the option you are providing. You may need to show a tab or setting that shows an
&amp;quot;offline library&amp;quot; or &lt;a href=&quot;https://web.dev/content-indexing-api/&quot;&gt;content index&lt;/a&gt;,
so the user can easily see what they have stored on their phone and what needs to
be saved. Make sure the settings are concise and be clear where the data will be stored and who has
access to it.&lt;/p&gt;
&lt;h3 id=&quot;show-the-actual-cost-of-an-action&quot;&gt;Show the actual cost of an action &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#show-the-actual-cost-of-an-action&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Many users equate offline capability with &#39;downloading&#39;. Users in countries where network
connections regularly fail or aren&#39;t available often share content with other users, or save content
for offline use when they have connectivity.&lt;/p&gt;
&lt;p&gt;Users on data plans may avoid downloading large files for fear of cost, so you may also want to
display an associated cost so users can make an active comparison for a specific file or task. For
example, if the music app above could detect if the user is on a data plan and show the file size so
that users can see the cost of a file.&lt;/p&gt;
&lt;h3 id=&quot;help-prevent-hacked-experiences&quot;&gt;Help prevent hacked experiences &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#help-prevent-hacked-experiences&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Often users hack an experience without realizing they are doing it. For example before cloud-based
file sharing web apps, it was common for users to save large files and attach them to emails so
they could carry on editing from a different device. It is important not to be pulled into their
hacked experience but rather look at what they are trying to achieve. In other words instead of
thinking of how you can make attaching a large file more user-friendly, solve the problem of
sharing large files across multiple devices.&lt;/p&gt;
&lt;h2 id=&quot;make-experiences-transferable-from-one-device-to-another&quot;&gt;Make experiences transferable from one device to another &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#make-experiences-transferable-from-one-device-to-another&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When building for flaky networks, try to sync as soon as the connection improves
so that the experience is transferable. For example, imagine a travel app losing
a network connection mid-way through a booking. When the connection is reestablished, the app syncs
with the user&#39;s account allowing them to continue their booking on their desktop device. Not being
able to transfer experiences can be incredibly jarring to users.&lt;/p&gt;
&lt;p&gt;Inform the user of the current state of their data. For example, you could show whether the
app has synced. Educate them where possible but try not to overburden them with messaging.&lt;/p&gt;
&lt;h2 id=&quot;create-inclusive-design-experiences&quot;&gt;Create inclusive design experiences &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#create-inclusive-design-experiences&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When designing seek to be inclusive by providing meaningful design devices, simple language,
standard iconography, and meaningful imagery that will guide the user to complete the action or task
rather than hinder their progress.&lt;/p&gt;
&lt;h3 id=&quot;use-simple,-concise-language&quot;&gt;Use simple, concise language &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#use-simple,-concise-language&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Good UX is not just about a well designed interface. It includes the path a user takes as well as
the words used in the app. Avoid tech jargon when explaining the state of the app or
individual UI components. Consider that the phrase &amp;quot;app offline&amp;quot; might not convey to the user the
current state of the app.&lt;/p&gt;
&lt;div class=&quot;switcher&quot;&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;worse&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Don&#39;t&lt;/p&gt;
  &lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
    &lt;img alt=&quot;A service worker icon is a bad example.&quot; decoding=&quot;async&quot; height=&quot;149&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 350px) 350px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MaYiuInHsZ2mbPQcbD4l.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MaYiuInHsZ2mbPQcbD4l.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MaYiuInHsZ2mbPQcbD4l.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MaYiuInHsZ2mbPQcbD4l.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MaYiuInHsZ2mbPQcbD4l.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MaYiuInHsZ2mbPQcbD4l.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MaYiuInHsZ2mbPQcbD4l.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MaYiuInHsZ2mbPQcbD4l.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MaYiuInHsZ2mbPQcbD4l.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MaYiuInHsZ2mbPQcbD4l.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MaYiuInHsZ2mbPQcbD4l.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/MaYiuInHsZ2mbPQcbD4l.png?auto=format&amp;w=700 700w&quot; width=&quot;350&quot; /&gt;
    &lt;figcaption&gt;
      Avoid terms that aren&#39;t intelligible to non-technical users.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;better&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Do&lt;/p&gt;
  &lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
    &lt;img alt=&quot;A download icon is a good example.&quot; decoding=&quot;async&quot; height=&quot;149&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 350px) 350px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZIjYKLFK5DbrDJDJeDqu.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZIjYKLFK5DbrDJDJeDqu.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZIjYKLFK5DbrDJDJeDqu.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZIjYKLFK5DbrDJDJeDqu.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZIjYKLFK5DbrDJDJeDqu.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZIjYKLFK5DbrDJDJeDqu.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZIjYKLFK5DbrDJDJeDqu.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZIjYKLFK5DbrDJDJeDqu.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZIjYKLFK5DbrDJDJeDqu.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZIjYKLFK5DbrDJDJeDqu.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZIjYKLFK5DbrDJDJeDqu.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/ZIjYKLFK5DbrDJDJeDqu.png?auto=format&amp;w=700 700w&quot; width=&quot;350&quot; /&gt;
    &lt;figcaption&gt;
      Use language and imagery that describe the action.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;h3 id=&quot;use-multiple-design-devices-to-create-accessible-user-experiences&quot;&gt;Use multiple design devices to create accessible user experiences &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#use-multiple-design-devices-to-create-accessible-user-experiences&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Use language, color, and visual components to demonstrate a change of state or current status.
Solely using color to demonstrate state may not be noticed by the user and may be inaccessible to
users with visual disabilities. Also, the instinct for designers is to use grayed UI
to represent offline, but this can have a loaded meaning on the web. Grayed UI such as input elements
on a form also means that an element is disabled. This can cause confusion if you &lt;em&gt;only&lt;/em&gt;
use color to depict state.&lt;/p&gt;
&lt;p&gt;To prevent misunderstandings, express different states to the user in multiple ways, for example
with color, labels, and UI components.&lt;/p&gt;
&lt;div class=&quot;switcher&quot;&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;worse&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Don&#39;t&lt;/p&gt;
  &lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
    &lt;img alt=&quot;A bad example only using color.&quot; decoding=&quot;async&quot; height=&quot;368&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 720px) 720px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/Uj28SN1ZepiIvya4YTe1.png?auto=format&amp;w=1440 1440w&quot; width=&quot;720&quot; /&gt;
    &lt;figcaption&gt;
      Avoid using color as the sole means to describe what is happening.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;figure class=&quot;compare flow&quot; data-type=&quot;better&quot; data-size=&quot;full&quot;&gt;&lt;p class=&quot;compare__label&quot;&gt;Do&lt;/p&gt;
  &lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
    &lt;img alt=&quot;A good example that uses color and text to show an error.&quot; decoding=&quot;async&quot; height=&quot;368&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 720px) 720px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/s78eC2GBEkDQouqhBYMO.png?auto=format&amp;w=1440 1440w&quot; width=&quot;720&quot; /&gt;
    &lt;figcaption&gt;
      Use a mixture of design elements to convey meaning.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;h3 id=&quot;use-icons-that-convey-meaning&quot;&gt;Use icons that convey meaning &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#use-icons-that-convey-meaning&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Make sure that information is conveyed correctly with meaningful text labels as well as icons. Icons
alone can be problematic, since the concept of offline on the web is relatively new. Users may
misunderstand icons used on their own. For example, using a floppy disc for save makes sense to an
older generation but young users who have never seen a floppy disc may be confused by the metaphor.
Likewise, the &#39;hamburger&#39; menu icon has been known to confuse users when presented without a label.&lt;/p&gt;
&lt;p&gt;When introducing an offline icon try to remain consistent with industry standard visuals (when they
exist) as well as providing a text label and description. For example, saving for offline might be
a typical download icon or perhaps if the action involves syncing it could be a syncing icon. Some
actions may be interpreted as saving for offline rather than demonstrating a network&#39;s status. Think
of the action you are trying to convey rather than presenting the user with an abstract concept. For
example, save or download data would be action-based.&lt;/p&gt;
&lt;img alt=&quot;Various icon examples that convey offline&quot; decoding=&quot;async&quot; height=&quot;299&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 700px) 700px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/h2FFD3nLIOdSzWg5H5kO.png?auto=format&amp;w=1400 1400w&quot; width=&quot;700&quot; /&gt;
&lt;p&gt;Offline can mean a number of things depending on the context, such as download, export, pin, etc.
For more inspiration checkout the
&lt;a href=&quot;https://material.io/resources/icons/&quot; rel=&quot;noopener&quot;&gt;Material Design icon set&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;use-skeleton-layouts-with-other-feedback-mechanisms&quot;&gt;Use skeleton layouts with other feedback mechanisms &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#use-skeleton-layouts-with-other-feedback-mechanisms&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A skeleton layout is essentially a wireframe version of your app that displays while content is
being loaded. This helps demonstrate to the user that content is about to be loaded. Consider also
using a preloader UI as well, with a text label informing the user that the app is loading. One
example would be to pulsate the wireframe content giving the app the feeling that it is alive and
loading. This reassures the user that something is happening and helps prevent resubmissions or
refreshes of your app.&lt;/p&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;A skeleton layout example.&quot; decoding=&quot;async&quot; height=&quot;665&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 360px) 360px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pUIrcbblhHe5YPSrrwRy.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pUIrcbblhHe5YPSrrwRy.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pUIrcbblhHe5YPSrrwRy.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pUIrcbblhHe5YPSrrwRy.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pUIrcbblhHe5YPSrrwRy.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pUIrcbblhHe5YPSrrwRy.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pUIrcbblhHe5YPSrrwRy.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pUIrcbblhHe5YPSrrwRy.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pUIrcbblhHe5YPSrrwRy.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pUIrcbblhHe5YPSrrwRy.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pUIrcbblhHe5YPSrrwRy.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/pUIrcbblhHe5YPSrrwRy.png?auto=format&amp;w=720 720w&quot; width=&quot;360&quot; /&gt;
  &lt;figcaption&gt;
    A skeleton placeholder layout is shown during the download of the article…
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style=&quot;display: inline-block; max-width: 45%;&quot;&gt;
  &lt;img alt=&quot;A loaded article example.&quot; decoding=&quot;async&quot; height=&quot;665&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 360px) 360px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/rRLScscdU9BE9Wt4RYAx.png?auto=format&amp;w=720 720w&quot; width=&quot;360&quot; /&gt;
  &lt;figcaption&gt;
    …that gets replaced with the real contents once the download finishes.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;dont-block-content&quot;&gt;Don&#39;t block content &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#dont-block-content&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In some applications, a user might trigger an action such as creating a new document. Some apps will
try to connect to a server in order to sync the new document and to demonstrate this they display an
intrusive loading modal dialog that covers the entire screen. This may work fine if the user has a
stable network connection, but if the network is unstable they won&#39;t be able to escape from this
action and the UI effectively blocks them from doing anything else. Network requests that block
content should be avoided. Allow the user to continue to browse your app and queue tasks that will
be performed and synced once the connection has improved.&lt;/p&gt;
&lt;p&gt;Demonstrate the state of an action by providing your users with feedback. For example, if a user is
editing a doc, consider changing the feedback design so it is visibly different from when they are
online but still shows that their file was &amp;quot;saved&amp;quot; and will sync when they have a network
connection. This will educate the user about the different states available and reassure them that
their task or action has been stored. This has the added benefit of the user growing more confident
using your application.&lt;/p&gt;
&lt;h2 id=&quot;design-for-the-next-billion&quot;&gt;Design for the next billion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#design-for-the-next-billion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In many regions, low-end devices are commonplace, connectivity is unreliable and, for many users,
data is unaffordable. You will need to earn user trust by being transparent and frugal with data.
Think about ways to help users on poor connections and simplify the interface to help speed up
tasks. Always try to ask users before downloading data-heavy content.&lt;/p&gt;
&lt;p&gt;Offer low bandwidth options for users on laggy connections. So if the network connection is slow,
provide small assets. Offer an option to choose high or low quality assets.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/offline-ux-design-guidelines/#conclusion&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Education is key to offline UX as users are unfamiliar with these concepts. Try to create associations
with things that are familiar, e.g downloading for later use is the same as offlining data.&lt;/p&gt;
&lt;p&gt;When designing for unstable network connections, remember these guidelines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Design for the success, failure, and instability of a network connection.&lt;/li&gt;
&lt;li&gt;Data may be expensive, so be considerate to the user.&lt;/li&gt;
&lt;li&gt;For most users globally, the tech environment is almost exclusively mobile.&lt;/li&gt;
&lt;li&gt;Low-end devices are commonplace, with limited storage, memory, and processing power, and small
displays and lower touchscreen quality. Make sure performance is a part of your design process.&lt;/li&gt;
&lt;li&gt;Allow users to browse your application when they are offline.&lt;/li&gt;
&lt;li&gt;Inform users of their current state and of changes in states.&lt;/li&gt;
&lt;li&gt;Try to provide offline by default if your app doesn&#39;t require much data.&lt;/li&gt;
&lt;li&gt;If the app is data-heavy, educate users about how they can download for offline use.&lt;/li&gt;
&lt;li&gt;Make experiences transferable between devices.&lt;/li&gt;
&lt;li&gt;Use language, icons, imagery, typography, and color together to express ideas to the user.&lt;/li&gt;
&lt;li&gt;Provide reassurance and feedback to help the user.&lt;/li&gt;
&lt;/ul&gt;
</content>
    <author>
      <name>Mustafa Kurtuldu</name>
    </author><author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
  
  <entry>
    <title>Read files in JavaScript</title>
    <link href="https://web.dev/read-files/"/>
    <updated>2010-06-18T00:00:00Z</updated>
    <id>https://web.dev/read-files/</id>
    <content type="html" mode="escaped">&lt;p&gt;Selecting and interacting with files on the user&#39;s local device is
one of the most commonly used features of the web. It allows users to select
files and upload them to a server, for example, uploading photos, or
submitting tax documents, etc. But, it also allows sites to read and
manipulate them without ever having to transfer the data across the network.&lt;/p&gt;
&lt;h2 id=&quot;the-modern-file-system-access-api&quot;&gt;The modern File System Access API &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#the-modern-file-system-access-api&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The File System Access API provides an easy way to both read from
and write to files and directories on the user&#39;s local system. It&#39;s currently
available in most Chromium-based browsers such as Chrome or Edge. To learn
more about it, see the &lt;a href=&quot;https://web.dev/file-system-access/&quot;&gt;File System Access API&lt;/a&gt; article.&lt;/p&gt;
&lt;p&gt;Since the File System Access API is not compatible with all browsers yet,
check out &lt;a href=&quot;https://github.com/GoogleChromeLabs/browser-fs-access&quot; rel=&quot;noopener&quot;&gt;browser-fs-access&lt;/a&gt;,
a helper library that uses the new API wherever it is available, but falls
back to legacy approaches when it is not.&lt;/p&gt;
&lt;h2 id=&quot;working-with-files,-the-classic-way&quot;&gt;Working with files, the classic way &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#working-with-files,-the-classic-way&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This guide shows you how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select files
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/read-files/#select-input&quot;&gt;Using the HTML input element&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/read-files/#select-dnd&quot;&gt;Using a drag-and-drop zone&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/read-files/#read-metadata&quot;&gt;Read file metadata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/read-files/#read-content&quot;&gt;Read a file&#39;s content&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;select&quot;&gt;Select files &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#select&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;select-input&quot;&gt;HTML input element &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#select-input&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The easiest way for users to select files is using the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/input/file&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt;&lt;/a&gt; element, which is supported in every
major browser. When clicked, it lets a user select a file, or multiple files
if the &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/input/file#Additional_attributes&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;multiple&lt;/code&gt;&lt;/a&gt; attribute is included, using
their operating system&#39;s built-in file selection UI. When the user finishes
selecting a file or files, the element&#39;s &lt;code&gt;change&lt;/code&gt; event fires. You can
access the list of files from &lt;code&gt;event.target.files&lt;/code&gt;, which is a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileList&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileList&lt;/code&gt;&lt;/a&gt; object. Each item in the &lt;code&gt;FileList&lt;/code&gt; is a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/File&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;File&lt;/code&gt;&lt;/a&gt; object.&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;!-- The `multiple` attribute lets users select multiple files. --&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;input&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;file&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;file-selector&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;multiple&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 punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fileSelector &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;file-selector&#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;  fileSelector&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;change&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;const&lt;/span&gt; fileList &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&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;fileList&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&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;/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; Check if the &lt;a href=&quot;https://web.dev/file-system-access/#ask-the-user-to-pick-a-file-to-read&quot;&gt;&lt;code&gt;window.showOpenFilePicker()&lt;/code&gt;&lt;/a&gt; method is a viable alternative for your use case, since it also gives you a file handle so you can possibly write back to the file, in addition to reading. This method can be &lt;a href=&quot;https://github.com/GoogleChromeLabs/browser-fs-access#opening-files&quot;&gt;polyfilled&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;This example lets a user select multiple files using their operating system&#39;s
built-in file selection UI and then logs each selected file to the console.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 480px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/input-type-file?attributionHidden=true&amp;sidebarCollapsed=true&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;input-type-file on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h4 id=&quot;accept&quot;&gt;Limit the types of files users can select &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#accept&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;In some cases, you may want to limit the types of files users can select.
For example, an image editing app should only accept images, not text files.
To do that, add an &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTML/Element/input/file#Additional_attributes&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;accept&lt;/code&gt;&lt;/a&gt; attribute to
the input element to specify which file types are accepted.&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;input&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;file&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;file-selector&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;accept&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;.jpg, .jpeg, .png&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;h3 id=&quot;select-dnd&quot;&gt;Custom drag-and-drop &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#select-dnd&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In some browsers, the &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt; element is also a drop target,
allowing users to drag-and-drop files into your app. But, the drop target is
small, and can be hard to use. Instead, once you&#39;ve provided the core
functionality using an &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt; element, you can provide a
large, custom drag-and-drop surface.&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; Check if the &lt;a href=&quot;https://web.dev/file-system-access/#drag-and-drop-integration&quot;&gt;&lt;code&gt;DataTransferItem.getAsFileSystemHandle()&lt;/code&gt;&lt;/a&gt; method is a viable alternative for your use case, since it also gives you a file handle so you can possibly write back to the file, in addition to reading. &lt;/div&gt;&lt;/aside&gt;
&lt;h4 id=&quot;choose-drop-zone&quot;&gt;Choose your drop zone &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#choose-drop-zone&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Your drop surface depends on the design of your application. You may
only want part of the window to be a drop surface, or potentially the entire
window.&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt=&quot;A screenshot of Squoosh, an image compression web app.&quot; decoding=&quot;async&quot; height=&quot;589&quot; loading=&quot;lazy&quot; sizes=&quot;(min-width: 800px) 800px, calc(100vw - 48px)&quot; src=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&quot; srcset=&quot;https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=200 200w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=228 228w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=260 260w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=296 296w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=338 338w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=385 385w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=439 439w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=500 500w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=571 571w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=650 650w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=741 741w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=845 845w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=964 964w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=1098 1098w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=1252 1252w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=1428 1428w, https://web-dev.imgix.net/image/tcFciHGuF3MxnTr1y5ue01OGLBn2/xX8UXdqkLmZXu3Ad1Z2q.png?auto=format&amp;w=1600 1600w&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Squoosh makes the entire window a drop zone.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Squoosh allows the user to drag and drop an image anywhere into the window,
and clicking &lt;strong&gt;select an image&lt;/strong&gt; invokes the &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt; element.
Whatever you choose as your drop zone, make sure it&#39;s clear to the user that
they can drag and drop files onto that surface.&lt;/p&gt;
&lt;h4 id=&quot;define-drop-zone&quot;&gt;Define the drop zone &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#define-drop-zone&quot;&gt;#&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To enable an element to be a drag-and-drop zone, you&#39;ll need to listen for
two events, &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Document/dragover_event&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;dragover&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Document/drop_event&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;drop&lt;/code&gt;&lt;/a&gt;. The &lt;code&gt;dragover&lt;/code&gt;
event updates the browser UI to visually indicate that the drag-and-drop
action is creating a copy of the file. The &lt;code&gt;drop&lt;/code&gt; event is fired after the
user drops the files onto the surface. Similar to the input element,
you can access the list of files from &lt;code&gt;event.dataTransfer.files&lt;/code&gt;, which is
a &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileList&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileList&lt;/code&gt;&lt;/a&gt; object. Each item in the &lt;code&gt;FileList&lt;/code&gt; is a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/File&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;File&lt;/code&gt;&lt;/a&gt; object.&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;const&lt;/span&gt; dropArea &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;drop-area&#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;dropArea&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;dragover&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;stopPropagation&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;  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 comment&quot;&gt;// Style the drag-and-drop as a &quot;copy file&quot; operation.&lt;/span&gt;&lt;br /&gt;  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dropEffect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;copy&#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;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;dropArea&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;drop&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&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;stopPropagation&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;  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 keyword&quot;&gt;const&lt;/span&gt; fileList &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataTransfer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;files&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;fileList&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;&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Event/stopPropagation&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;event.stopPropagation()&lt;/code&gt;&lt;/a&gt; and
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/Event/preventDefault&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;event.preventDefault()&lt;/code&gt;&lt;/a&gt; stop the browser&#39;s default
behavior and allow your code to run instead. Without them,
the browser would otherwise navigate away from your page and open the files
the user dropped into the browser window.&lt;/p&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://custom-drag-and-drop.glitch.me/&quot; rel=&quot;noopener&quot;&gt;Custom drag-and-drop&lt;/a&gt; for a live demonstration.&lt;/p&gt;
&lt;h3 id=&quot;directories&quot;&gt;What about directories? &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#directories&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unfortunately, today there isn&#39;t a good way to access a directory.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/HTMLInputElement/webkitdirectory&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;webkitdirectory&lt;/code&gt;&lt;/a&gt; attribute on the &lt;code&gt;&amp;lt;input type=&amp;quot;file&amp;quot;&amp;gt;&lt;/code&gt; element allows the user to choose a directory or directories. It&#39;s
&lt;a href=&quot;https://caniuse.com/#search=webkitdirectory&quot; rel=&quot;noopener&quot;&gt;supported in most major browsers&lt;/a&gt; except for Firefox
for Android.&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; Check if the &lt;a href=&quot;https://web.dev/file-system-access/#opening-a-directory-and-enumerating-its-contents&quot;&gt;&lt;code&gt;window.showDirectoryPicker()&lt;/code&gt;&lt;/a&gt; method is a viable alternative for your use case, since it also gives you a directory handle so you can possibly write back to the directory, in addition to reading. This method can be &lt;a href=&quot;https://github.com/GoogleChromeLabs/browser-fs-access#opening-directories&quot;&gt;polyfilled&lt;/a&gt;. &lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;If drag-and-drop is enabled, a user may try to drag a directory into the
drop zone. When the drop event is fired, it will include a &lt;code&gt;File&lt;/code&gt; object for
the directory, but does not provide access any of the files within the
directory.&lt;/p&gt;
&lt;h2 id=&quot;read-metadata&quot;&gt;Read file metadata &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#read-metadata&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/File&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;File&lt;/code&gt;&lt;/a&gt; object contains metadata about
the file. Most browsers provide the file name, the size of the file, and the
MIME type, though depending on the platform, different browsers may provide
different, or additional information.&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;getMetadataForFileList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;fileList&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;const&lt;/span&gt; file &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; fileList&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;// Not supported in Safari for iOS.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;NOT SUPPORTED&#39;&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;// Not supported in Firefox for Android or Opera for Android.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; file&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 string&quot;&gt;&#39;NOT SUPPORTED&#39;&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;// Unknown cross-browser support.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; size &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;size &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;size &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;NOT SUPPORTED&#39;&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 punctuation&quot;&gt;{&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; size&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;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;You can see this in action in the &lt;a href=&quot;https://input-type-file.glitch.me/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;input-type-file&lt;/code&gt;&lt;/a&gt; demo.&lt;/p&gt;
&lt;h2 id=&quot;read-content&quot;&gt;Read a file&#39;s content &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#read-content&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To read a file, use &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileReader&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;FileReader&lt;/code&gt;&lt;/a&gt;, which enables you to read
the content of a &lt;code&gt;File&lt;/code&gt; object into memory. You can instruct &lt;code&gt;FileReader&lt;/code&gt;
to read a file as an &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileReader/readAsArrayBuffer&quot; rel=&quot;noopener&quot;&gt;array buffer&lt;/a&gt;, a
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileReader/readAsDataURL&quot; rel=&quot;noopener&quot;&gt;data URL&lt;/a&gt;, or &lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileReader/readAsText&quot; rel=&quot;noopener&quot;&gt;text&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 keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;file&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;// Check if the file is an image.&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;file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;image/&#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 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;File is not an image.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&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;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reader &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;FileReader&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;  reader&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; &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 operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    img&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;result&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;  reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readAsDataURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&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 example above reads a &lt;code&gt;File&lt;/code&gt; provided by the user, then converts it to a
data URL, and uses that data URL to display the image in an &lt;code&gt;img&lt;/code&gt; element.
Check out the &lt;a href=&quot;https://read-image-file.glitch.me/&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;read-image-file&lt;/code&gt;&lt;/a&gt; demo to see how to
verify that the user has selected an image file.&lt;/p&gt;
&lt;div class=&quot;glitch-embed-wrap&quot; style=&quot;height: 480px; width: 100%;&quot;&gt;
  &lt;iframe allow=&quot;camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi&quot; loading=&quot;lazy&quot; src=&quot;https://glitch.com/embed/#!/embed/read-image-file?attributionHidden=true&amp;sidebarCollapsed=true&amp;previewSize=100&quot; style=&quot;height: 100%; width: 100%; border: 0;&quot; title=&quot;read-image-file on Glitch&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;h3 id=&quot;monitor-progress&quot;&gt;Monitor the progress of a file read &lt;a class=&quot;headline-link&quot; href=&quot;https://web.dev/read-files/#monitor-progress&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When reading large files, it may be helpful to provide some UX to indicate
how far the read has progressed. For that, use the
&lt;a href=&quot;https://developer.mozilla.org/docs/Web/API/FileReader/progress_event&quot; rel=&quot;noopener&quot;&gt;&lt;code&gt;progress&lt;/code&gt;&lt;/a&gt; event provided by &lt;code&gt;FileReader&lt;/code&gt;. The
&lt;code&gt;progress&lt;/code&gt; event provides two properties, &lt;code&gt;loaded&lt;/code&gt; (the amount read) and
&lt;code&gt;total&lt;/code&gt; (the amount to read).&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;highlight-line&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;file&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reader &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;FileReader&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  reader&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; &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 operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;    &lt;span class=&quot;token comment&quot;&gt;// Do something with result&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&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&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;/span&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;  reader&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;progress&#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 parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&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;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loaded &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;total&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; percent &lt;span class=&quot;token operator&quot;&gt;=&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;loaded &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;total&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;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&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 template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Progress: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&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;round&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;percent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&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;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/mark&gt;&lt;br /&gt;&lt;mark class=&quot;highlight-line highlight-line-active&quot;&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;/mark&gt;&lt;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;  reader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;readAsDataURL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&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;br /&gt;&lt;span class=&quot;highlight-line&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;p&gt;Hero image by Vincent Botta from &lt;a href=&quot;https://unsplash.com/photos/bv_rJXpNU9I&quot; rel=&quot;noopener&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
</content>
    <author>
      <name>Kayce Basques</name>
    </author><author>
      <name>Pete LePage</name>
    </author><author>
      <name>Thomas Steiner</name>
    </author>
  </entry>
</feed>
