直接用 animation: scroll 10s linear infinite 会卡顿或跳帧,因为单段动画终点跳回起点造成视觉断层;必须用双份内容拼接、translateX(-单份总宽)、linear缓动及overflow:hidden实现首尾无缝衔接。

为什么直接用 animation: scroll 10s linear infinite 会卡顿或跳帧
因为多数人只写单段动画,比如从 transform: translateX(0) 到 translateX(-100%),但元素移出视口后没有“接续”的副本,浏览器渲染时会在终点突然跳回起点,产生肉眼可见的停顿。这不是性能问题,是动画逻辑断层。
关键不是加 animation-timing-function,而是让滚动内容在视觉上「首尾相接」——你需要两份相同内容并排铺开,再用位移把它们串成环。
- 必须用
display: flex或inline-block让子项水平连续排列,不能靠white-space: nowrap+ 单个文字流,否则无法精确控制总宽度 -
@keyframes的from和to必须对应「完整位移一个可视区宽度」,不是-100%,而是-[单条内容宽度] - 容器要设
overflow: hidden,且不能有边框/内边距干扰计算
怎样用双份内容 + transform: translateX() 构建无缝循环
核心思路:把公告列表复制一份拼在后面,让总宽度 = 2 × 可视区宽度;然后用 translateX 把整个列表匀速左移,当第一份完全移出时,第二份刚好填满视口,视觉无间断。
示例结构:
立即学习“前端免费学习笔记(深入)”;
<div class="marquee">
<div class="marquee-content">
<span>公告1</span>
<span>公告2</span>
<span>公告3</span>
<!-- 复制一遍 -->
<span>公告1</span>
<span>公告2</span>
<span>公告3</span>
</div>
</div>- 给
.marquee设固定宽高、overflow: hidden -
.marquee-content用display: flex,子项flex-shrink: 0防止压缩 - @keyframes 名字叫
scroll-loop,from是transform: translateX(0),to是transform: translateX(-[单条内容总宽度]) - 动画时长按内容密度调,太快看不清,太慢显呆滞;建议从
20s起调
如何动态计算 translateX 终点值避免手动写死
硬写 -600px 或 -100% 很容易错——百分比是相对父容器,但你要位移的是内容整体宽度。最稳的方式是用 JS 测量 .marquee-content 的 scrollWidth,再除以 2(因为内容已复制一份),作为动画终点。
如果坚持纯 CSS,可这样近似:
- 给每条公告设固定宽(如
min-width: 200px),用calc(-1 * var(--item-count) * 200px) - 或利用 Flex 布局特性:设
.marquee-content { width: max-content; },再用translateX(-50%)配合animation-fill-mode: forwards——但此法兼容性差,Safari 15.4 前不支持max-content在动画中生效 - 真正可靠的纯 CSS 方案:用
inline-flex+white-space: nowrap,然后translateX(-100vw),前提是所有公告加起来宽度 ≥ 100vw,否则会留白
移动端 Safari 和旧版 Chrome 的常见断裂点
不是所有浏览器都把 transform 动画当成合成层处理,尤其当父容器有 border-radius 或 box-shadow 时,iOS Safari 可能强制重绘整层,导致掉帧甚至闪屏。
- 务必给滚动容器加
will-change: transform,但别滥用——只在动画开始前加,结束就移除(可用 JS 控制,或用@supports (will-change: transform)包裹) - 避免在
.marquee-content上设filter、opacity等触发全层重绘的属性 - Android WebView 4.4–6.0 对
@keyframes内transform支持不稳定,需加-webkit-transform前缀,且动画名不能含下划线 - 如果内容含图片,记得设
img { display: block; },防止行内图片基线对齐导致高度抖动
最麻烦的其实是字体加载时机:WebFont 还没就绪时,文字宽度算不准,双份内容可能错位。得等 document.fonts.ready 后再启动动画,或者用 font-display: swap 保底。


















