1(function ($) { 2 "use strict"; 3 4 const WGLMathUtils = { 5 map: (x, a, b, c, d) => (x - a) * (d - c) / (b - a) + c, 6 lerp: (a, b, n) => (1 - n) * a + n * b, 7 }; 8 9 const wglGetWinSize = () => { 10 return { 11 width: window.innerWidth, 12 height: window.innerHeight 13 }; 14 }; 15 let wglWinSize = wglGetWinSize(); 16 window.addEventListener('resize', () => wglWinSize = wglGetWinSize()); 17 18 class WGLTextPathHandlerClass extends elementorModules.frontend.handlers.Base { 19 getDefaultSettings() { 20 return { 21 selectors: { 22 pathContainer: '.wgl-text-path', 23 svg: '.wgl-text-path > svg', 24 line: '.wgl-text-path > .simple_line', 25 } 26 }; 27 } 28 29 getDefaultElements() { 30 var _this$getSettings = this.getSettings(), 31 selectors = _this$getSettings.selectors; 32 var element = this.$element[0]; 33 return { 34 widgetWrapper: element, 35 pathContainer: element.querySelector(selectors.pathContainer), 36 svg: element.querySelector(selectors.svg), 37 line: element.querySelector(selectors.line), 38 textPath: element.querySelector(selectors.textPath) 39 }; 40 } 41 42 fetchSVG() { 43 const { 44 url 45 } = this.elements.pathContainer.dataset; 46 47 if (!url || !url.endsWith('.svg')) { 48 return Promise.reject(url); 49 } 50 51 return fetch(url).then(res => res.text()).then(svg => { 52 this.elements.pathContainer.innerHTML = svg; // Re-initialize the elements, so the SVG tag will be added. 53 54 this.elements = this.getDefaultElements(); 55 }); 56 } 57 58 onInit() { 59 this.elements = this.getDefaultElements(); // Generate unique IDs using the wrapper's `data-id`. 60 if(this.elements.pathContainer.dataset.url){ 61 this.fetchSVG().then(() => { 62 this.initSVG(); 63 }); 64 }else{ 65 this.initSVG(); 66 } 67 68 } 69 70 initSVG(){ 71 // Generate unique IDs using the wrapper's `data-id`. 72 this.pathId = `wgl-path-${this.elements.widgetWrapper.dataset.id}`; 73 this.textPathId = `wgl-text-path-${this.elements.widgetWrapper.dataset.id}`; 74 75 if (!this.elements.svg && !this.elements.line) { 76 return; 77 } 78 79 this.getPropertyPosition(); 80 this.initTextPath(); 81 this.initAnimation(); 82 } 83 84 initAnimation() { 85 if(this.elements.pathContainer.classList.contains("loop_animation")){ 86 window.addEventListener('resize', () => { 87 this.getPropertyPosition(); 88 this.appendAnimate(); 89 }); 90 91 if(this.elements.pathContainer.classList.contains("clone_text")){ 92 window.addEventListener('resize', () => { 93 this.cloneText(); 94 }); 95 } 96 97 if(this.elements.line){ 98 this.animate_line(); 99 window.addEventListener('resize', this.debounce(() => { 100 this.createMarquee(); 101 }), false); 102 } 103 if( 104 this.elements.pathContainer.classList.contains("stop_on_hover") 105 && this.elements.svg 106 ){ 107 this.elements.svg.addEventListener('mouseenter', e => { 108 this.elements.svg.pauseAnimations(); 109 }); 110 111 this.elements.svg.addEventListener('touchstart', e => { 112 this.elements.svg.pauseAnimations(); 113 }); 114 115 this.elements.svg.addEventListener('touchend', e => { 116 this.elements.svg.unpauseAnimations(); 117 }); 118 119 this.elements.svg.addEventListener('mouseleave', e => { 120 this.elements.svg.unpauseAnimations(); 121 }); 122 } 123 } 124 125 if(this.elements.pathContainer.classList.contains("scroll_animation")){ 126 this.svgRect = this.elements.svg ? this.elements.svg.getBoundingClientRect() : this.elements.line.getBoundingClientRect(); 127 this.positionY = this.svgRect.top + window.pageYOffset; 128 129 window.addEventListener('resize', () => { 130 this.getPropertyPosition(); 131 this.appendAnimate(); 132 this.svgRect = this.elements.svg ? this.elements.svg.getBoundingClientRect() : this.elements.line.getBoundingClientRect(); 133 this.positionY = this.svgRect.top + window.pageYOffset; 134 }); 135 136 this.observer = new IntersectionObserver((entries) => {
137 entries.forEach(entry => { 138 this.isVisible = entry.intersectionRatio > 0; 139 if (!this.isVisible) { 140 this.update(); 141 } 142 }); 143 }); 144 145 this.observer.observe(this.elements.svg ? this.elements.svg : this.elements.line); 146 147 requestAnimationFrame(() => this.render()); 148 } 149 150 window.addEventListener('resize', () => { 151 this.textPosChange(); 152 }); 153 } 154 155 createMarquee() { 156 if ('animate' in this.marquee_el && typeof this.marquee_el.animate === 'function') { 157 if (typeof this.marquee !== 'undefined') this.marquee.cancel(); 158 if (!this.elements.line.querySelector('.text--word')) return; 159 let vw = this.elements.line.querySelector('.text--word').clientWidth; 160 this.marquee = this.marquee_el.animate([ 161 { transform: 'translateX(0)', offset: 0 }, 162 { transform: 'translateX(-' + vw + 'px)', offset: 1 } 163 ], 164 { 165 duration: this.animation_speed, 166 easing: 'linear', 167 delay: 0, 168 iterations: Infinity, 169 fill: 'forwards' 170 }); 171 } 172 } 173 174 animate_line() { 175 this.marquee = undefined; 176 this.marquee_el = this.elements.line.querySelector('.simple_line--wrapper'); 177 178 if ('animate' in this.marquee_el && typeof this.marquee_el.animate === 'function') { 179 this.createMarquee(); 180 if(this.elements.pathContainer.classList.contains("stop_on_hover")){ 181 this.marquee_el.addEventListener('mouseenter', () => {this.pauseMarquee()}); 182 this.marquee_el.addEventListener('mouseleave', () => {this.playMarquee()}); 183 } 184 } 185 } 186 187 playMarquee() { 188 if (this.marquee.playState === 'paused') this.marquee.play(); 189 } 190 191 pauseMarquee() { 192 if (this.marquee.playState === 'running') this.marquee.pause(); 193 } 194 195 debounce(func) { 196 var scheduled, context, args; 197 return function () { 198 context = this; args = []; 199 for (var i = 0; i < arguments.length; ++i) args[i] = arguments[i]; 200 !!scheduled && window.cancelAnimationFrame(scheduled); 201 scheduled = window.requestAnimationFrame(function () { 202 func.apply(context, args); 203 scheduled = null; 204 }); 205 } 206 } 207 208 textPosChange() { 209 document.querySelectorAll('tspan.text_divider').forEach(function(e){ 210 if(e.closest('svg')){ 211 e.setAttribute("dy", -1 * getComputedStyle(e.closest('svg')).getPropertyValue('--top-offset')); 212 e.setAttribute("dx", -1 * getComputedStyle(e.closest('svg')).getPropertyValue('--left-offset')); 213 } 214 }); 215 216 document.querySelectorAll('tspan.divider').forEach(function(e){ 217 if(e.closest('svg')){ 218 e.setAttribute("dy", getComputedStyle(e.closest('svg')).getPropertyValue('--top-offset')); 219 e.setAttribute("dx", getComputedStyle(e.closest('svg')).getPropertyValue('--left-offset')); 220 } 221 }); 222 } 223 224 setOffset(offset, endPoint) { 225 this.startOffset = { 226 value: parseInt(offset), 227 getDefaultStartPoint: offset || 0, 228 getDefaultEndPoint: endPoint || 0, 229 amt: 0.1 230 }; 231 232 if (!this.elements.textPath) { 233 return; 234 } 235 236 this.elements.textPath.setAttribute('startOffset', this.elements.pathContainer.classList.contains("clone_text") ? 0 : offset + ''); 237 } 238 239 appendAnimate() { 240 if (!this.elements.textPath) { 241 return; 242 } 243 if( 244 this.elements.pathContainer.classList.contains("loop_animation") && !this.elements.pathContainer.classList.contains("clone_text") 245 ){ 246 if(this.elements.svg.querySelector('animate')){ 247 this.elements.svg.querySelector('animate').remove(); 248 } 249 if(this.elements.pathContainer.dataset.typeSvg !== 'circle'){ 250 this.elements.textPath.insertAdjacentHTML('beforeend', '<animate attributeName="startOffset" from="'+this.startPoint +'" to ="'+ this.endPoint +'" begin="0s" dur="'+(this.animation_speed/1000)+'s" repeatCount="indef
250inite"/>'); 251 } 252 if(this.elements.pathContainer.dataset.typeSvg === 'circle'){ 253 this.elements.pathContainer.classList.add('rotate_circle'); 254 this.elements.svg.style.animationDuration = this.animation_speed/1000 + 's'; 255 } 256 } 257 } 258 259 cloneText() { 260 if (!this.elements.textPath && !this.elements.line) { 261 return; 262 } 263 264 let animation_speed = this.animation_speed; 265 let to = this.endPoint; 266 let from = this.startPoint; 267 let backspace = this.elements.pathContainer.dataset.backspaceCount; 268 269 if (this.elements.line) { 270 this.elements.line.querySelector('.simple_line--wrapper').innerHTML = ''; 271 } 272 273 this.word.innerHTML = this.getDefautWord; 274 this.width = Math.ceil(this.elements.textPath ? this.elements.textPath.getBBox().width : this.word.querySelector('.text--word').getBoundingClientRect().width); 275 this.times = Math.ceil(window.innerWidth / this.width); 276 277 if (this.elements.textPath) { 278 this.elements.textPath.parentNode.style.width = `${(this.times + 1) * this.width}px`; 279 } else { 280 this.elements.line.querySelector('.simple_line--wrapper').style.width = `${(this.times + 1) * this.width}px`; 281 } 282 283 let countItem = 0; 284 let i; 285 286 if (this.times !== Infinity && this.times < 100) { 287 let times_item = this.elements.pathContainer.classList.contains("clone_text") && this.elements.pathContainer.classList.contains("scroll_animation") ? 2 * this.times : this.times; 288 for (i = 0; i < times_item; i++) { 289 let space = ''; 290 for (let k = 0; k <= backspace; k++) { 291 space += '\u00A0'; 292 countItem++; 293 } 294 if (this.elements.textPath) { 295 this.word.insertAdjacentHTML('beforeend', space); 296 } else { 297 this.word.querySelectorAll('.text--word')[i].insertAdjacentHTML('beforeend', space); 298 } 299 300 if ( 301 this.elements.pathContainer.classList.contains("add_divider") 302 ) { 303 if (this.elements.textPath) { 304 this.word.insertAdjacentHTML('beforeend', '<tspan class="divider" dy="' + getComputedStyle(this.elements.svg).getPropertyValue('--top-offset') + '" dx="' + getComputedStyle(this.elements.svg).getPropertyValue('--left-offset') + '">' + this.divider() + '</tspan>'); 305 } else { 306 this.word.querySelectorAll('.text--word')[i].insertAdjacentHTML('beforeend', '<span class="divider">' + this.divider() + '</span>'); 307 } 308 } 309 310 this.word.insertAdjacentHTML('beforeend', this.getDefautWord); 311 } 312 313 if ( 314 !this.elements.pathContainer.classList.contains("scroll_animation") 315 ) { 316 if (this.elements.textPath) { 317 if(this.elements.pathContainer.dataset.typeSvg !== 'circle'){ 318 this.word.innerHTML += '<animate attributeName="startOffset" from="' + from + '" to ="' + to + '" begin="0s" dur="' + (animation_speed / 1000) + 's" repeatCount="indefinite"/>'; 319 } 320 if(this.elements.pathContainer.dataset.typeSvg === 'circle'){ 321 this.elements.pathContainer.classList.add('rotate_circle'); 322 this.elements.svg.style.animationDuration = animation_speed / 1000 + 's'; 323 } 324 } 325 326 } 327 } 328 329 this.textPosChange(); 330 } 331 332 onElementChange(setting) { 333 var _this$getElementSetti = this.getElementSettings(), 334 startPoint = this.elements.svg ? getComputedStyle(this.elements.svg).getPropertyValue('--start-point') : getComputedStyle(this.elements.line).getPropertyValue('--start-point'), 335 endPoint = this.elements.svg ? getComputedStyle(this.elements.svg).getPropertyValue('--end-point') : getComputedStyle(this.elements.line).getPropertyValue('--end-point') , 336 text = _this$getElementSetti.text; 337 338 this.animation_speed = _this$getElementSetti.animation_speed; 339 switch (setting) { 340 case 'start_point': 341 case 'end_point': 342 case 'start_point_mobile': 343 case 'start_point_mobile_extra': 344 case 'start_point_tablet': 345 case 'start_point_tablet_extra': 346 case 'start_point_laptop': 347 case 'start_point_desktop': 348 case 'start_point_widescreen': 349 case 'end_point_mobile': 350 case 'end_point_mobile_extra': 351 case 'end_point_tablet': 352 case 'end_point_tablet_extra': 353 case 'end_point_laptop': 354 case 'end_point_desktop': 355 case 'end_point_widescreen': 356 this.getPropertyPosition(); 357 this.setOffset(startPoint ? startPoint : 0, endPoint ? endPoint : 0); 358 this.appendAnimate(); 359 break; 360 361 case 'top_offset': 362 case 'left_offset': 363 case 'top_offset_mobile': 364 case 'top_offset_mobile_extra': 365 case 'top_offset_tablet': 366 case 'top_offset_tablet_extra': 367 case 'top_offset_laptop': 368 case 'top_offset_desktop': 369 case 'top_offset_widescreen': 370 case 'left_offset_mobile': 371 case 'left_offset_mobile_extra': 372 case 'left_offset_tablet': 373 case 'left_offset_tablet_extra': 374 case 'left_offset_laptop': 375 case 'left_offset_desktop': 376 case 'left_offset_widescreen': 377 this.textPosChange(); 378 break; 379 380 case 'text': 381 case 'animation_speed': 382 this.getPropertyPosition(); 383 this.setText(text); 384 this.appendAnimate(); 385 if(this.elements.pathContainer.classList.contains("clone_text")){ 386 this.cloneText(); 387 } 388 if(this.elements.line){ 389 this.animate_line(); 390 } 391 break; 392 393 default: 394 break; 395 } 396 } 397 398 attachIdToPath() { 399 // Prioritize the custom `data` attribute over the `path` element, and fallback to the first `path`. 400 if (!this.elements.svg) { 401 return; 402 } 403 404 var path = this.elements.svg.querySelector('[data-path-anchor]') || this.elements.svg.querySelector('path'); 405 if(!path){ 406 return; 407 } 408 path.id = this.pathId; 409 } 410 getPropertyPosition(){ 411 this.startPoint = this.elements.svg ? getComputedStyle(this.elements.svg).getPropertyValue('--start-point') : getComputedStyle(this.elements.line).getPropertyValue('--start-point'); 412 this.endPoint = this.elements.svg ? getComputedStyle(this.elements.svg).getPropertyValue('--end-point') : getComputedStyle(this.elements.line).getPropertyValue('--end-point'); 413 } 414 415 initTextPath() { 416 var _this$getElementSetti2 = this.getElementSettings();
417 this.animation_speed = _this$getElementSetti2.animation_speed; 418 419 var text = this.elements.pathContainer.dataset.text; 420 this.attachIdToPath(); // Generate the `textPath` element with its settings. 421 422 if(this.elements.svg){ 423 this.elements.svg.innerHTML += "\n\t\t\t<text>\n\t\t\t\t<textPath id=\"".concat(this.textPathId, "\" href=\"#").concat(this.pathId, "\"></textPath>\n\t\t\t</text>\n\t\t"); // Regenerate the elements object to have access to `this.elements.textPath`. 424 this.elements.textPath = this.elements.svg.querySelector("#".concat(this.textPathId)); 425 }else if(this.elements.line){ 426 this.elements.line.innerHTML += "<div class=\"simple_line--wrapper\"></div>"; 427 } 428 this.setOffset(this.startPoint ? this.startPoint : 0, this.endPoint ? this.endPoint : 0); 429 this.setText(text); 430 } 431 432 setText(newText) { 433 434 if(!newText){ 435 return; 436 } 437 var _this$getElementSetti4; 438 439 var _this$getElementSetti3 = (_this$getElementSetti4 = this.getElementSettings()) === null || _this$getElementSetti4 === void 0 ? void 0 : _this$getElementSetti4.link, 440 url = _this$getElementSetti3.url, 441 isExternal = _this$getElementSetti3.is_external, 442 nofollow = _this$getElementSetti3.nofollow; 443 444 var target = isExternal ? '_blank' : '', 445 rel = nofollow ? 'nofollow' : ''; // Add link attributes. 446 447 if( 448 this.elements.pathContainer.classList.contains("add_divider") 449 ){ 450 if(this.elements.svg){ 451 newText = '<tspan class="text_divider" dy="'+(-1 * getComputedStyle(this.elements.svg).getPropertyValue('--top-offset'))+'" dx="'+(-1 * getComputedStyle(this.elements.svg).getPropertyValue('--left-offset'))+'"> '.concat((0, newText), '</tspan>'); 452 }else{ 453 newText = '<span class="text"> '.concat((0, newText), '</span>'); 454 } 455 } 456 457 if (url) { 458 newText = "<a href=\"".concat((0, this.escapeHTML)(url), "\" rel=\"").concat(rel, "\" target=\"").concat(target, "\">").concat((0, newText), "</a>"); 459 } // Set the text. 460 461 if( 462 this.elements.pathContainer.classList.contains("add_divider") 463 && !this.elements.pathContainer.classList.contains("clone_text") 464 ){ 465 466 if(this.elements.svg){ 467 newText += '<tspan class="divider" dy="'+getComputedStyle(this.elements.svg).getPropertyValue('--top-offset')+'" dx="'+getComputedStyle(this.elements.svg).getPropertyValue('--left-offset')+'">'+ this.divider() +'</tspan>'; 468 }else{ 469 newText += '<span class="divider_line">'+ this.divider() +'</span>'; 470 } 471 } 472 473 if(this.elements.svg){ 474 this.elements.textPath.innerHTML = ''; 475 this.elements.textPath.innerHTML = newText; // Remove the cloned element if exists. 476 }else{ 477 let newNode = document.createElement('span'); 478 newNode.className = 'text--word'; 479 newNode.innerHTML = newText; 480 this.elements.line.querySelector('.simple_line--wrapper').innerHTML = ''; 481 this.elements.line.querySelector('.simple_line--wrapper').appendChild(newNode); 482 } 483 484 if(this.elements.pathContainer.classList.contains("clone_text")){ 485 this.word = this.elements.svg ? this.elements.textPath : this.elements.line.querySelector('.simple_line--wrapper'); 486 this.getDefautWord = this.word.innerHTML; 487 this.cloneText(); 488 } 489 490 this.appendAnimate(); 491 492 if(this.elements.svg){ 493 var existingClone = this.elements.svg.querySelector("#".concat(this.textPathId, "-clone")); 494 495 if (existingClone) { 496 existingClone.remove(); 497 } // Reverse the text if needed. 498 499 if (this.shouldReverseText()) { 500 // Keep an invisible selectable copy of original element for better a11y. 501 var clone = this.elements.textPath.cloneNode(); 502 clone.id += '-clone'; 503 clone.classList.add('elementor-hidden'); 504 clone.textContent = newText; 505 this.elements.textPath.parentNode.appendChild(clone); 506 this.reverseToRTL(); 507 } 508 } 509 } 510 511 isRTL() { 512 var _this$getElementSetti5 = this.getElementSettings(), 513 direction = _this$getElementSetti5.text_path_direction; 514 515 var isRTL = elementorFrontend.config.is_rtl; 516 517 if (direction) { 518 isRTL = 'rtl' === direction; 519 } 520 521 return isRTL; 522 } 523 524 shouldReverseText() { 525 return this.isRTL() && -1 === navigator.userAgent.indexOf('Firefox'); 526 } 527 528 escapeHTML(str) { 529 var specialChars = { 530 '&': '&', 531 '<': '<', 532 '>': '>', 533 "'": ''', 534 '"': '"' 535 }; 536 return str.replace(/[&<>'"]/g, function (tag) { 537 return specialChars[tag] || tag; 538 }); 539 } 540 541 reverseToRTL() { 542 // Make sure to use the inner `a` tag if exists. 543 var parentElement = this.elements.textPath; 544 parentElement = parentElement.querySelector('a') || parentElement; // Catch all RTL chars and reverse their order. 545 546 var pattern = /([\u0591-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE
54670-\uFEFC\s$&+,:;=?@#|'<>.^*()%!-]+)/ig; // Reverse the text. 547 548 parentElement.textContent = parentElement.textContent.replace(pattern, function (word) { 549 return word.split('').reverse().join(''); 550 }); // Add a11y attributes. 551 552 parentElement.setAttribute('aria-hidden', true); 553 } 554 555 computeOffset() { 556 const fromValue = parseInt(this.startOffset.getDefaultStartPoint); 557 const toValue = parseInt(this.startOffset.getDefaultEndPoint); 558 559 const val = WGLMathUtils.map(this.positionY - window.pageYOffset, wglWinSize.height, -1 * this.svgRect.height, fromValue, toValue); 560 if(fromValue > toValue){ 561 return Math.max(Math.min(val, fromValue), toValue); 562 }else{ 563 return Math.min(Math.max(val, fromValue), toValue); 564 } 565 } 566 567 updateTextPathOffset() { 568 if (!this.elements.textPath && !this.elements.line) { 569 return; 570 } 571 if(this.elements.pathContainer.dataset.typeSvg === 'circle'){ 572 let rotate = 360 * this.startOffset.value / 100; 573 this.elements.svg.style.transform = 'rotate(' + rotate + 'deg)'; 574 }else if(this.elements.pathContainer.dataset.typeSvg === 'wgl_line_simple'){ 575 this.elements.line.style.transform = 'translateX(' + this.startOffset.value + 'px)'; 576 }else{ 577 this.elements.textPath.setAttribute('startOffset', this.startOffset.value + '%'); 578 } 579 } 580 581 divider() { 582 let divider; 583 584 switch (this.elements.pathContainer.dataset.dType) { 585 case 'star': 586 divider = '*'; 587 break; 588 589 case 'line': 590 divider = '//'; 591 break; 592 593 case 'custom': 594 divider = this.elements.pathContainer.dataset.dCustom; 595 break; 596 597 default: 598 break; 599 } 600 601 return divider 602 } 603 604 update() { 605 const currentOffset = this.computeOffset(); 606 this.startOffset.value = WGLMathUtils.lerp(this.startOffset.value, currentOffset, this.startOffset.amt); 607 this.updateTextPathOffset(); 608 } 609 610 render() { 611 if ( this.isVisible ) { 612 this.update(); 613 } 614 requestAnimationFrame(() => this.render()); 615 } 616 } 617 618 jQuery(window).on('elementor/frontend/init', function () { 619 620 const addHandler = ($element) => { 621 elementorFrontend.elementsHandler.addHandler(WGLTextPathHandlerClass, { 622 $element, 623 }); 624 }; 625 626 elementorFrontend.hooks.addAction('frontend/element_ready/wgl-text-path.default', addHandler); 627 }); 628 629})(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.