创建 CSS 轮播
CSS overflow 模块定义了一些特性,可以用来创建灵活且无障碍的纯 CSS 轮播,它带有浏览器生成且可由开发者设置样式的滚动按钮和滚动标记。本指南将解释如何使用这些特性来创建一个轮播。
轮播概念
轮播是 Web 上的一个常见功能。它们通常以滚动内容区域的形式出现,其中包含多个项目,例如演示幻灯片、广告、头条新闻或关键产品特性。
用户可以通过点击或激活导航按钮或通过滑动来浏览这些项目。导航通常包括:
-
通常是“上一个”和“下一个”按钮或链接。
- 滚动标记
-
一系列按钮或链接图标,每个图标代表一个或多个项目,具体取决于轮播内每个滚动位置显示的项目数量。
轮播的一个关键特性是分页——项目感觉像是独立的内容片段,用户在它们之间移动,而不是形成一个连续的内容部分。你可以一次显示一个项目,或者在每个轮播“页面”上显示多个项目。当多个项目可见时,每次按下“下一个”或“上一个”按钮时,你可能会显示一组全新的项目。或者,你可以在列表的一端添加一个新项目,同时将另一端的项目移出视图。
使用 JavaScript 实现轮播可能相当脆弱且具有挑战性。它需要脚本将滚动标记与它们所代表的项目关联起来,同时不断更新滚动按钮以保持其正常运行。当使用 JavaScript 创建轮播时,还必须额外添加轮播和相关控件的无障碍性。
幸运的是,我们可以使用 CSS 轮播特性创建带有相关控件的无障碍轮播,而无需使用 JavaScript。
CSS 轮播特性
CSS 轮播特性提供了一些伪元素和伪类,使得仅使用 CSS 和 HTML 就可以创建轮播,浏览器以一种无障碍、灵活且一致的方式处理大部分滚动和链接引用。这些特性如下:
-
这些伪元素在滚动容器内部生成,代表滚动按钮,用于将容器向指定方向滚动。
::scroll-marker-group
-
在滚动容器内部生成;用于收集和布局滚动标记。
::scroll-marker
-
在滚动容器祖先元素的子元素内部或滚动容器的列内生成,以表示它们的滚动标记。可以选择这些标记将容器滚动到其关联的子元素或列,它们被收集在滚动容器的
::scroll-marker-group
中以便进行布局。 :target-current
-
这个伪类可以用来选择当前激活的滚动标记。它可以用来为当前激活的标记提供高亮样式,这对于可用性和无障碍性非常重要。
::column
-
当一个容器通过 CSS 多列布局 设置为多列显示其内容时,此伪元素代表生成的各个列。它可以与
::scroll-marker
结合使用,为每一列生成一个滚动标记。
单页轮播
我们的第一个示例是一个单页轮播,每个项目占据整个页面。我们有滚动标记作为底部导航,以及页面两侧的滚动按钮,使用户能够移动到下一页和上一页。
我们将使用 flexbox 来布局轮播,使用滚动捕捉来强制实现清晰的分页,并使用锚点定位来定位滚动按钮和滚动标记相对于轮播的位置。
HTML 由一个标题元素和一个无序列表组成,每个列表项都包含一些示例内容:
<h1>CSS carousel single item per page</h1>
<ul>
<li>
<h2>Page 1</h2>
</li>
<li>
<h2>Page 2</h2>
</li>
<li>
<h2>Page 3</h2>
</li>
<li>
<h2>Page 4</h2>
</li>
</ul>
使用 flexbox 进行轮播布局
我们使用 flexbox 来创建一行项目;<ul>
是 flex 容器,其子列表项 <li>
水平显示,每个项目占据轮播的整个宽度。
无序列表通过 100vw
的 width
设置来填充视口的全宽;它还被赋予了 300px
的 height
和一些 padding
。然后我们使用 flexbox 来布局列表——将 display
值设置为 flex
,使得子列表项以行显示(由于默认的 flex-direction
值为 row
),每个项目之间有 4vw
的 gap
。
ul {
width: 100vw;
height: 300px;
padding: 20px;
display: flex;
gap: 4vw;
}
现在是时候为列表项设置样式了。第一个声明提供了基本的样式。重要的声明是 flex
值为 0 0 100%
,这强制每个项目与容器(<ul>
)一样宽。结果,内容将溢出其容器,视口将水平滚动。
li {
list-style-type: none;
background-color: #eeeeee;
border: 1px solid #dddddd;
padding: 20px;
flex: 0 0 100%;
}
li:nth-child(even) {
background-color: cyan;
}
此外,每个偶数编号的列表项通过 :nth-child()
被赋予了不同的背景颜色,以便更容易地看到滚动效果。
在列表上设置滚动捕捉
在本节中,我们将在 <ul>
上设置一个 overflow 值,将其变成一个滚动容器,然后应用 CSS 滚动捕捉,使列表在内容滚动时捕捉到每个列表项的中心。
在 <ul>
上设置 overflow-x
值为 scroll
,使其内容在列表内水平滚动,而不是整个视口滚动。然后使用 CSS 滚动捕捉来捕捉到每个“页面”——设置 scroll-snap-type
值为 x mandatory
,使列表成为一个滚动捕捉容器。关键字 x
会使容器的捕捉目标在水平方向上被捕捉,而关键字 mandatory
意味着容器在滚动动作结束时总是会捕捉到一个捕捉目标。
ul {
overflow-x: scroll;
scroll-snap-type: x mandatory;
}
接下来,在列表项上设置 scroll-snap-align
值为 center
,这样当列表滚动时,它会捕捉到每个列表项的中心。
li {
scroll-snap-align: center;
}
到目前为止显示的代码呈现如下:
尝试通过滑动或使用滚动条来滚动列表,以查看滚动捕捉的效果。无论你在哪里结束滚动动作,一个项目总是会“捕捉”到位。
注意: 使用 CSS 轮播特性并非强制要求使用 CSS 滚动捕捉。但是,包含了滚动捕捉的轮播效果要好得多。如果没有滚动捕捉,滚动按钮和标记将不太可能在页面之间干净地导航,结果会不尽如人意。
创建滚动按钮
在本节中,我们将在示例中添加“上一个”和“下一个”滚动按钮,以提供一个在轮播页面之间导航的工具。这是通过使用 ::scroll-button()
伪元素实现的。
::scroll-button()
伪元素仅当其 content
属性被设置为除 none
之外的值时,才会在滚动容器内部生成按钮。每个 ::scroll-button()
代表一个滚动按钮,其滚动方向由选择器的参数指定。每个滚动容器最多可以生成四个滚动按钮,每个按钮将容器的内容向块轴或行内轴的开始或结束方向滚动。
你还可以指定一个 *
参数来用样式定位所有的 ::scroll-button()
伪元素。
首先,为所有滚动按钮设定一些基本样式,以及基于不同状态的样式。为键盘用户设置 :focus
样式是很重要的。此外,由于滚动按钮在无法再向该方向滚动时会自动设置为 disabled
,我们使用 :disabled
伪类来定位这种状态。
ul::scroll-button(*) {
border: 0;
font-size: 2rem;
background: none;
color: black;
opacity: 0.7;
cursor: pointer;
}
ul::scroll-button(*):hover,
ul::scroll-button(*):focus {
opacity: 1;
}
ul::scroll-button(*):active {
translate: 1px 1px;
}
ul::scroll-button(*):disabled {
opacity: 0.2;
cursor: unset;
}
接下来,通过 content
属性为左、右滚动按钮设置适当的图标,这也是导致滚动按钮生成的原因:
ul::scroll-button(left) {
content: "◄";
}
ul::scroll-button(right) {
content: "►";
}
定位滚动按钮
我们已经创建了滚动按钮。现在我们将使用 CSS 锚点定位将它们相对于轮播进行定位。
首先,在列表上设置一个参考 anchor-name
。接下来,每个滚动按钮的 position
设置为 absolute
,其 position-anchor
属性设置为在列表上定义的相同参考名称,以将两者关联起来。
ul {
anchor-name: --my-carousel;
}
ul::scroll-button(*) {
position: absolute;
position-anchor: --my-carousel;
}
为了实际定位每个滚动按钮,我们在它们的内边距属性上设置值。我们使用 anchor()
函数来定位按钮的指定边相对于轮播的边。在每种情况下,都使用 calc()
函数在按钮边缘和轮播边缘之间添加一些空间。例如,左滚动按钮的右边缘被定位在轮播左边缘向右 70 像素的位置。
ul::scroll-button(left) {
right: calc(anchor(left) - 70px);
bottom: calc(anchor(top) + 13px);
}
ul::scroll-button(right) {
left: calc(anchor(right) - 70px);
bottom: calc(anchor(top) + 13px);
}
加入滚动按钮的代码后,我们得到以下结果:
尝试按下“上一个”和“下一个”滚动按钮,看看页面是如何滚动的,同时遵循滚动捕捉的行为。还要注意,当列表滚动到内容开始处时,“上一个”按钮是如何自动禁用的,而当列表滚动到内容结束处时,“下一个”按钮是如何自动禁用的。
创建滚动标记
滚动标记是一组按钮,每个按钮都将轮播滚动到与其中一个内容页面相关的位置。它们提供了额外的导航工具,同时也指示了你在轮播页面中的进度。
在本节中,我们将向轮播添加滚动标记,这涉及三个主要功能:
scroll-marker-group
属性设置在滚动容器元素上。它需要被设置为一个非none
的值,以便生成::scroll-marker-group
伪元素;它的值指定了滚动标记组在轮播的 Tab 顺序和布局盒顺序中的出现位置(但不是 DOM 结构)——before
将其放在开始处,滚动按钮之前,而after
将其放在末尾。::scroll-marker-group
伪元素存在于滚动容器内部,用于将滚动标记作为一个整体进行收集、包含和布局。::scroll-marker
伪元素存在于滚动容器祖先元素的子元素和::column
片段内部,并代表它们的滚动标记。这些标记被收集在祖先元素的::scroll-marker-group
中以便进行布局。
首先,列表的 scroll-marker-group
属性被设置为 after
,这样 ::scroll-marker-group
伪元素就会在焦点和布局盒顺序中被放置在列表的 DOM 内容之后;这意味着它位于滚动按钮之后:
ul {
scroll-marker-group: after;
}
注意:或者,可以使用 scroll-target-group
从一个包含一组 <a>
元素的现有元素创建一个滚动标记组容器。
接下来,列表的 ::scroll-marker-group
伪元素使用 CSS 锚点定位相对于轮播进行定位,类似于滚动按钮,但它在轮播上水平居中,使用 justify-self
值为 anchor-center
。该组使用 flexbox 进行布局,justify-content
值为 center
,gap
为 20px
,这样其子元素(::scroll-marker
伪元素)就会在 ::scroll-marker-group
内部居中,并且每个元素之间有间隙。
ul::scroll-marker-group {
position: absolute;
position-anchor: --my-carousel;
top: calc(anchor(bottom) - 70px);
justify-self: anchor-center;
display: flex;
justify-content: center;
gap: 20px;
}
接下来,我们处理滚动标记本身的外观和感觉;它们可以像任何其他生成内容一样进行样式设置。需要注意的是,我们需要为 content
属性设置一个非 none
的值,以便实际生成滚动标记。我们还设置了一些基本样式,使标记显示为带轮廓的圆形:
li::scroll-marker {
content: "";
width: 16px;
height: 16px;
background-color: transparent;
border: 2px solid black;
border-radius: 50%;
}
注意: 生成的内容默认是内联的;我们可以对我们的滚动标记应用 width
和 height
,因为它们是作为 flex 项目进行布局的。
最后,对于本节,使用 :target-current
伪类来选择与当前可见“页面”相对应的滚动标记,突出显示用户在内容中滚动的进度。在这种情况下,我们将 background-color
设置为 black
,使其样式为一个实心圆。
li::scroll-marker:target-current {
background-color: black;
}
单页轮播最终效果
所有上述代码结合在一起,创建了以下结果:
自上一个实时示例以来,已经添加了滚动标记——尝试按下它们直接跳转到每个页面。请注意当前标记是如何高亮的,这样你就可以看到你在分页中的位置。也请尝试使用 Tab 键切换到滚动标记组,然后使用光标键循环浏览每个页面。
你还可以通过向左和向右滑动、拖动滚动条或按下滚动按钮来在页面之间导航。
响应式轮播:每页多个项目
第二个示例是一个每页有多个项目的轮播,它同样包含了滚动按钮和滚动标记用于在页面间导航。这个示例也是响应式的——根据视口的宽度,每个页面上会出现不同数量的项目。
这个示例与单页轮播示例非常相似,不同之处在于它不使用 flexbox 进行布局,而是使用 CSS 多列布局和 ::column
伪元素来创建跨越轮播整个宽度的任意列,这些列可能包含多个项目。
使用这种方法,我们可以确保如果视口变大或变小,而项目大小保持不变,我们永远不会有部分项目显示在滚动端口的边缘之外。在这种情况下,滚动标记是按列在滚动容器片段上创建的,而不是按项在子元素上创建的。
HTML 与上一个示例非常相似,只是列表项的数量要多得多,而且由于一次会显示多个项目,我们将其标记为项目而不是页面:
...
<li>
<h2>Item 1</h2>
</li>
...
这个示例的 CSS 也非常相似,除了在以下部分中解释的规则外。
使用列布局的轮播
这个示例使用 CSS 多列布局,而不是 flexbox,来布局轮播项目。columns
值为 1
强制每列都占据容器的整个宽度,内容一次显示一列。还应用了 text-align
值为 center
,强制内容与列表中心对齐。
ul {
width: 100vw;
height: 300px;
padding: 10px;
overflow-x: scroll;
scroll-snap-type: x mandatory;
columns: 1;
text-align: center;
}
我们为列表项提供了基本的盒子样式,然后应用布局样式,以允许一个或多个项目适应单个内容列,具体取决于视口宽度。随着列表变宽或变窄,数量会动态变化。
li {
list-style-type: none;
display: inline-block;
height: 100%;
width: 200px;
background-color: #eeeeee;
border: 1px solid #dddddd;
padding: 20px;
margin: 0 10px;
text-align: left;
}
li:nth-child(even) {
background-color: cyan;
}
关键的布局属性如下:
display
值为inline-block
被设置为强制列表项并排排列,并使列表水平滚动。- 为它们设置了一个
200px
的绝对width
来控制它们的大小,这意味着一个或多个项目将适应一个随着视口宽度增长和收缩的列。 - 为它们设置了
text-align
值为left
,以覆盖父容器上设置的text-align: center
,因此项目内容将左对齐。
scroll-snap-align
属性现在设置在 ::column
伪元素上——这些伪元素代表由 columns
属性生成的内容列——而不是列表项。我们想要捕捉到每个完整的列,而不是每个单独的列表项,每次滚动操作都显示所有新项目。
ul::column {
scroll-snap-align: center;
}
列滚动标记
在这个示例中,用于创建滚动标记的 CSS 与上一个示例几乎相同,只是选择器不同——滚动标记是在生成的 ::column
伪元素上创建的,而不是在列表项上。注意我们在这里包含了两个伪元素来在生成的列上生成滚动标记。
ul::column::scroll-marker {
content: "";
width: 16px;
height: 16px;
background-color: transparent;
border: 2px solid black;
border-radius: 10px;
}
ul::column::scroll-marker:target-current {
background-color: black;
}
响应式轮播最终效果
响应式轮播的渲染效果如下:
尝试通过向左和向右滑动、使用滚动条、按下滚动按钮以及按下滚动标记在不同页面之间导航。功能与单页 flexbox 示例类似,不同的是现在每个导航位置都有多个列表项;滚动标记设置在可能包含多个项目的列片段上,而不是每个项目上。
此外,尝试调整屏幕宽度,你会看到列表内能容纳的列表项数量发生了变化——因此生成的列数也发生了变化。随着列数的变化,滚动标记的数量会动态更新,以便每个列都在滚动标记组中有所代表。
另见
- CSS overflow 模块
- CSS anchor positioning 模块
- CSS scroll snap 模块
- CSS 轮播库 via chrome.dev (2025)