PageSourceSearch

https://greenfoxwindows.ca/wp-content/plugins/level10-gallery-editor//assets/js/frontend.js?ver=3.3.0

js greenfoxwindows.ca collected 2026-10-01 07:01:45 UTC 22,678 bytes, 792 lines download raw bytes

1(function () {
2/* -------------------------------------------------------------
3/
4/ frontend.js
5/
6/ Core gallery functionality.
7/ Currently requires jQuery.
8/
9/ -------------------------------------------------------------*/
10
11// Use theme lightbox for built-in galleries
12var lightbox = true;
13
14const MULTI_FILTER_TYPE_BUTTONS = 'buttons';
15const MULTI_FILTER_TYPE_CHECKBOXES = 'checkboxes';
16
17/* -------------------------------------------------------------
18/
19/ load events
20/
21/ -------------------------------------------------------------*/
22
23window.addEventListener( 'load', function() {
24	// Galleries
25	galleryModalHandler();
26	initGalleryFilter();
27	initShowMore();
28	initSubGallery();
29	autoOpenGallery();
30	initBeforeAfterSlider();
31} );
32
33function autoOpenGallery() {
34	jQuery( '.sub-gallery-select' ).each( function() {
35		const select = jQuery( this );
36		if ( !select.length ) return;
37		
38		const first = select.children().first();
39		if ( select.attr( 'sub-gallery-auto-scroll' ) === 'true' ) {
40			first.addClass( 'skip-scroll' );
41		}
42		first.click();
43	} );
44}
45
46/* -------------------------------------------------------------
47/
48/ galleries: modal box
49/
50/ -------------------------------------------------------------*/
51
52function galleryModalHandler() {
53
54	if ( !lightbox ) return;
55
56	var modal = jQuery( '.site-gallery-modal:not(.filter-modal)' );
57	var modalContent = jQuery( '.site-gallery-modal:not(.filter-modal) .modal-content' );
58	var galleryItem = jQuery( '.site-gallery.grid:not(.filter-modal) .gallery-item' );
59	var controlNext = jQuery( '.site-gallery-modal:not(.filter-modal) .control-next' );
60	var controlPrev = jQuery( '.site-gallery-modal:not(.filter-modal) .control-prev' );
61	var controlMagnify = jQuery( '.site-gallery-modal:not(.filter-modal) .control-magnify' );
62	var scrollBtn = jQuery( '.site-rtt' );
63	var current
64	var currentGallery
65	var currentMagnify
66	var next
67	var prev
68	var body = document.querySelector( 'body' );
69
70	if ( !modal.length ) return;
71
72	function showModal() {
73		body.classList.add( 'model-opened' );
74		let enableMagnify = currentGallery.attr( 'data-magnify' ) === 'true' &&
75			window.outerWidth > 768;
76		modal.get( 0 ).classList.toggle( 'magnify',  enableMagnify );
77		modal.fadeIn();
78		scrollBtn.removeClass( 'visible' );
79		jQuery( '#wpadminbar' ).hide();
80	}
81	
82	function hideModal() {
83		body.classList.remove( 'model-opened' );
84		jQuery( '#wpadminbar' ).show();
85		modal.fadeOut( function() {
86			clearModal();
87		} );
88	}
89	
90	function clearModal() {
91		modalContent.empty();
92		current = '';
93		currentGallery = '';
94		clearMagnify();
95		next = '';
96		prev = '';
97	}
98	
99	function initMagnify() {
100		if ( !modal.hasClass( 'magnify' ) ) return;
101		if ( modalContent.find( '.is-video' ).length ) {
102			controlMagnify.attr( 'disabled', 'disabled' );
103			if ( controlMagnify.hasClass( 'active' ) ) {
104				controlMagnify.attr( 'data-was-active', '1' );
105			}
106			controlMagnify.removeClass( 'active' );
107			return;
108		}
109		
110		controlMagnify.removeAttr( 'disabled' );
111		
112		if ( controlMagnify.attr( 'data-was-active' ) ) {
113			controlMagnify.addClass( 'active' );
114			controlMagnify.removeAttr( 'data-was-active' );
115		}
116		modal.toggleClass( 'magnify-active', controlMagnify.hasClass( 'active' ) );
117		let image = modalContent.find( 'img' ).get( 0 );
118		image.onload = () => {
119			let mag = new Magnify( modalContent.find( 'img' ).get( 0 ), 2 );
120			mag.toggle( controlMagnify.hasClass( 'active' ) );
121			currentMagnify = mag;
122			currentMagnify.update();
123		};
124	}
125	
126	function clearMagnify() {
127		if ( !currentMagnify ) return;
128		currentMagnify.destroy();
129		currentMagnify = '';
130		modal.removeClass( 'magnify-active' );
131	}
132	
133	function populateModal( html ) {
134		if ( !html ) html = current;
135		const index = jQuery( html ).index();
136		const el =  jQuery( html ).clone().appendTo( modalContent );
137		el.attr( 'data-index', index );
138		const extras = el.find( 'p' );
139		extras.hide();
140		el.find( 'img' ).on( 'load', function() {
141			extras.fadeIn();
142		} );
143		el.find( 'video' ).on( 'loadeddata', function() {
144			extras.fadeIn();
145		} );
146		clearMagnify();
147		initMagnify();
148		playVideo();
149	}
150	
151	function queueNext( html ) {
152		current = html;
153		prev = jQuery( html ).prevAll( '.gallery-item' ).not( '.gallery-item-hidden' ).first();
154		next = jQuery( html ).nextAll( '.gallery-item' ).not( '.gallery-item-hidden' ).first();
155		
156		attrConditional( controlPrev, 'disabled', 'disabled', !prev.length );
157		attrConditional( controlNext, 'disabled', 'disabled', !next.length );
158		
159		if ( next && next.length && next.hasClass( 'show-more-hidden' ) ) {
160			currentGallery.trigger( 'show-more' );
161		}
162	}
163	
164	function updateModal( done ) {
165		modalContent.fadeOut( 'fast' ).promise().done( function() {
166			modalContent.empty();
167			clearMagnify();
168			populateModal();
169			modalContent.fadeIn( 'fast' ).promise().done( function() {
170				if ( typeof done === 'function' ) done();
171			} );
172		});
173	}
174	
175	function modalForward() {
176		if ( !next || !next.length ) return;
177		queueNext( next );
178		updateModal( function() {
179			jQuery( document ).trigger( 'gallery-next' );
180		} );
181	}
182	
183	function modalBack() {
184		if ( !prev || !prev.length ) return;
185		queueNext( prev );
186		updateModal( function() {
187			jQuery( document ).trigger( 'gallery-prev' );
188		} );
189	}
190	
191	function modalMagnify() {
192		if ( !currentMagnify ) return;
193		
194		modal.toggleClass( 'magnify-active', controlMagnify.hasClass( 'active' ) );
195		currentMagnify.toggle( controlMagnify.hasClass( 'active' ) );
196	}
197	
198	function playVideo() {
199		let video = modalContent.find( 'video' );
200		if ( !video.length ) return;
201		video.get( 0 ).play();
202	}
203	
204	// Gallery item click: open modal
205	galleryItem.click( function() {
206		currentGallery = jQuery( this ).closest( '.site-gallery' );
207		queueNext( this );
208		updateModal( function() {
209			jQuery( document ).trigger( 'gallery-opened' );
210		} );
211		showModal();
212	} )
213	
214	// Escape key: close modal
215	jQuery( document ).on( 'keydown', function(e) {
216		if (e.key == 'Escape') {
217			hideModal();
218		}
219	} )
220	
221	// Close btn click: close modal
222	jQuery( '.site-gallery-modal .control-close' ).click( function() {
223		hideModal();
224	} );
225	
226	// Next btn click: next gallery item
227	jQuery( '.site-gallery-modal .control-next' ).click( function() {
228		modalForward();
229	} );
230	
231	// Prev btn click: previous gallery item
232	jQuery( '.site-gallery-modal .control-prev' ).click( function() {
233		modalBack();
234	} );
235	
236	// Magnify btn click: toggle magnify
237	jQuery( '.site-gallery-modal .control-magnify' ).click( function() {
238		if ( jQuery( this ).attr( 'disabled' ) ) return;
239		jQuery( this ).toggleClass( 'active' );
240		modalMagnify();
241	} );
242	
243	// Touch swipe: next/prev gallery item
244	
245	let possibleSwipe = false;
246	let touchstartX = 0
247	let touchendX = 0
248	
249	function checkDirection() {
250		if ( touchendX < touchstartX ) modalForward();
251		if ( touchendX > touchstartX ) modalBack();
252	}
253	
254	document.addEventListener( 'touchstart', e => {
255		possibleSwipe = e.touches.length == 1
256		touchstartX = e.changedTouches[ 0 ].screenX
257	} );
258
259	document.addEventListener( 'touchend', e => {
260		if ( ! possibleSwipe ) return;
261		touchendX = e.changedTouches[ 0 ].screenX
262		checkDirection()
263	} );
264	
265	// Modal click outside .modal-flex: close modal
266	jQuery( '.site-gallery-modal:not(.filter-modal)' ).click( function( event ) {
267		const self = jQuery( this );
268		var target = event.target;
269		var controls = self.get( 0 ).querySelector('.modal-controls');
270		var content = self.get( 0 ).querySelector('.modal-content');
271		var form = self.get( 0 ).querySelector('.modal-form');
272		if (
273			controls.contains( target ) ||
274			content.contains( target ) ||
275			form.contains( target)
276		) {
277			return;
278		}
279		hideModal();
280	});
281	
282}
283
284function initGalleryFilter() {
285	const galleries = jQuery( '.site-gallery' );
286	if ( !galleries.length ) return;
287	
288	galleries.each( function() {
289		galleryFilterHandler( jQuery( this ) );
290	} );
291}
292
293function galleryFilterHandler( gallery ) {
294	const parent = gallery.parent();
295	const galleryId = gallery.attr( 'id' );
296	const isSubGallery = parent.hasClass( 'sub-gallery' );
297	const globalTags = [];
298	let selectedTags = [];
299	const multiFilter = gallery.attr( 'data-multi-filter' ) === 'true';
300	const filterType = gallery.attr( 'data-filter-type' );
301	const disableFilters = gallery.attr( 'data-disable-filters' ) === 'true';
302	if ( disableFilters ) return;
303	const tagsOuter = jQuery( '<div>' )
304		.addClass( 'filter-tags' )
305		.insertBefore( gallery );
306	const noResults = jQuery( '<p>' )
307		.addClass( 'filter-no-results' )
308		.text( 'No results found. Please select different filters.' )
309		.insertAfter( tagsOuter );
310	let noResultsTID = 0;
311	if ( multiFilter ) {
312		tagsOuter.addClass( `multi-filter-${filterType}` );
313	}
314	
315	gallery.find( '.gallery-item' ).each( function () {
316		const item = jQuery( this );
317		const tagsAttr = item.attr( 'data-tags' );
318		const tags = tagsAttr ? tagsAttr.split( ',' ) : [];
319		const tagsElements = item.find( '.tags .tag' );
320		
321		item.data( 'data-tags', tags );
322		tagsElements.each( function() {
323			const tagEl = jQuery( this );
324			const html = tagEl.html();
325			
326			if ( globalTags.includes( html ) ) return true;
327			
328			globalTags.push( html );
329		} );
330	} );
331	
332	if ( !globalTags.length ) return;
333	
334	globalTags.sort();
335	if ( filterType === MULTI_FILTER_TYPE_CHECKBOXES ) {
336		jQuery( '<a>' )
337			.attr( 'href', '#' )
338			.html( 'Filter' )
339			.addClass( 'open-filters' )
340			.prependTo( tagsOuter );
341	}
342	else {
343		globalTags.forEach( function( tag ) {
344			jQuery( '<a>' )
345				.attr( 'href', '#' + tag )
346				.attr( 'data-tag', tag )
347				.html( tag )
348				.addClass( 'filter-tag' )
349				.appendTo( tagsOuter );
350		} );
351		let label = multiFilter ? 'Clear Filters' : 'Show All';
352		const showAll = jQuery( '<a>' )
353			.attr( 'href', '#all' )
354			.attr( 'data-tag', label )
355			.html( label )
356			.addClass( 'filter-tag filter-all' )
357			.prependTo( tagsOuter );
358		if ( !multiFilter ) {
359			showAll.addClass( 'selected' );
360		}
361	}
362
363	function handlePostTagsUpdate() {
364		noResults.hide();
365		clearTimeout( noResultsTID );
366		noResultsTID = setTimeout( () => {
367			if ( gallery.find( '.gallery-item:not(.gallery-item-hidden)' ).length > 0 ) return;
368			noResults.fadeIn();
369		}, 500 );
370		
371		showMoreHandler( gallery, true );
372	};
373	
374	function update( tag, noAnimate ) {
375		
376		let speed = noAnimate ? 0 : 300;
377		if ( tag === null || ( Array.isArray( tag ) && tag.length === 0 ) ) {
378			gallery.find( '.gallery-item' ).removeClass( 'gallery-item-hidden' ).fadeIn( speed );
379			handlePostTagsUpdate();
380			return;
381		}
382		
383		if ( multiFilter ) {
384			if ( !Array.isArray( tag ) ) {
385				tag = [ tag ];
386			}
387			
388			gallery.find( '.gallery-item' ).addClass( 'gallery-item-hidden' ).fadeOut( speed, function() {
389				const item = jQuery( this );
390				let hasTags = true;
391				for ( const t of tag ) {
392					if ( !item.data( 'data-tags' ).includesLower( t ) ) {
393						hasTags = false;
394						break;
395					}
396				}
397				
398				if ( hasTags ) {
399					item.removeClass( 'gallery-item-hidden' ).fadeIn( speed );
400				}
401				
402				handlePostTagsUpdate();
403			} );
404		}
405		else {
406			if ( Array.isArray( tag ) ) {
407				tag = tag[ 0 ];
408			}
409			gallery.find( '.gallery-item' ).fadeOut( speed, function() {
410				const item = jQuery( this );
411				if ( item.data( 'data-tags' ).includesLower( tag ) ) {
412					item.removeClass( 'gallery-item-hidden' ).fadeIn( speed );
413				}
414				else {
415					item.addClass( 'gallery-item-hidden' ).fadeOut( speed );
416				}
417				
418				handlePostTagsUpdate();
419			} );
420		}
421	}
422	
423	function removeSelectedTag() {
424		parent.find( '.filter-tag.selected' ).removeClass( 'selected' );
425	}
426	
427	// Modal
428	var modal = jQuery( '.site-gallery-modal.filter-modal' );
429	var body = document.querySelector( 'body' );
430	var scrollBtn = jQuery( '.site-rtt' );
431
432	function showModal() {
433		body.classList.add( 'model-opened' );
434		populateModalFilterTags();
435		modal.attr( 'current-gallery', gallery.attr( 'id' ) );
436		modal.fadeIn();
437		scrollBtn.removeClass( 'visible' );
438	}
439	
440	function hideModal() {
441		const tags = [];
442		jQuery( '.site-gallery-modal.filter-modal input[type="checkbox"]:checked' ).each( function() {
443			tags.push( jQuery( this ).val() );
444		} );
445		update( tags );
446		selectedTags = tags;
447		body.classList.remove( 'model-opened' );
448		modal.fadeOut();
449		jQuery( document ).trigger( 'gallery-closed' );
450	}
451	
452	function populateModalFilterTags() {
453		jQuery( '.filter-modal .modal-content .inner' ).empty();
454		for ( const tag of globalTags ) {
455			let checked = '';
456			if ( selectedTags.includes( tag ) ) {
457				checked = ' checked';
458			}
459			jQuery( `
460			<label>
461				<input type="checkbox" value="${tag}"${checked}/>
462				<span>${tag}</span>
463			</label>
464			` ).appendTo( jQuery( '.filter-modal .modal-content .inner' ) );
465		}
466	}
467	
468	// Escape key: close modal
469	jQuery(document).on( 'keydown', function(e) {
470		if ( e.key == 'Escape' ) {
471			hideModal();
472		}
473	})
474	
475	// Close btn click: close modal
476	jQuery( '.site-gallery-modal.filter-modal .close' ).click( function( e ) {
477		e.preventDefault();
478		const currentGallery = modal.attr( 'current-gallery' );
479		if ( currentGallery !== galleryId ) return;
480		hideModal();
481	});
482	
483	// Modal click outside .modal-flex: close modal
484	jQuery( '.site-gallery-modal.filter-modal' ).click( function(event) {
485		var target = event.target;
486		var content = document.querySelector( '.site-gallery-modal.filter-modal .modal-content' );
487		var inner = document.querySelector( '.site-gallery-modal.filter-modal .modal-content .inner' );
488		if ( content.contains( target ) || inner.contains( target ) ) return;
489		hideModal();
490	});
491	
492	jQuery( '.site-gallery-modal.filter-modal' ).on( 'change', 'input[type="checkbox"]', function( e ) {
493		const currentGallery = modal.attr( 'current-gallery' );
494		if ( currentGallery !== galleryId || multiFilter ) return;
495		const selected = jQuery( this );
496		modal.find( 'input[type="checkbox"]' ).prop( 'checked', false );
497		selected.prop( 'checked', true );
498	} );
499	
500	parent.on( 'click', '.filter-tag.filter-all', function( e ) {
501		e.preventDefault();
502		const tag = jQuery( this );
503		removeSelectedTag();
504		if ( !multiFilter ) {
505			tag.addClass( 'selected' );
506		}
507		update( null );
508	} );
509	
510	parent.on( 'click', '.filter-tag:not(.filter-all)', function( e ) {
511		e.preventDefault();
512		const tag = jQuery( this );
513		if ( multiFilter ) {
514			parent.find( '.filter-tag-all.selected' ).removeClass( 'selected' );
515			tag.toggleClass( 'selected' );
516			const selectedTags = parent.find( '.filter-tag.selected' ).map( function() {
517				return jQuery( this ).attr( 'data-tag' );
518			} ).get();
519			update( selectedTags );
520		}
521		else {
522			const selected = tag.attr( 'data-tag' );
523			removeSelectedTag();
524			tag.addClass( 'selected' );
525			update( selected );
526		}
527	} );
528	
529	if ( isSubGallery ) {
530		parent.on( 'showing', function() {
531			jQuery( '.site-gallery-modal .sub-gallery-title' ).html( gallery.attr( 'data-label' ) );
532			// move parent to top of siblings
533			const firstGallery = jQuery( '.sub-gallery' ).first();
534			if ( parent !== firstGallery ) {
535				parent.insertBefore( firstGallery );
536			}
537		} );
538		
539		parent.on( 'hiding', function() {
540			update( null, true );
541		} );
542	}
543	
544	tagsOuter.find( '.open-filters' ).on( 'click', function( e ) {
545		e.preventDefault();
546		showModal();
547	} );
548	
549	jQuery( document ).on( 'click', '.clear-filters', function( e ) {
550		e.preventDefault();
551		jQuery( '.site-gallery-modal.filter-modal input[type="checkbox"] ' ).prop( 'checked', false );
552	} );
553}
554
555function initSubGallery() {
556	jQuery( document ).on( 'click', '.sub-gallery-select > div', function( e ) {
557		e.preventDefault();
558		const self = jQuery( this );
559		const label = self.find( '.label .inner-label' ).text();
560		const dir = self.attr( 'data-sub-gallery' );
561		const hasAutoScroll = self.parent().attr( 'sub-gallery-auto-scroll' ) == 'true';
562		jQuery( '.sub-gallery' ).trigger( 'hiding' ).hide( 0, function() {
563			jQuery( '.sub-gallery[data-sub-gallery="' + dir + '"]' )
564				.trigger( 'showing' )
565				.show( 0 )
566				.find( '.filter-tag.filter-all' ).click();
567		} );
568		if ( hasAutoScroll ) {
569			if ( self.hasClass( 'skip-scroll' ) ) {
570				self.removeClass( 'skip-scroll' );
571			}
572			else {
573				const header = jQuery( '.site-header-menu-row' );
574				const offset = header.css( 'position' ) === 'sticky' ? header.outerHeight( true ) : 200;
575				const spacing = 10;
576				jQuery( 'html, body' ).animate( {
577					scrollTop: jQuery( '.selected-gallery-name' ).offset().top - offset - spacing
578				}, 100 );
579			}
580		}
581		jQuery( '.selected-gallery-name' ).text( label );
582		jQuery( '.gallery-select-prompt' ).remove();
583		self.addClass( 'selected' ).siblings().removeClass( 'selected' );
584	} );
585}
586
587function initShowMore() {
588	const galleries = jQuery( '.site-gallery' );
589	if ( !galleries.length ) return;
590	
591	galleries.each( function() {
592		showMoreHandler( jQuery( this ) );
593	} );
594}
595
596function showMoreHandler( gallery, resetShowMoreItems ) {
597	const MAX_INITIAL    = parseInt( l10GalleryArgs.maxInitialImages );
598	const PAGINATION     = parseInt( l10GalleryArgs.paginationCount );
599	const QUERY_SELECTOR = '.gallery-item.show-more-hidden:not(.gallery-item-hidden)';
600	const HIDE_CLASS     = 'show-more-hidden';
601	const container      = gallery.parent();
602	let observer;
603	let nextHidden;
604	
605	if ( resetShowMoreItems ) {
606		// do this on the next tick
607		gallery.find( '.gallery-item:not(.gallery-item-hidden)' )
608			.removeClass( HIDE_CLASS )
609			.slice( l10GalleryArgs.maxInitialImages )
610			.addClass( HIDE_CLASS );
611	}
612	
613	const items = gallery.find( QUERY_SELECTOR );
614	container.find( '.show-more-btn' ).remove();
615	if ( items.length < MAX_INITIAL ) return;
616	
617	nextHidden = items.slice( MAX_INITIAL );
618	nextHidden.addClass( HIDE_CLASS );
619	
620	const showMore = jQuery( `
621		<div class="site-btn show-more-btn">
622			<a href="javascript:void(0)" class="show-more">Show More</a>
623		</div>
624	` ).appendTo( container );
625	
626	showMore.on( 'click', function( e ) {
627		e.preventDefault();
628		const hidden = gallery.find( QUERY_SELECTOR );
629		if ( !hidden.length ) {
630			return;
631		}
632		const next = hidden.slice( 0, PAGINATION );
633		next.removeClass( HIDE_CLASS );
634		if ( !gallery.find( '.show-more-hidden' ).length && !showMore.hasClass( 'end-reached' ) ) {
635			showMore.addClass( 'end-reached' ).find( 'a' ).html( 'End Reached' );
636			observer.disconnect();
637		}
638	} );
639	
640	gallery.off( 'show-more' ).on( 'show-more', function() {
641		showMore.click();
642	} );
643	
644	observer = new IntersectionObserver( function( entries ) {
645		if ( !entries[ 0 ].isIntersecting ) return;
646		showMore.click();
647	}, { threshold: 0 } );
648	
649	observer.observe( showMore[ 0 ] );
650}
651
652function initBeforeAfterSlider() {
653	const sliders = jQuery( '.sub-gallery .before-after-slider .slider' );
654	sliders.on( 'mouseup', function() {
655		this.blur();
656	} );
657	sliders.on( 'input change', function( e ) {
658		const element = jQuery( this ).parents('.before-after-slider');
659		const pos = e.target.value;
660		element.find( '.before' ).css(
661			{ clipPath: 'polygon(0 0, ' + pos + '% 0, ' + pos + '% 100%, 0% 100%)' }
662		);
663		element.find( '.slider-button' ).css(
664			{
665				left: 'calc(' + pos + '%)',
666			}
667		);
668	});
669}
670
671Array.prototype.includesLower = function( value ) {
672	const lower = value.toLowerCase();
673	return this.some( element => {
674		return element.toLowerCase() === lower;
675	} );
676};
677
678function attrConditional( el, attr, value, condition ) {
679	if ( condition ) {
680		return el.attr( attr, value );
681	}
682	else {
683		return el.removeAttr( attr );
684	}
685};
686
687class Magnify {
688	constructor ( img, zoom ) {
689		let self = this;
690		let glass, w, h, borderWidth;
691		/* Create magnifier glass: */
692		glass = document.createElement( 'DIV' );
693		glass.setAttribute( 'class', 'magnifier-glass' );
694		
695		/* Insert magnifier glass: */
696		img.parentElement.insertBefore( glass, img );
697		
698		/* Set background properties for the magnifier glass: */
699		glass.style.backgroundImage = 'url("' + img.src + '")';
700		glass.style.backgroundRepeat = 'no-repeat';
701		borderWidth = parseFloat( getComputedStyle( img ).borderWidth );
702		w = glass.offsetWidth / 2;
703		h = glass.offsetHeight / 2;
704		
705		/* Execute a function when someone moves the magnifier glass over the image: */
706		glass.addEventListener( 'mousemove', moveMagnifier );
707		img.addEventListener( 'mousemove', moveMagnifier );
708		
709		img.parentElement.addEventListener( 'mouseenter', onEnter );
710		img.parentElement.addEventListener( 'mouseleave', onLeave );
711		
712		/*and also for touch screens:*/
713		glass.addEventListener( 'touchmove', moveMagnifier );
714		img.addEventListener( 'touchmove', moveMagnifier );
715		
716		// If the size of the image changes, we need to update the background size
717		window.addEventListener( 'resize', onResize );
718		
719		function moveMagnifier ( e ) {
720			let pos, x, y;
721			/* Prevent any other actions that may occur when moving over the image */
722			e.preventDefault();
723			/* Get the cursor's x and y positions: */
724			pos = getCursorPos( e );
725			x = pos.x;
726			y = pos.y;
727			let marginLeft  = parseFloat( getComputedStyle( img ).marginLeft );
728			/* Prevent the magnifier glass from being positioned outside the image: */
729			if ( x > img.width - ( w / zoom ) ) { x = img.width - ( w / zoom ); }
730			if ( x < w / zoom ) { x = w / zoom; }
731			if ( y >
731 img.height - ( h / zoom ) ) { y = img.height - ( h / zoom ); }
732			if ( y < h / zoom ) { y = h / zoom; }
733			/* Set the position of the magnifier glass: */
734			glass.style.left = ( x - w + marginLeft ) + 'px';
735			glass.style.top = ( y - h ) + 'px';
736			/* Display what the magnifier glass "sees": */
737			glass.style.backgroundPosition = '-' + ( ( x * zoom ) - w + borderWidth ) + 'px -' + ( ( y * zoom ) - h + borderWidth ) + 'px';
738		}
739		
740		function onEnter() {
741			img.parentElement.classList.add( 'magnify-hover' );
742			self.update();
743		}
744		
745		function onLeave() {
746			img.parentElement.classList.remove( 'magnify-hover' );
747		}
748		
749		function getCursorPos ( e ) {
750			let a, x = 0, y = 0;
751			e = e || window.event;
752			/* Get the x and y positions of the image: */
753			a = img.getBoundingClientRect();
754			/* Calculate the cursor's x and y coordinates, relative to the image: */
755			x = e.pageX - a.left;
756			y = e.pageY - a.top;
757			/* Consider any page scrolling: */
758			x = x - window.pageXOffset;
759			y = y - window.pageYOffset;
760			return { x: x, y: y };
761		}
762		
763		function onResize() {
764			glass.style.backgroundSize = ( img.width * zoom ) + 'px ' + ( img.height * zoom ) + 'px';
765		}
766		
767		this.toggle = function ( show ) {
768			glass.style.display = show ? 'block' : 'none';
769			
770			if ( show ) {
771				this.update();
772			}
773		};
774		
775		this.update = function () {
776			onResize();
777		};
778		
779		this.destroy = function () {
780			glass.remove();
781			img.removeEventListener( 'mousemove', moveMagnifier );
782			img.removeEventListener( 'touchmove', moveMagnifier );
783			img.parentElement.removeEventListener( 'mouseenter', onEnter );
784			img.parentElement.removeEventListener( 'mouseleave', onLeave );
785			window.removeEventListener( 'resize', onResize );
786		};
787
788		return this;
789	}
790}
791
792})();

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.