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.
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}
- CSS animations / transitions / WAAPI — the modern default. Animate
transform,opacity,stroke-dashoffset, and most presentation properties. {mặc định hiện đại. Animatetransform,opacity,stroke-dashoffset, và đa số property trình bày.} - 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.} - 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>tweenstransformwith atype(rotate/scale/translate). {tweentransformvới mộttype.}valueslists keyframes;dur,repeatCount,begincontrol timing. {valuesliệ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() và 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
transformandopacity— they run on the GPU compositor and don’t trigger layout. {Animatetransformvàopacity— chạy trên GPU compositor và không gây layout.} stroke-dashoffsetis cheap and smooth, which is why line-drawing feels buttery. {stroke-dashoffsetrẻ 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ọngprefers-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 setpathLength="100"or measure withgetTotalLength(). {KhôngpathLength, không phần trăm gọn. Hoặc đặtpathLength="100"hoặc đo bằnggetTotalLength().} - SMIL
from/tofor rotate needs the centre.from="0 100 70"— the100 70is the pivot, like the transform attribute. {from/tocủa SMIL cho rotate cần tâm.100 70là điểm xoay, giống thuộc tính transform.} - Don’t animate layout-triggering attributes in hot loops. Prefer
transformover animatingx/widthfor big scenes. {Đừng animate các thuộc tính gây layout trong vòng lặp nóng. Ưu tiêntransformhơn animatex/widthcho cảnh lớn.}
Practice, or it didn’t happen {Luyện tập, không thì coi như chưa học}
- 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, đặtpathLength="100", và vẽ nó khi tải trang.} - SMIL loader {Loader SMIL}: a circle whose
randopacitypulse forever — no CSS, no JS. {một circle córvàopacityđập mãi — không CSS, không JS.} - Orbit {Quỹ đạo}: send a dot around a circular path with
<animateMotion>, then addrotate="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êmrotate="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.}