
滚动字幕代码怎么写:用 HTML 和 CSS 实现基础效果
网页中的滚动字幕常用于展示公告、活动信息、直播状态或一行较长的提示。最简单的做法是让一段文字在固定区域内水平移动。本文使用 HTML 和 CSS 动画实现一个可运行的基础版本,并说明如何控制速度、暂停动画、处理窄屏和动态内容,以及哪些情况不适合用滚动字幕。
先判断是否适合滚动
滚动字幕能节省页面空间,但会持续吸引注意力,也可能让阅读变得困难。它比较适合展示低优先级、短小、可以稍后再看的信息,例如直播间的非关键提示或大屏上的活动名称。
不建议用它承载必须及时阅读的错误信息、付款说明、法律条款、操作步骤或重要通知。对这类内容,应优先让文字静止显示;空间有限时,可以采用明确的展开、分页或滚动容器,让用户自己控制阅读节奏。滚动字幕也不应作为唯一的信息传达方式:如果信息会影响操作结果,应在页面其他位置提供清晰、稳定的说明。
实现原理
基础实现由三部分组成:
.ticker是可视窗口,通过overflow: hidden隐藏窗口以外的内容。.ticker__track是移动轨道,使用 CSS@keyframes从右向左移动。- 轨道中放置两份相同内容。第一份移出窗口时,第二份接续出现,从而形成循环。
以下示例保存为 ticker.html 后即可在现代浏览器中打开运行。它包含完整的 HTML、CSS 和少量 JavaScript:CSS 负责动画,JavaScript 只负责在用户开启“暂停”选项时切换状态。
完整示例
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>滚动字幕示例</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 2rem;
font-family: system-ui, sans-serif;
color: #222;
}
.ticker {
--duration: 18s;
display: flex;
align-items: center;
gap: 1rem;
max-width: 42rem;
padding: 0.75rem 1rem;
border: 1px solid #b8c3d1;
border-radius: 0.5rem;
background: #f5f8fc;
}
.ticker__label {
flex: 0 0 auto;
font-weight: 700;
}
.ticker__viewport {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
}
.ticker__track {
display: flex;
width: max-content;
animation: ticker-scroll var(--duration) linear infinite;
}
.ticker__text {
flex: 0 0 auto;
padding-right: 3rem;
white-space: nowrap;
}
.ticker.is-paused .ticker__track {
animation-play-state: paused;
}
.ticker__toggle {
flex: 0 0 auto;
font: inherit;
}
@keyframes ticker-scroll {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
@media (prefers-reduced-motion: reduce) {
.ticker__track {
animation: none;
transform: none;
}
.ticker__text[aria-hidden="true"] {
display: none;
}
.ticker__viewport {
overflow: visible;
}
.ticker__track {
width: auto;
}
.ticker__text {
white-space: normal;
padding-right: 0;
}
}
</style>
</head>
<body>
<section class="ticker" aria-label="活动公告">
<strong class="ticker__label">公告</strong>
<div class="ticker__viewport">
<div class="ticker__track">
<span class="ticker__text">
周五晚 8 点开始维护,维护期间部分功能将暂时不可用。
</span>
<span class="ticker__text" aria-hidden="true">
周五晚 8 点开始维护,维护期间部分功能将暂时不可用。
</span>
</div>
</div>
<button class="ticker__toggle" type="button" aria-pressed="false">
暂停
</button>
</section>
<script>
const ticker = document.querySelector(".ticker");
const toggle = document.querySelector(".ticker__toggle");
toggle.addEventListener("click", () => {
const paused = ticker.classList.toggle("is-paused");
toggle.setAttribute("aria-pressed", String(paused));
toggle.textContent = paused ? "继续" : "暂停";
});
</script>
</body>
</html>
逐项理解关键样式
窗口负责裁切
.ticker__viewport 使用 overflow: hidden,让轨道可以在一个固定区域内移动而不撑开页面。它设置了 min-width: 0,这点尤其重要:在 flex 布局中,子项默认的最小宽度可能由其内容决定;如果不允许它缩小,长文本可能把容器撑宽,导致裁切看起来失效。
示例中的标签和按钮使用 flex: 0 0 auto,不参与压缩;中间窗口则使用 flex: 1 1 auto 占据剩余空间。若标题特别长或视口很窄,按钮和标签仍会占用空间,中间区域可能变得很窄。实际项目中可以在窄屏隐藏装饰性标签、把按钮移到下一行,或直接改用静态文本。
轨道为什么是两份文字
示例将轨道移动到 translateX(-50%)。由于轨道包含两份长度相同的内容,移动轨道宽度的一半后,第二份内容刚好来到第一份原先的位置,动画回到起点时视觉上就能衔接。
这个计算依赖两个条件:两份内容的宽度相同;轨道的总宽度恰好由这两份内容组成。示例中的两段文字完全相同,样式也相同,因此轨道宽度的一半等于第一段的宽度。若两份文字不一致,或两段之间的间距只加在其中一份上,循环位置就可能出现跳变或空隙。
padding-right: 3rem 用来在每份文字结尾与下一份开头之间留出间隔。它属于内容间距,不是滚动距离的通用修正值。调整文字、字体或间距后,应在不同宽度下实际检查接缝;若需要精确控制轨道距离,可以改用脚本测量内容宽度,但要同时处理字体加载、窗口缩放和内容更新。
速度和方向
动画持续时间由 --duration: 18s 控制。时间越短,移动越快;时间越长,移动越慢。该属性在示例中只是普通 CSS 自定义属性,没有固定的浏览器规定值。可以按内容长度和展示环境调整,但应以可读性为先,避免为了循环更快而把长句高速掠过。
动画使用 linear,表示运动速度保持均匀。若改成默认的缓动曲线,动画在每次循环的起点和终点会明显减速,再重新加速,连续滚动的感觉会不自然。示例从左向右的动画定义为从 translateX(0) 移动到 translateX(-50%);要反向滚动,可以交换起点与终点,或使用 animation-direction: reverse。修改后仍需核对重复内容和接缝位置。
也可以把速度做成不同档位,例如由页面状态切换 CSS 类,再设置不同的持续时间。不要仅凭一个固定的“字符数”估算速度:字形宽度、字体、字号和语言都会影响实际长度。尤其是中日韩文字与拉丁文字混排时,按字符数量推算会有明显误差。
减少动态效果与暂停控制
持续运动可能让部分用户难以阅读,也可能引发不适。示例通过 @media (prefers-reduced-motion: reduce) 检测用户系统中的减少动态效果偏好:不播放动画,显示完整静态文字,并隐藏纯粹用于循环的重复副本。
这一媒体查询不能替代页面上的暂停控制。用户无法预先知道页面会如何动画,也可能在系统设置中没有开启减少动态效果。因此示例另外提供了暂停按钮。点击后,JavaScript 给容器添加 is-paused 类,CSS 再通过 animation-play-state: paused 暂停动画;再次点击会继续播放。
按钮的 aria-pressed 用来表达当前是否处于按下状态,并随操作更新。按钮文字也在“暂停”和“继续”之间切换。真实项目还应检查暂停时文字是否留在用户容易阅读的位置,以及键盘用户能否通过 Tab 定位并用 Enter 或空格操作。不要让滚动字幕在焦点进入时自动跳动,避免用户正在查看内容时位置发生变化。
重复的第二份文字带有 aria-hidden="true",避免辅助技术把相同内容连续读两遍。这个属性只应加在纯视觉重复的副本上,不要误用于唯一的信息文本。示例没有把公告标记为实时区域,因为滚动本身不等同于“内容更新”;只有在内容确实会动态变化、且变化值得立即通知用户时,才应审慎考虑实时区域语义,避免频繁播报造成干扰。
动态内容、字体与窄屏边界
示例适用于构建时就确定内容的一条公告。若公告由接口加载,以下情况都可能改变动画表现:
- 内容为空或短于窗口,轨道仍可能移动,但视觉上没有必要滚动。
- 内容更新后,两份副本没有同步,循环接缝会出现不同文字。
- 字体尚未加载时测得的宽度与最终宽度不同,原先计算的位移可能不再准确。
- 浏览器窗口改变大小,窗口与文本的相对尺寸变化,原本合适的速度可能变得过快或过慢。
- 系统放大页面、页面缩放或用户设置较大的字号后,原有布局可能无法容纳标签和控制按钮。
如果内容由 JavaScript 写入,应确保两份内容始终来自同一个数据源,并在更新后检查动画是否仍有意义。短文本可以静态显示,长文本才启用滚动。对需要根据内容宽度计算时长或距离的实现,应该在字体加载完成和尺寸变化后重新测量,并清理不再需要的事件监听器;简单示例不包含这些逻辑,因此不能直接视为所有动态场景的完整组件。
此外,长字幕中的链接、按钮等交互元素不适合简单复制。如果复制了可聚焦控件,页面可能出现重复的 Tab 停靠点;如果只隐藏副本的可访问性树内容,视觉上仍会出现两套交互控件。较稳妥的做法是让滚动轨道只展示纯文本,把操作链接或按钮放在轨道之外、保持静止。
内容安全:文本不要当作 HTML 执行
滚动效果只负责展示,不会自动让内容变安全。若字幕来自用户输入、第三方接口或其他不可信来源,不要直接用 innerHTML 把字符串插入页面。需要按纯文本显示时,使用 textContent 创建或更新文本节点,浏览器会把其中的尖括号等字符按文本处理,而不是解析成 HTML。
const message = "维护通知:请稍后重试。";
const first = document.querySelector(".ticker__text");
const second = document.querySelector(
'.ticker__text[aria-hidden="true"]'
);
first.textContent = message;
second.textContent = message;
使用 textContent 适合纯文本场景;如果业务确实允许富文本,需要在可信的服务端或经审查的安全流程中按允许的标签和属性进行净化,不能把用户输入原样交给 innerHTML。不要把动画、CSS 转义或 aria-hidden 当成防止跨站脚本攻击的措施,它们都不是输入净化机制。
若把用户可控数据拼入 CSS 自定义属性、选择器或样式字符串,还需避免将原始输入直接拼接为 CSS。尽量把用户输入只作为文本内容处理;需要可配置的动画时长时,使用应用定义的有限档位,而不是接受任意 CSS 代码。不要误以为 CSS 自定义属性会自动验证数值。
常见错误排查
文字不动
- 检查
animation是否写在实际运动的轨道元素上。 - 检查动画名称是否与
@keyframes中的名称一致。 - 检查是否有其他规则覆盖了
animation,或用户环境触发了减少动态效果媒体查询。 - 确认轨道有可见宽度、窗口没有被布局规则压缩到不可见。
整页被长文字撑宽
通常是窗口没有裁切、flex 子项没有设置 min-width: 0,或轨道参与了页面的正常宽度计算。检查 overflow: hidden 是否放在窗口层,而不是误放在包含按钮的整个页面上;也检查轨道是否通过 width: max-content 保持自身内容宽度。
每轮开始时出现跳动或空白
检查是否真的有两份完全相同的内容,文本和间距样式是否一致,动画终点是否与副本边界吻合。若用固定像素位移,调整字体或窗口宽度后位移可能不再正确。若采用百分比位移,要明确百分比相对于哪个元素计算;示例利用轨道包含两份等宽内容这一条件,才可以用 -50%。
字幕太快或读不完
延长动画时间只能缓解速度问题,不能解决内容本身过长或信息重要性过高的问题。优先精简为一句简短提示,提供暂停控制;若用户必须读完全文,应让内容静止显示或提供一个稳定的完整文本区域。
暂停按钮不起作用
确认按钮事件绑定到了正确的元素,is-paused 类添加在动画轨道的祖先元素上,CSS 选择器也与实际类名一致。暂停按钮应是原生 <button>,不要用没有键盘行为的普通 <div> 代替。
关于旧式 <marquee>
过去一些网页使用 <marquee> 实现滚动文字。该元素不属于现代网页推荐的标准实现方式,不适合新项目依赖。CSS 动画更容易控制布局、方向、时长、暂停状态和减少动态效果偏好,也便于与现有样式系统整合。本文示例没有使用 <marquee>,不需要用脚本复制整个元素来模拟循环。
上线前检查清单
- 内容是否适合自动滚动,是否有静态阅读方案?
- 是否提供了暂停方式,并能用键盘操作?
- 是否尊重
prefers-reduced-motion? - 重复副本是否对辅助技术隐藏,唯一信息是否仍可读?
- 窄屏、放大、字体加载和长文本时,页面是否保持可用?
- 不可信内容是否按纯文本处理,没有直接拼进 HTML 或 CSS?
对于一条短公告,以上 HTML 与 CSS 已足以构建基础滚动效果。真正需要仔细权衡的不是动画语法,而是信息是否适合移动、用户能否暂停,以及在不同屏幕、辅助技术和内容更新情况下是否仍然清晰可读。





