PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.83
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.83
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / widgets / Auto_Scrolling_Text / script.js

script.js in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.83, at includes/widgets/Auto_Scrolling_Text/script.js

142 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Auto Scrolling Text — keeps the marquee running without a gap.
3 *
4 * The track scrolls left by exactly the width of one copy of the content. For
5 * that to look continuous, the track has to hold enough copies that the ones
6 * still to the right cover the visible strip at the moment the animation
7 * resets. One copy alone leaves a growing empty space and then jumps back,
8 * which is what the widget used to do.
9 *
10 * Rebuilding the copies forces layout and paint on a wide element, so it is
11 * done only when the number of copies actually has to change — otherwise a
12 * resize (which fires constantly on mobile as the address bar hides) would
13 * show up as a stutter in the animation.
14 */
15 (function () {
16 'use strict';
17
18 var GROUP = 'king-addons-auto-scrolling-text-group';
19
20 function requiredCopies(groupWidth, visibleWidth) {
21 // One copy scrolls out of view; the rest have to cover the strip.
22 return Math.max(2, Math.ceil(visibleWidth / groupWidth) + 1);
23 }
24
25 function fill(track) {
26 var strip = track.parentElement;
27 if (!strip) {
28 return;
29 }
30
31 var original = track.querySelector('.' + GROUP);
32 if (!original) {
33 return;
34 }
35
36 // Each copy is its own flex item, so the first one can be measured
37 // without disturbing the copies already in place.
38 var groupWidth = original.getBoundingClientRect().width;
39 var visibleWidth = strip.getBoundingClientRect().width;
40 if (groupWidth <= 0 || visibleWidth <= 0) {
41 return;
42 }
43
44 var wanted = requiredCopies(groupWidth, visibleWidth);
45 var current = track.querySelectorAll('.' + GROUP).length;
46 if (current === wanted) {
47 return; // nothing to do: leave the animation alone
48 }
49
50 if (current > wanted) {
51 var extra = track.querySelectorAll('.' + GROUP);
52 for (var i = extra.length - 1; i >= wanted; i--) {
53 extra[i].remove();
54 }
55 } else {
56 var fragment = document.createDocumentFragment();
57 for (var c = current; c < wanted; c++) {
58 var clone = original.cloneNode(true);
59 // A screen reader should hear the message once, not once per copy.
60 clone.setAttribute('aria-hidden', 'true');
61 fragment.appendChild(clone);
62 }
63 track.appendChild(fragment);
64 }
65
66 // Shifting by one copy lands the next copy exactly where this one began.
67 track.style.setProperty('--king-addons-marquee-shift', (-100 / wanted) + '%');
68 }
69
70 function observe(track) {
71 if (track.kingAddonsMarqueeWatched) {
72 return;
73 }
74 track.kingAddonsMarqueeWatched = true;
75
76 var strip = track.parentElement;
77 if (strip && window.ResizeObserver) {
78 // Reacts to the strip actually changing width, unlike window resize,
79 // which also fires on mobile scroll and on every orientation nudge.
80 new ResizeObserver(function () { fill(track); }).observe(strip);
81 }
82 }
83
84 function watchAll(root) {
85 var scope = root && root.querySelectorAll ? root : document;
86 Array.prototype.forEach.call(
87 scope.querySelectorAll('.king-addons-auto-scrolling-text-wrapper'),
88 function (track) {
89 fill(track); // cheap: returns straight away when nothing changed
90 observe(track); // one-time
91 }
92 );
93 }
94
95 function onReady() {
96 watchAll(document);
97
98 // Safety net for anything ResizeObserver does not catch (and for browsers
99 // without it). fill() measures and returns when nothing has to change,
100 // so this costs a measurement and never touches the DOM on its own.
101 var timer = null;
102 window.addEventListener('resize', function () {
103 clearTimeout(timer);
104 timer = setTimeout(function () { watchAll(document); }, 200);
105 });
106
107 // Web fonts land after first paint and change how wide a copy is.
108 if (document.fonts && document.fonts.ready) {
109 document.fonts.ready.then(function () { watchAll(document); });
110 }
111 }
112
113 if (document.readyState === 'complete' || document.readyState === 'interactive') {
114 onReady();
115 } else {
116 document.addEventListener('DOMContentLoaded', onReady);
117 }
118
119 /**
120 * Re-measure when Elementor re-renders the widget, which it does on every
121 * edit in the editor. Elementor fires this through jQuery, so it has to be
122 * bound with jQuery — a native listener never hears it.
123 */
124 function bindElementor() {
125 if (!window.elementorFrontend || !elementorFrontend.hooks) {
126 return false;
127 }
128 elementorFrontend.hooks.addAction(
129 'frontend/element_ready/king-addons-auto-scrolling-text.default',
130 function ($scope) { watchAll($scope && $scope[0] ? $scope[0] : document); }
131 );
132 return true;
133 }
134
135 if (window.jQuery) {
136 // Covers both orders: Elementor still to start, or already started.
137 if (!bindElementor()) {
138 jQuery(window).on('elementor/frontend/init', bindElementor);
139 }
140 }
141 }());
142