PageSourceSearch

https://brachetti.com/wp-content/plugins/lunfest-core/includes/elementor//assets/js/wgl_text_path.js?ver=1.0.1

js brachetti.com collected 2026-10-02 11:27:42 UTC 27,043 bytes, 629 lines download raw bytes

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            '&': '&amp;',
531            '<': '&lt;',
532            '>': '&gt;',
533            "'": '&#39;',
534            '"': '&quot;'
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.