Word Rotator:@counter-style 做纯 CSS 词轮播
「我们为 创业者 / 设计师 / 工程师…… 打造工具」这类句子里末尾那个词不断切换的效果,通常靠 JS 定时换文本。本页复刻 Frontend Masters 的一篇文章的思路,用零 JS 做到:把一组待轮播的词当成
@counter-style 的符号表,再用一个被 @property 注册成 <integer> 的自定义属性 --i 当索引。--i 是整数、又可被动画驱动,于是让它在时间轴上一格一格地走,counter(i, words)
就跟着逐词翻页。同一效果的另一条路子(滚动驱动)见本系列 Scroll-Snap Rotator。
1 · 静态机制:词表当符号,--i 当索引
先不谈动画。@counter-style words 用 system: cyclic 把五个词排成一张循环符号表,counter(w, words) 按计数器的值取第几个词。拖索引 --i 滑块,看末尾的词如何随之切换;把 --i 拖过 5 会发生什么——cyclic
会绕回头(第 6 个 = 第 1 个),这正是「轮播」可以无限转下去的根。
@counter-style words {
system: cyclic;
symbols: "创业者" "设计师" "工程师" "教育者" "梦想家";
}
@property --i {
syntax: "<integer>";
inherits: true;
initial-value: 1;
}
.word::after {
counter-reset: w var(--i);
content: counter(w, words);
}
三个零件各司其职:
| 描述符 / 属性 | 作用 |
|---|---|
@counter-style · system: cyclic |
把 symbols 里的词排成循环表;计数器值超过词数会绕回第一个,所以能无限轮播。 |
--i(@property <integer>) |
索引。注册成 <integer> 有两层意义:整数语义贴合「第几个」,且(下一节)让它能被动画插值。 |
counter-reset: w var(--i) |
把名为 w 的计数器置成 --i 的值;counter(w, words) 再用 words 这套符号渲染它。 |
2 · 让它自转:动画一个整数,词就逐个翻
既然词的切换只取决于整数 --i,那让 --i 沿时间轴走起来即可。给它写一段 @keyframes 从 1 到 6,配 animation: … steps(5) infinite——steps(5) 把这段整数区间切成五个等长台阶,每个台阶停在一个整数上、对应一个词,走到第 6(= 绕回第
1)时正好接上循环。拖速度调每词停留时长;勾上进出动画再叠一层 @keyframes 缩放,让每个词淡入淡出。全程零运行时 JS。
@keyframes spin { to { --i: 6; } }
.word {
animation: spin 3s steps(5) infinite;
}
@keyframes pop {
0%, 100% { transform: scale(.78); opacity: 0; }
20%, 80% { transform: none; opacity: 1; }
}
.word.pop {
animation: spin 3s steps(5) infinite,
pop .6s ease-in-out infinite;
}
3 · 为什么必须 @property <integer>
关键不在 @keyframes,而在 --i 被注册过。自定义属性默认类型是 *——动画 / 过渡时不可插值,只会在中途整个跳变一次。一旦用 @property 注册成
<integer>,它就能被逐整数插值:一条 transition: --i 2.5s linear 会让索引平滑地走 1→2→3→4→5,词便逐个翻。下面两列同样点「播放」、同样一句 transition: --i linear,唯一差别是左列注册了 --i、右列没注册。
@property --i { syntax: "<integer>"; initial-value: 1; }
.ok::after { counter-reset: w var(--i); content: counter(w, words); }
.ok { transition: --i 2.5s linear; }
.bad::after { counter-reset: w var(--j); content: counter(w, words); }
.bad { transition: --j 2.5s linear; }
现状提醒: @counter-style 与 @property 均已是 Baseline(Chrome 85+ / Safari 16.4+ / Firefox 128+),本页的纯 CSS 路径在现代浏览器都成立。真正必须有 @property 的是「让整数被动画 / 过渡插值」这一步。若浏览器不支持注册自定义属性,上面的 demo 会退回用
JS 按定时器递增 --i(词仍轮播,但失去 CSS 原生的逐帧插值),并在读数里标注。探测用运行期探针(尝试 CSS.registerProperty),而非 @supports。
4 · 延伸阅读
- Word Rotator with @counter-style · frontendmasters.com——本页出处:把词表塞进
@counter-style、用注册过的整数索引驱动counter()的完整推导。 - @counter-style · developer.mozilla.org——自定义计数器样式:
system(cyclic/fixed/symbolic…)、symbols、additive-symbols等描述符。 - @property · developer.mozilla.org——给自定义属性上类型,使其可被动画 / 过渡插值;另见本系列进阶:让本来不能动的东西动起来。
- counter() · developer.mozilla.org——在
content里渲染一个计数器,第二参数指定用哪套 counter-style。