<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chris How</title>
    <description>The latest articles on DEV Community by Chris How (@chrishow).</description>
    <link>https://dev.to/chrishow</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1191206%2F58c9acd6-5bde-4e0d-b6e6-afd38ae7717a.png</url>
      <title>DEV Community: Chris How</title>
      <link>https://dev.to/chrishow</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chrishow"/>
    <language>en</language>
    <item>
      <title>Some notes on SharedWorkers</title>
      <dc:creator>Chris How</dc:creator>
      <pubDate>Sun, 03 Nov 2024 09:01:36 +0000</pubDate>
      <link>https://dev.to/chrishow/some-notes-on-sharedworkers-3kkh</link>
      <guid>https://dev.to/chrishow/some-notes-on-sharedworkers-3kkh</guid>
      <description>&lt;p&gt;I recently needed to implement a shared worker in a project. Though they are super useful, there wasn't much info to be found in the usual places, so here are some pointers that might help searchers from the mysterious future. &lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker" rel="noopener noreferrer"&gt;SharedWorkers&lt;/a&gt; are a special class of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API" rel="noopener noreferrer"&gt;WebWorker&lt;/a&gt; that can be shared across multiple tabs, windows or other (regular) web workers. &lt;/p&gt;

&lt;p&gt;In my application, I needed a process which would poll for new application events (for example, a customer completing a purchase), and show a notification (using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API" rel="noopener noreferrer"&gt;Notifications API&lt;/a&gt;) to logged in administrators (or more specifically, those logged in administrators who had chosen to receive notifications). &lt;/p&gt;

&lt;p&gt;An administrator could have the application open in several tabs or windows, so it would be wasteful to have each tab polling for new events. I only wanted one notification per event, regardless of the number of open tabs or windows.&lt;/p&gt;

&lt;p&gt;SharedWorker to the rescue! Each of the open tabs or windows shares a single worker, which polls in the background, and shows just one notification per new event. &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a shared worker with Vite
&lt;/h2&gt;

&lt;p&gt;The first challenge was loading the shared worker in my Vite-based setup. &lt;/p&gt;

&lt;p&gt;If you're running Vite in dev mode, Vite serves the script from a different domain and port (eg &lt;code&gt;http://[::1]:5173/&lt;/code&gt;), which won't work, because shared workers must obey the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy" rel="noopener noreferrer"&gt;same-origin policy&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I tried various Vite workarounds for web workers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The official Vite web worker method doesn’t work for &lt;em&gt;shared&lt;/em&gt; workers due to the same-origin policy requirement. &lt;/li&gt;
&lt;li&gt;Blob URLs aren't supported for shared workers. &lt;/li&gt;
&lt;li&gt;Inlining the worker as a base64 string doesn’t work because the browser treats them as different workers: fine for web workers, but not for shared workers. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the end, I created a new route to serve the script either from the resources directory in dev, or the build directory in staging and live environments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;addRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/notifications-shared-worker.js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// If in dev environment, send the file from the resources folder&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'local'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;resource_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'js/notificationWatcherWorker.js'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text/javascript'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Otherwise, send the file from the public folder&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;public_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'build/assets/notificationWatcherWorker.js'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text/javascript'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;I then create the shared worker with that route as the URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SharedWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/notifications-shared-worker.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Debugging the Shared Worker
&lt;/h2&gt;

&lt;p&gt;You'll quickly find that any syntax or runtime errors in your shared worker don't appear in your devtools. Nor do any console log/warn/info calls. &lt;/p&gt;

&lt;p&gt;This one is easy, paste &lt;code&gt;chrome://inspect/#workers&lt;/code&gt; into your URL bar, find the shared worker and click on 'inspect'. Now you have a devtools window just for the shared worker. &lt;/p&gt;

&lt;h2&gt;
  
  
  Communicating back to the main tab or window
&lt;/h2&gt;

&lt;p&gt;To communicate back to the ‘parent’ tab, use the &lt;code&gt;port.postMessage&lt;/code&gt; method, as described in the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker" rel="noopener noreferrer"&gt;MDN SharedWorker documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, the example code only allows communication with the most recent ‘parent’ tab/window because it overwrites the communication port reference each time a parent connects.&lt;/p&gt;

&lt;p&gt;Instead, store an array of ports, and add each new port to the array when a new ‘parent’ connects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="nx"&gt;onconnect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get the port for this new connection&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="c1"&gt;// Add it to our array of ports&lt;/span&gt;
    &lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, send a message to all the parent pages like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="na"&gt;flavor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vanilla&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>sharedworker</category>
      <category>vite</category>
    </item>
    <item>
      <title>SQLite, case sensitivity, and Laravel testing</title>
      <dc:creator>Chris How</dc:creator>
      <pubDate>Thu, 21 Dec 2023 13:01:43 +0000</pubDate>
      <link>https://dev.to/chrishow/sqlite-case-sensitivity-and-laravel-testing-2en5</link>
      <guid>https://dev.to/chrishow/sqlite-case-sensitivity-and-laravel-testing-2en5</guid>
      <description>&lt;p&gt;Imagine you have a bunch of records like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$cars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'model'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Countach'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'registration'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'7075GHX'&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'model'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Mini'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'registration'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'9643tls'&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'model'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Mustang'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'registration'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'8862MBA'&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your user can search for cars by registration. It doesn't matter whether they search for &lt;code&gt;8862MBA&lt;/code&gt; or &lt;code&gt;8862mba&lt;/code&gt;, they're going to find the Mustang, right? &lt;/p&gt;

&lt;p&gt;Yes, that's right. In MySQL, and in MSSQL at least. But then, when you write your tests, you find that you &lt;em&gt;don't&lt;/em&gt; necessarily find the Mustang. Because you're using SQLite as your testing database, and SQLite uses case-sensitive matches. &lt;/p&gt;

&lt;p&gt;You may have already found that the 'where' filter in Laravel collections is case-sensitive, so though you can query the database successfully in a case-insensitive manner, you can't then filter the resulting collection the same way (though &lt;a href="https://gist.github.com/chrishow/b15cc8950504db215db601eee1ffb64c" rel="noopener noreferrer"&gt;here's a simple Collection macro that provides case-insensitive filtering&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So you're left with the icky situation that a &lt;code&gt;where&lt;/code&gt; in an Eloquent query works differently to a &lt;code&gt;where&lt;/code&gt; in a Collection, except where it doesn't. &lt;/p&gt;

&lt;p&gt;So I guess you can try remember to/get your team to always remember to change the case of this kind of supplied data before storing it in the database (😂) or get them to use raw SQL in where clauses (eg &lt;code&gt;-&amp;gt;whereRaw('UPPER(REGISTRATION) = ?', strtoupper($registration))&lt;/code&gt; 💀).&lt;/p&gt;

&lt;p&gt;SQlite supports a &lt;code&gt;collate&lt;/code&gt; clause in its &lt;code&gt;CREATE TABLE&lt;/code&gt; syntax, but this isn't supported in Laravel's Database schema driver for SQLite. &lt;/p&gt;

&lt;p&gt;If there is any interest, I may look into implementing it. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>A better way to build autoplaying feature videos.</title>
      <dc:creator>Chris How</dc:creator>
      <pubDate>Sun, 22 Oct 2023 10:02:57 +0000</pubDate>
      <link>https://dev.to/chrishow/a-simple-and-performant-zoom-on-scroll-element-22k9</link>
      <guid>https://dev.to/chrishow/a-simple-and-performant-zoom-on-scroll-element-22k9</guid>
      <description>&lt;p&gt;As part of the team working the website for self-driving AI company Wayve, we needed a neat way to present an auto-playing video that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retained interactivity without asking much of the user&lt;/li&gt;
&lt;li&gt;Started playing at the right time so the user didn't miss the beginning&lt;/li&gt;
&lt;li&gt;Was a bit "stickier" than just a straight-up embedded video would be. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So we implemented an auto-playing "zoom-on-scroll" element. &lt;/p&gt;

&lt;p&gt;Notice how the video of the London street scene automatically zooms to fill the window and starts playing when scrolled into view:&lt;/p&gt;

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

&lt;p&gt;The video also sticks around a while as you scroll, drawing the user's attention to it. It's not a massive drag to get past it; you don't have to click anything to dismiss it. You just scroll a bit further and the video "un-zooms" and stops playing. &lt;/p&gt;

&lt;p&gt;I've seen similar effects on other sites, but given that we were in a post-Internet Explorer world, I had an opportunity to implement it using modern techniques, leading to less jank, less code, and smoother animations. &lt;/p&gt;

&lt;p&gt;So let's have a look at the steps needed to implement such a feature. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Has the user scrolled the video into view?
&lt;/h2&gt;

&lt;p&gt;The first step is to know when the user scrolls the video element into the viewport. &lt;/p&gt;

&lt;p&gt;In the bad old days, we would calculate the vertical position of the video element, keep track of how far down the user has scrolled the page by trapping the &lt;code&gt;scroll&lt;/code&gt; event, working out whether our video element was inside the viewport, and recalculate everything when the window was resized, or dynamic elements were added or removed from the page. &lt;/p&gt;

&lt;p&gt;But now we can use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API" rel="noopener noreferrer"&gt;Intersection Observer API&lt;/a&gt;, which has been available on all modern browsers since 2019. &lt;/p&gt;

&lt;p&gt;Here is an example of this pattern in use: &lt;/p&gt;

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

&lt;p&gt;Stepping through this example, we first set the options for the observer. We're only interested in the &lt;code&gt;threshold&lt;/code&gt; option: how much of the element must be inside the viewport for the observer to consider the element as inside. &lt;/p&gt;

&lt;p&gt;We're going to plump for &lt;code&gt;1&lt;/code&gt;: consider the element to be 'inside the viewport' when 100% of it is inside the viewport (as opposed to — say — 75%).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;// 100% of element is in viewport&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we're going to add the callback for the intersection observer: what to do when an intersection change is observed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Add class 'in-view' to element if&lt;/span&gt;
    &lt;span class="c1"&gt;// it is within the viewport&lt;/span&gt;
    &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;in-view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intersectionRatio&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we are going to create an intersection observer with those options and that callback action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that this observer can observe any number of video elements, there's no need to create an observer for each element you want to observe; you can simply attach the same observer to as many video elements as you like. &lt;/p&gt;

&lt;p&gt;Here we're just going to attach it to one element for simplicity, the element with the id &lt;code&gt;observed&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;observed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Start the video playing
&lt;/h2&gt;

&lt;p&gt;Now we want to automatically start and stop the video as the user scrolls it into view. &lt;/p&gt;

&lt;p&gt;This is especially handy where you want a video to play without requiring the user to do anything onerous, but you don't want to start autoplaying when the page loads, as the user would miss the start of the video (it's below 'the fold'!)&lt;/p&gt;

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

&lt;p&gt;So we're going to tweak the callback that's supplied to the Insersection Observer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Find the video element inside this container&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isWithinViewport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intersectionRatio&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Add class 'in-view' to element if&lt;/span&gt;
    &lt;span class="c1"&gt;// it is within the viewport&lt;/span&gt;
    &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;in-view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isWithinViewport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isWithinViewport&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Play the video if it's within the viewport      &lt;/span&gt;
      &lt;span class="nx"&gt;videoElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Pause the video if not&lt;/span&gt;
      &lt;span class="nx"&gt;videoElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Zoom the video
&lt;/h3&gt;

&lt;p&gt;This is going to be a CSS-only solution: super fast and jank-free. &lt;/p&gt;

&lt;p&gt;What we're going to do is to use &lt;code&gt;sticky&lt;/code&gt; positioning on the video container element, and it with in an element which is taller than the viewport height. &lt;/p&gt;

&lt;p&gt;If we make the wrapper twice the viewport height, you will have to scroll two 'pages worth' to get past the video. &lt;/p&gt;

&lt;p&gt;So the wrapper will have the following style:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.observed-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* 200% viewport height */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we're going add some rules for the video container, and override the margin when the video container element has the 'in-view' class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.observed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sticky&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="m"&gt;0.2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Use CSS transitions to animate */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* When video is 'in view' */&lt;/span&gt;
&lt;span class="nc"&gt;.observed.in-view&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Remove the side margins */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a similar deal for the video element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;video&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* top spacing */&lt;/span&gt;
    &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;6em&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* bottom spacing */&lt;/span&gt;
    &lt;span class="nl"&gt;object-fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="m"&gt;0.2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* Use CSS transitions to animate */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* When video is 'in view' */&lt;/span&gt;
&lt;span class="nc"&gt;.observed.in-view&lt;/span&gt; &lt;span class="nt"&gt;video&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/* No top spacing, and fill viewport */&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Here's how that looks in action:&lt;/p&gt;

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

&lt;p&gt;And there you go: fast, jank-free, with butter-smooth transition animations. &lt;/p&gt;

&lt;p&gt;You can play with the real thing on the &lt;a href="https://wayve.ai" rel="noopener noreferrer"&gt;Wayve homepage&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>css</category>
      <category>javascript</category>
      <category>html</category>
      <category>animation</category>
    </item>
  </channel>
</rss>
