找到一项新技术的完美应用,感觉真是妙不可言。您可以阅读各种 便捷的入门指南,并欣赏 炫酷的演示,但当您第一次在自己的项目中使用它时……才是真正理解它的时刻。
在为会议日程构建灵活布局时,我对 CSS Grid 有了新的认识。项目的需要与网格的优势完美契合:一个二维(垂直和水平)布局,以及子元素的复杂放置。在构建概念验证的过程中,我发现了一些技巧,使代码高度易读,并且工作起来非常有趣。
生成的演示包含了一些有趣的 CSS Grid 功能用法,并迫使我处理一些日常生活中不会遇到的网格细节。
在开始之前,最好打开另一个选项卡,并参考 CSS-Tricks 的 CSS Grid 指南,以便在整个文章中参考我们涵盖的概念。
定义我们的布局需求
由于 WordCamp(每年在世界各地举办的数百场以 WordPress 为中心的会议)的存在,我着手创建以下布局。这些不同的活动规模和格式各异,但都使用相同的日程安排布局工具。

我曾帮助安排过几个 WordCamp 并设计过 WordCamp 网站,因此我知道现有 HTML 表格布局的缺点。如果您的日程安排不适合统一网格,那么……¯\_(ツ)_/¯
为了寻找更好的方法,我首先列出了布局需求
- 时长可变的会议(限于设定时间增量)
想象一下,三个房间里连续进行一个小时的演讲,另一个房间里进行一个两小时的研讨会。 - 跨越一个或多个“赛道”的会议
赛道通常与场地的特定房间相关联。就我在西雅图举办的本地 WordCamp 而言,场地可以拆除一堵墙来合并两个房间! - 日程安排可以包含空白空间
最后一刻的取消或超短的会议会在日程安排中产生空缺。 - 设计易于使用 CSS 自定义
WordCamp 网站仅允许通过 CSS 进行主题设置。 - 布局可以从 CMS 内容自动生成
由于我们是在数千个网站上从结构化的会议数据构建布局,因此我们不能依赖任何过于聪明或定制的 HTML 或 CSS。
开始:稳固的 HTML
在编写任何 CSS 之前,我总是从可靠的 HTML 开始。
顶级 <div>
将具有 .schedule
类,并充当网格父元素。每个唯一的开始时间都有其自己的标题,后面跟着所有在该时间开始的会议。每个会议的标记并不重要,但请确保无需查看布局即可了解会议何时何地举行。(您很快就会明白为什么。)
<h2>Conference Schedule</h2>
<div class="schedule">
<h3 class="time-slot">8:00am</h3>
<div class="session session-1 track-1">
<h4 class="session-title"><a href="#">Session Title</a></h4>
<span class="session-time">8:00am - 9:00am</span>
<span class="session-track">Track 1</span>
<span class="session-presenter">Presenter Name</span>
</div>
<!-- Sessions 2, 3, 4 -->
<h3 class="time-slot">9:00am</h3>
<div class="session session-5 track-1">
<h4 class="session-title"><a href="#">Session Title</a></h4>
<span class="session-time">9:00am - 10:00am</span>
<span class="session-track">Track 1</span>
<span class="session-presenter">Presenter Name</span>
</div>
<!-- Sessions 6, 7, 8 -->
<!-- etc... -->
</div> <!-- end .schedule -->
移动布局和网格回退完成!
添加一些您自己的 CSS 来美化界面,我们的移动布局和不支持 CSS Grid 的浏览器的回退功能已经完成了!
以下是使用我所用颜色后的外观

添加网格布局
现在是真正的 CSS Grid 部分了!
我在构建这个过程中获得的顿悟来自阅读 Robin 在 CSS-Tricks 上发表的文章 “使用 CSS Grid 创建条形图。” TL;DR:一行网格代表图表高度的 1%,因此条形跨越的行数与它所代表的百分比相同。
.chart {
display: grid;
grid-template-rows: repeat(100, 1fr); /* 1 row = 1%! */
}
.fifty-percent-bar {
grid-row: 51 / 101; /* 101 - 51 = 50 => 50% */
}
这让我意识到,网格非常适合任何与某种规则增量单位绑定的布局。在日程安排的情况下,该单位是时间!在本演示中,我们将使用 30 分钟的增量,但您可以根据自己的需要进行设置。(只需注意 限制 Grid 布局为 1000 行的 Chrome 浏览器错误。)
我尝试的第一个版本使用了与 Robin 的条形图类似的语法,以及一些基本数学运算来放置会议。我们使用 8 行,因为在上午 8 点到中午 12 点之间有 8 个 30 分钟的增量。请记住,隐式网格线编号从 1 开始(而不是 0),因此网格行编号为 1 到 9。

.schedule {
display: grid;
grid-template-rows: repeat(8, 1fr);
}
.session-1 {
grid-row: 1 / 3; /* 8am - 9am, 3 - 1 = 2 30-minute increment */
}
.session-2 {
grid-row: 3 / 6; /* 9am - 10:30am, 6-3 = 3 30-minute increments */
}
此技术的难题在于,在具有大量行的网格上放置项目非常抽象且令人困惑。(此问题也为 Robin 的条形图演示增加了大量复杂性。)
此时,命名网格线 应运而生!我们可以根据相应的时间给每行一个可预测的名称,而不是依赖网格线编号。

.schedule {
display: grid;
grid-template-rows:
[time-0800] 1fr
[time-0830] 1fr
[time-0900] 1fr
[time-0930] 1fr;
/* etc...
Note: Use 24-hour time for line names */
}
.session-1 {
grid-row: time-0800 / time-0900;
}
.session-2 {
grid-row: time-0900 / time-1030;
}
这真是妙不可言。无需进行复杂的数学计算来确定会议开始或结束前后的行数。更棒的是,我们可以使用存储在 WordPress 中的信息生成网格线名称和会议布局样式。将开始时间和结束时间传递给网格,一切就绪!
由于日程安排有多个赛道,因此我们需要为每个赛道创建一个列。赛道的工作方式类似于时间,为每条网格列线使用命名的赛道线。此外,还有一个额外的第一列用于开始时间标题。
.schedule { /* continued */
grid-template-columns:
[times] 4em
[track-1-start] 1fr
[track-1-end track-2-start] 1fr
[track-2-end track-3-start] 1fr
[track-3-end track-4-start] 1fr
[track-4-end];
}
但是,在这里,我们将命名网格线更进一步。每行获得两个名称:一个表示它开始的赛道,一个表示它结束的赛道。这并非严格必要,但它使代码更清晰,尤其是在会议跨越多个列时。
定义了基于时间和赛道的网格线后,我们现在只需知道会议的时间和赛道即可放置任何会议!
.session-8 {
grid-row: time-1030 / time-1100;
grid-column: track-2-start / track-3-end; /* spanning two tracks! */
}
将所有这些结合在一起,我们得到了一些冗长但非常易读的代码,使用起来非常愉快。
@media screen and (min-width: 700px) {
.schedule {
display: grid;
grid-gap: 1em;
grid-template-rows:
[tracks] auto /* Foreshadowing! */
[time-0800] 1fr
[time-0830] 1fr
[time-0900] 1fr
[time-0930] 1fr
[time-1000] 1fr
[time-1030] 1fr
[time-1100] 1fr
[time-1130] 1fr
[time-1200] 1fr;
grid-template-columns:
[times] 4em
[track-1-start] 1fr
[track-1-end track-2-start] 1fr
[track-2-end track-3-start] 1fr
[track-3-end track-4-start] 1fr
[track-4-end];
}
.time-slot {
grid-column: times;
}
}
<div class="session session-1 track-1" style="grid-column: track-1; grid-row: time-0800 / time-0900;">
<!-- details -->
</div>
<div class="session session-2 track-2" style="grid-column: track-2; grid-row: time-0800 / time-0900">
<!-- details -->
</div>
<!-- etc... -->
最终代码使用内联样式进行会议放置,我觉得这样很合适。如果您不喜欢这种方式,并且正在使用更新的浏览器,则可以通过 CSS 变量将 行名称传递给 CSS。
快速提示:使用 fr 单位与 auto 值作为行高
值得注意的一个细节是使用 fr
单位定义行高。
当使用 1fr
确定行高时,所有行都具有相同的高度。高度由日程安排中最高行的内容决定。(我必须阅读 W3C 的 fr
规范才能弄清楚这一点!)这会生成一个美丽的日程安排,其中高度与时间成比例,但也可能导致非常高的布局。

1fr
生成由网格中最高行决定的等高行。(查看演示:网格行高 1fr 与 auto)例如,如果您的日程安排网格以 15 分钟为增量,从早上 7 点到晚上 6 点,则总共有 48 行网格。在这种情况下,您可能希望使用 auto
作为行高,因为每个网格行的高度由其内容决定,因此日程安排更加紧凑。

auto
使每行的高度与其内容相同。(查看演示:网格行高 1fr 与 auto)
关于可访问性
某些 CSS Grid 技术存在真正的可访问性问题。具体来说,以不匹配源顺序的方式更改信息的可视顺序会给使用键盘导航的用户带来问题。
此布局利用此功能任意地在网格上放置项目,因此需要谨慎。但是,由于标题和源顺序与开始时间的可视化保持一致,因此在我看来,这似乎是一种安全的使用方式。
如果您受到启发想要做类似的事情,请仔细考虑可访问性。在这种情况下,按时间顺序排列信息是有意义的,但我可以想象一个合理的案例,即TAB
顺序应该向下遍历列而不是横跨行。(修改此演示以执行此操作应该不难!)
无论您做什么,始终要考虑可访问性。
添加粘性轨道名称
最后,是时候添加看起来像每个列顶部的表格标题的轨道名称了。由于会话的轨道已经可见,因此我选择使用aria-hidden="true"
将“标题”隐藏在辅助技术之外。
轨道名称位于第一行网格中,方便地命名为“tracks”。只要您没有遇到任何奇怪的溢出问题,position: sticky
就可以在您滚动时使这些名称保持可见。
<span class="track-slot" aria-hidden="true" style="grid-column: track-1;">Track 1</span>
<span class="track-slot" aria-hidden="true" style="grid-column: track-2;">Track 2</span>
<span class="track-slot" aria-hidden="true" style="grid-column: track-3;">Track 3</span>
<span class="track-slot" aria-hidden="true" style="grid-column: track-4;">Track 4</span>
.track-slot {
display: none; /* only visible with Grid layout */
}
@supports( display:grid ) {
@media screen and (min-width:700px) {
.track-slot {
grid-row: tracks;
display: block;
position: sticky;
top: 0;
z-index: 1000;
background-color: rgba(255,255,255,.9);
}
}
}
这是最终演示的一个巧妙的小收尾工作。✨
结果
以下是我们将所有内容组合在一起后的效果!
查看 CodePen 上的笔
使用 CSS Grid 创建会议日程,作者是 Mark Root-Wiley (@mrwweb)
在 CodePen 上。
我们才刚刚开始
这个日程安排绝对是我用 CSS Grid 做出的最令人满意的作品。我喜欢行命名的“数据驱动”和语义化方式,可访问性和CMS 需求也完美地融入其中,没有任何不便。
我唯一剩下的问题是还有哪些其他类型的“数据驱动”网格可以构建?我见过一些很棒的日历布局,这里还有一个大富翁棋盘布局。足球场、时间线、餐厅餐桌或剧院座位呢?还有什么?
这是一个惊人的网格使用案例。恭喜 Mark,你激励了我们。
巧妙地使用了网格,以一种超级干净的方式创建了这些日程布局。默认情况下,我本会使用 JavaScript 来完成这样的事情,但现在我开始考虑需要更深入地探索网格了。
很棒的文章。而且我喜欢你从一开始就使用了语义化 HTML 并考虑了可访问性。
在 HTML 示例中,不应该写
style="grid-column: track-1-start / track-1-end;"
而不是
style=
“grid-column: track-1;”`?
好问题,Stu!我必须承认,你确实发现了演示和文章代码之间不一致的地方;但是,文章代码确实有效。我已经更新了演示以反映这一点。现在,为什么它有效……
thing-start
和thing-end
,并将两条列线命名为thing-start
和thing-end
,则可以将网格项目分配到thing
网格区域,而无需显式定义该区域。反之,如果您定义了一个网格区域,即使没有显式定义这些线名,您也可以将其边缘称为thing-start
/thing-end
。我怀疑浏览器因此将thing-start'
解析为thing
,但我尚未在规范的任何地方找到将其定义为预期行为的地方。如果我能了解更多信息,我会再报告!感谢 Rachel Andrew,我已经得到了答案!
我会尝试在这里总结一下,但请查看她解释它的文章。
track-1-start
和track-1-end
定义了一个名为track-1
的隐式网格区域。如规范中所述
所以这有点令人费解,但我们正在使用由隐式网格区域生成的隐式网格线名!
关于这是否是一个好主意,评审团还在(你怎么看?),但它肯定更简洁。
很棒的博客,可以理解网格系统的概念。我将在我的网站上实现这个概念。
很棒的文章,Mark。几天前我们也被要求构建一个活动日程,这篇文章启发我在我们的博客上写了一些笔记。我们探索了 CSS 网格和 HTML 表格,并决定使用表格,因为表格数据、可访问性和浏览器兼容性——这些是关键因素。
https://www.liquidlight.co.uk/blog/create-an-event-schedule-with-html-table
很高兴看到你使用了表格,João。也很高兴看到你使用了几乎完全相同的网格语法!这篇文章篇幅过长,无法介绍表格替代方案,但我需要说明两点
无论是网格还是表格,我认为将时间直接包含在每个会话/事件的标记中都很有意义。
网格的绝妙用法,确实能激发思考。另一个很酷的用法可能是飞机座位,几年前我做过类似的工作,并通过 JavaScript 处理了它。
哇,这是网格的杀手级用法。我记得用表格来布局这样的程序,那真是太糟糕了!
我们在今年早些时候的毛里求斯开发者大会网站上使用了这种技术
https://2019.mscc.mu/sessions
这是一个没有真正参考的实验,所以看到你的示例中出现了类似的模式,这真的很酷!
https://github.com/mscraftsman/devcon2019/blob/master/web/app/src/views/PageSessions.vue#L488
对于响应式版本,由于我们使用的是 VueJS,因此我们使每一列都可滑动。
现在看到另外两个人使用非常相似的语法来命名网格线,这非常酷!一个有趣的区别是,我看到你使用了
span
语法而不是定义grid-row-end
。不过,我认为两种方式都很好:)感谢分享。这篇文章启发我创建了一个完全可自定义的日历
https://github.com/fabiogiolito/CSSGridCalendar
非常酷,Fabio。看起来很棒!确保你考虑了可访问性,特别是HTML。
您好,Mark Root-Wiley,感谢您撰写这篇文章!
我想征求您的许可,将这篇文章翻译成韩语,并在公司运营的博客(https://ui.toast.com/)和我们的Github wiki页面(https://github.com/nhn/fe.javascript)上发布。我将引用原文,绝不会将其用于获取任何经济价值。
请告诉我您的想法,希望您今天过得愉快:)
Jenny,这对我来说没问题,但鉴于它不是我的网站,你应该了解一下CSS Tricks的政策。感谢你的询问,我相信你会妥善处理署名问题。
我认为CSS Tricks是获得许可的。但它似乎不适用于这篇文章。而且CSS Tricks似乎没有给出正式的答复。
此外,由于版权属于您,如果您做出决定,我们希望翻译您的文章。
谢谢。
这是一篇关于构建会议日程的非常棒的文章。
如果任何时段与其他时段重叠,则必须清晰可见,例如所有事件在父级DiV内响应,那么我们该怎么做呢?