UNPKG

menuspy

Version:

A JavaScript library to make navigation menus highlight active item based on the scroll position.

195 lines (157 loc) 5.3 kB
/*! MenuSpy v1.3.0 (Jan 31 2018) - http://leocs.me/menuspy/ - Copyright (c) 2018 Leonardo Santos; MIT License */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.MenuSpy = factory()); }(this, (function () { 'use strict'; var utils = { extend: function extend(a, b) { for (var key in b) { if (b.hasOwnProperty(key)) { a[key] = b[key]; } } return a; }, offset: function offset(el) { var rect = el.getBoundingClientRect(); return { top: rect.top + window.pageYOffset, left: rect.left + window.pageXOffset }; }, scrollTop: function scrollTop() { return window.pageYOffset || document.documentElement.scrollTop; }, addClass: function addClass(el, className) { if (el.classList) { el.classList.add(className); } else { var classes = el.className.split(' '); var existingIndex = classes.indexOf(className); if (existingIndex === -1) { classes.push(className); } el.className = classes.join(' '); } }, removeClass: function removeClass(el, className) { if (el.classList) { el.classList.remove(className); } else { el.className = el.className.replace(new RegExp(("(^|\\b)" + (className.split(' ').join('|')) + "(\\b|$)"), 'gi'), ' '); } }, debounce: function debounce(fn, delay) { var timeout = null; return function() { var args = arguments; var context = this; if (!timeout) { timeout = setTimeout(function () { timeout = 0; return fn.apply(context, args); }, delay); } }; } }; var MenuSpy = function MenuSpy(element, options) { var this$1 = this; if (!element) { return; } var defaults = { menuItemSelector : 'a[href^="#"]', activeClass : 'active', threshold : 15, enableLocationHash : true, hashTimeout : 600, callback : null }; this.element = typeof element === 'string' ? document.querySelector(element) : element; this.options = utils.extend(defaults, options); this.assignValues(); this.debouncedAssignValuesFn = utils.debounce(function () { return this$1.assignValues(); }); window.addEventListener('resize', this.debouncedAssignValuesFn); this.debouncedHashFn = utils.debounce(function () { var hash = this$1.lastInViewElm ? ("#" + (this$1.lastInViewElm.id)) : '#'; if (history.replaceState) { history.replaceState(null, null, hash); } else { var st = utils.scrollTop(); window.location.hash = hash; window.scrollTo(0, st); } }, this.options.hashTimeout); this.cacheItems(); this.scrollFn(); }; MenuSpy.prototype.assignValues = function assignValues () { this.currScrollTop = 0; this.lastInViewElm = null; this.menuHeight = this.element.offsetHeight + this.options.threshold; this.menuItems = [].slice.call(this.element.querySelectorAll(this.options.menuItemSelector)); this.raf = null; }; MenuSpy.prototype.cacheItems = function cacheItems () { this.scrollItems = this.menuItems.map(function (elm) { var target = elm.dataset.target ? document.querySelector(elm.dataset.target) : document.getElementById(elm.hash.slice(1)); if (target) { var offset = Math.floor(utils.offset(target).top); return { elm: elm, target: target, offset: offset }; } return false; }); this.scrollItems = this.scrollItems.filter(Boolean).sort(function (a, b) { return a.offset - b.offset; }); }; MenuSpy.prototype.tick = function tick () { var fromTop = this.currScrollTop + this.menuHeight; var inViewElms = this.scrollItems.filter(function (item) { return item.offset < fromTop; }); this.activateItem(inViewElms.pop()); }; MenuSpy.prototype.activateItem = function activateItem (inViewElm) { var this$1 = this; var ref = this.options; var activeClass = ref.activeClass; var callback = ref.callback; if (!inViewElm) { this.scrollItems.forEach(function (item) { return utils.removeClass(item.elm.parentNode, activeClass); }); this.lastInViewElm = null; if (this.options.enableLocationHash) { this.debouncedHashFn(); } return; } if (this.lastInViewElm !== inViewElm.target) { this.lastInViewElm = inViewElm.target; this.scrollItems.forEach(function (item) { utils.removeClass(item.elm.parentNode, activeClass); if (item.target === inViewElm.target) { utils.addClass(item.elm.parentNode, activeClass); if (typeof callback === 'function') { callback.call(this$1, item); } if (this$1.options.enableLocationHash) { this$1.debouncedHashFn(); } } }); } }; MenuSpy.prototype.scrollFn = function scrollFn () { var st = utils.scrollTop(); if (this.currScrollTop !== st) { this.currScrollTop = st; this.tick(); } this.raf = window.requestAnimationFrame(this.scrollFn.bind(this)); }; MenuSpy.prototype.destroy = function destroy () { if (this.raf) { window.cancelAnimationFrame(this.raf); } window.removeEventListener('resize', this.debouncedAssignValuesFn); }; return MenuSpy; })));