jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

SVG from Zero to Senior · Part 6 — Animation: Line-Drawing, CSS & SMIL

Make SVG move: the famous self-drawing line with stroke-dasharray/stroke-dashoffset, CSS keyframes vs native SMIL <animate>/<animateTransform>, motion along a path with animateMotion, and when to pick which. With a scrubbable studio.

7 MIN READ

This is the part everyone is secretly waiting for. {Đây là phần ai cũng âm thầm chờ.} Animated SVG is what makes a logo draw itself, an icon morph, a loader spin, a chart grow. {SVG có animation là thứ làm logo tự vẽ, icon biến hình, loader quay, biểu đồ mọc lên.} The good news: SVG can be animated three different ways, and a senior knows which to reach for. {Tin tốt: SVG animate được theo ba cách, và senior biết cái nào để chọn.}

Scrub the checkmark, hit play, pause the orbiting dot — then read how each technique works. {Kéo dấu tích, bấm play, tạm dừng chấm bay quanh — rồi mới đọc cách từng kỹ thuật hoạt động.}

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

The three roads to motion {Ba con đường tới chuyển động}

  1. CSS animations / transitions / WAAPI — the modern default. Animate transform, opacity, stroke-dashoffset, and most presentation properties. {mặc định hiện đại. Animate transform, opacity, stroke-dashoffset, và đa số property trình bày.}
  2. SMIL (<animate>, <animateTransform>, <animateMotion>) — animation declared inside the SVG markup. Self-contained, no CSS or JS. {animation khai báo bên trong markup SVG. Độc lập, không CSS hay JS.}
  3. JavaScript (rAF / libraries) — for interaction-driven or physics motion. {cho chuyển động theo tương tác hoặc vật lý.}

The senior rule of thumb: CSS for app UI, SMIL for self-contained icons, JS when state or input drives the motion. {Quy tắc senior: CSS cho UI app, SMIL cho icon độc lập, JS khi trạng thái hoặc input điều khiển chuyển động.}

The line-drawing trick (the crown jewel) {Mánh vẽ-đường (viên ngọc vương miện)}

This single effect impresses everyone, and it’s pure cleverness with two properties you met in Part 3. {Hiệu ứng này làm ai cũng trầm trồ, và nó thuần là sự thông minh với hai property con đã gặp ở Phần 3.}

The idea: make the dash pattern one giant dash exactly as long as the whole path, then push it out of view with stroke-dashoffset, then animate the offset back to 0. {Ý tưởng: làm pattern nét đứt thành một nét khổng lồ dài đúng bằng cả path, rồi đẩy nó ra khỏi tầm nhìn bằng stroke-dashoffset, rồi animate offset về 0.}

.path {
  stroke-dasharray: 100;     /* one dash = full normalized length */
  stroke-dashoffset: 100;    /* shifted away → invisible */
  animation: draw 2s ease forwards;
}
@keyframes draw {
  to { stroke-dashoffset: 0; }  /* shift back → fully drawn */
}

The magic enabler is pathLength="100" on the element: it normalizes the path’s true length to 100, so you can use round percentages instead of measuring real geometry. {Bí quyết kích hoạt là pathLength="100" trên element: nó chuẩn hoá chiều dài thật của path về 100, nên con dùng phần trăm tròn thay vì đo hình học thật.}

<path d="M 30 75 L 80 115 L 170 30" pathLength="100"
      stroke-dasharray="100" stroke-dashoffset="100" />

Without pathLength, you’d read path.getTotalLength() in JS and plug that number in. {Không có pathLength, con phải đọc path.getTotalLength() bằng JS và nhét con số đó vào.} Scrub the slider in the demo and you’re literally driving stroke-dashoffset by hand. {Kéo slider trong demo và con đang lái stroke-dashoffset bằng tay theo nghĩa đen.}

SMIL — animation baked into the markup {SMIL — animation nướng sẵn trong markup}

SMIL elements sit inside the shape they animate. {Element SMIL nằm trong hình mà nó animate.}

<rect x="80" y="50" width="40" height="40">
  <!-- tween any attribute -->
  <animate attributeName="fill"
           values="#60a5fa;#c8ff00;#ef4444;#60a5fa"
           dur="4s" repeatCount="indefinite" />
  <!-- tween a transform -->
  <animateTransform attributeName="transform" type="rotate"
                    from="0 100 70" to="360 100 70"
                    dur="4s" repeatCount="indefinite" />
</rect>
  • <animate> tweens a plain attribute (fill, r, opacity, cx…). {tween một thuộc tính thường.}
  • <animateTransform> tweens transform with a type (rotate/scale/translate). {tween transform với một type.}
  • values lists keyframes; dur, repeatCount, begin control timing. {values liệt kê keyframe; dur, repeatCount, begin điều khiển thời gian.}

SMIL is wonderfully portable — the animation travels with the file, so an .svg icon animates even as an <img src>. {SMIL chuyển giao tuyệt vời — animation đi theo file, nên một icon .svg vẫn animate cả khi là <img src>.} It went through a deprecation scare years ago but is supported across modern browsers again. {Nó từng bị doạ khai tử nhiều năm trước nhưng giờ lại được hỗ trợ trên các trình duyệt hiện đại.} For app UI prefer CSS; for a portable animated icon, SMIL is ideal. {Cho UI app ưu tiên CSS; cho icon động chuyển giao được, SMIL lý tưởng.}

Motion along a path {Chuyển động dọc path}

<animateMotion> moves an element along a path — orbits, conveyors, a rocket on a trajectory. {<animateMotion> di element dọc một path — quỹ đạo, băng chuyền, tên lửa theo đường bay.}

<path id="track" d="M 20 110 Q 100 0 180 110" fill="none" />
<circle r="7" fill="#c8ff00">
  <animateMotion dur="3s" repeatCount="indefinite" rotate="auto">
    <mpath href="#track" />
  </animateMotion>
</circle>

rotate="auto" turns the element to face its travel direction — essential for arrows, optional for dots. {rotate="auto" xoay element hướng theo chiều di chuyển — cần cho mũi tên, tuỳ chọn cho chấm.} The CSS equivalent is offset-path/offset-distance, which animates beautifully on the compositor. {Tương đương CSS là offset-path/offset-distance, animate rất mượt trên compositor.}

Controlling SMIL from JS {Điều khiển SMIL từ JS}

A handy senior trick: the SVG root has pauseAnimations() and unpauseAnimations(), so you can pause all SMIL timelines at once — exactly what the demo’s pause button does. {Mánh senior tiện: SVG root có pauseAnimations()unpauseAnimations(), nên con tạm dừng tất cả timeline SMIL một lúc — đúng cái nút pause trong demo làm.}

Performance & accessibility {Hiệu năng & khả năng truy cập}

  • Animate transform and opacity — they run on the GPU compositor and don’t trigger layout. {Animate transformopacity — chạy trên GPU compositor và không gây layout.}
  • stroke-dashoffset is cheap and smooth, which is why line-drawing feels buttery. {stroke-dashoffset rẻ và mượt, nên vẽ-đường cảm giác mượt như bơ.}
  • Always respect prefers-reduced-motion. Wrap big motion in a media query and offer a calm fallback. {Luôn tôn trọng prefers-reduced-motion. Bọc chuyển động lớn trong media query và cho fallback nhẹ nhàng.}
@media (prefers-reduced-motion: reduce) {
  .path { animation: none; }
}

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

  • No pathLength, no clean percentages. Either set pathLength="100" or measure with getTotalLength(). {Không pathLength, không phần trăm gọn. Hoặc đặt pathLength="100" hoặc đo bằng getTotalLength().}
  • SMIL from/to for rotate needs the centre. from="0 100 70" — the 100 70 is the pivot, like the transform attribute. {from/to của SMIL cho rotate cần tâm. 100 70 là điểm xoay, giống thuộc tính transform.}
  • Don’t animate layout-triggering attributes in hot loops. Prefer transform over animating x/width for big scenes. {Đừng animate các thuộc tính gây layout trong vòng lặp nóng. Ưu tiên transform hơn animate x/width cho cảnh lớn.}

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

  1. Self-drawing signature {Chữ ký tự vẽ}: take any single-stroke path, set pathLength="100", and draw it on page load. {lấy một path một nét, đặt pathLength="100", và vẽ nó khi tải trang.}
  2. SMIL loader {Loader SMIL}: a circle whose r and opacity pulse forever — no CSS, no JS. {một circle có ropacity đập mãi — không CSS, không JS.}
  3. Orbit {Quỹ đạo}: send a dot around a circular path with <animateMotion>, then add rotate="auto" to a triangle and watch it bank into turns. {cho một chấm bay quanh path tròn với <animateMotion>, rồi thêm rotate="auto" cho tam giác và xem nó nghiêng vào khúc cua.}

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

Your SVGs can now move, draw themselves, and orbit. {SVG của con giờ chuyển động, tự vẽ, và bay quanh được.} Next we add depth and atmosphere. {Tiếp theo ta thêm chiều sâu và không khí.} In Part 7 we open the SVG filter engine — a tiny image-processing pipeline built from primitives like feGaussianBlur, feColorMatrix, feOffset, and feMerge — to make real drop shadows, glows, and frosted-glass effects entirely in the browser. {Ở Phần 7 ta mở cỗ máy filter của SVG — một pipeline xử lý ảnh tí hon dựng từ các primitive như feGaussianBlur, feColorMatrix, feOffset, và feMerge — để tạo đổ bóng, phát sáng, và hiệu ứng kính mờ thật hoàn toàn trong trình duyệt.}