← CSS Animation · 能力速览 / Word Rotator:@counter-style 做纯 CSS 词轮播 待审核 9 / 22
综合应用 · @counter-style + @property <integer>

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 wordssystem: 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 · 延伸阅读