flamingo-carotene-smooth-scroll-to
Version:
The smooth scroll module enables to scroll smoothly to a given y coordinate or dom element
321 lines (285 loc) • 10.5 kB
JavaScript
"use strict";
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
/**
* Scroll the page to a given Y Coordinate or to a dom element
* Animation is timebased, animation will skip frames, if client could not handle the speed
* */
var SmoothScrollTo = /*#__PURE__*/function () {
function SmoothScrollTo() {
_classCallCheck(this, SmoothScrollTo);
// animation duration
this.duration = 500;
// Y Position offset to browsers top
this.offset = 0;
// animation update interval
this.updateInterval = 25;
// internal property - starttime of the animation
this.animationStartTime = null;
// interval reference
this.timer = null;
// internal property - when animation was started - target position
this.currentTargetY = null;
// internal property - when animation was started - start position
this.currentStartY = null;
// callback that is called when a animation step was done
this.animationStepCallback = null;
// callback that is called when a animation is finished
this.finishCallback = null;
// config switch - cancel scroll animation, if users manually scrolls while smooth scrolling is active
this.cancelAnimationOnUserScroll = true;
// internal state - is a scrolling currently in progress
this.scrollingInProgress = false;
// the last position to which scrolling happens
this.lastScrollPosition = null;
// direction to which the current scrolling will take place
this.scrollDirection = null;
window.onscroll = this.onScrollHandler.bind(this);
}
/**
* Determines the Scroll Direction (up/down)
*
* @param fromY
* @param toY
* @returns {string}
*/
_createClass(SmoothScrollTo, [{
key: "getScrollDirection",
value: function getScrollDirection(fromY, toY) {
if (fromY < toY) {
return 'down';
} else {
return 'up';
}
}
/**
* Event Listener for "onscroll", to detect when user is scrolling...
* @param event
*/
}, {
key: "onScrollHandler",
value: function onScrollHandler(event) {
// if no scrolling is in progress by this function - get outta here
if (!this.scrollingInProgress) {
return;
}
// if scrolling is not cancelable on manual scrolling - leave instantly
if (!this.cancelAnimationOnUserScroll) {
return;
}
var currentY = window.pageYOffset;
var direction = this.getScrollDirection(this.lastScrollPosition, currentY);
// scrolling direction changed - stop automatic scrolling
if (this.scrollDirection !== direction) {
this.stop();
}
this.lastScrollPosition = currentY;
}
/**
* Change the overall animation duration
*
* @param {number} duration
* @returns {SmoothScrollTo}
*/
}, {
key: "setDuration",
value: function setDuration(duration) {
this.duration = duration;
return this;
}
/**
* Sets the behaviour, that manually scrolling cancels automatic scrolling
*
* @param {boolean} state
* @returns {SmoothScrollTo}
*/
}, {
key: "setCancelAnimationOnUserScroll",
value: function setCancelAnimationOnUserScroll(state) {
this.cancelAnimationOnUserScroll = state;
return this;
}
/**
* Set Scrolling target Offset
* @param {number} offset
* @returns {SmoothScrollTo}
*/
}, {
key: "setOffset",
value: function setOffset(offset) {
this.offset = offset;
return this;
}
/**
* Set AnimationStep Callback
* Function will be called with one parameter, which is the instance of this class -> callback(this)
* @param {function} callback
* @returns {SmoothScrollTo}
*/
}, {
key: "setAnimationStepCallback",
value: function setAnimationStepCallback(callback) {
this.animationStepCallback = callback;
return this;
}
/**
* Sets the currentTargetY
* This function can be used inside AnimationStepCallback to correct the targetPosition while animation is running
* @param {number} currentTargetY
* @returns {SmoothScrollTo}
*/
}, {
key: "setCurrentTargetY",
value: function setCurrentTargetY(currentTargetY) {
this.currentTargetY = currentTargetY;
return this;
}
/**
* Returns the current scroll Targetposition if animation is running
* @returns {number}
*/
}, {
key: "getCurrentTargetY",
value: function getCurrentTargetY() {
return this.currentTargetY;
}
/**
* Scrolls to a domNode or a position
* @param {HTMLElement|number} target
* @returns {SmoothScrollTo}
*/
}, {
key: "scrollTo",
value: function scrollTo(target) {
if (target instanceof HTMLElement) {
this.scrollToElement(target);
return this;
}
if (typeof target === 'number') {
this.scrollToY(target);
return this;
}
return this;
}
/**
* Scrolls to position
* @param {number} scrollY
*/
}, {
key: "scrollToY",
value: function scrollToY(scrollY) {
this.currentStartY = window.pageYOffset;
this.currentTargetY = scrollY - this.offset;
this._startAnimation(this.animationStepCallback);
}
/**
* Scrolls to a domNode
* @param {HTMLElement} domElement
*/
}, {
key: "scrollToElement",
value: function scrollToElement(domElement) {
this.currentStartY = window.pageYOffset;
this.setCurrentTargetY(this._getElementY(domElement) - this.offset);
this._startAnimation(function () {
this.setCurrentTargetY(this._getElementY(domElement) - this.offset);
if (typeof this.animationStepCallback == 'function') {
this.animationStepCallback(this);
}
}.bind(this));
}
/**
* Stops the current animation
*/
}, {
key: "stop",
value: function stop() {
this.lastScrollPosition = null;
this.scrollDirection = null;
this.scrollingInProgress = false;
if (this.timer !== null) {
clearInterval(this.timer);
if (typeof finishCallback === 'function') {
this.finishCallback(this);
}
}
}
/**
* Returns the Y-position (relative to page)
* @param {HTMLElement} domElement
* @returns {number}
* @private
*/
}, {
key: "_getElementY",
value: function _getElementY(domElement) {
var bounds = domElement.getBoundingClientRect();
return bounds.top + window.pageYOffset;
}
/**
* Starts the Animation
* @param {function} afterAnimationStepCallback
* @private
*/
}, {
key: "_startAnimation",
value: function _startAnimation(afterAnimationStepCallback) {
var _this = this;
// stopping running animation (if any)
this.stop();
// sets animation start time
this.animationStartTime = new Date().getTime();
var currentY = window.pageYOffset;
this.lastScrollPosition = currentY;
this.scrollingInProgress = true;
this.scrollDirection = this.getScrollDirection(currentY, this.currentTargetY);
// start timer
this.timer = setInterval(function () {
var currentTime = new Date().getTime();
var startTime = _this.animationStartTime;
var endTime = _this.animationStartTime + _this.duration;
var scrollToX = window.pageXOffset;
var scrollToY = _this.currentTargetY;
// animation time is up - jump directly to target
if (currentTime > endTime) {
window.scrollTo(scrollToX, scrollToY);
_this.stop(_this.timer);
return;
}
// animation time is in bounds - calculate timing fraction
var fraction = (currentTime - startTime) / _this.duration;
// correct position fraction with easing (quintEaseOut)
var easingPower = 5;
var easingFraction = 1 - Math.abs(Math.pow(fraction - 1, easingPower));
// calculate scrollToY by fraction
scrollToY = _this._lerp(_this.currentStartY, _this.currentTargetY, easingFraction);
// scroll to
window.scrollTo(scrollToX, scrollToY);
if (typeof afterAnimationStepCallback === 'function') {
afterAnimationStepCallback(_this);
}
}, this.updateInterval);
}
/**
* Calculates the position of the animation by time
* @param {number} startY Start position
* @param {number} targetY Target position
* @param {number} fraction Position of the animation (value between 0-1) 0 is start, 1 is end
* @returns {number}
* @private
*/
}, {
key: "_lerp",
value: function _lerp(startY, targetY, fraction) {
var diff = targetY - startY;
var pos = diff * fraction;
return startY + pos;
}
}]);
return SmoothScrollTo;
}();
module.exports = SmoothScrollTo;