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 }, 170 on: function(){ 171 $(this).each(function(){ 172 $(this).pluginOptions('hcSticky', { 173 on: true, 174 remember: {
175 offsetTop: $window.scrollTop() 176 } 177 }).hcSticky(); 178 }); 179 }, 180 destroy: function(){ 181 var $this = $(this), 182 options = $this.pluginOptions('hcSticky'), 183 $wrapper = $this.parent('.' + options.wrapperClassName); 184 185 // reset position to original 186 $this.removeData('hcStickyInit').css({ 187 position: $wrapper.css('position'), 188 top: $wrapper.css('top'), 189 bottom: $wrapper.css('bottom'), 190 left: $wrapper.css('left'), 191 right: $wrapper.css('right') 192 }).removeClass(options.className); 193 194 // remove events 195 $window.off('resize', options.fn.resize).off('scroll', options.fn.scroll); 196 197 // destroy wrapper 198 $this.unwrap(); 199 } 200 }); 201 202 // on/off settings 203 if (options && typeof options.on != 'undefined') { 204 if (options.on) { 205 this.hcSticky('on'); 206 } else { 207 this.hcSticky('off'); 208 } 209 } 210 211 // stop on commands 212 if (typeof options == 'string') return this; 213 214 // do our thing 215 return this.each(function(){ 216 217 var $this = $(this), 218 options = $this.pluginOptions('hcSticky'); 219 220 var $wrapper = (function(){ // wrapper exists 221 var $this_wrapper = $this.parent('.' + options.wrapperClassName); 222 if ($this_wrapper.length > 0) { 223 $this_wrapper.css({ 224 'height': $this.outerHeight(true), 225 'width': (function(){ 226 // check if wrapper already has width in % 227 var width = $this_wrapper.style('width'); 228 if (width.indexOf('%') >= 0 || width == 'auto') { 229 if ($this.css('box-sizing') == 'border-box' || $this.css('-moz-box-sizing') == 'border-box') { 230 $this.css('width', $this_wrapper.width()); 231 } else { 232 $this.css('width', $this_wrapper.width() - parseInt($this.css('padding-left') - parseInt($this.css('padding-right')))); 233 } 234 return width; 235 } else { 236 return $this.outerWidth(true); 237 } 238 })() 239 }); 240 return $this_wrapper; 241 } else { 242 return false; 243 } 244 })() || (function(){ // wrapper doesn't exist 245 246 var this_css = $this.style(['width', 'margin-left', 'left', 'right', 'top', 'bottom', 'float', 'display']); 247 var display = $this.css('display'); 248 249 var $this_wrapper = $('<div>', { 250 'class': options.wrapperClassName 251 }).css({ 252 'display': display, 253 'height': $this.outerHeight(true), 254 'width': (function(){ 255 if (this_css['width'].indexOf('%') >= 0 || (this_css['width'] == 'auto' && display != 'inline-block' && display != 'inline')) { // check if element has width in % 256 $this.css('width', parseFloat($this.css('width'))); 257 return this_css['width']; 258 } else if (this_css['width'] == 'auto' && (display == 'inline-block' || display == 'inline')) { 259 return $this.width(); 260 } else { 261 // check if margin is set to 'auto' 262 return (this_css['margin-left'] == 'auto') ? $this.outerWidth() : $this.outerWidth(true); 263 } 264 })(), 265 'margin': (this_css['margin-left']) ? 'auto' : null, 266 'position': (function(){ 267 var position = $this.css('position'); 268 return position == 'static' ? 'relative' : position; 269 })(), 270 'float': this_css['float'] || null, 271 'left': this_css['left'], 272 'right': this_css['right'], 273 'top': this_css['top'], 274 'bottom': this_css['bottom'], 275 'vertical-align': 'top' 276 }); 277 278 $this.wrap($this_wrapper); 279 280 // ie7 inline-block fix 281 if (ie === 7) { 282 if ($('head').find('style#hcsticky-iefix').length === 0) { 283 $('<style id="hcsticky-iefix">.' + options.wrapperClassName + ' {zoom: 1;}</style>').appendTo('head'); 284 } 285 } 286 287 // return appended element 288 return $this.parent(); 289 })(); 290 291 292 // check if we should go further 293 if ($this.data('hcStickyInit')) return; 294 // leave our mark 295 $this.data('hcStickyInit', true); 296 297 298 // check if referring element is document 299 var stickTo_document = options.stickTo && (options.stickTo
299== 'document' || (options.stickTo.nodeType && options.stickTo.nodeType == 9) || (typeof options.stickTo == 'object' && options.stickTo instanceof (typeof HTMLDocument != 'undefined' ? HTMLDocument : Document))) ? true : false; 300 301 // select container ;) 302 var $container = options.stickTo 303 ? stickTo_document 304 ? $document 305 : typeof options.stickTo == 'string' 306 ? $(options.stickTo) 307 : options.stickTo 308 : $wrapper.parent(); 309 310 // clear sticky styles 311 $this.css({ 312 top: 'auto', 313 bottom: 'auto', 314 left: 'auto', 315 right: 'auto' 316 }); 317 318 // attach event on entire page load, maybe some images inside element has been loading, so chek height again 319 $window.load(function(){ 320 if ($this.outerHeight(true) > $container.height()) { 321 $wrapper.css('height', $this.outerHeight(true)); 322 $this.hcSticky('reinit'); 323 } 324 }); 325 326 // functions for attachiung and detaching sticky 327 var _setFixed = function(args) { 328 // check if already floating 329 if ($this.hasClass(options.className)) return; 330 331 // apply styles 332 args = args || {}; 333 $this.css({ 334 position: 'fixed', 335 top: args.top || 0, 336 left: args.left || $wrapper.offset().left 337 }).addClass(options.className); 338 339 // start event 340 options.onStart.apply($this[0]); 341 // add class to wrpaeer 342 $wrapper.addClass('sticky-active'); 343 }, 344 _reset = function(args) { 345 args = args || {}; 346 args.position = args.position || 'absolute'; 347 args.top = args.top || 0; 348 args.left = args.left || 0; 349 350 // check if we should apply css 351 if ($this.css('position') != 'fixed' && parseInt($this.css('top')) == args.top) return; 352 353 // apply styles 354 $this.css({ 355 position: args.position, 356 top: args.top, 357 left: args.left 358 }).removeClass(options.className); 359 360 // stop event 361 options.onStop.apply($this[0]); 362 // remove class from wrpaeer 363 $wrapper.removeClass('sticky-active'); 364 }; 365 366 // sticky scroll function 367 var onScroll = function(init) { 368 369 // check if we need to run sticky 370 if (!options.on || !$this.is(':visible')) return; 371 372 // 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 373 if ($this.outerHeight(true) >= $container.height()) { 374 _reset(); 375 376 return; 377 } 378 379 var top_spacing = (options.innerSticker) ? $(options.innerSticker).position().top : ((options.innerTop) ? options.innerTop : 0), 380 wrapper_inner_top = $wrapper.offset().top, 381 bottom_limit = $container.height() - options.bottomEnd + (stickTo_document ? 0 : wrapper_inner_top), 382 top_limit = $wrapper.offset().top - options.top + top_spacing, 383 this_height = $this.outerHeight(true) + options.bottom, 384 window_height = $window.height(), 385 offset_top = $window.scrollTop(), 386 this_document_top = $this.offset().top, 387 this_window_top = this_document_top - offset_top, 388 bottom_distance; 389 390 391 // if sticky has been restarted with on/off wait for it to reach top or bottom 392 if (typeof options.remember != 'undefined' && options.remember) { 393 394 var position_top = this_document_top - options.top - top_spacing; 395 396 if (this_height - top_spacing > window_height && options.followScroll) { // element bigger than window with follow scroll on 397 398 if (position_top < offset_top && offset_top + window_height <= position_top + $this.height()) { 399 // element is in the middle of the screen, let our primary calculations do the work 400 options.remember = false;
401 } 402 403 } else { // element smaller than window or follow scroll turned off 404 405 if (options.remember.offsetTop > position_top) { 406 // slide up 407 if (offset_top <= position_top) { 408 _setFixed({ 409 top: options.top - top_spacing 410 }); 411 options.remember = false; 412 } 413 } else { 414 // slide down 415 if (offset_top >= position_top) { 416 _setFixed({ 417 top: options.top - top_spacing 418 }); 419 options.remember = false; 420 } 421 } 422 423 } 424 425 return; 426 } 427 428 429 if (offset_top > top_limit) { 430 431 // http://geek-and-poke.com/geekandpoke/2012/7/27/simply-explained.html 432 433 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)) { 434 // bottom reached end 435 _reset({ 436 top: bottom_limit - this_height + options.bottom - wrapper_inner_top 437 }); 438 } else if (this_height - top_spacing > window_height && options.followScroll) { 439 440 if (this_window_top + this_height <= window_height) { // element bigger than window with follow scroll on 441 442 if (getScroll.direction == 'down') { 443 // scroll down 444 _setFixed({ 445 top: window_height - this_height 446 }); 447 } else { 448 // scroll up 449 if (this_window_top < 0 && $this.css('position') == 'fixed') { 450 _reset({ 451 top: this_document_top - (top_limit + options.top - top_spacing) - getScroll.distanceY 452 }); 453 } 454 } 455 456 } else { // element smaller than window or follow scroll turned off 457 458 if (getScroll.direction == 'up' && this_document_top >= offset_top + options.top - top_spacing) { 459 // scroll up 460 _setFixed({ 461 top: options.top - top_spacing 462 }); 463 } else if (getScroll.direction == 'down' && this_document_top + this_height > window_height && $this.css('position') == 'fixed') { 464 // scroll down 465 _reset({ 466 top: this_document_top - (top_limit + options.top - top_spacing) - getScroll.distanceY 467 }); 468 } 469 470 } 471 } else { 472 // starting (top) fixed position 473 _setFixed({ 474 top: options.top - top_spacing 475 }); 476 } 477 } else { 478 // reset 479 _reset(); 480 } 481 482 }; 483 484 485 // store resize data in case responsive is on 486 var resize_timeout = false, 487 $resize_clone = false;
488 489 var onResize = function() { 490 491 // check if sticky is attached to scroll event 492 attachScroll(); 493 494 // check for off resolutions 495 checkResolutions(); 496 497 // check if we need to run sticky 498 if (!options.on) return; 499 500 var setLeft = function(){ 501 // set new left position 502 if ($this.css('position') == 'fixed') { 503 $this.css('left', $wrapper.offset().left); 504 } else { 505 $this.css('left', 0); 506 } 507 }; 508 509 // check for width change (css media queries) 510 if (options.responsive) { 511 // clone element and make it invisible 512 if (!$resize_clone) { 513 $resize_clone = $this.clone().attr('style', '').css({ 514 visibility: 'hidden', 515 height: 0, 516 overflow: 'hidden', 517 paddingTop: 0, 518 paddingBottom: 0, 519 marginTop: 0, 520 marginBottom: 0 521 }); 522 $wrapper.after($resize_clone); 523 } 524 525 var wrapper_width = $wrapper.style('width'); 526 var resize_clone_width = $resize_clone.style('width'); 527 528 if (resize_clone_width == 'auto' && wrapper_width != 'auto') { 529 resize_clone_width = parseInt($this.css('width')); 530 } 531 532 // recalculate wrpaeer width 533 if (resize_clone_width != wrapper_width) { 534 $wrapper.width(resize_clone_width); 535 } 536 537 // clear previous timeout 538 if (resize_timeout) { 539 clearTimeout(resize_timeout); 540 } 541 // timedout destroing of cloned elements so we don't clone it again and again while resizing the window 542 resize_timeout = setTimeout(function() { 543 // clear timeout id 544 resize_timeout = false; 545 // destroy cloned element 546 $resize_clone.remove(); 547 $resize_clone = false; 548 }, 250); 549 } 550 551 // set new left position 552 setLeft(); 553 554 // recalculate inner element width (maybe original width was in %) 555 if ($this.outerWidth(true) != $wrapper.width()) { 556 var this_w = ($this.css('box-sizing') == 'border-box' || $this.css('-moz-box-sizing') == 'border-box') 557 ? $wrapper.width() 558 : $wrapper.width() - parseInt($this.css('padding-left')) - parseInt($this.css('padding-right')); 559 // subtract margins 560 this_w = this_w - parseInt($this.css('margin-left')) - parseInt($this.css('margin-right')); 561 // set new width 562 $this.css('width', this_w); 563 } 564 }; 565 566 567 // remember scroll and resize callbacks so we can attach and detach them 568 $this.pluginOptions('hcSticky', {fn: { 569 scroll: onScroll, 570 resize: onResize 571 }}); 572 573 574 // check for off resolutions 575 var checkResolutions = function(){ 576 if (options.offResolutions) { 577 // convert to array 578 if (!$.isArray(options.offResolutions)) { 579 options.offResolutions = [options.offResolutions]; 580 } 581 582 var isOn = true; 583 584 $.each(options.offResolutions, function(i, rez){ 585 if (rez < 0) { 586 // below 587 if ($window.width() < rez * -1) { 588 isOn = false;
589 $this.hcSticky('off'); 590 } 591 } else { 592 // abowe 593 if ($window.width() > rez) { 594 isOn = false; 595 $this.hcSticky('off'); 596 } 597 } 598 }); 599 600 // turn on again 601 if (isOn && !options.on) { 602 $this.hcSticky('on'); 603 } 604 } 605 }; 606 checkResolutions(); 607 608 609 // attach resize function to event 610 $window.on('resize', onResize); 611 612 613 // attaching scroll function to event 614 var attachScroll = function(){ 615 var isAttached = false; 616 if ($._data(window, 'events').scroll != undefined) { 617 $.each($._data(window, 'events').scroll, function(i, f){ 618 if (f.handler == options.fn.scroll) { 619 isAttached = true; 620 } 621 }); 622 } 623 if (!isAttached) { 624 // run it once to disable glitching 625 options.fn.scroll(true); 626 // attach function to scroll event only once 627 $window.on('scroll', options.fn.scroll); 628 } 629 }; 630 attachScroll(); 631 632 }); 633 } 634 }); 635 636})(jQuery, this); 637 638 639 640// jQuery HC-PluginOptions 641// ============= 642// Version: 1.0 643// Copyright: Some Web Media 644// Author: Some Web Guy 645// Author URL: http://twitter.com/some_web_guy 646// Website: http://someweblog.com/ 647// License: Released under the MIT License www.opensource.org/licenses/mit-license.php 648 649(function($, undefined) { 650 "use strict"; 651 652 $.fn.extend({ 653 654 pluginOptions: function(pluginName, defaultOptions, userOptions, commands) { 655 656 // create object to store data 657 if (!this.data(pluginName)) this.data(pluginName, {}); 658 659 // return options 660 if (pluginName && typeof defaultOptions == 'undefined') return this.data(pluginName).options; 661 662 // update 663 userOptions = userOptions || (defaultOptions || {}); 664 665 if (typeof userOptions == 'object' || userOptions === undefined) { 666 667 // options 668 return this.each(function(){ 669 var $this = $(this); 670 671 if (!$this.data(pluginName).options) { 672 // init our options and attach to element 673 $this.data(pluginName, {options: $.extend(defaultOptions, userOptions || {})}); 674 // attach commands if any 675 if (commands) { 676 $this.data(pluginName).commands = commands; 677 } 678 } else { 679 // update existing options 680 $this.data(pluginName, $.extend($this.data(pluginName), {options: $.extend($this.data(pluginName).options, userOptions || {})})); 681 } 682 }); 683 684 } else if (typeof userOptions == 'string') { 685 686 return this.each(function(){ 687 $(this).data(pluginName).commands[userOptions].call(this); 688 }); 689 690 } else { 691 692 return this; 693 694 } 695 696 } 697 698 }); 699 700})(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.