必须用双份内容+精确位移对齐才能实现真正无缝滚动,单组translateX(-100%)因浏览器渲染机制必然闪动;需white-space:nowrap、display:block消除inline间隙,加will-change:transform启用硬件加速,并避免Safari兼容性问题。

直接用 @keyframes + transform: translateX() 做无限滚动,90% 的人第一步就错在只放一组内容、位移设为 -100%——这必然闪动或露白,不是浏览器问题,是逻辑断层。
为什么单组内容 + translateX(-100%) 一定不无缝
动画终点 translateX(-100%) 的 100% 指的是子元素自身宽度,不是容器宽。一组文字或图片滚完,视口右侧立刻空出一段;下一帧若没精确对齐“第二组开头”,就会撕裂或跳帧。尤其在 Safari、低端安卓 WebView 或高 DPI 屏上,这种断层肉眼可见。
根本原因:CSS 动画不理解“循环”,它只是从 A 点线性走到 B 点,然后硬切回 A。没有物理衔接,就没有视觉无缝。
-
overflow: hidden只能藏住溢出,藏不住动画终点和起点之间的空白 - 哪怕加了
will-change: transform,也救不了逻辑缺陷 - 用
margin-left或left替代transform更糟——触发重排,掉帧更明显
必须用双份内容,且总宽 ≥ 容器宽 × 2
DOM 里得写两遍相同内容(比如 <div class="track">A B C</div><div class="track">A B C</div>),或用 ::after 伪元素复制。关键不是“复制动作”,而是让两组拼起来的总宽度撑开到至少容器宽度的两倍。
立即学习“前端免费学习笔记(深入)”;
- 父容器必须设
white-space: nowrap,否则换行会破坏水平连续性 - 所有子元素(如
<img>或<span>)要设display: inline-block或display: block,干掉 inline 元素默认的基线间隙 - 别用
font-size: 0这种 hack——不同字体下间隙值不稳定,vertical-align: top更可靠 - 动画位移终点必须等于单组内容宽度(即
translateX(-100%)中的100%是单组宽),不是容器宽
animation-delay 负值不是可选,是必填项
不加负延迟,用户第一眼看到的是静止帧(translateX(0)),然后才开始动——这就是“首帧卡顿”。负延迟让动画直接从中间状态渲染,模拟出“已滚动一段时间”的自然起始。
- 若动画总时长是
20s,想让它从第8s状态开始,就写animation-delay: -8s - 必须配
animation-fill-mode: backwards,否则延迟期间样式会退回到未动画状态 - 负延迟绝对值不能 ≥ 动画时长,否则行为不可控(多数浏览器取模,但结果难预测)
- 如果用两组内容分别动画,d1 和 d2 的延迟要错开(比如
-20s和-10s),才能形成接力
hover 暂停时别信纯 CSS 的 animation-play-state: paused
暂停后恢复播放,动画会从当前时间点接续,但 transform 值是线性累积的——比如停在 67.3%,恢复瞬间位移值突变,导致视觉“抽搐”。CSS 本身无法冻结并重置动画计时器。
- 纯 CSS 场景下,唯一稳妥做法是把动画周期拉长(比如
40s以上),降低暂停感知 - 若需精准控制,必须用 JS 监听
mouseenter/mouseleave,动态修改animation-duration或重置animation属性 - 千万别用
transition去缓动animation-play-state——无效,且可能触发额外重绘
真正容易被忽略的,是 white-space: nowrap 和 display: block 必须同时生效——缺一不可。HTML 换行符、空格、字体渲染差异都会悄悄吃掉几像素宽度,让两组内容总宽刚好差那么一点点,结果就是滚动到某处突然露底。动手前先用 DevTools 检查实际 computed width,比猜参数靠谱得多。


















