第 12 章 SVG 动画

SVG 中有几种让图像动起来的方法

  • 基于 SMIL 的动画
  • 基于 CSS 的动画
  • 基于 Web Animations API 的动画

动画基础

SVG 的动画特效基于万维网联盟的“同步多媒体集成语言”(SMIL)规范

在这个动画系统中,可以指定想要进行动画的属性(如颜色、动作)的起始值和结束值,以及动画的开始时间和持续时间

  • attributeName:动画中应该持续改变的值
  • attributeType:默认值为 auto,可选值为 CSSXML。使用默认值时会先查找 CSS 属性,然后再找 XML 属性
  • from:起始值,可选。如果不指定,则使用父元素的值
  • to:结束值
  • by:一个从 from 值开始的偏移量,可以代替 to
  • values:指定中间值。定义动画在持续时间内使用的值的列表,用分号分隔
  • begin:动画开始时间。取值为
    • 一个数值,如 1s
    • 另一个动画的结束或开始时间,如 begin=c1.endbegin=c1.begin+1sc1.repeat(2)+2.5s 绑定为另一个动画的重复次数
  • end:动画结束时间。取值同 begin。结束时间和持续时间两者不能替代,动画何时结束取决于两者谁先到达。利用这个特性可以中途拦截动画
  • dur:动画持续时间
  • fill:动画结束时做什么。取值为
    • remove:默认值,返回到初始状态
    • freeze:保持结束状态
    • repeatCount:一个整型值,指定动画重复次数,可指定为 indefiniterepeatCountrepeatDur 通常只使用其中一个,若两者都指定,则取决于哪个属性先到达。
    • repeatDur:一个值,指定重复持续时间,可指定为 indefinite

基本动画示例

<!-- @format -->

<svg width="100%">
    <rect x="10" y="10" width="200" height="20" stroke="black" fill="none">
        <animate
            attributeName="width"
            attributeType="XML"
            from="200"
            to="20"
            begin="0s"
            dur="5s"
            fill="freeze"
        ></animate>
    </rect>
</svg>

单个对象上的多重动画

<!-- @format -->

<svg width="100%" height="200">
    <rect
        x="10"
        y="10"
        width="20"
        height="20"
        style="stroke: black; fill: green; fill-opacity: 0.25"
    >
        <animate
            attributeName="width"
            attributeType="XML"
            from="20"
            to="200"
            begin="0s"
            dur="5s"
            fill="freeze"
        ></animate>
        <animate
            attributeName="height"
            attributeType="XML"
            from="20"
            to="150"
            begin="0s"
            dur="5s"
            fill="freeze"
        ></animate>
        <animate
            attributename="fill-opacity"
            attributeType="CSS"
            from="0.25"
            to="1"
            begin="0s"
            dur="3s"
            fill="freeze"
        ></animate>
        <animate
            attributeName="fill-opacity"
            attributeType="CSS"
            from="1"
            to="0.25"
            begin="3s"
            dur="3s"
            fill="freeze"
        ></animate>
    </rect>
</svg>

多个对象的简单动画

<!-- @format -->

<svg width="100%">
    <rect
        x="10"
        y="10"
        width="20"
        height="20"
        style="stroke: black; fill: #cfc"
    >
        <animate
            attributeName="width"
            attributeType="XML"
            begin="0s"
            dur="5s"
            from="20"
            to="120"
            fill="freeze"
        ></animate>
        <animate
            attributeName="height"
            attributeType="XML"
            begin="0s"
            dur="5s"
            from="20"
            to="120"
            fill="freeze"
        ></animate>
    </rect>

    <circle cx="70" cy="70" r="20" style="fill: #ccf; stroke: black">
        <animate
            attributeName="r"
            attributeType="XML"
            begin="2s"
            dur="5s"
            from="20"
            to="50"
            fill="freeze"
        ></animate>
    </circle>
</svg>

动画时间详解

SVG 动画用到的动画时钟在 SVG 加载完成时开始启动计时,当用户离开页面时停止计时。可以用以下任意一种方式指定特定动画的开始和持续时间为一个数值

  • 时、分、秒形式的完整时间值。如 1:20:23
  • 分、秒形式的局部时间值。如 02:15
  • hminsms 缩写结尾的时间值。如 3.5s、1min

如果不指定单位,默认为秒

同步动画

可以绑定动画的开始时间为另一个动画的开始或者结束,而不是在文档加载时定义每个动画的开始时间

<!-- @format -->

<svg width="100%">
    <circle cx="60" cy="60" r="30" style="fill: #f9f; stroke: gray">
        <animate
            id="c1"
            attributeName="r"
            attributeType="XML"
            begin="0s"
            dur="4s"
            from="30"
            to="10"
            fill="freeze"
        ></animate>
    </circle>

    <circle cx="120" cy="60" r="10" style="fill: #9f9; stroke: gray">
        <animate
            attributeName="r"
            attributeType="XML"
            begin="c1.end"
            dur="4s"
            from="10"
            to="30"
            fill="freeze"
        ></animate>
    </circle>
</svg>

带有偏移量的同步动画

<!-- @format -->

<svg width="100%">
    <circle cx="60" cy="60" r="30" style="fill: #f9f; stroke: gray">
        <animate
            id="c1"
            attributeName="r"
            attributeType="XML"
            begin="0s"
            dur="4s"
            from="30"
            to="10"
            fill="freeze"
        />
    </circle>
    <circle cx="120" cy="60" r="10" style="fill: #9f9; stroke: gray">
        <animate
            attributeName="r"
            attributeType="XML"
            begin="c1.begin+1.25s"
            dur="4s"
            from="10"
            to="30"
            fill="freeze"
        />
    </circle>
</svg>

重复动作

重复动画

<!-- @format -->

<svg width="100%">
    <circle cx="60" cy="60" r="30" style="fill: none; stroke: red">
        <animate
            attributeName="cx"
            attributeType="XML"
            begin="0s"
            dur="5s"
            repeatCount="2"
            from="60"
            to="260"
            fill="freeze"
        />
    </circle>
    <circle cx="260" cy="90" r="30" style="fill: #ccf; stroke: black">
        <animate
            attributeName="cx"
            attributeType="XML"
            begin="0s"
            dur="5s"
            repeatDur="8s"
            from="260"
            to="60"
            fill="freeze"
        />
    </circle>
</svg>

带重复的同步动画

<!-- @format -->

<svg width="100%">
    <circle cx="60" cy="60" r="15" style="fill: none; stroke: red">
        <animate
            id="circleAnim"
            attributeName="cx"
            attributeType="XML"
            begin="0s"
            dur="5s"
            repeatCount="3"
            from="60"
            to="260"
            fill="freeze"
        />
    </circle>
    <rect
        x="230"
        y="80"
        width="30"
        height="30"
        style="fill: #ccf; stroke: black"
    >
        <animate
            attributeName="x"
            attributeType="XML"
            begin="circleAnim.repeat(1)+2.5s"
            dur="5s"
            from="230"
            to="30"
            fill="freeze"
        />
    </rect>
</svg>

对复杂的属性应用动画

几乎可以为任何属性和样式应用动画

颜色动画

只需给 fromto 属性应用有效的颜色即可

<!-- @format -->

<svg>
    <circle
        cx="60"
        cy="60"
        r="30"
        style="fill: #ff9; stroke: gray; stroke-width: 10"
    >
        <animate
            attributeName="fill"
            begin="2s"
            dur="4s"
            from="#ff9"
            to="red"
            fill="freeze"
        />
        <animate
            attributeName="stroke"
            begin="2s"
            dur="4s"
            from="gray"
            to="blue"
            fill="freeze"
        />
    </circle>
</svg>

路径和多边形动画

可以为值为数值列表的属性应用动画,只要列表中数字的数量没有改变即可。列表中的每个值都是单独变换的,这表明可以为路径数据或者多边形的点应用动画,只要维持点的数量和路径片段的类型即可

<!-- @format -->

<svg>
    <polygon points="30 30 70 30 90 70 10 70" style="fill: #fcc; stroke: black">
        <animate
            id="animation"
            attributeName="points"
            attributeType="XML"
            to="50 30 70 50 50 90 30 50"
            begin="0s"
            dur="5s"
            fill="freeze"
        />
    </polygon>
    <path
        d="M15 50 Q 40 15, 50 50, 65 32, 100 40"
        style="fill: none; stroke: black"
        transform="translate(0,50)"
    >
        <animate
            attributeName="d"
            attributeType="XML"
            to="M50 15 Q 15 40, 50 50, 32 65, 40 100"
            begin="0s"
            dur="5s"
            fill="freeze"
        />
    </path>
</svg>

指定多个值

通过 values 指定动画的特定的中间值列表,动画在持续时间内将按照该值列表进行变化

values 属性也可以用来实现交替来回的重复动画,即从起始值到结束值再回到起始值,使用 values="start; end; start;" 形式即可

<!-- @format -->

<svg>
    <circle cx="50" cy="50" r="30" style="fill: #ff9; stroke: black">
        <animate
            attributeName="fill"
            begin="2s"
            dur="4s"
            values="#ff9;#99f;#f99;#9f9"
            fill="freeze"
        />
    </circle>
</svg>

多级动画时间

当一个动画有多个值时,动画的持续时间就是要一次通过所有值的时间。默认情况下,动画的持续时间被划分为每个过渡周期等长的片段

keyTimes 属性允许以其他方式划分持续时间,格式为一个以分号分隔的列表,必须和 values 值的条目相同。第一个值始终为 0,最后一个始终为 1,中间用小数表示,代表动画的持续时间比例

对时间的更多控制可以通过 caleMode 属性完成,取值如下

  • pacedSVG 阅读器会计算后续各个值之间的间隔,并以此为依据划分持续时间,因此其变化的速度是恒定的(keyTimes 属性会被忽略)。适用于颜色、简单的数值、长度,但不能用于点列表或者路径数据
  • linear<animate> 元素的默认行为,每个过渡内的速度是恒定的,但是分配给每个过渡的时长是相等的(如果没有指定 keyTimes)或者由 keyTimes 决定
  • discrete:动画会从一个值跳到另一个值,但没有过渡。如果动画绘制了一个不支持过渡的属性(如 font-family),则会自动使用该模式
  • spline:动画会按照 keySplines 属性加速或减速,其值为贝塞尔曲线控制点列表,也是用分号分隔,列表条目必须与 values 的条目相同。(更多信息见 SVG 规范或 MDN 网站)

<set> 元素

对于非数字属性或者不能过渡的属性,有时候想要在动画序列的某个点上改变某个值

如,一个初始不可见的文本,使得它在某个时间变得可见,这里并不需要 fromto,此时可以使用 <set> 元素,它只需要一个 to 属性以及适当的时间信息

<!-- @format -->

<svg>
    <circle cx="60" cy="60" r="30" style="fill: #ff9; stroke: gray">
        <animate
            id="c1"
            attributeName="r"
            attributeType="XML"
            begin="0s"
            dur="4s"
            from="30"
            to="0"
            fill="freeze"
        />
    </circle>
    <text text-anchor="middle" x="60" y="60" style="visibility: hidden">
        <set
            attributeName="visibility"
            attributeType="CSS"
            to="visible"
            begin="4.5s"
            dur="1s"
            fill="freeze"
        />
        All gone!
    </text>
</svg>

<animateTransform> 元素

<animate> 元素并不适用于旋转、平移、缩放、倾斜变换,因为它们都在 transform 属性内,此时需要使用 <animateTransform> 元素,属性如下(其他属性与 <animate> 一致)

  • attributeType:目前仅支持 XML
  • attributeName:指定为 transform
  • type:指定要变换的属性之一:translatescalerotateskewXskewY
  • additive:默认值为 replace,如果打算为动画应用多个变换则应设置为 sum
<!-- @format -->

<svg width="100%">
    <g transform="translate(100, 60)">
        <rect
            x="-10"
            y="-10"
            width="20"
            height="20"
            style="fill: #ff9; stroke: black"
        >
            <animateTransform
                attributeType="XML"
                attributeName="transform"
                type="scale"
                from="1"
                to="4 2"
                begin="0s"
                dur="4s"
                fill="freeze"
            />
        </rect>
    </g>

    <g transform="translate(300, 60)">
        <rect
            x="-10"
            y="-10"
            width="20"
            height="20"
            style="fill: #ff9; stroke: black"
        >
            <animateTransform
                attributeName="transform"
                attributeType="XML"
                type="scale"
                from="1"
                to="4 2"
                additive="sum"
                begin="0s"
                dur="4s"
                fill="freeze"
            />
            <animateTransform
                attributeName="transform"
                attributeType="XML"
                type="rotate"
                from="0"
                to="45"
                additive="sum"
                begin="0s"
                dur="4s"
                fill="freeze"
            />
        </rect>
    </g>
</svg>

也可以用 additive="sum" 合并控制数值和颜色属性的动画元素的效果。如果动画使用 to 指定,则把它们加载一起会导致后续动画使用上一个动画的当前值作为它们的起点,如果动画使用 by 属性定义效果,或者同时使用 fromto,则最终值将会是所有单个变化的综合

<animateMotion> 元素

<animateMotion> 元素主要用于让对象沿着任意路径运动,无论是直线还是一系列的重叠循环路径

直线运动

简单设置 fromto 属性,然后给每个属性分配一对 (x, y) 坐标即可,类似于 translate(x, y) 的工作原理

<!-- @format -->

<svg>
    <g>
        <rect x="0" y="0" width="30" height="30" style="fill: #ccc" />
        <circle cx="30" cy="30" r="15" style="fill: #cfc; stroke: green" />
        <animateMotion from="0,0" to="60,30" dur="4s" fill="freeze" />
    </g>
</svg>

复杂的路径运动

需要用 path 属性,它的值与 <path> 元素的 d 属性的值的格式相同

<!-- @format -->

<svg height="180">
    <!-- 要移动的三角形路径 -->
    <path
        d="M50,125 C 100,25 150,225, 200, 125"
        style="fill: none; stroke: blue"
    />
    <!-- 三角形会沿着运动路径移动。路径原点与三角形底边中点垂直对齐 -->
    <path d="M-10,-3 L10,-3 L0,-25z" style="fill: yellow; stroke: red">
        <animateMotion
            path="M50,125 C 100,25 150,225, 200, 125"
            dur="6s"
            fill="freeze"
        />
    </path>
</svg>

带自动旋转的复杂路径动画

如果希望对象倾斜以使其 x 轴始终平行于路径的方向,只需要添加 rotate="auto" 属性即可

rotate 默认值为 0,即表示不旋转

<!-- @format -->

<svg height="180">
    <!-- 要移动的三角形路径 -->
    <path
        d="M50,125 C 100,25 150,225, 200, 125"
        style="fill: none; stroke: blue"
    />
    <!-- 三角形会沿着运动路径移动。路径原点与三角形底边中点垂直对齐 -->
    <path d="M-10,-3 L10,-3 L0,-25z" style="fill: yellow; stroke: red">
        <animateMotion
            path="M50,125 C 100,25 150,225, 200, 125"
            dur="6s"
            fill="freeze"
            rotate="auto"
        />
    </path>
</svg>

路径可以通过 <mpath> 元素引用,而无需重复设置 path 属性

<!-- @format -->

<svg height="180">
    <!-- 要移动的三角形路径 -->
    <path
        id="cubicCurve"
        d="M50,125 C 100,25 150,225, 200, 125"
        style="fill: none; stroke: blue"
    />
    <!-- 三角形会沿着运动路径移动。路径原点与三角形底边中点垂直对齐 -->
    <path d="M-10,-3 L10,-3 L0,-25z" style="fill: yellow; stroke: red">
        <animateMotion dur="6s" fill="freeze" rotate="auto">
            <mpath xlink:href="#cubicCurve"></mpath>
        </animateMotion>
    </path>
</svg>

为运动指定关键点和时间

匀速动画是 <animateMotion> 的默认行为,此时 calcMode="paced",后续移动之间需要花费的时间与它们之间的距离成正比(有一个例外,如果路径含有任意的 moveto 命令,都会被算作 0 距离,此时对象将会从一个子路径的结束位置立即跳转到下一个子路径的开始位置)

keyTimes 也可以用来设置动画运动的速度,当使用路径而不是 values 列表定义运动时,需要针对路径使用 keyPoints 属性指定关键点

keyPoints 是一个分号分隔的十进制数值列表。每个点表示对象应该按照 keyTimes 列表中响应的时间点沿着路径移动多远,范围由 0-1

keyPoints 无需按照 0-1 的顺序指定,它可以在路径的中间开始或结束,还可以向两个方向运动,但是列表的条目必须和 keyTimes 拥有相同的数量,并且还必须设置 calcMode="linear" 或者 calcMode="spline"

<!-- @format -->

<svg height="180">
    <path d="M-10,-3 L10,-3 L0,-25z" style="fill: yellow; stroke: red">
        <animateMotion
            path="M50,125 C 100,25 150,225, 200, 125"
            rotate="auto"
            keyPoints="0;0.2;0.8;1"
            keyTimes="0;0.33;0.66;1"
            calcMode="linear"
            dur="6s"
            fill="freeze"
        />
    </path>
</svg>

使用 CSS 处理 SVG 动画

与常规的 CSS 动画一样,此处不记录,只需注意一点:此时 SVG 的样式都建议通过 CSS 设置,特别是 transform,否则可能不生效

Last Updated:
Contributors: af