PageSourceSearch

https://almerecity.nl/wp-content/themes/Impreza/common/js/elements/page-scroller.js?ver=8.46.1

js almerecity.nl collected 2026-10-02 11:43:35 UTC 19,190 bytes, 617 lines download raw bytes

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.