/** * Magellan module. * @module foundation.magellan */ !function(Foundation, $) { 'use strict'; /** * 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. */ function Magellan(element, options) { this.$element = element; this.options = $.extend({}, Magellan.defaults, this.$element.data(), options); this._init(); Foundation.registerPlugin(this, 'Magellan'); } /** * Default settings for plugin */ Magellan.defaults = { /** * Amount of time, in ms, the animated scrolling should take between locations. * @option * @example 500 */ animationDuration: 500, /** * Animation style to use when scrolling between locations. * @option * @example 'ease-in-out' */ animationEasing: 'linear', /** * Number of pixels to use as a marker for location changes. * @option * @example 50 */ threshold: 50, /** * Class applied to the active locations link on the magellan container. * @option * @example 'active' */ activeClass: 'active', /** * Allows the script to manipulate the url of the current page, and if supported, alter the history. * @option * @example true */ deepLinking: false, /** * Number of pixels to offset the scroll of the page on item click if using a sticky nav bar. * @option * @example 25 */ barOffset: 0 }; /** * Initializes the Magellan plugin and calls functions to get equalizer functioning on load. * @private */ Magellan.prototype._init = function() { var id = this.$element[0].id || Foundation.GetYoDigits(6, 'magellan'), _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 */ Magellan.prototype.calcPoints = function(){ 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 */ Magellan.prototype._events = function() { var _this = this, $body = $('html, body'), opts = { duration: _this.options.animationDuration, easing: _this.options.animationEasing }; $(window).one('load', function(){ _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'), scrollPos = $(arrival).offset().top - _this.options.threshold / 2 - _this.options.barOffset; // requestAnimationFrame is disabled for this plugin currently // Foundation.Move(_this.options.animationDuration, $body, function(){ $body.stop(true).animate({ scrollTop: scrollPos }, opts); }); // }); }; /** * Calls necessary functions to update Magellan upon DOM change * @function */ Magellan.prototype.reflow = function(){ 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 */ Magellan.prototype._updateActive = function(/*evt, elem, scrollPos*/){ 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 = 0; } else{ var isDown = this.scrollPos < winPos, _this = this, curVisible = this.points.filter(function(p, i){ return isDown ? p <= winPos : p - _this.options.threshold <= winPos;//&& winPos >= _this.points[i -1] - _this.options.threshold; }); curIdx = curVisible.length ? curVisible.length - 1 : 0; } this.$active.removeClass(this.options.activeClass); this.$active = this.$links.eq(curIdx).addClass(this.options.activeClass); if(this.options.deepLinking){ var hash = this.$active[0].getAttribute('href'); 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 */ Magellan.prototype.destroy = function(){ 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); }; Foundation.plugin(Magellan, 'Magellan'); // Exports for AMD/Browserify if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') module.exports = Magellan; if (typeof define === 'function') define(['foundation'], function() { return Magellan; }); }(Foundation, jQuery);