fixedsticky.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. ;(function( win, $ ) {
  2. function featureTest( property, value, noPrefixes ) {
  3. // Thanks Modernizr! https://github.com/phistuck/Modernizr/commit/3fb7217f5f8274e2f11fe6cfeda7cfaf9948a1f5
  4. var prop = property + ':',
  5. el = document.createElement( 'test' ),
  6. mStyle = el.style;
  7. if( !noPrefixes ) {
  8. mStyle.cssText = prop + [ '-webkit-', '-moz-', '-ms-', '-o-', '' ].join( value + ';' + prop ) + value + ';';
  9. } else {
  10. mStyle.cssText = prop + value;
  11. }
  12. return mStyle[ property ].indexOf( value ) !== -1;
  13. }
  14. function getPx( unit ) {
  15. return parseInt( unit, 10 ) || 0;
  16. }
  17. var uniqueIdCounter = 0;
  18. var S = {
  19. classes: {
  20. plugin: 'fixedsticky',
  21. active: 'fixedsticky-on',
  22. inactive: 'fixedsticky-off',
  23. clone: 'fixedsticky-dummy',
  24. withoutFixedFixed: 'fixedsticky-withoutfixedfixed'
  25. },
  26. keys: {
  27. offset: 'fixedStickyOffset',
  28. position: 'fixedStickyPosition',
  29. id: 'fixedStickyId'
  30. },
  31. tests: {
  32. sticky: featureTest( 'position', 'sticky' ),
  33. fixed: featureTest( 'position', 'fixed', true )
  34. },
  35. // Thanks jQuery!
  36. getScrollTop: function() {
  37. var prop = 'pageYOffset',
  38. method = 'scrollTop';
  39. return win ? (prop in win) ? win[ prop ] :
  40. win.document.documentElement[ method ] :
  41. win.document.body[ method ];
  42. },
  43. bypass: function() {
  44. // Check native sticky, check fixed and if fixed-fixed is also included on the page and is supported
  45. return ( S.tests.sticky && !S.optOut ) ||
  46. !S.tests.fixed ||
  47. win.FixedFixed && !$( win.document.documentElement ).hasClass( 'fixed-supported' );
  48. },
  49. update: function( el ) {
  50. if( !el.offsetWidth ) { return; }
  51. var $el = $( el ),
  52. height = $el.outerHeight(),
  53. initialOffset = $el.data( S.keys.offset ),
  54. scroll = S.getScrollTop(),
  55. isAlreadyOn = $el.is( '.' + S.classes.active ),
  56. toggle = function( turnOn ) {
  57. $el[ turnOn ? 'addClass' : 'removeClass' ]( S.classes.active )
  58. [ !turnOn ? 'addClass' : 'removeClass' ]( S.classes.inactive );
  59. },
  60. viewportHeight = $( window ).height(),
  61. position = $el.data( S.keys.position ),
  62. skipSettingToFixed,
  63. elTop,
  64. elBottom,
  65. $parent = $el.parent(),
  66. parentOffset = $parent.offset().top,
  67. parentHeight = $parent.outerHeight();
  68. if( initialOffset === undefined ) {
  69. initialOffset = $el.offset().top;
  70. $el.data( S.keys.offset, initialOffset );
  71. $el.after( $( '<div>' ).addClass( S.classes.clone ).height( height ) );
  72. } else {
  73. $el.next( '.' + S.classes.clone ).height( height );
  74. }
  75. if( !position ) {
  76. // Some browsers require fixed/absolute to report accurate top/left values.
  77. skipSettingToFixed = $el.css( 'top' ) !== 'auto' || $el.css( 'bottom' ) !== 'auto';
  78. if( !skipSettingToFixed ) {
  79. $el.css( 'position', 'fixed' );
  80. }
  81. position = {
  82. top: $el.css( 'top' ) !== 'auto',
  83. bottom: $el.css( 'bottom' ) !== 'auto'
  84. };
  85. if( !skipSettingToFixed ) {
  86. $el.css( 'position', '' );
  87. }
  88. $el.data( S.keys.position, position );
  89. }
  90. function isFixedToTop() {
  91. var offsetTop = scroll + elTop;
  92. // Initial Offset Top
  93. return initialOffset < offsetTop &&
  94. // Container Bottom
  95. offsetTop + height <= parentOffset + parentHeight;
  96. }
  97. function isFixedToBottom() {
  98. // Initial Offset Top + Height
  99. return initialOffset + ( height || 0 ) > scroll + viewportHeight - elBottom &&
  100. // Container Top
  101. scroll + viewportHeight - elBottom >= parentOffset + ( height || 0 );
  102. }
  103. elTop = getPx( $el.css( 'top' ) );
  104. elBottom = getPx( $el.css( 'bottom' ) );
  105. if( position.top && isFixedToTop() || position.bottom && isFixedToBottom() ) {
  106. if( !isAlreadyOn ) {
  107. toggle( true );
  108. }
  109. } else {
  110. if( isAlreadyOn ) {
  111. toggle( false );
  112. }
  113. }
  114. },
  115. destroy: function( el ) {
  116. var $el = $( el );
  117. if (S.bypass()) {
  118. return $el;
  119. }
  120. return $el.each(function() {
  121. var $this = $( this );
  122. var id = $this.data( S.keys.id );
  123. $( win ).unbind( '.fixedsticky' + id );
  124. $this
  125. .removeData( [ S.keys.offset, S.keys.position, S.keys.id ] )
  126. .removeClass( S.classes.active )
  127. .removeClass( S.classes.inactive )
  128. .next( '.' + S.classes.clone ).remove();
  129. });
  130. },
  131. init: function( el ) {
  132. var $el = $( el );
  133. if( S.bypass() ) {
  134. return $el;
  135. }
  136. return $el.each(function() {
  137. var _this = this;
  138. var id = uniqueIdCounter++;
  139. $( this ).data( S.keys.id, id );
  140. $( win ).bind( 'scroll.fixedsticky' + id, function() {
  141. S.update( _this );
  142. }).trigger( 'scroll.fixedsticky' + id );
  143. $( win ).bind( 'resize.fixedsticky' + id , function() {
  144. if( $el.is( '.' + S.classes.active ) ) {
  145. S.update( _this );
  146. }
  147. });
  148. });
  149. }
  150. };
  151. win.FixedSticky = S;
  152. // Plugin
  153. $.fn.fixedsticky = function( method ) {
  154. if ( typeof S[ method ] === 'function') {
  155. return S[ method ].call( S, this);
  156. } else if ( typeof method === 'object' || ! method ) {
  157. return S.init.call( S, this );
  158. } else {
  159. throw new Error( 'Method `' + method + '` does not exist on jQuery.fixedsticky' );
  160. }
  161. };
  162. // Add fallback when fixed-fixed is not available.
  163. if( !win.FixedFixed ) {
  164. $( win.document.documentElement ).addClass( S.classes.withoutFixedFixed );
  165. }
  166. })( window, jQuery );