I'm trying to make a video animation that runs based on the scroll position within a sticky container. So far, I am able to get the mechanicals of the scrolling and video advancing working, but when scrolling down through the video, the frames advance very chooppily. Here's a codepen example illustrating the exact scenario I'm having:
https://codepen.io/Jdo300/pen/qBVrNaY
HTML
<p>Text</p>
<p>Text</p>
<p>Text</p>
<p>Text</p>
<p>Text</p>
<p>Text</p>
<div class="container" style="height: 5000px;">
<div class="video">
<!--<video controls="" src="https://www.midstory.org/wp-content/uploads/2022/01/AAPI-Chart.mp4"</video>-->
<video controls="" src="http://media.w3.org/2010/05/sintel/trailer.mp4"</video>
</div>
</div>
<p>Text</p>
<p>Text</p>
<p>Text</p>
<p>Text</p>
<p>Text</p>
<p>Text</p>
CSS
/* Refers to video outer container */
div.video {
position: sticky;
height: 80vmin;
top: 10vmin;
}
/* Referes to video itself */
div.video video {
height: 80vmin;
}
.container {
display: block;
background: gray;
width: fit-content;
}
JavaScript
var playbackConstant = 1000; // lower numbers = faster playback
var video = '.video video';
var videoContainer = '.container';
// Setup the scroll effect for the AAPI-Chart video
setupScrollPlayVideo(video, videoContainer, playbackConstant);
window.addEventListener('scroll', () =>
{
// Advance frames of video for animation
updateVideoPosition(video, getPercentScrolled(videoContainer));
});
/**
* Updates the video play position based on scroll percentage
* @param {*} video Video element to update
* @param {*} scrollPosition Scroll position (0 to 100)
*/
function updateVideoPosition(video, scrollPosition)
{
// Select video element
var vid = document.querySelector(video);
// Only proceed if the video and metadata has been loaded
if(!Number.isNaN(vid.duration))
{
// Calculate the video position based on the scroll position
//vid.currentTime = vid.duration * (scrollPosition / 100);
// Use requestAnimationFrame for smooth playback
function scrollPlay()
{
vid.currentTime = vid.duration * (scrollPosition / 100);
window.requestAnimationFrame(scrollPlay);
}
window.requestAnimationFrame(scrollPlay);
}
}
/**
* Returns the percentage (0 to 100) of the way scrolled through the given element (container)
* NOTE: 0% = top of container lined up with top of viewport
* 100% = bottom of container lined up with bottom of viewport
* @param {*} container CSS selector to get the container
* @returns Value (0 to 100) representing the percent scrolled through the container
*/
function getPercentScrolled(container)
{
container = document.querySelector(container).getBoundingClientRect();
let scrollPosition = -container.top;
let scrollHeight = container.height - window.innerHeight;
let percentScrolled = (scrollPosition / scrollHeight) * 100;
if (percentScrolled > 100) percentScrolled = 100;
else if (percentScrolled < 0) percentScrolled = 0;
//console.log('stickyElement.heightt: ', stickyElement.height);
//console.log('scrollPosition: ', scrollPosition);
//console.log('scrollHeight: ', scrollHeight);
//console.log('percentScrolled: ', percentScrolled);
return percentScrolled;
}
/**
* Plays a video based on scroll percentage through the specified container object
* NOTE: Container object height is scaled based on video length at start of page
* @param {*} video Video Element
* @param {*} stickyContainer Sticky Container element
* @param {*} scrollConstant Dimensionless constant determining how fast the video advances while scrolling
*/
function setupScrollPlayVideo(video, stickyContainer, scrollConstant)
{
// get reference to video's scroll container to set it's height based on the video duration
var setHeight = document.querySelector(stickyContainer);
// select video element
var vid = document.querySelector(video);
// dynamically set the page height according to video length
vid.addEventListener('loadedmetadata', function ()
{
setHeight.style.height = Math.floor(vid.duration) * scrollConstant + "px";
});
}
/**
* Injects a CSS selector into the parent of the given element selector. If removeFromElement = true, then the selector is also
* removed from the given element as well.
**/
function injectClassIntoParentContainer(elementSelector, selector, removeFromElement = false)
{
var element = document.querySelector(elementSelector);
if (element !== null)
{
element.parentNode.classList.add(selector);
if (removeFromElement === true)
element.classList.remove(selector);
}
else
console.log("Warning: cannot find element: ", element);
}
Is there a better way to achieve the same thing (using vanilla JavaScript preferably) to get smooth frame transitions? I'm trying to make a simplified version of something like the video scroll animation effect on this page here (check 2/3 of the way down under "Chapter 2": https://www.newyorker.com/news/a-reporter-at-large/china-xinjiang-prison-state-uighur-detention-camps-prisoner-testimony
Any idea how I can make my video scroll smoother?