Skip to content
Home » The secrets for an optimized scroll-based HTML5 video

The secrets for an optimized scroll-based HTML5 video

Recently, I had the opportunity to work on a scroll-based animation website. I tried for the first time the useScroll hook of Framer Motion. It allows us to easily create parallax effects and triggered animations. It’s not the topic, but I recommend you look at it. I’ll probably write a post about Framer Motion later. Please tell me in the comments if you’re interested.

Let’s go back to the point. You’ve probably already seen these Apple product pages with a lot of animations happening while you’re scrolling. They use different solutions to make the animations as smooth as possible, such as CSS translations/rotations/scales, real-time Canvas drawing, animated WebGL models, or video.

Now we are going to focus on the video technique.

Let’s create a demo

First, we write some HTML. We set an inline video, and 4 block elements.

<video id="video" src="YOUR VIDEO PATH" preload="auto" playsinline muted autoplay></video>
<div class="content">
  <div class="block">
    <p class="text">SCROLL DOWN ⇩</p>
  </div>
  <div class="block">
    <p class="text">CONTINUE</p>
  </div>
  <div class="block">
    <p class="text">CONTINUE</p>
  </div>
  <div class="block">
    <p class="text">CONTINUE</p>
  </div>
  <div class="block">
    <p class="text">SCROLL UP ⇧</p>
  </div>
</div>

Then a bit of CSS to set the video as a fixed fullscreen background, and each block element as fullscreen content on top of each other, so we can scroll through the page.

#canvas {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
.content {
  position: relative;
}
.block {
  margin: 0;
  height: 100vh;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
}
.text {
  padding: 8px 12px;
  background-color: white;
}

We finally use JavaScript to get the scroll position and update the video time accordingly on each frame.

// get the video element
const video = document.querySelector('#video')
// update video time according to scroll position on each frame
function animate() {
  // calculate scroll percent by comparing the scroll position to the total scroll height minus the window height
  const currentScrollPercent = window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)
  // update video time by multiplying scroll percent to the video total duration
  if (video.duration) {
    video.currentTime = currentScrollPercent * video.duration
  }
  requestAnimationFrame(animate)
}
requestAnimationFrame(animate)

Bonus: we can add some easing to the scroll and prevent updating the video time when the user is not scrolling.

let smoothScrollPercent = 0
let previousVideoTime = 0
// get the video element
const video = document.querySelector('#video')
// update video time according to scroll position on each frame
function animate() {
  // calculate scroll percent by comparing the scroll position to the total scroll height minus the window height
  const currentScrollPercent = window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)
  // smooth scroll percent
  smoothScrollPercent += (currentScrollPercent - smoothScrollPercent) * 0.1
  // update video time by multiplying scroll percent to the video total duration
  if (video.duration) {
    const currentVideoTime = smoothScrollPercent * video.duration
    // prevent seeking when previous video time is too close from current video time
    if (Math.abs(currentVideoTime - previousVideoTime) > .1) {
      previousVideoTime = currentVideoTime
      video.currentTime = currentVideoTime
    }
  }
  requestAnimationFrame(animate)
}
requestAnimationFrame(animate)

If you followed the steps, your video should now be playing according to the scroll position. You can find below a demo using an Apple product presentation video. On the top right corner you can choose between 3 different options (Regular Video, Optimized Video, Images), please select the “Regular Video” option. We’ll see more about this later.

Scroll-based HTML5 video demo

The Key Frame Interval problem

If you used your own video or selected the “Regular Video” option, you probably would notice some lags while scrolling (except if you’re using a Macbook M1/M2/M…). This is due to the video Key Frame Interval.

The keyframe interval is a fundamental concept in video encoding and compression.
It determines the frequency at which complete frames, called keyframes, are inserted into a video sequence.
These keyframes serve as anchor points, preserving visual details and enabling efficient compression.
The interval influences file size, visual quality, and streaming efficiency, making it crucial to strike a balance.
Definition from hollyland

To make it simple, when you encode a video, all the frames are not fully encoded. The “Regular Video”, for example, is encoded at 72 Key Frame Interval (this is the default setting of Adobe Media Encoder). It means one full frame is encoded every 72 frames.

So when you change the current time of the video player, it is likely to fall under a not fully encoded frame. The video player is less good at processing this kind of frame. In our case, we ask the video player to do it on each requestAnimationFrame. You can imagine that he doesn’t really like it.

The solution is pretty straightforward, you have to encode the video at a lower Key Frame Interval. 1 Key Frame Interval is actually the best, but be careful, the weight of the video will increase a lot. You will have to find the right compromise between quality and lag. You can try this solution in the previous demo by changing the option to “Optimized Video”.

The Android problem

As usual, nothing is easy with HTML5. There is still one more problem… Even with a low Key Frame Interval, it’s still laggy on Android devices. But this time, it is for a different reason. Android is actually not good at handling video scrubbing while using the native scroll. It is possible to overcome this problem using a virtual scroll, but let’s pretend we need to use the native one. How can we do that?

This problem is a good excuse for me to introduce you to another solution. What if we were using images instead of video? Just try selecting the “Images” option in the previous demo. Very smooth!

I will not explain the code to do it, but you can find the full source code of the scroll-based HTML5 video demo on GitHub. To sum up, you have to export all the frames of the video in JPG files (using FFmpeg for example). Then you load them and draw them in a canvas according to the scroll position, the same way we did before.

As you can think, there is also a drawback to this solution. Having all the frames exported as JPG is way more heavy than having one video. It’s not recommended to use this solution with a very long video and no matter what, it will be better to preload all the images before to give control to the user over the scroll.

Are we done?!

As you can see there isn’t really one good solution. You will have to choose the best one according to the length, movements, and colors of your video. But there is actually a library that can help you to create a smooth scrollable video on every device (thanks to Côme for sharing this library with me). You just have to give a regular video, and it handles all the hard work. You can find more details in their “Technical Details and Cross Browser Differences” section. It’s smart and interesting! For example, they sometimes use the WebCodecs API to get all the frames from the video and have them ready to draw to a canvas.

Thank you for reading!

If you liked this article, don’t hesitate to share it on your social networks, it helps me a lot.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *