Эффект фокуса галереи на чистом CSS с :not

Исходный узел: 1723238

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

Демонстрация ожидаемого эффекта «затухания» для элементов одного уровня, позволяющего пользователям «сосредоточиться» на определенном элементе.

Этот эффект требует выбора братьев и сестер зависшего элемента. Я использовал для этого JavaScript, добавляя или удаляя класс, определяющий правильные правила CSS для mouseenter и mouseleave события, подобные этому:

Хотя код делает свое дело, моя интуиция всегда подсказывала мне, что для достижения того же результата должен быть какой-то чистый CSS-способ. Несколько лет назад, работая над одним слайдером для своей компании, я придумал решение, похожее на как Крис Гилхоэд воссоздал знаменитую анимацию домашней страницы Netflix и я понял, что JavaScript для этого мне больше не нужен.

Пару месяцев назад я пытался реализовать такой же подход к ленте на основе сетки на веб-сайте моей компании, и — бум — это не сработало из-за промежутка между элементами!

К счастью для меня, оказалось, что так оставаться не должно, и снова мне не понадобился для этого JavaScript.

Разметка и базовый CSS

Давайте начнем кодирование, подготовив правильную разметку:

  • .grid основан на сетке

      список;

    • и .grid__child элементы
    • дети, с которыми мы хотим взаимодействовать.

    Разметка выглядит так:

    Стиль должен выглядеть так:

    .grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, 15rem);
      grid-gap: 1rem;
    }
    
    .grid__child {
      background: rgba(0, 0, 0, .1);
      border-radius: .5rem;
      aspect-ratio: 1/1;
    }

    Этот пример кода создаст три элемента списка, занимающих три столбца в сетке.

    Сила селекторов CSS

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

    1. при наведении курсора на контейнер должны измениться стили всех элементов внутри...  
    2. …кроме того, над которым в данный момент висит курсор.

    Давайте начнем с захвата каждого дочернего элемента, пока курсор находится над контейнером:

    .grid:hover .grid__child {
      /* ... */
    }

    Во-вторых, давайте исключим зависший в данный момент элемент и уменьшим opacity любого другого ребенка:

    .grid:hover .grid__child:not(:hover) {
      opacity: 0.3;
    }

    И этого было бы вполне достаточно для контейнеров без промежутков между дочерними элементами:

    Анимированный GIF курсора мыши, взаимодействующего с элементами, которые не разделены пробелами.
    Демонстрация решения, которое работает без пробелов.

    Однако в моем случае я не смог удалить эти пробелы:

    Анимированный GIF курсора мыши, нависающего над элементами. Однако, когда мышь входит в зазор между двумя элементами, эффект прекращается, когда мышь покидает элемент.
    Демонстрация проблемы, возникающей при введении пробелов.

    Когда я перемещал мышь между плитками, все дочерние элементы исчезали.

    Игнорирование пробелов

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

    Да, мы можем, используя pointer-events: none на .grid контейнер и вернуть их с pointer-events: auto на своих дочерних элементах:

    .grid {
      /* ... */
      pointer-events: none;
    }
    
    /* ... */
    
    .grid__child {
      /* ... */
      pointer-events: auto;
    }

    Давайте просто добавим классный переход по непрозрачности, и у нас есть готовый компонент:

    Возможно, будет еще круче, если мы добавим больше тайлов и создадим двухмерный макет:

    Окончательный CSS выглядит так:

    .grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, 15rem);
      grid-gap: 3rem;
      pointer-events: none;
    }
    
    .grid:hover .grid__child:not(:hover) {
      opacity: 0.3;
    }
    
    .grid__child {
      background: rgba(0, 0, 0, .1);
      border-radius: .5rem;
      aspect-ratio: 1/1;
      pointer-events: auto;
      transition: opacity 300ms;
    }

    Всего двумя дополнительными строками кода мы решили проблему пробелов!

    Возможные проблемы

    Хотя это компактное решение, в некоторых ситуациях могут потребоваться обходные пути.

    К сожалению, этот трюк не сработает, если вы хотите, чтобы контейнер можно было прокручивать, например, как в каком-нибудь горизонтальном слайдере. pointer-events: none style будет игнорировать не только событие наведения, но и все остальные. В таких случаях можно обернуть .grid в другом контейнере, например:

    Обзор

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

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

    Автор выбрал Технология Обучение получить пожертвование в рамках Пишите для DOnations программу.

    Отметка времени:

    Больше от CSS хитрости