How to enable simultaneous independent scrolling for multiple draggable elements using Interact.js?

I’m building a multi-touch application using HTML, CSS, jQuery, and Interact.js. The application has draggable squares that display content (HTML files loaded via iframes). Each square has its own content area, and I want users to scroll the content of different squares independently.

The issue arises when one user scrolls the content of one square while another user tries to scroll another square. In this case, the scrolling in the first square stops, and only the second square scrolls. My goal is to allow simultaneous independent scrolling in all squares without interruption.

Implemented Interact.js for drag, gesture, and touch-based interactions.
Used touchmove and touchend events for scrolling inside the iframe content areas.
Disabled interactions temporarily during scrolling using interact(element).unset() and re-enabled them after the scroll ends.
Expected Behavior:

Users should be able to scroll the content of multiple squares independently and simultaneously.
Actual Behavior:

When one user starts scrolling a square, any new scrolling interaction in another square interrupts or stops the first scroll.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Multi-touch Squares</title>
    <script src="js/jquery.min.js"></script>
    <script src="js/interact.min.js"></script>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    
    <!-- Buttons to show hidden squares -->
    <div class="show-buttons">
      <div class="show-btn type1" id="circle1"></div>
      <div class="show-btn type2" id="circle2"></div>
      <div class="show-btn type3" id="circle3"></div>
      <div class="show-btn type4" id="circle4"></div>
    </div>

    <!-- Boundary container for draggable squares -->
    <div id="boundary">
      <!-- Square 1 -->
      <div id="square1" class="square hidden">
        <div class="square-header">
          Name of Item 1
          <div class="close-btn">&times;</div>
        </div>
        <div class="square-content">
          <iframe src="a.html" frameborder="0" style="pointer-events: auto;"></iframe>
        </div>
      </div>
      <!-- Square 2 -->
      <div id="square2" class="square hidden">
        <div class="square-header">
          Name of Item 2
          <div class="close-btn">&times;</div>
        </div>
        <div class="square-content">
          <iframe src="b.html" frameborder="0" style="pointer-events: auto;"></iframe>
        </div>
      </div>
      <!-- Square 3 -->
      <div id="square3" class="square hidden">
        <div class="square-header">
          Name of Item 3
          <div class="close-btn">&times;</div>
        </div>
        <div class="square-content">
          <iframe src="c.html" frameborder="0" style="pointer-events: auto;"></iframe>
        </div>
      </div>
      <!-- Square 4 -->
      <div id="square4" class="square hidden">
        <div class="square-header">
          Name of Item 4
          <div class="close-btn">&times;</div>
        </div>
        <div class="square-content">
          <iframe src="d.html" frameborder="0" style="pointer-events: auto;"></iframe>
        </div>
      </div>
    </div>


    
    <script src="js/script.js"></script>
  </body>
</html>
/* Remove body margins and center content vertically and horizontally */
body, html {
    background-color: #3a3a3a;
    margin: 0;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
}

/* Define the boundary container for draggable squares */
#boundary {
    width: 1920px;
    height: 1080px;
    border: 2px solid #333;
    position: relative;
}

/* Style for draggable squares */
.square {
    width: 480px;
    height: 1080px;
    position: absolute;
    user-select: none;
    transform-origin: center;
    background-color: white;
    overflow: hidden;
    z-index: 500;
}

/* Content inside each square (scrollable if necessary) */
.square-content {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    overflow: auto;
    -webkit-overflow-scrolling: touch;
}

/* Make iframes fit square dimensions and remove borders */
.square iframe {
    width: 100%;
    height: 100%;
    border: none;
}

/* Individual styling for each square with unique colors and positions */
#square1 {
    border: 2px solid red;
    left: 50px;
    top: 50px;
    border-top: 30px solid #FF7F7F;
}
#square2 {
    border: 2px solid red;
    left: 300px;
    top: 50px;
    border-top: 30px solid #ADD8E6;
}
#square3 {
    border: 2px solid red;
    left: 50px;
    top: 300px;
    border-top: 30px solid #90EE90;
}
#square4 {
    border: 2px solid blue;
    left: 50px;
    top: 50px;
    border-top: 30px solid #ADD8E6;
}

/* Close button styling */
.close-btn {
    position: absolute;
    top: 5px;
    right: 5px;
    background: white;
    border: 1px solid #333;
    border-radius: 50%;
    width: 20px;
    height: 20px;
    line-height: 18px;
    text-align: center;
    cursor: pointer;
    font-family: Arial, sans-serif;
    font-size: 12px;
    z-index: 500;
    touch-action: none;
}

/* Styling for the show buttons container */
.show-buttons {
    position: absolute;
    top: 0%;             /* Position from the top */
    left: 50%;             /* Center horizontally */
    transform: translateX(-50%); /* Adjust to truly center */
    z-index: 1;
    display: flex;         /* Use flexbox for alignment */
}

/* Styling for each show button */
.show-btn {
    width: 50px;           /* Set a fixed width */
    height: 50px;          /* Set a fixed height */
    margin: 5px;           /* Space between circles */
    border-radius: 50%;    /* Make it circular */
    cursor: pointer;        /* Pointer cursor on hover */
    touch-action: none;     /* Disable touch actions */
}

/* Specific styles for each show button */
#circle1 { 
    width: 480px;          /* Specific width */
    height: 1080px;        /* Specific height */
    border-radius: 0;      /* Ensure square shape */
    background-color: red; 
}
#circle2 { 
    width: 480px;          /* Specific width */
    height: 1080px;        /* Specific height */
    border-radius: 0;      /* Ensure square shape */
    background-color: blue; 
}
#circle3 {
    width: 480px;          /* Specific width */
    height: 1080px;        /* Specific height */
    border-radius: 0;      /* Ensure square shape */
    background-color: green; 
}
#circle4 { 
    width: 480px;          /* Specific width */
    height: 1080px;        /* Specific height */
    border-radius: 0;      /* Ensure square shape */
    background-color: yellow; 
}

/* Hide buttons by default */
.hidden {
    display: none;
}

/* Styles for squares in scroll mode */
.square.scroll-mode {
    touch-action: pan-y pinch-zoom;
}

/* Styles for squares not in scroll mode */
.square:not(.scroll-mode) {
    touch-action: none;
}

/* Ensure iframe is interactive */
.square iframe {
    pointer-events: auto;
}

/* Disable interactions when not scrolling */
.square:not(.scroll-mode) iframe {
    pointer-events: none;
}

/* Allow interaction when in scroll mode */
.square.scroll-mode iframe {
    pointer-events: auto;
}
$(document).ready(function () {
    // Object to store transformation states (position, scale, rotation) for each square
    const transformStates = {};

    // Boundary element where squares are confined
    const boundary = document.getElementById('boundary');

    // Variables for touch-based scrolling in square content
    let touchStartY = 0;
    let scrolling = false;

    // Variables for tracking click vs drag
    let isDragging = false;
    let dragStartTime = 0;
    const DRAG_THRESHOLD = 10; // Pixel movement threshold
    const DRAG_TIME_THRESHOLD = 200; // Milliseconds

    // Track the highest z-index dynamically
    let highestZIndex = 1000;

    // Prevent default behavior for touch events on buttons to avoid accidental gestures
    $('.show-btn, .close-btn').on('touchstart touchmove touchend', function (e) {
        e.preventDefault(); // Prevent default touch behavior
    });

    // Disable dragging for the show-buttons (circles)
    interact('.show-btn').unset();

    // Explicitly ensure show-buttons cannot be moved
    $('.show-btn').on('mousedown touchstart', function (e) {
        e.stopPropagation(); // Stop event propagation to prevent dragging
    });

    // Function to set up interactions for draggable and gesturable elements
    function setupInteractions(element) {
        interact(element)
            .draggable({
                inertia: true, // Enable inertia for smooth dragging
                modifiers: [
                    interact.modifiers.restrict({
                        restriction: boundary, // Restrict dragging within boundary
                        elementRect: { top: 0, left: 0, bottom: 1, right: 1 },
                    }),
                ],
                listeners: {
                    start(event) {
                        bringToFrontIfNeeded(event.target); // Bring square to the front if needed
                    },
                    move: dragMoveListener, // Handle drag movements
                    end(event) {
                        // Retain z-index on release
                        const state = transformStates[event.target.id];
                        state.scale = state.currentScale;
                    },
                },
            })
            .gesturable({
                listeners: {
                    start(event) {
                        // Initialize the starting angle and scale for gestures
                        const state = transformStates[event.target.id];
                        state.startAngle = state.angle - event.angle;
                        state.startScale = state.currentScale;
                    },
                    move(event) {
                        // Update the scale and angle based on gesture movements
                        const state = transformStates[event.target.id];
                        const newScale = state.startScale * event.scale;
                        state.currentScale = Math.max(0.5, Math.min(1.5, newScale));

                        const interpolationFactor = 0.1;
                        state.scale = state.scale + (state.currentScale - state.scale) * interpolationFactor;
                        state.angle = state.startAngle + event.angle;

                        updateElementTransform(event.target); // Apply transformations
                    },
                    end(event) {
                        // Finalize the scale after gesture ends
                        const state = transformStates[event.target.id];
                        state.scale = state.currentScale;
                    },
                },
            });

        // Add touch/click listener to bring tapped squares to the front
        $(element).on('mousedown touchstart', function (e) {
            if (!isDragging) {
                bringToFrontIfNeeded(element); // Bring to front if not dragging
            }
        });

        const $content = $(element).find('.square-content');

        // Handle touch-based scrolling within square content
        $content.on('touchmove', function (e) {
            const touchY = e.originalEvent.touches[0].clientY;
            const deltaY = touchStartY - touchY;
            if (!scrolling && Math.abs(deltaY) > 10) {
                scrolling = true;
                $(element).addClass('scroll-mode'); // Enable scroll mode
                interact(element).unset(); // Disable interactions during scroll
            }

            if (scrolling) {
                e.stopPropagation(); // Stop event propagation
                this.scrollTop += deltaY; // Scroll content
                touchStartY = touchY; // Update touch start position
            }
        });

        // Reset scrolling state on touch end
        $content.on('touchend', function () {
            scrolling = false;
            $(element).removeClass('scroll-mode'); // Disable scroll mode
            setTimeout(() => setupInteractions(element), 100); // Re-enable interactions
        });
    }

    // Function to set up handlers for each circle button
    function setupCircleHandlers(circleId, squareIds) {
        $(`#${circleId}`).on("mousedown touchstart", function (e) {
            isDragging = false;
            dragStartTime = Date.now(); // Record start time

            const startX = e.type === "mousedown" ? e.pageX : e.originalEvent.touches[0].pageX;
            const startY = e.type === "mousedown" ? e.pageY : e.originalEvent.touches[0].pageY;

            $(document).on("mousemove touchmove", function (moveEvent) {
                const currentX = moveEvent.type === "mousemove" ? moveEvent.pageX : moveEvent.originalEvent.touches[0].pageX;
                const currentY = moveEvent.type === "mousemove" ? moveEvent.pageY : moveEvent.originalEvent.touches[0].pageY;

                if (Math.abs(currentX - startX) > DRAG_THRESHOLD || Math.abs(currentY - startY) > DRAG_THRESHOLD) {
                    isDragging = true; // Mark as dragging if movement exceeds threshold
                }
            });

            $(document).on("mouseup touchend", function () {
                $(document).off("mousemove touchmove");
                $(document).off("mouseup touchend");

                if (!isDragging && Date.now() - dragStartTime < DRAG_TIME_THRESHOLD) {
                    positionSquare(circleId, squareIds[0]); // Position square if not dragging
                }
            });
        });
    }

    // Function to position a square based on the corresponding circle button
    function positionSquare(circleId, squareId) {
        const circlePosition = $(`#${circleId}`).offset();
        const top = circlePosition.top;
        const left = circlePosition.left;

        const $square = $(`#${squareId}`);
        $square.css({
            top: top + 'px',
            left: left + 'px',
            transform: 'translate(0px, 0px) scale(1) rotate(0deg)'
        }).removeClass('hidden').show(); // Show and position square

        // Reset transform state when repositioned
        transformStates[squareId] = {
            x: 0,
            y: 0,
            scale: 1,
            angle: 0,
            currentScale: 1,
        };

        // Reinitialize interactions to ensure proper dragging
        setupInteractions($square[0]);

        // Reset iframe content to default
        const iframe = $square.find('iframe');
        if (iframe.length > 0) {
            const defaultSrc = iframe.attr('data-default-src');
            if (defaultSrc) {
                iframe.attr('src', defaultSrc);
            }
        }

        // Bring the square to the front
        bringToFrontIfNeeded($square[0]);
    }

    // Function to bring an element to the front by updating its z-index
    function bringToFrontIfNeeded(element) {
        const currentZIndex = parseInt($(element).css('z-index'), 10);
        if (currentZIndex < highestZIndex) {
            highestZIndex += 1; // Increment the highest z-index
            $(element).css('z-index', highestZIndex); // Assign the new z-index
        }
    }

    // Listener for drag movements
    function dragMoveListener(event) {
        const target = event.target;
        const state = transformStates[target.id];

        state.x += event.dx; // Update x position
        state.y += event.dy; // Update y position

        updateElementTransform(target); // Apply transformations
    }

    // Function to update the transform property of an element
    function updateElementTransform(element) {
        const state = transformStates[element.id];
        element.style.transform = `translate(${state.x}px, ${state.y}px) scale(${state.scale}) rotate(${state.angle}deg)`; // Apply CSS transform
    }

    // Set up handlers for each circle button
    setupCircleHandlers("circle1", ["square1"]);
    setupCircleHandlers("circle2", ["square2"]);
    setupCircleHandlers("circle3", ["square3"]);
    setupCircleHandlers("circle4", ["square4"]);

    // Initialize interactions for each square
    $(".square").each(function () {
        const element = this;
        const $square = $(element);

        // Initialize transformation state for this square
        transformStates[element.id] = {
            x: 0,
            y: 0,
            scale: 1,
            angle: 0,
            currentScale: 1,
        };

        // Store default iframe source
        const iframe = $square.find('iframe');
        if (iframe.length > 0) {
            iframe.attr('data-default-src', iframe.attr('src'));
        }

        setupInteractions(element); // Set up interactions for the square
    });

    // Close button handler to hide the square
    $(".close-btn").on("click touchend", function (e) {
        e.stopPropagation();
        e.preventDefault();
        const square = $(this).closest(".square");
        const squareId = square[0].id;
        
        // Force reset any ongoing interaction
        interact(square[0]).unset();
        
        // Reset transform state
        transformStates[squareId] = {
            x: 0,
            y: 0,
            scale: 1,
            angle: 0,
            currentScale: 1,
        };
        
        // Reset the square's transform
        square.css('transform', 'translate(0px, 0px) scale(1) rotate(0deg)');
        
        // Hide the square
        square.addClass("hidden").hide();
        
        // Simulate touchend/mouseup to reset any ongoing drag
        $(document).trigger('touchend');
        $(document).trigger('mouseup');
        
        // Re-initialize interactions after a brief delay
        setTimeout(() => {
            setupInteractions(square[0]);
        }, 100);
    });
});