/** * HSScrollNav Component. * * @author Htmlstream * @version 1.0 * @requires jQuery * */ ;(function ($) { 'use strict'; $.HSCore.components.HSScrollNav = { /** * Base configuraion of the component. * * @private */ _baseConfig: { duration: 400, easing: 'linear', over: $(), activeItemClass: 'active', afterShow: function(){}, beforeShow: function(){} }, /** * All initialized item on the page. * * @private */ _pageCollection: $(), /** * Initialization of the component. * * @param {jQuery} collection * @param {Object} config * * @public * @return {jQuery} */ init: function(collection, config) { var self = this; if( !collection || !collection.length ) return $(); collection.each(function(i, el) { var $this = $(el), itemConfig = config && $.isPlainObject(config) ? $.extend(true, {}, self._baseConfig, config, $this.data()) : $.extend(true, {}, self._baseConfig, $this.data()); if( !$this.data('HSScrollNav') ) { $this.data('HSScrollNav', new HSScrollNav($this, itemConfig)); self._pageCollection = self._pageCollection.add( $this ); } }); $(window).on('scroll.HSScrollNav', function(){ self._pageCollection.each(function(i, el) { $(el).data('HSScrollNav').highlight(); }); }).trigger('scroll.HSScrollNav'); return collection; } } /** * HSScrollNav. * * @param {jQuery} element * @param {Object} config * * @constructor */ function HSScrollNav(element, config) { /** * Current element. * * @public */ this.element = element; /** * Configuraion. * * @public */ this.config = config; /** * Sections. * * @public */ this._items = $(); this._makeItems(); this._bindEvents(); } /** * Return collection of sections. * * @private * @return {jQuery} */ HSScrollNav.prototype._makeItems = function() { var self = this; this.element.find('a[href^="#"]').each(function(i, el) { var $this = $(el); if( !$this.data('HSScrollNavSection') ) { $this.data('HSScrollNavSection', new HSScrollNavSection($this, self.config)); self._items = self._items.add( $this ); } }); }; /** * Binds necessary events. * * @private * @return {undefined} */ HSScrollNav.prototype._bindEvents = function() { var self = this; this.element.on('click.HSScrollNav', 'a[href^="#"]', function(e) { var link = this; self._lockHightlight = true; if(self.current) self.current.unhighlight(); link.blur(); self.current = $(link).data('HSScrollNavSection'); self.current.highlight(); $(this).data('HSScrollNavSection').show( function(){ self._lockHightlight = false; } ); e.preventDefault(); }); }; /** * Activates necessary menu item. * * @public */ HSScrollNav.prototype.highlight = function() { var self = this, items, currentItem, current, scrollTop; if(!this._items.length || this._lockHightlight) return; scrollTop = $(window).scrollTop(); if(scrollTop + $(window).height() === $(document).height()) { this.current = this._items.last().data('HSScrollNavSection'); this.unhighlight(); this.current.highlight(); this.current.changeHash(); return; } this._items.each(function(i, el){ var Section = $(el).data('HSScrollNavSection'), $section = Section.section; if(scrollTop > Section.offset) { current = Section; } }); if(current && this.current != current) { this.unhighlight(); current.highlight(); if(this.current) current.changeHash(); this.current = current; } }; /** * Deactivates all menu items. * * @public */ HSScrollNav.prototype.unhighlight = function() { this._items.each(function(i, el){ $(el).data('HSScrollNavSection').unhighlight(); }); }; /** * HSScrollNavSection. * * @param {jQuery} element * * @constructor */ function HSScrollNavSection(element, config) { var self = this; /** * Current section. * * @public */ this.element = element; /** * Configuration. * * @public */ this.config = config; /** * Getter for acces to the section element. * * @public */ Object.defineProperty(this, 'section', { value: $(self.element.attr('href')) }); /** * Getter for determinate position of the section relative to document. * * @public */ Object.defineProperty(this, 'offset', { get: function() { var header = $('.u-header'), headerStyles = getComputedStyle(header.get(0)), headerPosition = headerStyles.position, offset = self.section.offset().top; if(header.length && headerPosition == 'fixed' && parseInt(headerStyles.top) == 0) { offset = offset - header.outerHeight() - parseInt(headerStyles.marginTop); } if(self.config.over.length) { offset = offset - self.config.over.outerHeight(); } return offset; } }); } /** * Moves to the section. * * @public */ HSScrollNavSection.prototype.show = function(callback) { var self = this; if( !this.section.length ) return; self.config.beforeShow.call(self.section); this.changeHash(); $('html, body').stop().animate({ scrollTop: self.offset + 3 }, { duration: self.config.duration, easing: self.config.easing, complete: function() { $('html, body').stop().animate({ scrollTop: self.offset + 3 }, { duration: self.config.duration, easing: self.config.easing, complete: function() { self.config.afterShow.call(self.section); if($.isFunction(callback)) callback(); } }); } }); }; /** * Changes location's hash. * * @public */ HSScrollNavSection.prototype.changeHash = function() { this.section.attr('id', ''); window.location.hash = this.element.attr('href'); this.section.attr('id', this.element.attr('href').slice(1)); }; /** * Activates the menu item. * * @public */ HSScrollNavSection.prototype.highlight = function() { var parent = this.element.parent('li'); if(parent.length) parent.addClass(this.config.activeItemClass); }; /** * Deactivates the menu item. * * @public */ HSScrollNavSection.prototype.unhighlight = function() { var parent = this.element.parent('li'); if(parent.length) parent.removeClass(this.config.activeItemClass); }; })(jQuery);