Animating Chinese Character Transformations with D3.js: From SVG Paths to FIFO Queue Animation
This article demonstrates how to convert Chinese characters into SVG paths using Batik, then creates a D3.js animation that morphs one character into another, addressing path length mismatches with a FIFO‑style point‑by‑point transition and adding a swing effect via setInterval.
One month ago I wrote an article about using Batik in Kotlin to convert TTF fonts to SVG images, extracting the SVG Path of Chinese characters. Building on that, I explore animating a transformation from one character to another using D3.js.
I chose the font "Aa剑豪体" and randomly selected the characters 鼠 (mouse) and 鸭 (duck). Using the method described earlier, I extracted their complete SVG shapes.
With the two SVG paths ready, I created a simple D3.js animation that swaps the path data, adjusting the SVG size, zoom , and transform properties for better visual fit. The initial HTML skeleton looks like this:
<!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">
<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)");
// ... (animation code)
</script>
</body>
</html>The animation initially looks chaotic because the two paths have very different lengths and command sequences, causing abrupt transitions. Using d3-interpolate-path with D3 v5 did not solve the issue, and the interpolation sometimes produced NaN values.
To achieve a smoother point‑by‑point morph, I adopted a FIFO (first‑in‑first‑out) approach: the strokes of 鼠 gradually disappear while the strokes of 鸭 are drawn sequentially.
The first step is to split the target path into individual command segments. The following JavaScript extracts the segments by removing the leading M and trailing Z and using a regular expression to capture each command:
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));After splitting, I verified the segments by incrementally appending them to a red path element, which makes the intermediate steps easy to observe.
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);
});Having confirmed the incremental drawing, I moved to the FIFO animation. Because SVG transitions alone cannot handle the complex sequencing, I stored each frame as a function in an array and executed them with setInterval :
let path = g.append("path")
.attr("fill", "red")
.attr("d", "M" + source + "Z");
const funs = [];
let pre = source;
let step = "";
data.map((item, i) => {
step += item + " ";
const match = pre && actionReg.exec(source);
if (!match) {
pre = "";
} else if (["M", "L", "T"].indexOf(match[0]) !== -1) {
pre = source.substring(match.index + 1);
}
const d = "M" + pre + (pre ? "L" : "") + step.trimEnd() + "Z";
funs.push(() => path.attr("d", d));
});
const animation = setInterval(() => {
if (!funs.length) { clearInterval(animation); return; }
funs.shift()();
}, 20);To make the animation more lively, I added a swing effect by repeatedly applying a skew transform to the path:
let pathTran = path;
Array(8).fill(0).forEach(() => {
pathTran = pathTran.transition()
.attr("transform", "skewX(10)")
.duration(300)
.transition()
.attr("transform", "skewX(-10)")
.duration(300);
});
pathTran.transition().attr("transform", "").duration(600);The final result is a concise yet expressive animation that morphs one Chinese character into another using a FIFO‑style point transition, complemented by a subtle swinging motion. The approach showcases how D3.js can be combined with plain JavaScript timers to achieve custom SVG animations beyond built‑in transition capabilities.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.