Vì hình ảnh SVG có thể được nội tuyến trong HTML, nên chúng tôi có thể thao tác với chúng bằng JavaScript. Điều này có nghĩa là chúng ta có thể tạo hiệu ứng động cho các phần của hình ảnh từ mã, làm cho nó có tính tương tác hoặc xoay chuyển mọi thứ và tạo đồ họa từ dữ liệu.
Trong ví dụ này, chúng ta sẽ tạo một chiếc đồng hồ. Chúng tôi sẽ sử dụng SVG để vẽ đồng hồ và sử dụng JavaScript để tạo hoạt ảnh cho kim.
Hướng dẫn này nâng cao hơn một chút, đi sâu vào một số thuộc tính SVG ít rõ ràng hơn và tập trung vào hoạt ảnh với JavaScript. Nếu bạn muốn có cái nhìn tổng quan hơn về SVG, thì hãy xem bài viết trước của tôi, nơi chúng ta xem qua mã của 7 hình ảnh SVG đơn giản.
Bạn cũng có thể xem bài viết này dưới dạng video có nhiều nội dung hơn. Trong video, chúng tôi cũng đề cập đến sự tương tác.
SVG trong HTML
Trong bài viết trước, chúng ta đã biết rằng hình ảnh SVG có thể được tạo dòng trong tài liệu HTML. Chúng tôi đã nói về chính thẻ SVG, thẻ xác định kích thước của hình ảnh và vị trí của các thành phần hình ảnh.
Các yếu tố hình ảnh được đặt trong hình ảnh theo vị trí của chúng. Các viewBox
xác định cách các vị trí này nên được giải thích.
Hai số đầu tiên của thuộc tính đặt vị trí ở góc trên cùng bên trái. Cùng với kích thước được xác định bởi hai số cuối, chúng tạo thành một hệ tọa độ.

<html>
<head>
<title>Watch</title>
<link rel="stylesheet" href="https://www.freecodecamp.org/news/svg-javascript-tutorial/./index.css" />
</head>
<body>
<svg width="200" height="200" viewBox="-100 -100 200 200">
<circle
cx="0"
cy="0"
r="90"
fill="transparent"
stroke="#f0f0c9"
stroke-width="7"
/>
</svg>
<script src="./index.js"></script>
</body>
</html>
Trong ví dụ này, chúng tôi căn giữa hệ tọa độ. Các 0,0
tọa độ nằm ở giữa hình ảnh. Chúng tôi thiết lập với viewBox
rằng góc trên cùng bên trái phải là -100,-100
tọa độ và cả chiều rộng và chiều cao phải là 200 đơn vị.
Trong ví dụ này, kích thước được xác định bởi width
và height
và kích thước được xác định bởi viewBox
giống nhau. Điều này có nghĩa là một đơn vị trong hình ảnh sẽ là một pixel trong trình duyệt. Điêu nay không phải luc nao cung đung. Nếu cả hai không khớp thì hình ảnh sẽ tăng hoặc giảm tỷ lệ.
Cách làm kim phút và kim giờ của đồng hồ
Bây giờ chúng ta đã thiết lập nền tảng của mình, hãy bắt đầu viết mã cho đồng hồ. Chúng ta bắt đầu với kim phút và kim giờ.
Có nhiều cách để vẽ những đường nhỏ này. Chúng ta có thể vẽ từng đường một, nhưng có lẽ cách vẽ hiệu quả nhất là vẽ một vòng tròn với thuộc tính gạch ngang đặc biệt.
Các circle
thẻ trong ví dụ ban đầu của chúng tôi có vị trí trung tâm, bán kính cho kích thước, màu tô và đường viền cũng như chiều rộng đường viền.
Các phần tử SVG thường có các tùy chọn tạo kiểu tương tự như các phần tử HTML với CSS. Nhưng các tùy chọn này có tên thuộc tính khác nhau. Bạn có thể nghĩ về fill
tài sản như background-color
trong CSS. Và stroke
và stroke-width
tính chất cũng tương tự như border-color
và border-width
tính chất. Chỉ cần ghi nhớ rằng chúng không hoàn toàn giống nhau.
Chúng tôi cũng sẽ sử dụng fill
thuộc tính để đặt màu văn bản và chúng tôi sẽ sử dụng thuộc tính stroke
thuộc tính để đặt màu cho một dòng.
Bây giờ làm cách nào để biến một vòng tròn liên tục thành các điểm đánh dấu phút? Bạn có thể quen thuộc với border-style
thuộc tính trong CSS. Hầu hết bạn sẽ sử dụng đường viền liền nét, nhưng bạn cũng có thể có đường viền chấm hoặc nét đứt. Các kiểu viền này không phổ biến lắm, vì bạn không có nhiều tùy chọn để tinh chỉnh chúng trong CSS.

border-style
thuộc tính trong CSS cho các phần tử HTMLTrong SVG, chúng tôi có các khả năng tương tự với nhiều tùy chọn tùy chỉnh hơn. Chúng ta có thể sử dụng stroke-dasharray
các stroke-dashoffset
và pathLength
tính chất.
Hãy có một vài ví dụ. Trong ví dụ đầu tiên, chúng tôi đặt một số duy nhất là stroke-dasharray
. Điều này sẽ dẫn đến một đường viền đứt nét trong đó cả đoạn thẳng và khoảng cách đều có cùng độ dài.

stroke-dasharray
tài sản cho SVGThuộc tính này là một mảng mặc dù. Nếu chúng ta đặt hai số, thì số đầu tiên sẽ là độ dài của đoạn thẳng và số thứ hai sẽ là độ dài của khoảng cách. Bạn thậm chí có thể đặt nhiều hơn hai số, sau đó độ dài của dòng và khoảng cách sẽ luôn lấy số tiếp theo. Cho đến khi nó chạy hết mảng rồi mới vào đầu.
Chúng tôi sẽ thiết lập hai số. Một cho chiều dài của điểm đánh dấu phút và một cho khoảng cách giữa chúng. Tổng của hai điều này phải chính xác bằng độ dài của một phút trên vòng tròn. Chúng tôi biết rằng một giờ là 60 phút. Vì vậy, chúng ta có thể tính chu vi của vòng tròn, sau đó chia nó cho 60 để có được độ dài của một phút.
Nhưng có một cách tốt hơn. Thay vì tính chu vi hình tròn, ta có thể đi theo cách khác. Chúng ta có thể thiết lập pathLength
tài sản.
Tài sản này là một chút khó khăn. Nó không thay đổi kích thước vòng tròn nhưng ảnh hưởng đến cách diễn giải thuộc tính dasharray. Các nét gạch ngang sẽ được vẽ như thể hình tròn có chu vi được xác định bởi pathLength
.
Vì vậy, hãy thiết lập pathLength
đến 60
, đại diện cho 60 phút. Bây giờ tổng của đoạn thẳng và đoạn trống phải bằng 1. tôi đặt nó thành 0.2
và 0.8
trong ví dụ này.

pathLength
tài sản. Lưu ý rằng tổng của hai số tại stroke-dasharray
thuộc tính là một, phù hợp với độ dài của một phút.Bây giờ chúng tôi gần như đã hoàn thành, nhưng vẫn còn thiếu một phần nhỏ. Dấu gạch ngang bắt đầu ở vị trí sai. Để khắc phục, chúng ta phải dịch chuyển nó bằng một nửa độ dài của đoạn thẳng bằng cách sử dụng stroke-dashoffset
tài sản.
Thuộc tính bù trừ dấu gạch ngang có thể hơi phản trực giác, vì giá trị dương ở đây sẽ dịch chuyển dấu gạch ngang về phía sau. Bạn cũng có thể đặt nó thành một số dương để chuyển nó về phía trước.

stroke-dashoffset
Theo cách tương tự, chúng ta có thể đặt điểm đánh dấu giờ. Chúng tôi thêm một thẻ vòng kết nối mới với các thuộc tính gần như giống nhau. Điều khác biệt duy nhất là màu sắc và chúng ta có những khoảng trống dài hơn trong mảng gạch ngang.
. . .
<svg width="200" height="200" viewBox="-100 -100 200 200">
<circle
cx="0"
cy="0"
r="90"
fill="transparent"
stroke="#0f0e0e"
stroke-width="7"
stroke-dasharray="0.2 0.8"
stroke-dashoffset="0.1"
pathLength="60"
/>
<circle
cx="0"
cy="0"
r="90"
fill="transparent"
stroke="#f0f0c9"
stroke-width="7"
stroke-dasharray="0.2 4.8"
stroke-dashoffset="0.1"
pathLength="60"
/>
</svg>
. . .
Điều quan trọng cần lưu ý ở đây là việc xếp lớp trong SVG rất quan trọng. Các thẻ được thêm vào sau trong tài liệu sẽ nằm trên các thẻ trước đó. Nếu chúng ta thêm hai vòng tròn này theo thứ tự ngược lại, thì số phút sẽ bao phủ hoàn toàn các điểm đánh dấu giờ.
Vì SVG hiện có trong HTML, nên chúng tôi có thể di chuyển một số thuộc tính này khỏi CSS. Tuy nhiên, chúng tôi không thể di chuyển tất cả các thuộc tính. Có sự khác biệt giữa các thuộc tính xác định kiểu và các thuộc tính xác định hình dạng của một phần tử.
Ví dụ, bán kính xác định hình dạng của hình tròn, do đó, nó phải tuân theo mã SVG. Mặt khác, các thuộc tính tô và nét vẽ chúng ta có thể di chuyển.
. . .
<svg width="200" height="200" viewBox="-100 -100 200 200">
<circle class="minute_marker" r="90" pathLength="60" />
<circle class="hour_marker" r="90" pathLength="60" />
</svg>
. . .
.hour_marker {
fill: transparent;
stroke: #f0f0c9;
stroke-width: 7;
stroke-dasharray: 0.2, 4.8;
stroke-dashoffset: 0.1;
}
.minute_marker {
fill: transparent;
stroke: #0f0e0e;
stroke-width: 7;
stroke-dasharray: 0.2, 0.8;
stroke-dashoffset: 0.1;
}
Cách vẽ kim đồng hồ
Hãy thêm các bàn tay hiển thị thời gian. Ban đầu, chúng tôi vẽ những thứ này để hướng lên trên, sau đó biến chúng thành vị trí bằng JavaScript.

chúng tôi sử dụng line
yếu tố để vẽ tay. Để xác định một phần tử đường, chúng ta phải đặt tọa độ bắt đầu và kết thúc, cùng với một stroke
màu sắc và stroke-width
tài sản.
Để làm cho mọi thứ đẹp hơn một chút, chúng ta cũng có thể thêm stroke-linecap
thuộc tính để có giới hạn dòng tròn. Chúng tôi thêm các thuộc tính tạo kiểu này bằng CSS.
. . .
<svg width="200" height="200" viewBox="-100 -100 200 200">
<circle class="minute_marker" r="90" pathLength="60" />
<circle class="hour_marker" r="90" pathLength="60" />
<line class="hand" x1="0" y1="0" x2="0" y2="-50" />
<line class="hand hand--thick" x1="0" y1="-12" x2="0" y2="-50" />
<line class="hand" x1="0" y1="0" x2="0" y2="-80" />
<line class="hand hand--thick" x1="0" y1="-12" x2="0" y2="-80" />
<line class="hand hand--second" x1="0" y1="12" x2="0" y2="-80" />
</svg>
. . .
. . .
.hand {
stroke: #ffffff;
stroke-width: 2;
stroke-linecap: round;
}
.hand--thick {
stroke-width: 7;
}
.hand--second {
stroke: yellow;
}
Cách trỏ kim đồng hồ đúng hướng
Bây giờ làm thế nào để chúng ta chuyển những dòng này vào vị trí? Nếu chúng ta gán ID cho một phần tử, chúng ta có thể truy cập và thao tác nó từ JavaScript.
Tuy nhiên, phần tử nào chúng ta nên gán ID? Chúng tôi có hai yếu tố cho một tay. Để giải quyết vấn đề này, chúng ta có thể nhóm hai thành phần dòng này trong một thẻ nhóm. Bạn có thể coi thẻ nhóm là div
phần tử trong HTML.
Chúng tôi có thể gán ID cho nhóm này, sau đó chúng tôi có thể xoay toàn bộ nhóm vào vị trí từ JavaScript.
. . .
<svg width="800" height="800" viewBox="-100 -100 200 200">
<circle class="minute_marker" r="90" pathLength="60" />
<circle class="hour_marker" r="90" pathLength="60" />
<g id="hour_hand">
<line class="hand" x1="0" y1="0" x2="0" y2="-50" />
<line class="hand hand--thick" x1="0" y1="-12" x2="0" y2="-50" />
</g>
<g id="minute_hand">
<line class="hand" x1="0" y1="0" x2="0" y2="-80" />
<line class="hand hand--thick" x1="0" y1="-12" x2="0" y2="-80" />
</g>
<g id="second_hand">
<line class="hand hand--second" x1="0" y1="12" x2="0" y2="-80" />
</g>
</svg>
. . .
Trong tệp JavaScript, đầu tiên, chúng tôi lấy các phần tử tay theo ID. Sau đó, chúng tôi tạo một đối tượng Ngày và chúng tôi nhận được giờ, phút và giây hiện tại. Và cuối cùng, chúng tôi thiết lập các yếu tố’ transform
thuộc tính dựa trên các giá trị này.
const hoursElement = document.getElementById("hour_hand");
const minutesElement = document.getElementById("minute_hand");
const secondsElement = document.getElementById("second_hand");
const date = new Date();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
hoursElement.setAttribute("transform", `rotate(${(360 / 12) * hour})`);
minutesElement.setAttribute("transform", `rotate(${(360 / 60) * minute})`);
secondsElement.setAttribute("transform", `rotate(${(360 / 60) * second})`);
Thuộc tính biến đổi có thể bao gồm nhiều phép biến đổi như chia tỷ lệ, dịch hoặc nghiêng.
Chúng tôi đang thiết lập rotate
chuyển đổi, đòi hỏi một số. Con số này là một vòng quay giữa 0 và 360 độ. Đối với kim giờ, chúng ta chia 360 cho 12 để biết số vòng quay chúng ta cần mỗi giờ và nhân nó với giờ hiện tại. Điều này sẽ quay kim giờ về phía giờ hiện tại.
Đối với kim phút và kim giây, chúng ta làm tương tự, ngoại trừ việc chúng ta chia 360 cho 60, vì một giờ gồm 60 phút và 1 phút là 60 giây.
May mắn cho chúng tôi, trung tâm chuyển đổi theo mặc định là nguồn gốc, 0,0
Tọa độ. Nếu đây không phải là trường hợp chúng ta có thể đặt gốc biến đổi khác, nhưng vì viewBox
cài đặt, chúng tôi không cần điều đó.
Làm thế nào để Animate các xem Hvà
Bây giờ, điều này đã hiển thị thời gian hiện tại, nhưng hình ảnh của chúng tôi là tĩnh. Để theo kịp thời gian, chúng ta có thể sử dụng requestAnimationFrame
có chức năng cử động tay.
const hoursElement = document.getElementById("hour_hand");
const minutesElement = document.getElementById("minute_hand");
const secondsElement = document.getElementById("second_hand");
function animate() {
const date = new Date();
const hour = date.getHours() % 12;
const minute = date.getMinutes();
const second = date.getSeconds();
hoursElement.setAttribute("transform", `rotate(${(360 / 12) * hour})`);
minutesElement.setAttribute("transform", `rotate(${(360 / 60) * minute})`);
secondsElement.setAttribute("transform", `rotate(${(360 / 60) * second})`);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Chúng tôi di chuyển logic xoay thành một hàm animate và sử dụng hàm requestAnimationFrame.
Đầu tiên, chúng ta kích hoạt nó bằng cách gọi requestAnimationFrame bên ngoài hàm animate. Sau đó, để tiếp tục hoạt ảnh, chúng tôi cũng yêu cầu một khung hình khác ở cuối mỗi chu kỳ hoạt ảnh.
Nếu bạn muốn có hình ảnh động mượt mà hơn, thì bạn có thể tinh chỉnh vị trí. Thay vì có các vị trí riêng biệt cho các kim, chúng ta có thể xác định chúng theo cách mà chúng có thể chỉ ra các giây, phút và giờ.
const hoursElement = document.getElementById("hour_hand");
const minutesElement = document.getElementById("minute_hand");
const secondsElement = document.getElementById("second_hand");
function animate() {
const date = new Date();
const hour = date.getHours() + date.getMinutes() / 60;
const minute = date.getMinutes() + date.getSeconds() / 60;
const second = date.getSeconds() + date.getMilliseconds() / 1000;
hoursElement.setAttribute("transform", `rotate(${(360 / 12) * hour})`);
minutesElement.setAttribute("transform", `rotate(${(360 / 60) * minute})`);
secondsElement.setAttribute("transform", `rotate(${(360 / 60) * second})`);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Kim giờ sẽ không nhận được vị trí của nó chỉ dựa trên giờ, nhưng nó cũng sẽ quay nhẹ dựa trên số phút hiện tại.
Kim phút sẽ xem xét giây hiện tại trong vòng quay của nó. Và kim giây cũng sẽ tính bằng mili giây. Bằng cách này, tay của chúng ta sẽ có một chuyển động liên tục. Chúng sẽ không nhảy từ giây này sang giây khác, nhưng chúng sẽ sinh động.
Các bước tiếp theo – Cách làm cho đồng hồ tương tác
Bây giờ nếu chúng ta kiểm tra kết quả, chúng ta sẽ có một chiếc đồng hồ hoạt hình trơn tru.
Để đi xa hơn, bạn cũng có thể thêm cửa sổ lịch hiển thị ngày hiện tại, với text
thành phần. Và để đưa nó lên cấp độ tiếp theo, bạn thậm chí có thể thêm một trình xử lý sự kiện cho phần tử này, phần tử này sẽ chuyển đổi nội dung của nó giữa ngày hiện tại và chỉ báo AM/PM.
Nếu bạn gặp khó khăn, hãy xem video bên dưới, nơi chúng tôi cũng đề cập đến phần này.
Trộn SVG với JavaScript sẽ mở ra rất nhiều tùy chọn thú vị. Bạn có thể tạo hoạt ảnh cho mọi thứ, thêm tương tác và tạo đồ họa. Không thể chờ đợi để xem những gì bạn đưa ra.
Đăng ký để biết thêm hướng dẫn về Phát triển Web:
Hunor Márton Borbély
Phát triển trò chơi với JavaScript, hướng dẫn mã hóa sáng tạo, canvas HTML, SVG, Three.js và một số React và Vue https://twitter.com/HunorBorbelyhttps://codepen.io/HunorMarton…
