PageSourceSearch

https://almerecity.nl/wp-content/themes/Impreza/common/js/base/scroll.js?ver=8.46.1

js almerecity.nl collected 2026-10-02 11:43:09 UTC 21,867 bytes, 675 lines download raw bytes

1/**
2 * $us.scroll
3 *
4 * ScrollSpy, Smooth scroll links and hash-based scrolling all-in-one
5 *
6 * no_smooth_scroll - The presence of this class disables smooth scrolling
7 * for the selected element or for all elements inside the block.
8 */
9! function( $, _undefined ) {
10	"use strict";
11
12	const _document = document;
13	const _location = location;
14
15	const ceil = Math.ceil;
16
17	window.$ush = window.$ush || {};
18	window.$us = window.$us || {};
19
20	function USScroll( opts ) {
21		const self = this;
22
23		const defaultOpts = {
24
25			// Selectors for attach to smooth scroll
26			attachOnInit: [
27				'.menu-item a[href*="#"]',
28				'.menu-item[href*="#"]',
29				'.post_custom_field a[href*="#"]',
30				'.post_title a[href*="#"]',
31				'.w-ibanner a[href*="#"]',
32				'.vc_custom_heading a[href*="#"]',
33				'.vc_icon_element a[href*="#"]',
34				'.w-comments-title a[href*="#"]',
35				'.w-iconbox a[href*="#"]',
36				'.w-image a[href*="#"]:not([onclick])',
37				'.w-text a[href*="#"]',
38				'.w-toplink',
39				'a.smooth-scroll[href*="#"]',
40				'a.w-btn[href*="#"]:not([onclick]):not(.w-skip-btn)',
41				'a.w-grid-item-anchor[href*="#"]'
42			].join(),
43
44			// Classname that will be toggled on relevant buttons
45			buttonActiveClass: 'active',
46
47			// Classname that will be toggled on relevant menu items
48			menuItemActiveClass: 'current-menu-item',
49
50			// Classname that will be toggled on relevant menu ancestors
51			menuItemAncestorActiveClass: 'current-menu-ancestor',
52
53			// Duration of scroll animation
54			animationDuration: ( $us.canvasOptions || {} ).scrollDuration || 0,
55
56			// Easing for scroll animation
57			animationEasing: $us.getAnimationName( 'easeInOutExpo' ),
58
59			// End easing for scroll animation
60			endAnimationEasing: $us.getAnimationName( 'easeOutExpo' )
61		};
62
63		self.opts = $.extend( {}, defaultOpts, opts || {} );
64
65		// Hash blocks with targets and activity indicators.
66		self.blocks = {};
67
68		// Is scrolling to some specific block at the moment?
69		self.isScrolling = false;
70
71		// Boundable events
72		self._events = {
73			onAnchorClick: self.onAnchorClick.bind( self ),
74			onCancel: self.onCancel.bind( self ),
75			onScroll: self.onScroll.bind( self ),
76			onResize: self.onResize.bind( self )
77		};
78
79		$us.$window.on( 'resize load', $ush.debounce( self._events.onResize, 1 ) );
80		$ush.timeout( self._events.onResize, 75 );
81
82		$us.$window.on( 'scroll.noPreventDefault', self._events.onScroll );
83		$ush.timeout( self._events.onScroll, 75 );
84
85		if ( self.opts.attachOnInit ) {
86			self.attach( self.opts.attachOnInit );
87		}
88
89		// Recount scroll positions on any content changes.
90		$us.$canvas.on( 'contentChange', self._countAllPositions.bind( self ) );
91
92		// Handling initial document hash
93		if ( _location.hash && _location.hash.indexOf( '#!' ) == - 1 ) {
94			var hash = _location.hash,
95				scrollPlace = ( self.blocks[ hash ] !== _undefined )
96					? hash
97					: _undefined;
98
99			if ( scrollPlace === _undefined ) {
100				try {
101					const $target = $( hash );
102					if ( $target.length != 0 ) {
103						scrollPlace = $target;
104					}
105				}
106				catch ( error ) {
107					// No action is required here since scrollPlace is undefined.
108				}
109
110			}
111			if ( scrollPlace !== _undefined ) {
112
113				// While page loads, its content changes, and we'll keep the proper scroll on each sufficient content
114				// change until the page finishes loading or user scrolls the page manually.
115				var keepScrollPositionTimer = setInterval( () => {
116					self.scrollTo( scrollPlace );
117					// Additionally, let's check the states to avoid an infinite call.
118					if ( _document.readyState !== 'loading' ) {
119						clearInterval( keepScrollPositionTimer );
120					}
121				}, 100 );
122				const clearHashEvents = () => {
123					$us.$window.off( 'load mousewheel.noPreventDefault DOMMouseScroll touchstart.noPreventDefault', clearHashEvents );
124					// Content size still may change via other script right after page load
125					$ush.timeout( () => {
126						$us.canvas.resize();
127						self._countAllPositions();
128						// The size of the content can be changed using another script, so we recount the waypoints.
129						if ( $us.hasOwnProperty( 'waypoints' ) ) {
130							$us.waypoints._countAll();
131						}
132						self.scrollTo( scrollPlace );
133					}, 100 );
134				};
135				$us.$window.on( 'load mousewheel.noPreventDefault DOMMouseScroll touchstart.noPreventDefault', clearHashEvents );
136			}
137		}
138
139		// Basic set of options that should be extended by scrollTo methods
140		self.animateOpts = {
141			duration: self.opts.animationDuration,
142			easing: self.opts.animationEasing,
143			start: () => {
144				self.isScrolling = true;
145			},
146			complete: () => {
147				self.onCancel();
148			},
149		}
150	}
151
152	USScroll.prototype = {
153
154		/**
155		 * Count hash's target position and store it properly
156		 *
157		 * @param {String} hash
158		 */
159		_countPosition: function( hash ) {
160			const self = this;
161			var $target = self.blocks[ hash ].$target,
162				offsetTop = $target.offset().top;
163
164			// Get the real height for sticky elements,
165			// since after sticking their height changes
166			if ( $target.hasClass( 'type_sticky' ) ) {
167				var key = 'realTop';
168				if ( ! $target.hasClass( 'is_sticky' ) ) {
169					$target.removeData( key );
170				}
171				if ( ! $target.data( key ) ) {
172					$target.data( key, offsetTop );
173				}
174				offsetTop = $target.data( key ) || offsetTop;
175			}
176
177			// For support Footer Reveal
178			if ( $us.$body.hasClass( 'footer_reveal' ) && $target.closest( 'footer' ).length ) {
179				offsetTop = $us.$body.outerHeight( true ) + ( offsetTop - $us.$window.scrollTop() );
180			}
181
182			self.blocks[ hash ].top = ceil( offsetTop - $us.canvas.getOffsetTop() );
183		},
184
185		/**
186		 * Count all targets' positions for proper scrolling
187		 */
188		_countAllPositions: function() {
189			const self = this;
190			for ( const hash in self.blocks ) {
191				if ( self.blocks[ hash ] ) {
192					self._countPosition( hash );
193				}
194			}
195		},
196
197		/**
198		 * Indicate scroll position by hash
199		 *
200		 * @param {String} activeHash
201		 */
202		indicatePosition: function( activeHash ) {
203			const self = this;
204			for ( const hash in self.blocks ) {
205				if ( ! self.blocks[ hash ] ) {
206					continue;
207				}
208				const block = self.blocks[ hash ];
209
210				if ( ! $ush.isUndefined( block.buttons ) ) {
211					block.buttons.toggleClass( self.opts.buttonActiveClass, hash === activeHash );
212				}
213				if ( ! $ush.isUndefined( block.menuItems ) ) {
214					block.menuItems.toggleClass( self.opts.menuItemActiveClass, hash === activeHash );
215				}
216				// Removing active class for all Menu Ancestors first.
217				if ( ! $ush.isUndefined( block.menuAncestors ) ) {
218					block.menuAncestors.removeClass( self.opts.menuItemAncestorActiveClass );
219				}
220			}
221			// Adding active class for activeHash Menu Ancestors after all Menu Ancestors active classes was removed in
222			// previous loop. This way there would be no case when we first added classes for needed Menu Ancestors and
223			// then removed those classes while checking sibling menu item's hash.
224			if ( ! $ush.isUndefined( self.blocks[ activeHash ] ) && ! $ush.isUndefined( self.blocks[ activeHash ].menuAncestors ) ) {
225				self.blocks[ activeHash ].menuAncestors.addClass( self.opts.menuItemAncestorActiveClass );
226			}
227		},
228
229		/**
230		 * Attach anchors so their targets will be listened for possible scrolls
231		 *
232		 * @param {String|jQuery} anchors Selector or list of anchors to attach
233		 */
234		attach: function( anchors ) {
235			const self = this;
236			const $anchors = $( anchors ).not( '.no_smooth_scroll' );
237
238			if ( $anchors.length == 0 ) {
239				return;
240			}
241
242			var // Decode pathname to compare non-latin letters.
243				_pathname = decodeURIComponent( _location.pathname ),
244				// Location pattern to check absolute URLs for current location.
245				patternPathname = new RegExp( '^' + _pathname.replace( /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&" ) + '#' ),
246				// Pattern for getting a record or checking a string for a record.
247				patternPageId = /^\/?(\?page_id=\d+).*?/;
248
249			$anchors.each( ( index, anchor ) => {
250				const $anchor = $( anchor );
251
252				if ( $anchor.closest( '.no_smooth_scroll' ).length > 0 ) {
253					return;
254				}
255
256				// Link without a host
257				var href = $ush.toString( $anchor.attr( 'href' ) ).replace( _location.origin, '' ),
258					hash = $anchor.prop( 'hash' ),
259					hasProtocol = /^(https?:\/\/)/.test( href ),
260					hasPageId = patternPageId.test( href );
261
262				if (
263					// Ignoring ajax links
264					hash.indexOf( '#!' ) > -1
265					// Case when there is no hash-tag
266					|| href.indexOf( '#' ) < 0
267					// Case where /#hash-tag is used (Allowed on homepage)
268					|| ( href.substr( 0, 2 ) == '/#' && _location.search && _pathname == '/' )
269					// Сase when the domain name of a third-party resource
270					|| ( hasProtocol && href.indexOf( _location.origin ) !== 0 )
271					// Case when the search of another page example: /?page_id={id}#hash-tag
272					|| ( hasPageId && href.indexOf( ( _location.search.match( patternPageId ) || [] )[1] ) == -1 )
273					// Case when the pathname of another page example: /postname/#hash-tag
274					|| ( href.charAt( 0 ) == '/' && ! hasPageId && ! patternPathname.test( href ) )
275				) {
276					return;
277				}
278
279				// Do we have an actual target, for which we'll need to count geometry?
280				if ( hash != '' && hash != '#' ) {
281					// Attach target
282					if ( self.blocks[ hash ] === _undefined ) {
283						// NOTE: Page may contain multiple identifiers if “Hide on” is used. #5449
284						var $target = $( `[id="${hash.slice(1)}"]:visible` ), $originalTarget, type = '';
285						// Don't attach anchors that actually have no target
286						if ( $target.length == 0 ) {
287							return;
288						}
289
290						// If it's the only row in a section, than use section instead.
291						if (
292							$target.hasClass( 'g-cols' )
293							&& $target.hasClass( 'vc-row' )
294							&& $target.parent().children().length == 1
295						) {
296							$target = $target.closest( '.l-section' );
297						}
298						// If it's a tabs or tour item, then use it's tabs container
299						if ( $target.hasClass( 'w-tabs-section' ) ) {
300							const $newTarget = $target.closest( '.w-tabs' );
301							if ( ! $newTarget.hasClass( 'accordion' ) ) {
302								$originalTarget = $target;
303								$target = $newTarget;
304							}
305							type = 'tab-section';
306
307						} else if ( $target.hasClass( 'w-tabs' ) ) {
308							type = 'tabs';
309						}
310						self.blocks[ hash ] = {
311							type: type, $target: $target, $originalTarget: $originalTarget
312						};
313						self._countPosition( hash );
314					}
315					// Attach activity indicator
316					if ( $anchor.parent().length > 0 && $anchor.parent().hasClass( 'menu-item' ) ) {
317						var $menuIndicator = $anchor.closest( '.menu-item' );
318						self.blocks[ hash ].menuItems = ( self.blocks[ hash ].menuItems || $() ).add( $menuIndicator );
319						var $menuAncestors = $menuIndicator.parents( '.menu-item-has-children' );
320						if ( $menuAncestors.length > 0 ) {
321							self.blocks[ hash ].menuAncestors = ( self.blocks[ hash ].menuAncestors || $() ).add( $menuAncestors );
322						}
323					} else {
324						self.blocks[ hash ].buttons = ( self.blocks[ hash ].buttons || $() ).add( $anchor );
325					}
326				}
327
328				$anchor.on( 'click', self._events.onAnchorClick );
329			} );
330		},
331
332		/**
333		 * Get the place position
334		 *
335		 * @param {*} place
336		 * @return {{}}
337		 */
338		getPlacePosition: function( place ) {
339			const self = this;
340			const data = { newY: 0, type: '' };
341			// Scroll to top
342			if ( place === '' || place === '#' ) {
343				data.newY = 0;
344				data.type = 'top';
345			}
346			// Scroll by hash
347			else if ( self.blocks[ place ] !== _undefined ) {
348				// Position recalculation
349				self._countPosition( place );
350				data.newY = self.blocks[ place ].top;
351				data.type = 'hash';
352				place = self.blocks[ place ].$target;
353
354				// JQuery object handler
355			} else if ( place instanceof $ ) {
356				if ( place.hasClass( 'w-tabs-section' ) ) {
357					var newPlace = place.closest( '.w-tabs' );
358					if ( ! newPlace.hasClass( 'accordion' ) ) {
359						place = newPlace;
360					}
361				}
362				// Get the Y position, taking into account the height of the header, adminbar and sticky elements.
363				data.newY = place.offset().top;
364				data.type = 'element';
365			} else {
366				// Get the Y position, taking into account the height of the header, adminbar and sticky elements.
367				data.newY = place;
368			}
369
370			// If the page has a sticky section, then consider the height of the sticky section.
371			if (
372				$us.canvas.isStickySection()
373				&& $us.canvas.hasPositionStickySections()
374				&& ! $( place ).hasClass( 'type_sticky' )
375				&& $us.canvas.isAfterStickySection( place )
376			) {
377				data.newY -= $us.canvas.getStickySectionHeight();
378			}
379
380			return data;
381		},
382
383		/**
384		 * Scroll page to a certain position or hash
385		 *
386		 * @param {Number|String|jQuery} place
387		 * @param {Boolean} animate Enables or disables scroll animation
388		 */
389		scrollTo: function( place, animate ) {
390			const self = this;
391			var $place = $( place );
392			if ( $place.closest( '.w-popup-wrap' ).length ) {
393				self.scrollToPopupContent( place );
394				return true;
395			}
396
397			var offset = self.getPlacePosition( place ),
398				indicateActive = () => {
399					if ( offset.type === 'hash' ) {
400						self.indicatePosition( place );
401					} else {
402						self.onScroll();
403					}
404				};
405
406			if ( animate ) {
407				// Fix for iPads since scrollTop returns 0 all the time
408				if ( navigator.userAgent.match( /iPad/i ) != null && $( '.us_iframe' ).length && offset.type == 'hash' ) {
409					$place[0].scrollIntoView( { behavior: "smooth", block: "start" } );
410				}
411
412				var scrollTop = $us.$window.scrollTop(),
413					// Determining the direction of scrolling - up or down
414					scrollDirections = scrollTop < offset.newY
415						? 'down'
416						: 'up';
417
418				if ( scrollTop === offset.newY ) {
419					return;
420				}
421
422				// Animate options
423				const animateOpts = $.extend(
424					{},
425					self.animateOpts,
426					{
427						always: () => {
428							self.isScrolling = false;
429							indicateActive();
430						}
431					}
432				);
433
434				/**
435				 * Get and applying new values during animation
436				 *
437				 * @param number now
438				 * @param object fx
439				 */
440				animateOpts.step = ( now, fx ) => {
441					// Checking the position of the element, since the position may change if the leading elements
442					// were loaded with a lazy load
443					var newY = self.getPlacePosition( place ).newY;
444					// Since the header at the moment of scrolling the scroll can change the height,
445					// we will correct the position of the element
446					if ( $us.header.isHorizontal() && $us.header.stickyEnabled() ) {
447						newY -= $us.header.getCurrentHeight();
448					}
449
450					// Since elements can change height, we update the endpoint
451					// of each integration thanks to object references.
452					fx.end = newY;
453				};
454
455				// If the place has a css animation, then run it so that it executes and
456				// after that it is possible to get the correct endpoint
457				if ( $place.hasClass( 'us_animate_this' ) ) {
458					$place.trigger( 'us_startAnimate' );
459				}
460
461				// Start animation
462				$us.$htmlBody
463					.stop( true, false )
464					.animate( { scrollTop: offset.newY + 'px' }, animateOpts );
465
466				// Allow user to stop scrolling manually
467				$us.$window
468					.on( 'keydown mousewheel.noPreventDefault DOMMouseScroll touchstart.noPreventDefault', self._events.onCancel );
469
470			} else {
471
472				// If scrolling without animation, then we get the height of the header and change the position.
473				if ( $us.header.stickyEnabled() && $us.header.isHorizontal() ) {
474					offset.newY -= $us.header.getCurrentHeight( /* adminBar */true );
475				}
476
477				// Stop all animations and scroll to the set position
478				$us.$htmlBody
479					.stop( true, false )
480					.scrollTop( offset.newY );
481
482				indicateActive();
483			}
484		},
485
486		/**
487		 * Scroll popup content to a specific hash
488		 *
489		 * @param {String} place
490		 */
491		scrollToPopupContent: function( place ) {
492			const self = this;
493			const node = _document.getElementById( place.replace( '#', '' ) );
494
495			// Animate options
496			const animateOpts = $.extend(
497				{},
498				self.animateOpts,
499				{
500					always: () => {
501						self.isScrolling = false;
502					},
503				}
504			);
505
506			$( node ).closest( '.w-popup-wrap' )
507				.stop( true, false )
508				.animate( { scrollTop: node.offsetTop + 'px' }, animateOpts );
509
510			$us.$window
511				.on( 'keydown mousewheel.noPreventDefault DOMMouseScroll touchstart.noPreventDefault', self._events.onCancel );
512		},
513
514		/**
515		 * Called on anchor click.
516		 *
517		 * @param {Event} e
518		 * @return {Boolean}
519		 */
520		onAnchorClick: function( e ) {
521			e.preventDefault();
522
523			const self = this;
524			const $anchor = $( e.currentTarget );
525			const hash = $anchor.prop( 'hash' );
526
527			// Prevent scroll on mobile menu items that should show child sub-items on click by label
528			if (
529				$anchor.hasClass( 'w-nav-anchor' )
530				&& $anchor.closest( '.menu-item' ).hasClass( 'menu-item-has-children' )
531				&& $anchor.closest( '.w-nav' ).hasClass( 'type_mobile' )
532			) {
533				var menuOptions = $anchor.closest( '.w-nav' ).find( '.w-nav-options:first' )[0].onclick() || {},
534					dropByLabel = $anchor.parents( '.menu-item' ).hasClass( 'mobile-drop-by_label' ),
535					dropByArrow = $anchor.parents( '.menu-item' ).hasClass( 'mobile-drop-by_arrow' );
536				if ( dropByLabel || ( menuOptions.mobileBehavior && ! dropByArrow ) ) {
537					return false;
538				}
539			}
540
541			// Prevent scrolling if the URL is just a hash inside the popup
542			if (
543				$anchor.attr( 'href' ) === '#'
544				&& $anchor.closest( '.w-popup-wrap' ).length
545			) {
546				return false;
547			}
548
549			self.scrollTo( hash, true );
550
551			// Keep focus on the page.
552			if ( hash === '#page-top' ) {
553				const active = document.activeElement;
554				if ( active && typeof active.blur === 'function' ) {
555					active.blur();
556				}
557				if ( ! self.focusHolder ) {
558					self.focusHolder = document.createElement( 'div' );
559					self.focusHolder.tabIndex = -1;
560					self.focusHolder.classList.add( 'focus-holder' );
561					self.focusHolder.setAttribute( 'aria-hidden', 'true' );
562					document.body.appendChild( self.focusHolder );
563				}
564				requestAnimationFrame( () => {
565					self.focusHolder.focus( { preventScroll: true } );
566				} );
567				return;
568			}
569
570			self.indicatePosition( hash );
571
572			if ( typeof self.blocks[ hash ] !== 'undefined' ) {
573				var block = self.blocks[ hash ];
574				// When scrolling to an element, check for the presence of tabs, and if necessary, open the
575				// first section
576				if ( [ 'tabs', 'tab-section' ].includes( block.type ) ) {
577					var $linkedSection = $( `.w-tabs-section[id="${hash.substr(1)}"]`, block.$target );
578					if ( block.type === 'tabs' ) {
579						// Selects the first section
580						$linkedSection = $( '.w-tabs-section:first', block.$target );
581					} else if ( block.$target.hasClass( 'w-tabs-section' ) ) {
582						// The selected section
583						$linkedSection = block.$target;
584					}
585					if ( $linkedSection.length ) {
586						// Trigger a click event to open the first section.
587						$( '.w-tabs-section-header', $linkedSection ).trigger( 'click' );
588					}
589				} else if (
590					block.menuItems !== _undefined
591					&& $us.currentStateIs( ['mobiles', 'tablets'] )
592					&& $us.$body.hasClass( 'header-show' )
593				) {
594					$us.$body.removeClass( 'header-show' );
595				}
596			}
597		},
598
599		/**
600		 * Cancel scroll
601		 */
602		onCancel: function() {
603			$us.$htmlBody.stop( true, false );
604			$us.$window.off( 'keydown mousewheel.noPreventDefault DOMMouseScroll touchstart', this._events.onCancel );
605			this.isScrolling = false;
606		},
607
608		/**
609		 * Scroll handler
610		 */
611		onScroll: function() {
612			const self = this;
613			if ( self.isScrolling ) {
614				return;
615			}
616
617			var scrollTop = ceil( $us.header.getScrollTop() ),
618				activeHash;
619
620			// Fix the negative scroller issue in Safari.
621			scrollTop = ( scrollTop >= 0 )
622				? scrollTop
623				: 0;
624
625			for ( const hash in self.blocks ) {
626				const block = self.blocks[ hash ];
627				if ( ! block || activeHash || $ush.isNodeInViewport( block ) ) {
628					continue;
629				}
630				var top = block.top;
631				if ( ! $us.header.isHorizontal() ) {
632					// The with a vertical header, subtract only the height of the admin bar, if any.
633					top -= $us.canvas.getOffsetTop();
634				} else {
635					// Since the header at the moment of scrolling the scroll can change the height,
636					// we will correct the position of the element
637					if ( $us.header.stickyEnabled() ) {
638						top -= $us.header.getCurrentHeight( /* adminBar */true );
639					}
640					// If the page has a sticky section, then consider the height of the sticky section
641					if ( $us.canvas.hasStickySection() ) {
642						top -= $us.canvas.getStickySectionHeight();
643					}
644				}
645				top = $ush.parseInt( top.toFixed(0) );
646				if ( scrollTop >= top && scrollTop <= ( /* block bottom */top + block.$target.outerHeight( false ) ) ) {
647					activeHash = hash;
648				}
649				// For tabs, select only the active section
650				if ( activeHash && block.type === 'tab-section' && block.$originalTarget.is( ':hidden' ) ) {
651					activeHash = _undefined;
652				}
653			}
654
655			$ush.debounce_fn_1ms( self.indicatePosition.bind( self, activeHash ) );
656		},
657
658		/**
659		 * Resize handler
660		 */
661		onResize: function() {
662			const self = this;
663			// Delay the resize event to prevent glitches
664			$ush.timeout( () => {
665				self._countAllPositions();
666				self.onScroll();
667			}, 150 );
668			self._countAllPositions();
669			self.onScroll();
670		}
671	};
672
673	$( () => $us.scroll = new USScroll( $us.scrollOptions || {} ) );
674
675}( 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.