I am trying to debug some code I created for a webpage that auto scrolls to a video on the page once the video starts to come into view of the viewport. The code works but if you scroll too quickly, the code that autoscrolls the video overshoots the video because of the "inertia" of the scroll. I want to make it so that if the page is scrolling from inertia when the code grabs it and starts scrolling to the video, it will cancel out the inertia of the page so that it stops once the video is centered on the screen. Here's a minimal code example that shows the problem I'm having:
/////////////////////////////
// Main scroll event listener
//////////////////////////////
window.addEventListener("scroll", () => {
const videoScrollSpeed = 1000; // Speed of the overall scroll effect in ms
const videoScrollTriggerPercentage = 25; // How far the video placeholder is into the viewport before the scrolling effect is triggered
const video = "video"; // Get video reference
// Trigger the scrollToElement when the video placeholder is 25% within the viewport and call the inline callback function
// (This function works correctly)
scrollTriggeredCallback(video, videoScrollTriggerPercentage, function(trigger, triggerElement){
// This is the inline callback code that is called once the trigger event takes place
if (trigger === true) {
// Scrolls the document to center it on the given element
// (this is where the issues are)
scrollToElement(triggerElement, videoScrollSpeed, function(placeholder) {
// This is the inline callback code that is triggered once the scroll animation finishes
console.log ("Scroll complete");
});
}
});
});
/////////////////////////////
// Helper Functions
//////////////////////////////
/**
* Scrolls the given element into the center of the viewport using the given duration and easing curve. Once the animation completes, the optional callback function can be called
* @param {Obj} element The element to scroll into view
* @param {int} duration Time (in ms) for the animation to run
* @param {func} callback (Optional) Callback function to call after scrolling completes
*/
function scrollToElement(element, duration, callback) {
const elementRect = element.getBoundingClientRect();
let doc = document.documentElement;
if(doc.scrollTop === 0){
var t = doc.scrollTop;
++doc.scrollTop;
doc = (t + 1 === doc.scrollTop-- ? doc : document.body);
}
const startPosition = doc.scrollTop;
const absoluteElementTop = elementRect.top + window.pageYOffset;
const middle = absoluteElementTop - (window.innerHeight / 2) + (elementRect.height / 2);
let startTime, previousTimeStamp;
function stepScroll(timestamp) {
if (startTime === undefined) {
startTime = timestamp;
}
const elapsed = timestamp - startTime;
if (previousTimeStamp !== timestamp) {
// Calculate the current scroll position between startPosition and middle of the element to show based on the time elapsed
doc.scrollTop = incrementOverRange(startTime + elapsed, startTime, startTime + duration, startPosition, middle);
}
// Stop the animation after the duration has elapsed
if (elapsed < duration) {
previousTimeStamp = timestamp;
window.requestAnimationFrame(stepScroll);
} else {
if (callback) {
callback(element);
}
}
}
// Begin the animation
window.requestAnimationFrame(stepScroll);
}
/**
* Returns the X and Y offsets of an element due to it being shifted using the CSS Transform translate method
* @param {*} element Element reference (CSS selector string or DOMObject)
* @returns An array containing the X and Y offset coordinates
*/
function getCSSTranslationOffset(element) {
element = getDOMObject(element);
const computedStyle = window.getComputedStyle(element);
const transformMatrix = new DOMMatrix(computedStyle.transform);
const translationX = transformMatrix.m41;
const translationY = transformMatrix.m42;
return [transformMatrix.m41, transformMatrix.m42]
}
/**
* Returns a value within a custom range based on the input percent scrolled value
* @param {*} percentValue Value to be transformed from the start/end percent range to the min/max value range
* @param {*} startPercent Starting percentage value to begin incrementing the value range
* @param {*} endPercent Ending percentage value to end incrementing the value range
* @param {*} minValue Starting value of the value range
* @param {*} maxValue Ending value of the value range
* @returns The corresponding value within the value range
*/
function incrementOverRange(percentValue, startPercent, endPercent, minValue, maxValue) {
// Limit input range to start/end Percent range
if (percentValue < startPercent)
percentValue = startPercent;
else if (percentValue > endPercent)
percentValue = endPercent;
// NOTE: Formula borrowed from Arduino map() function
return ((percentValue - startPercent) * (maxValue - minValue) / (endPercent - startPercent) + minValue);
}
/**
* Triggers a callback function when the specified element scrolls into range. The callback function is called again once the element scrolls outside of the given range.
*
* Example usage:
*
* scrollTriggeredCallback(".triggerElement", 50, function(triggered) {
* if (triggered === true)
* // Do something here
* else
* // Do something else here
* });
*
* NOTE: This version of the function uses the viewport to calculate the scroll position of the element within the screen.
* Scroll position = 0% when the element's top boundary is at the bottom of the viewport
* Scroll position = 100% when the element's bottom boundary is at the top of the viewport
*
* @param {*} triggerElement The element to trigger the callback on
* @param {*} percentage Percentage value to trigger the animation
* @param {*} callback Function to call (with single parameter specfying whether animation was triggered or not) once animation trigger state changes
* If animation is triggered, input parameter is set to true and false otherwise.
*/
function scrollTriggeredCallback(triggerElement, percentage, callback) {
// Get references to the HTML element and its client rect object to work with
triggerElement = document.querySelector(triggerElement);
var triggerElementRect = triggerElement.getBoundingClientRect();
// Calculate the scroll position of the element within the viewport
let scrollPosition = triggerElementRect.top + triggerElementRect.height; // Sticky height >= viewport height
let scrollHeight = window.innerHeight + triggerElementRect.height;
let percentScrolled = 100 - (scrollPosition / scrollHeight) * 100;
// Limit the scroll range to 0-100%
if (percentScrolled > 100)
percentScrolled = 100;
else if (percentScrolled < 0)
percentScrolled = 0;
// Add the animation CSS selector to the given element if the percentScrolled value is within the given percentage range, and remove it otherwise
if (percentScrolled >= percentage) {
if(!triggerElement.classList.contains("triggered")) {
triggerElement.classList.add("triggered");
callback(true, triggerElement);
}
}
else if (percentScrolled <= 0) {
if (triggerElement.classList.contains("triggered")) {
triggerElement.classList.remove('triggered');
callback(false, triggerElement);
}
}
}
#content {
max-width: 560px;
margin-left: auto;
margin-right: auto;
}
span {
display: block;
height: 40vh;
width: 100%;
background: lightgray;
margin: 10px 0;
}
.video {
position: relative;
width: 100%;
height: calc(100% / 1.77777);
display: block;
cursor: pointer;
}
<html>
<head>
<link rel="stylesheet" href="style.css">
<!--<script defer src="../../Templates/Code Library/Animation Code Library.js"></script>-->
<script defer src="script.js"></script>
</head>
<body>
<div id="content">
<span>Text</span>
<span>Text</span>
<span>Text</span>
<span>Text</span>
<video class="video" controls loading="lazy" style="object-fit: cover">
<source src="https://www.staging7.midstory.org/wp-content/uploads/2022/01/Video-of-green-foliage.mp4" type="video/mp4">
</video>
<span>Text</span>
<span>Text</span>
<span>Text</span>
<span>Text</span>
</div>
</body>
</html>