1(function($){ 2 3 $.fn.theiaStickySidebar = function (options) { 4 var defaults = { 5 'containerSelector': '', 6 'additionalMarginTop': 0, 7 'additionalMarginBottom': 0, 8 'updateSidebarHeight': true, 9 'minWidth': 0, 10 'sidebarBehavior': 'modern' 11 }; 12 options = $.extend(defaults, options); 13 14 // Validate options 15 options.additionalMarginTop = parseInt(options.additionalMarginTop) || 0; 16 options.additionalMarginBottom = parseInt(options.additionalMarginBottom) || 0; 17 18 tryInitOrHookIntoEvents(options, this); 19 20 // Try doing init, otherwise hook into window.resize and document.scroll and try again then. 21 function tryInitOrHookIntoEvents(options, $that) { 22 var success = tryInit(options, $that); 23 24 if (!success) { 25 $(document).scroll(function (options, $that) { 26 return function (evt) { 27 var success = tryInit(options, $that); 28 29 if (success) { 30 $(this).unbind(evt); 31 } 32 }; 33 }(options, $that)); 34 $(window).resize(function (options, $that) { 35 return function (evt) { 36 var success = tryInit(options, $that); 37 38 if (success) { 39 $(this).unbind(evt); 40 } 41 }; 42 }(options, $that)) 43 } 44 } 45 46 // Try doing init if proper conditions are met. 47 function tryInit(options, $that) { 48 if (options.initialized === true) { 49 return true; 50 } 51 52 if ($('body').width() < options.minWidth) { 53 return false; 54 } 55 56 init(options, $that); 57 58 return true; 59 } 60 61 // Init the sticky sidebar(s). 62 function init(options, $that) { 63 options.initialized = true; 64 65 // Add CSS 66 $('head').append($('<style>.theiaStickySidebar:after {content: ""; display: table; clear: both;}</style>')); 67 68 $that.each(function () { 69 var o = {}; 70 71 o.sidebar = $(this); 72 73 // Save options 74 o.options = options || {}; 75 76 // Get container 77 o.container = $(o.options.containerSelector); 78 if (o.container.size() == 0) { 79 o.container = o.sidebar.parent(); 80 } 81 82 // Create sticky sidebar 83 o.sidebar.parents().css('-webkit-transform', 'none'); // Fix for WebKit bug - https://code.google.com/p/chromium/issues/detail?id=20574 84 o.sidebar.css({ 85 'position': 'relative', 86 'overflow': 'visible', 87 // The "box-sizing" must be set to "content-box" because we set a fixed height to this element when the sticky
87sidebar has a fixed position. 88 '-webkit-box-sizing': 'border-box', 89 '-moz-box-sizing': 'border-box', 90 'box-sizing': 'border-box' 91 }); 92 93 // Get the sticky sidebar element. If none has been found, then create one. 94 o.stickySidebar = o.sidebar.find('.theiaStickySidebar'); 95 if (o.stickySidebar.length == 0) { 96 o.sidebar.find('script').remove(); // Remove <script> tags, otherwise they will be run again on the next line. 97 o.stickySidebar = $('<div>').addClass('theiaStickySidebar').append(o.sidebar.children()); 98 o.sidebar.append(o.stickySidebar); 99 } 100 101 // Get existing top and bottom margins and paddings 102 o.marginTop = parseInt(o.sidebar.css('margin-top')); 103 o.marginBottom = parseInt(o.sidebar.css('margin-bottom')); 104 o.paddingTop = parseInt(o.sidebar.css('padding-top')); 105 o.paddingBottom = parseInt(o.sidebar.css('padding-bottom')); 106 107 // Add a temporary padding rule to check for collapsable margins. 108 var collapsedTopHeight = o.stickySidebar.offset().top; 109 var collapsedBottomHeight = o.stickySidebar.outerHeight(); 110 o.stickySidebar.css('padding-top', 1); 111 o.stickySidebar.css('padding-bottom', 1); 112 collapsedTopHeight -= o.stickySidebar.offset().top; 113 collapsedBottomHeight = o.stickySidebar.outerHeight() - collapsedBottomHeight - collapsedTopHeight; 114 if (collapsedTopHeight == 0) { 115 o.stickySidebar.css('padding-top', 0); 116 o.stickySidebarPaddingTop = 0; 117 } 118 else { 119 o.stickySidebarPaddingTop = 1; 120 } 121 122 if (collapsedBottomHeight == 0) { 123 o.stickySidebar.css('padding-bottom', 0); 124 o.stickySidebarPaddingBottom = 0; 125 } 126 else { 127 o.stickySidebarPaddingBottom = 1; 128 } 129 130 // We use this to know whether the user is scrolling up or down. 131 o.previousScrollTop = null; 132 133 // Scroll top (value) when the sidebar has fixed position. 134 o.fixedScrollTop = 0; 135 136 // Set sidebar to default values. 137 resetSidebar(); 138 139 o.onScroll = function (o) { 140 // Stop if the sidebar isn't visible. 141 if (!o.stickySidebar.is(":visible")) { 142 return; 143 } 144 145 // Stop if the window is too small. 146 if ($('body').width() < o.options.minWidth) { 147 resetSidebar(); 148 return; 149 } 150 151 // Stop if the sidebar width is larger than the container width (e.g. the theme is responsive and the sidebar is now below the content) 152 if (o.sidebar.outerWidth(true) + 50 > o.container.width()) { 153 resetSidebar(); 154 return; 155 } 156 157 var scrollTop = $(document).scrollTop(); 158 var position = 'static'; 159 160 // If the user has scrolled down enough for the sidebar to be clipped at the top, then we can consider changing its position. 161 if (scrollTop >= o.container.offset().top + (o.paddingTop + o.marginTop - o.options.additionalMarginTop)) { 162 // The top and bottom offsets, used in various calculations. 163 var offsetTop = o.paddingTop + o.marginTop + options.additionalMarginTop; 164 var offsetBottom = o.paddingBottom + o.marginBottom + options.additionalMarginBottom; 165 166 // All top and bottom positions are relative to the window, not to the parent elemnts. 167 var containerTop = o.container.offset().top; 168 var containerBottom = o.container.offset().top + getClearedHeight(o.container); 169 170 // The top and bottom offsets relative to the window screen top (zero) and bottom (window height). 171 var windowOffsetTop = 0 + options.additionalMarginTop; 172 var windowOffsetBottom; 173 174 var sidebarSmallerThanWindow = (o.stickySidebar.outerHeight() + offsetTop + offsetBottom) < $(window).height(); 175 if (sidebarSmallerThanWindow) { 176 windowOffsetBottom = windowOffsetTop + o.stickySidebar.outerHeight(); 177 } 178 else { 179 windowOffsetBottom = $(window).height() - o.marginBottom - o.paddingBottom - options.additionalMarginBottom; 180 } 181 182 var staticLimitTop = containerTop - scrollTop + o.paddingTop + o.marginTop; 183 var staticLimitBottom = containerBottom - scrollTop - o.paddingBottom - o.marginBottom; 184 185 var top = o.stickySidebar.offset().top - scrollTop; 186 var scrollTopDiff = o.previousScrollTop - scrollTop; 187 188 // If the sidebar position is fixed, then it won't move up or down by itself. So, we manually adjust the top coordinate. 189 if (o.stickySidebar.css('position') == 'fixed') { 190 if (o.options.sidebarBehavior == 'modern') { 191 top += scrollTopDiff; 192 } 193 } 194 195 if (o.options.sidebarBehavior == 'legacy') { 196 top = windowOffsetBottom - o.stickySidebar.outerHeight(); 197 top = Math.max(top, windowOffsetBottom - o.stickySidebar.outerHeight()); 198 } 199 200 if (scrollTopDiff > 0) { // If the user is scrolling up. 201 top = Math.min(top, windowOffsetTop); 202 } 203 else { // If the user is scrolling down. 204 top = Math.max(top, windowOffsetBottom - o.stickySidebar.outerHeight()); 205 } 206 207 top = Math.max(top, staticLimitTop); 208 209 top = Math.min(top, staticLimitBottom - o.stickySidebar.outerHeight()); 210 211 // If the sidebar is the same height as the container, we won't use fixed positioning. 212 var sidebarSameHeightAsContainer = o.container.height() == o.stickySidebar.outerHeight(); 213 214 if (!sidebarSameHeightAsContainer && top == windowOffsetTop) { 215 position = 'fixed'; 216 } 217 else if (!sidebarSameHeightAsContainer && top == windowOffsetBottom - o.stickySidebar.outerHeight()) { 218 position = 'fixed'; 219 } 220 else if (scrollTop + top - o.sidebar.offset().top - o.paddingTop <= options.additionalMarginTop) { 221 // Stuck to the top of the page. No special behavior. 222 position = 'static'; 223 } 224 else { 225 // Stuck to the bottom of the page. 226 position = 'absolute'; 227 } 228 } 229 230 /*
231 * Performance notice: It's OK to set these CSS values at each resize/scroll, even if they don't change. 232 * It's way slower to first check if the values have changed. 233 */ 234 if (position == 'fixed') { 235 o.stickySidebar.css({ 236 'position': 'fixed', 237 'width': o.sidebar.width(), 238 'top': top, 239 'left': o.sidebar.offset().left + parseInt(o.sidebar.css('padding-left')) 240 }); 241 } 242 else if (position == 'absolute') { 243 var css = {}; 244 245 if (o.stickySidebar.css('position') != 'absolute') { 246 css.position = 'absolute'; 247 css.top = scrollTop + top - o.sidebar.offset().top - o.stickySidebarPaddingTop - o.stickySidebarPaddingBottom; 248 } 249 250 css.width = o.sidebar.width(); 251 css.left = ''; 252 253 o.stickySidebar.css(css); 254 } 255 else if (position == 'static') { 256 resetSidebar(); 257 } 258 259 if (position != 'static') { 260 if (o.options.updateSidebarHeight == true) { 261 o.sidebar.css({ 262 'min-height': o.stickySidebar.outerHeight() + o.stickySidebar.offset().top - o.sidebar.offset().top + o.paddingBottom 263 }); 264 } 265 } 266 267 o.previousScrollTop = scrollTop; 268 }; 269 270 // Initialize the sidebar's position. 271 o.onScroll(o); 272 273 // Recalculate the sidebar's position on every scroll and resize. 274 $(document).scroll(function (o) { 275 return function () { 276 o.onScroll(o); 277 }; 278 }(o)); 279 $(window).resize(function (o) { 280 return function () { 281 o.stickySidebar.css({'position': 'static'}); 282 o.onScroll(o); 283 }; 284 }(o)); 285 286 // Reset the sidebar to its default state 287 function resetSidebar() { 288 o.fixedScrollTop = 0; 289 o.sidebar.css({ 290 'min-height': '1px' 291 }); 292 o.stickySidebar.css({ 293 'position': 'static', 294 'width': '' 295 }); 296 } 297 298 // Get the height of a div as if its floated children were cleared. Note that this function fails if the floats are more than one level deep. 299 function getClearedHeight(e) { 300 var height = e.height(); 301 302 e.children().each(function () { 303 height = Math.max(height, $(this).height()); 304 }); 305 306 return height; 307 } 308 }); 309 } 310 } 311 312 /*---------- FIXED SIDEBAR ----------*/ 313 $('.fixedSidebar') 314 .theiaStickySidebar({ 315 additionalMarginTop: 30 316 }); 317 318} )( jQuery );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.