jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

SVG from Zero to Senior · Part 11 — Path Morphing & Shape Interpolation

Animate the d attribute itself: the golden rule of morphing (same command structure), interpolating coordinates, SMIL path animation, why circles fight stars, and how flubber resamples paths. With a scrubbable morph lab.

Welcome back, padawan — you graduated, but the master has bonus scrolls. {Mừng con trở lại, đồ đệ — con đã tốt nghiệp, nhưng sư phụ còn vài cuộn bí kíp.} In the core ten you animated transforms and strokes. {Trong mười bài cốt lõi con đã animate transformstroke.} Now we animate the most fundamental thing of all: the shape’s own outline, the d attribute. {Giờ ta animate thứ căn bản nhất: chính đường viền của hình, thuộc tính d.} This is path morphing — a hamburger menu melting into an X, a play button flowing into pause, a checkmark blooming from nothing. {Đây là path morphing — nút hamburger tan thành chữ X, nút play chảy thành pause, dấu tích nở ra từ hư không.}

Scrub the slider to morph by hand, pick a target, then hit animate. {Kéo slider để morph bằng tay, chọn đích, rồi bấm animate.}

Open the full demo {Mở demo đầy đủ}: /tools/svg-morph-demo/.

The golden rule of morphing {Quy tắc vàng của morphing}

Morphing is just interpolation between two d strings. {Morphing chỉ là nội suy giữa hai chuỗi d.} But it only works cleanly under one condition: {Nhưng nó chỉ chạy mượt với một điều kiện:}

Both paths must have the same command structure — the same number of commands, of the same type, in the same order. {Hai path phải có cùng cấu trúc lệnh — cùng số lệnh, cùng loại, cùng thứ tự.}

If shape A is M L L (a 3-point polyline) and shape B is also M L L, then point 1 maps to point 1, point 2 to point 2, and so on. {Nếu hình A là M L L (polyline 3 điểm) và hình B cũng M L L, thì điểm 1 ánh xạ điểm 1, điểm 2 điểm 2, cứ thế.} You blend each coordinate independently and rebuild the string. {Con trộn từng toạ độ độc lập rồi dựng lại chuỗi.}

Linear interpolation (lerp) {Nội suy tuyến tính (lerp)}

The whole engine is one tiny function: {Cả cỗ máy là một hàm tí hon:}

// blend two arrays of numbers by factor t (0 → 1)
function lerpPath(a, b, t) {
  return a.map((n, i) => n + (b[i] - n) * t);
}

Represent each shape as a flat array of its coordinates, lerp the arrays, then write the numbers back into a d template: {Biểu diễn mỗi hình thành mảng phẳng các toạ độ, lerp các mảng, rồi viết số trở lại mẫu d:}

const check = [55,105, 90,140, 150,60];   // M L L
const cross = [60,60,  100,100, 140,140];

const mid = lerpPath(check, cross, 0.5);
const d = `M ${mid[0]} ${mid[1]} L ${mid[2]} ${mid[3]} L ${mid[4]} ${mid[5]}`;

t = 0 is the start shape, t = 1 is the target, and everything between is a real in-between shape. {t = 0 là hình đầu, t = 1 là đích, và mọi giá trị giữa là một hình trung gian thật.} Drive t with requestAnimationFrame and an easing function and you have a buttery morph — exactly what the demo’s animate button does. {Lái t bằng requestAnimationFrame và một hàm easing là con có morph mượt như bơ — đúng cái nút animate trong demo làm.}

The native option: SMIL on d {Lựa chọn thuần: SMIL trên d}

You can morph without any JS using SMIL, if the structures match: {Con morph được không cần JS bằng SMIL, nếu cấu trúc khớp:}

<path d="M55 105 L90 140 L150 60" fill="none" stroke="#c8ff00" stroke-width="6">
  <animate attributeName="d" dur="0.6s" fill="freeze"
    values="M55 105 L90 140 L150 60;
            M60 60 L100 100 L140 140" />
</path>

Clean and dependency-free for self-contained icon transitions. {Gọn và không phụ thuộc cho chuyển icon độc lập.} CSS can also animate d now (@keyframes { to { d: path('…') } }) where supported — same structural rule applies. {CSS giờ cũng animate d được (d: path('…')) ở nơi hỗ trợ — cùng quy tắc cấu trúc.}

When structures don’t match {Khi cấu trúc không khớp}

Real shapes rarely share structure — a circle is arcs, a star is lines, a logo is dozens of Béziers. {Hình thật hiếm khi cùng cấu trúc — vòng tròn là arc, sao là line, logo là hàng tá Bézier.} Naive interpolation then produces garbage or simply refuses. {Nội suy ngây thơ khi đó cho ra rác hoặc đơn giản là từ chối.} The fix is resampling: convert both paths to the same number of points (often all cubic Béziers), pair them up sensibly, then interpolate. {Cách sửa là resampling: chuyển cả hai path về cùng số điểm (thường toàn Bézier bậc ba), ghép chúng hợp lý, rồi mới nội suy.}

This is non-trivial, which is why libraries exist: {Việc này không tầm thường, nên mới có thư viện:}

  • flubber — best-in-class shape interpolation; handles different point counts and even splitting one shape into many. {nội suy hình tốt nhất; xử lý số điểm khác nhau và cả tách một hình thành nhiều.}
  • GSAP MorphSVGPlugin — production morphing with automatic point matching. {morphing production với tự khớp điểm.}
  • anime.js — simple morphs when structures already match. {morph đơn giản khi cấu trúc đã khớp.}

Senior call: hand-roll the lerp for icon toggles where you control both shapes (hamburger ↔ X, play ↔ pause); reach for flubber/GSAP when morphing arbitrary art. {Quyết định senior: tự code lerp cho toggle icon khi con kiểm soát cả hai hình; dùng flubber/GSAP khi morph art bất kỳ.}

The master’s warnings {Lời cảnh báo của sư phụ}

  • Same structure or it breaks. Match command types and counts, or resample first. {Cùng cấu trúc hoặc hỏng. Khớp loại và số lệnh, hoặc resample trước.}
  • Point order matters. Even matching counts morph weirdly if the points are listed in a different order — the shape “turns inside out.” {Thứ tự điểm quan trọng. Dù khớp số lượng vẫn morph kỳ quặc nếu điểm liệt kê khác thứ tự — hình “lộn từ trong ra”.}
  • Keep point counts modest for hand-rolled morphs; thousands of interpolated numbers per frame is a job for a library or canvas. {Giữ số điểm vừa phải cho morph tự code; hàng nghìn số nội suy mỗi frame là việc của thư viện hoặc canvas.}

Practice, or it didn’t happen {Luyện tập, không thì coi như chưa học}

  1. Hamburger → X {Hamburger → X}: three lines that morph into a cross on toggle (same M L structure each). {ba đường morph thành dấu X khi toggle.}
  2. Play → pause {Play → pause}: interpolate a triangle into two bars — design both with equal point counts. {nội suy tam giác thành hai thanh — thiết kế cả hai cùng số điểm.}
  3. Try to break it {Thử phá nó}: morph a 3-point path into a 5-point one with naive lerp and watch it fail, then fix by padding to equal counts. {morph path 3 điểm sang 5 điểm bằng lerp ngây thơ và xem nó hỏng, rồi sửa bằng cách đệm cho bằng số.}

What’s next {Phần tiếp theo}

You can now reshape outlines over time. {Giờ con biến hình đường viền theo thời gian.} Next, a wildly practical component built on a property you already met: in Part 12 we build radial progress rings and gauges with stroke-dasharray/stroke-dashoffset math — the loaders, percentage rings, and speedometers you see in every dashboard. {Tiếp theo, một component cực thực dụng dựng trên property con đã gặp: ở Phần 12 ta dựng vòng tiến trình và đồng hồ đo với toán stroke-dasharray/stroke-dashoffset — loader, vòng phần trăm, và đồng hồ tốc độ con thấy trong mọi dashboard.}