/** * Single Page Nav Plugin * Copyright (c) 2013 Chris Wojcik * Dual licensed under MIT and GPL. * @author Chris Wojcik * @version 1.1.0 */ // Utility if (typeof Object.create !== 'function') { Object.create = function(obj) { function F() {} F.prototype = obj; return new F(); }; } (function($, window, document, undefined) { "use strict"; var SinglePageNav = { init: function(options, container) { this.options = $.extend({}, $.fn.singlePageNav.defaults, options); this.container = container; this.$container = $(container); this.$links = this.$container.find('a'); if (this.options.filter !== '') { this.$links = this.$links.filter(this.options.filter); } this.$window = $(window); this.$htmlbody = $('html, body'); this.$links.on('click.singlePageNav', $.proxy(this.handleClick, this)); this.didScroll = false; this.checkPosition(); this.setTimer(); }, handleClick: function(e) { var self = this, link = e.currentTarget, $elem = $(link.hash); e.preventDefault(); if ($elem.length) { // Make sure the target elem exists // Prevent active link from cycling during the scroll self.clearTimer(); // Before scrolling starts if (typeof self.options.beforeStart === 'function') { self.options.beforeStart(); } self.setActiveLink(link.hash); self.scrollTo($elem, function() { if (self.options.updateHash) { document.location.hash = link.hash; } self.setTimer(); // After scrolling ends if (typeof self.options.onComplete === 'function') { self.options.onComplete(); } }); } }, scrollTo: function($elem, callback) { var self = this; var target = self.getCoords($elem).top; var called = false; self.$htmlbody.stop().animate( {scrollTop: target}, { duration: self.options.speed, complete: function() { if (typeof callback === 'function' && !called) { callback(); } called = true; } } ); }, setTimer: function() { var self = this; self.$window.on('scroll.singlePageNav', function() { self.didScroll = true; }); self.timer = setInterval(function() { if (self.didScroll) { self.didScroll = false; self.checkPosition(); } }, 250); }, clearTimer: function() { clearInterval(this.timer); this.$window.off('scroll.singlePageNav'); this.didScroll = false; }, // Check the scroll position and set the active section checkPosition: function() { var scrollPos = this.$window.scrollTop(); var currentSection = this.getCurrentSection(scrollPos); this.setActiveLink(currentSection); }, getCoords: function($elem) { return { top: Math.round($elem.offset().top) - this.options.offset }; }, setActiveLink: function(href) { var $activeLink = this.$container.find("a[href='" + href + "']"); var $parent = $activeLink.parent(); this.$container.find('.' + this.options.currentClass).removeClass(this.options.currentClass); $parent.addClass(this.options.currentClass); }, getCurrentSection: function(scrollPos) { var i, hash, coords, section; for (i = 0; i < this.$links.length; i++) { hash = this.$links[i].hash; if ($(hash).length) { coords = this.getCoords($(hash)); if (scrollPos >= coords.top - this.options.threshold) { section = hash; } } } // The current section or the first link return section || this.$links[0].hash; } }; $.fn.singlePageNav = function(options) { return this.each(function() { var singlePageNav = Object.create(SinglePageNav); singlePageNav.init(options, this); }); }; $.fn.singlePageNav.defaults = { offset: 0, threshold: 120, speed: 400, currentClass: 'current', updateHash: false, filter: '', onComplete: false, beforeStart: false }; })(jQuery, window, document);