1// jQuery HC-Sticky 2// ============= 3// Version: 1.2.43 4// Copyright: Some Web Media 5// Author: Some Web Guy 6// Author URL: http://twitter.com/some_web_guy 7// Website: http://someweblog.com/ 8// Plugin URL: https://github.com/somewebmedia/hc-sticky 9// License: Released under the MIT License www.opensource.org/licenses/mit-license.php 10// Description: Cross-browser jQuery plugin that makes any element attached to the page and always visible while you scroll. 11 12(function($, window, undefined) { 13 "use strict"; 14 15 // console.log shortcut 16 var log = function(t){console.log(t)}; 17 18 var $window = $(window), 19 document = window.document, 20 $document = $(document); 21 22 // detect IE version 23 var ie = (function(){var undef, v = 3, div = document.createElement('div'), all = div.getElementsByTagName('i'); while (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->', all[0]){}; return v > 4 ? v : undef})(); 24 25 /*---------------------------------------------------- 26 Global functions 27 ----------------------------------------------------*/ 28 29 // check for scroll direction and speed 30 var getScroll = function() { 31 var pageXOffset = window.pageXOffset !== undefined ? window.pageXOffset : (document.compatMode == "CSS1Compat" ? window.document.documentElement.scrollLeft : window.document.body.scrollLeft), 32 pageYOffset = window.pageYOffset !== undefined ? window.pageYOffset : (document.compatMode == "CSS1Compat" ? window.document.documentElement.scrollTop : window.document.body.scrollTop); 33 34 if (typeof getScroll.x == 'undefined') { 35 getScroll.x = pageXOffset; 36 getScroll.y = pageYOffset; 37 } 38 if (typeof getScroll.distanceX == 'undefined') { 39 getScroll.distanceX = pageXOffset; 40 getScroll.distanceY = pageYOffset; 41 } else { 42 getScroll.distanceX = pageXOffset - getScroll.x; 43 getScroll.distanceY = pageYOffset - getScroll.y; 44 } 45 46 var diffX = getScroll.x - pageXOffset, 47 diffY = getScroll.y - pageYOffset; 48 49 getScroll.direction = diffX < 0 ? 'right' : 50 diffX > 0 ? 'left' : 51 diffY <= 0 ? 'down' : 52 diffY > 0 ? 'up' : 'first'; 53 54 getScroll.x = pageXOffset;
55 getScroll.y = pageYOffset; 56 }; 57 $window.on('scroll', getScroll); 58 59 60 // little original style plugin 61 $.fn.style = function(style) { 62 if (!style) return null; 63 64 var $this = $(this), 65 value; 66 67 // clone element 68 var $clone = $this.clone().css('display','none'); 69 // randomize the name of cloned radio buttons, otherwise selections get screwed 70 $clone.find('input:radio').attr('name','copy-' + Math.floor((Math.random()*100)+1)); 71 // insert clone to DOM 72 $this.after($clone); 73 74 var getStyle = function(el, style){ 75 var val; 76 if (el.currentStyle) { 77 // replace dashes with capitalized letter, e.g. padding-left to paddingLeft 78 val = el.currentStyle[style.replace(/-\w/g, function(s){return s.toUpperCase().replace('-','')})]; 79 } else if (window.getComputedStyle) { 80 val = document.defaultView.getComputedStyle(el,null).getPropertyValue(style); 81 } 82 // check for margin:auto 83 val = (/margin/g.test(style)) ? ((parseInt(val) === $this[0].offsetLeft) ? val : 'auto') : val; 84 return val; 85 }; 86 87 if (typeof style == 'string') { 88 value = getStyle($clone[0], style); 89 } else { 90 value = {}; 91 $.each(style, function(i, s){ 92 value[s] = getStyle($clone[0], s); 93 }); 94 } 95 96 // destroy clone 97 $clone.remove(); 98 99 return value || null; 100 }; 101 102 103 /*---------------------------------------------------- 104 jQuery plugin 105 ----------------------------------------------------*/ 106 107 $.fn.extend({ 108 109 hcSticky: function(options) { 110 111 // check if selected element exist in DOM, user doesn't have to worry about that 112 if (this.length == 0) return this; 113 114 this.pluginOptions('hcSticky', { 115 top: 0, 116 bottom: 0, 117 bottomEnd: 0, 118 innerTop: 0, 119 innerSticker: null, 120 className: 'sticky', 121 wrapperClassName: 'wrapper-sticky', 122 stickTo: null, 123 responsive: true, 124 followScroll: true, 125 offResolutions: null, 126 onStart: $.noop, 127 onStop: $.noop, 128 on: true, 129 fn: null // used only by the plugin 130 }, options || {}, { 131 reinit: function(){ 132 // just call itself again 133 $(this).hcSticky(); 134 }, 135 stop: function(){ 136 $(this).pluginOptions('hcSticky', {on: false}).each(function(){ 137 var $this = $(this), 138 options = $this.pluginOptions('hcSticky'), 139 $wrapper = $this.parent('.' + options.wrapperClassName); 140 141 // set current position 142 var top = $this.offset().top - $wrapper.offset().top; 143 $this.css({ 144 position: 'absolute', 145 top: top, 146 bottom: 'auto', 147 left: 'auto', 148 right: 'auto' 149 }).removeClass(options.className); 150 }); 151 }, 152 off: function(){ 153 $(this).pluginOptions('hcSticky', {on: false}).each(function(){ 154 var $this = $(this), 155 options = $this.pluginOptions('hcSticky'), 156 $wrapper = $this.parent('.' + options.wrapperClassName); 157 158 // clear position 159 $this.css({ 160 position: 'relative', 161 top: 'auto', 162 bottom: 'auto', 163 left: 'auto', 164 right: 'auto' 165 }).removeClass(options.className); 166 167 $wrapper.css('height', 'auto'); 168 169 /* Added by me */ 170 /* Clear width */ 171 $this.css('width', ''); 172 /**/ 173 }); 174 }, 175 on: function(){ 176 $(this).each(function(){ 177 $(this).pluginOptions('hcSticky', { 178 on: true, 179 remember: { 180 offsetTop: $window.scrollTop() 181 } 182 }).hcSticky(); 183 }); 184 }, 185 destroy: function(){ 186 var $this = $(this), 187 options = $this.pluginOptions('hcSticky'), 188 $wrapper = $this.parent('.' + options.wrapperClassName); 189 190 // reset position to original 191 $this.removeData('hcStickyInit').css({ 192 position: $wrapper.css('position'), 193 top: $wrapper.css('top'), 194 bottom: $wrapper.css('bottom'), 195 left: $wrapper.css('left'), 196 right: $wrapper.css('right') 197 }).removeClass(options.className); 198 199 // remove events 200 $window.off('resize', options.fn.resize).off('scroll', options.fn.scroll); 201 202 // destroy wrapper 203 $this.unwrap(); 204 } 205 }); 206 207 // on/off settings 208 if (options && typeof options.on != 'undefined') { 209 if (options.on) { 210 this.hcSticky('on'); 211 } else { 212 this.hcSticky('off'); 213 } 214 } 215 216 // stop on commands 217 if (typeof options == 'string') return this; 218 219 // do our thing 220 return this.each(function(){ 221 222 var $this = $(this), 223 options = $this.pluginOptions('hcSticky'); 224 225 var $wrapper = (function(){ // wrapper exists 226 var $this_wrapper = $this.parent('.' + options.wrapperClassName); 227 if ($this_wrapper.length > 0) { 228 $this_wrapper.css({ 229 'height': $this.outerHeight(true), 230 'width': (function(){ 231 // check if wrapper already has width in % 232 var width = $this_wrapper.style('width'); 233 if (width.indexOf('%') >= 0 || width == 'auto') { 234 if ($this.css('box-sizing') == 'border-box' || $this.css('-moz-box-sizing') == 'border-box') { 235 $this.css('width', $this_wrapper.width()); 236 } else { 237 $this.css('width', $this_wrapper.width() - parseInt($this.css('padding-left') - parseInt($this.css('padding-right')))); 238 } 239 return width; 240 } else { 241 return $this.outerWidth(true); 242 } 243 })() 244 }); 245 return $this_wrapper; 246 } else { 247 return false;
248 } 249 })() || (function(){ // wrapper doesn't exist 250 251 var this_css = $this.style(['width', 'margin-left', 'left', 'right', 'top', 'bottom', 'float', 'display']); 252 var display = $this.css('display'); 253 254 var $this_wrapper = $('<div>', { 255 'class': options.wrapperClassName 256 }).css({ 257 'display': display, 258 'height': $this.outerHeight(true), 259 'width': (function(){ 260 if (this_css['width'].indexOf('%') >= 0 || (this_css['width'] == 'auto' && display != 'inline-block' && display != 'inline')) { // check if element has width in % 261 $this.css('width', parseFloat($this.css('width'))); 262 return this_css['width']; 263 } else if (this_css['width'] == 'auto' && (display == 'inline-block' || display == 'inline')) { 264 return $this.width(); 265 } else { 266 // check if margin is set to 'auto' 267 return (this_css['margin-left'] == 'auto') ? $this.outerWidth() : $this.outerWidth(true); 268 } 269 })(), 270 'margin': (this_css['margin-left']) ? 'auto' : null, 271 'position': (function(){ 272 var position = $this.css('position'); 273 return position == 'static' ? 'relative' : position; 274 })(), 275 'float': this_css['float'] || null, 276 'left': this_css['left'], 277 'right': this_css['right'], 278 'top': this_css['top'], 279 'bottom': this_css['bottom'], 280 'vertical-align': 'top' 281 }); 282 283 $this.wrap($this_wrapper); 284 285 // ie7 inline-block fix 286 if (ie === 7) { 287 if ($('head').find('style#hcsticky-iefix').length === 0) { 288 $('<style id="hcsticky-iefix">.' + options.wrapperClassName + ' {zoom: 1;}</style>').appendTo('head'); 289 } 290 } 291 292 // return appended element 293 return $this.parent(); 294 })(); 295 296 297 // check if we should go further 298 if ($this.data('hcStickyInit')) return; 299 // leave our mark 300 $this.data('hcStickyInit', true); 301 302 303 // check if referring element is document 304 var stickTo_document = options.stickTo && (options.stickTo == 'document' || (options.stickTo.nodeType && options.stickTo.nodeType == 9) || (typeof options.stickTo == 'object' && options.stickTo instanceof (typeof HTMLDocument != 'undefined' ? HTMLDocument : Document))) ? true : false; 305 306 // select container ;) 307 var $container = options.stickTo 308 ? stickTo_document 309 ? $document 310 : typeof options.stickTo == 'string' 311 ? $wrapper.parents(options.stickTo) /* Modified by me */ 312 : options.stickTo 313 : $wrapper.parent(); 314 315 // clear sticky styles 316 $this.css({ 317 top: 'auto', 318 bottom: 'auto', 319 left: 'auto', 320 right: 'auto' 321 }); 322 323 // attach event on entire page load, maybe some images inside element has been loading, so chek height again 324 $window.load(function(){ 325 if ($this.outerHeight(true) > $container.height()) { 326 $wrapper.css('height', $this.outerHeight(true)); 327 $this.hcSticky('reinit'); 328 } 329 }); 330 331 // functions for attachiung and detaching sticky 332 var _setFixed = function(args) { 333 // check if already floating 334 if ($this.hasClass(options.className)) return; 335 336 // apply styles 337 args = args || {}; 338 $this.css({ 339 position: 'fixed', 340 top: args.top || 0, 341 left: args.left || $wrapper.offset().left 342 }).addClass(options.className); 343 344 // start event 345 options.onStart.apply($this[0]); 346 // add class to wrpaeer 347 $wrapper.addClass('sticky-active'); 348 }, 349 _reset = function(args) { 350 args = args || {}; 351 args.position = args.position || 'absolute'; 352 args.top = args.top || 0; 353 args.left = args.left || 0; 354 355 // check if we should apply css 356 if ($this.css('position') != 'fixed' && parseInt($this.css('top')) == args.top) return; 357 358 // apply styles 359 $this.css({ 360 position: args.position, 361 top: args.top, 362 left: args.left 363 }).removeClass(options.className); 364 365 // stop event 366 options.onStop.apply($this[0]); 367 // remove class from wrpaeer 368 $wrapper.removeClass('sticky-active'); 369 }; 370 371 // sticky scroll function 372 var onScroll = function(init) { 373 374 // check if we need to run sticky 375 if (!options.on || !$this.is(':visible')) return; 376 377 // if the element is the same height or larger than the container then let's reset it so that it returns to the original position 378 if ($this.outerHeight(true) >= $container.height()) { 379 _reset(); 380 381 return; 382 } 383 384 var top_spacing = (options.innerSticker) ? $(options.innerSticker).position().top : ((options.innerTop) ? options.innerTop : 0), 385 wrapper_inner_top = $wrapper.offset().top, 386 bottom_limit = $container.height() - options.bottomEnd + (stickTo_document ? 0 : wrapper_inner_top), 387 top_limit = $wrapper.offset().top - options.top + top_spacing, 388 this_height = $this.outerHeight(true) + options.bottom, 389 window_height = $window.height(), 390 offset_top = $window.scrollTop(), 391 this_document_top = $this.offset().top, 392 this_window_top = this_document_top - offset_top, 393 bottom_distance; 394 395 396 // if sticky has been restarted with on/off wait for it to reach top or bottom 397 if (typeof options.remember != 'undefined' && options.remember) { 398 399 var position_top = this_document_top - options.top - top_spacing; 400 401 if (this_height - top_spacing > window_height && options.followScroll) { // element bigger than window with follow scroll on 402 403 if (position_top < offset_top && offset_top + window_height <= position_top + $this.height()) { 404 // element is in the middle of the screen, let our primary calculations do the work 405 options.remember = false;
406 } 407 408 } else { // element smaller than window or follow scroll turned off 409 410 if (options.remember.offsetTop > position_top) { 411 // slide up 412 if (offset_top <= position_top) { 413 _setFixed({ 414 top: options.top - top_spacing 415 }); 416 options.remember = false; 417 } 418 } else { 419 // slide down 420 if (offset_top >= position_top) { 421 _setFixed({ 422 top: options.top - top_spacing 423 }); 424 options.remember = false; 425 } 426 } 427 428 } 429 430 return; 431 } 432 433 434 if (offset_top > top_limit) { 435 436 // http://geek-and-poke.com/geekandpoke/2012/7/27/simply-explained.html 437 438 if (bottom_limit + options.bottom - (options.followScroll && window_height < this_height ? 0 : options.top) <= offset_top + this_height - top_spacing - ((this_height - top_spacing > window_height - (top_limit - top_spacing) && options.followScroll) ? (((bottom_distance = this_height - window_height - top_spacing) > 0) ? bottom_distance : 0) : 0)) { 439 // bottom reached end 440 _reset({ 441 top: bottom_limit - this_height + options.bottom - wrapper_inner_top 442 }); 443 } else if (this_height - top_spacing > window_height && options.followScroll) { 444 445 if (this_window_top + this_height <= window_height) { // element bigger than window with follow scroll on 446 447 if (getScroll.direction == 'down') { 448 // scroll down 449 _setFixed({ 450 top: window_height - this_height 451 }); 452 } else { 453 // scroll up 454 if (this_window_top < 0 && $this.css('position') == 'fixed') { 455 _reset({ 456 top: this_document_top - (top_limit + options.top - top_spacing) - getScroll.distanceY 457 }); 458 } 459 } 460 461 } else { // element smaller than window or follow scroll turned off 462 463 if (getScroll.direction == 'up' && this_document_top >= offset_top + options.top - top_spacing) { 464 // scroll up 465 _setFixed({ 466 top: options.top - top_spacing 467 }); 468 } else if (getScroll.direction == 'down' && this_document_top + this_height > window_height && $this.css('position') == 'fixed') { 469 // scroll down 470 _reset({ 471 top: this_document_top - (top_limit + options.top - top_spacing) - getScroll.distanceY 472 }); 473 } 474 475 } 476 } else { 477 // starting (top) fixed position 478 _setFixed({ 479 top: options.top - top_spacing 480 }); 481 } 482 } else { 483 // reset 484 _reset(); 485 } 486 487 }; 488 489 490 // store resize data in case responsive is on 491 var resize_timeout = false, 492 $resize_clone = false; 493 494 var onResize = function() { 495 496 // check if sticky is attached to scroll event 497 attachScroll(); 498 499 // check for off resolutions 500 checkResolutions(); 501 502 // check if we need to run sticky 503 if (!options.on) return; 504 505 var setLeft = function(){ 506 // set new left position 507 if ($this.css('position') == 'fixed') { 508 $this.css('left', $wrapper.offset().left); 509 } else { 510 $this.css('left', 0); 511 } 512 }; 513 514 // check for width change (css media queries) 515 if (options.responsive) { 516 // clone element and make it invisible 517 if (!$resize_clone) { 518 $resize_clone = $this.clone().attr('style', '').css({ 519 visibility: 'hidden', 520 height: 0, 521 overflow: 'hidden', 522 paddingTop: 0, 523 paddingBottom: 0, 524 marginTop: 0, 525 marginBottom: 0 526 }); 527 $wrapper.after($resize_clone); 528 } 529 530 var wrapper_width = $wrapper.style('width'); 531 var resize_clone_width = $resize_clone.style('width'); 532 533 if (resize_clone_width == 'auto' && wrapper_width != 'auto') { 534 resize_clone_width = parseInt($this.css('width')); 535 } 536 537 // recalculate wrpaeer width 538 if (resize_clone_width != wrapper_width) { 539 $wrapper.width(resize_clone_width); 540 } 541 542 // clear previous timeout 543 if (resize_timeout) { 544 clearTimeout(resize_timeout); 545 } 546 // timedout destroing of cloned elements so we don't clone it again and again while resizing the window 547 resize_timeout = setTimeout(function() { 548 // clear timeout id 549 resize_timeout = false;
550 // destroy cloned element 551 $resize_clone.remove(); 552 $resize_clone = false; 553 }, 250); 554 } 555 556 // set new left position 557 setLeft(); 558 559 // recalculate inner element width (maybe original width was in %) 560 if ($this.outerWidth(true) != $wrapper.width()) { 561 var this_w = ($this.css('box-sizing') == 'border-box' || $this.css('-moz-box-sizing') == 'border-box') 562 ? $wrapper.width() 563 : $wrapper.width() - parseInt($this.css('padding-left')) - parseInt($this.css('padding-right')); 564 // subtract margins 565 this_w = this_w - parseInt($this.css('margin-left')) - parseInt($this.css('margin-right')); 566 // set new width 567 $this.css('width', this_w); 568 } 569 }; 570 571 572 // remember scroll and resize callbacks so we can attach and detach them 573 $this.pluginOptions('hcSticky', {fn: { 574 scroll: onScroll, 575 resize: onResize 576 }}); 577 578 579 // check for off resolutions 580 var checkResolutions = function(){ 581 if (options.offResolutions) { 582 // convert to array 583 if (!$.isArray(options.offResolutions)) { 584 options.offResolutions = [options.offResolutions]; 585 } 586 587 var isOn = true; 588 589 $.each(options.offResolutions, function(i, rez){ 590 if (rez < 0) { 591 // below 592 if ($window.width() < rez * -1) { 593 isOn = false; 594 $this.hcSticky('off'); 595 } 596 } else { 597 // abowe 598 if ($window.width() > rez) { 599 isOn = false; 600 $this.hcSticky('off'); 601 } 602 } 603 }); 604 605 // turn on again 606 if (isOn && !options.on) { 607 $this.hcSticky('on'); 608 } 609 } 610 }; 611 checkResolutions(); 612 613 614 // attach resize function to event 615 $window.on('resize', onResize); 616 617 618 // attaching scroll function to event 619 var attachScroll = function(){ 620 var isAttached = false; 621 if ($._data(window, 'events').scroll != undefined) { 622 $.each($._data(window, 'events').scroll, function(i, f){ 623 if (f.handler == options.fn.scroll) { 624 isAttached = true; 625 } 626 }); 627 } 628 if (!isAttached) { 629 // run it once to disable glitching 630 options.fn.scroll(true); 631 // attach function to scroll event only once 632 $window.on('scroll', options.fn.scroll); 633 } 634 }; 635 attachScroll(); 636 637 }); 638 } 639 }); 640 641})(jQuery, this); 642 643 644 645// jQuery HC-PluginOptions 646// ============= 647// Version: 1.0 648// Copyright: Some Web Media 649// Author: Some Web Guy 650// Author URL: http://twitter.com/some_web_guy 651// Website: http://someweblog.com/ 652// License: Released under the MIT License www.opensource.org/licenses/mit-license.php 653 654(function($, undefined) { 655 "use strict"; 656 657 $.fn.extend({ 658 659 pluginOptions: function(pluginName, defaultOptions, userOptions, commands) { 660 661 // create object to store data 662 if (!this.data(pluginName)) this.data(pluginName, {}); 663 664 // return options 665 if (pluginName && typeof defaultOptions == 'undefined') return this.data(pluginName).options; 666 667 // update 668 userOptions = userOptions || (defaultOptions || {}); 669 670 if (typeof userOptions == 'object' || userOptions === undefined) { 671 672 // options 673 return this.each(function(){ 674 var $this = $(this); 675 676 if (!$this.data(pluginName).options) { 677 // init our options and attach to element 678 $this.data(pluginName, {options: $.extend(defaultOptions, userOptions || {})}); 679 // attach commands if any 680 if (commands) { 681 $this.data(pluginName).commands = commands; 682 } 683 } else { 684 // update existing options 685 $this.data(pluginName, $.extend($this.data(pluginName), {options: $.extend($this.data(pluginName).options, userOptions || {})})); 686 } 687 }); 688 689 } else if (typeof userOptions == 'string') { 690 691 return this.each(function(){ 692 $(this).data(pluginName).commands[userOptions].call(this); 693 }); 694 695 } else { 696 697 return this; 698 699 } 700 701 } 702 703 }); 704 705})(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.