马黑黑 发表于 2024-9-19 18:35

让svg内部元素运行CSS动画

本帖最后由 马黑黑 于 2024-9-19 18:38 编辑 <br /><br /><style>
#artbox { margin: auto; font-size: 18px; }
#artbox > p { margin: 12px 0; }
#artbox mark { margin: 0 4px; padding: 0 4px; background: lightblue; }
.txtRed { color: red; }
svg { border: 1px solid; }
</style>

<div id="artbox">
        <p>svg有自己一整套动画系统,诸如 set、animate、animateMotion 和 animateTransform 等,都可以在svg中以标签的形式为svg子元素制作动画。遗憾的是svg动画实现过程有点麻烦,不仅动画属性繁多,而且每一个动画标签只能负责一个基于子元素的属性变更(如改变圆心坐标)动画,做一个可能不算复杂的动画也需要多个动画标签来配套完成。CSS动画则简便得多,一切基于元素不同属性的动画都可以放在 @keyframes 选择器中进行表述,然后元素通过 animate 属性引用 @keyframes 选择器的动画名称即可,animation 还可以引用多个 @keyframes 动画并以不同的方式和运行周期运动动画。</p>
        <p>先看纯svg的动画例子。下面的示例,矩形元素rect自上而下垂直落下的同时旋转360度:</p>

<svg width="400" height="200" viewBox="0 0 400 200">
        <rect x="180" y="0" width="40" height="40" fill="olive">
                <animateTransform attributeName="transform" type="rotate" from="0 200 20" to="360 200 220" dur="5s" begin="0s" additive="sum"                 repeatCount="indefinite"/>
                <animateTransform attributeName="transform" type="translate" from="0 0" to="0 200" dur="5s" begin="0s" additive="sum" repeatCount="indefinite"/>
        </rect>
</svg>
        <p>代码:</p>
        <div class="hE"><pre></pre></div>
        <p>查看代码,rect标签内两次使用了 <span class="txtRed">animateTransform</span> 动画标签,一次设置rect的旋转 <span class="txtRed">rotate</span>,另一次设置rect的位移 <span class="txtRed">translate</span>。animateTransform动画多个属性联合使用时需要配置 <span class="txtRed">additive</span> 属性,值为 <span class="txtRed">sum</span> 表示动画叠加 <span class="txtRed">repalce</span> 表示动画替换——即后一个动画替换前一个动画。需要特别注意,旋转动画的 <span class="txtRed">from</span> 和 <span class="txtRed">to</span> 值要设置从{200 20}到{200 220}的rect自己的中心点坐标,不设置或设置不当rect的运动将不是垂直下落;当然,可以给rect加入style属性设置 transform-box 和 transform-origin,像这样,<mark>style="transform-box: fill-box; transform-origin: center"</mark>,如此from和to属性赋值使用单值即旋转角度就可以了。</p>

<p>前述CSS动画的灵活性其实也可以用于svg,换言之,如果需要,我们完全可以避开svg原生动画标签,转而通过CSS来定制svg内部元素基于@keyframes+animation的动画。现在,我们使用CSS动画实现上例的效果:</p>

<svg width="400" height="200" viewBox="0 0 400 200">
        <style>
                .down { fill: olive; stroke: none; transform-box: fill-box; transform-origin: center; animation: down 5s linear infinite; }
                @keyframes down { to { transform: translateY(200px) rotate(360deg); } }
        </style>
        <rect x="180" y="0" width="40" height="40" class="down"></rect>
</svg>
        <p>代码:</p>
        <div class="hE"><pre></pre></div>

        <p>查看代码会发现,CSS写在<mark>&lt;svg&gt; ... &lt;/svg&gt;</mark>标签内,这和做HTML帖子将CSS放在<mark>&lt;body&gt; ... &lt;/body&gt;</mark>标签之内是同一个道理。CSS代码中,<span class="txtRed">.down</span> 选择器 ① 强制转换原点基于自己的中心。这是非常有必要的,因为即使是使用CSS @keyframes动画,svg内的元素依然不会自己改变坐标系;② 设定填充样式 <span class="txtRed">fill</span>、描边样式 <span class="txtRed">stroke</span> 和动画 <span class="txtRed">animation</span> 属性。动画选择器 <span class="txtRed">@keyframes</span> 则设置了一个动画:自上而下移动+旋转360度,二者放在同一个 transform 属性里进行表述,非常简洁。</p>
        <p>有意思的是,svg内CSS设定的@keyframes动画,transform的translate平移数值支持百分比,且百分比基于父元素,这和HTML的CSS相同属性动画值不同,后者的百分比基于子元素即引用动画的元素。原因很简单:针对 transform 形变,HTML元素以自我中心点为运动原点,所以translate平移百分比数值基于自身;svg里的元素则以svg画布左上角为形变原点,所以百分比平移数值基于父元素即svg画布。此外,非常重要的,svg标签加上版本号和命名空间属性存为.svg文档之后是合法的,可以当图片使用,像如下代码:</p>

<div class="hE"><pre>
&lt;svg width="400" height="200" viewBox="0 0 400 200" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"&gt;
        &lt;style&gt;
                .down { fill: olive; stroke: none; transform-box: fill-box; transform-origin: center; animation: down 5s linear infinite; }
                @keyframes down { to { transform: translateY(200px) rotate(360deg); } }
        &lt;/style&gt;
        &lt;rect x="180" y="0" width="40" height="40" class="down"&gt;&lt;/rect&gt;
&lt;/svg&gt;
</pre></div>

        <p>svg使用CSS动画除了本文介绍的方法,还可以使用XML的语言规范为svg设置CSS动画,甚至可以和HTML一样使用外部CSS文档。</p>
</div>

<script>
var sc = document.createElement('script');
sc.chartset = 'utf-8';
sc.src = 'https://638183.freep.cn/638183/web/js2024/helight.js';
document.body.appendChild(sc);

var svgs = artbox.querySelectorAll('svg'),
        pres = artbox.querySelectorAll('pre');

svgs.forEach((pre,key) => {
        var str = svgs.outerHTML.replaceAll('<',"&lt;");
        str = str.replaceAll('>', '&gt;');
        pres.innerHTML = str;
});
</script>

起个网名好难 发表于 2024-9-19 20:07

评分把帖子破坏了{:5_102:}

<style>…………</style> 放在svg外也一样。

红影 发表于 2024-9-19 22:13

使用svg自己的动画系统不如使用CSS的简便且熟悉呢{:4_173:}

红影 发表于 2024-9-19 22:17

svg的自己的动画太麻烦了,比如那个transform是从0到200,结果rotate的纵向居中就要跟着是20到220,这样一个不小心就会弄错啊。
还是css好,一个center就不用管了{:4_173:}

花飞飞 发表于 2024-9-19 22:23

能动起来的都厉害,何况还有多种实现方法。。{:4_199:}

马黑黑 发表于 2024-9-19 22:50

花飞飞 发表于 2024-9-19 22:23
能动起来的都厉害,何况还有多种实现方法。。

svg的动画略难一些

马黑黑 发表于 2024-9-19 22:51

红影 发表于 2024-9-19 22:17
svg的自己的动画太麻烦了,比如那个transform是从0到200,结果rotate的纵向居中就要跟着是20到220,这样一 ...

是的。我个人觉得 animateTransform是多余的,应该让 animate 属性里的 transform 能发挥动画功能。

马黑黑 发表于 2024-9-19 22:52

红影 发表于 2024-9-19 22:13
使用svg自己的动画系统不如使用CSS的简便且熟悉呢

不过也是因为先入为主吧,接触CSS动画要早一些

马黑黑 发表于 2024-9-19 22:53

起个网名好难 发表于 2024-9-19 20:07
评分把帖子破坏了

………… 放在svg外也一样。

你存为 .svg 文档时会报错,除非严格按 XML 语法写style 标签

红影 发表于 2024-9-19 23:40

马黑黑 发表于 2024-9-19 22:51
是的。我个人觉得 animateTransform是多余的,应该让 animate 属性里的 transform 能发挥动画功能。

反正我是觉得简单的更容易让人接受。

红影 发表于 2024-9-19 23:40

马黑黑 发表于 2024-9-19 22:52
不过也是因为先入为主吧,接触CSS动画要早一些

是的,所以更习惯些。

马黑黑 发表于 2024-9-20 07:44

红影 发表于 2024-9-19 23:40
是的,所以更习惯些。

CSS动画也是要简洁得多

马黑黑 发表于 2024-9-20 07:44

红影 发表于 2024-9-19 23:40
反正我是觉得简单的更容易让人接受。

这是自然。不过复杂的机制能做出更为精巧的东东,比如光刻机的制作。

红影 发表于 2024-9-20 14:08

马黑黑 发表于 2024-9-20 07:44
CSS动画也是要简洁得多

两者能通用,非常好呢。

红影 发表于 2024-9-20 14:08

马黑黑 发表于 2024-9-20 07:44
这是自然。不过复杂的机制能做出更为精巧的东东,比如光刻机的制作。

也需要学得更多些才行。

花飞飞 发表于 2024-9-20 21:30

马黑黑 发表于 2024-9-19 22:50
svg的动画略难一些

面熟和面生的区别,其实应该都不容易{:4_173:}

梦江南 发表于 2024-9-21 08:37

老师发的都是最尖端的代码编程,佩服了!

马黑黑 发表于 2024-9-21 11:39

梦江南 发表于 2024-9-21 08:37
老师发的都是最尖端的代码编程,佩服了!

果酱了

马黑黑 发表于 2024-9-21 11:47

花飞飞 发表于 2024-9-20 21:30
面熟和面生的区别,其实应该都不容易

CSS容易得多

花飞飞 发表于 2024-9-21 20:26

马黑黑 发表于 2024-9-21 11:47
CSS容易得多

难怪看着比较亲切{:4_170:}纯svg一个单词快一米长,太吓人了。
页: [1] 2
查看完整版本: 让svg内部元素运行CSS动画