文字形变

在长度接近的短文本之间进行平滑替换,保留阅读位置。

预览

实时预览

文字变化时逐字符播放动画,把注意力引向新值。

预览
构建
权威词义

术语定义与近义词辨析

英文定义逐字保留自 Animation Vocabulary;中文翻译和辨析帮助你选对术语。

核心术语text-morph

文字形变

Text morph
权威英文定义

Text that animates character by character when it changes, drawing attention to the new value.

中文翻译

文字变化时逐字符播放动画,把注意力引向新值。

查看全部 91 个术语
设计规范

使用这段动效前的判断

目的、频率、触发、时序、可打断性和手势规则共同决定实现。

目的

在长度接近的短文本值之间平滑替换,并把注意力引向变化内容。

频率

适合低到中频指标、状态词和短标签;正文段落直接替换。

触发

由值变化触发,只动画发生变化的字符或词组。

进入 / 离开

旧字形弱化并让位,新字形在同一基线出现;容器宽度保持可预测。

可打断性

快速更新时合并中间值,只向最新文本收敛。

手势与键盘

无需手势;实时值提供 aria-live 策略,避免逐字符播报。

减弱动效策略

立即替换完整文本,使用静态高亮或一次短淡入提示变化。

输出

复制到你的界面

当前参数会同步生成视觉状态 CSS、语义 HTML、所需的原生 JS 运行时和交互提示词。

随参数实时更新
motion.css
.motion-demo.motion-demo--text-morph {
  position: relative;
  display: grid;
  place-items: center;
  width: min(100%, 34rem);
  min-height: 15rem;
  padding: 2rem;
  overflow: hidden;
  color: #18181b;
  background: #fafafa;
  border: 1px solid #e4e4e7;
  border-radius: 12px;
  isolation: isolate;
}

.motion-demo.motion-demo--text-morph *, .motion-demo.motion-demo--text-morph *::before, .motion-demo.motion-demo--text-morph *::after {
  box-sizing: border-box;
}

.motion-demo.motion-demo--text-morph .motion-surface {
  position: relative;
  display: grid;
  gap: 0.65rem;
  width: min(100%, 18rem);
  padding: 1.25rem;
  background: #ffffff;
  border: 1px solid #e4e4e7;
  border-radius: 8px;
  box-shadow: 0 1px 2px rgb(0 0 0 / 6%), 0 10px 28px rgb(0 0 0 / 7%);
}

.motion-demo.motion-demo--text-morph .motion-line {
  display: block;
  width: 100%;
  height: 0.5rem;
  background: #e4e4e7;
  border-radius: 999px;
}

.motion-demo.motion-demo--text-morph .motion-line:last-child { width: 62%; }

.motion-demo.motion-demo--text-morph .motion-button {
  position: relative;
  min-height: 2.75rem;
  padding: 0.75rem 1.1rem;
  color: white;
  font: inherit;
  font-weight: 700;
  background: #18181b;
  border: 0;
  border-radius: 7px;
  cursor: pointer;
}

.motion-demo.motion-demo--text-morph .motion-button:focus-visible { outline: 3px solid rgb(37 99 235 / 32%); outline-offset: 3px; }

.motion-demo.motion-demo--text-morph .motion-action-row {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  margin-top: 0.85rem;
}

.motion-demo.motion-demo--text-morph .motion-replay-scene {
  width: min(100%, 22rem);
  display: grid;
  place-items: center;
}

.motion-demo.motion-demo--text-morph .motion-pausable-scene {
  width: min(100%, 22rem);
  display: grid;
  place-items: center;
}

.motion-demo.motion-demo--text-morph button.motion-surface {
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.motion-demo.motion-demo--text-morph .motion-replay-button,
.motion-demo.motion-demo--text-morph .motion-undo-button,
.motion-demo.motion-demo--text-morph .motion-dismiss-button,
.motion-demo.motion-demo--text-morph .motion-pause-button {
  min-height: 2.75rem;
  padding: 0.65rem 0.9rem;
  color: #18181b;
  font: 650 0.78rem/1 ui-sans-serif, system-ui, sans-serif;
  background: #ffffff;
  border: 1px solid #d4d4d8;
  border-radius: 7px;
  cursor: pointer;
}

.motion-demo.motion-demo--text-morph .motion-replay-button:focus-visible,
.motion-demo.motion-demo--text-morph .motion-undo-button:focus-visible,
.motion-demo.motion-demo--text-morph .motion-dismiss-button:focus-visible,
.motion-demo.motion-demo--text-morph .motion-pause-button:focus-visible,
.motion-demo.motion-demo--text-morph [data-reorder-item]:focus-visible,
.motion-demo.motion-demo--text-morph [data-swipe-target]:focus-visible {
  outline: 3px solid rgb(37 99 235 / 32%);
  outline-offset: 3px;
}

.motion-demo.motion-demo--text-morph .motion-status {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.motion-demo.motion-demo--text-morph .motion-ripple-ink {
  position: absolute;
  width: 12px;
  height: 12px;
  pointer-events: none;
  background: rgb(255 255 255 / 72%);
  border-radius: 50%;
  transform: translate(-50%, -50%) scale(0.95);
  opacity: 0;
}

.motion-demo.motion-demo--text-morph .motion-state-stack { position: relative; width: min(100%, 18rem); height: 9rem; }
.motion-demo.motion-demo--text-morph .motion-state { position: absolute; inset: 0; display: grid; place-items: center; padding: 1rem; background: #ffffff; border: 1px solid #e4e4e7; border-radius: 8px; }
.motion-demo.motion-demo--text-morph .motion-state--to { background: #18181b; color: white; }

.motion-demo.motion-demo--text-morph .motion-scroll-track > span { color: #71717a; font: 600 0.72rem/1.2 ui-monospace, monospace; letter-spacing: 0.08em; text-transform: uppercase; }
.motion-demo.motion-demo--text-morph .motion-orbit-item { display: grid; place-items: center; width: 2.5rem; height: 2.5rem; color: white; background: #2563eb; border-radius: 50%; }
.motion-demo.motion-demo--text-morph .motion-text-stack { position: relative; width: min(100%, 18rem); min-height: 4rem; }
.motion-demo.motion-demo--text-morph svg { width: min(100%, 20rem); height: auto; }

.motion-demo.motion-demo--text-morph .motion-word { position: absolute; font-size: 2rem; font-weight: 700; animation: motion-text-morph 240ms cubic-bezier(0.23, 1, 0.32, 1) 120ms both; }
.motion-demo.motion-demo--text-morph .motion-word--from { animation-name: motion-text-morph-out; }
@keyframes motion-text-morph { from { opacity: 0; filter: blur(8px); transform: translateY(0.35em); } to { opacity: 1; filter: blur(0); transform: none; } }
@keyframes motion-text-morph-out { to { opacity: 0; filter: blur(8px); transform: translateY(-0.35em); } }

@keyframes motion-text-morph-reduced-fade {
  from { opacity: 0.84; }
  to { opacity: 1; }
}

.force-reduced-motion .motion-demo.motion-demo--text-morph {
  animation: motion-text-morph-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
}

.force-reduced-motion .motion-demo.motion-demo--text-morph *,
.force-reduced-motion .motion-demo.motion-demo--text-morph *::before,
.force-reduced-motion .motion-demo.motion-demo--text-morph *::after {
  animation: none !important;
  animation-delay: 0ms !important;
  animation-iteration-count: 1 !important;
  transition-property: opacity, color, background-color, border-color !important;
  transition-duration: 160ms !important;
  transition-delay: 0ms !important;
  scroll-behavior: auto !important;
}

.force-reduced-motion .motion-demo.motion-demo--text-morph .motion-button,
.force-reduced-motion .motion-demo.motion-demo--text-morph [data-reorder-item],
.force-reduced-motion .motion-demo.motion-demo--text-morph [data-swipe-target],
.force-reduced-motion .motion-demo.motion-demo--text-morph [data-spring-target] {
  transform: none !important;
}

.force-reduced-motion .motion-demo.motion-demo--text-morph .motion-path { stroke-dashoffset: 0 !important; }
.force-reduced-motion .motion-demo.motion-demo--text-morph .motion-word--from { opacity: 0 !important; visibility: hidden; }
.force-reduced-motion .motion-demo.motion-demo--text-morph .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }

@media (prefers-reduced-motion: reduce) {
  .motion-demo.motion-demo--text-morph {
    animation: motion-text-morph-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
  }

  .motion-demo.motion-demo--text-morph *, .motion-demo.motion-demo--text-morph *::before, .motion-demo.motion-demo--text-morph *::after {
    animation: none !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-property: opacity, color, background-color, border-color !important;
    transition-duration: 160ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  .motion-demo.motion-demo--text-morph .motion-button,
  .motion-demo.motion-demo--text-morph [data-reorder-item],
  .motion-demo.motion-demo--text-morph [data-swipe-target],
  .motion-demo.motion-demo--text-morph [data-spring-target] {
    transform: none !important;
  }

  .motion-demo.motion-demo--text-morph .motion-path { stroke-dashoffset: 0 !important; }
  .motion-demo.motion-demo--text-morph .motion-word--from { opacity: 0 !important; visibility: hidden; }
  .motion-demo.motion-demo--text-morph .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }
}
检查要点

交付前检查

  • 测试不同字数、CJK 字符和换行边界。
  • 确保屏幕阅读器只接收最终值。
无障碍

保持信息可达

减弱动效

立即替换完整文本,使用静态高亮或一次短淡入提示变化。

参数

参数参考

参数范围 / 选项默认值作用
duration80ms–1200ms240ms控制一次动效完成所需的时间。
blur0px–20px8px调节过渡模糊。
delay0ms–600ms120ms控制动效开始前的等待时间。
ease柔和 / 清脆 / 平稳 / 匀速柔和控制速度随时间变化的方式。