真正可用的循环跑马灯需用@keyframes+animation配合transform:translateX()、linear时序、内容复制及iOS专属hack(如translateZ(0)),缺一不可。

直接用 @keyframes + animation 就能做出真正可用的循环跑马灯,但90%的人卡在“看起来动了,但一循环就跳帧”或“iOS上不动”,问题不在会不会写动画,而在几个关键细节没对齐。
为什么用 transform: translateX() 而不是 left 或 margin-left
因为 left 和 margin-left 触发 layout(重排),动画一卡一卡;transform 只走合成层,GPU 加速,滚动丝滑。尤其在中低端安卓机或 iOS Safari 上,差别肉眼可见。
- 必须写成
transform: translateX(0)→transform: translateX(-100%),不能用像素值硬写(比如-300px),否则响应式失效 - 如果容器是 flex 或 grid 子项,
left可能完全不生效,transform是唯一可靠路径 - 别把
translateX和translateZ(0)写在同一行声明里,例如transform: translateX(0) translateZ(0),iOS 会忽略后者;要拆成两条:
.marquee-content {
transform: translateX(0);
transform: translateZ(0);
}
animation-timing-function 必须显式设为 linear
默认的 ease 会让跑马灯起步慢、中间快、收尾又慢——人眼一看就是“一顿一顿”,根本不像匀速滚动。这不是 bug,是设计如此。
- 错误写法:
animation: scroll 12s infinite;(没声明 timing function,走浏览器默认) - 正确写法:
animation: scroll 12s linear infinite;或在@keyframes外层加animation-timing-function: linear - 即使
@keyframes里只写了from/to,也不能省略linear,它控制的是动画播放节奏,不是关键帧插值
无缝循环靠内容复制,不是靠动画参数
CSS 动画播完就跳回起点,单段文本永远做不到视觉无缝。所谓“无缝”,本质是让第二段内容紧接第一段末尾,动画位移距离刚好等于容器宽度,这样播到末尾时,第二段开头正好顶上来。
- HTML 里得手动复制内容:
<div class="marquee-content">文本文本</div>,不能只写一次 - 复制部分必须紧挨着,中间不能有空格、换行或注释,否则多出空白间隙
- 动态内容(比如 Vue/React 渲染)必须同步复制 DOM 节点,不能只复制字符串再
innerHTML插入 - 起始位置用
padding-left: 100%或transform: translateX(100%),确保第一帧时文本完全在容器右侧外
iOS Safari 卡顿/闪烁的临时解法
iOS 15–16 的 WebKit 渲染管线对纯 translateX 有已知缺陷,尤其在低电量模式或页面复杂时,动画可能卡住或闪烁。这不是你代码的问题。
- 给滚动元素加
transform: translateZ(0)(注意:不是父容器) - 或者加
will-change: transform,但慎用,长期开启可能增加内存压力 - 如果用了 Vue/React,确保这个样式应用在最终渲染的 DOM 元素上,而不是 wrapper div
最麻烦的地方其实是“复制内容”和“iOS 渲染 hack”的组合:既要保证 HTML 结构干净可维护,又要让 translateZ(0) 真正生效——这两点一旦漏掉一个,跑马灯就在生产环境里半死不活。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











