Skip to content

Instantly share code, notes, and snippets.

@XianYeeXing
Created April 14, 2019 08:17
Show Gist options
  • Save XianYeeXing/50c313d368ef6d56373901fde29fc550 to your computer and use it in GitHub Desktop.
Save XianYeeXing/50c313d368ef6d56373901fde29fc550 to your computer and use it in GitHub Desktop.
/**
* USAGE:
* const scrollTo = require('../lib/animated-scroll-to');
* scrollTo(window.innerHeight, {speed: 1000, elemrnt: window});
*/
(function() {
'use strict';
// desiredOffset - page offset to scroll to
// speed - duration of the scroll per 1000px
function __ANIMATE_SCROLL_TO(desiredOffset) {
var userOptions = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var options = {
speed: 500,
minDuration: 250,
maxDuration: 1500,
cancelOnUserAction: true,
element: window,
horizontal: false,
onComplete: undefined,
passive: true,
offset: 0
};
var optionsKeys = Object.keys(options);
// Override default options
for (var i = 0; i < optionsKeys.length; i++) {
var key = optionsKeys[i];
if (typeof userOptions[key] !== 'undefined') {
options[key] = userOptions[key];
}
}
if (!options.cancelOnUserAction && options.passive) {
options.passive = false;
if (userOptions.passive) {
console && console.warn(
'animated-scroll-to:\n "passive" was set to "false" to prevent errors, ' +
'as using "cancelOnUserAction: false" doesn\'t work with passive events.')
}
}
if (desiredOffset instanceof HTMLElement) {
if (userOptions.element && userOptions.element instanceof HTMLElement) {
if (options.horizontal) {
desiredOffset = (desiredOffset.getBoundingClientRect().left + userOptions.element.scrollLeft)
- userOptions.element.getBoundingClientRect().left;
} else {
desiredOffset = (desiredOffset.getBoundingClientRect().top + userOptions.element.scrollTop)
- userOptions.element.getBoundingClientRect().top;
}
} else if (options.horizontal) {
var scrollLeft = window.scrollX || document.documentElement.scrollLeft;
desiredOffset = scrollLeft + desiredOffset.getBoundingClientRect().left;
} else {
var scrollTop = window.scrollY || document.documentElement.scrollTop;
desiredOffset = scrollTop + desiredOffset.getBoundingClientRect().top;
}
}
// Add additonal user offset
desiredOffset += options.offset
options.isWindow = options.element === window;
var initialScrollPosition = null;
var initialAxisScollPosition = 0;
var maxScroll = null;
if (options.isWindow) {
if (options.horizontal) {
// get cross browser scroll positions
initialScrollPosition = window.scrollX || document.documentElement.scrollLeft;
initialAxisScollPosition = window.scrollY || document.documentElement.scrollTop;
// cross browser document height minus window height
maxScroll = Math.max(
document.body.scrollWidth, document.documentElement.scrollWidth,
document.body.offsetWidth, document.documentElement.offsetWidth,
document.body.clientWidth, document.documentElement.clientWidth
) - window.innerWidth;
} else {
// get cross browser scroll positions
initialScrollPosition = window.scrollY || document.documentElement.scrollTop;
initialAxisScollPosition = window.scrollX || document.documentElement.scrollLeft;
// cross browser document width minus window width
maxScroll = Math.max(
document.body.scrollHeight, document.documentElement.scrollHeight,
document.body.offsetHeight, document.documentElement.offsetHeight,
document.body.clientHeight, document.documentElement.clientHeight
) - window.innerHeight;
}
} else {
// DOM element
if (options.horizontal) {
initialScrollPosition = options.element.scrollLeft;
maxScroll = options.element.scrollWidth - options.element.clientWidth;
} else {
initialScrollPosition = options.element.scrollTop;
maxScroll = options.element.scrollHeight - options.element.clientHeight;
}
}
// If the scroll position is greater than maximum available scroll
if (desiredOffset > maxScroll) {
desiredOffset = maxScroll;
}
// Calculate diff to scroll
var diff = desiredOffset - initialScrollPosition;
// Do nothing if the page is already there
if (diff === 0) {
// Execute callback if there is any
if (options.onComplete && typeof options.onComplete === 'function') {
options.onComplete()
}
return;
}
// Calculate duration of the scroll
var duration = Math.abs(Math.round((diff / 1000) * options.speed));
// Set minimum and maximum duration
if (duration < options.minDuration) {
duration = options.minDuration;
} else if (duration > options.maxDuration) {
duration = options.maxDuration;
}
var startingTime = Date.now();
// Request animation frame ID
var requestID = null;
// Method handler
var handleUserEvent = null;
var userEventOptions = { passive: options.passive };
if (options.cancelOnUserAction) {
// Set handler to cancel scroll on user action
handleUserEvent = function() {
removeListeners();
cancelAnimationFrame(requestID);
};
window.addEventListener('keydown', handleUserEvent, userEventOptions);
window.addEventListener('mousedown', handleUserEvent, userEventOptions);
} else {
// Set handler to prevent user actions while scroll is active
handleUserEvent = function(e) { e.preventDefault(); };
window.addEventListener('scroll', handleUserEvent, userEventOptions);
}
window.addEventListener('wheel', handleUserEvent, userEventOptions);
window.addEventListener('touchstart', handleUserEvent, userEventOptions);
var removeListeners = function () {
window.removeEventListener('wheel', handleUserEvent, userEventOptions);
window.removeEventListener('touchstart', handleUserEvent, userEventOptions);
if (options.cancelOnUserAction) {
window.removeEventListener('keydown', handleUserEvent, userEventOptions);
window.removeEventListener('mousedown', handleUserEvent, userEventOptions);
} else {
window.removeEventListener('scroll', handleUserEvent, userEventOptions);
}
};
var step = function () {
var timeDiff = Date.now() - startingTime;
var t = (timeDiff / duration) - 1;
var easing = t * t * t + 1;
var scrollPosition = Math.round(initialScrollPosition + (diff * easing));
var doScroll = function(position) {
if (options.isWindow) {
if (options.horizontal) {
options.element.scrollTo(position, initialAxisScollPosition);
} else {
options.element.scrollTo(initialAxisScollPosition, position);
}
} else if (options.horizontal) {
options.element.scrollLeft = position;
} else {
options.element.scrollTop = position;
}
}
if (timeDiff < duration && scrollPosition !== desiredOffset) {
// If scroll didn't reach desired offset or time is not elapsed
// Scroll to a new position
// And request a new step
doScroll(scrollPosition);
requestID = requestAnimationFrame(step);
} else {
// If the time elapsed or we reached the desired offset
// Set scroll to the desired offset (when rounding made it to be off a pixel or two)
// Clear animation frame to be sure
doScroll(desiredOffset);
cancelAnimationFrame(requestID);
// Remove listeners
removeListeners();
// Animation is complete, execute callback if there is any
if (options.onComplete && typeof options.onComplete === 'function') {
options.onComplete()
}
}
};
// Start animating scroll
requestID = requestAnimationFrame(step);
}
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
module.exports = __ANIMATE_SCROLL_TO;
exports = module.exports;
}
exports.default = __ANIMATE_SCROLL_TO;
} else if (window) {
window.animateScrollTo = __ANIMATE_SCROLL_TO;
}
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment