1/*! fixto - v0.3.0 - 2014-05-03 2* http://github.com/bbarakaci/fixto/*/ 3 4 5var fixto = (function ($, window, document) { 6 7 // Start Computed Style. Please do not modify this module here. Modify it from its own repo. See address below. 8 9 /*! Computed Style - v0.1.0 - 2012-07-19 10 * https://github.com/bbarakaci/computed-style 11 * Copyright (c) 2012 Burak Barakaci; Licensed MIT */ 12 var computedStyle = (function() { 13 var computedStyle = { 14 getAll : function(element){ 15 return document.defaultView.getComputedStyle(element); 16 }, 17 get : function(element, name){ 18 return this.getAll(element)[name]; 19 }, 20 toFloat : function(value){ 21 return parseFloat(value, 10) || 0; 22 }, 23 getFloat : function(element,name){ 24 return this.toFloat(this.get(element, name)); 25 }, 26 _getAllCurrentStyle : function(element) { 27 return element.currentStyle; 28 } 29 }; 30 31 if (document.documentElement.currentStyle) { 32 computedStyle.getAll = computedStyle._getAllCurrentStyle; 33 } 34 35 return computedStyle; 36 37 }()); 38 39 // End Computed Style. Modify whatever you want to. 40 41 var mimicNode = (function(){ 42 /* 43 Class Mimic Node 44 Dependency : Computed Style 45 Tries to mimick a dom node taking his styles, dimensions. May go to his repo if gets mature. 46 */ 47 48 function MimicNode(element) { 49 this.element = element; 50 extraclass = this.element.className.split(" ")[0]; 51 this.replacer = document.createElement('div'); 52 this.replacer.className = this.replacer.className + " " + extraclass + "-replacer replacer"; 53 this.replacer.style.visibility = 'hidden'; 54 this.hide(); 55 element.parentNode.insertBefore(this.replacer, element); 56 } 57 58 MimicNode.prototype = { 59 replace : function(){ 60 var rst = this.replacer.style; 61 var styles = computedStyle.getAll(this.element); 62 63 // rst.width = computedStyle.width(this.element) + 'px'; 64 // rst.height = this.element.offsetHeight + 'px'; 65 66 // Setting offsetWidth 67 rst.width = this._width(); 68 rst.height = this._height(); 69 70 // Adobt margins 71 rst.marginTop = styles.marginTop; 72 rst.marginBottom = styles.marginBottom; 73 rst.marginLeft = styles.marginLeft; 74 rst.marginRight = styles.marginRight; 75 76 // Adopt positioning 77 rst.cssFloat = styles.cssFloat; 78 rst.styleFloat = styles.styleFloat; //ie8; 79 rst.position = styles.position; 80 rst.top = styles.top; 81 rst.right = styles.right; 82 rst.bottom = styles.bottom; 83 rst.left = styles.left; 84 // rst.borderStyle = styles.borderStyle; 85 86 rst.display = styles.display; 87 88 }, 89 90 hide: function () { 91 this.replacer.style.display = 'none'; 92 }, 93 94 _width : function(){ 95 return this.element.getBoundingClientRect().width + 'px'; 96 }, 97 98 _widthOffset : function(){ 99 return this.element.offsetWidth + 'px'; 100 }, 101 102 _height : function(){ 103 return this.element.getBoundingClientRect().height + 'px'; 104 }, 105 106 _heightOffset : function(){ 107 return this.element.offsetHeight + 'px'; 108 }, 109 110 destroy: function () { 111 $(this.replacer).remove(); 112 113 // set properties to null to break references 114 for (var prop in this) { 115 if (this.hasOwnProperty(prop)) { 116 this[prop] = null; 117 } 118 } 119 } 120 }; 121 122 var bcr = document.documentElement.getBoundingClientRect(); 123 if(!bcr.width){ 124 MimicNode.prototype._width = MimicNode.prototype._widthOffset; 125 MimicNode.prototype._height = MimicNode.prototype._heightOffset; 126 } 127 128 return { 129 MimicNode:MimicNode, 130 computedStyle:computedStyle 131 }; 132 }()); 133 134 // Class handles vendor prefixes 135 function Prefix() { 136 // Cached vendor will be stored when it is detected 137 this._vendor = null; 138 139 //this._dummy = document.createElement('div'); 140 } 141 142 Prefix.prototype = { 143 144 _vendors: { 145 webkit: { cssPrefix: '-webkit-', jsPrefix: 'Webkit'}, 146 moz: { cssPrefix: '-moz-', jsPrefix: 'Moz'}, 147 ms: { cssPrefix: '-ms-', jsPrefix: 'ms'}, 148 opera: { cssPrefix: '-o-', jsPrefix: 'O'} 149 }, 150 151 _prefixJsProperty: function(vendor, prop) { 152 return vendor.jsPrefix + prop[0].toUpperCase() + prop.substr(1); 153 }, 154 155 _prefixValue: function(vendor, value) { 156 return vendor.cssPrefix + value; 157 }, 158 159 _valueSupported: function(prop, value, dummy) { 160 // IE8 will throw Illegal Argument when you attempt to set a not supported value. 161 try { 162 dummy.style[prop] = value; 163 return dummy.style[prop] === value; 164 } 165 catch(er) { 166 return false;
167 } 168 }, 169 170 /** 171 * Returns true if the property is supported 172 * @param {string} prop Property name 173 * @returns {boolean} 174 */ 175 propertySupported: function(prop) { 176 // Supported property will return either inine style value or an empty string. 177 // Undefined means property is not supported. 178 return document.documentElement.style[prop] !== undefined; 179 }, 180 181 /** 182 * Returns prefixed property name for js usage 183 * @param {string} prop Property name 184 * @returns {string|null} 185 */ 186 getJsProperty: function(prop) { 187 // Try native property name first. 188 if(this.propertySupported(prop)) { 189 return prop; 190 } 191 192 // Prefix it if we know the vendor already 193 if(this._vendor) { 194 return this._prefixJsProperty(this._vendor, prop); 195 } 196 197 // We don't know the vendor, try all the possibilities 198 var prefixed; 199 for(var vendor in this._vendors) { 200 prefixed = this._prefixJsProperty(this._vendors[vendor], prop); 201 if(this.propertySupported(prefixed)) { 202 // Vendor detected. Cache it. 203 this._vendor = this._vendors[vendor]; 204 return prefixed; 205 } 206 } 207 208 // Nothing worked 209 return null; 210 }, 211 212 /** 213 * Returns supported css value for css property. Could be used to check support or get prefixed value string. 214 * @param {string} prop Property 215 * @param {string} value Value name 216 * @returns {string|null} 217 */ 218 getCssValue: function(prop, value) { 219 // Create dummy element to test value 220 var dummy = document.createElement('div'); 221 222 // Get supported property name 223 var jsProperty = this.getJsProperty(prop); 224 225 // Try unprefixed value 226 if(this._valueSupported(jsProperty, value, dummy)) { 227 return value; 228 } 229 230 var prefixedValue; 231 232 // If we know the vendor already try prefixed value 233 if(this._vendor) { 234 prefixedValue = this._prefixValue(this._vendor, value); 235 if(this._valueSupported(jsProperty, prefixedValue, dummy)) { 236 return prefixedValue; 237 } 238 } 239 240 // Try all vendors 241 for(var vendor in this._vendors) { 242 prefixedValue = this._prefixValue(this._vendors[vendor], value); 243 if(this._valueSupported(jsProperty, prefixedValue, dummy)) { 244 // Vendor detected. Cache it. 245 this._vendor = this._vendors[vendor]; 246 return prefixedValue; 247 } 248 } 249 // No support for value 250 return null; 251 } 252 }; 253 254 var prefix = new Prefix(); 255 256 // We will need this frequently. Lets have it as a global until we encapsulate properly. 257 var transformJsProperty = prefix.getJsProperty('transform'); 258 259 // Will hold if browser creates a positioning context for fixed elements. 260 var fixedPositioningContext; 261 262 // Checks if browser creates a positioning context for fixed elements. 263 // Transform rule will create a positioning context on browsers who follow the spec. 264 // Ie for example will fix it according to documentElement 265 // TODO: Other css rules also effects. perspective creates at chrome but not in firefox. transform-style preserve3d effects. 266 function checkFixedPositioningContextSupport() { 267 var support = false; 268 var parent = document.createElement('div'); 269 var child = document.createElement('div'); 270 parent.appendChild(child); 271 parent.style[transformJsProperty] = 'translate(0)'; 272 // Make sure there is space on top of parent 273 parent.style.marginTop = '10px'; 274 parent.style.visibility = 'hidden'; 275 child.style.position = 'fixed'; 276 child.style.top = 0; 277 document.body.appendChild(parent); 278 var rect = child.getBoundingClientRect(); 279 // If offset top is greater than 0 meand transformed element created a positioning context. 280 if(rect.top > 0) { 281 support = true; 282 } 283 // Remove dummy content 284 document.body.removeChild(parent); 285 return support; 286 } 287 288 // It will return null if position sticky is not supported 289 var nativeStickyValue = prefix.getCssValue('position', 'sticky'); 290 291 // It will return null if position fixed is not supported 292 var fixedPositionValue = prefix.getCssValue('position', 'fixed'); 293 294 // Dirty business 295 var ie = navigator.appName === 'Microsoft Internet Explorer'; 296 var ieversion; 297 298 if(ie){ 299 ieversion = parseFloat(navigator.appVersion.split("MSIE")[1]); 300 } 301 302 function FixTo(child, parent, options) { 303 this.child = child; 304 this._$child = $(child); 305 this.parent = parent; 306 this.options = { 307 className: 'fixto-fixed', 308 top: 0 309 }; 310 this._setOptions(options); 311 } 312 313 FixTo.prototype = { 314 // Returns the total outerHeight of the elements passed to mind option. Will return 0 if none. 315 _mindtop: function () { 316 var top = 0; 317 if(this._$mind) { 318 var el; 319 var rect; 320 var height; 321 for(var i=0, l=this._$mind.length; i<l; i++) { 322 el = this._$mind[i]; 323 rect = el.getBoundingClientRect(); 324 if(rect.height) { 325 top += rect.height; 326 } 327 else { 328 var styles = computedStyle.getAll(el); 329 top += el.offsetHeight + computedStyle.toFloat(styles.marginTop) + computedStyle.toFloat(styles.marginBottom); 330 } 331 } 332 } 333 return top; 334 }, 335 336 // Public method to stop the behaviour of this instance. 337 stop: function () { 338 this._stop(); 339 this._running = false;
340 }, 341 342 // Public method starts the behaviour of this instance. 343 start: function () { 344 345 // Start only if it is not running not to attach event listeners multiple times. 346 if(!this._running) { 347 this._start(); 348 this._running = true; 349 } 350 }, 351 352 //Public method to destroy fixto behaviour 353 destroy: function () { 354 this.stop(); 355 356 this._destroy(); 357 358 // Remove jquery data from the element 359 this._$child.removeData('fixto-instance'); 360 361 // set properties to null to break references 362 for (var prop in this) { 363 if (this.hasOwnProperty(prop)) { 364 this[prop] = null; 365 } 366 } 367 }, 368 369 _setOptions: function(options) { 370 $.extend(this.options, options); 371 if(this.options.mind) { 372 this._$mind = $(this.options.mind); 373 } 374 if(this.options.zIndex) { 375 this.child.style.zIndex = this.options.zIndex; 376 } 377 }, 378 379 setOptions: function(options) { 380 this._setOptions(options); 381 this.refresh(); 382 }, 383 384 // Methods could be implemented by subclasses 385 386 _stop: function() { 387 388 }, 389 390 _start: function() { 391 392 }, 393 394 _destroy: function() { 395 396 }, 397 398 refresh: function() { 399 400 } 401 }; 402 403 // Class FixToContainer 404 function FixToContainer(child, parent, options) { 405 FixTo.call(this, child, parent, options); 406 this._replacer = new mimicNode.MimicNode(child); 407 this._ghostNode = this._replacer.replacer; 408 409 this._saveStyles(); 410
411 this._saveViewportHeight(); 412 413 // Create anonymous functions and keep references to register and unregister events. 414 this._proxied_onscroll = this._bind(this._onscroll, this); 415 this._proxied_onresize = this._bind(this._onresize, this); 416 417 this.start(); 418 } 419 420 FixToContainer.prototype = new FixTo(); 421 422 $.extend(FixToContainer.prototype, { 423 424 // Returns an anonymous function that will call the given function in the given context 425 _bind : function (fn, context) { 426 return function () { 427 return fn.call(context); 428 }; 429 }, 430 431 // at ie8 maybe only in vm window resize event fires everytime an element is resized. 432 _toresize : ieversion===8 ? document.documentElement : window, 433 434 _onscroll: function _onscroll() { 435 this._scrollTop = document.documentElement.scrollTop || document.body.scrollTop; 436 this._parentBottom = (this.parent.offsetHeight + this._fullOffset('offsetTop', this.parent)) - computedStyle.getFloat(this.parent, 'paddingBottom'); 437 if (!this.fixed) { 438 439 var childStyles = computedStyle.getAll(this.child); 440 441 if ( 442 this._scrollTop < this._parentBottom && 443 this._scrollTop > (this._fullOffset('offsetTop', this.child) - this.options.top - this._mindtop()) && 444 this._viewportHeight > (this.child.offsetHeight + computedStyle.toFloat(childStyles.marginTop) + computedStyle.toFloat(childStyles.marginBottom)) 445 ) { 446 447 this._fix(); 448 this._adjust(); 449 } 450 } else { 451 if (this._scrollTop > this._parentBottom || this._scrollTop < (this._fullOffset('offsetTop', this._ghostNode) - this.options.top - this._mindtop())) { 452 this._unfix(); 453 return; 454 } 455 this._adjust(); 456 } 457 }, 458 459 _adjust: function _adjust() { 460 var top = 0; 461 var mindTop = this._mindtop(); 462 var diff = 0; 463 var childStyles = computedStyle.getAll(this.child); 464 var context = null; 465 466 if(fixedPositioningContext) { 467 // Get positioning context. 468 context = this._getContext(); 469 if(context) { 470 // There is a positioning context. Top should be according to the context. 471 top = Math.abs(context.getBoundingClientRect().top); 472 } 473 } 474 475 diff = (this._parentBottom - this._scrollTop) - (this.child.offsetHeight + computedStyle.toFloat(childStyles.marginBottom) + mindTop + this.options.top); 476 477 if(diff>0) { 478 diff = 0; 479 } 480 481 this.child.style.top = (diff + mindTop + top + this.options.top) - computedStyle.toFloat(childStyles.marginTop) + 'px'; 482 }, 483 484 // Calculate cumulative offset of the element. 485 // Optionally according to context 486 _fullOffset: function _fullOffset(offsetName, elm, context) { 487 var offset = elm[offsetName]; 488 var offsetParent = elm.offsetParent; 489 490 // Add offset of the ascendent tree until we reach to the document root or to the given context 491 while (offsetParent !== null && offsetParent !== context) { 492 offset = offset + offsetParent[offsetName]; 493 offsetParent = offsetParent.offsetParent; 494 } 495 496 return offset; 497 }, 498 499 // Get positioning context of the element. 500 // We know that the closest parent that a transform rule applied will create a positioning context. 501 _getContext: function() { 502 var parent; 503 var element = this.child; 504 var context = null; 505 var styles; 506 507 // Climb up the treee until reaching the context 508 while(!context) { 509 parent = element.parentNode; 510 if(parent === document.documentElement) { 511 return null; 512 } 513 514 styles = computedStyle.getAll(parent); 515 // Element has a transform rule
516 if(styles[transformJsProperty] !== 'none') { 517 context = parent; 518 break; 519 } 520 element = parent; 521 } 522 return context; 523 }, 524 525 _fix: function _fix() { 526 var child = this.child; 527 var childStyle = child.style; 528 var childStyles = computedStyle.getAll(child); 529 var left = child.getBoundingClientRect().left; 530 var width = childStyles.width; 531 532 this._saveStyles(); 533 534 if(document.documentElement.currentStyle){ 535 // Function for ie<9. When hasLayout is not triggered in ie7, he will report currentStyle as auto, clientWidth as 0. Thus using offsetWidth. 536 // Opera also falls here 537 width = (child.offsetWidth) - (computedStyle.toFloat(childStyles.paddingLeft) + computedStyle.toFloat(childStyles.paddingRight) + computedStyle.toFloat(childStyles.borderLeftWidth) + computedStyle.toFloat(childStyles.borderRightWidth)) + 'px'; 538 } 539 540 // Ie still fixes the container according to the viewport. 541 if(fixedPositioningContext) { 542 var context = this._getContext(); 543 if(context) { 544 // There is a positioning context. Left should be according to the context. 545 left = child.getBoundingClientRect().left - context.getBoundingClientRect().left; 546 } 547 } 548 549 childStyle.left = (left - computedStyle.toFloat(childStyles.marginLeft)) + 'px'; 550 childStyle.width = width; 551 this._replacer.replace(); 552 childStyle.position = 'fixed'; 553 childStyle.top = this._mindtop() + this.options.top - computedStyle.toFloat(childStyles.marginTop) + 'px'; 554 this._$child.addClass(this.options.className); 555 this.fixed = true; 556 }, 557 558 _unfix: function _unfix() { 559 var childStyle = this.child.style; 560 this._replacer.hide(); 561 childStyle.position = this._childOriginalPosition; 562 childStyle.top = this._childOriginalTop; 563 childStyle.width = this._childOriginalWidth; 564 childStyle.left = this._childOriginalLeft; 565 this._$child.removeClass(this.options.className); 566 this.fixed = false; 567 }, 568 569 _saveStyles: function(){ 570 var childStyle = this.child.style; 571 this._childOriginalPosition = childStyle.position; 572 this._childOriginalTop = childStyle.top; 573 this._childOriginalWidth = childStyle.width; 574 this._childOriginalLeft = childStyle.left; 575 }, 576 577 _onresize: function () { 578 this.refresh(); 579 }, 580
581 _saveViewportHeight: function () { 582 // ie8 doesn't support innerHeight 583 this._viewportHeight = window.innerHeight || document.documentElement.clientHeight; 584 }, 585 586 _stop: function() { 587 // Unfix the container immediately. 588 this._unfix(); 589 // remove event listeners 590 $(window).unbind('scroll', this._proxied_onscroll); 591 $(this._toresize).unbind('resize', this._proxied_onresize); 592 }, 593 594 _start: function() { 595 // Trigger onscroll to have the effect immediately. 596 this._onscroll(); 597 598 // Attach event listeners 599 $(window).bind('scroll', this._proxied_onscroll); 600 $(this._toresize).bind('resize', this._proxied_onresize); 601 }, 602 603 _destroy: function() { 604 // Destroy mimic node instance 605 this._replacer.destroy(); 606 }, 607 608 refresh: function() { 609 this._saveViewportHeight(); 610 this._unfix(); 611 this._onscroll(); 612 } 613 }); 614 615 function NativeSticky(child, parent, options) { 616 FixTo.call(this, child, parent, options); 617 this.start(); 618 } 619 620 NativeSticky.prototype = new FixTo(); 621 622 $.extend(NativeSticky.prototype, { 623 _start: function() { 624 625 var childStyles = computedStyle.getAll(this.child); 626 627 this._childOriginalPosition = childStyles.position; 628 this._childOriginalTop = childStyles.top; 629 630 this.child.style.position = nativeStickyValue; 631 this.refresh(); 632 }, 633 634 _stop: function() { 635 this.child.style.position = this._childOriginalPosition; 636 this.child.style.top = this._childOriginalTop; 637 }, 638 639 refresh: function() { 640 this.child.style.top = this._mindtop() + this.options.top + 'px'; 641 } 642 }); 643 644 645 646 var fixTo = function fixTo(childElement, parentElement, options) { 647 if((nativeStickyValue && !options) || (nativeStickyValue && options && options.useNativeSticky !== false)) { 648 // Position sticky supported and user did not disabled the usage of it. 649 return new NativeSticky(childElement, parentElement, options); 650 } 651 else if(fixedPositionValue) { 652 // Position fixed supported 653 654 if(fixedPositioningContext===undefined) { 655 // We don't know yet if browser creates fixed positioning contexts. Check it. 656 fixedPositioningContext = checkFixedPositioningContextSupport(); 657 } 658 659 return new FixToContainer(childElement, parentElement, options); 660 } 661 else { 662 return 'Neither fixed nor sticky positioning supported'; 663 } 664 }; 665 666 /* 667 No support for ie lt 8 668 */ 669 670 if(ieversion<8){ 671 fixTo = function(){ 672 return 'not supported'; 673 }; 674 } 675 676 // Let it be a jQuery Plugin 677 $.fn.fixTo = function (targetSelector, options) { 678 679 var $targets = $(targetSelector); 680 681 var i = 0; 682 return this.each(function () { 683 684 // Check the data of the element. 685 var instance = $(this).data('fixto-instance'); 686 687 // If the element is not bound to an instance, create the instance and save it to elements data. 688 if(!instance) { 689 $(this).data('fixto-instance', fixTo(this, $targets[i], options)); 690 } 691 else { 692 // If we already have the instance here, expect that targetSelector parameter will be a string 693 // equal to a public methods name. Run the method on the instance without checking if 694 // it exists or it is a public method or not. Cause nasty errors when necessary. 695 var method = targetSelector; 696 instance[method].call(instance, options); 697 } 698 i++; 699 }); 700 }; 701 702 /* 703 Expose 704 */ 705 706 return { 707 FixToContainer: FixToContainer, 708 fixTo: fixTo, 709 computedStyle:computedStyle, 710 mimicNode:mimicNode 711 }; 712 713 714}(window.jQuery, window, document));
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.