1/* 2 * By Elementor Team 3 */ 4( function( $ ) { 5 var Sticky = function( element, userSettings ) { 6 var $element, 7 isSticky = false, 8 isFollowingParent = false, 9 isReachedEffectsPoint = false, 10 elements = {}, 11 settings; 12 13 var defaultSettings = { 14 to: 'top', 15 offset: 0, 16 effectsOffset: 0, 17 parent: false, 18 classes: { 19 sticky: 'sticky', 20 stickyActive: 'sticky-active', 21 stickyEffects: 'sticky-effects', 22 spacer: 'sticky-spacer', 23 }, 24 }; 25 26 var initElements = function() { 27 $element = $( element ).addClass( settings.classes.sticky ); 28 29 elements.$window = $( window ); 30 31 if ( settings.parent ) { 32 if ( 'parent' === settings.parent ) { 33 elements.$parent = $element.parent(); 34 } else { 35 elements.$parent = $element.closest( settings.parent ); 36 } 37 } 38 }; 39 40 var initSettings = function() { 41 settings = jQuery.extend( true, defaultSettings, userSettings ); 42 }; 43 44 var bindEvents = function() { 45 elements.$window.on( { 46 scroll: onWindowScroll, 47 resize: onWindowResize, 48 } ); 49 }; 50 51 var unbindEvents = function() { 52 elements.$window 53 .off( 'scroll', onWindowScroll ) 54 .off( 'resize', onWindowResize ); 55 }; 56 57 var init = function() { 58 initSettings(); 59 60 initElements(); 61 62 bindEvents(); 63 64 checkPosition(); 65 }; 66 67 var backupCSS = function( $elementBackupCSS, backupState, properties ) { 68 var css = {}, 69 elementStyle = $elementBackupCSS[ 0 ].style; 70 71 properties.forEach( function( property ) { 72 css[ property ] = undefined !== elementStyle[ property ] ? elementStyle[ property ] : ''; 73 } ); 74 75 $elementBackupCSS.data( 'css-backup-' + backupState, css ); 76 }; 77 78 var getCSSBackup = function( $elementCSSBackup, backupState ) { 79 return $elementCSSBackup.data( 'css-backup-' + backupState ); 80 }; 81 82 var addSpacer = function() { 83 elements.$spacer = $element.clone() 84 .addClass( settings.classes.spacer ) 85 .css( { 86 visibility: 'hidden', 87 transition: 'none', 88 animation: 'none', 89 } ); 90 91 $element.after( elements.$spacer ); 92 }; 93 94 var removeSpacer = function() { 95 elements.$spacer.remove(); 96 }; 97 98 var stickElement = function() { 99 backupCSS( $element, 'unsticky', [ 'position', 'width', 'margin-top', 'margin-bottom', 'top', 'bottom' ] ); 100 101 var css = { 102 position: 'fixed', 103 width: getElementOuterSize( $element, 'width' ), 104 marginTop: 0, 105 marginBottom: 0, 106 }; 107 108 css[ settings.to ] = settings.offset; 109 110 css[ 'top' === settings.to ? 'bottom' : 'top' ] = ''; 111 112 $element 113 .css( css ) 114 .addClass( settings.classes.stickyActive ); 115 }; 116 117 var unstickElement = function() { 118 $element 119 .css( getCSSBackup( $element, 'unsticky' ) ) 120 .removeClass( settings.classes.stickyActive ); 121 }; 122 123 var followParent = function() { 124 backupCSS( elements.$parent, 'childNotFollowing', [ 'position' ] ); 125 126 elements.$parent.css( 'position', 'relative' ); 127 128 backupCSS( $element, 'notFollowing', [ 'position', 'top', 'bottom' ] ); 129 130 var css = { 131 position: 'absolute', 132 }; 133 134 css[ settings.to ] = ''; 135 136 css[ 'top' === settings.to ? 'bottom' : 'top' ] = 0; 137 138 $element.css( css ); 139 140 isFollowingParent = true; 141 }; 142 143 var unfollowParent = function() { 144 elements.$parent.css( getCSSBackup( elements.$parent, 'childNotFollowing' ) ); 145 146 $element.css( getCSSBackup( $element, 'notFollowing' ) ); 147 148 isFollowingParent = false; 149 }; 150 151 var getElementOuterSize = function( $elementOuterSize, dimension, includeMargins ) { 152 var computedStyle = getComputedStyle( $elementOuterSize[ 0 ] ), 153 elementSize = parseFloat( computedStyle[ dimension ] ), 154 sides = 'height' === dimension ? [ 'top', 'bottom' ] : [ 'left', 'right' ], 155 propertiesToAdd = []; 156 157 if ( 'border-box' !== computedStyle.boxSizing ) { 158 propertiesToAdd.push( 'border', 'padding' ); 159 } 160 161 if ( includeMargins ) { 162 propertiesToAdd.push( 'margin' ); 163 } 164 165 propertiesToAdd.forEach( function( property ) { 166 sides.forEach( function( side ) { 167 elementSize += parseFloat( computedStyle[ property + '-' + side ] ); 168 } ); 169 } ); 170 171 return elementSize; 172 }; 173 174 var getElementViewportOffset = function( $elementViewportOffset ) { 175 var windowScrollTop = elements.$window.scrollTop(), 176 elementHeight = getElementOuterSize( $elementViewportOffset, 'height' ),
177 viewportHeight = innerHeight, 178 elementOffsetFromTop = $elementViewportOffset.offset().top, 179 distanceFromTop = elementOffsetFromTop - windowScrollTop, 180 topFromBottom = distanceFromTop - viewportHeight; 181 182 return { 183 top: { 184 fromTop: distanceFromTop, 185 fromBottom: topFromBottom, 186 }, 187 bottom: { 188 fromTop: distanceFromTop + elementHeight, 189 fromBottom: topFromBottom + elementHeight, 190 }, 191 }; 192 }; 193 194 var stick = function() { 195 addSpacer(); 196 197 stickElement(); 198 199 isSticky = true; 200 201 $element.trigger( 'sticky:stick' ); 202 }; 203 204 var unstick = function() { 205 unstickElement(); 206 207 removeSpacer(); 208 209 isSticky = false; 210 211 $element.trigger( 'sticky:unstick' ); 212 }; 213 214 var checkParent = function() { 215 var elementOffset = getElementViewportOffset( $element ), 216 isTop = 'top' === settings.to; 217 218 if ( isFollowingParent ) { 219 var isNeedUnfollowing = isTop ? elementOffset.top.fromTop > settings.offset : elementOffset.bottom.fromBottom < -settings.offset; 220 221 if ( isNeedUnfollowing ) { 222 unfollowParent(); 223 } 224 } else { 225 var parentOffset = getElementViewportOffset( elements.$parent ), 226 parentStyle = getComputedStyle( elements.$parent[ 0 ] ), 227 borderWidthToDecrease = parseFloat( parentStyle[ isTop ? 'borderBottomWidth' : 'borderTopWidth' ] ), 228 parentViewportDistance = isTop ? parentOffset.bottom.fromTop - borderWidthToDecrease : parentOffset.top.fromBottom + borderWidthToDecrease, 229 isNeedFollowing = isTop ? parentViewportDistance <= elementOffset.bottom.fromTop : parentViewportDistance >= elementOffset.top.fromBottom; 230 231 if ( isNeedFollowing ) { 232 followParent(); 233 } 234 } 235 }; 236 237 var checkEffectsPoint = function( distanceFromTriggerPoint ) { 238 if ( isReachedEffectsPoint && -distanceFromTriggerPoint < settings.effectsOffset ) { 239 $element.removeClass( settings.classes.stickyEffects ); 240 241 isReachedEffectsPoint = false; 242 } else if ( ! isReachedEffectsPoint && -distanceFromTriggerPoint >= settings.effectsOffset ) { 243 $element.addClass( settings.classes.stickyEffects ); 244 245 isReachedEffectsPoint = true; 246 } 247 }; 248 249 var checkPosition = function() { 250 var offset = settings.offset, 251 distanceFromTriggerPoint; 252 253 if ( isSticky ) { 254 var spacerViewportOffset = getElementViewportOffset( elements.$spacer ); 255 256 distanceFromTriggerPoint = 'top' === settings.to ? spacerViewportOffset.top.fromTop - offset : -spacerViewportOffset.bottom.fromBottom - offset; 257 258 if ( settings.parent ) { 259 checkParent(); 260 } 261 262 if ( distanceFromTriggerPoint > 0 ) { 263 unstick(); 264 } 265 } else { 266 var elementViewportOffset = getElementViewportOffset( $element ); 267 268 distanceFromTriggerPoint = 'top' === settings.to ? elementViewportOffset.top.fromTop - offset : -elementViewportOffset.bottom.fromBottom - offset; 269 270 if ( distanceFromTriggerPoint <= 0 ) { 271 stick(); 272 273 if ( settings.parent ) { 274 checkParent(); 275 } 276 } 277 } 278 279 checkEffectsPoint( distanceFromTriggerPoint ); 280 }; 281 282 var onWindowScroll = function() { 283 checkPosition(); 284 }; 285 286 var onWindowResize = function() { 287 if ( ! isSticky ) { 288 return; 289 } 290 291 unstickElement(); 292 293 stickElement(); 294 295 if ( settings.parent ) { 296 // Force recalculation of the relation between the element and its parent 297 isFollowingParent = false; 298 299 checkParent(); 300 } 301 }; 302 303 this.destroy = function() { 304 if ( isSticky ) { 305 unstick(); 306 } 307 308 unbindEvents(); 309 310 $element.removeClass( settings.classes.sticky ); 311 }; 312 313 init(); 314 }; 315 316 $.fn.sticky = function( settings ) { 317 var isCommand = 'string' === typeof settings; 318 319 this.each( function() { 320 var $this = $( this ); 321 322 if ( ! isCommand ) { 323 $this.data( 'sticky', new Sticky( this, settings ) ); 324 325 return; 326 } 327 328 var instance = $this.data( 'sticky' ); 329 330 if ( ! instance ) { 331 throw Error( 'Trying to perform the `' + settings + '` method prior to initialization' ); 332 } 333 334 if ( ! instance[ settings ] ) { 335 throw ReferenceError( 'Method `' + settings + '` not found in sticky instance' ); 336 } 337 338 instance[ settings ].apply( instance, Array.prototype.slice.call( arguments, 1 ) ); 339 340 if ( 'destroy' === settings ) { 341 $this.removeData( 'sticky' ); 342 } 343 } ); 344 345 return this; 346 }; 347 348 window.Sticky = Sticky; 349} )( 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.