在CSS网格中的可扩展部分

118 阅读7分钟

我喜欢CSS网格。我喜欢只用几行代码就能实现完全响应的网格布局,通常根本不需要任何媒体查询。我很喜欢用CSS Grid来制作有趣的布局,同时保持HTML标记的干净和简单。

但是最近,我遇到了一个需要解决的独特的用户界面难题。基本上,任何给定的网格单元都可以有一个按钮,打开另一个更大的区域,也是网格的一部分。但这个新的更大的网格单元需要:

  1. 就在打开它的单元格的下面,并且
  2. 全宽

原来有一个很好的解决方案,而且根据CSS Grid本身的精神,它只涉及几行代码。在这篇文章中,我将结合三个单行的CSS Grid "技巧 "来解决这个问题。完全不需要JavaScript。

对我需要解决的实际问题的解释

下面是我需要做的一个极简的UI例子。

这是我们实际的产品卡网格,在我们的Storybook组件库中呈现。

A grid of product cards in a three by two layout. Each card has a placeholder gray image, product name, descriptions, price, and small text.

每个产品卡都需要添加一个新的 "快速查看 "按钮,这样,当点击时,它就会:

  • 动态地 "注入 "一个新的全宽的卡片(包含更详细的产品信息),就在被点击的产品卡片下面。
  • 不破坏现有的卡片网格(即保留DOM源顺序和浏览器中渲染的卡片的视觉顺序),并且
  • 仍然是完全响应的。

嗯......这在我们目前的CSS网格实现中可能吗?

我肯定需要借助于JavaScript来重新计算卡片的位置,并将它们移动,特别是在浏览器调整大小时?对吗?

谷歌不是我的朋友。我找不到任何可以帮助我的东西。即使是搜索 "快速浏览 "的实现,也只能找到一些使用模态或叠加来呈现注入的卡片的例子。毕竟,在这种情况下,模态通常是唯一的选择,因为它能让用户关注新的内容,而不需要打乱页面的其他部分。

我在这个问题上睡了一觉,最终通过结合CSS Grid的一些最强大、最有用的功能找到了一个可行的解决方案。

CSS网格技巧#1

我已经为我们的默认网格系统采用了第一个技巧,而产品卡片网格是这种方法的一个具体实例。这里有一些(简化的)代码:

.grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, 20rem);
}

这段代码中的*"秘诀 "*是grid-template-columns: repeat(auto-fit, 20rem); ,它为我们提供了一个带列的网格(本例中为20rem 宽),这些列在可用空间内自动排列,当空间不足时,将包裹到下一行。

auto-fitauto-fill 的关系感到好奇?Sara Soueidan已经写了一个关于如何工作的精彩解释。Sara还解释了如何结合minmax() ,使列宽能够 "灵活",但为了本文的目的,我想定义固定的列宽,以达到简单的目的。

CSS网格技巧#2

接下来,我必须在网格中加入一个新的全宽卡片:

.fullwidth {
  grid-column: 1 / -1;
}

这段代码之所以有效,是因为技巧1中的grid-template-columns 创建了一个 "显式 "网格,所以可以为.fullwidth 卡片定义开始和结束列,其中1 / -1 意味着 "从第1列开始,并跨越每一列,直到最后一列"。

很好,一个全宽的卡片被注入到网格中。但是......现在我们在全宽卡的上方有了空隙。

Two rows of four rectangles. All of the rectangles are light gray and numbered, except one that has a wheat-colored background and another box beneath it containing text, and taking up the full container width.

CSS网格技巧之三

填补空隙--我之前已经用一种假的砖石方法做了这个:

.grid {
  grid-auto-flow: dense;
}

就是这样!所需的布局实现了。

grid-auto-flow 属性控制了CSS网格自动排版算法的工作方式。在这种情况下,dense 包装算法会尝试在网格中提前填上洞。

  • 我们所有的网格列都是一样的宽度。如果列的宽度是灵活的,例如,通过使用minmax(20rem, 1f) ,密集包装也能发挥作用。
  • 我们所有的网格 "单元格 "在每一行的高度都是一样的。这是默认的CSS网格行为。网格容器隐含了align-items: stretch ,导致单元格占据了100%的可用行高。

这一切的结果是,我们的网格中的洞被填满了--而美丽的部分是,在渲染的输出中保留了原始的源顺序。从可访问性的角度来看,这很重要。

请参阅MDN,了解关于CSS网格自动放置的完整解释。

完整的解决方案

CodePen嵌入回退

这三个组合技巧提供了一个简单的布局解决方案,只需要很少的CSS。没有媒体查询,也不需要JavaScript。

但是......我们仍然需要JavaScript?

是的,我们需要。但不是用于任何布局计算。它纯粹是用于管理点击事件、焦点状态、注入的卡片显示等功能

在原型的演示中,全宽的卡片已经被硬编码在HTML中的DOM中的正确位置,而JavaScript只是切换它们的显示属性。

然而,在生产环境中,注入的卡片可能会被JavaScript获取并放置在正确的位置。像电子商务网站上的产品的网格布局往往有很重的DOM,我们要避免不必要地用大量额外的 "隐藏 "内容来进一步增加页面的重量。

快速浏览应该被视为一种渐进式的增强,所以如果JavaScript无法加载,用户就会被带到相应的产品细节页面。

可访问性考虑

我热衷于使用正确的语义HTML标记,在绝对必要的情况下添加aria- ,并确保用户界面在键盘和屏幕阅读器中都能使用。

因此,这里列出了使这个模式尽可能无障碍的考虑因素:

  • 产品卡的网格使用了<ul><li> ,因为我们要显示的是一个产品的列表。因此,辅助技术(例如屏幕阅读器)将理解这些卡片之间的关系,并且用户将被告知列表中有多少个项目。
  • 产品卡本身是<article> ,有适当的标题,等等。
  • .fullwidth 卡片被注入时,HTML源顺序被保留,提供了一个很好的自然标签顺序进入注入的内容,并再次出来到下一个卡片。
  • 整个卡片的网格被包裹在一个aria-live 区域中,这样DOM的变化就会公布给屏幕阅读器。
  • 焦点管理确保了注入的卡片收到键盘焦点,并且在关闭卡片时,键盘焦点会返回到最初触发卡片可见性的按钮。

虽然在原型中没有演示,但这些额外的增强功能可以被添加到任何生产实现中:

  • 确保注入的卡片在被关注时,有一个适当的标签。这可以简单到把标题作为内容中的第一个元素。
  • 绑定ESC键来关闭注入的卡片。
  • 滚动浏览器窗口,使注入的卡片在视口中完全可见。

收尾工作

那么,你怎么看?

当我们想显示额外的内容时,这可能是一个很好的替代模态的方法,但在这个过程中不会劫持整个视口。这在其他情况下也可能很有趣--想想图片网格中的照片标题,帮助性文本,等等。它甚至可能成为某些情况下的替代方案,在这些情况下,我们通常会使用<details>/<summary> (因为我们知道这些只在某些情况下使用最好)。

总之,我对你如何使用它感兴趣,或者你如何以不同的方式处理它。请在评论中告诉我。