SVG from Zero to Senior · Part 5 — Transforms, Groups & Nested Coordinate Systems
Move and reuse SVG: the transform attribute (translate/scale/rotate/skew), why transform order matters, grouping with <g>, transform-origin, and the power of nested coordinate systems with inner <svg>. With a live order-flipping sandbox.
So far everything has lived in one flat coordinate system, drawn exactly where you typed it. {Tới giờ mọi thứ sống trong một hệ toạ độ phẳng, vẽ đúng chỗ con gõ.} Now we learn to move the grid itself. {Giờ ta học cách di chuyển chính cái lưới.} This is the chapter where SVG stops being static drawings and starts becoming reusable components. {Đây là chương mà SVG thôi là bản vẽ tĩnh và bắt đầu thành component tái dùng.}
Drag the sliders, then hit the order button to flip translate → rotate into rotate → translate and watch the square land somewhere completely different. {Kéo slider, rồi bấm nút đổi thứ tự để lật translate → rotate thành rotate → translate và xem hình vuông đáp xuống chỗ hoàn toàn khác.}
Open the full demo {Mở demo đầy đủ}: /tools/svg-transforms-demo/.
The mental shift: you transform the grid, not the shape {Cú lật tư duy: con biến đổi cái lưới, không phải hình}
This is the key insight that makes everything click. {Đây là cái nhìn then chốt làm mọi thứ thông suốt.} A transform doesn’t move an object through space — it re-defines the coordinate system the object is drawn in. {Một transform không di chuyển vật thể trong không gian — nó định nghĩa lại hệ toạ độ mà vật thể được vẽ trong đó.} translate(40, 0) means “shift this element’s personal graph paper 40 units right, then draw it normally.” {translate(40, 0) nghĩa là “dịch giấy kẻ ô riêng của element này 40 đơn vị sang phải, rồi vẽ như bình thường.”} Once you think in moving grids, transform order stops being mysterious. {Khi con nghĩ theo lưới di chuyển, thứ tự transform thôi bí ẩn.}
The transform functions {Các hàm transform}
<g transform="translate(40 20) rotate(30) scale(1.5) skewX(10)"> … </g>
translate(tx ty)— shift the origin. {dịch gốc toạ độ.}scale(s)orscale(sx sy)— stretch the grid (also scales stroke width and position!). {kéo lưới (scale luôn cả stroke width và vị trí!).}rotate(deg)orrotate(deg cx cy)— rotate around the origin, or around an explicit point(cx,cy). {xoay quanh gốc, hoặc quanh một điểm(cx,cy).}skewX(deg)/skewY(deg)— slant the grid. {nghiêng lưới.}
You can also write a raw matrix(a b c d e f) — every transform compiles down to one — but you almost never hand-write it. {Con cũng viết được matrix(a b c d e f) thô — mọi transform đều biên dịch về một cái — nhưng gần như không bao giờ viết tay.}
Why order matters (the #1 transform bug) {Vì sao thứ tự quan trọng (lỗi transform số 1)}
Transforms in the attribute apply left to right as nested grids, which means the last one is closest to the shape. {Transform trong thuộc tính áp dụng trái sang phải như các lưới lồng nhau, nghĩa là cái cuối gần hình nhất.} The practical effect: {Hệ quả thực tế:}
<!-- translate first: move right, THEN spin in place -->
<g transform="translate(40 0) rotate(45)"> … </g>
<!-- rotate first: spin around the origin, which FLINGS it on an arc -->
<g transform="rotate(45) translate(40 0)"> … </g>
In the second case, rotating first turns the whole coordinate system, so the later translate(40 0) moves along the rotated X-axis — the shape swings out on an arc. {Ở trường hợp hai, xoay trước làm xoay cả hệ toạ độ, nên translate(40 0) sau đó di chuyển dọc trục X đã xoay — hình văng ra theo cung tròn.} This is the classic “why is my icon flying off?” bug. {Đây là lỗi kinh điển “sao icon của tôi bay đi đâu mất?”.} Flip the order button in the demo until it’s reflex. {Bấm nút đổi thứ tự trong demo cho tới khi thành phản xạ.}
transform-origin — pick your pivot {transform-origin — chọn điểm xoay}
By default rotation and scale pivot around the SVG origin (0,0) — the top-left — which is rarely what you want. {Mặc định xoay và scale lấy gốc SVG (0,0) — góc trên-trái — làm tâm, hiếm khi là cái con muốn.} Two fixes: {Hai cách sửa:}
<!-- 1. the function's built-in centre argument -->
<rect transform="rotate(45 100 100)" … />
<!-- 2. CSS transform-origin (works on SVG elements too) -->
<rect style="transform: rotate(45deg); transform-origin: 100px 100px" … />
Senior tip: prefer the CSS
transformproperty for animated transforms (it composites on the GPU and respectstransform-origincleanly), and thetransformattribute for static structural layout. {Mẹo senior: ưu tiên propertytransformcủa CSS cho transform có animation (nó composite trên GPU và tôn trọngtransform-origingọn gàng), và thuộc tínhtransformcho bố cục tĩnh.}
Groups — the <g> element {Nhóm — element <g>}
<g> bundles elements so one transform (or one fill, opacity, class…) applies to all of them. {<g> gom các element để một transform (hay một fill, opacity, class…) áp cho tất cả.}
<g transform="translate(20 60)" fill="#60a5fa">
<circle cx="32" cy="32" r="18" />
<text x="58" y="30">Card</text>
</g>
Move the group → everything moves together. {Di nhóm → mọi thứ di cùng.} This is how you build a chart axis, a labelled icon, or a draggable component: structure it as a group and transform the group as a unit. {Đây là cách con dựng trục biểu đồ, icon có nhãn, hay component kéo được: cấu trúc nó thành nhóm và transform cả nhóm như một khối.} Groups also nest, multiplying their transforms down the tree. {Nhóm cũng lồng nhau, nhân các transform xuống cây.}
Nested <svg> — a fresh coordinate system {<svg> lồng — một hệ toạ độ mới tinh}
The senior move: an <svg> can contain another <svg>. {Nước cờ senior: một <svg> có thể chứa một <svg> khác.} The inner one establishes a brand-new viewport and coordinate system, with its own x, y, width, height, and even its own viewBox. {Cái bên trong thiết lập một viewport và hệ toạ độ hoàn toàn mới, với x, y, width, height riêng, thậm chí viewBox riêng.}
<svg viewBox="0 0 200 200">
<svg x="100" y="20" width="80" height="80" viewBox="0 0 10 10">
<!-- here coordinates run 0–10, mapped into that 80×80 box -->
<circle cx="5" cy="5" r="4" fill="#c8ff00" />
</svg>
</svg>
This lets you drop in a self-contained widget that doesn’t care about the outer coordinates — perfect for embedding a reusable icon at any size and position without recomputing every number. {Cái này cho con thả vào một widget độc lập không quan tâm toạ độ bên ngoài — hoàn hảo để nhúng một icon tái dùng ở mọi kích thước và vị trí mà không phải tính lại từng con số.} It’s also how <symbol> + <use> sprites work under the hood, which we cover in Part 10. {Đó cũng là cách sprite <symbol> + <use> hoạt động bên dưới, ta nói ở Phần 10.}
The master’s warnings {Lời cảnh báo của sư phụ}
- Order is right-to-left for the shape.
translate then rotate≠rotate then translate. {Thứ tự là phải-sang-trái với hình.translate rồi rotate≠rotate rồi translate.} scalescales strokes too. A 2px stroke underscale(3)renders 6px. Usevector-effect="non-scaling-stroke"to keep it crisp. {scalescale cả stroke. Stroke 2px dướiscale(3)thành 6px. Dùngvector-effect="non-scaling-stroke"để giữ nét.}- Default pivot is (0,0). Rotations swing wildly until you set a centre. {Tâm mặc định là (0,0). Phép xoay văng tứ tung cho tới khi con đặt tâm.}
Practice, or it didn’t happen {Luyện tập, không thì coi như chưa học}
- Clock hand {Kim đồng hồ}: a thin rect rotated with
rotate(deg 100 100)around the clock centre. Change the angle and watch it sweep. {một rect mảnh xoay bằngrotate(deg 100 100)quanh tâm đồng hồ.} - The order experiment {Thí nghiệm thứ tự}: build the flinging-arc bug on purpose with
rotate() translate(), then fix it by swapping. {cố tình tạo lỗi văng-cung vớirotate() translate(), rồi sửa bằng cách đổi chỗ.} - Grouped badge {Huy hiệu nhóm}: a
<g>containing a circle + text; move the whole badge with onetranslate. {một<g>chứa circle + text; di cả huy hiệu bằng mộttranslate.}
What’s next {Phần tiếp theo}
You can now move, rotate, scale, group, and nest — the structural toolkit of a senior. {Giờ con di, xoay, scale, gom nhóm, và lồng được — bộ đồ nghề cấu trúc của senior.} Time to make it move over time. {Đến lúc làm nó chuyển động theo thời gian.} In Part 6 we animate: the famous line-drawing effect with stroke-dasharray/stroke-dashoffset, CSS keyframes vs native SMIL <animate>/<animateTransform>, and moving an element along a path with <animateMotion>. {Ở Phần 6 ta animate: hiệu ứng vẽ-đường trứ danh với stroke-dasharray/stroke-dashoffset, CSS keyframes vs SMIL <animate>/<animateTransform> thuần, và di chuyển element dọc path với <animateMotion>.}