Scrubbing Video on Scroll


Scroll the video below (Note: only compatible with Chrome, close all tabs for best performance)

During a recent visit to Apple’s website, I noticed an amazing animation on their Macbook product page. As I scrolled down to learn more about their new Macbook, the laptop on the screen opened and changed its orientation based on my scroll. I thought it was so cool, I decided to figure out how it worked. As I dug through the site with Chrome developer tools I realized that the ‘animation’ was actually a very well produced video. I figured out that my scroll was controlling where the ‘playhead’ position was in this video. Simple enough. Now to build my own.

Creating a scrollable video can be broken down into three parts:

  1. Capturing a value to set the playhead position to
  2. Setting the playhead to that value
  3. Repeating this code at a very fast time interval to create smooth video playback
Below you can see the html that creates the flower video above.

<div id="vid-container" style="overflow-y: scroll; height:400px;">
  <div id="vid-container-2" style="height:800px;position;relative">
    <video id="v0" style="position: absolute;left: 0;width: 100%;" tabindex="0" >
      <source type="video/mp4; codecs=&quot;avc1.42E01E, mp4a.40.2&quot;" src=""></source> 
    </video>
  </div>
</div>  

The code creates a small div, which should be just large enough to fit the dimensions of the video. By using ‘overflow-y: scroll;’, any contents included in this div, that are larger than the height of the div, can now be scrolled. So to take advantage of this, I create a taller div inside of my original div. Finally, I include the video and source inside of this div with absolute positioning so it will no move as the content inside the div is scrolled. In order to make this video work in the context of my blog page, I used a lot of inline styling to preserve the integrity of my blog css file. I would reccomend moving all this styling to a sepearte css file if you would plan on creating a scrollable video on your own site.

Below is the javascript that makes the video come alive!

var vid = document.getElementById('v0');

// pause video on load
vid.pause();
 
// pause video on document scroll (stops autoplay once scroll started)
$('#vid-container').onscroll = function(){
    vid.pause();
};

// refresh video frames on interval for smoother playback
// dividing the totalTime scrubTime calculation by 25 determines 
// how many frames will be covered in the span of a 'normal' scroll
// the lower the number, the more frames will be covered in a scroll
setInterval(function(){
    var totalTime = 290
    var scrubTimeSelect = $('#vid-container-2').position();
    var scrubTime = scrubTimeSelect.top
    vid.currentTime = eval(totalTime+ " " + "-" + " " + scrubTime) /25;
}, 40);

I have included some comments to explain what each part of the code is doing. The setInterval function is one of the more powerful parts of this code. It finds how far the div containing the flower video has scrolled, does a calculation to inverse that number (so the flower goes from closed to open and not opened to close), and finally sets the current time of the video. It then repeats this code every 40ms. This allows for smooth playback experience. For clarity, the video is not actually ‘playing’ during any point of this code. This code simply sets a new frame based on how far a user has scrolled and repeats this process very frequently.

To get this to work in a production environment, you will need to load the video file into a blob url so it can be stored in memory instead of sending get requests to a server every 40ms.

$( document ).ready(function() {

  var xhr = new XMLHttpRequest();
  xhr.open('GET', '/images/flower-slow-bloom-4k.mp4', true);
  xhr.responseType = 'blob';

  xhr.onload = function(e) {
    if (this.status == 200) {
      // Note: .response instead of .responseText
      var blob = this.response;
      var vid_url = URL.createObjectURL(blob);
      $('video').attr('src',vid_url)
      var video = $('video');
      video[0].onload = function(){
        debugger;
        URL.revokeObjectURL(vid_url)
      }
      video[0].play()
    }
  };

  xhr.send();
    console.log( "ready!" );
});

One thing to note is the video scroll can become laggy when a user has a lot of tabs open.

What really excites me…

…is other implementations of value input to control video playback. While a scroll is an interesting way of controlling the values passed to vid.currentTime, there are many other possbilities including physical devices, audio input, etc. Additionally, I’m interested in seeing how different video sources could be used instead of being constrained to one video stored locally. Find out more about this project at my Github page to stay up to date and, as always, feel free to contribute and submit a pull request!