PageSourceSearch

https://cms.math.ca/wp-content/plugins/ultimate-elementor/assets/js/uael-sticky-header.js?ver=1.45.4

js math.ca collected 2026-09-24 08:46:19 UTC 24,189 bytes, 616 lines download raw bytes

1/**
2 * UAE Sticky Header Script
3 * 
4 * @package UAEL
5 */
6
7(function($) {
8    'use strict';
9
10    /**
11     * UAE Sticky Header Class
12     */
13    var UAEStickyHeader = function($element) {
14        this.$element = $element;
15        this.settings = this.getSettings();
16        this.isSticky = false;
17        this.lastScrollTop = 0;
18        this.scrollDirection = 'up';
19        this.ticking = false;
20        this.resizeTimer = null;
21        
22        // Only initialize if sticky is enabled
23        if (this.settings.enable === 'yes') {
24            this.init();
25        }
26    };
27
28    UAEStickyHeader.prototype = {
29        /**
30         * Get element settings
31         */
32        getSettings: function() {
33            // First try to get from the new data attribute
34            var stickySettings = this.$element.data('uae-sticky-settings');
35            if (stickySettings) {
36                // Parse if it's a string
37                if (typeof stickySettings === 'string') {
38                    try {
39                        stickySettings = JSON.parse(stickySettings);
40                    } catch (e) {
41                        stickySettings = {};
42                    }
43                }
44                return {
45                    enable: stickySettings.uae_sticky_header_enable || '',
46                    devices: stickySettings.uae_sticky_devices || ['desktop', 'tablet', 'mobile'],
47                    scrollDistance: stickySettings.uae_sticky_scroll_distance || { size: 100, unit: 'px' },
48                    scrollDistanceTablet: stickySettings.uae_sticky_scroll_distance_tablet || null,
49                    scrollDistanceMobile: stickySettings.uae_sticky_scroll_distance_mobile || null,
50                    
51                    // Visual effects
52                    transparentEnable: stickySettings.uae_sticky_transparent_enable || '',
53                    transparencyLevel: stickySettings.uae_sticky_transparency_level || { size: 100, unit: '%' },
54                    
55                    backgroundEnable: stickySettings.uae_sticky_background_enable || '',
56                    backgroundType: stickySettings.uae_sticky_background_type || 'solid',
57                    backgroundColor: stickySettings.uae_sticky_background_color || '#ffffff',
58                    
59                    gradientColor1: stickySettings.uae_sticky_gradient_color_1 || '#ffffff',
60                    gradientLocation1: stickySettings.uae_sticky_gradient_location_1 || { size: 0, unit: '%' },
61                    gradientColor2: stickySettings.uae_sticky_gradient_color_2 || '#f0f0f0',
62                    gradientLocation2: stickySettings.uae_sticky_gradient_location_2 || { size: 100, unit: '%' },
63                    gradientType: stickySettings.uae_sticky_gradient_type || 'linear',
64                    gradientAngle: stickySettings.uae_sticky_gradient_angle || { size: 180, unit: 'deg' },
65                    
66                    borderEnable: stickySettings.uae_sticky_border_enable || '',
67                    borderColor: stickySettings.uae_sticky_border_color || '#e0e0e0',
68                    borderThickness: stickySettings.uae_sticky_border_thickness || { size: 1, unit: 'px' },
69                    
70                    shadowEnable: stickySettings.uae_sticky_shadow_enable || '',
71                    shadowColor: stickySettings.uae_sticky_shadow_color || 'rgba(0, 0, 0, 0.1)',
72                    shadowVertical: stickySettings.uae_sticky_shadow_vertical || { size: 0, unit: 'px' },
73                    shadowBlur: stickySettings.uae_sticky_shadow_blur || { size: 10, unit: 'px' },
74                    shadowSpread: stickySettings.uae_sticky_shadow_spread || { size: 0, unit: 'px' },
75                    
76                    hideOnScrollDown: stickySettings.uae_sticky_hide_on_scroll_down || '',
77                    hideThreshold: stickySettings.uae_sticky_hide_threshold || { size: 10, unit: '%' },
78                    hideThresholdTablet: stickySettings.uae_sticky_hide_threshold_tablet || null,
79                    hideThresholdMobile: stickySettings.uae_sticky_hide_threshold_mobile || null
80                };
81            }
82            
83            // Fallback to old method
84            var data = this.$element.data('settings') || {};
85            return {
86                enable: data.uae_sticky_header_enable || '',
87                devices: data.uae_sticky_devices || ['desktop', 'tablet', 'mobile'],
88                scrollDistance: data.uae_sticky_scroll_distance || { size: 100, unit: 'px' },
89                scrollDistanceTablet: data.uae_sticky_scroll_distance_tablet || null,
90                scrollDistanceMobile: data.uae_sticky_scroll_distance_mobile || null,
91                
92                // Visual effects
93                transparentEnable: data.uae_sticky_transparent_enable || '',
94                transparencyLevel: data.uae_sticky_transparency_level || { size: 100, unit: '%' },
95                
96                backgroundEnable: data.uae_sticky_background_enable || '',
97                backgroundType: data.uae_sticky_background_type || 'solid',
98                backgroundColor: data.uae_sticky_background_color || '#ffffff',
99              
100                gradientColor1: data.uae_sticky_gradient_color_1 || '#ffffff',
101                gradientLocation1: data.uae_sticky_gradient_location_1 || { size: 0, unit: '%' },
102                gradientColor2: data.uae_sticky_gradient_color_2 || '#f0f0f0',
103                gradientLocation2: data.uae_sticky_gradient_location_2 || { size: 100, unit: '%' },
104                gradientType: data.uae_sticky_gradient_type || 'linear',
105                gradientAngle: data.uae_sticky_gradient_angle || { size: 180, unit: 'deg' },
106                
107                borderEnable: data.uae_sticky_border_enable || '',
108                borderColor: data.uae_sticky_border_color || '#e0e0e0',
109                borderThickness: data.uae_sticky_border_thickness || { size: 1, unit: 'px' },
110                
111                shadowEnable: data.uae_sticky_shadow_enable || '',
112                shadowColor: data.uae_sticky_shadow_color || 'rgba(0, 0, 0, 0.1)',
113                shadowVertical: data.uae_sticky_shadow_vertical || { size: 0, unit: 'px' },
114                shadowBlur: data.uae_sticky_shadow_blur || { size: 10, unit: 'px' },
115                shadowSpread: data.uae_sticky_shadow_spread || { size: 0, unit: 'px' },
116                
117                hideOnScrollDown: data.uae_sticky_hide_on_scroll_down || '',
118                hideThreshold: data.uae_sticky_hide_threshold || { size: 10, unit: '%' },
119                hideThresholdTablet: data.uae_sticky_hide_threshold_tablet || null,
120                hideThresholdMobile: data.uae_sticky_hide_threshold_mobile || null
121            };
122        },
123
124        /**
125         * Initialize sticky header
126         */
127        init: function() {
128            var self = this;
129            
130            // Check if current device is enabled
131            if (!this.isDeviceEnabled()) {
132                return;
133            }
134            
135            // Set initial styles
136            this.setInitialStyles();
137            
138            // Bind events
139            this.bindEvents();
140            
141            // Initial check
142            this.checkScroll();
143        },
144
145        /**
146         * Check if sticky is enabled for current device
147         */
148        isDeviceEnabled: function() {
149            var currentDevice = this.getCurrentDevice();
150            return this.settings.devices.indexOf(currentDevice) !== -1;
151        },
152
153        /**
154         * Get current device type
155         */
156        getCurrentDevice: function() {
157            var width = window.innerWidth;
158            
159            if (width >= 1025) {
160                return 'desktop';
161            } else if (width >= 768 && width < 1025) {
162                return 'tablet';
163            } else {
164                return 'mobile';
165            }
166        },
167
168        /**
169         * Get scroll distance for current device
170         */
171        getScrollDistance: function() {
172            var device = this.getCurrentDevice();
173            var scrollDistance = this.settings.scrollDistance;
174            
175            if (device === 'tablet' && this.settings.scrollDistanceTablet) {
176                scrollDistance = this.settings.scrollDistanceTablet;
177            } else if (device === 'mobile' && this.settings.scrollDistanceMobile) {
178                scrollDistance = this.settings.scrollDistanceMobile;
179            }
180            
181            // Convert percentage to pixels if needed
182            if (scrollDistance.unit === '%') {
183                return (window.innerHeight * scrollDistance.size) / 100;
184            }
185            
186            return scrollDistance.size;
187        },
188
189        /**
190         * Get hide threshold for current device
191         */
192        getHideThreshold: function() {
193            var device = this.getCurrentDevice();
194            var hideThreshold = this.settings.hideThreshold;
195            
196            if (device === 'tablet' && this.settings.hideThresholdTablet) {
197                hideThreshold = this.settings.hideThresholdTablet;
198            } else if (device === 'mobile' && this.settings.hideThresholdMobile) {
199                hideThreshold = this.settings.hideThresholdMobile;
200            }
201            
202            // Convert percentage to pixels if needed
203            if (hideThreshold.unit === '%') {
204                return (window.innerHeight * hideThreshold.size) / 100;
205            }
206            
207            return hideThreshold.size;
208        },
209
210        /**
211         * Set initial styles
212         */
213        setInitialStyles: function() {
214            // Add identifier class
215            this.$element.addClass('uae-sticky-header-element');
216            
217            // Store original background color
218            var currentBg = this.$element.css('background-color');
219            this.$element.data('original-background', currentBg);
220            
221            // Don't apply transparency here - only when sticky
222            
223            // Set transition
224            this.$element.css({
225                'transition': 'all 0.3s ease-in-out'
226            });
227        },
228
229        /**
230         * Apply transparency
231         */
232        applyTransparency: function () {
233            var opacity = (100 - this.settings.transparencyLevel.size) / 100;
234            var self = this; // Fix for 'this' context in replace callback
235        
236            var currentBgColor = this.$element.css('background-color');
237            var currentBgImage = this.$element.css('background-image');
238            var hasGradient = currentBgImage && currentBgImage !== 'none';
239        
240            var blurStyles = {
241                'backdrop-filter': 'blur(10px)',
242                '-webkit-backdrop-filter': 'blur(10px)'
243            };
244        
245            if (hasGradient) {
246                // Replace hex colors (3 or 6 digit) in gradient with RGBA having opacity
247                var modifiedGradient = currentBgImage.replace(/#([0-9a-f]{3,6})\b/gi, function (hex) {
248                    return self.convertHexToRgba(hex, opacity);
249                });
250                
251                // Also replace rgb/rgba colors
252                modifiedGradient = modifiedGradient.replace(/rgba?\(([^)]+)\)/gi, function(match, values) {
253                    var parts = values.split(',').map(function(v) { return v.trim(); });
254                    if (parts.length === 3) {
255                        // rgb format - add alpha
256                        return 'rgba(' + parts[0] + ', ' + parts[1] + ', ' + parts[2] + ', ' + opacity + ')';
257                    } else if (parts.length === 4) {
258                        // rgba format - replace alpha
259                        return 'rgba(' + parts[0] + ', ' + parts[1] + ', ' + parts[2] + ', ' + opacity + ')';
260                    }
261                    return match;
262                });
263        
264                this.$element.css($.extend({
265                    'background-image': modifiedGradient
266                }, blurStyles));
267            } else if (
268                currentBgColor &&
269                currentBgColor !== 'transparent' &&
270                currentBgColor !== 'rgba(0, 0, 0, 0)'
271            ) {
272                // Solid color — convert to rgba
273                var rgbaColor = this.convertToRgba(currentBgColor, opacity);
274                this.$element.css($.extend({
275                    'background-color': rgbaColor
276                }, blurStyles));
277            } else {
278                // No background, apply transparent white
279                this.$element.css($.extend({
280                    'background-color': 'rgba(255, 255, 255, ' + opacity + ')'
281                }, blurStyles));
282            }
283        },
284
285         /**
286         * Convert Hexa color to RGBA with opacity
287         */
288        convertHexToRgba: function(hex, alpha) {
289            hex = hex.replace('#', '');
290            if (hex.length === 3) {
291                hex = hex.split('').map(char => char + char).join('');
292            }
293            var bigint = parseInt(hex, 16);
294            var r = (bigint >> 16) & 255;
295            var g = (bigint >> 8) & 255;
296            var b = bigint & 255;
297        
298            return `rgba(${r}, ${g}, ${b}, ${alpha})`;
299        },
300        
301        
302        /**
303         * Convert color to RGBA with opacity
304         */
305        convertToRgba: function(color, opacity) {
306            // If already rgba, update the opacity
307            if (color.indexOf('rgba') === 0) {
308                return color.replace(/[\d\.]+\)$/g, opacity + ')');
309            }
310            
311            // If rgb, convert to rgba
312            if (color.indexOf('rgb') === 0) {
313                return color.replace('rgb', 'rgba').replace(')', ', ' + opacity + ')');
314            }
315            
316            // If hex, convert to rgba
317            if (color.indexOf('#') === 0) {
318                var hex = color.replace('#', '');
319                var r = parseInt(hex.substring(0, 2), 16);
320                var g = parseInt(hex.substring(2, 4), 16);
321                var b = parseInt(hex.substring(4, 6), 16);
322                return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + opacity + ')';
323            }
324            
325            // Default fallback
326            return 'rgba(255, 255, 255, ' + opacity + ')';
327        },
328
329        /**
330         * Bind events
331         */
332        bindEvents: function() {
333            var self = this;
334            
335            // Scroll event with RAF
336            $(window).on('scroll.uaeStickyHeader', function() {
337                self.requestTick();
338            });
339            
340            // Resize event with debounce
341            $(window).on('resize.uaeStickyHeader', function() {
342                clearTimeout(self.resizeTimer);
343                self.resizeTimer = setTimeout(function() {
344                    self.handleResize();
345                }, 250);
346            });
347            
348            // Elementor editor events
349            if (window.elementorFrontend && window.elementorFrontend.isEditMode()) {
350                elementor.channels.editor.on('change', function(model) {
351                    if (model.el === self.$element[0]) {
352                        self.settings = self.getSettings();
353                        self.checkScroll();
354                    }
355                });
356            }
357        },
358
359        /**
360         * Request animation frame for scroll
361         */
362        requestTick: function() {
363            var self = this;
364            
365            if (!this.ticking) {
366                requestAnimationFrame(function() {
367                    self.checkScroll();
368                    self.ticking = false;
369                });
370                this.ticking = true;
371            }
372        },
373
374        /**
375         * Check scroll and apply sticky
376         */
377        checkScroll: function() {
378            var scrollTop = $(window).scrollTop();
379            var scrollDistance = this.getScrollDistance();
380            
381            // Detect scroll direction
382            if (scrollTop > this.lastScrollTop) {
383                this.scrollDirection = 'down';
384            } else {
385                this.scrollDirection = 'up';
386            }
387            this.lastScrollTop = scrollTop;
388            
389            // Check if should be sticky
390            if (scrollTop >= scrollDistance) {
391                if (!this.isSticky) {
392                    this.makeSticky();
393                }
394                
395                // Handle hide on scroll down
396                if (this.settings.hideOnScrollDown === 'yes') {
397                    this.handleHideOnScroll();
398                }
399            } else {
400                if (this.isSticky) {
401                    this.removeSticky();
402                }
403            }
404        },
405
406        /**
407         * Make element sticky
408         */
409        makeSticky: function() {
410            this.isSticky = true;
411            this.$element.addClass('uae-sticky--active');
412            
413            // Apply visual effects
414            this.applyVisualEffects();
415            
416            // Add fixed positioning
417            this.$element.css({
418                'position': 'fixed',
419                'top': '0',
420                'left': '0',
421                'right': '0',
422                'z-index': '9999',
423                'width': '100%'
424            });
425            
426            // Add placeholder to prevent layout shift
427            this.addPlaceholder();
428        },
429
430        /**
431         * Remove sticky
432         */
433        removeSticky: function() {
434            this.isSticky = false;
435            this.$element.removeClass('uae-sticky--active uae-sticky--hidden');
436            
437            // Remove visual effects
438            this.removeVisualEffects();
439            
440            // Remove fixed positioning
441            this.$element.css({
442                'position': '',
443                'top': '',
444                'left': '',
445                'right': '',
446                'z-index': '',
447                'width': ''
448            });
449            
450            // Remove placeholder
451            this.removePlaceholder();
452        },
453
454        /**
455         * Apply visual effects when sticky
456         */
457        applyVisualEffects: function() {
458            var self = this;
459            
460            // Background
461            if (this.settings.backgroundEnable === 'yes') {
462                if (this.settings.backgroundType === 'solid') {
463                    // Use jQuery's css method with important flag
464                    this.$element.css('background-color', this.settings.backgroundColor);
465                    // Also set via native style property for better specificity
466                    this.$element[0].style.setProperty('background-color', this.settings.backgroundColor, 'important');
467                } else {
468                    // Gradient
469                    var gradient = this.buildGradient();
470                    this.$element.css('background-image', gradient);
471                    this.$element[0].style.setProperty('background-image', gradient, 'important');
472                    this.$element[0].style.setProperty('background-color', 'transparent', 'important');
473                }
474            } else if (this.settings.transparentEnable === 'yes') {
475                // If no background is set but transparency is enabled, keep it transparent when sticky
476                this.applyTransparency();
477            }
478            
479            // Border
480            if (this.settings.borderEnable === 'yes') {
481                var borderValue = this.settings.borderThickness.size + 'px solid ' + this.settings.borderColor;
482                this.$element.css('border-bottom', borderValue);
483            }
484            
485            // Shadow
486            if (this.settings.shadowEnable === 'yes') {
487                var shadowValue = '0 ' + 
488                    this.settings.shadowVertical.size + 'px ' + 
489                    this.settings.shadowBlur.size + 'px ' + 
490                    this.settings.shadowSpread.size + 'px ' + 
491                    this.settings.shadowColor;
492                this.$element.css('box-shadow', shadowValue);
493            }
494        },
495
496        /**
497         * Remove visual effects
498         */
499        removeVisualEffects: function() {
500            // Store original background before removing
501            var originalBg = this.$element.data('original-background');
502            
503            this.$element.css({
504                'background-color': originalBg || '',
505                'background-image': '',
506                'border-bottom': '',
507                'box-shadow': '',
508                'backdrop-filter': '',
509                '-webkit-backdrop-filter': '',
510                'opacity': '' // Reset opacity for gradient transparency
511            });
512            
513            // Don't reapply transparency here - header should return to original state
514        },
515
516        /**
517         * Build gradient CSS
518         */
519        buildGradient: function() {
520            var type = this.settings.gradientType;
521            var color1 = this.settings.gradientColor1 + ' ' + this.settings.gradientLocation1.size + '%';
522            var color2 = this.settings.gradientColor2 + ' ' + this.settings.gradientLocation2.size + '%';
523            
524            if (type === 'linear') {
525                return 'linear-gradient(' + this.settings.gradientAngle.size + 'deg, ' + color1 + ', ' + color2 + ')';
526            } else {
527                return 'radial-gradient(circle, ' + color1 + ', ' + color2 + ')';
528            }
529        },
530
531        /**
532         * Add placeholder element
533         */
534        addPlaceholder: function() {
535            if (!this.$placeholder) {
536                var height = this.$element.outerHeight();
537                this.$placeholder = $('<div class="uae-sticky-placeholder"></div>').css({
538                    'height': height + 'px',
539                    'visibility': 'hidden'
540                });
541                this.$element.after(this.$placeholder);
542            }
543        },
544
545        /**
546         * Remove placeholder element
547         */
548        removePlaceholder: function() {
549            if (this.$placeholder) {
550                this.$placeholder.remove();
551                this.$placeholder = null;
552            }
553        },
554
555        /**
556         * Handle hide on scroll down
557         */
558        handleHideOnScroll: function() {
559            var threshold = this.getHideThreshold();
560            
561            if (this.scrollDirection === 'down' && this.lastScrollTop > threshold) {
562                this.$element.addClass('uae-sticky--hidden');
563                this.$element.css('transform', 'translateY(-100%)');
564            } else if (this.scrollDirection === 'up') {
565                this.$element.removeClass('uae-sticky--hidden');
566                this.$element.css('transform', 'translateY(0)');
567            }
568        },
569
570        /**
571         * Handle resize
572         */
573        handleResize: function() {
574            // Check if device is still enabled
575            if (!this.isDeviceEnabled()) {
576                this.removeSticky();
577                return;
578            }
579            
580            // Update placeholder height if sticky
581            if (this.isSticky && this.$placeholder) {
582                this.$placeholder.css('height', this.$element.outerHeight() + 'px');
583            }
584            
585            // Recheck scroll
586            this.checkScroll();
587        },
588
589        /**
590         * Destroy sticky header
591         */
592        destroy: function() {
593            $(window).off('.uaeStickyHeader');
594            this.removeSticky();
595            this.$element.removeClass('uae-sticky-header-element');
596        }
597    };
598
599    /**
600     * Initialize on ready
601     */
602    $(window).on('elementor/frontend/init', function() {
603        // Handler for sections and containers
604        var stickyHandler = function($scope) {
605            // Check if it's an HFE header
606            if ($scope.closest('.hfe-site-header, .site-header, header').length > 0) {
607                new UAEStickyHeader($scope);
608            }
609        };
610
611        // Register handlers
612        elementorFrontend.hooks.addAction('frontend/element_ready/section', stickyHandler);
613        elementorFrontend.hooks.addAction('frontend/element_ready/container', stickyHandler);
614    });
615
616})(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.