Frontend Development 8 min read

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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Animating Chinese Character Transformations with D3.js: From SVG Paths to FIFO Queue Animation

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.

animationJavaScriptSVGPath Morphingd3.jsFIFO
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.