@@ 16697-17645 (lines=949) @@ | ||
16694 | * |
|
16695 | */ |
|
16696 | ||
16697 | ;(function ($, window, document, undefined) { |
|
16698 | ||
16699 | "use strict"; |
|
16700 | ||
16701 | window = (typeof window != 'undefined' && window.Math == Math) |
|
16702 | ? window |
|
16703 | : (typeof self != 'undefined' && self.Math == Math) |
|
16704 | ? self |
|
16705 | : Function('return this')() |
|
16706 | ; |
|
16707 | ||
16708 | $.fn.sticky = function(parameters) { |
|
16709 | var |
|
16710 | $allModules = $(this), |
|
16711 | moduleSelector = $allModules.selector || '', |
|
16712 | ||
16713 | time = new Date().getTime(), |
|
16714 | performance = [], |
|
16715 | ||
16716 | query = arguments[0], |
|
16717 | methodInvoked = (typeof query == 'string'), |
|
16718 | queryArguments = [].slice.call(arguments, 1), |
|
16719 | returnedValue |
|
16720 | ; |
|
16721 | ||
16722 | $allModules |
|
16723 | .each(function() { |
|
16724 | var |
|
16725 | settings = ( $.isPlainObject(parameters) ) |
|
16726 | ? $.extend(true, {}, $.fn.sticky.settings, parameters) |
|
16727 | : $.extend({}, $.fn.sticky.settings), |
|
16728 | ||
16729 | className = settings.className, |
|
16730 | namespace = settings.namespace, |
|
16731 | error = settings.error, |
|
16732 | ||
16733 | eventNamespace = '.' + namespace, |
|
16734 | moduleNamespace = 'module-' + namespace, |
|
16735 | ||
16736 | $module = $(this), |
|
16737 | $window = $(window), |
|
16738 | $scroll = $(settings.scrollContext), |
|
16739 | $container, |
|
16740 | $context, |
|
16741 | ||
16742 | selector = $module.selector || '', |
|
16743 | instance = $module.data(moduleNamespace), |
|
16744 | ||
16745 | requestAnimationFrame = window.requestAnimationFrame |
|
16746 | || window.mozRequestAnimationFrame |
|
16747 | || window.webkitRequestAnimationFrame |
|
16748 | || window.msRequestAnimationFrame |
|
16749 | || function(callback) { setTimeout(callback, 0); }, |
|
16750 | ||
16751 | element = this, |
|
16752 | ||
16753 | documentObserver, |
|
16754 | observer, |
|
16755 | module |
|
16756 | ; |
|
16757 | ||
16758 | module = { |
|
16759 | ||
16760 | initialize: function() { |
|
16761 | ||
16762 | module.determineContainer(); |
|
16763 | module.determineContext(); |
|
16764 | module.verbose('Initializing sticky', settings, $container); |
|
16765 | ||
16766 | module.save.positions(); |
|
16767 | module.checkErrors(); |
|
16768 | module.bind.events(); |
|
16769 | ||
16770 | if(settings.observeChanges) { |
|
16771 | module.observeChanges(); |
|
16772 | } |
|
16773 | module.instantiate(); |
|
16774 | }, |
|
16775 | ||
16776 | instantiate: function() { |
|
16777 | module.verbose('Storing instance of module', module); |
|
16778 | instance = module; |
|
16779 | $module |
|
16780 | .data(moduleNamespace, module) |
|
16781 | ; |
|
16782 | }, |
|
16783 | ||
16784 | destroy: function() { |
|
16785 | module.verbose('Destroying previous instance'); |
|
16786 | module.reset(); |
|
16787 | if(documentObserver) { |
|
16788 | documentObserver.disconnect(); |
|
16789 | } |
|
16790 | if(observer) { |
|
16791 | observer.disconnect(); |
|
16792 | } |
|
16793 | $window |
|
16794 | .off('load' + eventNamespace, module.event.load) |
|
16795 | .off('resize' + eventNamespace, module.event.resize) |
|
16796 | ; |
|
16797 | $scroll |
|
16798 | .off('scrollchange' + eventNamespace, module.event.scrollchange) |
|
16799 | ; |
|
16800 | $module.removeData(moduleNamespace); |
|
16801 | }, |
|
16802 | ||
16803 | observeChanges: function() { |
|
16804 | if('MutationObserver' in window) { |
|
16805 | documentObserver = new MutationObserver(module.event.documentChanged); |
|
16806 | observer = new MutationObserver(module.event.changed); |
|
16807 | documentObserver.observe(document, { |
|
16808 | childList : true, |
|
16809 | subtree : true |
|
16810 | }); |
|
16811 | observer.observe(element, { |
|
16812 | childList : true, |
|
16813 | subtree : true |
|
16814 | }); |
|
16815 | observer.observe($context[0], { |
|
16816 | childList : true, |
|
16817 | subtree : true |
|
16818 | }); |
|
16819 | module.debug('Setting up mutation observer', observer); |
|
16820 | } |
|
16821 | }, |
|
16822 | ||
16823 | determineContainer: function() { |
|
16824 | if(settings.container) { |
|
16825 | $container = $(settings.container); |
|
16826 | } |
|
16827 | else { |
|
16828 | $container = $module.offsetParent(); |
|
16829 | } |
|
16830 | }, |
|
16831 | ||
16832 | determineContext: function() { |
|
16833 | if(settings.context) { |
|
16834 | $context = $(settings.context); |
|
16835 | } |
|
16836 | else { |
|
16837 | $context = $container; |
|
16838 | } |
|
16839 | if($context.length === 0) { |
|
16840 | module.error(error.invalidContext, settings.context, $module); |
|
16841 | return; |
|
16842 | } |
|
16843 | }, |
|
16844 | ||
16845 | checkErrors: function() { |
|
16846 | if( module.is.hidden() ) { |
|
16847 | module.error(error.visible, $module); |
|
16848 | } |
|
16849 | if(module.cache.element.height > module.cache.context.height) { |
|
16850 | module.reset(); |
|
16851 | module.error(error.elementSize, $module); |
|
16852 | return; |
|
16853 | } |
|
16854 | }, |
|
16855 | ||
16856 | bind: { |
|
16857 | events: function() { |
|
16858 | $window |
|
16859 | .on('load' + eventNamespace, module.event.load) |
|
16860 | .on('resize' + eventNamespace, module.event.resize) |
|
16861 | ; |
|
16862 | // pub/sub pattern |
|
16863 | $scroll |
|
16864 | .off('scroll' + eventNamespace) |
|
16865 | .on('scroll' + eventNamespace, module.event.scroll) |
|
16866 | .on('scrollchange' + eventNamespace, module.event.scrollchange) |
|
16867 | ; |
|
16868 | } |
|
16869 | }, |
|
16870 | ||
16871 | event: { |
|
16872 | changed: function(mutations) { |
|
16873 | clearTimeout(module.timer); |
|
16874 | module.timer = setTimeout(function() { |
|
16875 | module.verbose('DOM tree modified, updating sticky menu', mutations); |
|
16876 | module.refresh(); |
|
16877 | }, 100); |
|
16878 | }, |
|
16879 | documentChanged: function(mutations) { |
|
16880 | [].forEach.call(mutations, function(mutation) { |
|
16881 | if(mutation.removedNodes) { |
|
16882 | [].forEach.call(mutation.removedNodes, function(node) { |
|
16883 | if(node == element || $(node).find(element).length > 0) { |
|
16884 | module.debug('Element removed from DOM, tearing down events'); |
|
16885 | module.destroy(); |
|
16886 | } |
|
16887 | }); |
|
16888 | } |
|
16889 | }); |
|
16890 | }, |
|
16891 | load: function() { |
|
16892 | module.verbose('Page contents finished loading'); |
|
16893 | requestAnimationFrame(module.refresh); |
|
16894 | }, |
|
16895 | resize: function() { |
|
16896 | module.verbose('Window resized'); |
|
16897 | requestAnimationFrame(module.refresh); |
|
16898 | }, |
|
16899 | scroll: function() { |
|
16900 | requestAnimationFrame(function() { |
|
16901 | $scroll.triggerHandler('scrollchange' + eventNamespace, $scroll.scrollTop() ); |
|
16902 | }); |
|
16903 | }, |
|
16904 | scrollchange: function(event, scrollPosition) { |
|
16905 | module.stick(scrollPosition); |
|
16906 | settings.onScroll.call(element); |
|
16907 | } |
|
16908 | }, |
|
16909 | ||
16910 | refresh: function(hardRefresh) { |
|
16911 | module.reset(); |
|
16912 | if(!settings.context) { |
|
16913 | module.determineContext(); |
|
16914 | } |
|
16915 | if(hardRefresh) { |
|
16916 | module.determineContainer(); |
|
16917 | } |
|
16918 | module.save.positions(); |
|
16919 | module.stick(); |
|
16920 | settings.onReposition.call(element); |
|
16921 | }, |
|
16922 | ||
16923 | supports: { |
|
16924 | sticky: function() { |
|
16925 | var |
|
16926 | $element = $('<div/>'), |
|
16927 | element = $element[0] |
|
16928 | ; |
|
16929 | $element.addClass(className.supported); |
|
16930 | return($element.css('position').match('sticky')); |
|
16931 | } |
|
16932 | }, |
|
16933 | ||
16934 | save: { |
|
16935 | lastScroll: function(scroll) { |
|
16936 | module.lastScroll = scroll; |
|
16937 | }, |
|
16938 | elementScroll: function(scroll) { |
|
16939 | module.elementScroll = scroll; |
|
16940 | }, |
|
16941 | positions: function() { |
|
16942 | var |
|
16943 | scrollContext = { |
|
16944 | height : $scroll.height() |
|
16945 | }, |
|
16946 | element = { |
|
16947 | margin: { |
|
16948 | top : parseInt($module.css('margin-top'), 10), |
|
16949 | bottom : parseInt($module.css('margin-bottom'), 10), |
|
16950 | }, |
|
16951 | offset : $module.offset(), |
|
16952 | width : $module.outerWidth(), |
|
16953 | height : $module.outerHeight() |
|
16954 | }, |
|
16955 | context = { |
|
16956 | offset : $context.offset(), |
|
16957 | height : $context.outerHeight() |
|
16958 | }, |
|
16959 | container = { |
|
16960 | height: $container.outerHeight() |
|
16961 | } |
|
16962 | ; |
|
16963 | if( !module.is.standardScroll() ) { |
|
16964 | module.debug('Non-standard scroll. Removing scroll offset from element offset'); |
|
16965 | ||
16966 | scrollContext.top = $scroll.scrollTop(); |
|
16967 | scrollContext.left = $scroll.scrollLeft(); |
|
16968 | ||
16969 | element.offset.top += scrollContext.top; |
|
16970 | context.offset.top += scrollContext.top; |
|
16971 | element.offset.left += scrollContext.left; |
|
16972 | context.offset.left += scrollContext.left; |
|
16973 | } |
|
16974 | module.cache = { |
|
16975 | fits : ( (element.height + settings.offset) <= scrollContext.height), |
|
16976 | sameHeight : (element.height == context.height), |
|
16977 | scrollContext : { |
|
16978 | height : scrollContext.height |
|
16979 | }, |
|
16980 | element: { |
|
16981 | margin : element.margin, |
|
16982 | top : element.offset.top - element.margin.top, |
|
16983 | left : element.offset.left, |
|
16984 | width : element.width, |
|
16985 | height : element.height, |
|
16986 | bottom : element.offset.top + element.height |
|
16987 | }, |
|
16988 | context: { |
|
16989 | top : context.offset.top, |
|
16990 | height : context.height, |
|
16991 | bottom : context.offset.top + context.height |
|
16992 | } |
|
16993 | }; |
|
16994 | module.set.containerSize(); |
|
16995 | ||
16996 | module.stick(); |
|
16997 | module.debug('Caching element positions', module.cache); |
|
16998 | } |
|
16999 | }, |
|
17000 | ||
17001 | get: { |
|
17002 | direction: function(scroll) { |
|
17003 | var |
|
17004 | direction = 'down' |
|
17005 | ; |
|
17006 | scroll = scroll || $scroll.scrollTop(); |
|
17007 | if(module.lastScroll !== undefined) { |
|
17008 | if(module.lastScroll < scroll) { |
|
17009 | direction = 'down'; |
|
17010 | } |
|
17011 | else if(module.lastScroll > scroll) { |
|
17012 | direction = 'up'; |
|
17013 | } |
|
17014 | } |
|
17015 | return direction; |
|
17016 | }, |
|
17017 | scrollChange: function(scroll) { |
|
17018 | scroll = scroll || $scroll.scrollTop(); |
|
17019 | return (module.lastScroll) |
|
17020 | ? (scroll - module.lastScroll) |
|
17021 | : 0 |
|
17022 | ; |
|
17023 | }, |
|
17024 | currentElementScroll: function() { |
|
17025 | if(module.elementScroll) { |
|
17026 | return module.elementScroll; |
|
17027 | } |
|
17028 | return ( module.is.top() ) |
|
17029 | ? Math.abs(parseInt($module.css('top'), 10)) || 0 |
|
17030 | : Math.abs(parseInt($module.css('bottom'), 10)) || 0 |
|
17031 | ; |
|
17032 | }, |
|
17033 | ||
17034 | elementScroll: function(scroll) { |
|
17035 | scroll = scroll || $scroll.scrollTop(); |
|
17036 | var |
|
17037 | element = module.cache.element, |
|
17038 | scrollContext = module.cache.scrollContext, |
|
17039 | delta = module.get.scrollChange(scroll), |
|
17040 | maxScroll = (element.height - scrollContext.height + settings.offset), |
|
17041 | elementScroll = module.get.currentElementScroll(), |
|
17042 | possibleScroll = (elementScroll + delta) |
|
17043 | ; |
|
17044 | if(module.cache.fits || possibleScroll < 0) { |
|
17045 | elementScroll = 0; |
|
17046 | } |
|
17047 | else if(possibleScroll > maxScroll ) { |
|
17048 | elementScroll = maxScroll; |
|
17049 | } |
|
17050 | else { |
|
17051 | elementScroll = possibleScroll; |
|
17052 | } |
|
17053 | return elementScroll; |
|
17054 | } |
|
17055 | }, |
|
17056 | ||
17057 | remove: { |
|
17058 | lastScroll: function() { |
|
17059 | delete module.lastScroll; |
|
17060 | }, |
|
17061 | elementScroll: function(scroll) { |
|
17062 | delete module.elementScroll; |
|
17063 | }, |
|
17064 | minimumSize: function() { |
|
17065 | $container |
|
17066 | .css('min-height', '') |
|
17067 | ; |
|
17068 | }, |
|
17069 | offset: function() { |
|
17070 | $module.css('margin-top', ''); |
|
17071 | } |
|
17072 | }, |
|
17073 | ||
17074 | set: { |
|
17075 | offset: function() { |
|
17076 | module.verbose('Setting offset on element', settings.offset); |
|
17077 | $module |
|
17078 | .css('margin-top', settings.offset) |
|
17079 | ; |
|
17080 | }, |
|
17081 | containerSize: function() { |
|
17082 | var |
|
17083 | tagName = $container.get(0).tagName |
|
17084 | ; |
|
17085 | if(tagName === 'HTML' || tagName == 'body') { |
|
17086 | // this can trigger for too many reasons |
|
17087 | //module.error(error.container, tagName, $module); |
|
17088 | module.determineContainer(); |
|
17089 | } |
|
17090 | else { |
|
17091 | if( Math.abs($container.outerHeight() - module.cache.context.height) > settings.jitter) { |
|
17092 | module.debug('Context has padding, specifying exact height for container', module.cache.context.height); |
|
17093 | $container.css({ |
|
17094 | height: module.cache.context.height |
|
17095 | }); |
|
17096 | } |
|
17097 | } |
|
17098 | }, |
|
17099 | minimumSize: function() { |
|
17100 | var |
|
17101 | element = module.cache.element |
|
17102 | ; |
|
17103 | $container |
|
17104 | .css('min-height', element.height) |
|
17105 | ; |
|
17106 | }, |
|
17107 | scroll: function(scroll) { |
|
17108 | module.debug('Setting scroll on element', scroll); |
|
17109 | if(module.elementScroll == scroll) { |
|
17110 | return; |
|
17111 | } |
|
17112 | if( module.is.top() ) { |
|
17113 | $module |
|
17114 | .css('bottom', '') |
|
17115 | .css('top', -scroll) |
|
17116 | ; |
|
17117 | } |
|
17118 | if( module.is.bottom() ) { |
|
17119 | $module |
|
17120 | .css('top', '') |
|
17121 | .css('bottom', scroll) |
|
17122 | ; |
|
17123 | } |
|
17124 | }, |
|
17125 | size: function() { |
|
17126 | if(module.cache.element.height !== 0 && module.cache.element.width !== 0) { |
|
17127 | element.style.setProperty('width', module.cache.element.width + 'px', 'important'); |
|
17128 | element.style.setProperty('height', module.cache.element.height + 'px', 'important'); |
|
17129 | } |
|
17130 | } |
|
17131 | }, |
|
17132 | ||
17133 | is: { |
|
17134 | standardScroll: function() { |
|
17135 | return ($scroll[0] == window); |
|
17136 | }, |
|
17137 | top: function() { |
|
17138 | return $module.hasClass(className.top); |
|
17139 | }, |
|
17140 | bottom: function() { |
|
17141 | return $module.hasClass(className.bottom); |
|
17142 | }, |
|
17143 | initialPosition: function() { |
|
17144 | return (!module.is.fixed() && !module.is.bound()); |
|
17145 | }, |
|
17146 | hidden: function() { |
|
17147 | return (!$module.is(':visible')); |
|
17148 | }, |
|
17149 | bound: function() { |
|
17150 | return $module.hasClass(className.bound); |
|
17151 | }, |
|
17152 | fixed: function() { |
|
17153 | return $module.hasClass(className.fixed); |
|
17154 | } |
|
17155 | }, |
|
17156 | ||
17157 | stick: function(scroll) { |
|
17158 | var |
|
17159 | cachedPosition = scroll || $scroll.scrollTop(), |
|
17160 | cache = module.cache, |
|
17161 | fits = cache.fits, |
|
17162 | sameHeight = cache.sameHeight, |
|
17163 | element = cache.element, |
|
17164 | scrollContext = cache.scrollContext, |
|
17165 | context = cache.context, |
|
17166 | offset = (module.is.bottom() && settings.pushing) |
|
17167 | ? settings.bottomOffset |
|
17168 | : settings.offset, |
|
17169 | scroll = { |
|
17170 | top : cachedPosition + offset, |
|
17171 | bottom : cachedPosition + offset + scrollContext.height |
|
17172 | }, |
|
17173 | direction = module.get.direction(scroll.top), |
|
17174 | elementScroll = (fits) |
|
17175 | ? 0 |
|
17176 | : module.get.elementScroll(scroll.top), |
|
17177 | ||
17178 | // shorthand |
|
17179 | doesntFit = !fits, |
|
17180 | elementVisible = (element.height !== 0) |
|
17181 | ; |
|
17182 | if(elementVisible && !sameHeight) { |
|
17183 | ||
17184 | if( module.is.initialPosition() ) { |
|
17185 | if(scroll.top >= context.bottom) { |
|
17186 | module.debug('Initial element position is bottom of container'); |
|
17187 | module.bindBottom(); |
|
17188 | } |
|
17189 | else if(scroll.top > element.top) { |
|
17190 | if( (element.height + scroll.top - elementScroll) >= context.bottom ) { |
|
17191 | module.debug('Initial element position is bottom of container'); |
|
17192 | module.bindBottom(); |
|
17193 | } |
|
17194 | else { |
|
17195 | module.debug('Initial element position is fixed'); |
|
17196 | module.fixTop(); |
|
17197 | } |
|
17198 | } |
|
17199 | ||
17200 | } |
|
17201 | else if( module.is.fixed() ) { |
|
17202 | ||
17203 | // currently fixed top |
|
17204 | if( module.is.top() ) { |
|
17205 | if( scroll.top <= element.top ) { |
|
17206 | module.debug('Fixed element reached top of container'); |
|
17207 | module.setInitialPosition(); |
|
17208 | } |
|
17209 | else if( (element.height + scroll.top - elementScroll) >= context.bottom ) { |
|
17210 | module.debug('Fixed element reached bottom of container'); |
|
17211 | module.bindBottom(); |
|
17212 | } |
|
17213 | // scroll element if larger than screen |
|
17214 | else if(doesntFit) { |
|
17215 | module.set.scroll(elementScroll); |
|
17216 | module.save.lastScroll(scroll.top); |
|
17217 | module.save.elementScroll(elementScroll); |
|
17218 | } |
|
17219 | } |
|
17220 | ||
17221 | // currently fixed bottom |
|
17222 | else if(module.is.bottom() ) { |
|
17223 | ||
17224 | // top edge |
|
17225 | if( (scroll.bottom - element.height) <= element.top) { |
|
17226 | module.debug('Bottom fixed rail has reached top of container'); |
|
17227 | module.setInitialPosition(); |
|
17228 | } |
|
17229 | // bottom edge |
|
17230 | else if(scroll.bottom >= context.bottom) { |
|
17231 | module.debug('Bottom fixed rail has reached bottom of container'); |
|
17232 | module.bindBottom(); |
|
17233 | } |
|
17234 | // scroll element if larger than screen |
|
17235 | else if(doesntFit) { |
|
17236 | module.set.scroll(elementScroll); |
|
17237 | module.save.lastScroll(scroll.top); |
|
17238 | module.save.elementScroll(elementScroll); |
|
17239 | } |
|
17240 | ||
17241 | } |
|
17242 | } |
|
17243 | else if( module.is.bottom() ) { |
|
17244 | if( scroll.top <= element.top ) { |
|
17245 | module.debug('Jumped from bottom fixed to top fixed, most likely used home/end button'); |
|
17246 | module.setInitialPosition(); |
|
17247 | } |
|
17248 | else { |
|
17249 | if(settings.pushing) { |
|
17250 | if(module.is.bound() && scroll.bottom <= context.bottom ) { |
|
17251 | module.debug('Fixing bottom attached element to bottom of browser.'); |
|
17252 | module.fixBottom(); |
|
17253 | } |
|
17254 | } |
|
17255 | else { |
|
17256 | if(module.is.bound() && (scroll.top <= context.bottom - element.height) ) { |
|
17257 | module.debug('Fixing bottom attached element to top of browser.'); |
|
17258 | module.fixTop(); |
|
17259 | } |
|
17260 | } |
|
17261 | } |
|
17262 | } |
|
17263 | } |
|
17264 | }, |
|
17265 | ||
17266 | bindTop: function() { |
|
17267 | module.debug('Binding element to top of parent container'); |
|
17268 | module.remove.offset(); |
|
17269 | $module |
|
17270 | .css({ |
|
17271 | left : '', |
|
17272 | top : '', |
|
17273 | marginBottom : '' |
|
17274 | }) |
|
17275 | .removeClass(className.fixed) |
|
17276 | .removeClass(className.bottom) |
|
17277 | .addClass(className.bound) |
|
17278 | .addClass(className.top) |
|
17279 | ; |
|
17280 | settings.onTop.call(element); |
|
17281 | settings.onUnstick.call(element); |
|
17282 | }, |
|
17283 | bindBottom: function() { |
|
17284 | module.debug('Binding element to bottom of parent container'); |
|
17285 | module.remove.offset(); |
|
17286 | $module |
|
17287 | .css({ |
|
17288 | left : '', |
|
17289 | top : '' |
|
17290 | }) |
|
17291 | .removeClass(className.fixed) |
|
17292 | .removeClass(className.top) |
|
17293 | .addClass(className.bound) |
|
17294 | .addClass(className.bottom) |
|
17295 | ; |
|
17296 | settings.onBottom.call(element); |
|
17297 | settings.onUnstick.call(element); |
|
17298 | }, |
|
17299 | ||
17300 | setInitialPosition: function() { |
|
17301 | module.debug('Returning to initial position'); |
|
17302 | module.unfix(); |
|
17303 | module.unbind(); |
|
17304 | }, |
|
17305 | ||
17306 | ||
17307 | fixTop: function() { |
|
17308 | module.debug('Fixing element to top of page'); |
|
17309 | if(settings.setSize) { |
|
17310 | module.set.size(); |
|
17311 | } |
|
17312 | module.set.minimumSize(); |
|
17313 | module.set.offset(); |
|
17314 | $module |
|
17315 | .css({ |
|
17316 | left : module.cache.element.left, |
|
17317 | bottom : '', |
|
17318 | marginBottom : '' |
|
17319 | }) |
|
17320 | .removeClass(className.bound) |
|
17321 | .removeClass(className.bottom) |
|
17322 | .addClass(className.fixed) |
|
17323 | .addClass(className.top) |
|
17324 | ; |
|
17325 | settings.onStick.call(element); |
|
17326 | }, |
|
17327 | ||
17328 | fixBottom: function() { |
|
17329 | module.debug('Sticking element to bottom of page'); |
|
17330 | if(settings.setSize) { |
|
17331 | module.set.size(); |
|
17332 | } |
|
17333 | module.set.minimumSize(); |
|
17334 | module.set.offset(); |
|
17335 | $module |
|
17336 | .css({ |
|
17337 | left : module.cache.element.left, |
|
17338 | bottom : '', |
|
17339 | marginBottom : '' |
|
17340 | }) |
|
17341 | .removeClass(className.bound) |
|
17342 | .removeClass(className.top) |
|
17343 | .addClass(className.fixed) |
|
17344 | .addClass(className.bottom) |
|
17345 | ; |
|
17346 | settings.onStick.call(element); |
|
17347 | }, |
|
17348 | ||
17349 | unbind: function() { |
|
17350 | if( module.is.bound() ) { |
|
17351 | module.debug('Removing container bound position on element'); |
|
17352 | module.remove.offset(); |
|
17353 | $module |
|
17354 | .removeClass(className.bound) |
|
17355 | .removeClass(className.top) |
|
17356 | .removeClass(className.bottom) |
|
17357 | ; |
|
17358 | } |
|
17359 | }, |
|
17360 | ||
17361 | unfix: function() { |
|
17362 | if( module.is.fixed() ) { |
|
17363 | module.debug('Removing fixed position on element'); |
|
17364 | module.remove.minimumSize(); |
|
17365 | module.remove.offset(); |
|
17366 | $module |
|
17367 | .removeClass(className.fixed) |
|
17368 | .removeClass(className.top) |
|
17369 | .removeClass(className.bottom) |
|
17370 | ; |
|
17371 | settings.onUnstick.call(element); |
|
17372 | } |
|
17373 | }, |
|
17374 | ||
17375 | reset: function() { |
|
17376 | module.debug('Resetting elements position'); |
|
17377 | module.unbind(); |
|
17378 | module.unfix(); |
|
17379 | module.resetCSS(); |
|
17380 | module.remove.offset(); |
|
17381 | module.remove.lastScroll(); |
|
17382 | }, |
|
17383 | ||
17384 | resetCSS: function() { |
|
17385 | $module |
|
17386 | .css({ |
|
17387 | width : '', |
|
17388 | height : '' |
|
17389 | }) |
|
17390 | ; |
|
17391 | $container |
|
17392 | .css({ |
|
17393 | height: '' |
|
17394 | }) |
|
17395 | ; |
|
17396 | }, |
|
17397 | ||
17398 | setting: function(name, value) { |
|
17399 | if( $.isPlainObject(name) ) { |
|
17400 | $.extend(true, settings, name); |
|
17401 | } |
|
17402 | else if(value !== undefined) { |
|
17403 | settings[name] = value; |
|
17404 | } |
|
17405 | else { |
|
17406 | return settings[name]; |
|
17407 | } |
|
17408 | }, |
|
17409 | internal: function(name, value) { |
|
17410 | if( $.isPlainObject(name) ) { |
|
17411 | $.extend(true, module, name); |
|
17412 | } |
|
17413 | else if(value !== undefined) { |
|
17414 | module[name] = value; |
|
17415 | } |
|
17416 | else { |
|
17417 | return module[name]; |
|
17418 | } |
|
17419 | }, |
|
17420 | debug: function() { |
|
17421 | if(!settings.silent && settings.debug) { |
|
17422 | if(settings.performance) { |
|
17423 | module.performance.log(arguments); |
|
17424 | } |
|
17425 | else { |
|
17426 | module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
|
17427 | module.debug.apply(console, arguments); |
|
17428 | } |
|
17429 | } |
|
17430 | }, |
|
17431 | verbose: function() { |
|
17432 | if(!settings.silent && settings.verbose && settings.debug) { |
|
17433 | if(settings.performance) { |
|
17434 | module.performance.log(arguments); |
|
17435 | } |
|
17436 | else { |
|
17437 | module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
|
17438 | module.verbose.apply(console, arguments); |
|
17439 | } |
|
17440 | } |
|
17441 | }, |
|
17442 | error: function() { |
|
17443 | if(!settings.silent) { |
|
17444 | module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); |
|
17445 | module.error.apply(console, arguments); |
|
17446 | } |
|
17447 | }, |
|
17448 | performance: { |
|
17449 | log: function(message) { |
|
17450 | var |
|
17451 | currentTime, |
|
17452 | executionTime, |
|
17453 | previousTime |
|
17454 | ; |
|
17455 | if(settings.performance) { |
|
17456 | currentTime = new Date().getTime(); |
|
17457 | previousTime = time || currentTime; |
|
17458 | executionTime = currentTime - previousTime; |
|
17459 | time = currentTime; |
|
17460 | performance.push({ |
|
17461 | 'Name' : message[0], |
|
17462 | 'Arguments' : [].slice.call(message, 1) || '', |
|
17463 | 'Element' : element, |
|
17464 | 'Execution Time' : executionTime |
|
17465 | }); |
|
17466 | } |
|
17467 | clearTimeout(module.performance.timer); |
|
17468 | module.performance.timer = setTimeout(module.performance.display, 0); |
|
17469 | }, |
|
17470 | display: function() { |
|
17471 | var |
|
17472 | title = settings.name + ':', |
|
17473 | totalTime = 0 |
|
17474 | ; |
|
17475 | time = false; |
|
17476 | clearTimeout(module.performance.timer); |
|
17477 | $.each(performance, function(index, data) { |
|
17478 | totalTime += data['Execution Time']; |
|
17479 | }); |
|
17480 | title += ' ' + totalTime + 'ms'; |
|
17481 | if(moduleSelector) { |
|
17482 | title += ' \'' + moduleSelector + '\''; |
|
17483 | } |
|
17484 | if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { |
|
17485 | console.groupCollapsed(title); |
|
17486 | if(console.table) { |
|
17487 | console.table(performance); |
|
17488 | } |
|
17489 | else { |
|
17490 | $.each(performance, function(index, data) { |
|
17491 | console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); |
|
17492 | }); |
|
17493 | } |
|
17494 | console.groupEnd(); |
|
17495 | } |
|
17496 | performance = []; |
|
17497 | } |
|
17498 | }, |
|
17499 | invoke: function(query, passedArguments, context) { |
|
17500 | var |
|
17501 | object = instance, |
|
17502 | maxDepth, |
|
17503 | found, |
|
17504 | response |
|
17505 | ; |
|
17506 | passedArguments = passedArguments || queryArguments; |
|
17507 | context = element || context; |
|
17508 | if(typeof query == 'string' && object !== undefined) { |
|
17509 | query = query.split(/[\. ]/); |
|
17510 | maxDepth = query.length - 1; |
|
17511 | $.each(query, function(depth, value) { |
|
17512 | var camelCaseValue = (depth != maxDepth) |
|
17513 | ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) |
|
17514 | : query |
|
17515 | ; |
|
17516 | if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { |
|
17517 | object = object[camelCaseValue]; |
|
17518 | } |
|
17519 | else if( object[camelCaseValue] !== undefined ) { |
|
17520 | found = object[camelCaseValue]; |
|
17521 | return false; |
|
17522 | } |
|
17523 | else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { |
|
17524 | object = object[value]; |
|
17525 | } |
|
17526 | else if( object[value] !== undefined ) { |
|
17527 | found = object[value]; |
|
17528 | return false; |
|
17529 | } |
|
17530 | else { |
|
17531 | return false; |
|
17532 | } |
|
17533 | }); |
|
17534 | } |
|
17535 | if ( $.isFunction( found ) ) { |
|
17536 | response = found.apply(context, passedArguments); |
|
17537 | } |
|
17538 | else if(found !== undefined) { |
|
17539 | response = found; |
|
17540 | } |
|
17541 | if($.isArray(returnedValue)) { |
|
17542 | returnedValue.push(response); |
|
17543 | } |
|
17544 | else if(returnedValue !== undefined) { |
|
17545 | returnedValue = [returnedValue, response]; |
|
17546 | } |
|
17547 | else if(response !== undefined) { |
|
17548 | returnedValue = response; |
|
17549 | } |
|
17550 | return found; |
|
17551 | } |
|
17552 | }; |
|
17553 | ||
17554 | if(methodInvoked) { |
|
17555 | if(instance === undefined) { |
|
17556 | module.initialize(); |
|
17557 | } |
|
17558 | module.invoke(query); |
|
17559 | } |
|
17560 | else { |
|
17561 | if(instance !== undefined) { |
|
17562 | instance.invoke('destroy'); |
|
17563 | } |
|
17564 | module.initialize(); |
|
17565 | } |
|
17566 | }) |
|
17567 | ; |
|
17568 | ||
17569 | return (returnedValue !== undefined) |
|
17570 | ? returnedValue |
|
17571 | : this |
|
17572 | ; |
|
17573 | }; |
|
17574 | ||
17575 | $.fn.sticky.settings = { |
|
17576 | ||
17577 | name : 'Sticky', |
|
17578 | namespace : 'sticky', |
|
17579 | ||
17580 | silent : false, |
|
17581 | debug : false, |
|
17582 | verbose : true, |
|
17583 | performance : true, |
|
17584 | ||
17585 | // whether to stick in the opposite direction on scroll up |
|
17586 | pushing : false, |
|
17587 | ||
17588 | context : false, |
|
17589 | container : false, |
|
17590 | ||
17591 | // Context to watch scroll events |
|
17592 | scrollContext : window, |
|
17593 | ||
17594 | // Offset to adjust scroll |
|
17595 | offset : 0, |
|
17596 | ||
17597 | // Offset to adjust scroll when attached to bottom of screen |
|
17598 | bottomOffset : 0, |
|
17599 | ||
17600 | // will only set container height if difference between context and container is larger than this number |
|
17601 | jitter : 5, |
|
17602 | ||
17603 | // set width of sticky element when it is fixed to page (used to make sure 100% width is maintained if no fixed size set) |
|
17604 | setSize : true, |
|
17605 | ||
17606 | // Whether to automatically observe changes with Mutation Observers |
|
17607 | observeChanges : false, |
|
17608 | ||
17609 | // Called when position is recalculated |
|
17610 | onReposition : function(){}, |
|
17611 | ||
17612 | // Called on each scroll |
|
17613 | onScroll : function(){}, |
|
17614 | ||
17615 | // Called when element is stuck to viewport |
|
17616 | onStick : function(){}, |
|
17617 | ||
17618 | // Called when element is unstuck from viewport |
|
17619 | onUnstick : function(){}, |
|
17620 | ||
17621 | // Called when element reaches top of context |
|
17622 | onTop : function(){}, |
|
17623 | ||
17624 | // Called when element reaches bottom of context |
|
17625 | onBottom : function(){}, |
|
17626 | ||
17627 | error : { |
|
17628 | container : 'Sticky element must be inside a relative container', |
|
17629 | visible : 'Element is hidden, you must call refresh after element becomes visible. Use silent setting to surpress this warning in production.', |
|
17630 | method : 'The method you called is not defined.', |
|
17631 | invalidContext : 'Context specified does not exist', |
|
17632 | elementSize : 'Sticky element is larger than its container, cannot create sticky.' |
|
17633 | }, |
|
17634 | ||
17635 | className : { |
|
17636 | bound : 'bound', |
|
17637 | fixed : 'fixed', |
|
17638 | supported : 'native', |
|
17639 | top : 'top', |
|
17640 | bottom : 'bottom' |
|
17641 | } |
|
17642 | ||
17643 | }; |
|
17644 | ||
17645 | })( jQuery, window, document ); |
|
17646 | ||
17647 | /*! |
|
17648 | * # Semantic UI 2.2.11 - Tab |
@@ 11-959 (lines=949) @@ | ||
8 | * |
|
9 | */ |
|
10 | ||
11 | ;(function ($, window, document, undefined) { |
|
12 | ||
13 | "use strict"; |
|
14 | ||
15 | window = (typeof window != 'undefined' && window.Math == Math) |
|
16 | ? window |
|
17 | : (typeof self != 'undefined' && self.Math == Math) |
|
18 | ? self |
|
19 | : Function('return this')() |
|
20 | ; |
|
21 | ||
22 | $.fn.sticky = function(parameters) { |
|
23 | var |
|
24 | $allModules = $(this), |
|
25 | moduleSelector = $allModules.selector || '', |
|
26 | ||
27 | time = new Date().getTime(), |
|
28 | performance = [], |
|
29 | ||
30 | query = arguments[0], |
|
31 | methodInvoked = (typeof query == 'string'), |
|
32 | queryArguments = [].slice.call(arguments, 1), |
|
33 | returnedValue |
|
34 | ; |
|
35 | ||
36 | $allModules |
|
37 | .each(function() { |
|
38 | var |
|
39 | settings = ( $.isPlainObject(parameters) ) |
|
40 | ? $.extend(true, {}, $.fn.sticky.settings, parameters) |
|
41 | : $.extend({}, $.fn.sticky.settings), |
|
42 | ||
43 | className = settings.className, |
|
44 | namespace = settings.namespace, |
|
45 | error = settings.error, |
|
46 | ||
47 | eventNamespace = '.' + namespace, |
|
48 | moduleNamespace = 'module-' + namespace, |
|
49 | ||
50 | $module = $(this), |
|
51 | $window = $(window), |
|
52 | $scroll = $(settings.scrollContext), |
|
53 | $container, |
|
54 | $context, |
|
55 | ||
56 | selector = $module.selector || '', |
|
57 | instance = $module.data(moduleNamespace), |
|
58 | ||
59 | requestAnimationFrame = window.requestAnimationFrame |
|
60 | || window.mozRequestAnimationFrame |
|
61 | || window.webkitRequestAnimationFrame |
|
62 | || window.msRequestAnimationFrame |
|
63 | || function(callback) { setTimeout(callback, 0); }, |
|
64 | ||
65 | element = this, |
|
66 | ||
67 | documentObserver, |
|
68 | observer, |
|
69 | module |
|
70 | ; |
|
71 | ||
72 | module = { |
|
73 | ||
74 | initialize: function() { |
|
75 | ||
76 | module.determineContainer(); |
|
77 | module.determineContext(); |
|
78 | module.verbose('Initializing sticky', settings, $container); |
|
79 | ||
80 | module.save.positions(); |
|
81 | module.checkErrors(); |
|
82 | module.bind.events(); |
|
83 | ||
84 | if(settings.observeChanges) { |
|
85 | module.observeChanges(); |
|
86 | } |
|
87 | module.instantiate(); |
|
88 | }, |
|
89 | ||
90 | instantiate: function() { |
|
91 | module.verbose('Storing instance of module', module); |
|
92 | instance = module; |
|
93 | $module |
|
94 | .data(moduleNamespace, module) |
|
95 | ; |
|
96 | }, |
|
97 | ||
98 | destroy: function() { |
|
99 | module.verbose('Destroying previous instance'); |
|
100 | module.reset(); |
|
101 | if(documentObserver) { |
|
102 | documentObserver.disconnect(); |
|
103 | } |
|
104 | if(observer) { |
|
105 | observer.disconnect(); |
|
106 | } |
|
107 | $window |
|
108 | .off('load' + eventNamespace, module.event.load) |
|
109 | .off('resize' + eventNamespace, module.event.resize) |
|
110 | ; |
|
111 | $scroll |
|
112 | .off('scrollchange' + eventNamespace, module.event.scrollchange) |
|
113 | ; |
|
114 | $module.removeData(moduleNamespace); |
|
115 | }, |
|
116 | ||
117 | observeChanges: function() { |
|
118 | if('MutationObserver' in window) { |
|
119 | documentObserver = new MutationObserver(module.event.documentChanged); |
|
120 | observer = new MutationObserver(module.event.changed); |
|
121 | documentObserver.observe(document, { |
|
122 | childList : true, |
|
123 | subtree : true |
|
124 | }); |
|
125 | observer.observe(element, { |
|
126 | childList : true, |
|
127 | subtree : true |
|
128 | }); |
|
129 | observer.observe($context[0], { |
|
130 | childList : true, |
|
131 | subtree : true |
|
132 | }); |
|
133 | module.debug('Setting up mutation observer', observer); |
|
134 | } |
|
135 | }, |
|
136 | ||
137 | determineContainer: function() { |
|
138 | if(settings.container) { |
|
139 | $container = $(settings.container); |
|
140 | } |
|
141 | else { |
|
142 | $container = $module.offsetParent(); |
|
143 | } |
|
144 | }, |
|
145 | ||
146 | determineContext: function() { |
|
147 | if(settings.context) { |
|
148 | $context = $(settings.context); |
|
149 | } |
|
150 | else { |
|
151 | $context = $container; |
|
152 | } |
|
153 | if($context.length === 0) { |
|
154 | module.error(error.invalidContext, settings.context, $module); |
|
155 | return; |
|
156 | } |
|
157 | }, |
|
158 | ||
159 | checkErrors: function() { |
|
160 | if( module.is.hidden() ) { |
|
161 | module.error(error.visible, $module); |
|
162 | } |
|
163 | if(module.cache.element.height > module.cache.context.height) { |
|
164 | module.reset(); |
|
165 | module.error(error.elementSize, $module); |
|
166 | return; |
|
167 | } |
|
168 | }, |
|
169 | ||
170 | bind: { |
|
171 | events: function() { |
|
172 | $window |
|
173 | .on('load' + eventNamespace, module.event.load) |
|
174 | .on('resize' + eventNamespace, module.event.resize) |
|
175 | ; |
|
176 | // pub/sub pattern |
|
177 | $scroll |
|
178 | .off('scroll' + eventNamespace) |
|
179 | .on('scroll' + eventNamespace, module.event.scroll) |
|
180 | .on('scrollchange' + eventNamespace, module.event.scrollchange) |
|
181 | ; |
|
182 | } |
|
183 | }, |
|
184 | ||
185 | event: { |
|
186 | changed: function(mutations) { |
|
187 | clearTimeout(module.timer); |
|
188 | module.timer = setTimeout(function() { |
|
189 | module.verbose('DOM tree modified, updating sticky menu', mutations); |
|
190 | module.refresh(); |
|
191 | }, 100); |
|
192 | }, |
|
193 | documentChanged: function(mutations) { |
|
194 | [].forEach.call(mutations, function(mutation) { |
|
195 | if(mutation.removedNodes) { |
|
196 | [].forEach.call(mutation.removedNodes, function(node) { |
|
197 | if(node == element || $(node).find(element).length > 0) { |
|
198 | module.debug('Element removed from DOM, tearing down events'); |
|
199 | module.destroy(); |
|
200 | } |
|
201 | }); |
|
202 | } |
|
203 | }); |
|
204 | }, |
|
205 | load: function() { |
|
206 | module.verbose('Page contents finished loading'); |
|
207 | requestAnimationFrame(module.refresh); |
|
208 | }, |
|
209 | resize: function() { |
|
210 | module.verbose('Window resized'); |
|
211 | requestAnimationFrame(module.refresh); |
|
212 | }, |
|
213 | scroll: function() { |
|
214 | requestAnimationFrame(function() { |
|
215 | $scroll.triggerHandler('scrollchange' + eventNamespace, $scroll.scrollTop() ); |
|
216 | }); |
|
217 | }, |
|
218 | scrollchange: function(event, scrollPosition) { |
|
219 | module.stick(scrollPosition); |
|
220 | settings.onScroll.call(element); |
|
221 | } |
|
222 | }, |
|
223 | ||
224 | refresh: function(hardRefresh) { |
|
225 | module.reset(); |
|
226 | if(!settings.context) { |
|
227 | module.determineContext(); |
|
228 | } |
|
229 | if(hardRefresh) { |
|
230 | module.determineContainer(); |
|
231 | } |
|
232 | module.save.positions(); |
|
233 | module.stick(); |
|
234 | settings.onReposition.call(element); |
|
235 | }, |
|
236 | ||
237 | supports: { |
|
238 | sticky: function() { |
|
239 | var |
|
240 | $element = $('<div/>'), |
|
241 | element = $element[0] |
|
242 | ; |
|
243 | $element.addClass(className.supported); |
|
244 | return($element.css('position').match('sticky')); |
|
245 | } |
|
246 | }, |
|
247 | ||
248 | save: { |
|
249 | lastScroll: function(scroll) { |
|
250 | module.lastScroll = scroll; |
|
251 | }, |
|
252 | elementScroll: function(scroll) { |
|
253 | module.elementScroll = scroll; |
|
254 | }, |
|
255 | positions: function() { |
|
256 | var |
|
257 | scrollContext = { |
|
258 | height : $scroll.height() |
|
259 | }, |
|
260 | element = { |
|
261 | margin: { |
|
262 | top : parseInt($module.css('margin-top'), 10), |
|
263 | bottom : parseInt($module.css('margin-bottom'), 10), |
|
264 | }, |
|
265 | offset : $module.offset(), |
|
266 | width : $module.outerWidth(), |
|
267 | height : $module.outerHeight() |
|
268 | }, |
|
269 | context = { |
|
270 | offset : $context.offset(), |
|
271 | height : $context.outerHeight() |
|
272 | }, |
|
273 | container = { |
|
274 | height: $container.outerHeight() |
|
275 | } |
|
276 | ; |
|
277 | if( !module.is.standardScroll() ) { |
|
278 | module.debug('Non-standard scroll. Removing scroll offset from element offset'); |
|
279 | ||
280 | scrollContext.top = $scroll.scrollTop(); |
|
281 | scrollContext.left = $scroll.scrollLeft(); |
|
282 | ||
283 | element.offset.top += scrollContext.top; |
|
284 | context.offset.top += scrollContext.top; |
|
285 | element.offset.left += scrollContext.left; |
|
286 | context.offset.left += scrollContext.left; |
|
287 | } |
|
288 | module.cache = { |
|
289 | fits : ( (element.height + settings.offset) <= scrollContext.height), |
|
290 | sameHeight : (element.height == context.height), |
|
291 | scrollContext : { |
|
292 | height : scrollContext.height |
|
293 | }, |
|
294 | element: { |
|
295 | margin : element.margin, |
|
296 | top : element.offset.top - element.margin.top, |
|
297 | left : element.offset.left, |
|
298 | width : element.width, |
|
299 | height : element.height, |
|
300 | bottom : element.offset.top + element.height |
|
301 | }, |
|
302 | context: { |
|
303 | top : context.offset.top, |
|
304 | height : context.height, |
|
305 | bottom : context.offset.top + context.height |
|
306 | } |
|
307 | }; |
|
308 | module.set.containerSize(); |
|
309 | ||
310 | module.stick(); |
|
311 | module.debug('Caching element positions', module.cache); |
|
312 | } |
|
313 | }, |
|
314 | ||
315 | get: { |
|
316 | direction: function(scroll) { |
|
317 | var |
|
318 | direction = 'down' |
|
319 | ; |
|
320 | scroll = scroll || $scroll.scrollTop(); |
|
321 | if(module.lastScroll !== undefined) { |
|
322 | if(module.lastScroll < scroll) { |
|
323 | direction = 'down'; |
|
324 | } |
|
325 | else if(module.lastScroll > scroll) { |
|
326 | direction = 'up'; |
|
327 | } |
|
328 | } |
|
329 | return direction; |
|
330 | }, |
|
331 | scrollChange: function(scroll) { |
|
332 | scroll = scroll || $scroll.scrollTop(); |
|
333 | return (module.lastScroll) |
|
334 | ? (scroll - module.lastScroll) |
|
335 | : 0 |
|
336 | ; |
|
337 | }, |
|
338 | currentElementScroll: function() { |
|
339 | if(module.elementScroll) { |
|
340 | return module.elementScroll; |
|
341 | } |
|
342 | return ( module.is.top() ) |
|
343 | ? Math.abs(parseInt($module.css('top'), 10)) || 0 |
|
344 | : Math.abs(parseInt($module.css('bottom'), 10)) || 0 |
|
345 | ; |
|
346 | }, |
|
347 | ||
348 | elementScroll: function(scroll) { |
|
349 | scroll = scroll || $scroll.scrollTop(); |
|
350 | var |
|
351 | element = module.cache.element, |
|
352 | scrollContext = module.cache.scrollContext, |
|
353 | delta = module.get.scrollChange(scroll), |
|
354 | maxScroll = (element.height - scrollContext.height + settings.offset), |
|
355 | elementScroll = module.get.currentElementScroll(), |
|
356 | possibleScroll = (elementScroll + delta) |
|
357 | ; |
|
358 | if(module.cache.fits || possibleScroll < 0) { |
|
359 | elementScroll = 0; |
|
360 | } |
|
361 | else if(possibleScroll > maxScroll ) { |
|
362 | elementScroll = maxScroll; |
|
363 | } |
|
364 | else { |
|
365 | elementScroll = possibleScroll; |
|
366 | } |
|
367 | return elementScroll; |
|
368 | } |
|
369 | }, |
|
370 | ||
371 | remove: { |
|
372 | lastScroll: function() { |
|
373 | delete module.lastScroll; |
|
374 | }, |
|
375 | elementScroll: function(scroll) { |
|
376 | delete module.elementScroll; |
|
377 | }, |
|
378 | minimumSize: function() { |
|
379 | $container |
|
380 | .css('min-height', '') |
|
381 | ; |
|
382 | }, |
|
383 | offset: function() { |
|
384 | $module.css('margin-top', ''); |
|
385 | } |
|
386 | }, |
|
387 | ||
388 | set: { |
|
389 | offset: function() { |
|
390 | module.verbose('Setting offset on element', settings.offset); |
|
391 | $module |
|
392 | .css('margin-top', settings.offset) |
|
393 | ; |
|
394 | }, |
|
395 | containerSize: function() { |
|
396 | var |
|
397 | tagName = $container.get(0).tagName |
|
398 | ; |
|
399 | if(tagName === 'HTML' || tagName == 'body') { |
|
400 | // this can trigger for too many reasons |
|
401 | //module.error(error.container, tagName, $module); |
|
402 | module.determineContainer(); |
|
403 | } |
|
404 | else { |
|
405 | if( Math.abs($container.outerHeight() - module.cache.context.height) > settings.jitter) { |
|
406 | module.debug('Context has padding, specifying exact height for container', module.cache.context.height); |
|
407 | $container.css({ |
|
408 | height: module.cache.context.height |
|
409 | }); |
|
410 | } |
|
411 | } |
|
412 | }, |
|
413 | minimumSize: function() { |
|
414 | var |
|
415 | element = module.cache.element |
|
416 | ; |
|
417 | $container |
|
418 | .css('min-height', element.height) |
|
419 | ; |
|
420 | }, |
|
421 | scroll: function(scroll) { |
|
422 | module.debug('Setting scroll on element', scroll); |
|
423 | if(module.elementScroll == scroll) { |
|
424 | return; |
|
425 | } |
|
426 | if( module.is.top() ) { |
|
427 | $module |
|
428 | .css('bottom', '') |
|
429 | .css('top', -scroll) |
|
430 | ; |
|
431 | } |
|
432 | if( module.is.bottom() ) { |
|
433 | $module |
|
434 | .css('top', '') |
|
435 | .css('bottom', scroll) |
|
436 | ; |
|
437 | } |
|
438 | }, |
|
439 | size: function() { |
|
440 | if(module.cache.element.height !== 0 && module.cache.element.width !== 0) { |
|
441 | element.style.setProperty('width', module.cache.element.width + 'px', 'important'); |
|
442 | element.style.setProperty('height', module.cache.element.height + 'px', 'important'); |
|
443 | } |
|
444 | } |
|
445 | }, |
|
446 | ||
447 | is: { |
|
448 | standardScroll: function() { |
|
449 | return ($scroll[0] == window); |
|
450 | }, |
|
451 | top: function() { |
|
452 | return $module.hasClass(className.top); |
|
453 | }, |
|
454 | bottom: function() { |
|
455 | return $module.hasClass(className.bottom); |
|
456 | }, |
|
457 | initialPosition: function() { |
|
458 | return (!module.is.fixed() && !module.is.bound()); |
|
459 | }, |
|
460 | hidden: function() { |
|
461 | return (!$module.is(':visible')); |
|
462 | }, |
|
463 | bound: function() { |
|
464 | return $module.hasClass(className.bound); |
|
465 | }, |
|
466 | fixed: function() { |
|
467 | return $module.hasClass(className.fixed); |
|
468 | } |
|
469 | }, |
|
470 | ||
471 | stick: function(scroll) { |
|
472 | var |
|
473 | cachedPosition = scroll || $scroll.scrollTop(), |
|
474 | cache = module.cache, |
|
475 | fits = cache.fits, |
|
476 | sameHeight = cache.sameHeight, |
|
477 | element = cache.element, |
|
478 | scrollContext = cache.scrollContext, |
|
479 | context = cache.context, |
|
480 | offset = (module.is.bottom() && settings.pushing) |
|
481 | ? settings.bottomOffset |
|
482 | : settings.offset, |
|
483 | scroll = { |
|
484 | top : cachedPosition + offset, |
|
485 | bottom : cachedPosition + offset + scrollContext.height |
|
486 | }, |
|
487 | direction = module.get.direction(scroll.top), |
|
488 | elementScroll = (fits) |
|
489 | ? 0 |
|
490 | : module.get.elementScroll(scroll.top), |
|
491 | ||
492 | // shorthand |
|
493 | doesntFit = !fits, |
|
494 | elementVisible = (element.height !== 0) |
|
495 | ; |
|
496 | if(elementVisible && !sameHeight) { |
|
497 | ||
498 | if( module.is.initialPosition() ) { |
|
499 | if(scroll.top >= context.bottom) { |
|
500 | module.debug('Initial element position is bottom of container'); |
|
501 | module.bindBottom(); |
|
502 | } |
|
503 | else if(scroll.top > element.top) { |
|
504 | if( (element.height + scroll.top - elementScroll) >= context.bottom ) { |
|
505 | module.debug('Initial element position is bottom of container'); |
|
506 | module.bindBottom(); |
|
507 | } |
|
508 | else { |
|
509 | module.debug('Initial element position is fixed'); |
|
510 | module.fixTop(); |
|
511 | } |
|
512 | } |
|
513 | ||
514 | } |
|
515 | else if( module.is.fixed() ) { |
|
516 | ||
517 | // currently fixed top |
|
518 | if( module.is.top() ) { |
|
519 | if( scroll.top <= element.top ) { |
|
520 | module.debug('Fixed element reached top of container'); |
|
521 | module.setInitialPosition(); |
|
522 | } |
|
523 | else if( (element.height + scroll.top - elementScroll) >= context.bottom ) { |
|
524 | module.debug('Fixed element reached bottom of container'); |
|
525 | module.bindBottom(); |
|
526 | } |
|
527 | // scroll element if larger than screen |
|
528 | else if(doesntFit) { |
|
529 | module.set.scroll(elementScroll); |
|
530 | module.save.lastScroll(scroll.top); |
|
531 | module.save.elementScroll(elementScroll); |
|
532 | } |
|
533 | } |
|
534 | ||
535 | // currently fixed bottom |
|
536 | else if(module.is.bottom() ) { |
|
537 | ||
538 | // top edge |
|
539 | if( (scroll.bottom - element.height) <= element.top) { |
|
540 | module.debug('Bottom fixed rail has reached top of container'); |
|
541 | module.setInitialPosition(); |
|
542 | } |
|
543 | // bottom edge |
|
544 | else if(scroll.bottom >= context.bottom) { |
|
545 | module.debug('Bottom fixed rail has reached bottom of container'); |
|
546 | module.bindBottom(); |
|
547 | } |
|
548 | // scroll element if larger than screen |
|
549 | else if(doesntFit) { |
|
550 | module.set.scroll(elementScroll); |
|
551 | module.save.lastScroll(scroll.top); |
|
552 | module.save.elementScroll(elementScroll); |
|
553 | } |
|
554 | ||
555 | } |
|
556 | } |
|
557 | else if( module.is.bottom() ) { |
|
558 | if( scroll.top <= element.top ) { |
|
559 | module.debug('Jumped from bottom fixed to top fixed, most likely used home/end button'); |
|
560 | module.setInitialPosition(); |
|
561 | } |
|
562 | else { |
|
563 | if(settings.pushing) { |
|
564 | if(module.is.bound() && scroll.bottom <= context.bottom ) { |
|
565 | module.debug('Fixing bottom attached element to bottom of browser.'); |
|
566 | module.fixBottom(); |
|
567 | } |
|
568 | } |
|
569 | else { |
|
570 | if(module.is.bound() && (scroll.top <= context.bottom - element.height) ) { |
|
571 | module.debug('Fixing bottom attached element to top of browser.'); |
|
572 | module.fixTop(); |
|
573 | } |
|
574 | } |
|
575 | } |
|
576 | } |
|
577 | } |
|
578 | }, |
|
579 | ||
580 | bindTop: function() { |
|
581 | module.debug('Binding element to top of parent container'); |
|
582 | module.remove.offset(); |
|
583 | $module |
|
584 | .css({ |
|
585 | left : '', |
|
586 | top : '', |
|
587 | marginBottom : '' |
|
588 | }) |
|
589 | .removeClass(className.fixed) |
|
590 | .removeClass(className.bottom) |
|
591 | .addClass(className.bound) |
|
592 | .addClass(className.top) |
|
593 | ; |
|
594 | settings.onTop.call(element); |
|
595 | settings.onUnstick.call(element); |
|
596 | }, |
|
597 | bindBottom: function() { |
|
598 | module.debug('Binding element to bottom of parent container'); |
|
599 | module.remove.offset(); |
|
600 | $module |
|
601 | .css({ |
|
602 | left : '', |
|
603 | top : '' |
|
604 | }) |
|
605 | .removeClass(className.fixed) |
|
606 | .removeClass(className.top) |
|
607 | .addClass(className.bound) |
|
608 | .addClass(className.bottom) |
|
609 | ; |
|
610 | settings.onBottom.call(element); |
|
611 | settings.onUnstick.call(element); |
|
612 | }, |
|
613 | ||
614 | setInitialPosition: function() { |
|
615 | module.debug('Returning to initial position'); |
|
616 | module.unfix(); |
|
617 | module.unbind(); |
|
618 | }, |
|
619 | ||
620 | ||
621 | fixTop: function() { |
|
622 | module.debug('Fixing element to top of page'); |
|
623 | if(settings.setSize) { |
|
624 | module.set.size(); |
|
625 | } |
|
626 | module.set.minimumSize(); |
|
627 | module.set.offset(); |
|
628 | $module |
|
629 | .css({ |
|
630 | left : module.cache.element.left, |
|
631 | bottom : '', |
|
632 | marginBottom : '' |
|
633 | }) |
|
634 | .removeClass(className.bound) |
|
635 | .removeClass(className.bottom) |
|
636 | .addClass(className.fixed) |
|
637 | .addClass(className.top) |
|
638 | ; |
|
639 | settings.onStick.call(element); |
|
640 | }, |
|
641 | ||
642 | fixBottom: function() { |
|
643 | module.debug('Sticking element to bottom of page'); |
|
644 | if(settings.setSize) { |
|
645 | module.set.size(); |
|
646 | } |
|
647 | module.set.minimumSize(); |
|
648 | module.set.offset(); |
|
649 | $module |
|
650 | .css({ |
|
651 | left : module.cache.element.left, |
|
652 | bottom : '', |
|
653 | marginBottom : '' |
|
654 | }) |
|
655 | .removeClass(className.bound) |
|
656 | .removeClass(className.top) |
|
657 | .addClass(className.fixed) |
|
658 | .addClass(className.bottom) |
|
659 | ; |
|
660 | settings.onStick.call(element); |
|
661 | }, |
|
662 | ||
663 | unbind: function() { |
|
664 | if( module.is.bound() ) { |
|
665 | module.debug('Removing container bound position on element'); |
|
666 | module.remove.offset(); |
|
667 | $module |
|
668 | .removeClass(className.bound) |
|
669 | .removeClass(className.top) |
|
670 | .removeClass(className.bottom) |
|
671 | ; |
|
672 | } |
|
673 | }, |
|
674 | ||
675 | unfix: function() { |
|
676 | if( module.is.fixed() ) { |
|
677 | module.debug('Removing fixed position on element'); |
|
678 | module.remove.minimumSize(); |
|
679 | module.remove.offset(); |
|
680 | $module |
|
681 | .removeClass(className.fixed) |
|
682 | .removeClass(className.top) |
|
683 | .removeClass(className.bottom) |
|
684 | ; |
|
685 | settings.onUnstick.call(element); |
|
686 | } |
|
687 | }, |
|
688 | ||
689 | reset: function() { |
|
690 | module.debug('Resetting elements position'); |
|
691 | module.unbind(); |
|
692 | module.unfix(); |
|
693 | module.resetCSS(); |
|
694 | module.remove.offset(); |
|
695 | module.remove.lastScroll(); |
|
696 | }, |
|
697 | ||
698 | resetCSS: function() { |
|
699 | $module |
|
700 | .css({ |
|
701 | width : '', |
|
702 | height : '' |
|
703 | }) |
|
704 | ; |
|
705 | $container |
|
706 | .css({ |
|
707 | height: '' |
|
708 | }) |
|
709 | ; |
|
710 | }, |
|
711 | ||
712 | setting: function(name, value) { |
|
713 | if( $.isPlainObject(name) ) { |
|
714 | $.extend(true, settings, name); |
|
715 | } |
|
716 | else if(value !== undefined) { |
|
717 | settings[name] = value; |
|
718 | } |
|
719 | else { |
|
720 | return settings[name]; |
|
721 | } |
|
722 | }, |
|
723 | internal: function(name, value) { |
|
724 | if( $.isPlainObject(name) ) { |
|
725 | $.extend(true, module, name); |
|
726 | } |
|
727 | else if(value !== undefined) { |
|
728 | module[name] = value; |
|
729 | } |
|
730 | else { |
|
731 | return module[name]; |
|
732 | } |
|
733 | }, |
|
734 | debug: function() { |
|
735 | if(!settings.silent && settings.debug) { |
|
736 | if(settings.performance) { |
|
737 | module.performance.log(arguments); |
|
738 | } |
|
739 | else { |
|
740 | module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
|
741 | module.debug.apply(console, arguments); |
|
742 | } |
|
743 | } |
|
744 | }, |
|
745 | verbose: function() { |
|
746 | if(!settings.silent && settings.verbose && settings.debug) { |
|
747 | if(settings.performance) { |
|
748 | module.performance.log(arguments); |
|
749 | } |
|
750 | else { |
|
751 | module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
|
752 | module.verbose.apply(console, arguments); |
|
753 | } |
|
754 | } |
|
755 | }, |
|
756 | error: function() { |
|
757 | if(!settings.silent) { |
|
758 | module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); |
|
759 | module.error.apply(console, arguments); |
|
760 | } |
|
761 | }, |
|
762 | performance: { |
|
763 | log: function(message) { |
|
764 | var |
|
765 | currentTime, |
|
766 | executionTime, |
|
767 | previousTime |
|
768 | ; |
|
769 | if(settings.performance) { |
|
770 | currentTime = new Date().getTime(); |
|
771 | previousTime = time || currentTime; |
|
772 | executionTime = currentTime - previousTime; |
|
773 | time = currentTime; |
|
774 | performance.push({ |
|
775 | 'Name' : message[0], |
|
776 | 'Arguments' : [].slice.call(message, 1) || '', |
|
777 | 'Element' : element, |
|
778 | 'Execution Time' : executionTime |
|
779 | }); |
|
780 | } |
|
781 | clearTimeout(module.performance.timer); |
|
782 | module.performance.timer = setTimeout(module.performance.display, 0); |
|
783 | }, |
|
784 | display: function() { |
|
785 | var |
|
786 | title = settings.name + ':', |
|
787 | totalTime = 0 |
|
788 | ; |
|
789 | time = false; |
|
790 | clearTimeout(module.performance.timer); |
|
791 | $.each(performance, function(index, data) { |
|
792 | totalTime += data['Execution Time']; |
|
793 | }); |
|
794 | title += ' ' + totalTime + 'ms'; |
|
795 | if(moduleSelector) { |
|
796 | title += ' \'' + moduleSelector + '\''; |
|
797 | } |
|
798 | if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { |
|
799 | console.groupCollapsed(title); |
|
800 | if(console.table) { |
|
801 | console.table(performance); |
|
802 | } |
|
803 | else { |
|
804 | $.each(performance, function(index, data) { |
|
805 | console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); |
|
806 | }); |
|
807 | } |
|
808 | console.groupEnd(); |
|
809 | } |
|
810 | performance = []; |
|
811 | } |
|
812 | }, |
|
813 | invoke: function(query, passedArguments, context) { |
|
814 | var |
|
815 | object = instance, |
|
816 | maxDepth, |
|
817 | found, |
|
818 | response |
|
819 | ; |
|
820 | passedArguments = passedArguments || queryArguments; |
|
821 | context = element || context; |
|
822 | if(typeof query == 'string' && object !== undefined) { |
|
823 | query = query.split(/[\. ]/); |
|
824 | maxDepth = query.length - 1; |
|
825 | $.each(query, function(depth, value) { |
|
826 | var camelCaseValue = (depth != maxDepth) |
|
827 | ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) |
|
828 | : query |
|
829 | ; |
|
830 | if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { |
|
831 | object = object[camelCaseValue]; |
|
832 | } |
|
833 | else if( object[camelCaseValue] !== undefined ) { |
|
834 | found = object[camelCaseValue]; |
|
835 | return false; |
|
836 | } |
|
837 | else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { |
|
838 | object = object[value]; |
|
839 | } |
|
840 | else if( object[value] !== undefined ) { |
|
841 | found = object[value]; |
|
842 | return false; |
|
843 | } |
|
844 | else { |
|
845 | return false; |
|
846 | } |
|
847 | }); |
|
848 | } |
|
849 | if ( $.isFunction( found ) ) { |
|
850 | response = found.apply(context, passedArguments); |
|
851 | } |
|
852 | else if(found !== undefined) { |
|
853 | response = found; |
|
854 | } |
|
855 | if($.isArray(returnedValue)) { |
|
856 | returnedValue.push(response); |
|
857 | } |
|
858 | else if(returnedValue !== undefined) { |
|
859 | returnedValue = [returnedValue, response]; |
|
860 | } |
|
861 | else if(response !== undefined) { |
|
862 | returnedValue = response; |
|
863 | } |
|
864 | return found; |
|
865 | } |
|
866 | }; |
|
867 | ||
868 | if(methodInvoked) { |
|
869 | if(instance === undefined) { |
|
870 | module.initialize(); |
|
871 | } |
|
872 | module.invoke(query); |
|
873 | } |
|
874 | else { |
|
875 | if(instance !== undefined) { |
|
876 | instance.invoke('destroy'); |
|
877 | } |
|
878 | module.initialize(); |
|
879 | } |
|
880 | }) |
|
881 | ; |
|
882 | ||
883 | return (returnedValue !== undefined) |
|
884 | ? returnedValue |
|
885 | : this |
|
886 | ; |
|
887 | }; |
|
888 | ||
889 | $.fn.sticky.settings = { |
|
890 | ||
891 | name : 'Sticky', |
|
892 | namespace : 'sticky', |
|
893 | ||
894 | silent : false, |
|
895 | debug : false, |
|
896 | verbose : true, |
|
897 | performance : true, |
|
898 | ||
899 | // whether to stick in the opposite direction on scroll up |
|
900 | pushing : false, |
|
901 | ||
902 | context : false, |
|
903 | container : false, |
|
904 | ||
905 | // Context to watch scroll events |
|
906 | scrollContext : window, |
|
907 | ||
908 | // Offset to adjust scroll |
|
909 | offset : 0, |
|
910 | ||
911 | // Offset to adjust scroll when attached to bottom of screen |
|
912 | bottomOffset : 0, |
|
913 | ||
914 | // will only set container height if difference between context and container is larger than this number |
|
915 | jitter : 5, |
|
916 | ||
917 | // set width of sticky element when it is fixed to page (used to make sure 100% width is maintained if no fixed size set) |
|
918 | setSize : true, |
|
919 | ||
920 | // Whether to automatically observe changes with Mutation Observers |
|
921 | observeChanges : false, |
|
922 | ||
923 | // Called when position is recalculated |
|
924 | onReposition : function(){}, |
|
925 | ||
926 | // Called on each scroll |
|
927 | onScroll : function(){}, |
|
928 | ||
929 | // Called when element is stuck to viewport |
|
930 | onStick : function(){}, |
|
931 | ||
932 | // Called when element is unstuck from viewport |
|
933 | onUnstick : function(){}, |
|
934 | ||
935 | // Called when element reaches top of context |
|
936 | onTop : function(){}, |
|
937 | ||
938 | // Called when element reaches bottom of context |
|
939 | onBottom : function(){}, |
|
940 | ||
941 | error : { |
|
942 | container : 'Sticky element must be inside a relative container', |
|
943 | visible : 'Element is hidden, you must call refresh after element becomes visible. Use silent setting to surpress this warning in production.', |
|
944 | method : 'The method you called is not defined.', |
|
945 | invalidContext : 'Context specified does not exist', |
|
946 | elementSize : 'Sticky element is larger than its container, cannot create sticky.' |
|
947 | }, |
|
948 | ||
949 | className : { |
|
950 | bound : 'bound', |
|
951 | fixed : 'fixed', |
|
952 | supported : 'native', |
|
953 | top : 'top', |
|
954 | bottom : 'bottom' |
|
955 | } |
|
956 | ||
957 | }; |
|
958 | ||
959 | })( jQuery, window, document ); |
|
960 |