变“鼠”为“鸭”——为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尺寸以及zoom、transform等属性更好的适应画面,还做了个g标签套住并将定位交给它,动画效果如下图所示:

d8cd6bc2bcf747a3bfe9a79c44b6678d~tplv-k3u1fbpfcp-watermark.gif

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

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

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

事先要将“鸭鼠”各自的path定义为常量source、result,将二者开头的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的阿里云开发者社区。

目录
相关文章
|
缓存 监控 NoSQL
【Redis性能瓶颈揭秘】「调优系列」深入分析热Key的排查策略和解决方案
【Redis性能瓶颈揭秘】「调优系列」深入分析热Key的排查策略和解决方案
216318 12
|
JavaScript 程序员 CDN
推荐 7 个 Vue2、Vue3 源码解密分析的重磅开源项目 👍
推荐 7 个 Vue2、Vue3 源码解密分析的重磅开源项目 👍
1035 0
推荐 7 个 Vue2、Vue3 源码解密分析的重磅开源项目 👍
|
人工智能 数据可视化 测试技术
Apifox与Apipost对比,2025年功能对比与选项建议
Apifox 和 Apipost 作为国内 API 一体化协作平台的佼佼者,都在不断进化,力求为用户提供更全面的解决方案。本文将聚焦“2025 版”,基于两款工具截至 2024 年末至 2025 年中旬的预期功能和行业发展趋势,进行一次全方位、深度的功能对比,旨在为开发者、测试工程师、产品经理及技术决策者在选型时提供有价值的参考。
3579 123
|
Linux 编译器 数据处理
探索Linux中的`ld`命令:链接器的奥秘
`ld`是Linux中的链接器,将编译后的目标文件链接成可执行或共享库。作为GNU Binutils的一部分,它处理符号引用、重定位,支持多种文件格式和库类型。常用参数包括`-o`指定输出文件,`-l`链接库,`-L`指定搜索路径。编译器驱动程序常用于简化链接过程。理解符号解析、选择静态/动态链接及管理库版本是使用`ld`的关键。使用`--verbose`调试链接问题,并考虑优化选项。
|
负载均衡 Java Nacos
Nacos有几种负载均衡策略?
Nacos有几种负载均衡策略?
1818 0
Nacos有几种负载均衡策略?
|
小程序 JavaScript
Taro@3.x+Vue@3.x+TS开发微信小程序,使用轮播图
本文介绍了使用 Taro 和 Vue 创建轮播组件的两种方法:一是通过 `&lt;swiper&gt;` 实现,二是利用 Nut UI 的 `&lt;nut-swiper&gt;` 组件实现。
844 2
Taro@3.x+Vue@3.x+TS开发微信小程序,使用轮播图
|
存储 程序员
操作系统(1)----操作系统的运行机制
操作系统(1)----操作系统的运行机制
734 0
|
JavaScript 前端开发
pdf.js插件使用记录,在线打开pdf
原文:pdf.js插件使用记录,在线打开pdf 天记录一个js库:pdf.js。主要是实现在线打开pdf功能。因为项目需求需要能在线查看pdf文档,所以就研究了一下这个控件。 有些人很好奇,在线打开pdf文档浏览器不是支持吗。
3510 1
|
传感器 监控 搜索推荐
智能纺织品:健康监测与生活方式的结合
【10月更文挑战第22天】智能纺织品融合了传感器、导电纤维和微电子元件等先进技术,不仅改变了穿着体验,还为健康监测和生活方式的改善带来了新机遇。它们能实时监测心率、血压等生理数据,通过无线通信技术传输至手机或云端,实现远程监控与数据分析。未来,智能纺织品将更加智能化、个性化和环保,成为日常生活中不可或缺的一部分。
|
XML 前端开发 JavaScript
深入探究iframe:网页嵌入的魔法盒子(下)
深入探究iframe:网页嵌入的魔法盒子(下)

热门文章

最新文章