1/** 2 * UpSolution Element: Page Scroller. 3 */ 4;( function( $, _undefined ) { 5 "use strict"; 6 7 const _window = window; 8 const _document = document; 9 10 const abs = Math.abs; 11 12 /** 13 * Function call after 0.5s 14 * 15 * @debounced 16 */ 17 const __debounce_fn_500ms = $ush.debounce( $ush.fn, /* wait */500 ); 18 19 $us.PageScroller = function( container, options ) { 20 this.init( container, options ); 21 }; 22 23 $us.PageScroller.prototype = { 24 init: function( container, options ) { 25 var defaults = { 26 coolDown: 100, 27 /** 28 * @param {Number} Duration of scroll animation. 29 */ 30 animationDuration: 1000, 31 /** 32 * @param {String} Easing for scroll animation. 33 */ 34 animationEasing: $us.getAnimationName( 'easeInOutExpo' ), 35 36 /** 37 * @param {String} End easing for scroll animation. 38 */ 39 endAnimationEasing: $us.getAnimationName( 'easeOutExpo' ), 40 }; 41 42 this.options = $.extend( {}, defaults, options ); 43 44 this.$container = $( container ); 45 this.activeSection = 0; 46 this.sections = []; 47 48 this.initialSections = []; 49 this.hiddenSections = []; 50 this.currHidden = []; 51 this.dots = []; 52 this.scrolls = []; 53 this.usingDots = false; 54 this.footerReveal = $us.$body.hasClass( 'footer_reveal' ); 55 this.isTouch = ( ( 'ontouchstart' in _window ) || ( navigator.msMaxTouchPoints > 0 ) || ( navigator.maxTouchPoints ) ); 56 this.disableWidth = ( this.$container.data( 'disablewidth' ) !== _undefined ) ? this.$container.data( 'disablewidth' ) : 768; 57 this.hiddenClasses = { 58 // Ultimate Addons classes and width limits. 59 'uvc_hidden-xs': [0, 479], 60 'uvc_hidden-xsl': [480, 767], 61 'uvc_hidden-sm': [768, 991], 62 'uvc_hidden-md': [992, 1199], 63 'uvc_hidden-ml': [1200, 1823], 64 'uvc_hidden-lg': [1824, 99999], // 99999 max screen resolution. 65 66 // WPBakery classes 67 'vc_hidden-xs': [0, 767], 68 'vc_hidden-sm': [768, 991], 69 'vc_hidden-md': [992, 1199], 70 'vc_hidden-lg': [1200, 99999], // 99999 max screen resolution. 71 72 }; 73 74 if ( this.$container.data( 'speed' ) !== _undefined ) { 75 this.options.animationDuration = this.$container.data( 'speed' ); 76 } 77 78 // Bindable events. 79 this._events = { 80 destroy: this._destroy.bind( this ), 81 mouseWheelHandler: this._mouseWheelHandler.bind( this ), 82 resize: this.resize.bind( this ), 83 scroll: this.scroll.bind( this ) 84 }; 85 86 this._attachEvents(); 87 88 // Destroying all dependencies when deleting an element in the usbuilder 89 this.$container 90 .on( 'usb.removeHtml', this._events.destroy ) 91 92 $us.$canvas 93 .on( 'contentChange', $ush.debounce( this._events.resize, 5 ) ); 94 95 /** 96 * @type debounced 97 */ 98 var _resize = $ush.debounce( this._events.resize, 50 ); 99 100 // Pass stop param from Page Scroller not to execute this handler 101 $us.$window 102 .on( 'scroll.noPreventDefault', $ush.debounce( this._events.scroll, 5 ) ) 103 .on( 'resize', ( _, stopExecute ) => { 104 if ( ! stopExecute ) { 105 _resize(); 106 } 107 } ); 108 109 // Late init not to load things twice 110 $ush.timeout( this._init.bind( this ), 100 ); 111 }, 112 113 is_popup: function() { 114 // Detect popup not to trigger extra events. 115 return $us.$html.hasClass( 'us_popup_is_opened' ); 116 }, 117 118 /** 119 * Add dynamic breakpoints using Theme options 120 */ 121 _add_dynamic_breakpoints: function() { 122 var custom_breakpoints = $us.responsiveBreakpoints, 123 default_breakpoints = { 124 'hide_on_mobiles': [0, 767], 125 'hide_on_tablets': [768, 991], 126 'hide_on_laptops': [992, 1199], 127 'hide_on_default': [1200, 99999], 128 }; 129 130 if ( custom_breakpoints ) { 131 default_breakpoints = { 132 'hide_on_mobiles': [0, custom_breakpoints.mobiles], 133 'hide_on_tablets': [( custom_breakpoints.mobiles + 1 ), custom_breakpoints.tablets], 134 'hide_on_laptops': [( custom_breakpoints.tablets + 1 ), custom_breakpoints.laptops], 135 'hide_on_default': [( custom_breakpoints.laptops + 1 ), 9999] 136 }; 137 } 138 this.hiddenClasses = Object.assign( this.hiddenClasses, default_breakpoints ); 139 }, 140 141 _init: function() { 142 // Add header, only when it's not sticky and not transparent, because it occupies part of the screen in 143 // that case. 144 if ( $us.header.isStatic() && $us.header.isHorizontal() && ! $us.header.isTransparent
144() ) { 145 $us.canvas.$header.each( function() { 146 var $section = $us.canvas.$header, 147 section = { 148 $section: $section, 149 area: 'header', 150 }; 151 this._countPosition( section ); 152 this.sections.push( section ); 153 this.initialSections.push( section ); 154 }.bind( this ) ); 155 } 156 157 // Add dynamic breakpoints from Theme options 158 this._add_dynamic_breakpoints(); 159 160 // Adding canvas sections. 161 $( '.l-main > .l-section, .l-footer > .l-section', $us.$canvas ).each( function( key, elm ) { 162 var $section = $( elm ), 163 section = { 164 $section: $section, 165 hiddenBoundaries: [], 166 area: 'content', 167 isSticky: $section.hasClass( 'type_sticky' ) 168 }, 169 addedWidths = []; 170 171 // Handle hidden sections. 172 hidden: 173 for ( var i in this.hiddenClasses ) { 174 if ( this.hiddenClasses.hasOwnProperty( i ) ) { 175 var low = this.hiddenClasses[ i ][ 0 ], 176 high = this.hiddenClasses[ i ][ 1 ]; 177 if ( $section.hasClass( i ) ) { 178 var addedWidthLength = addedWidths.length, 179 j; 180 181 // Save added sections to exclude doubles. 182 addedWidths.push( [low, high] ); 183 184 // Exclude repeating widths. 185 for ( j = 0; j < addedWidthLength; j ++ ) { 186 if ( addedWidths[ j ][ 0 ] === low && addedWidths[ j ][ 1 ] === high ) { 187 break hidden; 188 } 189 } 190 section.hiddenBoundaries.push( [low, high] ); 191 // Avoid doubles 192 if ( this.hiddenSections.indexOf( key ) === - 1 ) { 193 this.hiddenSections.push( key ); 194 } 195 196 } 197 } 198 } 199 200 this._countPosition( section, key ); 201 this.sections.push( section ); 202 this.initialSections.push( section ); 203 }.bind( this ) ); 204 205 // Save last content section for reveal footer sections. 206 this.lastContentSectionIndex = this.sections.length - 1; 207 208 // Adding footer sections 209 $( '.l-footer > .l-section' ).each( function( key, elm ) { 210 var $section = $( elm ), 211 section = { 212 $section: $section, 213 area: 'footer', 214 isSticky: $section.hasClass( 'type_sticky' ) 215 }; 216 this._countPosition( section, key ); 217 this.sections.push( section ); 218 this.initialSections.push( section ); 219 }.bind( this ) ); 220 221 // Resize after load 222 $ush.timeout( this.resize.bind( this ), 100 ); 223 224 // Adding dots for canvas sections. 225 this.$dotsContainer = this.$container.find( '.w-scroller-dots' ); 226 if ( this.$dotsContainer.length ) { 227 this.usingDots = true; 228 229 this.$firstDot = this.$dotsContainer.find( '.w-scroller-dot' ).first(); 230 this.redrawDots( true ); 231 // Initialize scroll to determine the position of dots. 232 this.scroll(); 233 } 234 235 }, 236 237 /** 238 * Destroying all dependencies 239 * 240 * @event 241 */ 242 _destroy: function() { 243 var self = this; 244 $us.$document.off( 'mousewheel DOMMouseScroll MozMousePixelScroll' ); 245 _document.removeEventListener( 'mousewheel', self._events.mouseWheelHandler ); 246 _document.removeEventListener( 'DOMMouseScroll', self._events.mouseWheelHandler ); 247 _document.removeEventListener( 'MozMousePixelScroll', self._events.mouseWheelHandler ); 248 $us.$canvas.off( 'touchstart touchmove' ); 249 }, 250 251 isSectionHidden: function( section ) { 252 if ( ! this.initialSections[ section ].hiddenBoundaries || ! this.initialSections[ section ].hiddenBoundaries.length ) { 253 return false; 254 } 255 var currWidth = _window.innerWidth, 256 isHidden = false; 257 258 for ( var i = 0; i < this.initialSections[ section ].hiddenBoundaries.length; i ++ ) { 259 var low = this.initialSections[ section ].hiddenBoundaries[ i ][ 0 ], 260 high = this.initialSections[ section ].hiddenBoundaries[ i ][ 1 ]; 261 262 if ( currWidth >= low && currWidth <= high ) { 263 isHidden = true; 264 break; 265 } 266 } 267 268 return isHidden; 269 }, 270 271 redrawDots: function( inited ) { 272 if ( ! this.usingDots || ! this.$dotsContainer || ! this.$dotsContainer.length ) { 273 return false; 274 } 275 // Clean up dots container. 276 this.$dotsContainer.html( '' ); 277 278 for ( var i = 0; i < this.sections.length; i ++ ) { 279 // Don't add dots for footer sections 280 if ( this.sections[ i ].area === 'footer' && ! this.$container.data( 'footer-dots' ) ) { 281 continue; 282 } 283 this.$firstDot.clone().appendTo( this.$dotsContainer ); 284 } 285 286 this.$dots = this.$dotsContainer.find( '.w-scroller-dot' ); 287 this.$dots.each( function( key, elm ) { 288 var $dot = $( elm ); 289 this.dots[ key ] = $dot; 290 $dot 291 .click( function() { 292 this.scrollTo( key ); 293 this.$dots.removeClass( 'active' ); 294 $dot.addClass( 'active' ); 295 }.bind( this ) ) 296 // Control of the number of points. 297 .toggleClass( 'hidden', this.sections[ key ].isSticky && $us.$window.width() > $us.canvas.options.columnsStackingWidth ); 298 }.bind( this ) ); 299 if ( !! inited && this.dots[ this.activeSection ] ) { 300 this.dots[ this.activeSection ].addClass( 'active' ); 301 } 302 this.$dotsContainer.addClass( 'show' ); 303 }, 304 305 recountSections: function() { 306 if ( this.currHidden ) { 307 // Set sections to initial state to extract hidden. 308 for ( var initialSection in this.initialSections ) { 309 this.sections[ initialSection ] = this.initialSections[ initialSection ]; 310 } 311 } 312 313 // Loop backward to don't mess with the indexes. 314 for ( var i = this.hiddenSections.length - 1; i >= 0; i -- ) { 315 var indexOfTheItem = this.currHidden.indexOf( this.hiddenSections[ i ] ); 316 317 if ( this.isSectionHidden( this.hiddenSections[ i ] ) ) { 318 // Add to currently hidden if it wasn't added before. 319 if ( indexOfTheItem === - 1 ) { 320 this.currHidden.push( this.hiddenSections[ i ] ); 321 } 322 this.sections.splice( this.hiddenSections[ i ], 1 ); 323 } else { 324 this.currHidden.splice( indexOfTheItem, 1 ); 325 } 326 } 327 328 this.redrawDots( true /* Set active section after resize */ ); 329 }, 330 331 getScrollSpeed: function( number ) { 332 var sum = 0, 333 lastElements = this.scrolls.slice( Math.max( this.scrolls.length - number, 1 ) ); 334 335 for ( var i = 0; i < lastElements.length; i ++ ) { 336 sum = sum + lastElements[ i ]; 337 } 338 339 return Math.ceil( sum / number ); 340 }, 341 342 /** 343 * Wheel scroll handler 344 * 345 * @event 346 * @param {Event} e 347 */ 348 _mouseWheelHandler: function( e ) { 349 const self = this; 350 if ( 351 $us.usbPreview() // Skip events for builder in preview mode 352 || self.is_popup() // Cancel processing if a modal window is open on the page 353 ) { 354 return; 355 } 356 357 e.preventDefault(); 358 var currentTime = $ush.time(), 359 target = self.activeSection, 360 direction = e.wheelDelta || - e.detail, 361 speedEnd, speedMiddle, isAccelerating; 362 363 if ( self.scrolls.length > 149 ) { 364 self.scrolls.shift(); 365 } 366 self.scrolls.push( abs( direction ) ); 367 368 if ( ( currentTime - self.previousMouseWheelTime ) > self.options.coolDown ) { 369 self.scrolls = []; 370 } 371 self.previousMouseWheelTime = currentTime; 372 373 speedEnd = self.getScrollSpeed( 10 ); 374 speedMiddle = self.getScrollSpeed( 70 ); 375 isAccelerating = speedEnd >= speedMiddle; 376 377 if ( isAccelerating ) { 378 if ( direction < 0 ) { 379 target ++; 380 } else if ( direction > 0 ) { 381 target --; 382 } 383 if ( self.sections[ target ] === _undefined ) { 384 return; 385 } 386 self.scrollTo( target ); 387 self.lastScroll = currentTime; 388 } 389 }, 390 391 _attachEvents: function() { 392 const self = this; 393 394 self._destroy(); 395 396 if ( 397 $us.$window.width() > self.disableWidth 398 && $us.mobileNavOpened <= 0 399 && ! $us.$html.hasClass( 'cloverlay_fixed' ) 400 ) { 401 _document.addEventListener( 'mousewheel', self._events.mouseWheelHandler, { passive: false } ); 402 _document.addEventListener( 'DOMMouseScroll', self._events.mouseWheelHandler, { passive: false } ); 403 _document.addEventListener( 'MozMousePixelScroll', self._events.mouseWheelHandler, { passive: false } ); 404 405 if ( $.isMobile || self.isTouch ) { 406 407 self.touchStartPageY = 0; 408 self.touchClientX = 0; 409 self.touchClientY = 0; 410 411 $us.$canvas.on( 'touchstart.noPreventDefault', ( e ) => { 412 const originalEvent = e.originalEvent; 413 if ( $ush.isUndefined( originalEvent.pointerType ) || originalEvent.pointerType !== 'mouse' ) { 414 self.touchStartPageY = originalEvent.touches[0].pageY; 415 self.touchClientX = originalEvent.touches[0].clientX; 416 self.touchClientY = originalEvent.touches[0].clientY; 417 } 418 } ); 419 420 $us.$canvas.on( 'touchmove', function( e ) { 421 422 e.preventDefault(); 423 424 const time = $ush.time(); 425 const originalEvent = e.originalEvent; 426 const touchEndPageY = originalEvent.touches[0].pageY; 427 428 var currentX = originalEvent.touches[0].clientX, 429 currentY = originalEvent.touches[0].clientY, 430 diffX = self.touchClientX - currentX, 431 diffY = self.touchClientY - currentY; 432 // Swipe left or right to exit. 433 if ( abs( diffX ) > abs( diffY ) ) { 434 return; 435 } 436 self.touchClientX = currentX; 437 self.touchClientY = currentY; 438 439 var target = self.activeSection; 440 441 if ( abs( self.touchStartPageY - touchEndPageY ) > ( $us.$window.height() / 50 ) ) { 442 if ( self.touchStartPageY > touchEndPageY ) { 443 target ++; 444 } else if ( touchEndPageY > self.touchStartPageY ) { 445 target --; 446 } 447 if ( $ush.isUndefined( self.sections[ target ] ) ) { 448 return; 449 } 450 self.scrollTo( target ); 451 self.lastScroll = time; 452 } 453 } ); 454 } 455 } 456 }, 457 458 _countPosition: function( section, key ) { 459 section.top = section.$section.offset().top - $us.canvas.getOffsetTop(); 460 // Count footer reveal sections separately 461 if ( this.footerReveal && section.area === 'footer' && key !== _undefined ) { 462 if ( _window.innerWidth > parseInt( $us.canvasOptions.columnsStackingWidth ) - 1 ) { 463 464 if ( this.sections[ key - 1 ] !== _undefined && this.sections[ key - 1 ].area === 'footer' ) { 465 // Takes previous footer section coordinates, works after init when resize happens 466 section.top = this.sections[ key - 1 ].bottom; 467 } else { 468 var rowIndex = ( this.sections[ this.lastContentSectionIndex + key ] !== _undefined ) 469 ? this.lastContentSectionIndex + key 470 : key - 1; 471 section.top = this.sections[ rowIndex ].bottom; 472 } 473 } 474 } 475 section.bottom = section.top + section.$section.outerHeight( false ); 476 }, 477 478 _countAllPositions: function() { 479 var counter = 0; 480 for ( var section in this.sections ) { 481 if ( this.sections[ section ].$section.length ) { 482 this._countPosition( this.sections[ section ], counter ); 483 } 484 counter ++; 485 } 486 }, 487 488 scrollTo: function( target ) { 489 var currentTime = $ush.time(); 490 if ( this.previousScrollTime !== _undefined && ( currentTime - this.previousScrollTime < this.options.animationDuration ) ) { 491 return; 492 } 493 this.previousScrollTime = currentTime; 494 495 // For dots points from sticky block. 496 if ( this.sections[ target ].isSticky && $us.$window.width() > $us.canvas.options.columnsStackingWidth ) { 497 if ( target > this.activeSection ) { 498 target += 1; 499 } else { 500 target -= 1; 501 } 502 } 503 504 if ( this.usingDots ) { 505 this.$dots.removeClass( 'active' ); 506 if ( this.dots[ target ] !== _undefined ) { 507 this.dots[ target ].addClass( 'active' ); 508 } 509 } 510 511 // For a header that has sticky and auto-hide enabled, add the height of the header when scrolling to the 512 // bottom, this will allow not to recalculate the position of the page section when hide header. 513 var top = Math.ceil( this.sections[ target ][ 'top' ] || 0 ); 514 515 if ( top === Math.ceil( $us.header.getScrollTop() ) ) { 516 return; 517 } 518 519 // Animate options 520 var animateOptions = { 521 duration: this.options.animationDuration, 522 easing: this.options.animationEasing, 523 start: function() { 524 this.isScrolling = true; 525 }.bind( this ), 526 always: function() { 527 this.isScrolling = false; 528 this.activeSection = target; 529 }.bind( this ), 530 /** 531 * Get and applying new values during animation. 532 * 533 * @param number now 534 * @param object fx 535 */ 536 step: function( now, fx ) { 537 var newTop = top; 538 // Since the header at the moment of scrolling the scroll can change the height,
539 // we will correct the position of the element. 540 if ( $us.header.stickyEnabled() ) { 541 newTop -= $us.header.getCurrentHeight( /* adminBar */true ); 542 } 543 if ( fx.end !== newTop ) { 544 $us.$htmlBody 545 .stop( true, false ) 546 .animate( { scrollTop: newTop }, $.extend( animateOptions, { 547 easing: this.options.endAnimationEasing 548 } ) ); 549 } 550 }.bind( this ) 551 }; 552 $us.$htmlBody 553 .stop( true, false ) 554 .animate( { scrollTop: top }, animateOptions ); 555 }, 556 557 resize: function( e ) { 558 if ( this.is_popup() ) { 559 return false; 560 } 561 this._attachEvents(); 562 this.recountSections(); 563 // Delaying the resize event to prevent glitches. 564 $ush.timeout( this._countAllPositions.bind( this ), 150 ); 565 }, 566 567 scroll: function() { 568 if ( this.is_popup() ) { 569 return false; 570 } 571 572 var currentTime = $ush.time(); 573 if ( ( currentTime - this.lastScroll ) < ( this.options.coolDown + this.options.animationDuration ) ) { 574 return; 575 } 576 577 __debounce_fn_500ms( function() { 578 var scrollTop = $ush.parseInt( $us.$window.scrollTop() ); 579 if ( $us.header.isPinned() ) { 580 scrollTop += $us.header.getCurrentHeight( /* adminBar */true ); 581 } 582 583 for ( var index in this.sections ) { 584 var section = this.sections[ index ]; 585 if ( 586 scrollTop >= $ush.parseInt( section.top - 1 ) 587 && scrollTop < $ush.parseInt( section.bottom - 1 ) 588 && section.area === 'content' 589 && this.activeSection !== index 590 ) { 591 this.activeSection = index; 592 // Note: Do not add break because everything should be checked! 593 } 594 } 595 596 if ( this.usingDots ) { 597 this.$dots.removeClass( 'active' ); 598 if ( this.dots[ this.activeSection ] !== _undefined ) { 599 this.dots[ this.activeSection ].addClass( 'active' ); 600 } 601 } 602 }.bind( this ) ); 603 } 604 }; 605 606 $.fn.usPageScroller = function( options ) { 607 return this.each( function() { 608 $( this ).data( 'usPageScroller', new $us.PageScroller( this, options ) ); 609 } ); 610 }; 611 612 $( function() { 613 // Delay to destination ult-vc-hide-row 614 $ush.timeout( () => $( '.w-scroller' ).usPageScroller(), 0 ); 615 } ); 616 617} )( 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.