# king-addons/51.1.65/includes/widgets/Interactive_Image_Sheen/script.js

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

- Page: https://pluginprobe.com/plugins/king-addons/51.1.65/code/includes/widgets/Interactive_Image_Sheen/script.js
- Raw: https://pluginprobe.com/plugins/king-addons/51.1.65/raw/includes/widgets/Interactive_Image_Sheen/script.js
- Modified: 2026-06-28T21:26:56+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.65/code/includes/widgets/Interactive_Image_Sheen/script.js#L10-L20`.

```javascript
"use strict";
/**
 * Interactive Image Sheen Widget JavaScript.
 *
 * Handles follow cursor mode, mobile fallback, and accessibility.
 * Pro features: cursor tracking with lerp smoothing, reveal on scroll.
 *
 * @package King_Addons
 */
(($) => {
    /**
     * Parse JSON settings from data attribute.
     *
     * @param {HTMLElement} root - Root element.
     * @returns {Object} Parsed settings.
     */
    const parseSettings = (root) => {
        if (!root || !root.dataset) {
            return {};
        }

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

    /**
     * Check if user prefers reduced motion.
     *
     * @returns {boolean}
     */
    const prefersReducedMotion = () =>
        window.matchMedia &&
        window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    /**
     * Check if device is touch-only.
     *
     * @returns {boolean}
     */
    const isTouchDevice = () =>
        window.matchMedia &&
        window.matchMedia("(hover: none) and (pointer: coarse)").matches;

    /**
     * Linear interpolation.
     *
     * @param {number} start - Start value.
     * @param {number} end - End value.
     * @param {number} factor - Interpolation factor (0-1).
     * @returns {number}
     */
    const lerp = (start, end, factor) => start + (end - start) * factor;

    /**
     * Clamp value between min and max.
     *
     * @param {number} value - Value to clamp.
     * @param {number} min - Minimum.
     * @param {number} max - Maximum.
     * @returns {number}
     */
    const clamp = (value, min, max) => Math.min(Math.max(value, min), max);

    /**
     * Initialize widget instance.
     *
     * @param {jQuery} $scope - Widget scope.
     */
    const initWidget = ($scope) => {
        const root = $scope.find(".kng-iis").first()[0];
        if (!root || root.dataset.kngIisInit === "yes") {
            return;
        }

        root.dataset.kngIisInit = "yes";
        const options = parseSettings(root);

        const isPro = root.classList.contains("kng-iis--pro");
        const reducedMotion = prefersReducedMotion();
        const isTouch = isTouchDevice();

        // Pro settings
        const followEnabled = isPro && options.followEnabled === true;
        const followStrength = options.followStrength || 50;
        const followSmoothing = options.followSmoothing || 0.1;
        const followAngleShift = options.followAngleShift === true;
        const mobileMode = options.mobileMode || "disable";
        const focusTrigger = options.focusTrigger !== false;

        // State for follow cursor
        let targetX = 0.5;
        let targetY = 0.5;
        let currentX = 0.5;
        let currentY = 0.5;
        let isHovering = false;
        let animationId = null;

        const sheens = root.querySelectorAll(".kng-iis__sheen");

        /**
         * Update sheen position based on cursor.
         */
        const updateSheenPosition = () => {
            if (!isHovering && Math.abs(currentX - 0.5) < 0.01 && Math.abs(currentY - 0.5) < 0.01) {
                cancelAnimationFrame(animationId);
                animationId = null;
                return;
            }

            // Lerp towards target
            currentX = lerp(currentX, isHovering ? targetX : 0.5, followSmoothing);
            currentY = lerp(currentY, isHovering ? targetY : 0.5, followSmoothing);

            // Calculate transform
            const strength = followStrength / 100;
            const translateX = (currentX - 0.5) * 200 * strength;
            const translateY = (currentY - 0.5) * 50 * strength;

            // Update sheen position
            sheens.forEach((sheen, index) => {
                const layerFactor = 1 - index * 0.15; // Slightly different for each layer
                let transform = `translateX(${translateX * layerFactor}%) translateY(${translateY * layerFactor}%)`;

                if (followAngleShift) {
                    const angle = (currentX - 0.5) * 30;
                    transform += ` rotate(${angle}deg)`;
                }

                sheen.style.transform = transform;
            });

            animationId = requestAnimationFrame(updateSheenPosition);
        };

        /**
         * Setup follow cursor interaction.
         */
        const setupFollowCursor = () => {
            if (!followEnabled || reducedMotion || isTouch) {
                return;
            }

            root.addEventListener("mouseenter", () => {
                isHovering = true;
                root.classList.add("kng-iis--following");
                if (!animationId) {
                    animationId = requestAnimationFrame(updateSheenPosition);
                }
            });

            root.addEventListener("mouseleave", () => {
                isHovering = false;
                root.classList.remove("kng-iis--following");
                // Animation continues to lerp back to center
                if (!animationId) {
                    animationId = requestAnimationFrame(updateSheenPosition);
                }
            });

            root.addEventListener("mousemove", (e) => {
                const rect = root.getBoundingClientRect();
                targetX = clamp((e.clientX - rect.left) / rect.width, 0, 1);
                targetY = clamp((e.clientY - rect.top) / rect.height, 0, 1);
            });
        };

        /**
         * Setup mobile reveal on scroll (Pro).
         */
        const setupMobileReveal = () => {
            if (mobileMode !== "reveal" || !isTouch) {
                return;
            }

            if (!("IntersectionObserver" in window)) {
                return;
            }

            const observer = new IntersectionObserver(
                (entries) => {
                    entries.forEach((entry) => {
                        if (entry.isIntersecting) {
                            root.classList.add("kng-iis--in-view");
                            observer.unobserve(root);
                        }
                    });
                },
                {
                    threshold: 0.5,
                }
            );

            observer.observe(root);
        };

        /**
         * Setup focus trigger for accessibility.
         */
        const setupFocusTrigger = () => {
            if (!focusTrigger) {
                return;
            }

            const focusable = root.querySelectorAll("a, button, [tabindex]");
            focusable.forEach((el) => {
                el.addEventListener("focus", () => {
                    root.classList.add("kng-iis--focused");
                });

                el.addEventListener("blur", () => {
                    root.classList.remove("kng-iis--focused");
                });
            });
        };

        /**
         * Handle reduced motion preference.
         */
        const setupReducedMotion = () => {
            if (!reducedMotion) {
                return;
            }

            // CSS handles the visual changes via media query
            // We just ensure no JS animations run
            if (animationId) {
                cancelAnimationFrame(animationId);
                animationId = null;
            }
        };

        /**
         * Parse sheen color to RGB for CSS calc.
         * (Helper for advanced gradient calculations)
         */
        const setupSheenColor = () => {
            const computedStyle = getComputedStyle(root);
            const sheenColor = computedStyle.getPropertyValue("--kng-iis-sheen-color").trim();

            // Convert hex to RGB for CSS calc
            if (sheenColor.startsWith("#")) {
                let r, g, b;
                if (sheenColor.length === 4) {
                    r = parseInt(sheenColor[1] + sheenColor[1], 16);
                    g = parseInt(sheenColor[2] + sheenColor[2], 16);
                    b = parseInt(sheenColor[3] + sheenColor[3], 16);
                } else {
                    r = parseInt(sheenColor.slice(1, 3), 16);
                    g = parseInt(sheenColor.slice(3, 5), 16);
                    b = parseInt(sheenColor.slice(5, 7), 16);
                }
                root.style.setProperty("--kng-iis-sheen-color-rgb", `${r}, ${g}, ${b}`);
            }
        };

        // Initialize
        setupSheenColor();
        setupReducedMotion();

        if (isPro) {
            setupFollowCursor();
            setupMobileReveal();
        }

        setupFocusTrigger();

        // Cleanup function
        root.kngIisCleanup = () => {
            if (animationId) {
                cancelAnimationFrame(animationId);
            }
        };
    };

    // Initialize on Elementor frontend ready
    $(window).on("elementor/frontend/init", () => {
        elementorFrontend.hooks.addAction(
            "frontend/element_ready/king-addons-interactive-image-sheen.default",
            ($scope) => {
                elementorFrontend.elementsHandler.addHandler(
                    elementorModules.frontend.handlers.Base.extend({
                        onInit: function () {
                            initWidget(this.$element);
                        },
                    }),
                    { $element: $scope }
                );
            }
        );

        // Pro version
        elementorFrontend.hooks.addAction(
            "frontend/element_ready/king-addons-interactive-image-sheen-pro.default",
            ($scope) => {
                elementorFrontend.elementsHandler.addHandler(
                    elementorModules.frontend.handlers.Base.extend({
                        onInit: function () {
                            initWidget(this.$element);
                        },
                    }),
                    { $element: $scope }
                );
            }
        );
    });
})(jQuery);

```
