'use strict'; !function($) { /** * Magellan module. * @module foundation.magellan */ class Magellan { /** * Creates a new instance of Magellan. * @class * @fires Magellan#init * @param {Object} element - jQuery object to add the trigger to. * @param {Object} options - Overrides to the default plugin settings. */ constructor(element, options) { this.$element = element; this.options = $.extend({}, Magellan.defaults, this.$element.data(), options); this._init(); this.calcPoints(); Foundation.registerPlugin(this, 'Magellan'); } /** * Initializes the Magellan plugin and calls functions to get equalizer functioning on load. * @private */ _init() { var id = this.$element[0].id || Foundation.GetYoDigits(6, 'magellan'); var _this = this; this.$targets = $('[data-magellan-target]'); this.$links = this.$element.find('a'); this.$element.attr({ 'data-resize': id, 'data-scroll': id, 'id': id }); this.$active = $(); this.scrollPos = parseInt(window.pageYOffset, 10); this._events(); } /** * Calculates an array of pixel values that are the demarcation lines between locations on the page. * Can be invoked if new elements are added or the size of a location changes. * @function */ calcPoints() { var _this = this, body = document.body, html = document.documentElement; this.points = []; this.winHeight = Math.round(Math.max(window.innerHeight, html.clientHeight)); this.docHeight = Math.round(Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight)); this.$targets.each(function(){ var $tar = $(this), pt = Math.round($tar.offset().top - _this.options.threshold); $tar.targetPoint = pt; _this.points.push(pt); }); } /** * Initializes events for Magellan. * @private */ _events() { var _this = this, $body = $('html, body'), opts = { duration: _this.options.animationDuration, easing: _this.options.animationEasing }; $(window).one('load', function(){ if(_this.options.deepLinking){ if(location.hash){ _this.scrollToLoc(location.hash); } } _this.calcPoints(); _this._updateActive(); }); this.$element.on({ 'resizeme.zf.trigger': this.reflow.bind(this), 'scrollme.zf.trigger': this._updateActive.bind(this) }).on('click.zf.magellan', 'a[href^="#"]', function(e) { e.preventDefault(); var arrival = this.getAttribute('href'); _this.scrollToLoc(arrival); }); $(window).on('popstate', function(e) { if(_this.options.deepLinking) { _this.scrollToLoc(window.location.hash); } }); } /** * Function to scroll to a given location on the page. * @param {String} loc - a properly formatted jQuery id selector. Example: '#foo' * @function */ scrollToLoc(loc) { // Do nothing if target does not exist to prevent errors if (!$(loc).length) {return false;} this._inTransition = true; var _this = this, scrollPos = Math.round($(loc).offset().top - this.options.threshold / 2 - this.options.barOffset); $('html, body').stop(true).animate( { scrollTop: scrollPos }, this.options.animationDuration, this.options.animationEasing, function() {_this._inTransition = false; _this._updateActive()} ); } /** * Calls necessary functions to update Magellan upon DOM change * @function */ reflow() { this.calcPoints(); this._updateActive(); } /** * Updates the visibility of an active location link, and updates the url hash for the page, if deepLinking enabled. * @private * @function * @fires Magellan#update */ _updateActive(/*evt, elem, scrollPos*/) { if(this._inTransition) {return;} var winPos = /*scrollPos ||*/ parseInt(window.pageYOffset, 10), curIdx; if(winPos + this.winHeight === this.docHeight){ curIdx = this.points.length - 1; } else if(winPos < this.points[0]){ curIdx = undefined; } else{ var isDown = this.scrollPos < winPos, _this = this, curVisible = this.points.filter(function(p, i){ return isDown ? p - _this.options.barOffset <= winPos : p - _this.options.barOffset - _this.options.threshold <= winPos; }); curIdx = curVisible.length ? curVisible.length - 1 : 0; } this.$active.removeClass(this.options.activeClass); this.$active = this.$links.filter('[href="#' + this.$targets.eq(curIdx).data('magellan-target') + '"]').addClass(this.options.activeClass); if(this.options.deepLinking){ var hash = ""; if(curIdx != undefined){ hash = this.$active[0].getAttribute('href'); } if(hash !== window.location.hash) { if(window.history.pushState){ window.history.pushState(null, null, hash); }else{ window.location.hash = hash; } } } this.scrollPos = winPos; /** * Fires when magellan is finished updating to the new active element. * @event Magellan#update */ this.$element.trigger('update.zf.magellan', [this.$active]); } /** * Destroys an instance of Magellan and resets the url of the window. * @function */ destroy() { this.$element.off('.zf.trigger .zf.magellan') .find(`.${this.options.activeClass}`).removeClass(this.options.activeClass); if(this.options.deepLinking){ var hash = this.$active[0].getAttribute('href'); window.location.hash.replace(hash, ''); } Foundation.unregisterPlugin(this); } } /** * Default settings for plugin */ Magellan.defaults = { /** * Amount of time, in ms, the animated scrolling should take between locations. * @option * @type {number} * @default 500 */ animationDuration: 500, /** * Animation style to use when scrolling between locations. Can be `'swing'` or `'linear'`. * @option * @type {string} * @default 'linear' * @see {@link https://api.jquery.com/animate|Jquery animate} */ animationEasing: 'linear', /** * Number of pixels to use as a marker for location changes. * @option * @type {number} * @default 50 */ threshold: 50, /** * Class applied to the active locations link on the magellan container. * @option * @type {string} * @default 'active' */ activeClass: 'active', /** * Allows the script to manipulate the url of the current page, and if supported, alter the history. * @option * @type {boolean} * @default false */ deepLinking: false, /** * Number of pixels to offset the scroll of the page on item click if using a sticky nav bar. * @option * @type {number} * @default 0 */ barOffset: 0 } // Window exports Foundation.plugin(Magellan, 'Magellan'); }(jQuery);