← CSS Animation · 能力速览 / 多条动画与 Motion Path:沿曲线和任意路径运动 待审核 14 / 22
multiple animation · offset · motion path

多条动画与 Motion Path:沿曲线和任意路径运动

让元素的运动轨迹超出「直线平移」有两条路:其一,给一个元素写多条动画,不同属性各用各的 timing-function,合成出单条动画做不到的曲线;其二,用 offset-*(motion path)直接给出一条几何路径,把 offset-distance 从 0% 动画到 100%,元素沿路径走完全程。本页两节分别演示。

1 · 一个元素跑多条动画

animation 支持逗号分隔写多条:animation: a 1s, b 2s;。这不只是简写上的便利——单条动画里,一个元素的不同属性无法用不同的 timing-function;拆成多条动画,每条管一个属性、各用各的缓动,才能合成出曲线轨迹。

语法为 animation: <animation-defined>[, <animation-defined>]*,即逗号分隔的多条动画定义。单条动画无法让同一元素的不同属性使用不同 timing-function,multiple animation 可以。

transform 不能拆成多条组合(同一属性会互相覆盖)——想用 transform 走曲线,可改用容器嵌套(见下方演示的「嵌套点」)。

新进展:如今 translate / rotate / scale 是各自独立的 CSS 属性,且与 transform 属性也彼此独立(渲染时按 translaterotatescaletransformtranslate \to rotate \to scale \to transform 串联),各用一条动画、各用各的 timing-function 即可叠加,互不覆盖。于是「嵌套点」那条曲线也能不嵌套做出来:把 X 交给 transform、Y 交给 translate,两个独立属性各用各的缓动即可(见下方演示的「独立点」)。

四个点都从左上跑到右下,各自演示一种做法:

直线点——单条动画 vh 直接 transform: translate(160px,160px),匀速走对角线。

曲线点——两条动画:topeaseleftlinear。两个方向进度不同步,于是走出一条弧线

嵌套点——因为 transform 不能拆开组合,改用容器嵌套:外层 translateX(linear)、内层 ::after translateY(ease),两层 transform 叠加,同样得到曲线——且走的是合成线程友好的 transform

独立点——和「嵌套点」完全相同的曲线,但不嵌套:把两个轴拆到两个独立属性上——X 用 transform: translateX(linear)、Y 用 translate(ease)。transformtranslate 是各自独立的属性,各跑各的缓动、互不覆盖,合成出和「嵌套点」重合的弧线——只用一个元素,这是「嵌套点」在现代 CSS 里的替代写法。

2 · Motion Path:offset-* 属性

多条动画合成的曲线终归是「两个一维缓动的叠加」;offset-*(替代旧的 motion-* 语法)则让元素沿一条任意几何路径移动:给一条 offset-path,再把 offset-distance 从 0% 动画到 100%,还能用 offset-rotate 让元素始终朝着前进方向。

属性 说明
offset-path ray() · url(#svgPath);basic-shape:circle / ellipse / inset / polygon / path / xywh / rect / shape;box-edge:content-box / padding-box / border-box / fill-box / stroke-box / view-box
offset-distance 在路径上的偏移距离,如 offset-distance: 50%——动画它即可让元素「跑」起来。
offset-position 路径默认起点时(如 ray(0deg) / circle(80px))用它定起点。normal = 50% 50%,auto = left top,或 <position>
offset-anchor 元素哪个点贴着路径走;auto = transform-origin。
offset-rotate auto 朝前进方向(从 x 轴正向算)· <angle> 固定角(向上为 0deg)· auto <angle> 叠加 · reverse = auto 180deg。

3 · DEMO:7 个点,7 种 offset-path

每个点都跑 offset-distance: 0% → 100%,只是 offset-path 不同。灰线是其中绿色点用的 path()

offset-rotate: reverse 让方块在沿路径走时反向朝前(本 demo 各点都加了它)。path() 直接吃一段 SVG path 数据;ray() 从一个点按角度发射;rect / inset / circle / xywh 等 basic-shape 则把形状的轮廓当作路径。

延伸阅读:Responsive CSS Motion Path · CSS-Tricks——offset-path 的坐标是写死的像素,容器一缩放路径就错位。这篇用 SVG viewBox + path() 把路径做成随容器自适应的响应式 motion path,正好补上本页 demo 没覆盖的「容器尺寸变化」问题。

4 · JS 版:getPointAtLength 把任意元素铺上轨道

上一节的 offset-path 是声明式的:一条路径只能驱动单个元素跑 offset-distance。要把多个元素按里程铺在同一条路径上、或读出路径上任意一点的坐标与切线方向,就得用 SVG 几何 API。SVGGeometryElementgetTotalLength() 给出路径总弧长,getPointAtLength(d) 返回里程 d 处的 {x, y};切线方向没有直接 API,在 d 前后各采一点、对差值求 atan2 即得。下面 N 个元素相位均分,用 requestAnimationFrame 沿同一条 path 循环传送。

API 作用
getTotalLength() 路径总弧长(用户坐标),里程的标尺。
getPointAtLength(d) 里程 d 处的坐标点。第 i 个元素放到 ((i/N+phase)((i / N + phase) % 1) \times total,相位均分即均匀铺开。
切线朝向 无直接 API:取 getPointAtLength(dε)getPointAtLength(d - \varepsilon )getPointAtLength(d+ε)getPointAtLength(d + \varepsilon ),atan2(dy, dx) 即该处切线角,让元素鼻子朝着前进方向。

对照上一节:CSS offset-path 一条只管一个元素、坐标写死像素;这里同一条 path 上的 N 个元素全靠 JS 实时算位置,数量、朝向、载体都能随手改——把任意 DOM 元素「穿」到路径上正是 getPointAtLength 相对声明式 motion path 的能力边界。本框是响应式的(aspect-ratio 等比缩放),所以元素用百分比定位:getPointAtLength 返回的是 viewBox 用户坐标,除以 viewBox 宽高换算成 %;切线角在等比缩放下不变,可直接用。