diff --git a/jquery.sticky.js b/jquery.sticky.js index f50486f..4f69673 100644 --- a/jquery.sticky.js +++ b/jquery.sticky.js @@ -25,263 +25,296 @@ var slice = Array.prototype.slice; // save ref to original slice() var splice = Array.prototype.splice; // save ref to original slice() - var defaults = { - topSpacing: 0, - bottomSpacing: 0, - className: 'is-sticky', - wrapperClassName: 'sticky-wrapper', - center: false, - getWidthFrom: '', - widthFromWrapper: true, // works only when .getWidthFrom is empty - responsiveWidth: false, - zIndex: 'auto' - }, - $window = $(window), - $document = $(document), - sticked = [], - windowHeight = $window.height(), - scroller = function() { - var scrollTop = $window.scrollTop(), - documentHeight = $document.height(), - dwh = documentHeight - windowHeight, - extra = (scrollTop > dwh) ? dwh - scrollTop : 0; + var defaults = { + topSpacing: 0, + bottomSpacing: 0, + className: 'is-sticky', + wrapperClassName: 'sticky-wrapper', + center: false, + getWidthFrom: '', + widthFromWrapper: true, // works only when .getWidthFrom is empty + responsiveWidth: false, + zIndex: 'auto' + }, + $window = $(window), + $document = $(document), + sticked = [], + windowHeight = $window.height(), + scroller = throttle(function() { + console.log('hi') + var scrollTop = $window.scrollTop(), + documentHeight = $document.height(), + dwh = documentHeight - windowHeight, + extra = (scrollTop > dwh) ? dwh - scrollTop : 0; - for (var i = 0, l = sticked.length; i < l; i++) { - var s = sticked[i], - elementTop = s.stickyWrapper.offset().top, - etse = elementTop - s.topSpacing - extra; + for (var i = 0, l = sticked.length; i < l; i++) { + var s = sticked[i], + elementTop = s.stickyWrapper.offset().top, + etse = elementTop - s.topSpacing - extra; - //update height in case of dynamic content - s.stickyWrapper.css('height', s.stickyElement.outerHeight()); + //update height in case of dynamic content + s.stickyWrapper.css('height', s.stickyElement.outerHeight()); - if (scrollTop <= etse) { - if (s.currentTop !== null) { - s.stickyElement - .css({ - 'width': '', - 'position': '', - 'top': '', - 'z-index': '' - }); - s.stickyElement.parent().removeClass(s.className); - s.stickyElement.trigger('sticky-end', [s]); - s.currentTop = null; - } - } - else { - var newTop = documentHeight - s.stickyElement.outerHeight() - - s.topSpacing - s.bottomSpacing - scrollTop - extra; - if (newTop < 0) { - newTop = newTop + s.topSpacing; - } else { - newTop = s.topSpacing; - } - if (s.currentTop !== newTop) { - var newWidth; - if (s.getWidthFrom) { - newWidth = $(s.getWidthFrom).width() || null; - } else if (s.widthFromWrapper) { - newWidth = s.stickyWrapper.width(); - } - if (newWidth == null) { - newWidth = s.stickyElement.width(); - } - s.stickyElement - .css('width', newWidth) - .css('position', 'fixed') - .css('top', newTop) - .css('z-index', s.zIndex); + if (scrollTop <= etse) { + if (s.currentTop !== null) { + s.stickyElement + .css({ + 'width': '', + 'position': '', + 'top': '', + 'z-index': '' + }); + s.stickyElement.parent().removeClass(s.className).css('height','');; + s.stickyElement.trigger('sticky-end', [s]); + s.currentTop = null; + } + } + else { + var newTop = documentHeight - s.stickyElement.outerHeight() + - s.topSpacing - s.bottomSpacing - scrollTop - extra; + if (newTop < 0) { + newTop = newTop + s.topSpacing; + } else { + newTop = s.topSpacing; + } + if (s.currentTop !== newTop) { + var newWidth; + if (s.getWidthFrom) { + newWidth = $(s.getWidthFrom).width() || null; + } else if (s.widthFromWrapper) { + newWidth = s.stickyWrapper.width(); + } + if (newWidth == null) { + newWidth = s.stickyElement.width(); + } + s.stickyElement + .css('width', newWidth) + .css('position', 'fixed') + .css('top', newTop) + .css('z-index', s.zIndex); - s.stickyElement.parent().addClass(s.className); + s.stickyElement.parent().addClass(s.className); - if (s.currentTop === null) { - s.stickyElement.trigger('sticky-start', [s]); - } else { - // sticky is started but it have to be repositioned - s.stickyElement.trigger('sticky-update', [s]); - } + if (s.currentTop === null) { + s.stickyElement.trigger('sticky-start', [s]); + } else { + // sticky is started but it have to be repositioned + s.stickyElement.trigger('sticky-update', [s]); + } - if (s.currentTop === s.topSpacing && s.currentTop > newTop || s.currentTop === null && newTop < s.topSpacing) { - // just reached bottom || just started to stick but bottom is already reached - s.stickyElement.trigger('sticky-bottom-reached', [s]); - } else if(s.currentTop !== null && newTop === s.topSpacing && s.currentTop < newTop) { - // sticky is started && sticked at topSpacing && overflowing from top just finished - s.stickyElement.trigger('sticky-bottom-unreached', [s]); - } + if (s.currentTop === s.topSpacing && s.currentTop > newTop || s.currentTop === null && newTop < s.topSpacing) { + // just reached bottom || just started to stick but bottom is already reached + s.stickyElement.trigger('sticky-bottom-reached', [s]); + } else if(s.currentTop !== null && newTop === s.topSpacing && s.currentTop < newTop) { + // sticky is started && sticked at topSpacing && overflowing from top just finished + s.stickyElement.trigger('sticky-bottom-unreached', [s]); + } - s.currentTop = newTop; - } + s.currentTop = newTop; + } - // Check if sticky has reached end of container and stop sticking - var stickyWrapperContainer = s.stickyWrapper.parent(); - var unstick = (s.stickyElement.offset().top + s.stickyElement.outerHeight() >= stickyWrapperContainer.offset().top + stickyWrapperContainer.outerHeight()) && (s.stickyElement.offset().top <= s.topSpacing); + // Check if sticky has reached end of container and stop sticking + var stickyWrapperContainer = s.stickyWrapper.parent(); + var unstick = (s.stickyElement.offset().top + s.stickyElement.outerHeight() >= stickyWrapperContainer.offset().top + stickyWrapperContainer.outerHeight()) && (s.stickyElement.offset().top <= s.topSpacing); - if( unstick ) { - s.stickyElement - .css('position', 'absolute') - .css('top', '') - .css('bottom', 0) - .css('z-index', ''); - } else { - s.stickyElement - .css('position', 'fixed') - .css('top', newTop) - .css('bottom', '') - .css('z-index', s.zIndex); - } - } - } - }, - resizer = function() { - windowHeight = $window.height(); + if( unstick ) { + s.stickyElement + .css('position', 'absolute') + .css('top', '') + .css('bottom', 0) + .css('z-index', ''); + } else { + s.stickyElement + .css('position', 'fixed') + .css('top', newTop) + .css('bottom', '') + .css('z-index', s.zIndex); + } + } + } + },200), + resizer = throttle(function() { + windowHeight = $window.height(); - for (var i = 0, l = sticked.length; i < l; i++) { - var s = sticked[i]; - var newWidth = null; - if (s.getWidthFrom) { - if (s.responsiveWidth) { - newWidth = $(s.getWidthFrom).width(); + for (var i = 0, l = sticked.length; i < l; i++) { + var s = sticked[i]; + var newWidth = null; + if (s.getWidthFrom) { + if (s.responsiveWidth) { + newWidth = $(s.getWidthFrom).width(); + } + } else if(s.widthFromWrapper) { + newWidth = s.stickyWrapper.width(); + } + if (newWidth != null) { + s.stickyElement.css('width', newWidth); + } } - } else if(s.widthFromWrapper) { - newWidth = s.stickyWrapper.width(); - } - if (newWidth != null) { - s.stickyElement.css('width', newWidth); - } - } - }, - methods = { - init: function(options) { - return this.each(function() { - var o = $.extend({}, defaults, options); - var stickyElement = $(this); + },200), + methods = { + init: function(options) { + return this.each(function() { + var o = $.extend({}, defaults, options); + var stickyElement = $(this); - var stickyId = stickyElement.attr('id'); - var wrapperId = stickyId ? stickyId + '-' + defaults.wrapperClassName : defaults.wrapperClassName; - var wrapper = $('
') - .attr('id', wrapperId) - .addClass(o.wrapperClassName); + var stickyId = stickyElement.attr('id'); + var wrapperId = stickyId ? stickyId + '-' + defaults.wrapperClassName : defaults.wrapperClassName; + var wrapper = $('
') + .attr('id', wrapperId) + .addClass(o.wrapperClassName); - stickyElement.wrapAll(function() { - if ($(this).parent("#" + wrapperId).length == 0) { - return wrapper; - } -}); + stickyElement.wrapAll(function() { + if ($(this).parent("#" + wrapperId).length == 0) { + return wrapper; + } + }); - var stickyWrapper = stickyElement.parent(); + var stickyWrapper = stickyElement.parent(); - if (o.center) { - stickyWrapper.css({width:stickyElement.outerWidth(),marginLeft:"auto",marginRight:"auto"}); - } + if (o.center) { + stickyWrapper.css({width:stickyElement.outerWidth(),marginLeft:"auto",marginRight:"auto"}); + } - if (stickyElement.css("float") === "right") { - stickyElement.css({"float":"none"}).parent().css({"float":"right"}); - } + if (stickyElement.css("float") === "right") { + stickyElement.css({"float":"none"}).parent().css({"float":"right"}); + } - o.stickyElement = stickyElement; - o.stickyWrapper = stickyWrapper; - o.currentTop = null; + o.stickyElement = stickyElement; + o.stickyWrapper = stickyWrapper; + o.currentTop = null; - sticked.push(o); + sticked.push(o); - methods.setWrapperHeight(this); - methods.setupChangeListeners(this); - }); - }, + methods.setWrapperHeight(this); + methods.setupChangeListeners(this); + }); + }, - setWrapperHeight: function(stickyElement) { - var element = $(stickyElement); - var stickyWrapper = element.parent(); - if (stickyWrapper) { - stickyWrapper.css('height', element.outerHeight()); - } - }, + setWrapperHeight: function(stickyElement) { + var element = $(stickyElement); + var stickyWrapper = element.parent(); + if (stickyWrapper) { + stickyWrapper.css('height', element.outerHeight()); + } + }, + + setupChangeListeners: function(stickyElement) { + if (window.MutationObserver) { + var mutationObserver = new window.MutationObserver(function(mutations) { + if (mutations[0].addedNodes.length || mutations[0].removedNodes.length) { + methods.setWrapperHeight(stickyElement); + } + }); + mutationObserver.observe(stickyElement, {subtree: true, childList: true}); + } else { + if (window.addEventListener) { + stickyElement.addEventListener('DOMNodeInserted', function() { + methods.setWrapperHeight(stickyElement); + }, false); + stickyElement.addEventListener('DOMNodeRemoved', function() { + methods.setWrapperHeight(stickyElement); + }, false); + } else if (window.attachEvent) { + stickyElement.attachEvent('onDOMNodeInserted', function() { + methods.setWrapperHeight(stickyElement); + }); + stickyElement.attachEvent('onDOMNodeRemoved', function() { + methods.setWrapperHeight(stickyElement); + }); + } + } + }, + update: scroller, + unstick: function(options) { + return this.each(function() { + var that = this; + var unstickyElement = $(that); - setupChangeListeners: function(stickyElement) { - if (window.MutationObserver) { - var mutationObserver = new window.MutationObserver(function(mutations) { - if (mutations[0].addedNodes.length || mutations[0].removedNodes.length) { - methods.setWrapperHeight(stickyElement); + var removeIdx = -1; + var i = sticked.length; + while (i-- > 0) { + if (sticked[i].stickyElement.get(0) === that) { + splice.call(sticked,i,1); + removeIdx = i; + } + } + if(removeIdx !== -1) { + unstickyElement.unwrap(); + unstickyElement + .css({ + 'width': '', + 'position': '', + 'top': '', + 'float': '', + 'z-index': '' + }) + ; + } + }); } - }); - mutationObserver.observe(stickyElement, {subtree: true, childList: true}); + }; + + // should be more efficient than using $window.scroll(scroller) and $window.resize(resizer): + if (window.addEventListener) { + window.addEventListener('scroll', scroller, false); + window.addEventListener('resize', resizer, false); + } else if (window.attachEvent) { + window.attachEvent('onscroll', scroller); + window.attachEvent('onresize', resizer); + } + + $.fn.sticky = function(method) { + if (methods[method]) { + return methods[method].apply(this, slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method ) { + return methods.init.apply( this, arguments ); } else { - if (window.addEventListener) { - stickyElement.addEventListener('DOMNodeInserted', function() { - methods.setWrapperHeight(stickyElement); - }, false); - stickyElement.addEventListener('DOMNodeRemoved', function() { - methods.setWrapperHeight(stickyElement); - }, false); - } else if (window.attachEvent) { - stickyElement.attachEvent('onDOMNodeInserted', function() { - methods.setWrapperHeight(stickyElement); - }); - stickyElement.attachEvent('onDOMNodeRemoved', function() { - methods.setWrapperHeight(stickyElement); - }); - } + $.error('Method ' + method + ' does not exist on jQuery.sticky'); } - }, - update: scroller, - unstick: function(options) { - return this.each(function() { - var that = this; - var unstickyElement = $(that); + }; - var removeIdx = -1; - var i = sticked.length; - while (i-- > 0) { - if (sticked[i].stickyElement.get(0) === that) { - splice.call(sticked,i,1); - removeIdx = i; - } - } - if(removeIdx !== -1) { - unstickyElement.unwrap(); - unstickyElement - .css({ - 'width': '', - 'position': '', - 'top': '', - 'float': '', - 'z-index': '' - }) - ; - } - }); - } + $.fn.unstick = function(method) { + if (methods[method]) { + return methods[method].apply(this, slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method ) { + return methods.unstick.apply( this, arguments ); + } else { + $.error('Method ' + method + ' does not exist on jQuery.sticky'); + } }; - // should be more efficient than using $window.scroll(scroller) and $window.resize(resizer): - if (window.addEventListener) { - window.addEventListener('scroll', scroller, false); - window.addEventListener('resize', resizer, false); - } else if (window.attachEvent) { - window.attachEvent('onscroll', scroller); - window.attachEvent('onresize', resizer); - } - $.fn.sticky = function(method) { - if (methods[method]) { - return methods[method].apply(this, slice.call(arguments, 1)); - } else if (typeof method === 'object' || !method ) { - return methods.init.apply( this, arguments ); - } else { - $.error('Method ' + method + ' does not exist on jQuery.sticky'); - } - }; + //@todo Remove debounce in future release + function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; + }; - $.fn.unstick = function(method) { - if (methods[method]) { - return methods[method].apply(this, slice.call(arguments, 1)); - } else if (typeof method === 'object' || !method ) { - return methods.unstick.apply( this, arguments ); - } else { - $.error('Method ' + method + ' does not exist on jQuery.sticky'); + function throttle (callback, limit) { + var wait = false; // Initially, we're not waiting + return function () { // We return a throttled function + if (!wait) { // If we're not waiting + callback.call(); // Execute users function + wait = true; // Prevent future invocations + setTimeout(function () { // After a period of time + wait = false; // And allow future invocations + callback.call(); + }, limit); + } + } } - }; - $(function() { - setTimeout(scroller, 0); - }); + + $(function() { + setTimeout(scroller, 0); + }); }));