https://cms.math.ca/wp-content/plugins/ultimate-elementor/assets/js/uael-sticky-header.js?ver=1.45.4
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.