1// sidebar 2/*! 3 * Theia Sticky Sidebar v1.7.0 4 * https://github.com/WeCodePixels/theia-sticky-sidebar 5 * 6 * Glues your website's sidebars, making them permanently visible while scrolling. 7 * 8 * Copyright 2013-2016 WeCodePixels and other contributors 9 * Released under the MIT license 10 */ 11 12(function ($) { 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); 373 374jQuery(document).ready(function() { 375 jQuery('.sidebar-wrapper').theiaStickySidebar({ 376 // Settings 377 additionalMarginTop: 0, 378 additionalMarginBottom: 0, 379 }); 380}); 381 382 383 384 385// Back to top button 386(function() { 387 $(document).ready(function() { 388 return $(window).scroll(function() { 389 return $(window).scrollTop() > 600 ? $("#back-to-top").addClass("show") : $("#back-to-top").removeClass("show") 390 }), $("#back-to-top").click(function() { 391 return $("html,body").animate({ 392 scrollTop: "0" 393 }) 394 }) 395 }) 396}).call(this); 397 398//test.js 399function testTheiaStickySidebars() { 400 var me = {}; 401 me.scrollTopStep = 1; 402 me.currentScrollTop = 0; 403 me.values = null; 404 405 window.scrollTo(0, 1),300; 406 window.scrollTo(0, 0); 407 408 $(window).scroll(function(me) { 409 return function(event) { 410 var newValues = []; 411 412 // Get sidebar offsets. 413 $('.theiaStickySidebar').each(function() { 414 newValues.push($(this).offset().top); 415 }); 416 417 if (me.values != null) { 418 var ok = true; 419 420 for (var j = 0; j < newValues.length; j++) { 421 var diff = Math.abs(newValues[j] - me.values[j]); 422 if (diff > 1) { 423 ok = false;
424 425 console.log('Offset difference for sidebar #' + (j + 1) + ' is ' + diff + 'px'); 426 427 // Highlight sidebar. 428 $($('.theiaStickySidebar')[j]).css('background', 'yellow'); 429 } 430 } 431 432 if (ok == false) { 433 // Stop test. 434 $(this).unbind(event); 435 436 alert('Bummer. Offset difference is bigger than 1px for some sidebars, which will be highlighted in yellow. Check the logs. Aborting.'); 437 438 return; 439 } 440 } 441 442 me.values = newValues; 443 444 // Scroll to bottom. We don't cache ($(document).height() - $(window).height()) since it may change (e.g. after images are loaded). 445 if (me.currentScrollTop < ($(document).height() - $(window).height()) && me.scrollTopStep == 1) { 446 me.currentScrollTop += me.scrollTopStep; 447 window.scrollTo(0, me.currentScrollTop); 448 } 449 // Then back up. 450 else if (me.currentScrollTop > 0) { 451 me.scrollTopStep = -1; 452 me.currentScrollTop += me.scrollTopStep; 453 window.scrollTo(0, me.currentScrollTop); 454 } 455 // Then stop. 456 else { 457 $(this).unbind(event); 458 459 alert("Great success!"); 460 } 461 462 }; 463 }(me)); 464} 465 466 467 468 469 470 471 472 473 474 475 476
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.