Tìm hiểu về SVG

Một ngày đẹp trời, mình thấy trang DiagramPlus. Mình thắc mắc là làm sao có thể làm những cái line cùng với những animation khi focus vào 1 cái table như vậy.

DiagramPlus

Lúc đó inspect lên thì thấy là dùng SVG. Mở code lên thì đúng là khó khăn vì đọc mấy cái số trong mấy cái element thì chả hiểu gì cả. Cộng với nhiều khi làm việc với svg thì mình cũng chủ yếu sửa mấy cái đơn giản về style thôi chứ cụ thể nó vẽ như nào mình cũng không biết. Vì thế nên mình quyết tâm tìm hiểu xem SVG là cái gì và làm sao để có thể dùng nó.

Vậy rốt cuộc, SVG là gì?

SVG là viết tắt của Scalable Vector Graphics. SVG định nghĩa vector-based graphics bằng định dạng XML.

Có 1 điều đặc biệt ở SVG đó là hình ảnh từ SVG sẽ không bị mất chất lượng khi zoom in. Khác với Raster graphics như PNG, JPEG... Thì hình ảnh sẽ được lưu dưới dạng ma trận hay 1 lưới điểm ảnh. Khi zoom in thì các điểm ảnh sẽ được phóng to lên và khiến cho giảm chất lượng hình ảnh. SVG sử dụng các công thức toán học để vẽ nên là hình ảnh sẽ không bị mất chất lượng khi zoom in.

SVG có những lợi gì?

Mình đã thử viết 1 file html sử dụng 30 icon sử dụng file png. Và 1 file html sử dụng 30 icon SVG nhưng inline. Thì khi load lên thì tốc độ tải khác hẳn.

SVG inlinePNG file

Đương nhiên rằng việc viết SVG vào HTML sẽ không phải là tốt nhất. Vì nếu bạn viết SVG vào HTML thì sẽ không thể cache và reuse được. Có 1 cách đó là SVG sprites. nó sẽ giúp bạn có thể cache và reuse được SVG. Nhưng nó cũng có nhược điểm là nếu bạn muốn thay đổi style của SVG thì sẽ phải thay đổi trong file CSS.

Để implement được SVG sprites bạn có thể xem tại đây.

<?xml version="1.0" encoding="utf-8"?>
  <svg xmlns="http://www.w3.org/2000/svg">
    <defs>
      <symbol id="setting" viewBox="0 0 50 50" >
      ....
      </symbol>
      <symbol id="phone" viewBox="0 0 50 50" >
      ....
      </symbol>
    </defs>
</svg>

Định nghĩa tất cả các hình ảnh trong file svg. Kèm theo mỗi symbold là 1 id cho mỗi hình. Sau đó bạn có thể sử dụng các hình ảnh đó bằng cách sử dụng use element. Nếu ví dụ bạn muốn sử dụng hình ảnh setting thì sẽ là như sau

<svg class="icon" viewBox="0 0 50 50">
  <use href="./icons.svg#setting" />
</svg>
SVG sprites

Sau khi sử dụng SVG sprites thì bạn đã có thể load được toàn bộ các icon lên trong 1 file và có thể dùng net work cache để cache lại các icon đó.

SVG có những shape cơ bản nào?

1. Rectangle

Hình chữ nhật được định nghĩa bằng element rect

<rect x="0" y="0" width="50" height="50" rx="5" ry="5" fill="#ffab73" />

Trong đó x, y là toạ độ của hình chữ nhật. width, height là kích thước của hình chữ nhật.rx, ry là border-radius của hình chữ nhật.

2. Circle

Hình tròn được định nghĩa bằng element circle

<circle cx="25" cy="25" r="25" fill="#ffab73" />

Trong đó cx, cx là toạ độ tâm của hình tròn. rx là bán kính hình tròn

3. Ellipse

Hình Ellip được định nghĩa bằng element ellipse

<ellipse cx="100" cy="50" rx="100" ry="50" fill="#ffab73" />

Trong đó cx, cx là toạ độ tâm của hình ellip. rx, ry là bán kính ngang dọc của hình ellip.

4. Line

Một đường thẳng được định nghĩa bằng element line

<line x1="5" y1="5" x2="99" y2="99" stroke="#ffab73" stroke-width="3"/>

Trong đó x1, y1x2, y2 là 2 điểm của đường thẳng.

5. Polyline

polyline định nghĩa 1 tập các đoạn thẳng kết nối với nhau
<polyline
  points="0,0 40,0 40,40 80,40 80,0 120,0 120,40 160,40 160,0 200,0 200,40"
  stroke="#ffab73"
  fill="none"/>

Trong đó points là tập hợp các điểm đầu và điểm cuối của đoạn thẳng

6. polygon

polygon định nghĩa 1 tập các đoạn thẳng kết nối với nhau
<polygon
  points="0,0 40,0 40,40 80,40 80,0 120,0 120,40 160,40 160,0 200,0 200,40 0 40"
  fill="#ffab73"
  stroke="#ffab73"
/>

Trong đó points là tập hợp các điểm đầu và điểm cuối của đoạn thẳng. Điểm cuối của đoạn thẳng cuối cùng sẽ được tự động nối với điểm đầu của đoạn thẳng đầu tiên tạo nên 1 hình khép kín

Vậy làm sao để vẽ được đường cong?

Thường thì để vẽ ra đường các đường cong phức tạp thì sẽ dùng các tool online để tạo ra thay vì code chay. Nhưng để hiểu được nó là cái gì thì ở đây mình sẽ giải thích cách để dùng nó.

Điểm tuyệt đối là được xác định toạ độ vị trí chính xác sẽ vẽ tới.
Điểm tương đối là được xác định bằng cách cộng toạ độ vị trí của điểm hiện tại với giá trị được cung cấp.

Sử dụng element path để vẽ ra các đường cong. Với thuộc tính d bên trong path dùng để diễn tả shape trong path bao gồm các lệnh moveto, line, curve, arc closepath

moveto

Lệnh moveto bắt đầu bằng M hoặc m dùng để di chuyển tới 1 điểm xác định.
M dùng để di chuyển tới 1 điểm tuyệt đối. m dùng để di chuyển tới 1 điểm tương đối.

closepath

Lệnh closepath dùng để vẽ 1 đường thẳng kết nối từ điểm cuối cùng và điểm đầu tiên của path. Điều đặc biệt là khác với lệnh moveto thì dùng z hay Z đều được.

line

Lệnh line dùng để vẽ 1 đường thẳng kết nối 2 điểm, từ điểm hiện tại cho đến 1 điểm xác định.

L / l
<path d="M0 0 L50 50 l100 0" stroke="black" stroke-width="10" fill="none" />

Bắt đầu bằng L dùng để vẽ 1 đường thẳng tới 1 điểm tuyêt đối..
Bắt đầu bằng l dùng để vẽ 1 đường thẳng tới 1 điểm tương đối so với điểm hiện tại

H / h, V / v
<path d="M0 0 L50 50 H 100 V 100 h 50 v 50" stroke="black" stroke-width="10" fill="none" />

Bắt đầu bằng H dùng để vẽ 1 đường ngang tới 1 điểm tuyệt đối với toạ độ x được cung cấp
Bắt đầu bằng h dùng để vẽ 1 đường ngang tới 1 điểm tương đối với điểm hiện tại. x = currentX + dx
Bắt đầu bằng V dùng để vẽ 1 đường dọc tới 1 điểm tuyệt đối với toạ độ y được cung cấp
Bắt đầu bằng v dùng để vẽ 1 đường dọc tới 1 điểm tương đối với điểm hiện tại y = currentY + dy

curve

Có 3 nhóm lệnh trong curve được sử dụng để vẽ các đường cong. Bao gồm Cubic Bézier (C, c, S, s), Quadratic Bézier (Q, q, T, t), and Elliptical arc (A, a)..

Đầu tiên phải tìm hiểu xem Cubic Bézier là gì

Cubic Bézier là Bézier Curve bậc ba

Cubic Bézier là một đường cong được định nghĩa bằng các điểm: điểm bắt đầu, điểm kết thúc và 2 điểm điều khiển.

Vậy thì chính xác nó được máy tính vẽ như thế nào?

P0P3P1P2Q0T1Q1XT2Q2

Tưởng tượng quá trình vẽ sẽ bắt đầu từ 0% tới 100%. Bắt đầu bằng các điểm P0, P1, P2, có các điểm Q0, Q1, Q2 bắt đầu chạy từ điểm xuất phát tương ứng. Ứng với từng thời điểm chúng ta sẽ xác định điểm T1 và T2 là các điểm tương ứng với số % hiện tại. Sau đó tiếp tục nối 2 điểm T1 và T2 lại với nhau. Chúng ta lấy điểm X với là toạ độ của điểm cần vẽ, với toạ độ X là toạ độ dựa trên giá trị % của quá trình vẽ hiện tại dựa trên đường thằng T1 T2. Hình bên trên là mô tả lúc quá trình vẽ đang ở 50% và cách xác định điểm X. Đường Cubic Bézier là tập hợp các điểm X trong suốt quá trình vẽ

Cubic Bezier

Mô phỏng quá trình vẽ của Cubic Bézier

<path d="M100 200 C150.0 70.0, 350.0 30.0, 450 200" />
Bạn có thể kéo thử các điểm điều khiển để thấy được sự thay đổi của đường cong.

Đường cong Cubic Bézier trong SVG được vẽ bằng lệnh C / c. C để xác định các toạ độ tuyệt đối và c để xác định điểm có giá trị được tính bằng điểm hiện tại cộng với dx / dy

Ngoài ra bạn có thể thấy Cubic Bézier ở các thuộc tính transition-timing-function của css. Bạn có thể đọc thêm tại transition timing function easing function

Để vẽ được những đường cong Cubic Bézier mượt mà từ điểm kết thúc của đường cong phía trước. Chúng ta có thể sử dụng lệnh S/s để vẽ tiếp đường cong. Lệnh này sẽ tự động xác định điểm điều khiển thứ 1 dựa trên điểm điều khiển thứ 2 của đường cong phía trước. Lệnh S/s sẽ nhận vào 2 điểm là điểm điều khiển thứ 2 và điểm kết thúc

<path d="M10 200 C150.0 70.0, 250.0 30.0, 550.0 200.0 S450.0 30.0, 550 200" />
Bạn có thể kéo thử các điểm điều khiển để thấy được sự thay đổi của đường cong.

Quadratic Bézier

Quadratic Bézier là Bézier Curve bậc hai

Quadratic Bézier là đường cong mà chỉ có 1 điểm kiểm soát. Nó yêu cầu một điểm kiểm soát xác định độ dốc của đường cong ở cả điểm bắt đầu và điểm kết thúc. Nó có hai tham số: điểm kiểm soát và điểm cuối của đường cong.

Q x1 y1, x y 
 (or) 
q dx1 dy1, dx dy

Tương tự với Cubic Bézier, chúng ta có thể dùng lệnh T để vẽ tiếp 1 đường cong dựa theo đường cong phía trước. Lệnh này sẽ tự động xác định điểm kiểm soát dựa trên điểm kiểm soát của đường cong phía trước. Nó chỉ có 1 tham số là điểm kết thúc của đường cong. Và chỉ duy nhất dùng được nếu phía trước là lệnh Q hoặc T

T x y 
 (or) 
t dx dy
ƒ
<path d="M 10 80 Q52.5 10, 95 80, 180 80" />
<path
   d="M 10 80 Q 52.5 10, 95 80 T 180 80"
   stroke="green"
   fill="transparent"
 />        

Arc

Lệnh A dùng để vẽ một đường vong cung với cú pháp là

A rx ry x-axis-rotation large-arc-flag sweep-flag x y

Trong đó:

large-arc-flag and sweep-flag

Với 2 thông số large-arc-flagsweep-flag có thể xác định được 4 cung tròn khác nhau.

Animation trong SVG

Để làm được animation trong SVG thì chúng ta sẽ sử dụng element animate, element animateMotion animateTransform để tạo ra các animation

<animate
  attributeName="fill"
  values="red;green;red"
  dur="4s"
  repeatCount="indefinite"
/>
<animateMotion
  path="M 0 0 L 40 0 z"
  dur="4s"
  repeatCount="indefinite"
/>
<animateTransform
  attributeName="transform"
  attributeType="XML"
  type="rotate"
  from="0 30 30"
  to="360 30 30"
  dur="1s"
  repeatCount="indefinite"
/>

Cùng nhau vẽ thử Line Chart dùng SVG

Để vẽ được animation như trên thì dùng stroke-darkarray stroke-darkoffset để tạo ra animation

Thử thay đổi giá trị của stroke-darkarray stroke-darkoffset để xem sự thay đổi của đường thẳng

Nếu bạn để ý thì nếu để stroke-darkarray là 600. Rồi giảm stroke-darkoffset từ 600 về 0 thì sẽ thấy đường thẳng như đang chạy từ điểm bắt đầu về điểm kết thúc. Vậy để làm animation thì chúng ta chỉ cần cho giá trị của stroke-darkoffset chạy về 0 là được. Nhưng mà làm sao để biết chính xác được giá trị của stroke-darkoffset là bao nhiêu?. Giá trị của stroke-darkoffset sẽ là bằng độ dài của path và chúng ta có thể dùng hàm getTotalLength của Javascript để lấy giá trị. Sau đó chúng ta có thể viết 1 đoạn animate trong path là được rồi.

<animate
  attributeName="stroke-dashoffset"
  to="0"
  dur="3s"
  fill="freeze"
  />

fill="freeze" dùng để giữ giá trị của thuộc tính sau khi animation kết thúc. Nếu không có thì giá trị của thuộc tính sẽ trở về giá trị ban đầu.

Text trong SVG

Để viết text trong SVG thì ta dùng element text. Nếu muốn text có thể wrap text thì dùng element tspan bên trong element text.

<svg viewBox="0 0 200 50" xmlns="http://www.w3.org/2000/svg">
  <text x="1" y="30" class="text-xs">
    Hello
    <tspan fill="#ffab73"> World </tspan>!
  </text>
</svg>
Hello World !

Hoặc là nếu bạn muốn text uốn cong theo 1 đường cong nào đó thì dùng element textPath bên trong element text. Chỉ cần vẽ ra path rồi dùng textPath trỏ tới dựa theo id của path. Nếu muốn ẩn path đi thì bỏ vào trong 1 element defs là được

<svg
    className="border border-gray-200 max-w-[600px] w-full"
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0  200 160"
    >
  <defs>
    <path
      d="M 10 80 Q 52.5 10, 95 80 T 180 80"
      stroke="#ffab73"
      fill="transparent"
      id="path"
    />
  </defs>
  <text className="text-xs">
    <textPath href="#path">
      Quick brown fox jumps over the lazy dog.
    </textPath>
  </text>
</svg>
Quick brown fox jumps over the lazy dog.

Tổng kết

Nhìn chung thì việc tự code 1 cái svg hoàn toàn thì rất là phức tạp để làm. Nhưng mà việc biết qua cơ bản về các shape và 1 vài thuộc tính của nó cũng có thể giúp mình dễ dàng thay đổi hoặc giúp đỡ trong công việc làm Front-end. Bài viết này như một cách để mình note lại những gì mà mình học được trong cả quá trình tìm hiểu về SVG. Nếu có sai sót gì thì mọi người cứ góp ý cho mình nhé.

Vì bài viết này được chia sẻ vào ngày 24/12 nên mình xin chúc mọi người một mùa Giáng sinh an lành và một năm mới hạnh phúc.

Tham khảo