28

I am interested in setting up an HTML page with multiple video clips such that each video clip plays only while visible and then pauses when out of view.

I have found this great example of how this can be implemented with one clip, but I have been unable to modify the code to work with multiple clips. Perhaps I need to convert this code into a function for easy re-usability?

Here is what I have so far (JS Bin linked above modified for 2 clips instead of one).

This code seems to work for only one of the two clips.

<!DOCTYPE html>
<html>
    <!--   Created using jsbin.com   Source can be edited via http://jsbin.com/ocupor/1/edit
    -->
    <head>
        <meta charset=utf-8 />
        <title>JS Bin</title>
        <style>
            #right {
                position: absolute;
                top: 2000px;
            }
            #video1 {
                position: absolute;
                left: 2000px;
                top: 2000px;
            }
            #video2 {
                position: absolute;
                left: 2000px;
                top: 3000px;
            }

        </style>

        <style id="jsbin-css">
        </style>
    </head>
    #
    <body style="width: 4000px; height: 4000px;">
        <div id="info"></div>
        <div id="down">
            scroll down please...
        </div>
        <div id="right">
            scroll right please...
        </div>
        <video id="video1">
            <source src="http://video-js.zencoder.com/oceans-clip.mp4"/>

        </video>
        <script>
            var video = document.getElementById('video1'), fraction = 0.8;

            function checkScroll() {
                var x = video.offsetLeft, y = video.offsetTop, w = video.offsetWidth, h = video.offsetHeight, r = x + w, //right
                b = y + h, //bottom
                visibleX, visibleY, visible;

                visibleX = Math.max(0, Math.min(w, window.pageXOffset + window.innerWidth - x, r - window.pageXOffset));
                visibleY = Math.max(0, Math.min(h, window.pageYOffset + window.innerHeight - y, b - window.pageYOffset));

                visible = visibleX * visibleY / (w * h);

                if (visible > fraction) {
                    video.play();
                } else {
                    video.pause();
                }
            }

            checkScroll();
            window.addEventListener('scroll', checkScroll, false);
            window.addEventListener('resize', checkScroll, false);
        </script>

        <video id="video2">
            <source src="http://video-js.zencoder.com/oceans-clip.mp4"/>

        </video>
        <script>
            var video = document.getElementById('video2'), fraction = 0.8;

            function checkScroll() {
                var x = video.offsetLeft, y = video.offsetTop, w = video.offsetWidth, h = video.offsetHeight, r = x + w, //right
                b = y + h, //bottom
                visibleX, visibleY, visible;

                visibleX = Math.max(0, Math.min(w, window.pageXOffset + window.innerWidth - x, r - window.pageXOffset));
                visibleY = Math.max(0, Math.min(h, window.pageYOffset + window.innerHeight - y, b - window.pageYOffset));

                visible = visibleX * visibleY / (w * h);

                if (visible > fraction) {
                    video.play();
                } else {
                    video.pause();
                }
            } checkScroll();
            window.addEventListener('scroll', checkScroll, false);
            window.addEventListener('resize', checkScroll, false);

        </script>

    </body>
</html>
Narendra Jadhav
  • 10,052
  • 15
  • 33
  • 44
UltrasoundJelly
  • 1,845
  • 2
  • 18
  • 32

13 Answers13

27

OK, I think, it must be something like this:

var videos = document.getElementsByTagName("video");

function checkScroll() {
    var fraction = 0.8; // Play when 80% of the player is visible.

    for(var i = 0; i < videos.length; i++) {

        var video = videos[i];

        var x = video.offsetLeft, y = video.offsetTop, w = video.offsetWidth, h = video.offsetHeight, r = x + w, //right
            b = y + h, //bottom
            visibleX, visibleY, visible;

            visibleX = Math.max(0, Math.min(w, window.pageXOffset + window.innerWidth - x, r - window.pageXOffset));
            visibleY = Math.max(0, Math.min(h, window.pageYOffset + window.innerHeight - y, b - window.pageYOffset));

            visible = visibleX * visibleY / (w * h);

            if (visible > fraction) {
                video.play();
            } else {
                video.pause();
            }

    }

}

window.addEventListener('scroll', checkScroll, false);
window.addEventListener('resize', checkScroll, false);
shakram02
  • 10,812
  • 4
  • 22
  • 21
saike
  • 906
  • 8
  • 15
27

Using the isInViewport plugin and jQuery, here's my code for the task

$('video').each(function(){
    if ($(this).is(":in-viewport")) {
        $(this)[0].play();
    } else {
        $(this)[0].pause();
    }
})
qwazix
  • 906
  • 11
  • 17
23

None of the above seemed to work for me, but I finally found a way: you'll need the visible plugin, and this little piece of code right here:

$(window).scroll(function() {
    $('video').each(function() {
        if ($(this).visible(true)) {
            $(this)[0].play();
        } else {
            $(this)[0].pause();
        }
    })
});

This will allow any video to play only when it gets into viewport. By replacing visible( true ) by visible() , you can set it to play only when the entire video DOM element is in viewport.

Community
  • 1
  • 1
Pierre Olivier Tran
  • 1,669
  • 4
  • 24
  • 40
  • 1
    If you guys don't want to use a plugin, check [this](https://stackoverflow.com/questions/487073/how-to-check-if-element-is-visible-after-scrolling#answer-23361984) answer out. – John Doe Aug 08 '21 at 19:06
9

Y'all need to get with the times and use IntersectionObserver (and the appropriate polyfill or babeifyl). This script will play/pause all videos on a page when they scroll in/out of view. Boom.

<script crossorigin="anonymous" src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver%2CIntersectionObserverEntry"></script> 
<script>
    let video = document.querySelector('video');
    let isPaused = false;
    let observer = new IntersectionObserver((entries, observer) => { 
        entries.forEach(entry => {
        if(entry.intersectionRatio!=1  && !video.paused){
            video.pause();
            isPaused = true;
        }
        else if(isPaused) {
            video.play(); 
            isPaused=false}
        });
    }, {threshold: 1});
    observer.observe(video);
</script>

source

Tristanisginger
  • 2,181
  • 4
  • 28
  • 41
7

Need to check if the video is visible during the scrolling.

 $(window).scroll(function() {
    $('video').each(function(){
        if ($(this).is(":in-viewport")) {
            $(this)[0].play();
        } else {
            $(this)[0].pause();
        }
    })
});
st4n13y
  • 99
  • 1
  • 3
3

Using jQuery, isInViewport, and Coffeescript, the complete solution for me looked like this:

$(window).scroll ->
  $('video:in-viewport').each -> $(@)[0].play()
  $('video:not(:in-viewport)').each -> $(@)[0].pause()
pdobb
  • 17,688
  • 5
  • 59
  • 74
2

Old question, but just wanted to add my two cents, I initially started with the jQuery code above, but ran into some issues with the implementation. This solution should work with multiple videos, and also prevents a problem where the user pauses a video and tries to scroll away and it just starts again:

<script>
    var videoList = [];
    var scrollPauseList = [];
    var clickedPauseList = [];
</script>
<script>    
    var myScrollFunc = function() {

        $(".video-js").each(function(){ 

            var inView = $(this).is(":in-viewport");
            var isPaused = $(this)[0].player.paused();
            var playerIdx = videoList.indexOf(this.id);
            var scrollPaused = scrollPauseList[playerIdx];
            var clickPaused = clickedPauseList[playerIdx];

            if (inView) {                       
                var hasEnded = $(this)[0].player.ended();
                var curTime = $(this)[0].player.currentTime();
                var hasStarted = curTime > 0;
                if(hasStarted && !hasEnded && !clickPaused)
                {
                    scrollPauseList[playerIdx] = false;
                    $(this)[0].player.play();
                }
            } else if(!isPaused) {                      
                scrollPauseList[playerIdx] = true;
                $(this)[0].player.pause();
            }
        });
    };      

    $(window).scroll(myScrollFunc);     
</script>   

<video  
  class="video-js" controls></video>

<script>
$(".video-js").each(function(){ 
        videoList[videoList.length] = this.id;
        scrollPauseList[scrollPauseList.length] = false;
        clickedPauseList[scrollPauseList.length] = false;
    }); 

for(var i = 0; i < videoList.length; i++)
{
    var playerID = videoList[i];
    var player = videojs(playerID);

    player.on('pause', function() {
        var pID = videoList.indexOf(this.id());

        if(!scrollPauseList[pID])
        {
            clickedPauseList[pID] = true;
            scrollPauseList[pID] = false;
        }
        else
        {
            clickedPauseList[pID] = false;
            scrollPauseList[pID] = false;
        }
    });
}       
</script>

I've scrubbed some stuff, and i'm using video-js, so you may need to modify it a bit to get your implementation to work.

doveiya
  • 29
  • 4
2

Tried many solutions, the only one partially working is the one posted below. The problem is that having 3 videos on the page, the second one and the third one are basically controlled by the first one.

So they start playing when the page is loaded (while they are supposed to play when in viewport) and they get paused when the first get paused, any suggestion on having this working with multiple videos?

Tried using getElementById but didn't work, tried also jquery plugins but no good results.

Here you have the www page where you can see what happen and all source code of course.

http://185.197.128.183/~monompro/

window.onload = function() {

    var videos = document.getElementsByTagName("video"),
        fraction = 0.8;

    function checkScroll() {

        for (var i = 0; i < videos.length; i++) {

            var video = videos[i];

            var x = video.offsetLeft,
                y = video.offsetTop,
                w = video.offsetWidth,
                h = video.offsetHeight,
                r = x + w, //right
                b = y + h, //bottom
                visibleX, visibleY, visible;

            visibleX = Math.max(0, Math.min(w, window.pageXOffset + window.innerWidth - x, r - window.pageXOffset));
            visibleY = Math.max(0, Math.min(h, window.pageYOffset + window.innerHeight - y, b - window.pageYOffset));

            visible = visibleX * visibleY / (w * h);

            if (visible > fraction) {
                video.play();
            } else {
                video.pause();
            }

        }

    }

    window.addEventListener('scroll', checkScroll, false);
    window.addEventListener('resize', checkScroll, false);
}
Narendra Jadhav
  • 10,052
  • 15
  • 33
  • 44
2

As explained here, the offsetTop/offsetLeft/etc. approaches are slower and more error prone than the newer getBoundingClientRect approach. Here's some working code to play any videos that are even partially visible in the viewport:

function playVisibleVideos() {
  document.querySelectorAll("video").forEach(video => elementIsVisible(video) ? video.play() : video.pause());
}

function elementIsVisible(el) {
  let rect = el.getBoundingClientRect();
  return (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
}

let playVisibleVideosTimeout;
window.addEventListener("scroll", () => {
  clearTimeout(playVisibleVideosTimeout);
  playVisibleVideosTimeout = setTimeout(playVisibleVideos, 100);
}, {passive: true});

window.addEventListener("resize", playVisibleVideos);
window.addEventListener("DOMContentLoaded", playVisibleVideos);

The setTimeout stuff ensures that the playVisibleVideos() function isn't called any more often than once every 100ms as the user scrolls (so it doesn't cause lag). The {passive: true} ensures the scroll handler function doesn't "block" scrolling (which can cause scroll lag).

Note that it seems like @Tristanisginger's answer using the more modern IntersectionObserver approach may be a better choice than this one for most people.

joe
  • 3,752
  • 1
  • 32
  • 41
1

In case anyone else runs into this question, I was unable to use Saike's solution on my WordPress site because of the way the videos were auto embedded (MediaElement player). However, qwazix's solution worked with some modification. Here is the jQuery code that works with the IsInView plugin. Here are my include scripts (placed at the end of footer.php in my theme folder).

<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="js/isInViewport.min.js" type="text/javascript"></script>
<script src="js/scrollview.min.js" type="text/javascript"></script>

And the jQuery code (modify 400 to your tolerance liking)

  $(function() {
      $(window).scroll(function() {
          $('.wp-video-shortcode').each(function() {
              var str = $(this).attr('id');
              var arr = str.split('_');
              typecheck = arr[0];
              if ($(this).is(":in-viewport( 400 )") && typecheck == "mep") {
                  mejs.players[$(this).attr('id')].media.play();
              } else if (typecheck == "mep") {
                  mejs.players[$(this).attr('id')].media.pause();
              }
          });
      });
  });

Only issue I have with this code is that it does restart a video clip on scroll even if paused by the user. Wasn't a deal-breaking issue on my site. Here is the code in action: Ultrasoundoftheweek.com

UltrasoundJelly
  • 1,845
  • 2
  • 18
  • 32
1

This is how I managed to play a video only when the user scrolls to it. I used IsInViewport plugin. Hope you find it useful!

$(window).scroll(function() {

    var video = $('.yourvideo');

    $(video).each(function(){

        if(video.is(':in-viewport')){

            video[0].play();

            video.removeClass('yourvideo');
            //I removed class to stop repeating the action ".play()" when it is scrolled again.
        }
    });
});
YakovL
  • 7,557
  • 12
  • 62
  • 102
1

If you're looking for a simple solution without any dependencies, here it is:

 let video = document.getElementById('video')

 function playVideoOnScroll () {
    const threshold = 300 //px above the video to start playing
    let offset = video.getBoundingClientRect().top
    if (offset < threshold) {
      demoVideo.play()
    } else {
      demoVideo.pause()
    }
  }

  window.addEventListener('scroll', playVideoOnScroll, false)
  window.addEventListener('resize', playVideoOnScroll, false)
David D.
  • 557
  • 7
  • 20
0

My working solution in vanilla 2023 Javascript using IntersectionObserver:

document.addEventListener('DOMContentLoaded', function() {
    const videos = document.querySelectorAll('video');
    const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                entry.target.play();
            } else {
                entry.target.pause();
            }
        });
    });
    videos.forEach(video => {
        observer.observe(video);
    });
});
ronny
  • 33
  • 6