How to make sticky, floating videos on page scroll

A few days ago we released version 1.1 of our great new magazine theme for WordPress, Public Opinion, and I’m here to share with you the basics of how we added floating, sticky videos to the theme’s video post formats so that you can easily add it on your own website if you wish!

Here’s what we’ll be making (scroll up and down to see it in action, also start playing the video, it won’t stop while repositioning!):

0

This kind of functionality is surprisingly easy to implement, and it applies to any kind of embedded iframe video, without the need of the video provider’s API or cloning the video element. It merely needs a few lines of CSS and some basic JavaScript (we’ll be using Sass and jQuery for this tutorial).

High level overview

For our floating video we’ll be using simple iframe embeds (the example contains a video from YouTube), and our whole approach will be to check whether the user has scrolled past the video’s bottom. When that happens, we’ll add a class on an element that wraps the iframe (with JavaScript), and we’ll force that element into a fixed position, on the bottom right of the viewport (with CSS). When the user scrolls back up, we’ll be removing that class so that the video gets back into its initial place. Simple enough! Let’s get started!

The code

<div class="video-wrap">
  <div class="video">
    <iframe width="600" height="340" src="https://www.youtube.com/embed/pfqkRYSs4Rg" frameborder="0" gesture="media" allowfullscreen></iframe>
  </div>
</div>
HTML

The HTML we’ll be using is pretty simple, but we need to wrap the iframe with two div elements. The .video element is what we’ll actually position on the bottom right, and we’ll keep its parent (.video-wrap) in place, as a reference of the video’s initial position. It’ll soon become much clearer why.

@keyframes fade-in-up {
  0% { opacity: 0; }
  100% { transform: translateY(0); opacity: 1; }
}

.video {
  iframe {
    max-width: 100%;
    max-height: 100%;
  }

  &.stuck {
    position: fixed;
    bottom: 20px;
    right: 20px;
    width: 260px;
    height: 145px;
    transform: translateY(100%);
    animation: fade-in-up .25s ease forwards;
  }
}
CSS

That’s pretty much every style we need including the slide up animation.

Notice that we pretty much only provide styles for when the video is stuck (i.e. when it has the .stuck class), and just force the underlying iframe to take up its dimensions. As you can see, what we’re doing here is placing it on the bottom right of the viewport, and reducing its size (width and height). For the added effect, we also sprinkle a fancy sliding up CSS animation.

Making it all work with JavaScript

We’ll just need a mere seventeen lines of jQuery to make everything work:

var $window = $(window);
var $videoWrap = $('.video-wrap');
var $video = $('.video');
var videoHeight = $video.outerHeight();

$window.on('scroll',  function() {
  var windowScrollTop = $window.scrollTop();
  var videoBottom = videoHeight + $videoWrap.offset().top;
  
  if (windowScrollTop > videoBottom) {
    $videoWrap.height(videoHeight);
    $video.addClass('stuck');
  } else {
    $videoWrap.height('auto');
    $video.removeClass('stuck');
  }
});
Making the video float on scroll

Let’s break it down.

On the first few lines we cache the required elements into variables, as a good practice. This avoids needless allocations every time we scroll in the page.

Notice that on line 4 we also store the original video’s height:

...
var videoHeight = $video.outerHeight();
...
Storing the video wrapper's height for later use

The reason we do this is to keep the content below the video from jumping around when we apply position: fixed to the video element. Remember that fixed position elements (like absolutely positioned ones) escape the page’s flow and leave “empty space” behind. When the video floats, we’ll apply this height to its parent wrapper making the content below completely oblivious to the video’s absence. That’s one of the reasons why we needed an extra parent element.

After this basic set up, we’re attaching a scroll event listener to the window element. While we scroll, we keep checking whether the total amount of the window’s vertical scroll is bigger than the distance from the bottom of the video element to the top of the document.

To get the total amount the page is scrolled at any point we use jQuery’s handy .scrollTop method on the window object which gives us exactly that.

To get the bottom vertical position of an element, we only need to calculate its top offset (which is the distance from the top of the element to the top of the document) plus the element’s height. Here’s a small diagram to help you visualize it:

So all we need to check is if the window’s scrollTop amount is greater than the video bottom’s distance from the top, and when it’s true, make the video float by simply applying the .stuck class we coded earlier with CSS.

At the same time, we also apply the initial height to the video’s outermost parent to prevent the content from jumping. When the scroll is back up and the video gets into view (i.e. when windowScrollTop is less than videoBottom) we remove the stuck CSS class and reset the parent’s height to auto!

...

$window.on('scroll',  function() {
  var windowScrollTop = $window.scrollTop();
  var videoBottom = videoHeight + $videoWrap.offset().top;
  
  if (windowScrollTop > videoBottom) {
    $videoWrap.height(videoHeight);
    $video.addClass('stuck');
  } else {
    $videoWrap.height('auto');
    $video.removeClass('stuck');
  }
});
Making the video float

And that’s it! Keep in mind that this technique will work with any kind of video provider, YouTube, Vimeo (pretty much with all of WordPress’s supported video providers), and also any kind of element (doesn’t have to be a video!).

Here’s the final result again:

0

What do you think of floating videos? Helpful or distracting? Let us know in the comments! (Thankfully, our themes include an on/off setting, like with most features!)

Related Articles

11 comments

  1. Alexei says:

    Hello! I really liked it, but it’s impossible to make the video adaptive, help?

    1. Vassilis Mastorostergios says:

      Hello, can you let me know exactly what you mean adaptive?

  2. Maurice says:

    Will this work with a vimeo video as well?

    1. Vassilis Mastorostergios says:

      Absolutely! In fact, with every video provider or embed.

  3. hossein says:

    do we have to use your external js and css?
    how we can use just this js and css?

    1. Vassilis Mastorostergios says:

      Heya, every theme should have its own style.css so the styles can go there. Also pretty much every theme has its own JavaScript code in a file somewhere, so we suggest the scripts go there :)

  4. danilo says:

    cant make it work on blogger, what do i do ???

    1. Nik says:

      Hello.
      Assuming you have added the wrapper class of the video properly and placed the JavaScript bit on a proper place in the code, try going here https://codepen.io/vmasto_1470672674/pen/KyyMwq and in the SCSS box, click the top right arrow and select View Compiled CSS, then grab the CSS and paste it in your stylesheet.

  5. Fernando Flores says:

    It is really fantastic, thank you very much for the work. a queries please, if I have more than one video in a publication how to avoid overlapping one another when scrolling through the page. and another question how to put a button to close the floating video.

    1. Nik says:

      Hello Fernando.
      Our simple tutorial here was not built with hosting multiple videos on one page I’m afraid and expanding it to do that would not properly fit in a comment reply. However if you are comfortable with reading some simple code I can point you to our Elements Plus! plugin’s code here. Specifically have a look at these three files 1, 2, 3 which essentially built an Elementor element which can in theory handle a case like the one you are describing. Please note that this also has not been extensively tested for such a scenario because we believe that multiple videos would add unneeded noise on a page, but all the parts are there, it should work.
      Best regards.

Leave a Reply

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