# king-addons/51.1.83/includes/widgets/Auto_Scrolling_Text/script.js

King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder, version 51.1.83. 142 lines.

- Page: https://pluginprobe.com/plugins/king-addons/51.1.83/code/includes/widgets/Auto_Scrolling_Text/script.js
- Raw: https://pluginprobe.com/plugins/king-addons/51.1.83/raw/includes/widgets/Auto_Scrolling_Text/script.js
- Modified: 2026-09-16T13:52:08+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/king-addons/51.1.83/code/includes/widgets/Auto_Scrolling_Text/script.js#L10-L20`.

```javascript
/**
 * Auto Scrolling Text — keeps the marquee running without a gap.
 *
 * The track scrolls left by exactly the width of one copy of the content. For
 * that to look continuous, the track has to hold enough copies that the ones
 * still to the right cover the visible strip at the moment the animation
 * resets. One copy alone leaves a growing empty space and then jumps back,
 * which is what the widget used to do.
 *
 * Rebuilding the copies forces layout and paint on a wide element, so it is
 * done only when the number of copies actually has to change — otherwise a
 * resize (which fires constantly on mobile as the address bar hides) would
 * show up as a stutter in the animation.
 */
(function () {
    'use strict';

    var GROUP = 'king-addons-auto-scrolling-text-group';

    function requiredCopies(groupWidth, visibleWidth) {
        // One copy scrolls out of view; the rest have to cover the strip.
        return Math.max(2, Math.ceil(visibleWidth / groupWidth) + 1);
    }

    function fill(track) {
        var strip = track.parentElement;
        if (!strip) {
            return;
        }

        var original = track.querySelector('.' + GROUP);
        if (!original) {
            return;
        }

        // Each copy is its own flex item, so the first one can be measured
        // without disturbing the copies already in place.
        var groupWidth = original.getBoundingClientRect().width;
        var visibleWidth = strip.getBoundingClientRect().width;
        if (groupWidth <= 0 || visibleWidth <= 0) {
            return;
        }

        var wanted = requiredCopies(groupWidth, visibleWidth);
        var current = track.querySelectorAll('.' + GROUP).length;
        if (current === wanted) {
            return;                      // nothing to do: leave the animation alone
        }

        if (current > wanted) {
            var extra = track.querySelectorAll('.' + GROUP);
            for (var i = extra.length - 1; i >= wanted; i--) {
                extra[i].remove();
            }
        } else {
            var fragment = document.createDocumentFragment();
            for (var c = current; c < wanted; c++) {
                var clone = original.cloneNode(true);
                // A screen reader should hear the message once, not once per copy.
                clone.setAttribute('aria-hidden', 'true');
                fragment.appendChild(clone);
            }
            track.appendChild(fragment);
        }

        // Shifting by one copy lands the next copy exactly where this one began.
        track.style.setProperty('--king-addons-marquee-shift', (-100 / wanted) + '%');
    }

    function observe(track) {
        if (track.kingAddonsMarqueeWatched) {
            return;
        }
        track.kingAddonsMarqueeWatched = true;

        var strip = track.parentElement;
        if (strip && window.ResizeObserver) {
            // Reacts to the strip actually changing width, unlike window resize,
            // which also fires on mobile scroll and on every orientation nudge.
            new ResizeObserver(function () { fill(track); }).observe(strip);
        }
    }

    function watchAll(root) {
        var scope = root && root.querySelectorAll ? root : document;
        Array.prototype.forEach.call(
            scope.querySelectorAll('.king-addons-auto-scrolling-text-wrapper'),
            function (track) {
                fill(track);        // cheap: returns straight away when nothing changed
                observe(track);     // one-time
            }
        );
    }

    function onReady() {
        watchAll(document);

        // Safety net for anything ResizeObserver does not catch (and for browsers
        // without it). fill() measures and returns when nothing has to change,
        // so this costs a measurement and never touches the DOM on its own.
        var timer = null;
        window.addEventListener('resize', function () {
            clearTimeout(timer);
            timer = setTimeout(function () { watchAll(document); }, 200);
        });

        // Web fonts land after first paint and change how wide a copy is.
        if (document.fonts && document.fonts.ready) {
            document.fonts.ready.then(function () { watchAll(document); });
        }
    }

    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        onReady();
    } else {
        document.addEventListener('DOMContentLoaded', onReady);
    }

    /**
     * Re-measure when Elementor re-renders the widget, which it does on every
     * edit in the editor. Elementor fires this through jQuery, so it has to be
     * bound with jQuery — a native listener never hears it.
     */
    function bindElementor() {
        if (!window.elementorFrontend || !elementorFrontend.hooks) {
            return false;
        }
        elementorFrontend.hooks.addAction(
            'frontend/element_ready/king-addons-auto-scrolling-text.default',
            function ($scope) { watchAll($scope && $scope[0] ? $scope[0] : document); }
        );
        return true;
    }

    if (window.jQuery) {
        // Covers both orders: Elementor still to start, or already started.
        if (!bindElementor()) {
            jQuery(window).on('elementor/frontend/init', bindElementor);
        }
    }
}());

```
