在网页开发中,带有无限循环滑动动画的logo集合是一个经典组件。从古老(现已废弃)的<marquee>
元素开始,我们可以找到无数示例和实现方式。几年前我也曾写过一篇相关文章。
**"为什么还要再写一篇?"**你可能会问。CSS不断发展,涌现出许多强大新特性,因此我始终在寻找优化改进的空间。现在我们就将运用一些新的CSS特性来实现这个效果。
截至本文撰写时,只有基于Chrome的浏览器完全支持我们将使用的特性,包括
shape()
、sibling-index()
和sibling-count()
等功能。
在上面的演示中,我们实现了一个支持任意数量图片的无限跑马灯动画。只需在HTML中添加任意数量的元素,完全无需修改CSS代码。通过调整一个变量就能轻松控制可见图片数量,而且效果是响应式的——调整屏幕尺寸时可以看到平滑的适配效果。
你可能会认为代码冗长且充满复杂计算,但实际上只需不到10行CSS代码,完全不需要JavaScript:
.container {
--s: 150px; /* 图片尺寸 */
--d: 8s; /* 动画时长 */
--n: 4; /* 可见图片数量 */
display: flex;
overflow: hidden;
}
img {
width: var(--s);
offset: shape(from calc(var(--s)/-2) 50%,hline by calc(sibling-count()*max(100%/var(--n),var(--s))));
animation: x var(--d) linear infinite calc(-1*sibling-index()*var(--d)/sibling-count());
}
@keyframes x {
to { offset-distance: 100%; }
}
乍看之下可能有些复杂,尤其是那个奇怪的offset
属性!别盯着它看太久,我们会一起剖析它,到文章结束时它就会显得相当简单了。
实现思路
创建跑马灯效果最棘手的部分是实现循环动画——每个元素需要"跳转"回起点重新滑动。早期的实现会复制元素来模拟无限动画,但这不是个好方法,因为它需要操作HTML,可能引发可访问性和性能问题。
一些现代实现依赖于复杂的平移动画,在元素移出可视区域时(用户看不见)创建"跳转"效果,同时在可视区域内保持连续运动。这种方法很完美,但需要复杂计算,且可能依赖于HTML中的元素数量。
如果我们能有一种原生方法创建带有"跳转"的连续动画,同时适用于任意数量元素就完美了。第一部分是可以实现的,我们甚至不需要现代CSS——可以使用offset
结合path()
,其中路径是一条直线。
在path中,我使用SVG语法定义一条线,然后通过将offset-distance
从0%动画到100%来沿该线移动图像。初看很完美,但这种方法不够灵活,因为path()
只接受硬编码的像素值。
为了克服path()
的限制,我们将使用新的shape()
函数!以下是规范中的描述:
shape()函数使用一组与path()大致等效的命令,但采用更标准的CSS语法,并允许完整的CSS功能,如额外单位和数学函数...从这个意义上说,shape()是path()的超集。
我们将使用shape()
而非path()
来绘制线条,以便利用CSS功能并根据元素数量控制线条。
具体实现
从HTML结构开始——容器内包含一组图片:
<div class="container">
<img src="">
<img src="">
<!-- 任意数量的图片 -->
</div>
我们将容器设为flex布局,移除图片间的默认间距,并确保即使容器较小也不会换行(记住flex-wrap
默认是nowrap
)。
假设我们想同时显示N张图片,需要将容器宽度设为N × 图片尺寸
:
.container {
--s: 100px; /* 图片尺寸 */
--n: 4; /* 可见图片数量 */
display: flex;
width: calc(var(--n) * var(--s));
overflow: hidden;
}
img {
width: var(--s);
}
要实现连续动画,线条长度需要等于图片总数乘以单张图片尺寸。我们可以在图片元素上定义offset
属性,并利用新的sibling-count()
获取图片总数:
offset: shape(from X Y, hline by calc(sibling-count() * var(--s)));
尝试将X Y
设为0 0
,会发现所有图片堆叠在一起且位置偏移。这是因为offset
应用于子元素(图片),但参考系是父容器。0 0
表示线条起点在父容器的左上角。
通过将线条位置调整为0 50%
,可以把图片带入容器内:
offset: shape(from 0 50%, hline by calc(sibling-count() * var(--s)));
再将X
值设为-S/2
,线条起点移到容器外,就看不到图片的"跳转"了:
offset: shape(from calc(var(--s)/-2) 50%, hline by calc(sibling-count() * var(--s)));
为了解决图片重叠问题,需要为每张图片设置不同的延迟。我们可以利用sibling-index()
获取每张图片在容器中的索引,从而为每张图片设置不同的延迟:
animation:
x var(--d) linear infinite
calc(-1*sibling-index()*var(--d)/sibling-count());
最终完整代码如下:
.container {
--s: 100px; /* 图片尺寸 */
--d: 4s; /* 动画时长 */
--n: 4; /* 可见图片数量 */
display: flex;
width: calc(var(--n) * var(--s));
overflow: hidden;
}
img {
width: var(--s);
offset: shape(from calc(var(--s)/-2) 50%, hline by calc(sibling-count() * var(--s)));
animation: x var(--d) linear infinite calc(-1*sibling-index()*var(--d)/sibling-count());
}
@keyframes x {
to {offset-distance: 100%}
}
实现响应式
前面的例子中我们固定了容器宽度来显示指定数量的图片,但如果容器宽度未知如何实现响应式?我们希望在不固定宽度的情况下同时显示N张图片。
关键观察是:如果容器宽度大于N×S
,图片间会有空隙,因此shape()
定义的线条需要更长以包含额外空间。线条新长度应为:
hline by calc(sibling-count() * 100% / var(--n))
因为offset
以父容器为参考系,100%
就代表容器宽度。更新后的offset值为:
shape(from calc(var(--s)/-2) 50%, hline by calc(sibling-count() * 100% / var(--n)));
现在动画已经是响应式的了。但如果容器太小,图片会重叠。我们可以结合之前的代码,确保线条长度至少等于图片总数乘以单张图片尺寸:
max(sibling-count() * 100%/var(--n), sibling-count() * var(--s))
进一步优化代码:
calc(sibling-count() * max(100%/var(--n),var(--s)))
还可以添加一个小值作为图片间的最小间距:
calc(sibling-count() * max(100%/var(--n),var(--s) + 10px))
这样我们就实现了一个完全响应式的现代CSS跑马灯动画!
更多示例
虽然我们使用图片来解释这项技术,但它可以轻松扩展到任何类型的内容,唯一要求/限制是项目宽度必须相同。
我们可以创建文本动画:
<div class="container">
<div>Text 1</div>
<div>Text 2</div>
<!-- 更多文本项 -->
</div>
或者更复杂的图文组合:
<div class="container">
<div class="item">
<img src="...">
<p>Description 1</p>
</div>
<div class="item">
<img src="...">
<p>Description 2</p>
</div>
<!-- 更多项目 -->
</div>
在这些例子中,我们使用flex-shrink: 0
来避免flex项目在容器变小时的默认收缩效果(图片不会收缩到小于定义尺寸,所以之前没有这个问题)。
总结
虽然你们中的许多人可能永远不需要跑马灯动画,但这是一个探索现代CSS特性的好机会,比如shape()
和sibling-*()
函数。更不用说CSS变量、calc()
、max()
等的使用——尽管它们更常见,但我仍然认为属于现代CSS的一部分。
使用
min()
或max()
并不总是那么直观,但我有一个小教程可以帮助你判断何时使用哪个。
原文链接:https://frontendmasters.com/blog/infinite-marquee-animation-using-modern-css/
作者:Temani Afif

優(yōu)網(wǎng)科技秉承"專業(yè)團(tuán)隊(duì)、品質(zhì)服務(wù)" 的經(jīng)營理念,誠信務(wù)實(shí)的服務(wù)了近萬家客戶,成為眾多世界500強(qiáng)、集團(tuán)和上市公司的長期合作伙伴!
優(yōu)網(wǎng)科技成立于2001年,擅長網(wǎng)站建設(shè)、網(wǎng)站與各類業(yè)務(wù)系統(tǒng)深度整合,致力于提供完善的企業(yè)互聯(lián)網(wǎng)解決方案。優(yōu)網(wǎng)科技提供PC端網(wǎng)站建設(shè)(品牌展示型、官方門戶型、營銷商務(wù)型、電子商務(wù)型、信息門戶型、微信小程序定制開發(fā)、移動端應(yīng)用(手機(jī)站、APP開發(fā))、微信定制開發(fā)(微信官網(wǎng)、微信商城、企業(yè)微信)等一系列互聯(lián)網(wǎng)應(yīng)用服務(wù)。