scroll-navigation-menu
Version:
A library to perform animated scrolling between sections and highlight the active section's <a> tag in the navigation menu
493 lines (410 loc) • 17.2 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["ScrollNavigation"] = factory();
else
root["ScrollNavigation"] = factory();
})(window, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 4);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = 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, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _animatedScrollTo = __webpack_require__(3);
var _animatedScrollTo2 = _interopRequireDefault(_animatedScrollTo);
var _defaultSettings = __webpack_require__(2);
var _defaultSettings2 = _interopRequireDefault(_defaultSettings);
var _utils = __webpack_require__(1);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var ScrollNavigation = function () {
function ScrollNavigation() {
var _this = this;
var settings = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, ScrollNavigation);
this.settings = _extends({}, _defaultSettings2.default, settings);
this._targetsRanges = new WeakMap();
this._anchors = [];
this.onAnchorClick = this.onAnchorClick.bind(this);
this._mapAnchorToSectionPosition = this._mapAnchorToSectionPosition.bind(this);
this._setCurrentHighlight = this._setCurrentHighlight.bind(this);
window.addEventListener('resize', function () {
_this._targetsRanges = new WeakMap();
_this._anchors.forEach(_this._mapAnchorToSectionPosition);
_this._setCurrentHighlight();
});
}
_createClass(ScrollNavigation, [{
key: '_updateAnchorActiveState',
value: function _updateAnchorActiveState(anchor) {
var active = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
if (anchor && (0, _utils.isHidden)(anchor)) {
return;
}
if (anchor && anchor.classList) {
if (active) {
anchor.classList.add(this.settings.activeClass);
} else {
anchor.classList.remove(this.settings.activeClass);
}
}
}
}, {
key: 'onAnchorClick',
value: function onAnchorClick(e) {
e.preventDefault();
var anchor = e.currentTarget;
// this is needed since the href attr might have more than just the hash
var targetAnchor = anchor.getAttribute('href').split("#")[1];
var elementToScroll = document.getElementById(targetAnchor);
if (!elementToScroll) {
return;
}
var anchorPosition = elementToScroll.getBoundingClientRect().top;
var positionToScroll = anchorPosition + (0, _utils.getScrollPosition)();
(0, _animatedScrollTo2.default)(positionToScroll + this.settings.offset, {
minDuration: this.settings.animationDuration, maxDuration: this.settings.animationDuration,
onComplete: function onComplete() {
anchor.blur();
}
});
}
}, {
key: '_mapAnchorToSectionPosition',
value: function _mapAnchorToSectionPosition(anchor) {
// this is needed since the href attr might have more than just the hash
var targetAnchor = anchor.getAttribute('href').split("#")[1];
var elementToScroll = document.getElementById(targetAnchor);
if (!elementToScroll) {
return;
}
var elementBoundaries = elementToScroll.getBoundingClientRect();
var elementInitialPosition = elementBoundaries.top + this.settings.offset + (0, _utils.getScrollPosition)();
var elementEndPosition = elementInitialPosition + elementBoundaries.height;
this._targetsRanges.set(anchor, [elementInitialPosition, elementEndPosition]);
}
}, {
key: '_setCurrentHighlight',
value: function _setCurrentHighlight() {
var _this2 = this;
this._anchors.forEach(function (anchor) {
var anchorTargetRange = _this2._targetsRanges.get(anchor);
if (anchorTargetRange && (0, _utils.isScrollInRange)(anchorTargetRange)) {
_this2._updateAnchorActiveState(anchor);
} else {
_this2._updateAnchorActiveState(anchor, false);
}
});
}
}, {
key: '_setupHighlights',
value: function _setupHighlights() {
this._targetsRanges = new WeakMap();
this._anchors.forEach(this._mapAnchorToSectionPosition);
window.addEventListener('scroll', this._setCurrentHighlight, { passive: true });
}
}, {
key: 'start',
value: function start() {
var _this3 = this;
this._anchors = [].concat(_toConsumableArray(document.querySelectorAll(this.settings.linksSelector)));
this._anchors.forEach(function (anchor) {
return anchor.addEventListener('click', _this3.onAnchorClick);
});
this._setupHighlights();
this._setCurrentHighlight();
}
}, {
key: 'stop',
value: function stop() {
var _this4 = this;
if (this._anchors && this._anchors.length) {
this._anchors.forEach(function (anchor) {
anchor.removeEventListener('click', _this4.onAnchorClick);
_this4._updateAnchorActiveState(anchor, false);
});
}
window.removeEventListener('scroll', this._setCurrentHighlight, { passive: true });
this._targetsRanges = null;
this._anchors = null;
}
}]);
return ScrollNavigation;
}();
exports.default = ScrollNavigation;
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
exports.isHidden = isHidden;
exports.isScrollInRange = isScrollInRange;
exports.getScrollPosition = getScrollPosition;
function isHidden(anchor) {
var _anchor$getBoundingCl = anchor.getBoundingClientRect(),
left = _anchor$getBoundingCl.left,
top = _anchor$getBoundingCl.top,
width = _anchor$getBoundingCl.width,
height = _anchor$getBoundingCl.height;
return !(left + top + width + height);
}
function isScrollInRange(range) {
var _range = _slicedToArray(range, 2),
start = _range[0],
end = _range[1];
return start <= getScrollPosition() && end > getScrollPosition();
}
function getScrollPosition() {
return window.scrollY || window.pageYOffset;
}
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = {
animationDuration: 233,
activeClass: 'active',
linksSelector: '.scroll',
offset: 0
};
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
;
(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];
if (desiredOffset instanceof HTMLElement) {
if (userOptions.element && userOptions.element instanceof HTMLElement) {
desiredOffset = desiredOffset.getBoundingClientRect().top + userOptions.element.scrollTop - userOptions.element.getBoundingClientRect().top;
} else {
var scrollTop = window.scrollY || document.documentElement.scrollTop;
desiredOffset = scrollTop + desiredOffset.getBoundingClientRect().top;
}
}
var options = {
speed: 500,
minDuration: 250,
maxDuration: 1500,
cancelOnUserAction: true,
element: window,
onComplete: undefined
};
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];
}
}
options.isWindow = options.element === window;
var initialScrollPosition = null;
var maxScroll = null;
if (options.isWindow) {
// get cross browser scroll position
initialScrollPosition = window.scrollY || document.documentElement.scrollTop;
// cross browser document height minus window height
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
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;
if (options.cancelOnUserAction) {
// Set handler to cancel scroll on user action
handleUserEvent = function handleUserEvent() {
removeListeners();
cancelAnimationFrame(requestID);
};
window.addEventListener('keydown', handleUserEvent);
window.addEventListener('mousedown', handleUserEvent);
} else {
// Set handler to prevent user actions while scroll is active
handleUserEvent = function handleUserEvent(e) {
e.preventDefault();
};
window.addEventListener('scroll', handleUserEvent);
}
window.addEventListener('wheel', handleUserEvent);
window.addEventListener('touchstart', handleUserEvent);
var removeListeners = function removeListeners() {
window.removeEventListener('wheel', handleUserEvent);
window.removeEventListener('touchstart', handleUserEvent);
if (options.cancelOnUserAction) {
window.removeEventListener('keydown', handleUserEvent);
window.removeEventListener('mousedown', handleUserEvent);
} else {
window.removeEventListener('scroll', handleUserEvent);
}
};
var step = function step() {
var timeDiff = Date.now() - startingTime;
var t = timeDiff / duration - 1;
var easing = t * t * t + 1;
var scrollPosition = Math.round(initialScrollPosition + diff * easing);
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
if (options.isWindow) {
options.element.scrollTo(0, scrollPosition);
} else {
options.element.scrollTop = 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
if (options.isWindow) {
options.element.scrollTo(0, desiredOffset);
} else {
options.element.scrollTop = 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 (true) {
if (typeof module !== 'undefined' && module.exports) {
module.exports = __ANIMATE_SCROLL_TO;
exports = module.exports;
}
exports.default = __ANIMATE_SCROLL_TO;
} else {}
}).call(undefined);
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
;
var _scrollNavigationMenu = __webpack_require__(0);
var _scrollNavigationMenu2 = _interopRequireDefault(_scrollNavigationMenu);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
module.exports = __webpack_require__(0).default;
/***/ })
/******/ ]);
});