使用 :not 实现纯 CSS 画廊聚焦效果

Avatar of Krzysztof Gonciarz
Krzysztof Gonciarz 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

过去,我经常需要想办法如何为容器内的所有元素添加样式,但包括悬停的那个元素。

Animated GIF of a mouse cursor hovering over elements. The element the mouse cursor enters remains visible and the other elements fade.
预期“淡出”效果的演示,让用户能够“聚焦”于特定元素。

此效果需要选择悬停元素的兄弟元素。我过去会使用 JavaScript 来实现这一点,在mouseentermouseleave 事件中添加或删除定义适当 CSS 规则的类,类似于这样

尽管这段代码可以解决问题,但我总有一种感觉,一定有某种纯 CSS 的方法可以达到相同的效果。几年前,在为公司开发某个滑块时,我想出了一个类似于Chris Geelhoed 如何在 CSS 中重现著名的 Netflix 首页动画 的解决方案,我意识到我不再需要 JavaScript 了。

几个月前,我试图在我的公司网站上基于网格的 Feed 中实现相同的方法,结果——砰的一声——由于元素之间的间隙,它不起作用了!

幸运的是,事实证明它不必一直这样,我再次不需要 JavaScript 了。

标记和基础 CSS

让我们通过准备适当的标记开始编码

  • .grid 是一个基于网格的<ul> 列表;
  • .grid__child 元素是我们想要交互的<li> 子元素。

标记如下所示

<ul class="grid">
  <li class="grid__child"></li>
  <li class="grid__child"></li>
  <li class="grid__child"></li>
</ul>

样式应如下所示

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

对于子元素之间没有间隙的容器,这将绰绰有余

Animated GIF of a mouse cursor interacting with elements that are not separated by any gaps.
无需间隙即可使用的解决方案的演示。

但是,在我的情况下,我无法移除这些间隙

Animated GIF of a mouse cursor hovering over elements. However, when the mouse enters a gap between two elements, the effect ends as the mouse leaves the element.
引入间隙时遇到的问题的演示。

当我将鼠标在块之间移动时,所有子元素都淡出了。

忽略间隙

我们可以假设间隙是容器中未被其子元素覆盖的部分。我们不希望每次光标进入容器时都运行该效果,而是在它悬停在内部的某个元素上时运行。那么,我们能否忽略光标在间隙上方的移动呢?

可以,我们可以使用.grid 容器上的pointer-events: none,并在其子元素上使用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 样式不仅会忽略悬停事件,还会忽略所有其他事件。在这种情况下,您可以将.grid 包装到另一个容器中,如下所示

<div class="container">
  <ul class="grid">
    <li class="grid__child"></li>
    <li class="grid__child"></li>
    <li class="grid__child"></li>
    <li class="grid__child"></li>
    <li class="grid__child"></li>
    <li class="grid__child"></li>
    <li class="grid__child"></li>
  </ul>
</div>

总结

我强烈建议您进行实验,并尝试为通常需要一定复杂程度的任务找到更简单、更原生方法。像 CSS 这样的 Web 技术正变得越来越强大,通过使用开箱即用的原生解决方案,您可以在无需维护代码并将其交给浏览器供应商的情况下获得出色的效果。

希望您喜欢这个简短的教程,并觉得它有用。谢谢!

作者选择了科技教育基金作为“为捐赠写作”计划的一部分接收捐款。