基于触发源的动画

让展开或缩放从触发点发生,保持动作来源的空间连续性。

预览

实时预览

元素从触发它的控件位置展开,例如弹出层从打开它的按钮处生长,而非沿用 CSS 默认的自身中心。

预览
基于触发源的动画
权威词义

术语定义与近义词辨析

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

核心术语origin-aware-animation

基于触发源的动画

Origin-aware animation
权威英文定义

An element animates out of its trigger, like a popover growing from the button that opened it instead of from its own center which is the default in CSS.

中文翻译

元素从触发它的控件位置展开,例如弹出层从打开它的按钮处生长,而非沿用 CSS 默认的自身中心。

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

使用这段动效前的判断

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

目的

让弹出层或展开内容从触发控件处生长,保持动作来源可追踪。

频率

适合中频菜单、气泡和局部展开;全屏页面切换使用更稳定的空间规则。

触发

由具体触发控件打开内容时触发,并根据触发点与浮层位置计算原点。

进入 / 离开

进入从触发点缩放并淡入;离开回到同一触发点,随后恢复焦点。

可打断性

浮层位置变化时平滑更新原点,关闭动作从当前几何状态开始。

手势与键盘

无需拖拽;触发控件必须支持键盘打开、Escape 关闭和正确焦点管理。

减弱动效策略

保持触发点与浮层的静态位置关联,使用短淡入淡出替代缩放位移。

输出

复制到你的界面

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

随参数实时更新
motion.css
.motion-demo.motion-demo--origin-aware-animation {
  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--origin-aware-animation *, .motion-demo.motion-demo--origin-aware-animation *::before, .motion-demo.motion-demo--origin-aware-animation *::after {
  box-sizing: border-box;
}

.motion-demo.motion-demo--origin-aware-animation .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--origin-aware-animation .motion-line {
  display: block;
  width: 100%;
  height: 0.5rem;
  background: #e4e4e7;
  border-radius: 999px;
}

.motion-demo.motion-demo--origin-aware-animation .motion-line:last-child { width: 62%; }

.motion-demo.motion-demo--origin-aware-animation .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--origin-aware-animation .motion-button:focus-visible { outline: 3px solid rgb(37 99 235 / 32%); outline-offset: 3px; }

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

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

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

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

.motion-demo.motion-demo--origin-aware-animation .motion-replay-button,
.motion-demo.motion-demo--origin-aware-animation .motion-undo-button,
.motion-demo.motion-demo--origin-aware-animation .motion-dismiss-button,
.motion-demo.motion-demo--origin-aware-animation .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--origin-aware-animation .motion-replay-button:focus-visible,
.motion-demo.motion-demo--origin-aware-animation .motion-undo-button:focus-visible,
.motion-demo.motion-demo--origin-aware-animation .motion-dismiss-button:focus-visible,
.motion-demo.motion-demo--origin-aware-animation .motion-pause-button:focus-visible,
.motion-demo.motion-demo--origin-aware-animation [data-reorder-item]:focus-visible,
.motion-demo.motion-demo--origin-aware-animation [data-swipe-target]:focus-visible {
  outline: 3px solid rgb(37 99 235 / 32%);
  outline-offset: 3px;
}

.motion-demo.motion-demo--origin-aware-animation .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--origin-aware-animation .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--origin-aware-animation .motion-state-stack { position: relative; width: min(100%, 18rem); height: 9rem; }
.motion-demo.motion-demo--origin-aware-animation .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--origin-aware-animation .motion-state--to { background: #18181b; color: white; }

.motion-demo.motion-demo--origin-aware-animation .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--origin-aware-animation .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--origin-aware-animation .motion-text-stack { position: relative; width: min(100%, 18rem); min-height: 4rem; }
.motion-demo.motion-demo--origin-aware-animation svg { width: min(100%, 20rem); height: auto; }

.motion-demo.motion-demo--origin-aware-animation .motion-surface { transform-origin: top left; animation: motion-origin-aware-animation 220ms cubic-bezier(0.23, 1, 0.32, 1) both; }
@keyframes motion-origin-aware-animation { from { opacity: 0; transform: scale(0.88); } to { opacity: 1; transform: scale(1); } }

@keyframes motion-origin-aware-animation-reduced-fade {
  from { opacity: 0.84; }
  to { opacity: 1; }
}

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

.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation *,
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation *::before,
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation *::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--origin-aware-animation .motion-button,
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation [data-reorder-item],
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation [data-swipe-target],
.force-reduced-motion .motion-demo.motion-demo--origin-aware-animation [data-spring-target] {
  transform: none !important;
}

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

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

  .motion-demo.motion-demo--origin-aware-animation *, .motion-demo.motion-demo--origin-aware-animation *::before, .motion-demo.motion-demo--origin-aware-animation *::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--origin-aware-animation .motion-button,
  .motion-demo.motion-demo--origin-aware-animation [data-reorder-item],
  .motion-demo.motion-demo--origin-aware-animation [data-swipe-target],
  .motion-demo.motion-demo--origin-aware-animation [data-spring-target] {
    transform: none !important;
  }

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

交付前检查

  • 在四个边缘位置测试原点计算。
  • 确认滚动或重排后关闭路径仍指向当前触发控件。
无障碍

保持信息可达

减弱动效

保持触发点与浮层的静态位置关联,使用短淡入淡出替代缩放位移。

参数

参数参考

参数范围 / 选项默认值作用
duration80ms–1200ms220ms控制一次动效完成所需的时间。
scale70%–105%88%控制元素相对最终尺寸的起始比例。
origin左上 / 右上 / 左下 / 右下 / 中心左上选择动作来源。
ease柔和 / 清脆 / 平稳 / 匀速柔和控制速度随时间变化的方式。