关于如何在 Web 上构建类似于 Instagram Stories 体验的基础概述。
在这篇文章中,我想分享关于为 Web 构建 Stories 组件的思考,该组件是响应式的,支持键盘导航,并且可在各种浏览器中运行。
如果您希望亲身体验构建此 Stories 组件,请查看 Stories 组件 Codelab。
如果您更喜欢视频,这里是这篇文章的 YouTube 版本
概述
Stories UX 的两个流行示例是 Snapchat Stories 和 Instagram Stories(更不用说 fleets)。通常,在 UX 术语中,Stories 通常是仅限移动设备的、以点击为中心的模式,用于导航多个订阅。例如,在 Instagram 上,用户打开朋友的 Story 并浏览其中的图片。他们通常一次浏览许多朋友的 Story。通过点击设备的右侧,用户可以跳到该朋友的下一个 Story。通过向右滑动,用户可以跳到不同的朋友。Story 组件与轮播非常相似,但允许导航多维数组而不是一维数组。这就像每个轮播内部都有一个轮播。🤯

第二个“堆叠式”故事轮播
👍 列表中的列表,又名:多维数组
为工作选择合适的工具
总而言之,我发现由于一些关键的 Web 平台功能,构建此组件非常简单。让我们来介绍一下它们!
CSS Grid
我们的布局对于 CSS Grid 来说并非难事,因为它配备了一些强大的内容处理方法。
朋友布局
我们的主要 .stories
组件包装器是移动优先的水平滚动视图
.stories {
inline-size: 100vw;
block-size: 100vh;
display: grid;
grid: 1fr / auto-flow 100%;
gap: 1ch;
overflow-x: auto;
scroll-snap-type: x mandatory;
overscroll-behavior: contain;
touch-action: pan-x;
}
/* desktop constraint */
@media (hover: hover) and (min-width: 480px) {
max-inline-size: 480px;
max-block-size: 848px;
}
让我们分解一下 grid
布局
- 我们使用
100vh
和100vw
在移动设备上显式填充视口,并在桌面设备上约束大小 /
分隔我们的行和列模板auto-flow
转换为grid-auto-flow: column
- 自动流模板是
100%
,在这种情况下,它是滚动窗口宽度
在手机上,将其视为行大小为视口高度,每列为视口宽度。继续以 Snapchat Stories 和 Instagram Stories 为例,每列将是一个朋友的 Story。我们希望朋友的 Stories 继续在视口之外,以便我们有滚动到的位置。Grid 将创建布局 HTML 所需的列数,用于每个朋友的 Story,从而为我们创建一个动态且响应式的滚动容器。Grid 使我们能够集中整个效果。
堆叠
对于每个朋友,我们需要他们的 Stories 处于可分页状态。为了准备动画和其他有趣的模式,我选择了一个堆栈。当我说堆栈时,我的意思是像您俯视三明治一样,而不是像您从侧面看一样。
使用 CSS Grid,我们可以定义一个单单元格网格(即正方形),其中行和列共享一个别名 ([story]
),然后每个子元素都被分配到该别名单单元格空间
.user {
display: grid;
grid: [story] 1fr / [story] 1fr;
scroll-snap-align: start;
scroll-snap-stop: always;
}
.story {
grid-area: story;
background-size: cover;
…
}
这使我们的 HTML 可以控制堆叠顺序,并且还可以使所有元素保持在流中。请注意,我们不需要对 absolute
定位或 z-index
做任何事情,也不需要使用 height: 100%
或 width: 100%
进行框校正。父网格已经定义了 Story 图片视口的大小,因此无需告诉任何这些 Story 组件来填充它!
CSS 滚动捕捉点
CSS 滚动捕捉点规范 使在滚动时将元素锁定到视口中变得轻而易举。在这些 CSS 属性存在之前,您必须使用 JavaScript,而且……至少可以说很棘手。查看 Sarah Drasner 的 Introducing CSS Scroll Snap Points,以深入了解如何使用它们。
scroll-snap-points
样式的水平滚动。没有它,用户可以像往常一样自由滚动。有了它,浏览器会轻轻地停留在每个项目上。.stories { display: grid; grid: 1fr / auto-flow 100%; gap: 1ch; overflow-x: auto; scroll-snap-type: x mandatory; overscroll-behavior: contain; touch-action: pan-x; }
.user { display: grid; grid: [story] 1fr / [story] 1fr; scroll-snap-align: start; scroll-snap-stop: always; }
我选择滚动捕捉点有几个原因
- 免费无障碍功能。滚动捕捉点规范指出,默认情况下,按 向左箭头 和 向右箭头 键应在捕捉点之间移动。
- 不断增长的规范。滚动捕捉点规范一直在获得新功能和改进,这意味着我的 Stories 组件可能只会从现在开始变得更好。
- 易于实现。滚动捕捉点实际上是为以触摸为中心的水平分页用例而构建的。
- 免费的平台风格惯性。每个平台都将以其风格滚动和停止,而不是规范化的惯性,后者可能具有奇怪的滚动和停止风格。
跨浏览器兼容性
我们在 Opera、Firefox、Safari 和 Chrome 以及 Android 和 iOS 上进行了测试。以下是我们在功能和支持方面发现差异的 Web 功能的简要概述。
尽管如此,我们确实有一些 CSS 没有应用,因此某些平台目前错失了 UX 优化。我很高兴不需要管理这些功能,并且相信它们最终会到达其他浏览器和平台。
scroll-snap-stop
轮播是促使创建 CSS 滚动捕捉点规范的主要 UX 用例之一。与 Stories 不同,轮播并不总是需要在用户与之交互后停在每张图像上。快速循环浏览轮播可能很好或受到鼓励。另一方面,Stories 最好逐个导航,而这正是 scroll-snap-stop
提供的。
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
}
在撰写本文时,scroll-snap-stop
仅在基于 Chromium 的浏览器上受支持。查看 浏览器兼容性 以获取更新。但这并不是障碍。这只是意味着在不受支持的浏览器上,用户可能会意外跳过朋友。因此,用户只需更加小心,否则我们需要编写 JavaScript 以确保未被跳过的朋友不会被标记为已查看。
如果您有兴趣,请在 规范中阅读更多内容。
overscroll-behavior
您是否曾经在滚动模态框时,突然开始滚动模态框后面的内容?overscroll-behavior
让开发者可以捕获该滚动,并且永远不会让它离开。它在各种情况下都很好用。我的 Stories 组件使用它来防止其他滑动和滚动操作离开组件。
.stories {
overflow-x: auto;
overscroll-behavior: contain;
}
Safari 和 Opera 是两个不 支持 它的浏览器,这完全没问题。这些用户将获得他们习惯的 overscroll 体验,并且可能永远不会注意到这种增强功能。我个人是忠实粉丝,并且喜欢将其作为我实现的几乎每个 overscroll 功能的一部分。这是一个无害的补充,只会带来改进的 UX。
scrollIntoView({behavior: 'smooth'})
当用户点击或单击并已到达朋友的故事集的末尾时,就该移动到滚动捕捉点集中的下一个朋友了。使用 JavaScript,我们能够引用下一个朋友并请求将其滚动到视图中。对此基本功能的支持非常出色;每个浏览器都将其滚动到视图中。但是,并非每个浏览器都以 'smooth'
的方式执行此操作。这只是意味着它被滚动到视图中而不是捕捉到视图中。
element.scrollIntoView({
behavior: 'smooth'
})
Safari 是唯一不支持此处 behavior: 'smooth'
的浏览器。查看 浏览器兼容性 以获取更新。
动手实践
现在您知道我是如何做到的,您会如何做呢?让我们使我们的方法多样化,并学习在 Web 上构建的所有方法。创建一个 Glitch,在 Twitter 上给我发推文 您的版本,我将把它添加到下面的 社区混音 部分。
社区混音
- @geoffrich_ 使用 Svelte:演示 & 代码
- @GauteMeekOlsen 使用 Vue:演示 + 代码
- @AnaestheticsApp 使用 Lit:演示 & 代码