<?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: Nikhil Kumar Swain</title>
    <description>The latest articles on DEV Community by Nikhil Kumar Swain (@nikhilswain).</description>
    <link>https://dev.to/nikhilswain</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F254651%2F5b25f613-69e1-45d3-bc15-2c490ffe42af.jpg</url>
      <title>DEV Community: Nikhil Kumar Swain</title>
      <link>https://dev.to/nikhilswain</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nikhilswain"/>
    <language>en</language>
    <item>
      <title>Scroll-scrubbed video that stays smooth with any video file</title>
      <dc:creator>Nikhil Kumar Swain</dc:creator>
      <pubDate>Fri, 09 Oct 2026 15:01:53 +0000</pubDate>
      <link>https://dev.to/nikhilswain/scroll-scrubbed-video-that-stays-smooth-with-any-video-file-5d8k</link>
      <guid>https://dev.to/nikhilswain/scroll-scrubbed-video-that-stays-smooth-with-any-video-file-5d8k</guid>
      <description>&lt;p&gt;A video that moves with the page is one of the nicer effects on the web. A product turns as you scroll, or a scene plays out at the reader's pace. It looks like a few lines of JavaScript, and with the right file it is. With a video exported straight from an editor, the same few lines stutter, and scrolling backwards is worse. This article explains where that stutter comes from, what the usual fixes cost, and the approach I ended up building into a small library, &lt;a href="https://vidscroll.js.org/" rel="noopener noreferrer"&gt;vidscroll&lt;/a&gt;, so that any video file scrubs smoothly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxcz0zvfxg90f8w9ubhoc.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxcz0zvfxg90f8w9ubhoc.webp" alt="An animated scene of passengers in a train carriage, with a caption over the video and a list of six scenes on the right" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1. A scroll-scrubbed video: it moves only as fast as the page scrolls. Animation by roenais.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The obvious approach: set &lt;code&gt;currentTime&lt;/code&gt; on scroll
&lt;/h2&gt;

&lt;p&gt;My first version was the obvious one. Pin a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt;, put a tall spacer behind it, and on each animation frame map the scroll position to a time in the video. Simplified, it comes down to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onScroll&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;requestAnimationFrame&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;spacer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollHeight&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;innerHeight&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;progress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scrollY&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;max&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scroll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onScroll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;passive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a video prepared for scrubbing, this keeps up with the scroll. With a typical export, the same code stutters.&lt;/p&gt;

&lt;p&gt;Nothing in the handler changes between those two cases. The only difference is the file, so that's where the problem is. Rewriting the scroll logic won't fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why exported videos stutter when you scrub them
&lt;/h2&gt;

&lt;p&gt;A video file doesn't store every frame as a picture. Every so often it stores a complete one, a keyframe, and the frames in between only store what changed. To show any frame, the browser starts at the keyframe before it and decodes forward.&lt;/p&gt;

&lt;p&gt;Normal playback never notices, because it only moves forward. Scrubbing jumps to a new point many times a second, in both directions, and every jump pays for the walk from the previous keyframe.&lt;/p&gt;

&lt;p&gt;Most exported videos have keyframes a few seconds apart, because that keeps files small. A 60-second 1080p export I tested had 8 keyframes in total, one every 8.3 seconds. On the demo clip I measured, the median seek took 287 ms. That's under four new frames per second, however fast the rest of the page runs.&lt;/p&gt;

&lt;p&gt;For scrubbing, keyframe spacing matters more than resolution, bitrate or codec. Lowering the resolution of a file with sparse keyframes rarely fixes the stutter.&lt;/p&gt;

&lt;p&gt;You can check a file with &lt;code&gt;ffprobe&lt;/code&gt;, which lists the time of every keyframe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffprobe &lt;span class="nt"&gt;-v&lt;/span&gt; error &lt;span class="nt"&gt;-select_streams&lt;/span&gt; v:0 &lt;span class="nt"&gt;-skip_frame&lt;/span&gt; nokey &lt;span class="nt"&gt;-show_entries&lt;/span&gt; &lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pts_time &lt;span class="nt"&gt;-of&lt;/span&gt; &lt;span class="nv"&gt;csv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 hero.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gaps of up to about half a second scrub well. Anything much longer and every seek gets expensive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbsjm6d029v1fq7y4c9m7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbsjm6d029v1fq7y4c9m7.webp" alt="Two timelines of the same clip: keyframes every few seconds on top, every third of a second below" width="800" height="183"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2. The same clip as exported (top) and prepared for scrubbing (bottom).&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Two common fixes, and what they cost
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Prepare every file
&lt;/h3&gt;

&lt;p&gt;Re-encode the video with keyframes close together, for example one every 10 frames. Scrubbing becomes smooth, because any frame is at most about 10 frames from a keyframe. With ffmpeg, for a landscape video, the flags that matter are these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; hero.mp4 &lt;span class="nt"&gt;-an&lt;/span&gt; &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="nv"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-2&lt;/span&gt;:720 &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-preset&lt;/span&gt; slow &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-g&lt;/span&gt; 10 &lt;span class="nt"&gt;-keyint_min&lt;/span&gt; 10 &lt;span class="nt"&gt;-sc_threshold&lt;/span&gt; 0 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart hero.scroll.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-g 10 -keyint_min 10&lt;/code&gt; puts a keyframe every 10 frames, and &lt;code&gt;-sc_threshold 0&lt;/code&gt; stops the encoder adding extra ones at scene cuts.&lt;/p&gt;

&lt;p&gt;The cost is that someone has to do it for every video, every time the video changes, and the files get bigger:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;The same 60 s clip&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Keyframes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original export, 1080p&lt;/td&gt;
&lt;td&gt;4.7 MB&lt;/td&gt;
&lt;td&gt;8, one every 8.3 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prepared, 720p&lt;/td&gt;
&lt;td&gt;11.9 MB&lt;/td&gt;
&lt;td&gt;180, one every 0.33 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prepared, 1080p&lt;/td&gt;
&lt;td&gt;22.4 MB&lt;/td&gt;
&lt;td&gt;180, one every 0.33 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Decode every frame up front
&lt;/h3&gt;

&lt;p&gt;Decode all frames into images ahead of time and draw them to a canvas. Scrubbing is then instant, whatever the file.&lt;/p&gt;

&lt;p&gt;The cost is memory. A decoded 1080p frame is about 8 MB, so 10 seconds at 30 fps comes to about 2.5 GB. That works for a few seconds of small video, and not for a minute of HD on a phone.&lt;/p&gt;

&lt;h2&gt;
  
  
  A third route: fix the file only when it needs fixing
&lt;/h2&gt;

&lt;p&gt;The third route is to take whatever file you're given, keep it compressed, and fix it only when its keyframes are too far apart. That takes four pieces, and vidscroll is built from them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read the keyframe layout without decoding
&lt;/h3&gt;

&lt;p&gt;An MP4 already says where its keyframes are. The sample table in the file's index lists which frames are keyframes (the &lt;code&gt;stss&lt;/code&gt; box) and how long each frame lasts (&lt;code&gt;stts&lt;/code&gt;), and fragmented MP4s carry the same information in their &lt;code&gt;moof&lt;/code&gt;/&lt;code&gt;trun&lt;/code&gt; boxes. Reading those boxes takes a small parser and no decoding at all; in vidscroll it's &lt;code&gt;src/core/probe.ts&lt;/code&gt;. Other containers, such as WebM, need a demuxer instead.&lt;/p&gt;

&lt;p&gt;From that list, the largest gap between keyframes tells you whether the file is fine as it is. vidscroll uses 0.5 seconds as the limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Re-encode in the browser with WebCodecs
&lt;/h3&gt;

&lt;p&gt;If the gaps are too long, the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebCodecs_API" rel="noopener noreferrer"&gt;WebCodecs API&lt;/a&gt; lets the page re-encode the video with the browser's own encoder. vidscroll uses &lt;a href="https://mediabunny.dev/" rel="noopener noreferrer"&gt;Mediabunny&lt;/a&gt; on top of it, loaded only when a re-encode is needed, so pages with prepared files never download it.&lt;/p&gt;

&lt;p&gt;The copy has a keyframe every quarter of a second, a short side of at most 720 px, at most 30 frames per second, and no audio. In my tests, a 60-second 1080p clip took about 8 to 10 seconds to re-encode on a desktop computer and about 20 seconds on a phone. Shorter clips are proportionally faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache the result
&lt;/h3&gt;

&lt;p&gt;Re-encoding on every visit would waste those seconds each time. The copy goes into the browser's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage" rel="noopener noreferrer"&gt;Cache Storage&lt;/a&gt;, so later visits load it straight away, with no download and no re-encode.&lt;/p&gt;

&lt;p&gt;The cache key has to change whenever the result would. vidscroll ties each entry to the video's URL, the re-encode settings, and the file's &lt;code&gt;ETag&lt;/code&gt;, &lt;code&gt;Last-Modified&lt;/code&gt; or &lt;code&gt;Content-Length&lt;/code&gt;, so replacing the file on the server starts over.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let people scroll while it works
&lt;/h3&gt;

&lt;p&gt;Waiting behind a spinner would leave the page blank for the whole re-encode. Instead, the downloaded original goes on screen straight away.&lt;/p&gt;

&lt;p&gt;Scrolling forward plays it, with &lt;code&gt;playbackRate&lt;/code&gt; following the scroll speed. Playing forward never goes back to a keyframe, so it stays smooth even on a badly encoded file. Scrolling backwards still has to seek, so it's choppy for those few seconds.&lt;/p&gt;

&lt;p&gt;When the copy is ready, a second &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; stacked underneath seeks to the current time and then takes over. It lands on exactly the frame on screen, so the switch isn't visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it on a page
&lt;/h2&gt;

&lt;p&gt;In React, a &lt;code&gt;&amp;lt;ScrollVideo&amp;gt;&lt;/code&gt; is a block in your page, and each &lt;code&gt;&amp;lt;Section&amp;gt;&lt;/code&gt; is active over part of the scroll:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ScrollVideo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Section&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vidscroll&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Hero&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollVideo&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/videos/hero.mp4"&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"400vh"&lt;/span&gt; &lt;span class="na"&gt;smoothScroll&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Section&lt;/span&gt; &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.33&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Intro&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Section&lt;/span&gt; &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.33&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.66&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Middle&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Section&lt;/span&gt; &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mf"&gt;0.66&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Outro&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ScrollVideo&lt;/span&gt;&lt;span class="p"&gt;&amp;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;&lt;code&gt;src&lt;/code&gt; is any URL to a video file: your public folder, a CDN, or a bundler import. Without React, the experimental &lt;a href="https://vidscroll.js.org/docs/without-react/element/" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;vid-scroll&amp;gt;&lt;/code&gt; element&lt;/a&gt; does the same on any page.&lt;/p&gt;

&lt;p&gt;For production, the package's CLI runs that same kind of ffmpeg command for you, so visitors never wait for a re-encode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx vidscroll encode public/videos/hero.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I built it for landing pages, homepages and storytelling sites, the kind of scrollytelling where text is tied to scenes and the reader sets the pace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it doesn't fit yet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Long videos, especially at 1080p.&lt;/strong&gt; Anything over about two minutes or 100 MB gets slow to download and to re-encode, and vidscroll warns in the console. Trim the video, or prepare it with the CLI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The browser's copy is bigger than the CLI's.&lt;/strong&gt; For the 60-second clip, the in-browser re-encode made 18.6 MB where the CLI makes 11.9 MB at the same resolution. It only lives in the visitor's browser, but it's one more reason to prepare videos for production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first visit to an unprepared video.&lt;/strong&gt; Scrolling backwards is choppy until the smooth copy arrives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Videos with sound.&lt;/strong&gt; Scroll-driven video is always muted, and the re-encode drops the audio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching needs the right headers.&lt;/strong&gt; Without &lt;code&gt;ETag&lt;/code&gt;, &lt;code&gt;Last-Modified&lt;/code&gt; or &lt;code&gt;Content-Length&lt;/code&gt;, there's no way to tell whether the file changed, so the re-encode runs on every visit. Caching also needs HTTPS or &lt;code&gt;localhost&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Before you build your next scroll video
&lt;/h2&gt;

&lt;p&gt;Whatever you build it with, three things decide whether it feels smooth:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check the keyframes before the code.&lt;/strong&gt; If scrubbing stutters, look at the file first. Gaps over half a second will hurt, however good the scroll handler is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prepare the files you control.&lt;/strong&gt; One ffmpeg command with closely spaced keyframes beats fixing it at runtime: a smaller download and smooth scrubbing from the first visit. Use 720p unless the video fills a large screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For the files you don't, fix them once and remember it.&lt;/strong&gt; Read the container instead of decoding it, re-encode only when the gaps are too long, cache the result, and keep the page scrollable while that runs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the whole idea behind vidscroll, and you can try it without installing anything. Drop one of your own videos into the &lt;a href="https://vidscroll.js.org/docs/videos/checker/" rel="noopener noreferrer"&gt;video checker&lt;/a&gt;: it shows every keyframe, times seeks on your device and gives you the command to fix the file. The video is read in the page and never uploaded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjgoddc61t7fzi6ddbdk3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjgoddc61t7fzi6ddbdk3.webp" alt="The vidscroll video checker reporting 8 keyframes in a 60-second 1080p file, a median seek of 272 ms, and the command to prepare it" width="800" height="992"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3. The checker on the 60-second export from earlier: keyframes 8.3 s apart and seeks too slow to scrub smoothly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the result looks like the one above, you've found your stutter, and fixing it is one command. To see the rest, there are &lt;a href="https://vidscroll.js.org/#demos" rel="noopener noreferrer"&gt;demos&lt;/a&gt;, a &lt;a href="https://vidscroll.js.org/playground/" rel="noopener noreferrer"&gt;playground&lt;/a&gt; for the options, and a longer explanation of &lt;a href="https://vidscroll.js.org/docs/start/how-it-works/" rel="noopener noreferrer"&gt;how it works&lt;/a&gt;. The code is on &lt;a href="https://github.com/nikhilswain/vidscroll" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, and the package is on &lt;a href="https://www.npmjs.com/package/vidscroll" rel="noopener noreferrer"&gt;npm&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i vidscroll
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scroll-driven video is one of the few effects on the web that hands the pace of a story to the reader. When it stutters, the whole effect falls apart, and it's easy to blame the scroll code, the framework or the device. Often the cause is sitting in the file: keyframes a few seconds apart. Once you know that, it stops being a mystery and becomes a fixable problem, whether you fix it with one ffmpeg command, build the runtime fix yourself, or let a library do it.&lt;/p&gt;

&lt;p&gt;If you try it on a video that doesn't behave, &lt;a href="https://github.com/nikhilswain/vidscroll/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; with the checker's report. Odd files make the best test cases.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://zerro.dev/blog/smooth-scroll-scrubbed-video-any-file/" rel="noopener noreferrer"&gt;zerro.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>react</category>
      <category>animation</category>
    </item>
    <item>
      <title>5 Famous Array Methods in JavaScript.</title>
      <dc:creator>Nikhil Kumar Swain</dc:creator>
      <pubDate>Mon, 16 Sep 2019 15:03:03 +0000</pubDate>
      <link>https://dev.to/nikhilswain/5-famous-array-methods-in-javascript-1i6a</link>
      <guid>https://dev.to/nikhilswain/5-famous-array-methods-in-javascript-1i6a</guid>
      <description>&lt;h4&gt;
  
  
  map() , reduce() , filter() , some() , every() .
&lt;/h4&gt;

&lt;p&gt;JavaScript provide us lots of useful methods to ease our work.In this article we will take a closer look at some JavaScript famous &lt;strong&gt;Array methods&lt;/strong&gt; that will help us to code efficiently.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcdqrc0pl0vyi0jyc0nk5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcdqrc0pl0vyi0jyc0nk5.png" width="627" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Array.prototype.map()&lt;/strong&gt;
The map() method creates a &lt;strong&gt;new array&lt;/strong&gt; with the results of calling a function for every array element.It accepts a callback function as a parameter and we have access to a ccurrent value of an array, current index and array itself as well.
&lt;strong&gt;Syntax :&lt;/strong&gt; &lt;em&gt;array&lt;/em&gt;.map(&lt;em&gt;function(currentValue, index, arr), thisValue&lt;/em&gt;)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const array = [2,4,6,8];

const newArr = array.map(val =&amp;gt; val*2);

// [4, 8, 12, 16]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could also pass a callback function as a parameter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const callbackFunc = val =&amp;gt; val + 2;

const array = [2,4,6,8];

cons newArr = array.map(sum)

// [4, 6, 8, 10]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chaining is also available in so we can do something like this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const sum = val =&amp;gt; val + 2;

const mul = val =&amp;gt; val * 2;

const array = [1, 2, 3, 4, 5];

const newArray = array.map(sum).map(mul);

// [6, 8, 10, 12, 14]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Array.prototype.reduce()&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The reduce() method executes a &lt;strong&gt;reducer&lt;/strong&gt; function (that you provide) on each element of the array, resulting in a single output value.&lt;br&gt;&lt;br&gt;
The reducer function takes four arguments:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Accumulator&lt;/li&gt;
&lt;li&gt;Current Value&lt;/li&gt;
&lt;li&gt;Current Index&lt;/li&gt;
&lt;li&gt;Source Array&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your reducer function’s returned value is assigned to the accumulator, whose value is remembered across each iteration throughout the array and ultimately becomes the final, single resulting value.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Syntax :&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;array&lt;/em&gt;.reduce(&lt;em&gt;function(total, currentValue, currentIndex, arr), initialValue&lt;/em&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function myFunc( total, num ) {
return total + num ;
}

const numbers = [30, 20, 10];

numbers.reduce(myFunc)
// 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also provide an initial value instead of taking the first value as an accumulator by default :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const num = [5, 10, 15, 10];

const sum = num.reduce(( acc, val ) =&amp;gt; acc + val, 100);

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Array.prototype.filter()&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The filter() method creates a new array with all elements that pass the test implemented by the provided function.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Syntax :&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
const newArray = arr.filter(callback(element[, index[, array]])[, thisArg])&lt;/p&gt;

&lt;p&gt;callback : Function is a predicate, to test each element of the array. Return true to keep the element, false otherwise.&lt;/p&gt;

&lt;p&gt;It accepts three arguments:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;element&lt;/em&gt; : The current element being processed in the array.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;index :&lt;/em&gt; (Optional) The index of the current element being processed in the array.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;array&lt;/em&gt; : (Optional) The array filter was called upon.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;thisArg :&lt;/em&gt; (Optional) Value to use as this when executing callback.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const band = ['Against', 'the', 'current', 'is', 'my', 'favourite', 'band'];


const newBand = band.filter( word =&amp;gt; word.length &amp;gt; 5 );

// ["Against", "current", "favourite"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s look at another example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const isGreater = (val) =&amp;gt; {
return val &amp;gt; 12;
}

const realNumbers = [3,6,12,15,18,21].filter(isGreater);

// [15, 18, 21]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Array.prototype.some()&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
As the name suggest the some() method tests whether at least &lt;strong&gt;one element&lt;/strong&gt; in the array passes the test implemented by the provided function. It returns a Boolean value .&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Syntax :&lt;/strong&gt; &lt;em&gt;array&lt;/em&gt;.some(&lt;em&gt;function(currentValue, index, arr), thisValue&lt;/em&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const names = [const names = ['sheldon', 'lenoard', 'raj', 'howard', 'penny'];

names.some(having =&amp;gt; having === 'sheldon');

// true

names.some(having =&amp;gt; having === 'chrissy');

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

&lt;/div&gt;



&lt;p&gt;Another example of some() method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const number = [5,7,10,12,15,17,20];

number.some(mulOfFive =&amp;gt; mulOfFive % 5 === 0);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Array.prototype.every()&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Unlike some() the every() method tests whether all elements in the array pass the test implemented by the provided function. It also returns a Boolean value.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Syntax :&lt;/strong&gt; &lt;em&gt;array&lt;/em&gt;.every(&lt;em&gt;function(currentValue, index, arr), thisValue&lt;/em&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const band = ['ATC','ATC','ATC','ATC'];

band.every(having =&amp;gt; having === 'ATC');

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

&lt;/div&gt;



&lt;p&gt;Another example of some() method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const marks = [80, 75, 52, 91, 63];

marks.every(mark =&amp;gt; mark &amp;gt; 50);

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

&lt;/div&gt;



</description>
      <category>arraymethods</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
