变“鼠”为“鸭”——为SVG Path制作FIFO路径变换动画,效果丝滑

简介: 曾撰文《使用batik在kotlin中将TTF字体转换为SVG图像》介绍了如何将汉字转为SVG Path路径进行展示和变换,以此为基础用动画将一个汉字变为另一个汉字,感官上很好玩

theme: cyanosis

一个月前我曾撰文《使用batik在kotlin中将TTF字体转换为SVG图像》,介绍了如何将汉字转为SVG Path路径进行展示和变换,以此为基础不妨畅想一下,用动画将一个汉字变为另一个汉字,听上去是不是很简单呢?下面动手实践一下:

我随便找了一个字体Aa剑豪体,然后随机选取了两个汉字:,再用上文提到的文章介绍的提取整体字形区块方法取出了SVG:
image.png

image.png

可以看到很简单就提取出了两个字整体的字形,下面用D3做一个简单的变换动画展示:

初始变换

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <title>鼠鼠我鸭</title>
    </head>
    <body style="text-align: center"></body>
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <script type="module">
        const _svg = d3
                    .select("body")
                    .append("svg")
                    .attr("width", "1000")
                    .attr("height", "1000")
                    .style("zoom", "0.3")
                    .style("transform", "rotateX(180deg)");
        _svg.append("g")
                    .attr("transform", "translate(0, 160)")
                    .append("path")
                    .attr("fill", "#3fd")
                    .attr("stroke", "black")
                    .attr("stroke-width", "4")
                    .attr("d", 上面提到的鼠的SVG_Path...)
                    .transition().delay(1000).duration(1200)
                    .attr("fill", "#ef2")
                    .attr("d", 上面提到的鸭的SVG_Path...);
    </script>
</html>

这里调整了svg尺寸以及zoomtransform等属性更好的适应画面,还做了个g标签套住并将定位交给它,动画效果如下图所示:

d8cd6bc2bcf747a3bfe9a79c44b6678d~tplv-k3u1fbpfcp-watermark.gif

很明显的看到,效果非常奇怪,怎么一开始就突然变得很乱?一开始乱这一下显得很突兀,这是因为两段图像的path长度就相差很多,绘进方式也完全不一样,很难真正的渐变过去,我试了一个有优化此过程的库d3-interpolate-path,用上去效果也没有什么差别,而且它用的还是d3@v5版本的,不知道怎么path中还会穿插一些NaN的值,很怪异,看来只能自己做了。

想真正的点对点的渐移过去,可能还是有些难的,所以我想出了一个较为简单的方案,实现一种队列式的效果,的笔画慢慢消失,而则跟随在后面逐步画出,实现一种像队列中常说的FIFO(先进先出)的效果

首先就是拆解,做一个while拆分两个字所有的节点,然后再一步步绘上拆出来的节点以验证拆的是否完整,再才能进行后面的处理。

事先要将“鸭鼠”各自的path定义为常量sourceresult,将二者开头的M与结尾的Z都去掉(中间的M不要去掉),因为动画中字形是流动的,起止点不应提前定义。

拆分路径点

const source = 鼠的SVG_Path(没有MZ)...
const result = 鸭的SVG_Path(没有MZ)...
const actionReg = new RegExp(/[a-z]/, "gi");
const data = new Array();
let match;
let lastIndex;
while ((match = actionReg.exec(result))) {
   
        data.push(result.substring(lastIndex, match.index));
        lastIndex = match.index;
}
data.push(result.substring(lastIndex));

就这样就能把的部分拆开了,先直接累加到试验一下是否成功:

叠加试验

let tran = g
        .append("path")
        .attr("fill", "red")
        .attr("stroke", "black")
        .attr("stroke-width", "4")
        .attr("d", "M" + source + "Z")
        .transition()
        .delay(800);

let step = "L";
data.map(item => {
   
        step += item + " ";
        tran = tran.transition().attr("d", "M" + source + step.trimEnd() + "Z").duration(20);
});

首先是把上面path独立出来改一改,变成红色的利于观看,然后下面慢慢的拼合上每个节点,效果如下:

524cdb41fb0f43b9b5bfa4bcc1573ca7~tplv-k3u1fbpfcp-watermark.gif

是理想中的效果,那么下一步就是加速FIFO先进先出的变换了:

FIFO先进先出

这一步是不能用SVG动画的,要用setInterval定时器进行动画调节,SVG始终还是只能处理很简单的path变化,效果不如直接变来的好,这里设计成把每一帧的动画存进一个方法数组然后交给setInterval计时器循环执行(写起来比较方便),先是改一下tran的定义,因为不是动画了,所以现在改叫path就好了,border也不需要了:

let path = g
        .append("path")
        .attr("fill", "red")
        .attr("d", "M" + source + "Z");

就这样简单的初始化一下就好了,然后就是最核心的一个过程,path的绘制循序就像一个FIFO队列:

let step = "";
let pre = source;
const funs = new Array();
data.map(async function (item, i) {
   
        step += item + " ";
        match = pre && actionReg.exec(source);
        if (!match) {
   
                pre = "";
        } else if (~["M", "L", "T"].indexOf(match[0])) {
   
                pre = source.substring(match.index + 1);
        }
        const d = "M" + pre + (pre ? "L" : "") + step.trimEnd() + "Z";
        funs.push(() => path.attr("d", d));
});

首先是pre负责的字形,这个字形是要慢慢消失的前部,这个前部不是所有的节点都能用的,而是"M", "L", "T"这种明确有点位的动作才行,毕竟这是动画的起始点。然后step就是代表,要一步一步累加。循环结束funs数组也就累计好了所有的帧(方法),然后用定时器执行这些带参方法即可:

const animation = setInterval(() => {
   
        if (!funs.length) {
   
                clearInterval(animation);
                return;
        }
        funs.shift()();
}, 20);

这种方式虽然非常少见,不过这个定时器流程还是很好理解的过程,效果如下:

e01f73b2d06345c7a289e9503b94a010~tplv-k3u1fbpfcp-watermark.gif

是想象中的效果,但稍微有些单调,可以加上一段摇摆的动画配合变换:

摇摆动画

let pathTran = path;
Array(8)
    .fill(0)
    .map(function () {
   
        pathTran = pathTran
            .transition()
            .attr("transform", "skewX(10)")
            .duration(300)
            .transition()
            .attr("transform", "skewX(-10)")
            .duration(300);
    });
pathTran.transition().attr("transform", "").duration(600);

这段动画要不断赋值才能形成连贯动画,所以直接用path处理动画是不行的,因为上面计时器也是用到这个path对象,所以要额外定义一个pathTran专门用于动画,这段摇摆动画效果如下:

shu.gif

时间掐的刚刚好,那边计时器停掉,这边摇摆动画也缓停了。

写的十分简便,一点小创意,供大家参考观赏。

本文写作于2023年6月7日并发布于lyrieek的掘金,于2023年7月18日进行修订发布于lyrieek的阿里云开发者社区。

目录
相关文章
|
移动开发 小程序 前端开发
Taro 的实现原理是怎么样的?
Taro 的实现原理是怎么样的?
613 0
|
10月前
|
机器学习/深度学习 人工智能 C++
《CMake:掌控 C++人工智能项目编译的魔法棒》
在 C++ 人工智能项目的开发中,CMake 作为一款强大的构建工具,能够高效管理项目的编译流程。本文深入探讨了如何利用 CMake 处理复杂的项目结构、管理库文件链接、定制编译选项、支持跨平台编译以及生成和管理构建系统,帮助开发者高效构建、扩展和维护 C++ 人工智能项目。
164 17
|
11月前
|
Unix 编译器 iOS开发
Zig 环境安装
Zig 环境安装
353 2
Zig 环境安装
|
12月前
|
机器学习/深度学习 数据采集 算法
【大语言模型】-最新研究进展-2024-10-11
【大语言模型】-最新研究进展-2024-10-11,最新的5篇论文速读。
545 0
|
12月前
ThreeJs手动控制动画播放与暂停
这篇文章介绍了如何在Three.js中手动控制动画的播放与暂停,包括设置动画混合器、监听按键事件以调整动画状态和速度的方法。
386 0
ThreeJs手动控制动画播放与暂停
|
12月前
ThreeJs制作管道中水流效果
这篇文章详细介绍了如何在Three.js中创建具有动态水流效果的管道模型,通过纹理贴图的移动来模拟水流的视觉效果。
790 2
ThreeJs制作管道中水流效果
|
12月前
ThreeJs制作管道水流效果
这篇文章详细说明了如何在Three.js中创建具有流动水效果的管道模型,通过使用纹理贴图的动态偏移来模拟水流的视觉效果。
649 3
|
存储 程序员
操作系统(1)----操作系统的运行机制
操作系统(1)----操作系统的运行机制
302 0
|
API Android开发
55. 【Android教程】位图:Bitmap
55. 【Android教程】位图:Bitmap
246 0
|
机器学习/深度学习 编解码 算法
Yolov5改进算法之添加Res2Net模块
Res2Net(Residual Resolution Network)是一种用于图像处理和计算机视觉任务的深度卷积神经网络架构。它旨在解决传统的ResNet(Residual Network)存在的问题,如对不同尺度和分辨率特征的建模不足以及网络深度受限的问题。Res2Net通过引入多分支的结构和逐级增加的分辨率来提高网络的表达能力,从而在各种视觉任务中取得了显著的性能提升。
753 0