如何在 HTML/CSS 中创建具有悬停叠加效果的水平滚动元素?

问题描述 投票:0回答:1

我正在开发一个网页,需要创建具有以下属性的元素:

  • 可水平滚动:该元素包含多张舞蹈卡,每张代表一种舞蹈。当鼠标靠近元素的左边缘或右边缘时,它应该自动向左或向右滚动。
  • 悬停效果:当鼠标悬停在舞蹈卡上时,该卡应增大尺寸并覆盖其上方的其他元素,而不改变其他元素的布局或间距。

这是我当前的 HTML 结构:

<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>

我已经成功地单独实现了这些功能;但问题是我找不到一种方法让两种行为同时发挥作用。

我发现问题出在horizontal-scroll-videos元素的定义上,特别是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;
}

当启用overflow-x: auto时:水平滚动效果完美。然而,悬停效果被切断,并且舞蹈卡无法根据需要覆盖其他元素。

滚动工作但悬停不起作用

当启用overflow-x:visible时:悬停效果正常工作,并且舞蹈卡根据需要覆盖其他元素。但是,水平滚动功能丢失了。

悬停有效,但滚动无效

我尝试根据用户交互动态切换overflow-x行为,但问题是滚动丢失了它的位置。当切换回overflow-x:visible时,滚动位置重置,滚动期间显示的新内容会丢失。

悬停效果和滚动效果的代码为:

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;
    });
});

我是这个领域的初学者,任何有关如何实现这一目标的帮助或建议将不胜感激!

javascript html css scroll hover
1个回答
0
投票

悬停效果停止工作的原因是因为悬停效果仅检查当您将鼠标移动到某个元素上时,而不检查该元素是否在鼠标不移动时在鼠标下方移动。

因为你的滚动是自动的,所以当舞蹈卡在鼠标下移动时:悬停不会触发。

为了保留两者的功能,您可能需要使用 Javascript 而不是 CSS 检查鼠标是否位于舞蹈卡上。我相信 Javascript 事件 onmouseover 和 onmouseenter 会遇到与 CSS :hover 相同的问题。

这让事情变得更加困难。解决此问题的一种方法是不断检查 document.elementFromPoint 以获取您悬停的元素,如果它是舞蹈卡,则触发您使用 :hover 所要的效果。然后,只要该元素不是舞蹈卡,就重置该卡的值。 https://developer.mozilla.org/en-US/docs/Web/API/Document/elementFromPoint

问题可能有更简单的解决方案,但我认为这是解决问题的一种方法。

© www.soinside.com 2019 - 2024. All rights reserved.