Reputation: 1900
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>
Upvotes: 30
Views: 127611
Reputation: 1281
The accepted answer works, but introduces unnecessary complexity. The solution can be achieved with jQuery alone; there is no plugin needed to achieve the same results.
$(window).scroll(function () {
const viewportBottom = window.scrollY + window.innerHeight;
$('video').each(function () {
if ($(this).offset().top + $(this).height() < window.scrollY) {
$(this)[0].pause();
} else if ($(this).offset().top < viewportBottom) {
$(this)[0].play();
}
});
});
Upvotes: 0
Reputation: 43
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);
});
});
Upvotes: 1
Reputation: 5692
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.
Upvotes: 2
Reputation: 2691
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>
Upvotes: 9
Reputation: 567
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)
Upvotes: 1
Reputation: 1056
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);
Upvotes: 27
Reputation: 1875
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.
Upvotes: 23
Reputation: 51
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);
}
Upvotes: 2
Reputation: 1900
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
Upvotes: 1
Reputation: 11
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.
}
});
});
Upvotes: 1
Reputation: 18037
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()
Upvotes: 3
Reputation: 114
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();
}
})
});
Upvotes: 7
Reputation: 29
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.
Upvotes: 2
Reputation: 976
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();
}
})
Upvotes: 28