1/*! 2 * Theia Sticky Sidebar v1.7.0 3 * https://github.com/WeCodePixels/theia-sticky-sidebar 4 * 5 * Glues your website's sidebars, making them permanently visible while scrolling. 6 * 7 * Copyright 2013-2016 WeCodePixels and other contributors 8 * Released under the MIT license 9 */ 10 11(function ($) { 12 "use strict"; 13 $.fn.theiaStickySidebar = function (options) { 14 var defaults = { 15 'containerSelector': '', 16 'additionalMarginTop': 0, 17 'additionalMarginBottom': 0, 18 'updateSidebarHeight': true, 19 'minWidth': 0, 20 'disableOnResponsiveLayouts': true, 21 'sidebarBehavior': 'modern', 22 'defaultPosition': 'relative', 23 'namespace': 'TSS' 24 }; 25 options = $.extend(defaults, options); 26 27 // Validate options 28 options.additionalMarginTop = parseInt(options.additionalMarginTop) || 0; 29 options.additionalMarginBottom = parseInt(options.additionalMarginBottom) || 0; 30 31 tryInitOrHookIntoEvents(options, this); 32 33 // Try doing init, otherwise hook into window.resize and document.scroll and try again then. 34 function tryInitOrHookIntoEvents(options, $that) { 35 var success = tryInit(options, $that); 36 37 if (!success) {
38 console.log('TSS: Body width smaller than options.minWidth. Init is delayed.'); 39 40 $(document).on('scroll.' + options.namespace, function (options, $that) { 41 return function (evt) { 42 var success = tryInit(options, $that); 43 44 if (success) { 45 $(this).unbind(evt); 46 } 47 }; 48 }(options, $that)); 49 $(window).on('resize.' + options.namespace, function (options, $that) { 50 return function (evt) { 51 var success = tryInit(options, $that); 52 53 if (success) { 54 $(this).unbind(evt); 55 } 56 }; 57 }(options, $that)) 58 } 59 } 60 61 // Try doing init if proper conditions are met. 62 function tryInit(options, $that) { 63 if (options.initialized === true) { 64 return true; 65 } 66 67 if ($('body').width() < options.minWidth) { 68 return false; 69 } 70 71 init(options, $that); 72 73 return true; 74 } 75 76 // Init the sticky sidebar(s). 77 function init(options, $that) { 78 options.initialized = true; 79 80 // Add CSS 81 var existingStylesheet = $('#theia-sticky-sidebar-stylesheet-' + options.namespace); 82 if (existingStylesheet.length === 0) { 83 $('head').append($('<style id="theia-sticky-sidebar-stylesheet-' + options.namespace + '">.theiaStickySidebar:after {content: ""; display: table; clear: both;}</style>')); 84 } 85 86 $that.each(function () { 87 var o = {}; 88 89 o.sidebar = $(this); 90 91 // Save options 92 o.options = options || {}; 93 94 // Get container 95 o.container = $(o.options.containerSelector); 96 if (o.container.length == 0) { 97 o.container = o.sidebar.parent(); 98 } 99 100 // Create sticky sidebar 101 o.sidebar.parents().css('-webkit-transform', 'none'); // Fix for WebKit bug - https://code.google.com/p/chromium/issues/detail?id=20574 102 o.sidebar.css({ 103 'position': o.options.defaultPosition, 104 'overflow': 'visible', 105 // The "box-sizing" must be set to "content-box" because we set a fixed height to this element when the sticky
105sidebar has a fixed position. 106 '-webkit-box-sizing': 'border-box', 107 '-moz-box-sizing': 'border-box', 108 'box-sizing': 'border-box' 109 }); 110 111 // Get the sticky sidebar element. If none has been found, then create one. 112 o.stickySidebar = o.sidebar.find('.theiaStickySidebar'); 113 if (o.stickySidebar.length == 0) { 114 // Remove <script> tags, otherwise they will be run again when added to the stickySidebar. 115 var javaScriptMIMETypes = /(?:text|application)\/(?:x-)?(?:javascript|ecmascript)/i; 116 o.sidebar.find('script').filter(function (index, script) { 117 return script.type.length === 0 || script.type.match(javaScriptMIMETypes); 118 }).remove(); 119 120 o.stickySidebar = $('<div>').addClass('theiaStickySidebar').append(o.sidebar.children()); 121 o.sidebar.append(o.stickySidebar); 122 } 123 124 // Get existing top and bottom margins and paddings 125 o.marginBottom = parseInt(o.sidebar.css('margin-bottom')); 126 o.paddingTop = parseInt(o.sidebar.css('padding-top')); 127 o.paddingBottom = parseInt(o.sidebar.css('padding-bottom')); 128
129 // Add a temporary padding rule to check for collapsable margins. 130 var collapsedTopHeight = o.stickySidebar.offset().top; 131 var collapsedBottomHeight = o.stickySidebar.outerHeight(); 132 o.stickySidebar.css('padding-top', 1); 133 o.stickySidebar.css('padding-bottom', 1); 134 collapsedTopHeight -= o.stickySidebar.offset().top; 135 collapsedBottomHeight = o.stickySidebar.outerHeight() - collapsedBottomHeight - collapsedTopHeight; 136 if (collapsedTopHeight == 0) { 137 o.stickySidebar.css('padding-top', 0); 138 o.stickySidebarPaddingTop = 0; 139 } 140 else { 141 o.stickySidebarPaddingTop = 1; 142 } 143 144 if (collapsedBottomHeight == 0) { 145 o.stickySidebar.css('padding-bottom', 0); 146 o.stickySidebarPaddingBottom = 0; 147 } 148 else { 149 o.stickySidebarPaddingBottom = 1; 150 } 151 152 // We use this to know whether the user is scrolling up or down. 153 o.previousScrollTop = null; 154 155 // Scroll top (value) when the sidebar has fixed position. 156 o.fixedScrollTop = 0; 157 158 // Set sidebar to default values. 159 resetSidebar(); 160 161 o.onScroll = function (o) { 162 // Stop if the sidebar isn't visible. 163 if (!o.stickySidebar.is(":visible")) { 164 return; 165 } 166 167 // Stop if the window is too small. 168 if ($('body').width() < o.options.minWidth) { 169 resetSidebar(); 170 return; 171 } 172 173 // 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) 174 if (o.options.disableOnResponsiveLayouts) { 175 var sidebarWidth = o.sidebar.outerWidth(o.sidebar.css('float') == 'none'); 176 177 if (sidebarWidth + 50 > o.container.width()) { 178 resetSidebar(); 179 return; 180 } 181 } 182 183 var scrollTop = $(document).scrollTop(); 184 var position = 'static'; 185 186 // If the user has scrolled down enough for the sidebar to be clipped at the top, then we can consider changing its position. 187 if (scrollTop >= o.sidebar.offset().top + (o.paddingTop - o.options.additionalMarginTop)) { 188 // The top and bottom offsets, used in various calculations. 189 var offsetTop = o.paddingTop + options.additionalMarginTop; 190 var offsetBottom = o.paddingBottom + o.marginBottom + options.additionalMarginBottom; 191 192 // All top and bottom positions are relative to the window, not to the parent elemnts. 193 var containerTop = o.sidebar.offset().top; 194 var containerBottom = o.sidebar.offset().top + getClearedHeight(o.container); 195 196 // The top and bottom offsets relative to the window screen top (zero) and bottom (window height). 197 var windowOffsetTop = 0 + options.additionalMarginTop; 198 var windowOffsetBottom; 199 200 var sidebarSmallerThanWindow = (o.stickySidebar.outerHeight() + offsetTop + offsetBottom) < $(window).height(); 201 if (sidebarSmallerThanWindow) { 202 windowOffsetBottom = windowOffsetTop + o.stickySidebar.outerHeight(); 203 } 204 else { 205 windowOffsetBottom = $(window).height() - o.marginBottom - o.paddingBottom - options.additionalMarginBottom; 206 } 207 208 var staticLimitTop = containerTop - scrollTop + o.paddingTop; 209 var staticLimitBottom = containerBottom - scrollTop - o.paddingBottom - o.marginBottom; 210 211 var top = o.stickySidebar.offset().top - scrollTop; 212 var scrollTopDiff = o.previousScrollTop - scrollTop; 213 214 // If the sidebar position is fixed, then it won't move up or down by itself. So, we manually adjust the top coordinate. 215 if (o.stickySidebar.css('position') == 'fixed') { 216 if (o.options.sidebarBehavior == 'modern') { 217 top += scrollTopDiff; 218 } 219 } 220 221 if (o.options.sidebarBehavior == 'stick-to-top') { 222 top = options.additionalMarginTop; 223 } 224 225 if (o.options.sidebarBehavior == 'stick-to-bottom') { 226 top = windowOffsetBottom - o.stickySidebar.outerHeight(); 227 } 228 229 if (scrollTopDiff > 0) { // If the user is scrolling up. 230 top = Math.min(top, windowOffsetTop); 231 } 232 else { // If the user is scrolling down. 233 top = Math.max(top, windowOffsetBottom - o.stickySidebar.outerHeight()); 234 } 235 236 top = Math.max(top, staticLimitTop); 237 238 top = Math.min(top, staticLimitBottom - o.stickySidebar.outerHeight()); 239 240 // If the sidebar is the same height as the container, we won't use fixed positioning. 241 var sidebarSameHeightAsContainer = o.container.height() == o.stickySidebar.outerHeight(); 242 243 if (!sidebarSameHeightAsContainer && top == windowOffsetTop) { 244 position = 'fixed'; 245 } 246 else if (!sidebarSameHeightAsContainer && top == windowOffsetBottom - o.stickySidebar.outerHeight()) { 247 position = 'fixed'; 248 } 249 else if (scrollTop + top - o.sidebar.offset().top - o.paddingTop <= options.additionalMarginTop) { 250 // Stuck to the top of the page. No special behavior. 251 position = 'static'; 252 } 253 else { 254 // Stuck to the bottom of the page. 255 position = 'absolute'; 256 } 257 } 258 259 /*
260 * Performance notice: It's OK to set these CSS values at each resize/scroll, even if they don't change. 261 * It's way slower to first check if the values have changed. 262 */ 263 if (position == 'fixed') { 264 var scrollLeft = $(document).scrollLeft(); 265 266 o.stickySidebar.css({ 267 'position': 'fixed', 268 'width': getWidthForObject(o.stickySidebar) + 'px', 269 'transform': 'translateY(' + top + 'px)', 270 'left': (o.sidebar.offset().left + parseInt(o.sidebar.css('padding-left')) - scrollLeft) + 'px', 271 'top': '0px' 272 }); 273 } 274 else if (position == 'absolute') { 275 var css = {}; 276 277 if (o.stickySidebar.css('position') != 'absolute') { 278 css.position = 'absolute'; 279 css.transform = 'translateY(' + (scrollTop + top - o.sidebar.offset().top - o.stickySidebarPaddingTop - o.stickySidebarPaddingBottom) + 'px)'; 280 css.top = '0px'; 281 } 282 283 css.width = getWidthForObject(o.stickySidebar) + 'px'; 284 css.left = ''; 285 286 o.stickySidebar.css(css); 287 } 288 else if (position == 'static') { 289 resetSidebar(); 290 } 291 292 if (position != 'static') { 293 if (o.options.updateSidebarHeight == true) { 294 o.sidebar.css({ 295 'min-height': o.stickySidebar.outerHeight() + o.stickySidebar.offset().top - o.sidebar.offset().top + o.paddingBottom 296 }); 297 } 298 } 299 300 o.previousScrollTop = scrollTop; 301 }; 302 303 // Initialize the sidebar's position. 304 o.onScroll(o); 305 306 // Recalculate the sidebar's position on every scroll and resize. 307 $(document).on('scroll.' + o.options.namespace, function (o) { 308 return function () { 309 o.onScroll(o); 310 }; 311 }(o)); 312 $(window).on('resize.' + o.options.namespace, function (o) { 313 return function () { 314 o.stickySidebar.css({'position': 'static'}); 315 o.onScroll(o); 316 }; 317 }(o)); 318 319 // Recalculate the sidebar's position every time the sidebar changes its size. 320 if (typeof ResizeSensor !== 'undefined') { 321 new ResizeSensor(o.stickySidebar[0], function (o) { 322 return function () { 323 o.onScroll(o); 324 }; 325 }(o)); 326 } 327 328 // Reset the sidebar to its default state 329 function resetSidebar() { 330 o.fixedScrollTop = 0; 331 o.sidebar.css({ 332 'min-height': '1px' 333 }); 334 o.stickySidebar.css({ 335 'position': 'static', 336 'width': '', 337 'transform': 'none' 338 }); 339 } 340 341 // 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. 342 function getClearedHeight(e) { 343 var height = e.height(); 344 345 e.children().each(function () { 346 height = Math.max(height, $(this).height()); 347 }); 348 349 return height; 350 } 351 }); 352 } 353 354 function getWidthForObject(object) { 355 var width; 356 357 try { 358 width = object[0].getBoundingClientRect().width; 359 } 360 catch (err) { 361 } 362 363 if (typeof width === "undefined") { 364 width = object.width(); 365 } 366 367 return width; 368 } 369 370 return this; 371 } 372})(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.