# king-addons/51.1.78/includes/widgets/Scroll_Story_Sections/script.js

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

- Page: https://pluginprobe.com/plugins/king-addons/51.1.78/code/includes/widgets/Scroll_Story_Sections/script.js
- Raw: https://pluginprobe.com/plugins/king-addons/51.1.78/raw/includes/widgets/Scroll_Story_Sections/script.js
- Modified: 2026-08-25T03:27:10+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.78/code/includes/widgets/Scroll_Story_Sections/script.js#L10-L20`.

```javascript
"use strict";

(function ($) {
    const clamp = (value, min, max) => Math.min(Math.max(value, min), max);

    const parseSettings = (root) => {
        if (!root || !root.dataset) {
            return {};
        }

        try {
            return JSON.parse(root.dataset.settings || "{}");
        } catch (e) {
            return {};
        }
    };

    const getScrollTop = () =>
        window.pageYOffset || document.documentElement.scrollTop || 0;

    const getOffsetTop = (element) => {
        const rect = element.getBoundingClientRect();
        return rect.top + getScrollTop();
    };

    const initScrollStory = ($scope) => {
        const root = $scope.find(".king-addons-scroll-story")[0];
        if (!root || root.dataset.scrollStoryInit === "yes") {
            return;
        }

        const steps = Array.from(
            root.querySelectorAll(".king-addons-scroll-story__step")
        );
        const panels = Array.from(
            root.querySelectorAll(".king-addons-scroll-story__panel")
        );
        const list = root.querySelector(".king-addons-scroll-story__steps");

        if (!steps.length || steps.length !== panels.length) {
            return;
        }

        root.dataset.scrollStoryInit = "yes";

        const settings = parseSettings(root);
        const activationOffset = clamp(
            Number(settings.activationOffset) || 40,
            10,
            80
        );
        const smoothScroll =
            settings.smoothScroll !== false &&
            settings.smoothScroll !== "no" &&
            settings.smoothScroll !== "0";
        const scrollDuration = Math.max(
            Number(settings.scrollDuration) || 600,
            0
        );

        const prefersReduced =
            window.matchMedia &&
            window.matchMedia("(prefers-reduced-motion: reduce)").matches;
        const isEditor =
            window.elementorFrontend &&
            elementorFrontend.isEditMode &&
            elementorFrontend.isEditMode();

        let activeIndex = -1;

        const setActive = (index) => {
            const nextIndex = clamp(index, 0, steps.length - 1);
            if (nextIndex === activeIndex) {
                return;
            }

            activeIndex = nextIndex;
            root.setAttribute("data-active-index", String(activeIndex));

            steps.forEach((step, idx) => {
                const isActive = idx === activeIndex;
                step.classList.toggle("is-active", isActive);
                step.classList.toggle("is-completed", idx < activeIndex);
                step.classList.toggle("is-upcoming", idx > activeIndex);

                const button = step.querySelector(
                    ".king-addons-scroll-story__step-button"
                );
                if (button) {
                    if (isActive) {
                        button.setAttribute("aria-current", "step");
                    } else {
                        button.removeAttribute("aria-current");
                    }
                }
            });

            panels.forEach((panel, idx) => {
                const isActive = idx === activeIndex;
                panel.classList.toggle("is-active", isActive);
                panel.setAttribute("aria-hidden", isActive ? "false" : "true");
                if (isActive) {
                    panel.removeAttribute("hidden");
                } else {
                    panel.setAttribute("hidden", "hidden");
                }
            });
        };

        const getClosestIndex = () => {
            const threshold = window.innerHeight * (activationOffset / 100);
            let intersecting = null;
            let lastAbove = null;

            steps.forEach((step, idx) => {
                const rect = step.getBoundingClientRect();
                if (rect.top <= threshold && rect.bottom >= threshold) {
                    intersecting = idx;
                }
                if (rect.top <= threshold) {
                    lastAbove = idx;
                }
            });

            if (intersecting !== null) {
                return intersecting;
            }

            if (list) {
                const listRect = list.getBoundingClientRect();
                if (listRect.top >= 0 && listRect.bottom < threshold) {
                    return activeIndex >= 0 ? activeIndex : 0;
                }
            }

            if (lastAbove !== null) {
                return lastAbove;
            }

            return activeIndex >= 0 ? activeIndex : 0;
        };

        const smoothScrollTo = (targetY, duration) => {
            if (prefersReduced || duration <= 0) {
                window.scrollTo(0, targetY);
                return;
            }

            const startY = getScrollTop();
            const distance = targetY - startY;
            const startTime =
                (window.performance && performance.now()) || Date.now();

            const easeInOutQuad = (t) =>
                t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;

            const tick = (now) => {
                const currentTime =
                    (window.performance && performance.now()) || now;
                const elapsed = currentTime - startTime;
                const progress = Math.min(elapsed / duration, 1);
                const eased = easeInOutQuad(progress);

                window.scrollTo(0, startY + distance * eased);

                if (progress < 1) {
                    window.requestAnimationFrame(tick);
                }
            };

            window.requestAnimationFrame(tick);
        };

        const scrollToStep = (index) => {
            const target = steps[index];
            if (!target) {
                return;
            }

            const offsetPx = window.innerHeight * (activationOffset / 100);
            const targetTop = Math.max(getOffsetTop(target) - offsetPx, 0);

            if (!smoothScroll || prefersReduced) {
                window.scrollTo(0, targetTop);
                return;
            }

            smoothScrollTo(targetTop, scrollDuration);
        };

        steps.forEach((step, index) => {
            const button = step.querySelector(
                ".king-addons-scroll-story__step-button"
            );
            if (!button) {
                return;
            }

            button.addEventListener("click", (event) => {
                event.preventDefault();
                setActive(index);
                if (!isEditor) {
                    scrollToStep(index);
                }
            });

            button.addEventListener("keydown", (event) => {
                if (event.key !== "Enter" && event.key !== " ") {
                    return;
                }
                event.preventDefault();
                setActive(index);
                if (!isEditor) {
                    scrollToStep(index);
                }
            });
        });

        if (isEditor) {
            setActive(0);
            return;
        }

        setActive(getClosestIndex());

        if ("IntersectionObserver" in window) {
            const observerMargin = `-${activationOffset}% 0px -${
                100 - activationOffset
            }% 0px`;
            const observer = new IntersectionObserver(
                (entries) => {
                    const shouldUpdate = entries.some(
                        (entry) => entry.isIntersecting
                    );
                    if (shouldUpdate) {
                        setActive(getClosestIndex());
                    }
                },
                {
                    rootMargin: observerMargin,
                    threshold: 0,
                }
            );

            steps.forEach((step) => observer.observe(step));
        } else {
            let ticking = false;
            const onScroll = () => {
                if (ticking) {
                    return;
                }
                ticking = true;
                window.requestAnimationFrame(() => {
                    setActive(getClosestIndex());
                    ticking = false;
                });
            };
            window.addEventListener("scroll", onScroll, { passive: true });
        }

        window.addEventListener("resize", () => {
            setActive(getClosestIndex());
        });
    };

    $(window).on("elementor/frontend/init", () => {
        elementorFrontend.hooks.addAction(
            "frontend/element_ready/king-addons-scroll-story-sections.default",
            initScrollStory
        );
    });
})(jQuery);

```
