I have a video that I want to play based on the user's scroll position (as the user scrolls, the video plays; when the scroll stops the video pauses).
This works fine with most mp4 videos, however when I try to us a video that has a 1920x1080 resolution the playback is "choppy", only updating the video image once the user is done scrolling (mouseup) and not during the scroll (mousedown).
Here is an example in jsfiddle. The default video in the example has a resolution of 1920x1080. The commented video link is 1280x720 and works fine. This happens with every 1080 video tested. 1920 width is preferred because the video in the finished product should be full-screen-width and should cover most desktop screens without losing much resolution quality.
Here is the code:
HTML
<div id="main-wrapper">
<video id="main-video" width="auto" height="240" controls preload>
<source src="http://mirrors.standaloneinstaller.com/video-sample/jellyfish-25-mbps-hd-hevc.mp4" type="video/mp4">
<!--
<source src="http://mirrors.standaloneinstaller.com/video-sample/star_trails.mp4" type="video/mp4">
-->
Your browser does not support the video tag.
</video>
</div>
JavaScript:
var myContent = $('#main-video');
var myContentConainter =$('#main-wrapper');
var vid = document.getElementById("main-video");
var originalTop = 0;
var scrollPos=0;
var atTopState = false;
var vid = document.getElementById("main-video");
var videoToHeightRatio= .012;
var videoLength;
vid.onloadedmetadata = function() {
videoLength =vid.duration;
};
vid.pause();
document.addEventListener("scroll", function(){
////// Set up variables ////////
var windowTop = $(window).scrollTop();
var windowBottom = $(window).scrollTop() + $(window).height();
var windowMidPoint = ($(window).scrollTop()) +( $(window).height() / 2 );
var contentTop = $(myContent).offset().top;
var contentBottom = contentTop + $(myContent).height();
var contentMidpoint = contentTop + ($(myContent).height()/2);
var windowHeight = window.innerHeight;
var windowWidth = window.innerWidth;
//Video stuff
var currentSec = vid.currentTime;
var videoContainerOverlow = videoLength/videoToHeightRatio;
var containerHeight = $(window).height();
var newContainerHeight = containerHeight + videoContainerOverlow
myContentConainter.css('height',newContainerHeight+"px");
if (windowTop >= contentTop){
if (atTopState==false){
originalTop = contentTop;
myContent.css("position","fixed")
myContent.css("top","0")
}
vid.currentTime = (windowTop / videoContainerOverlow) * videoLength;
atTopState = true;
}
if( windowTop < originalTop){
myContent.css("position","unset")
myContent.css("top","unset")
atTopState=false;
}
if (windowBottom <= contentTop){
myContent.css("position","unset")
myContent.css("top","unset")
atTopState=false;
}
});
CSS:
#main-wrapper{
background-image:linear-gradient(blue,red);
height:300vh;
}
#main-video{
}
I need help with a particular bit of JS I'm using to make HTML5 videos play when in view.
The code:
$(document).ready(function() {
// Get media - with autoplay disabled (audio or video)
var media = $('#video1, #video2, #video3, #video4, #video5');
var tolerancePixel = 10;
function checkMedia(){
// Get current browser top and bottom
var scrollTop = $(window).scrollTop() + tolerancePixel;
var scrollBottom = $(window).scrollTop() + $(window).height() - tolerancePixel;
//if ($(window).scrollTop() > $(window).height() - 100) {
media.each(function(index, el) {
var yTopMedia = $(this).offset().top;
var yBottomMedia = $(this).height() + yTopMedia;
if(scrollTop < yBottomMedia && scrollBottom > yTopMedia){
$(this).get(0).play();
} else {
$(this).get(0).pause();
}
});
//}
}
$(document).on('scroll', checkMedia);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div style="height:500px"></div>
<video muted id="video4" class="lightbulbs" width="100%" height="auto">
<source src="http://www.ddi.com.au/culture/img/lightbulbs.mp4" type="video/mp4">
</video>
<div style="height:500px"></div>
I obtained this code from this answer: https://stackoverflow.com/a/26508106/10213848
My issue is that once the video is finished, it can be triggered again by scrolling upward. I need the video/s to only play once and not get triggered again.
Any help would be greatly appreciated.
You can use a variable to mark whether the video had played. if it already played, do not play it again:
$(document).ready(function() {
// Get media - with autoplay disabled (audio or video)
var media = $('#video1, #video2, #video3, #video4, #video5');
var tolerancePixel = 10;
var hasPlayMap = {};
function checkMedia(){
// Get current browser top and bottom
var scrollTop = $(window).scrollTop() + tolerancePixel;
var scrollBottom = $(window).scrollTop() + $(window).height() - tolerancePixel;
//if ($(window).scrollTop() > $(window).height() - 100) {
media.each(function(index, el) {
var yTopMedia = $(this).offset().top;
var yBottomMedia = $(this).height() + yTopMedia;
if(scrollTop < yBottomMedia && scrollBottom > yTopMedia){
var thisId = $(this).attr("id");
if (hasPlayMap[thisId]){
return;
}
hasPlayMap[thisId] = true;
$(this).get(0).play();
} else {
$(this).get(0).pause();
}
});
//}
}
$(document).on('scroll', checkMedia);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div style="height:500px"></div>
<video muted id="video4" class="lightbulbs" width="100%" height="auto">
<source src="http://www.ddi.com.au/culture/img/lightbulbs.mp4" type="video/mp4">
</video>
<div style="height:500px"></div>
Sorry, I know this looks a lot like NewToJS' answer in the comments, sadly I was thinking of the same thing.
Since this is the effect you wanted, please do not green tick this since NewToJS got the answer first (granted, that is, if you had planned to).
You can set the video's onend event to set the video's onplay event:
document.querySelector('video').forEach(function(element) {
// Set the onend event to...
element.onend = function() {
// ...set the onplay event to...
this.onplay = function() {
// ...stop playing video when the video starts playing
this.pause();
this.currentTime = 0;
};
};
});
Pure jQuery version:
$('video').each(function(ix, ele) {
ele.addEventListener('end', function() {
this.addEventListener('play', function() {
ele.pause();
ele.currentTime = 0;
});
});
});
This is an url of video
<iframe src="http://xxxx.com/embed-xxx-720x405.html" frameborder="0" marginwidth="0" marginheight="0" scrolling="NO" width="720" height="405"></iframe>
I got this JS to change width and height in url from 640px:
$(window).on("load", function(event){
var w = $(this).width() -50;
if(w <= 640)
$('.videoimg iframe').each(function () {
$(this).attr('src', $(this).attr('src').replace('720', '' + w + ''));
$(this).attr('src', $(this).attr('src').replace('405', '305'));
$(this).attr('width', $(this).attr('width').replace('720', '' + w + ''));
$(this).attr('height', $(this).attr('height').replace('405', '305'));
})
});
It works with loading a website, but I need that it should work when I flip my phone on landscape so people don't need to refresh the page.
Resize function doesnt work, it should be something like that
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) {
window.addEventListener("orientationchange", function() {
rvlWidth(w);
}, false);
But I dont know how to do it, do you have any solutions for that?
It seems like the rotation event is set before the 'load' event is fired + 'w' is not defined in the context (at least on the firest time)
try gather the codes as the following
var onRotateCallback = function() {
var screenWidth = $(window).width() - 50;
var videoFrames = $('.videoimg iframe');
if (screenWidth <= 640) {
videoFrames.each(function() {
var frame = $(this);
var originalSRC = $(this).attr('src');
var newSRC = originalSRC.replace('720', screenWidth).replace('405', '305');
frame.attr('src', newSRC);
frame.attr('width', screenWidth);
frame.attr('height', '350');
});
}
};
$(window).on("load", function(e) {
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) {
onRotateCallback();
$(window).on("resize", function() {
onRotateCallback();
});
}
});
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>
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();
}
})
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);
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.
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
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();
}
})
});
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()
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.
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);
}
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.
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.
}
});
});
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
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)
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);
});
});