简单的svg动画

857 阅读2分钟

一、效果展示

动画1

<circle cx="22" cy="10" r="4" fill="black">

<animate attributeName="opacity" from="1" to="0" dur="1s" begin="0.33s" repeatCount="indefinite" />

</circle>

这里设置了 opacity 透明度从 1 到 0,时间是1秒。设置三个圆点的开始时间 begin 依次顺延,重复次数 repeatCount 为无限次,实现三个点的加载动画。

动画2

<rect x="0" y="50" width="100" height="100" fill="rgb(227,102,90)">

<animateTransform

attributeName="transform"

type="rotate"

from="0 50 100"

to="360 50 100"

dur="3s"

repeatCount="indefinite" />

</rect>

为方块 rect 添加 animateTransform 动画,这里设置的是 rotate 角度变换,同样是循环无限次数,需要注意的是 rotate 的值需要设置方块的旋转点,from 后两个参数设定以方块的中心做为旋转点。

动画3

<rect x="0" y="50" width="100" height="100" fill="rgb(227,102,90)">

    <animateTransform

        attributeName="transform"

        type="rotate"

        from="0 50 100"

        to="360 50 100"

        dur="3s"

        begin="0s;first.begin+9s"

        id="first"

    />

</rect>

设置三个方块依次旋转,除了动画次数设置为indefinite 外,还可以不断重置动画的开始时间;给动画标签定义一个 id 比如:first,后面添加 begin="0s;first.begin+9s",这样开始时间不断的被重置,再搭配给不同方块的时间差,实现三个方块依次转动。

动画4

<defs>

<path id="motionPath1" d="M50,50 L150,50 L150,150 L50,150 L50,50" />

<path id="motionPath2" d="M150,150 L50,150 L50,50 L150,50 L150,150" />

</defs>

声明两个方块路径,这里就是简单的起点为对角线,path 一致的两个路径,实现路径动画。

<circle cx="0" cy="0" r="12" fill="rgb(94,191,90)">

    <animateMotion

    dur="4s"

    repeatCount="indefinite"

    rotate="auto">

    <mpath xlink:href="#motionPath1" />

    </animateMotion>

</circle>

通过 mpath xlink:href="#motionPath1" 为圆点规划路径。这里需要注意的是,设置路径需要在最外层 svg 标签里添加 xmlns:xlink 这个命名空间标识

<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"></svg>
动画5

动画6

动画7

动画8

<defs>

<path id="motionPath1" d="M50,100 m -50,0 a 50 50 0 1 1 100 0"/>

<path id="motionPath2" d="M150,100 m -50,0 a 50 50 0 1 0 100 0"/>

<path id="motionPath3" d="M150,100 m 50,0 a 50 50 0 1 0 -100 0"/>

<path id="motionPath4" d="M50,100 m 50,0 a 50 50 0 1 1 -100 0"/>

</defs>

这里定义四个半圆的路径,这里需要注意下绘制的方向:顺时针、逆时针,配合时间差完成“绕8”的动画行为。

动画9

分解图片平滑过渡

class SmallRect {
    allCopies = [];
    pathId = "";
    duration = 5;

    constructor(pathId, duration = 5) {
        this.pathId = pathId;
        this.duration = duration;
    }

    parseImage(filePath) {
        const svgNS = "http://www.w3.org/2000/svg";
        const svg = document.getElementById("svg");
        const img = new Image();
        img.src = filePath;
        img.onload = () => {
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext("2d");
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, 0, 0);
        // 获取图片像素数据
        const imageData = ctx.getImageData(0, 0, img.width, img.height);
        const data = imageData.data;
        // 遍历像素点并生成矩形
        const step = 1; // 每隔 1 个像素取一个点
        // 横向分段
        const copies = img.width / step;
        for (let i = 0; i < copies; i++) {
            const g = document.createElementNS(svgNS, "g");
            g.setAttribute("id", this.pathId + "G" + i);
            this.allCopies.push(new SmallRectPosition(g));
            svg.appendChild(g);
        }

        // 操作像素
        for (let y = 0; y < img.height; y += step) {
            for (let x = 0; x < img.width; x += step) {
                const index = (y * img.width + x) * 4;
                const r = data[index];
                const g = data[index + 1];
                const b = data[index + 2];
                const a = data[index + 3] / 255;

                // 创建矩形
                const rect = document.createElementNS(svgNS, "rect");
                rect.setAttribute("x", 0);
                rect.setAttribute("y", -(img.height / 2.0) + y);
                rect.setAttribute("width", step);
                rect.setAttribute("height", step);
                rect.setAttribute("fill", `rgba(${r},${g},${b},${a})`);
                const copiesIndex = x % img.width;
                this.allCopies[copiesIndex].g.appendChild(rect);
            }
        }

        // 计算路径长度
        const path = document.getElementById(this.pathId);
        const pathLength = path.getTotalLength();
        // 计算延迟时间
        const delay = this.duration / pathLength;
        // 添加动画
        this.allCopies.forEach((position, index) => {
        const lastCopiesG = this.allCopies[this.allCopies.length - 1];
        // 创建 animateMotion 元素
        const animateMotion = document.createElementNS(
        svgNS,
        "animateMotion"
        );
        animateMotion.setAttribute("dur", this.duration);
        animateMotion.setAttribute("begin", delay * index + "s");
        animateMotion.setAttribute("repeatCount", "indefinite");
        animateMotion.setAttribute("rotate", "auto");
        animateMotion.setAttributeNS(
        "http://www.w3.org/1999/xlink",
        "xlink:href",
        "#" + this.pathId + "G" + index
        );

        // 创建 mpath 元素并引用路径
        const mpath = document.createElementNS(svgNS, "mpath");
        mpath.setAttributeNS(
        "http://www.w3.org/1999/xlink",
        "xlink:href",
        "#" + this.pathId
        );
        animateMotion.appendChild(mpath);
        svg.appendChild(animateMotion);
        });
        };
        }
        });
    }
}

二、思考与总结

动画都比较简单,但它或许能为开发场景打开一个新的思路。