Как создать элемент с горизонтальной прокруткой и эффектом наложения при наведении в HTML/CSS?

Я работаю над веб-страницей, на которой мне нужно создать элемент со следующими свойствами:

  • Горизонтальная прокрутка: элемент содержит несколько танцевальных карточек, каждая из которых представляет танец. Он должен автоматически прокручиваться влево или вправо, когда мышь находится возле левого или правого края элемента.
  • Эффект наведения: когда указатель мыши наводится на карточку танца, карточка должна увеличиваться в размере и накладываться на другие элементы над ней, не меняя макет или расстояние между другими элементами.

Вот моя текущая структура 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>

Мне удалось реализовать каждую из этих функций индивидуально; но проблема в том, что я не могу найти способ, чтобы оба поведения функционировали одновременно.

Я обнаружил, что проблема связана с определением элемента горизонтальной прокрутки видео, особенно с переполнением-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;
    });
});

Я новичок в этой области, и буду очень признателен за любую помощь или предложения о том, как этого добиться!

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
0
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Редактировать

Кажется, я совершенно неверно истолковал ваш вопрос. К сожалению, overflow-x и overflow-y по умолчанию имеют неприятное поведение: они не позволяют сделать один из них видимым, а другой — прокручивать/скрывать. Вот вопрос Stack Overflow, который подробно объясняет это.

Я бы предложил использовать одно из представленных ими решений.

Оригинальный ответ для тех, кто пытается решить проблемы с наведением и перемещением мыши.

Причина, по которой эффект наведения перестает работать, заключается в том, что эффект наведения проверяется только тогда, когда вы наводите указатель мыши на элемент, а НЕ если этот элемент перемещается под мышью, пока мышь не движется.

Поскольку прокрутка происходит автоматически, когда танцевальные карточки перемещаются под мышью, :hover не срабатывает.

Чтобы сохранить функциональность обоих, вам, вероятно, придется проверить, находится ли указатель мыши над танцевальной карточкой с помощью Javascript вместо CSS. Я считаю, что события Javascript onmouseover и onmouseenter будут иметь те же проблемы, что и CSS :hover.

Это немного усложняет задачу. Один из способов добиться этого — постоянно проверять с помощью document.elementFromPoint элемент, над которым вы наводите курсор, и, если это танцевальная карта, запускать эффекты, которые вы бы сделали с помощью :hover. Затем, если элемент не является танцевальной картой, сбросьте значение этой карты. https://developer.mozilla.org/en-US/docs/Web/API/Document/elementFromPoint

Возможно, есть более простые решения проблемы, но я думаю, что это один из способов решения этой проблемы.

Вот еще один вопрос о переполнении стека, иллюстрирующий ту же проблему. Вы также можете попробовать их решение: Запуск: наведение курсора на движущийся элемент без перемещения мыши

Большое спасибо за ваш ответ! Теперь я понимаю, что мои слова, возможно, были неточными при описании проблемы. Частично работает эффект наведения: показывает описание танца и увеличивает карточку танца. На скриншотах видно, что поведение при наведении срабатывает, но не так, как ожидалось. Под «ожидаемым» я подразумеваю, что танцевальная карточка должна накладываться поверх других элементов, отображая при этом ее описание и увеличивая ее размер. Проблема в том, что эффекта наложения не происходит — карточка обрезается контейнером горизонтальной прокрутки, а не выходит за его пределы.

Yuyan Wang 08.08.2024 19:34

Я бы сказал, что самым простым подходом (если он существует) было бы найти способ закодировать это так, чтобы контент мог переполняться при прокрутке, а также выходить за пределы контейнера горизонтальной прокрутки для наложения поверх другого контента. Если у вас есть какие-либо предложения о том, как этого добиться, или есть другой подход, который может работать лучше, я буду очень признателен за ваши советы! Еще раз спасибо за вашу помощь!

Yuyan Wang 08.08.2024 19:37

Я вижу, это имеет смысл. Можете ли вы показать текущие правила, которые вы установили для элемента при :hover?

Sam Sabin 08.08.2024 19:40

Конечно, у меня есть: .dance-card.hover { align-self: flex-end; box-shadow: var(--raised---level-3); border-radius: var(--dc-hover-corner-radius); gap: 0; transition-duration: var(--dc-hover-duration); z-index: 1; } .dance-card-video.hover { width: var(--dc-hover-width); border-radius: var(--dc-hover-corner-radius) var(--dc-hover-corner-radius) 0 0; box-shadow: none; position: absolute; bottom: 0; z-index: 1; transition-duration: var(--dc-hover-duration); }

Yuyan Wang 08.08.2024 20:01

Другие вопросы по теме