SVG from Zero to Senior · Part 3 — Painting: Fill, Stroke, Gradients & Patterns
Make SVG beautiful: fill and the two fill rules, the whole stroke family (width, linecap, linejoin, dasharray), then reusable linear/radial gradients and tiling patterns defined once in defs. With a live paint playground.
Giờ con tả được mọi hình (Phần 1) và mọi đường viền (Phần 2). Nhưng hình không sơn thì vô hình — đến lúc mở hộp màu. Trong SVG, việc sơn chia làm hai: fill (bên trong) và stroke (đường viền), và mỗi cái phong phú hơn background/border của CSS nhiều.
Chỉnh playground — đổi nguồn fill, kéo slider stroke — rồi mới đọc cách từng phần hoạt động.
Mở demo đầy đủ:
Thuộc tính trình bày vs CSS
Đầu tiên, một sự thật tầm senior giúp tiết kiệm hàng giờ bối rối. Sơn có thể đặt theo hai cách:
<!-- presentation attribute -->
<circle fill="red" stroke="black" />
<!-- ...or CSS, which wins over the attribute -->
<circle style="fill: red; stroke: black" />
Thuộc tính trình bày hoạt động như CSS rất yếu — bất kỳ luật CSS thật nào (kể cả class) cũng ghi đè chúng. Đó là lý do con có thể đổi màu icon bằng .icon { fill: currentColor } dù file SVG đã nhúng sẵn fill="#000". Ta khai thác mạnh điều này ở Phần 10.
fill và hai fill rule
fill nhận mọi màu CSS, none, currentColor, hoặc tham chiếu url(#id). Phần tinh tế là bên trong được quyết định thế nào cho các hình tự cắt, qua fill-rule:
- đếm hướng cuốn; gần như luôn là cái con muốn.
- tô dựa trên số cạnh một tia cắt qua; cho ra sao kiểu “lỗ bánh donut” và phần chồng.
<path d="…star…" fill-rule="evenodd" />
Phần lớn thời gian con bỏ qua nó — cho tới khi một ngôi sao tô trông đặc dù con muốn có lỗ. Giờ con biết cái công tắc.
Họ stroke
stroke là nơi SVG vượt border của CSS, vì đường viền đi theo mọi path, không chỉ cái hộp.
<path d="…"
stroke="#60a5fa"
stroke-width="6"
stroke-linecap="round" /* butt | round | square — the END of an open line */
stroke-linejoin="round" /* miter | round | bevel — where segments meet */
stroke-dasharray="12 6" /* 12 on, 6 off, repeating */
stroke-dashoffset="0" /* shift the dash pattern — the key to line-drawing */
/>
Vài điều người mới bỏ lỡ:
- Stroke nằm giữa đường — nửa tràn vào trong, nửa ra ngoài.
stroke-linecapchỉ ảnh hưởng đầu của subpath hở; hình kín dùnglinejoinkhắp nơi.stroke-dasharray+stroke-dashoffsetlà mánh đằng sau animation “tự vẽ” ta dựng ở Phần 6. Nhớ cặp đôi này — nó có ích về sau.
defs + url(#id): định nghĩa một lần, sơn nhiều lần
Gradient và pattern không phải màu — chúng là paint server con định nghĩa trong <defs> và tham chiếu bằng id. <defs> chứa những thứ không vẽ trực tiếp, chỉ để tham chiếu.
Gradient tuyến tính
<defs>
<linearGradient id="brand" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#c8ff00" />
<stop offset="100%" stop-color="#60a5fa" />
</linearGradient>
</defs>
<rect width="100" height="100" fill="url(#brand)" />
x1,y1 → x2,y2 là hướng của gradient. Mặc định toạ độ là objectBoundingBox (0→1 trên hình); chuyển gradientUnits="userSpaceOnUse" để dùng toạ độ user thật.
Gradient toả tròn
<radialGradient id="sun" cx="50%" cy="40%" r="60%">
<stop offset="0%" stop-color="#f59e0b" />
<stop offset="100%" stop-color="#ef4444" />
</radialGradient>
cx,cy,r đặt vòng tròn; thêm fx,fy để dời điểm sáng lệch tâm cho hiệu ứng quả cầu 3D.
Pattern
<pattern id="dots" width="20" height="20" patternUnits="userSpaceOnUse">
<circle cx="10" cy="10" r="4" fill="#34d399" />
</pattern>
<rect width="200" height="200" fill="url(#dots)" />
Pattern lát một bản vẽ nhỏ khắp vùng fill — chấm, gạch chéo, thậm chí ảnh texture. Cùng một id gradient/pattern sơn được hàng trăm hình; nó chỉ được định nghĩa đúng một lần.
Lời cảnh báo của sư phụ
fillmặc định đen, không trong suốt. Hình không có thuộc tínhfilllà đen đặc, không phải vô hình. Đặtfill="none"nếu chỉ muốn viền.- CSS thắng thuộc tính trình bày. Nếu
fill="red"“không ăn”, có một luật CSS đang ghi đè. - Id phải duy nhất trên trang. Hai
id="g"thì cái thứ hai bị bỏ qua — lỗi kinh điển khi inline nhiều SVG.
Luyện tập, không thì coi như chưa học
- Huy hiệu thương hiệu: rect bo góc tô gradient tuyến tính 2 stop và stroke 2px.
- Vòng nét đứt:
<circle>vớifill="none", stroke dày, vàstroke-dasharrayđể thành vòng chấm. Rồi animatestroke-dashoffsettrong DevTools và cảm nhận Phần 6 sắp tới. - Một gradient, ba hình: định nghĩa một gradient trong
<defs>và fill một rect, một circle, một path bằng nó.
Phần tiếp theo
Hình, path, và sơn — giờ con vẽ và tô được mọi thứ. Tiếp theo ta thêm chữ. Ở Phần 4 ta làm chủ text SVG: định vị chính xác bằng x/y/dx/dy, style span bằng <tspan>, và món đinh — uốn nhãn chạy theo mọi đường cong bằng <textPath>.