How to Create a Horizontally Scrollable Element with Hover Overlay Effect in HTML/CSS?

I’m working on a webpage where I need to create an element with the following properties:

  • Horizontally Scrollable: The element contains multiple dance cards, each representing a dance. It should automatically scroll to the left or right when the mouse is near the left or right edges of the element.
  • Hover Effect: When the mouse hovers over a dance card, the card should increase in size and overlay other elements above it, without changing the layout or spacing of the other elements.

Here’s my current HTML structure:

<div class="horizontal-scroll-videos">
    {% for dance in dances %}
        <a href="/sample/{{ dance.id }}" class="dance-card">
            <div class="dance-card-image-description">
                <img src="{{ dance.image_url }}" class="dance-card-image" alt="{{ dance.name }}">
                <video class="dance-card-video" dance-video-path="{{ dance.video }}" muted></video>
                <p class="dance-card-description body-text">{{ dance.description }}</p>
            </div>
            <div class="dance-card-body">
                <h5 class="dance-card-title">{{ dance.name }}</h5>
                <div class="dance-card-tags">
                    <span class="badge bg-primary">{{ dance.difficulty }}</span>
                    <span class="badge bg-secondary">{{ dance.genre }}</span> 
                    {% if dance.has_instructions %}
                    <span class="badge bg-warning">Tutorial</span> <!-
                    {% endif %}
                </div>
            </div>
        </a>
    {% endfor %}
</div>

I have managed to implement each of these features individually; but problem is that I cannot find a way to have both behaviours functioning at the same time.

I’ve found out that the problem is around the definition of the horizontal-scroll-videos element, particularly about the overflow-x.

.horizontal-scroll-videos {
  display: flex;
  align-items: flex-start;
  gap: var(--inter-video-gap);
  align-self: stretch;
  overflow-x: auto;
  overflow-y: visible;
  scroll-behavior: smooth;
  white-space: nowrap;
  scrollbar-width: none;
}

When overflow-x: auto is enabled: The horizontal scrolling works perfectly. However, the hover effect is cut off, and the dance cards are not able to overlay other elements as desired.

Srolling working but hover not working

When overflow-x: visible is enabled: The hover effect works correctly, and the dance cards overlay other elements as desired. However, the horizontal scrolling functionality is lost.

Hover working but scrolling not working

I have tried to dynamically toggle overflow-x behavior based on user interaction, but the problem is that the scroll loses its position. When switching back to overflow-x: visible, the scroll position resets, and the new content displayed during the scroll is lost.

Code for the hover effect and the scrolling effect is:

function addHoverEffectDanceCards(){
    const danceCards = document.querySelectorAll(".dance-card");
    danceCards.forEach(danceCard => {
        // Get the video and image elements within the dance card
        const video = danceCard.querySelector(".dance-card-video");
        const image = danceCard.querySelector(".dance-card-image");

        // Get the children (the elements within) the dance card
        const children = danceCard.children;
        const childrenArray = Array.from(children);

        childrenArray.forEach(child => {
            // If any element in a dance card gets moused over, add the hover class to every element in the dance card
            child.addEventListener("mouseover", function() {
                const container = danceCard.closest(".horizontal-scroll-videos");
                const containerRect = container.getBoundingClientRect();
                const danceCardRect = danceCard.getBoundingClientRect();
                // Check if the dance card is fully visible within the container; don't show preview if it is not
                if (danceCardRect.left >= containerRect.left && 
                    danceCardRect.right <= containerRect.right) {
                    childrenArray.forEach(child => {
                        classes = child.classList;
                        classes.add("hover");

                        // Add the hover to the children within the dance-card-image-description div
                        if (classes.contains("dance-card-image-description")) {
                            const imgDesChildren = child.children;
                            const imgDesChildrenArray = Array.from(imgDesChildren);

                            imgDesChildrenArray.forEach(imgDesChild => {
                                imgDesChild.classList.add("hover");
                            });
                        };
                    });

                    // Add the hover class to the dance card itself
                    danceCard.classList.add("hover");

                    // Check if the video src for preview is loaded
                    if (!video.src) {
                        // Get the video if it is not defined
                        fetch('/generate_video_url', {
                            method: 'POST',
                            headers: {
                                'Content-Type': 'application/json'
                            },
                            body: JSON.stringify({video_path: video.getAttribute('dance-video-path')})
                        })
                        .then(response => response.json())
                        .then(data => {
                            video.src = data.video_url; // Asign the video
                        })
                        .catch(error => console.error('Error fetching video presigned URL:', error));
                    } 
                    
                    // Start playing the preview
                    video.currentTime = 0;
                    video.play();
                    video.style.display = "block";
                    image.style.display = "none";
                }
            });

            // Remove the hover when no longer mousing over
            child.addEventListener("mouseout", function() {
                childrenArray.forEach(child => {
                    classes = child.classList;
                    classes.remove("hover");

                    // Remove the hover effect from the children inside the dance-card-image-description div
                    if (classes.contains("dance-card-image-description")) {
                        const imgDesChildren = child.children;
                        const imgDesChildrenArray = Array.from(imgDesChildren);
        
                        imgDesChildrenArray.forEach(imgDesChild => {
                            imgDesChild.classList.remove("hover");
                        });
                    };
                });

                // Remove the hover class from the dance card itself
                danceCard.classList.remove("hover");

                // Pause the video and show the image
                video.pause();
                video.style.display = "none";
                image.style.display = "block";
            });
        });
    });

const horizontalScrollContainers = document.querySelectorAll(".horizontal-scroll-videos");

horizontalScrollContainers.forEach(container => {
    let scrollInterval;

    container.addEventListener('mouseover', (e) => {
        const screenWidth = window.innerWidth;
        const scrollThreshold = 200;
        // Check if mouse is within the scrollThreshold from the right edge
        const checkLeftScroll = (e) => {
            const mouseX = e.clientX;
            return mouseX > screenWidth - scrollThreshold;
        };
        const checkRightScroll = (e) => {
            const mouseX = e.clientX;
            return mouseX < scrollThreshold;
        }
        if (checkLeftScroll(e)) {
            scrollInterval = setInterval(() => {container.scrollLeft += 180;}, 30);
        } else if (checkRightScroll(e)) {
            scrollInterval = setInterval(() => {container.scrollLeft -= 180;}, 30);
        }
    });

    container.addEventListener('mouseout', () => {
        clearInterval(scrollInterval);
        scrollInterval = null;
    });
});

I am a beginner in this field, and any help or suggestions on how to achieve this would be greatly appreciated!