Buổi 5: HTML (3): biểu mẫu và đa phương tiện
Hãy hình dung bạn đang làm trang web cho một trung tâm đào tạo: khách vào trang phải đăng ký khóa học (nhập họ tên, email, chọn ngày sinh, chọn hình thức học, đồng ý điều khoản), còn trang giới thiệu thì cần logo, ảnh lớp học, video bài giảng mẫu và tài liệu PDF. Hai nửa công việc này chính là nội dung buổi học hôm nay. Phần đầu — biểu mẫu (form) — là cách duy nhất để một trang HTML "hỏi" người dùng và gửi câu trả lời về máy chủ. Phần sau — nội dung đa phương tiện — là cách đưa ảnh, đồ họa vector, âm thanh, phim và tài liệu vào trang. Đây cũng là buổi cuối cùng của ba buổi học HTML; kết thúc buổi bạn đã có đủ "vốn" HTML để dựng một trang web hoàn chỉnh trước khi sang CSS.
Mục tiêu buổi học
- Sau buổi này bạn có thể dựng một biểu mẫu (form) hoàn chỉnh với
action,methodvà các điều khiển nhập liệu phù hợp (text, email, number, range, search, tel, password, url). - Sau buổi này bạn có thể chọn đúng phần tử lựa chọn (radio, checkbox, select, datalist), phần tử thời gian, tập tin, nút bấm cho từng loại dữ liệu cần thu thập.
- Sau buổi này bạn có thể gán nhãn (label), gom nhóm bằng fieldset/legend, dùng
hidden,progress,meter,outputđúng mục đích. - Sau buổi này bạn có thể dùng các thuộc tính
value,readonly,disabled,required,pattern,min,max,step,placeholder,autofocus,novalidate... để kiểm soát dữ liệu ngay trên trình duyệt. - Sau buổi này bạn có thể phân biệt GET và POST, giải thích vì sao thiếu thuộc tính
namethì dữ liệu không được gửi. - Sau buổi này bạn có thể nhúng ảnh bitmap (
img,map/area), ảnh vector SVG, âm thanh/phim (audio,video,source,track,iframe) và PDF (embed) vào tài liệu HTML.
Bản đồ buổi học
| Phần | Nội dung | Thời lượng gợi ý |
|---|---|---|
| 1. Biểu mẫu — khái niệm, ô nhập, vùng nhập | form, input (text, email, number, range, search, tel, password, url), textarea |
30 phút |
| 1. Biểu mẫu — lựa chọn, thời gian, tập tin | radio, datalist, checkbox, select/optgroup; date, time, month, week, datetime-local; file | 35 phút |
| 1. Biểu mẫu — nút bấm, phần tử khác, thuộc tính | button, reset, submit, action, method; hidden, fieldset, label, progress, meter, output; các thuộc tính kiểm tra |
40 phút |
| 2. Nội dung đa phương tiện | Bitmap và map, SVG, audio/video/track/iframe, embed PDF |
35 phút |
| Thực hành & ôn tập | Bài 2.1–2.5 của giáo trình, câu hỏi ôn tập, bài tập trên Codelab | 40 phút |
Khoảng một nửa thời gian của buổi là thực hành: sau mỗi phần lý thuyết bạn gõ và chạy thử mã ngay trong bài, rồi làm nhóm bài tập tương ứng trên Codelab.
1. Biểu mẫu
Giáo trình: Chương 2 — mục "Biểu mẫu" (Khái niệm về biểu mẫu; Ô nhập văn bản; Vùng nhập văn bản; Lựa chọn; Nhập thời gian; Nhập tập tin; Nút bấm; Các phần tử biểu mẫu khác; Các thuộc tính quan trọng khác).
1.1. Khái niệm về biểu mẫu
Phần tử <form> định nghĩa ra biểu mẫu (form) được dùng để thu thập dữ liệu do người dùng nhập. Bạn đã gặp biểu mẫu hằng ngày: màn hình đăng nhập, ô tìm kiếm của Google, phiếu đăng ký khóa học, form thanh toán khi mua hàng. Theo giáo trình:
- Một phần tử
formbao gồm các phần tử để người dùng nhập hoặc lựa chọn dữ liệu. - Các phần tử nhập trong form có thể nhận nhiều kiểu dữ liệu đầu vào khác nhau, như: văn bản (text), hộp kiểm (checkbox), nút lựa chọn (radio), nút bấm xác nhận (submit)...
- Các phần tử của form hầu hết được tạo ra bởi thẻ
<input>với thuộc tínhtypexác định kiểu đầu vào khác nhau. - Các phần tử form cũng được tạo ra bởi các thẻ
<textarea>,<select>,<option>,<button>...
Hình dung một phiếu đăng ký giấy: có ô gạch chân để điền tên, các ô vuông để đánh dấu, danh sách để khoanh tròn, và cuối cùng là nơi nộp phiếu. <form> là cả tờ phiếu; mỗi ô là một điều khiển (control). Phần tử form được khai báo như sau (action cho biết nộp phiếu ở đâu):
<form action="save.php">
... các phần tử nhập ...
</form>Phần tử <form> có nhiều thuộc tính như action, method,... Những thuộc tính này quyết định cách thức gửi dữ liệu từ máy người dùng tới server; bạn sẽ học kỹ ở mục "Nút bấm". Giáo trình chia các phần tử nhập thành bốn nhóm (cộng một nhóm "khác"):
| Nhóm | Các phần tử |
|---|---|
| 1. Nhập văn bản và số | input với type: text, email, number, range, search, tel, password, url |
| 2. Thời gian | input với type: time, week, month, date, datetime-local |
| 3. Lựa chọn | input type="checkbox", select, input type="radio", datalist, option |
| 4. Nút bấm | input type="image", input type="reset", input type="submit", input type="button", button |
| Các phần tử khác | input type="color", input type="hidden", input type="file" |
<form action="save.php" method="post">ho_ten=Nguyen+Van+Anemail=an%40neu.edu.vnca=sanghinhthuc=onlineÔ không có name hoặc bị disabled sẽ không được gửi đi.
input type="text"Văn bản một dòng
input type="email"Kiểm tra dạng hòm thư
input type="number"Chỉ nhận số, có min / max / step
input type="range"Thanh trượt chọn giá trị
input type="search"Ô tìm kiếm
input type="tel"Số điện thoại (dùng pattern)
input type="password"Che ký tự đã nhập
input type="url"Địa chỉ Web hợp lệ
input type="date"Chọn ngày
input type="time"Chọn giờ
input type="month"Chọn tháng
input type="week"Chọn tuần
input type="datetime-local"Ngày và giờ địa phương
input type="checkbox"Chọn nhiều (cùng name)
input type="radio"Chọn một trong nhóm (cùng name)
<select>Danh sách thả xuống
<datalist>Vừa gõ vừa gợi ý
input type="submit"Gửi biểu mẫu
input type="reset"Đặt lại mọi ô nhập
input type="button"Nút thường, gắn với JavaScript
<button>Nút có thể chứa cả HTML
input type="color"Bảng chọn màu
input type="file"Chọn tập tin (accept, multiple)
<textarea>Văn bản nhiều dòng
input type="hidden"Ẩn nhưng vẫn được gửi đi
requiredbắt buộc nhậpplaceholderchữ gợi ý mờvaluegiá trị mặc địnhreadonlychỉ đọc, vẫn gửidisabledvô hiệu, không gửimaxlengthgiới hạn số ký tựmin / max / stepkhoảng giá trịpatternkhớp biểu thức chính quymultiplechọn nhiều tệp/giá trịautofocustự đặt con trỏautocompletebật/tắt gợi ý tự điềnacceptgiới hạn loại tệpThuộc tính type của thẻ input quyết định kiểu nhập (văn bản, số, thời gian, lựa chọn, nút bấm…); mỗi ô có thuộc tính name mới được gửi lên máy chủ. Các ô bên dưới là điều khiển thật, có thể thao tác.
Hình trên là "bảng tra cứu" các điều khiển: mỗi ô tương ứng với một giá trị type. Hãy nhìn lướt để nhận ra hình dạng của từng loại, rồi quay lại đây khi cần chọn điều khiển cho một dữ liệu cụ thể. Ngoài ra bạn có thể tự thử mọi thứ trong phòng thí nghiệm form bên dưới: chọn type, đổi thuộc tính và xem ô nhập cùng dữ liệu được gửi đi thay đổi ra sao.
📝 Phòng thí nghiệm biểu mẫu
Chọn type + thuộc tính rồi bấm Gửi để xem dữ liệu đi đâu<form action="/save" method="get"> <label for="f">E-mail:</label> <input type="email" id="f" name="email" placeholder="[email protected]" required> <button type="submit">Gửi</button> </form>
Bạn hãy thử đổi type từ text sang number, rồi sang date; để ý ô nhập thay đổi hình dạng và dữ liệu gửi đi luôn có dạng tên=giá trị. Đây là điểm mấu chốt: trình duyệt chỉ gửi những điều khiển có name, và gửi dưới dạng cặp tên/giá trị.
Ví dụ dưới đây là biểu mẫu nhỏ nhất có thể chạy được. Chúng ta thêm một đoạn script ngắn để chặn việc chuyển trang (trong khung thử nghiệm không có máy chủ thật) và in ra đúng chuỗi dữ liệu mà trình duyệt sẽ gửi đi:
<form action="save.php">
<label for="ten">Họ tên:</label>
<input type="text" id="ten" name="hoten" value="Lan Anh">
<input type="submit" value="Gửi">
</form>
<script>
const f = document.querySelector("form");
f.addEventListener("submit", (e) => {
e.preventDefault();
console.log(new URLSearchParams(new FormData(f)).toString());
});
f.requestSubmit(); // mô phỏng bấm nút Gửi
</script>Dòng kết quả hoten=Lan+Anh cho thấy tên điều khiển (hoten) đứng trước dấu =, giá trị đứng sau, còn dấu cách được mã hóa thành +. Bạn sẽ gặp lại cơ chế này khi học GET/POST.
Lưu ý: Trong mọi ví dụ của bài này, đoạn
scriptchỉ có tác dụng minh họa dữ liệu gửi đi. Ở trang web thật,actiontrỏ tới một chương trình trên máy chủ (PHP, Node.js, ASP.NET...) và bạn không cần đoạn script này.
1.2. Ô nhập văn bản
Để yêu cầu người dùng nhập văn bản đơn thuần (plain text), ta dùng phần tử <input> với type là text. Ví dụ của giáo trình nhập họ đệm và tên:
Họ đệm: <input type="text" id="last_name" name="last_name" size="15">
Tên: <input type="text" id="first_name" name="first_name" size="15">Dữ liệu văn bản đơn thuần là kiểu dữ liệu phổ biến nhất: chuỗi, số, địa chỉ email, số điện thoại... Các dữ liệu này nếu dùng text thì không được kiểm soát định dạng. Với mỗi loại dữ liệu ấy HTML đều có một giá trị type tương ứng, vừa giúp trình duyệt kiểm tra vừa giúp thiết bị di động hiển thị bàn phím phù hợp.
Với type="email", ô nhập chứa một địa chỉ email. Tùy trình duyệt, địa chỉ sẽ được tự động kiểm tra khi người dùng nhập hoặc gửi; một số điện thoại thông minh còn thêm ký tự "@" hoặc ".com" vào bàn phím ảo và gợi ý các email đã nhập trước đây.
<form>
E-mail: <input type="email" id="email" name="email" value="abc">
</form>
<script>
const o = document.getElementById("email");
console.log("Giá trị:", o.value);
console.log("Sai định dạng email (typeMismatch)?", o.validity.typeMismatch);
o.value = "[email protected]";
console.log("Sau khi sửa, hợp lệ?", o.checkValidity());
</script>Bạn thấy validity.typeMismatch là true với "abc" và biến mất khi giá trị đúng dạng. Đây là lớp kiểm tra miễn phí mà trình duyệt cung cấp; sang buổi 13 bạn sẽ tự viết thêm kiểm tra bằng JavaScript.
Number
type="number" định nghĩa trường đầu vào là số, có thể giới hạn khoảng cho phép bằng min, max. Ví dụ của giáo trình: tuổi từ 18 đến 99. Còn step quy định bước nhảy: với min="0" max="100" step="10", các giá trị hợp lệ là 0, 10, 20,..., 100.
Tuổi (từ 18 đến 99): <input type="number" id="age" name="age" min="18" max="99" value="18"><br>
Điểm (bước 10): <input type="number" name="points" min="0" max="100" step="10">
<script>
const age = document.getElementById("age");
age.value = 15;
console.log("15 nhỏ hơn min?", age.validity.rangeUnderflow);
age.value = 120;
console.log("120 lớn hơn max?", age.validity.rangeOverflow);
age.value = 30;
console.log("30 hợp lệ?", age.checkValidity());
</script>Với ô nhập số, trình duyệt thường hiển thị mũi tên tăng/giảm, bàn phím ảo trên điện thoại đổi sang bàn phím số, và dữ liệu được kiểm tra sau khi người dùng nhập xong.
Range, search, tel, password, url
type="range": điều khiển để chọn một số không đòi hỏi độ chính xác cao (thanh trượt). Mặc định từ 0 đến 100; đổi bằngmin,max,step. Ví dụ<input type="range" name="points" min="0" max="10">.type="search": dùng cho trường tìm kiếm. Kháctexttùy từng trình duyệt: có thể hiện dấu X để xóa từ đang gõ, hoặc gợi ý các từ đã tìm gần đây.type="tel": để nhập số điện thoại. Giao diện nhìn không kháctext, nhưng trên di động bàn phím chỉ hiện ký tự dành cho số điện thoại. Nên kèmpatternđể kiểm soát định dạng, ví dụpattern="[0-9]{3}-[0-9]{2}-[0-9]{3}".type="password": nội dung nhập không hiển thị, thay bằng ký tự đại diện (Chrome dùng hình tròn đen). Trình duyệt thường ghi nhớ và tự điền mật khẩu sau khi xác thực được người dùng.type="url": để nhập địa chỉ URL; trình duyệt có thể tự kiểm tra, và bàn phím ảo có thể thêm ".com".
<form>
<p>Điểm (0–10): <input type="range" name="points" min="0" max="10" value="7"></p>
<p>Tìm kiếm: <input type="search" name="googlesearch"></p>
<p>Số điện thoại: <input type="tel" id="phone" name="phone" pattern="[0-9]{3}-[0-9]{2}-[0-9]{3}" placeholder="090-12-345"></p>
<p>Mật khẩu: <input type="password" name="password" value="bimat123"></p>
<p>Trang chủ: <input type="url" id="home" name="homepage"></p>
</form>
<script>
const sdt = document.getElementById("phone");
sdt.value = "0901234";
console.log("0901234 khớp mẫu?", !sdt.validity.patternMismatch);
sdt.value = "090-12-345";
console.log("090-12-345 khớp mẫu?", !sdt.validity.patternMismatch);
const url = document.getElementById("home");
url.value = "neu.edu.vn";
console.log("neu.edu.vn là URL hợp lệ?", !url.validity.typeMismatch);
url.value = "https://neu.edu.vn";
console.log("https://neu.edu.vn là URL hợp lệ?", !url.validity.typeMismatch);
</script>Để ý: type="url" yêu cầu có giao thức (https://). Đây là lỗi nhập liệu rất phổ biến — người dùng gõ neu.edu.vn và bị từ chối. Bạn nên có placeholder gợi ý dạng mong muốn (xem thêm ở mục các thuộc tính).
Mẹo: Trang tra cứu kiểu
inputcủa biểu mẫu được giáo trình giới thiệu tại fit.neu.edu.vn/smartdoc (mục "Kiểu input của biểu mẫu"); bạn cũng có thể dùng ô "web-playground" của Codelab để thử bất kỳtypenào.
1.3. Vùng nhập văn bản
Phần tử <textarea> xác định một vùng nhập nhiều dòng, cho phép nhập văn bản trên một không gian rộng hơn. Kích thước xác định bởi số dòng (rows) và số cột trên mỗi dòng (cols). Chiều rộng tương ứng với số ký tự có thể nhập, nhưng chỉ là tương đối vì các ký tự có độ rộng khác nhau.
Bạn cũng có thể xác định kích cỡ theo pixel bằng CSS (width, height). Khi dùng textarea, vùng soạn thảo có thể được kéo giãn bằng chuột ở góc dưới bên phải; tính năng này tắt được bằng resize: none.
<form>
<label for="request">Các yêu cầu khác (rows/cols):</label>
<textarea id="request" name="request" style="display: block" rows="4" cols="40"></textarea>
<label for="request2">Các yêu cầu khác (theo pixel, không kéo giãn):</label>
<textarea id="request2" name="request2" style="width:400px; height:80px; display:block; resize:none"></textarea>
</form>
<script>
const t = document.getElementById("request");
console.log("rows =", t.rows, "· cols =", t.cols);
t.value = "Cần học vào buổi tối";
console.log("Nội dung:", t.value);
</script>Cảnh báo:
textareakhông có thuộc tínhvaluetrong thẻ — nội dung mặc định nằm giữa thẻ mở và thẻ đóng (<textarea>nội dung</textarea>). Luôn viết cả thẻ đóng; thiếu</textarea>sẽ khiến phần HTML còn lại bị nuốt vào ô nhập.
Hai ví dụ trên đều dùng label với for, đúng như giáo trình. Bạn sẽ học label đầy đủ ở mục "Các phần tử biểu mẫu khác"; còn bây giờ chỉ cần nhớ: for của nhãn phải trùng với id của điều khiển. Thuộc tính required (bắt buộc nhập) và placeholder (gợi ý mờ) cũng sẽ có mục riêng, nhưng bạn có thể dùng ngay trong bài tập đầu tiên.
Vừa học xong ô nhập và vùng nhập, hãy làm nhóm bài sau để quen với việc gắn nhãn, dùng placeholder, required và textarea:
1.4. Lựa chọn
Khi dữ liệu chỉ có một số phương án cố định (giới tính, hình thức học, ca học, tỉnh thành...), đừng bắt người dùng gõ chữ: hãy cho họ chọn. Giáo trình giới thiệu bốn công cụ: radio, datalist, checkbox và select.
a. Radio
Ô nhập với type="radio" tạo ra một ô lựa chọn. Các ô lựa chọn có cùng giá trị thuộc tính name sẽ tạo thành một nhóm; người dùng chỉ được chọn một tùy chọn trong nhóm. Tên "radio button" xuất phát từ chiếc đài thời xưa: bấm một nút chọn kênh thì nút đang bật tự nhả ra.
<form>
<span>Hình thức học:</span>
<input type="radio" name="hinhthuc" value="online" id="online" checked>
<label for="online">Trực tuyến</label>
<input type="radio" name="hinhthuc" value="offline" id="offline">
<label for="offline">Tại cơ sở</label>
</form>
<script>
const f = document.querySelector("form");
const hienThi = () => console.log("Gửi đi:", new URLSearchParams(new FormData(f)).toString());
hienThi();
document.getElementById("offline").click();
hienThi();
</script>Kết quả cho thấy dù có hai ô, dữ liệu gửi đi chỉ có một cặp hinhthuc=...: cặp của ô đang được chọn, với giá trị lấy từ thuộc tính value. Nếu bạn quên name hoặc đặt hai tên khác nhau, hai ô sẽ độc lập và người dùng chọn được cả hai.
b. Datalist
Phần tử <datalist> đưa ra một danh sách các tùy chọn định sẵn cho một phần tử <input>. Các phương án khai báo bằng <option>. Thuộc tính list của input phải tham chiếu tới id của datalist. Khác với select, người dùng vẫn được gõ giá trị không có trong danh sách.
<label for="city">Thành phố:</label>
<input id="city" name="city" list="cities" placeholder="Gõ hoặc chọn">
<datalist id="cities">
<option value="Hà Nội">
<option value="Thành phố Hồ Chí Minh">
<option value="Đà Nẵng">
<option value="Hải Phòng">
<option value="Cần Thơ">
</datalist>
<script>
const o = document.getElementById("city");
console.log("input trỏ tới datalist:", o.getAttribute("list"));
console.log("Số gợi ý:", document.getElementById("cities").options.length);
o.value = "Huế"; // gõ giá trị ngoài danh sách vẫn được
console.log("Giá trị tự gõ:", o.value);
</script>c. Checkbox
type="checkbox" được dùng để chọn nhiều phương án (mỗi hộp bật/tắt độc lập). Ví dụ ca học: Sáng, Chiều, Tối. Giáo trình lưu ý: checkbox được thiết kế đi cùng nhãn (label), nhờ đó người dùng bấm vào chữ cũng chọn được — thao tác dễ hơn nhiều so với việc nhắm đúng ô vuông nhỏ.
<form>
<span>Ca học:</span>
<input type="checkbox" name="ca" value="sang" id="sang" checked><label for="sang">Sáng</label>
<input type="checkbox" name="ca" value="chieu" id="chieu"><label for="chieu">Chiều</label>
<input type="checkbox" name="ca" value="toi" id="toi"><label for="toi">Tối</label>
</form>
<script>
const f = document.querySelector("form");
const hienThi = () => console.log(new URLSearchParams(new FormData(f)).toString() || "(chưa chọn ca nào)");
hienThi();
document.querySelector("label[for=chieu]").click(); // bấm vào CHỮ "Chiều"
hienThi();
</script>Khác với radio, nhiều checkbox có thể cùng name và khi cùng chọn thì dữ liệu gửi đi có nhiều cặp trùng tên (ca=sang&ca=chieu). Checkbox không được chọn thì không gửi gì cả.
d. Select
Phần tử <select> định nghĩa một danh sách thả xuống (dropdown); bên trong khai báo các tùy chọn bằng <option>.
selected: tùy chọn được chọn sẵn.size: số giá trị nhìn thấy cùng lúc; phần còn lại phải cuộn.multiple: cho phép chọn nhiều hơn một giá trị.<optgroup label="…">: nhóm các lựa chọn để danh sách có cấu trúc hợp lý hơn.
<form>
<label for="course-select">Chọn khóa học:</label>
<select id="course-select" name="khoa">
<optgroup label="Khóa học lập trình">
<option>Python</option>
<option>Java</option>
<option selected>JavaScript</option>
</optgroup>
<optgroup label="Cơ sở dữ liệu">
<option>MySQL</option>
<option>SQL Server</option>
<option>MongoDB</option>
</optgroup>
</select>
<br><br>
<select name="cars" size="4" multiple>
<option value="volvo" selected>Volvo</option>
<option value="saab">Saab</option>
<option value="fiat" selected>Fiat</option>
<option value="audi">Audi</option>
</select>
</form>
<script>
const f = document.querySelector("form");
console.log(new URLSearchParams(new FormData(f)).toString());
</script>Dòng kết quả cho thấy: option không có value thì nội dung chữ của nó chính là giá trị gửi đi (khoa=JavaScript), còn select multiple gửi nhiều cặp cùng tên (cars=volvo&cars=fiat).
Giáo trình: "Phần tử
<select>kết hợp với<option>sẽ tạo ra một tùy chọn giống cả radio button và checkbox": không cómultiplethì chỉ chọn được một (như radio), cómultiplethì chọn được nhiều (như checkbox). Trên điện thoại,selectthường hiển thị dạng cửa sổ chọn, đôi khi cho vuốt để lựa chọn.
Bảng tổng hợp giúp bạn chọn công cụ lựa chọn phù hợp:
| Tình huống | Nên dùng | Vì sao |
|---|---|---|
| 2–5 phương án, chỉ chọn một | radio |
Thấy hết phương án, chọn nhanh |
| Nhiều phương án, chọn nhiều | checkbox |
Mỗi ô bật/tắt độc lập |
| Nhiều phương án (hàng chục), chọn một | select |
Tiết kiệm diện tích |
| Gợi ý nhưng vẫn cho gõ tự do | input + datalist |
Chọn nhanh hoặc nhập giá trị riêng |
| Danh sách dài có phân nhóm | select + optgroup |
Có cấu trúc, dễ tìm |
📝 Phòng thí nghiệm biểu mẫu
Chọn type + thuộc tính rồi bấm Gửi để xem dữ liệu đi đâu<form action="/save" method="get"> <label for="f">E-mail:</label> <input type="email" id="f" name="email" placeholder="[email protected]" required> <button type="submit">Gửi</button> </form>
Phòng thí nghiệm form cũng có phần radio/checkbox/select: hãy đổi lựa chọn và quan sát ô "dữ liệu được gửi đi". Giờ làm nhóm bài về lựa chọn:
1.5. Nhập thời gian
HTML có năm kiểu input dành riêng cho thời gian; tùy trình duyệt sẽ hiện bảng chọn ngày/giờ thay vì bắt người dùng tự gõ. Điều quan trọng với lập trình viên là định dạng giá trị mà chúng gửi đi, luôn theo chuẩn ISO nên dễ xử lý ở máy chủ:
type |
Cho phép chọn | Định dạng giá trị | Ví dụ |
|---|---|---|---|
date |
Ngày (ngày-tháng-năm) | YYYY-MM-DD |
2025-09-05 |
time |
Giờ (không múi giờ) | hh:mm |
14:30 |
datetime-local |
Ngày và giờ, không có múi giờ | YYYY-MM-DDThh:mm |
2025-09-05T14:30 |
month |
Tháng và năm | YYYY-MM |
2025-09 |
week |
Tuần và năm | YYYY-Www |
2025-W36 |
Giáo trình lần lượt trình bày: a. datetime-local (ví dụ "Ngày bắt đầu"), b. month, c. week, d. time. Chúng ta thử cả năm kiểu trong một biểu mẫu:
<form>
<p>Ngày bắt đầu: <input type="datetime-local" name="start" value="2025-09-05T14:30"></p>
<p>Tháng: <input type="month" name="thang" value="2025-09"></p>
<p>Chọn tuần: <input type="week" name="week" value="2025-W36"></p>
<p>Chọn giờ: <input type="time" name="time" value="14:30"></p>
<p>Ngày sinh: <input type="date" name="ngaysinh" value="2005-03-15"></p>
</form>
<script>
const f = document.querySelector("form");
for (const [ten, giatri] of new FormData(f)) console.log(ten + " = " + giatri);
</script>Dù người dùng thấy 05/09/2025 (tùy ngôn ngữ trình duyệt), dữ liệu gửi đi luôn là 2025-09-05. Hai thuộc tính min và max làm việc với các kiểu thời gian — bạn sẽ dùng ở mục thuộc tính, ví dụ giới hạn ngày sinh. Nếu giá trị gán không đúng định dạng (ví dụ value="5/9/2025"), trình duyệt bỏ qua và để trống ô nhập.
Lưu ý: Kiểu
datetime-localkhông có múi giờ. Nếu hệ thống của bạn phục vụ người dùng ở nhiều múi giờ, hãy lưu thêm múi giờ riêng; đừng giả định giờ đã chọn là giờ của máy chủ.
1.6. Nhập tập tin
Phần tử <input> với type="file" tạo ra trường cho phép người dùng chọn tập tin để tải lên máy chủ. Hai thuộc tính đi kèm quan trọng:
accept: giới hạn loại tập tin được chọn, ví dụaccept="image/*,.pdf"(mọi ảnh và các tệp.pdf);multiple: cho phép chọn nhiều tập tin một lần.
<form action="/upload" method="post" enctype="multipart/form-data">
<label for="f1">Chọn file:</label>
<input type="file" id="f1" name="myFile" accept="image/*,.pdf">
<br>
<label for="f2">Lựa chọn ảnh:</label>
<input type="file" id="f2" name="img" accept="image/*" multiple>
</form>
<script>
const a = document.getElementById("f1"), b = document.getElementById("f2");
console.log("accept:", a.accept, "· multiple:", a.multiple);
console.log("accept:", b.accept, "· multiple:", b.multiple);
console.log("Số tệp đã chọn:", a.files.length);
</script>Mở rộng: Muốn gửi tập tin thật lên máy chủ, thẻ
formphải dùngmethod="post"và thêmenctype="multipart/form-data"như ví dụ trên (giáo trình không đi sâu phần này vì nó thuộc về lập trình phía máy chủ). Thuộc tínhacceptchỉ là gợi ý cho hộp thoại chọn tệp; máy chủ vẫn phải kiểm tra lại loại tệp vì người dùng có thể vượt qua giới hạn này.
1.7. Nút bấm
Phần tử <input> với type="button" tạo ra một nút bấm; nút có thể bấm bằng chuột hoặc bằng phím tắt nếu khai báo thuộc tính accesskey. Phần tử <button> cũng tạo ra nút bấm, nhưng khác ở chỗ: button có nội dung bên trong (văn bản, hoặc cả hình ảnh), còn input type="button" là phần tử rỗng (chữ trên nút lấy từ value).
input type="button" |
button |
|
|---|---|---|
| Cú pháp | <input type="button" value="Chữ"> |
<button type="button">Chữ</button> |
| Có thẻ đóng | Không (phần tử rỗng) | Có |
| Nội dung bên trong | Không | Văn bản, ảnh, biểu tượng |
Hai nút có chức năng đặc biệt dành cho biểu mẫu:
type="reset": nút thiết lập lại, đưa các trường về giá trị ban đầu.type="submit": nút xác nhận, gửi dữ liệu đến máy chủ.
<form action="/submit.php" method="post">
<label for="ten">Tên:</label>
<input type="text" id="ten" name="ten" value="Lan">
<br><br>
<input type="button" value="Thêm vào yêu thích" accesskey="s">
<button type="button" onclick="alert('Hello World!')">Click Me!</button>
<input type="reset" value="Nhập lại">
<input type="submit" value="Đăng ký">
</form>
<script>
const f = document.querySelector("form");
f.addEventListener("submit", (e) => {
e.preventDefault();
console.log("Sẽ gửi tới:", f.getAttribute("action"), "bằng", f.method.toUpperCase());
console.log(new URLSearchParams(new FormData(f)).toString());
});
document.querySelector("button").click(); // bấm "Click Me!"
const o = document.getElementById("ten");
o.value = "Đã sửa";
f.reset(); // giống bấm "Nhập lại"
console.log("Sau reset:", o.value);
f.requestSubmit(); // giống bấm "Đăng ký"
</script>Bạn thấy: reset đưa ô tên về Lan (giá trị khai báo ban đầu), còn nút type="button" không làm gì nếu bạn chưa gắn mã JavaScript.
Cảnh báo: Trong
form, phần tử<button>không khai báotypethì mặc định làsubmit. Muốn nút chỉ để chạy JavaScript mà không gửi form, hãy viết rõtype="button".
Thuộc tính action, target, method của form
Thuộc tính action của form xác định hành động khi form được submit: thông thường, dữ liệu được gửi đến một trang trên máy chủ chứa kịch bản phía máy chủ (server-side script) để xử lý, ví dụ /submit.php. Nếu bỏ qua action, dữ liệu được gửi đến chính trang hiện tại.
Thuộc tính target của form tương tự phần tử a: giá trị mặc định _self nghĩa là kết quả hiển thị trong cửa sổ hiện tại; _blank mở kết quả trong tab mới (<form action="/submit.php" target="_blank">).
Thuộc tính method quy định phương thức HTTP dùng để gửi: GET hoặc POST. Mặc định là GET.
| GET | POST | |
|---|---|---|
| Dữ liệu nằm ở | Trên URL, dạng /submit.php?name1=value1&name2=value2 |
Trong phần thân của yêu cầu, không hiện trên URL |
| Giới hạn độ dài | Có (tùy trình duyệt/máy chủ, thường khoảng 2.000 ký tự) | Không bị URL giới hạn (giới hạn thực tế do cấu hình máy chủ) |
| Bookmark | Có thể đánh dấu trang | Không thể bookmark |
| Bộ nhớ đệm (cache) | Có thể dùng cache | Không dùng cache |
| Ký tự | Chỉ ký tự ASCII (liên quan đến URL) | Không bị hạn chế như URL |
| Dữ liệu nhạy cảm | Không bao giờ dùng | Luôn dùng |
| Phù hợp | Dữ liệu không nhạy cảm, như chuỗi truy vấn của Google | Gửi biểu mẫu phổ biến hơn |
Hãy tự quan sát dữ liệu GET xuất hiện trên URL như thế nào. Ví dụ dưới đây dùng đối tượng URL để dựng địa chỉ giống hệt trình duyệt sẽ làm khi gửi form bằng GET:
const form = { action: "https://example.com/submit.php", data: { name1: "value1", name2: "Lan Anh" } };
const url = new URL(form.action);
for (const [k, v] of Object.entries(form.data)) url.searchParams.append(k, v);
console.log("GET ->", url.pathname + url.search);
console.log("POST ->", url.pathname, "| dữ liệu nằm trong thân yêu cầu:", url.searchParams.toString());Dòng GET có phần ?name1=value1&name2=Lan+Anh ngay trên địa chỉ — ai nhìn thanh địa chỉ cũng đọc được; dòng POST thì địa chỉ "sạch". Giáo trình nhắc thêm: khi người dùng bấm refresh hoặc nút back sau khi gửi bằng POST, dữ liệu có thể bị gửi lần nữa, nên trình duyệt thường cảnh báo.
Thuộc tính name: không có tên thì không được gửi
Giáo trình nhấn mạnh: mỗi trường đầu vào cần có thuộc tính name; nếu bỏ qua name, dữ liệu đó sẽ không được gửi đi. Ví dụ gốc: biểu mẫu có hai ô nhưng chỉ ô "Last name" (có name) được gửi.
<form action="/action_page.php">
First name:<br>
<input type="text" value="Mickey"><br>
Last name:<br>
<input type="text" name="lastname" value="Mouse"><br><br>
<input type="submit" value="Submit">
</form>
<script>
const f = document.querySelector("form");
f.addEventListener("submit", (e) => {
e.preventDefault();
console.log("Dữ liệu gửi đi:", new URLSearchParams(new FormData(f)).toString());
});
f.requestSubmit();
</script>Chỉ có lastname=Mouse xuất hiện; ô "First name" biến mất khỏi dữ liệu. Đây là lỗi mà người mới học gặp rất thường xuyên: ô nhập hiển thị bình thường, người dùng gõ đầy đủ, nhưng máy chủ không nhận được gì.
Cảnh báo: Luôn dùng POST nếu dữ liệu chứa thông tin nhạy cảm hoặc cá nhân (mật khẩu, số điện thoại, email...). Dùng GET thì mật khẩu sẽ nằm trên URL, lưu trong lịch sử trình duyệt và nhật ký máy chủ.
1.8. Các phần tử biểu mẫu khác
Hidden — trường ẩn
Phần tử <input type="hidden"> gửi một dữ liệu tới server mà không hiển thị ra cho người dùng. Ví dụ muốn gửi mã số 23114306030131 từ máy người dùng lên server: <input type="hidden" name="userID" value="23114306030131">. Người dùng không thấy ô này trên trang, nhưng nó vẫn nằm trong dữ liệu gửi đi.
Cảnh báo: "Ẩn" chỉ là ẩn về giao diện; ai mở DevTools cũng xem và sửa được giá trị. Không bao giờ đặt mật khẩu hoặc dữ liệu cần bảo mật vào trường ẩn, và máy chủ không được tin tuyệt đối vào giá trị nhận về.
Fieldset và legend — nhóm các điều khiển
Phần tử <fieldset> dùng để nhóm dữ liệu liên quan trong một biểu mẫu; <legend> định nghĩa chú thích cho fieldset. Khi nhóm các điều khiển lại, việc điều khiển cả nhóm dễ dàng hơn: có thể vô hiệu hóa (disabled) hoặc ẩn/hiện (display) cả nhóm thay vì từng điều khiển.
<form action="/submit.php">
<fieldset>
<legend>Thông tin cá nhân:</legend>
<label for="firstname">Tên:</label><br>
<input type="text" id="firstname" name="firstname" value="Mickey"><br><br>
<label for="lastname">Họ:</label><br>
<input type="text" id="lastname" name="lastname" value="Mouse"><br><br>
<input type="submit" value="Gửi">
</fieldset>
</form>
<script>
const fs = document.querySelector("fieldset");
console.log("Chú thích nhóm:", fs.querySelector("legend").textContent);
fs.disabled = true;
const o = document.getElementById("firstname");
console.log("Khóa cả nhóm bằng 1 dòng -> ô Tên bị khóa?", o.matches(":disabled"));
console.log("Dữ liệu gửi đi:", new URLSearchParams(new FormData(document.querySelector("form"))).toString() || "(rỗng)");
</script>Một dòng fieldset.disabled = true đã khóa mọi điều khiển bên trong, và vì điều khiển bị khóa không được gửi đi nên dữ liệu gửi là rỗng.
Label — nhãn
Phần tử <label> thường khai báo cùng với các phần tử nhập. Giáo trình nêu ba lợi ích:
- Các phần tử có thể dùng nhãn gồm
input,meter,progress,select,textarea; - Nhãn sẽ được đọc to khi người dùng dùng trình đọc màn hình (screen reader) và chọn vào điều khiển — liên quan trực tiếp đến tính tiếp cận (accessibility);
- Nhãn giúp bấm vào các vùng nhỏ như radio button hay checkbox dễ dàng hơn.
Có hai cách gán nhãn: dùng thuộc tính for có giá trị trùng id của điều khiển, hoặc đặt điều khiển bên trong thẻ label.
<form action="/action_page.php">
<p>Ngôn ngữ yêu thích (cách 1: dùng for/id):</p>
<input type="radio" id="html" name="fav_language" value="HTML">
<label for="html">HTML</label><br>
<input type="radio" id="css" name="fav_language" value="CSS">
<label for="css">CSS</label><br>
<input type="radio" id="javascript" name="fav_language" value="JavaScript">
<label for="javascript">JavaScript</label><br><br>
<label>Cách 2: lồng trong nhãn <input type="checkbox" name="nho" value="1"></label><br><br>
<input type="submit" value="Submit">
</form>
<script>
const nhan = document.querySelector("label[for=css]");
console.log("Nhãn gắn với điều khiển có id:", nhan.control.id);
const nhan2 = document.querySelectorAll("label")[3];
console.log("Nhãn lồng nhau gắn với:", nhan2.control.name);
nhan.click(); // bấm vào chữ CSS
console.log("Radio CSS được chọn?", document.getElementById("css").checked);
</script>Bấm vào chữ "CSS" là radio tương ứng được chọn — đó chính là tác dụng của việc for trùng id.
Progress và meter
Phần tử <progress> không phải phần tử nhập; nó thể hiện tiến độ hoàn thành một tác vụ (thường phối hợp với JavaScript để cập nhật). Phần tử <meter> hiển thị tương tự nhưng dùng để biểu diễn một đại lượng đo lường (tốc độ, dung lượng, nhiệt độ...). Hai phần tử có mục đích khác nhau: tiến độ của một việc đang chạy (progress) và một giá trị trong một thang đo (meter).
<p><label for="file">Đang tải file:</label>
<progress id="file" value="32" max="100"> 32% </progress></p>
<p><label for="disk">Dung lượng ổ đĩa:</label>
<meter id="disk" min="0" max="100" low="30" high="80" optimum="20" value="65">65 GB</meter></p>
<script>
const p = document.getElementById("file");
console.log("progress:", p.value + "/" + p.max, "=", Math.round(p.position * 100) + "%");
p.value = 75;
console.log("Sau khi cập nhật:", p.value + "%");
console.log("meter:", document.getElementById("disk").value, "GB");
</script>Output
Phần tử <output> được dùng để hiển thị kết quả đầu ra, ví dụ thực hiện một phép tính rồi hiện kết quả. Ví dụ gốc của giáo trình: kéo thanh range hoặc sửa số trong ô number, kết quả cộng hiện ngay lập tức nhờ sự kiện oninput của form.
<form oninput="x.value=parseInt(a.value) + parseInt(b.value)">
0 <input type="range" id="a" name="a" value="50"> 100 +
<input type="number" id="b" name="b" value="50"> =
<output name="x"></output>
</form>Trong thuộc tính oninput, bạn có thể gọi trực tiếp các điều khiển bằng name của chúng (a, b, x). Bản chạy thử dưới đây làm cùng việc nhưng dùng addEventListener (cách bạn sẽ học kỹ ở buổi 12) để bạn quan sát được kết quả:
<form id="tong">
0 <input type="range" id="a" name="a" value="50"> 100 +
<input type="number" id="b" name="b" value="50"> =
<output name="x">100</output>
</form>
<script>
const f = document.getElementById("tong");
const tinh = () => { f.elements.x.value = parseInt(f.elements.a.value) + parseInt(f.elements.b.value); };
f.addEventListener("input", tinh);
f.elements.b.value = 25;
f.dispatchEvent(new Event("input")); // mô phỏng người dùng nhập
console.log("50 + 25 =", f.elements.x.value);
</script>Ví dụ thực tế: Trang đặt vé có ô "Số lượng" (
number), ô "Đơn giá" (ẩn bằnghidden) vàoutput"Thành tiền" tự cập nhật; thanhprogresshiển thị bước "2/4" của quy trình đặt vé.
Vừa học xong tập tin, thời gian, trường ẩn và nhóm fieldset, bạn có đủ kiến thức cho nhóm bài sau:
1.9. Các thuộc tính quan trọng khác
Các phần tử nhập có rất nhiều thuộc tính. Phần này tóm tắt cách dùng, theo thứ tự của giáo trình. Bảng tổng hợp trước, ví dụ chạy thử sau:
| Thuộc tính | Tác dụng | Dùng với |
|---|---|---|
value |
Giá trị ban đầu; cũng là giá trị gửi đi khi người dùng không nhập gì | hầu hết điều khiển |
readonly |
Chỉ đọc (không sửa được) nhưng vẫn được gửi đi | text, textarea... |
disabled |
Vô hiệu hóa; dữ liệu không được gửi đi | mọi điều khiển |
size |
Độ rộng trường theo số ký tự hiển thị | text, select... |
maxlength |
Chiều dài tối đa; vượt quá thì trình duyệt không cho gõ tiếp, không báo lỗi | text, textarea... |
autocomplete |
Bật/tắt tự động điền (on/off) |
form, text, search, url, tel, email, password, date, month, week, time, datetime-local, number, range, color |
novalidate |
Thuộc tính của form: không kiểm tra dữ liệu khi gửi |
form |
autofocus |
Tự đặt con trỏ vào trường khi trang tải | mọi điều khiển |
height/width |
Chiều cao/chiều rộng của ảnh | input type="image" |
list |
Trỏ tới id của datalist |
input |
min/max |
Giá trị tối thiểu/tối đa | number, range, date, datetime-local, month, time, week |
multiple |
Cho nhập/chọn nhiều giá trị | email, file |
pattern |
Biểu thức chính quy mà giá trị phải khớp (kèm title mô tả) |
text, search, url, tel, email |
placeholder |
Gợi ý mờ, mất đi khi bắt đầu nhập | text, search, url, tel, email, password |
required |
Trường bắt buộc điền | hầu hết điều khiển |
step |
Khoảng nhảy cho điều khiển dạng số | number, range, date... |
📝 Phòng thí nghiệm biểu mẫu
Chọn type + thuộc tính rồi bấm Gửi để xem dữ liệu đi đâu<form action="/save" method="get"> <label for="f">E-mail:</label> <input type="email" id="f" name="email" placeholder="[email protected]" required> <button type="submit">Gửi</button> </form>
Hãy dùng phòng thí nghiệm form để bật/tắt required, readonly, disabled... rồi nhìn dữ liệu gửi đi. Ví dụ dưới đây gom nhiều thuộc tính cùng lúc và in ra trạng thái kiểm tra hợp lệ:
<form action="/action_page.php" id="dk">
<p>Tên (bắt buộc): <input type="text" id="ten" name="username" required placeholder="Nguyễn Văn A" autofocus></p>
<p>Biệt danh (tối đa 10 ký tự): <input type="text" name="nick" maxlength="10"></p>
<p>Mã quốc gia (3 chữ cái): <input type="text" id="mq" name="country_code" pattern="[A-Za-z]{3}" title="Mã quốc gia gồm 3 chữ cái"></p>
<p>Ngày sinh (trước 1980): <input type="date" id="ns" name="bday" max="1979-12-31"></p>
<p>Số lượng (1 đến 5, bước 1): <input type="number" id="sl" name="quantity" min="1" max="5"></p>
<p>Chỉ đọc: <input type="text" name="ro" value="John" readonly></p>
<p>Bị khóa: <input type="text" name="dis" value="John" disabled></p>
<input type="submit" value="Gửi">
</form>
<script>
const f = document.getElementById("dk");
const bao = (ten) => {
const o = document.getElementById(ten);
return ten + " hợp lệ? " + o.checkValidity();
};
console.log("Lúc đầu, form hợp lệ?", f.checkValidity(), "(thiếu trường bắt buộc)");
document.getElementById("ten").value = "Lan";
document.getElementById("mq").value = "VN";
console.log(bao("mq"), "(VN chỉ có 2 chữ cái)");
document.getElementById("mq").value = "VNM";
document.getElementById("ns").value = "1990-05-01";
console.log(bao("ns"), "(1990 sau mốc max 1979-12-31)");
document.getElementById("ns").value = "1975-05-01";
document.getElementById("sl").value = 6;
console.log(bao("sl"), "(6 lớn hơn max 5)");
document.getElementById("sl").value = 3;
console.log("Cuối cùng, form hợp lệ?", f.checkValidity());
console.log("Dữ liệu gửi:", new URLSearchParams(new FormData(f)).toString());
</script>Hai điểm cần đối chiếu trong kết quả: trường readonly (ro=John) có trong dữ liệu gửi, còn trường disabled (dis) không có. Đây là khác biệt thường được hỏi trong phỏng vấn.
Giải thích từng thuộc tính
valuexác định giá trị ban đầu, cũng là giá trị gửi đi khi người dùng không nhập gì.readonlykhiến trường không thể thay đổi nhưng người dùng vẫn chọn/sao chép được;disabledvô hiệu hóa hẳn, dữ liệu của trường không được gửi đi khi submit.sizelà độ rộng trường theo số ký tự hiển thị;maxlengthlà chiều dài tối đa — vượt quá, trình duyệt không cho gõ tiếp chứ không đưa ra thông báo nào.autocomplete: khi bật, trình duyệt tự hoàn thành giá trị dựa trên những gì người dùng đã nhập trước đây. Có thể đặtoncho form vàoffcho các trường cụ thể, hoặc ngược lại, ví dụE-mail: <input type="email" name="email" autocomplete="off">. Một số trình duyệt cần người dùng kích hoạt chức năng này.novalidate(của<form>): dữ liệu form không cần kiểm tra khi gửi, ví dụ<form action="/action_page.php" novalidate>; hữu ích khi bạn muốn tự kiểm tra bằng JavaScript.autofocus: con trỏ tự đặt vào trường khi tải trang; mỗi trang chỉ nên có một trườngautofocus.height/width: kích thước ảnh củainput type="image", ví dụ<input type="image" src="img_submit.gif" alt="Submit" width="48" height="48">.list: chỉ đến phần tửdatalistchứa các lựa chọn định sẵn (xem mục datalist).min/max: giới hạn tối thiểu/tối đa; làm việc vớinumber,range,date,datetime-local,month,time,week. Ví dụ: ngày trước năm 1980 làmax="1979-12-31", ngày sau năm 2000 làmin="2000-01-02".multiple: cho phép nhập nhiều hơn một giá trị; làm việc vớiemailvàfile.pattern: biểu thức chính quy (regular expression) mà giá trị phải khớp, thường dùng vớitext,search,url,tel,email; dùng thuộc tínhtitleđể mô tả, giúp người dùng hình dung nên nhập thế nào, ví dụpattern="[A-Za-z]{3}".placeholder: gợi ý hiển thị trong ô nhập, mất đi khi người dùng bắt đầu nhập.required: trường phải được điền đầy đủ.step: khoảng nhảy cho điều khiển dạng số; nếustep="3"các số hợp lệ là -3, 0, 3, 6...
<form novalidate id="nv">
E-mail: <input type="email" name="user_email" value="khong-hop-le">
<input type="submit" value="Gửi (novalidate)">
</form>
<form id="cv">
E-mail: <input type="email" name="user_email" value="khong-hop-le">
<input type="submit" value="Gửi (có kiểm tra)">
</form>
<script>
const nv = document.getElementById("nv"), cv = document.getElementById("cv");
let guiNV = 0, guiCV = 0;
nv.addEventListener("submit", (e) => { e.preventDefault(); guiNV++; });
cv.addEventListener("submit", (e) => { e.preventDefault(); guiCV++; });
nv.requestSubmit();
cv.requestSubmit();
console.log("Form có novalidate: đã gửi", guiNV, "lần");
console.log("Form có kiểm tra: đã gửi", guiCV, "lần (bị chặn vì email sai)");
</script>Form có novalidate vẫn gửi dù email sai; form bình thường bị trình duyệt chặn lại và hiện thông báo. Cần biết rằng kiểm tra phía trình duyệt không thay thế kiểm tra phía máy chủ: người dùng có thể tắt hoặc vượt qua nó.
Mẹo: Khi dùng
pattern, luôn thêmtitleđể lời nhắc lỗi của trình duyệt có nghĩa ("Mã quốc gia gồm 3 chữ cái" thay vì "Please match the requested format").
Nhóm bài cuối của phần biểu mẫu kết hợp nhiều thuộc tính kiểm tra và các điều khiển đã học:
2. Nội dung đa phương tiện
Giáo trình: Chương 2 — mục "Nội dung đa phương tiện" (Hình ảnh Bitmap; Hình ảnh Vector; Âm thanh và phim; Tập tin PDF).
Một trang web chỉ có chữ thì khô khan. Nội dung đa phương tiện (multimedia) gồm ảnh, đồ họa, âm thanh, phim và tài liệu giúp trang sinh động và truyền tải thông tin hiệu quả hơn. Mỗi loại nội dung có phần tử HTML riêng; chọn đúng phần tử là bước đầu của một trang web nhanh, đẹp, dễ tiếp cận.
<img src="a.png" alt="…">Phóng to thấy các ô vuông răng cưa; dung lượng tăng theo số điểm ảnh.
- ▪ JPEG: ảnh chụp, nén mất dữ liệu
- ▪ PNG: nền trong suốt
- ▪ GIF: 256 màu, ảnh động
- ▪ WebP: hiện đại, nhẹ
<circle cx cy r …>Phóng to tùy ý vẫn mịn vì ảnh là các hình (circle, rect, polygon, text…) mô tả bằng mã XML.
<svg width="100" height="100"><circle cx="50" cy="50" r="40" /></svg>
<audio controls><source src="horse.mp3" type="audio/mpeg">Trình duyệt không hỗ trợ audio.</audio>
<video width="320" controls poster="bia.jpg"><source src="movie.mp4" type="video/mp4"><track kind="captions" srclang="vi" src="vi.vtt"></video>
movie.mp4trình duyệt hỗ trợ → phát
movie.oggchỉ dùng nếu mp4 không chạy được
Văn bản dự phònghiện khi không thẻ nào dùng được
controlshiện thanh điều khiểnautoplaytự chạylooplặp lạimutedtắt tiếngpreloadtải trướcposterảnh bìa trước khi chạywidth / heightkích thước (pixel)srcđịa chỉ tệp<img><svg> hoặc <img src="*.svg"><audio><video><iframe><embed>Nhúng PDF bằng <embed type="application/pdf">; thuộc tính alt của img là bắt buộc để trình đọc màn hình đọc được.
Ảnh bitmap lưu theo điểm ảnh nên phóng to sẽ vỡ hạt, ảnh vector SVG lưu bằng mã lệnh nên luôn sắc nét; âm thanh và phim nhúng bằng audio, video với nhiều source dự phòng.
Sơ đồ trên đối chiếu các định dạng: bitmap (lưu từng điểm ảnh — ảnh chụp, ảnh nền), vector/SVG (lưu hình dạng bằng công thức — logo, biểu tượng), âm thanh và video. Hãy nhớ nguyên tắc: ảnh chụp đời thực dùng bitmap; logo, biểu tượng, sơ đồ đơn giản dùng vector.
2.1. Hình ảnh Bitmap
Hình ảnh bitmap là hình ảnh lưu trữ dữ liệu theo dạng điểm ảnh (pixel). Ảnh được đưa vào tài liệu HTML bằng phần tử <img>. Phần tử img chỉ chứa các thuộc tính và không có thẻ đóng; thuộc tính src xác định URL (địa chỉ Web) của hình ảnh.
<img src="url">Ảnh có thể nằm trong thư mục khác với trang. Nếu không chỉ định, trình duyệt tìm ảnh cùng thư mục với trang; thông thường ảnh được cất trong thư mục con nên cần ghi rõ tên thư mục trong src (ví dụ /images/html5.gif). Cũng có thể dùng ảnh trên máy chủ khác bằng URL đầy đủ (ví dụ https://www.w3schools.com/images/w3schools_green.jpg). Ví dụ dưới đây dùng một ảnh PNG cực nhỏ (10×6 pixel) nhúng sẵn bằng data: URI để bạn thấy rõ "bitmap là một lưới điểm ảnh":
<style>
.pixel { image-rendering: pixelated; }
</style>
<p>Ảnh gốc 10×6 pixel, phóng to lên 300×180:</p>
<img class="pixel" id="anh" alt="Ảnh bitmap 10 x 6 điểm ảnh sắc màu"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAIAAAB1kpiRAAAAk0lEQVR42g3LEQOGQAwG4H7Jx/FxfJwkx+OTZDyeJPE4ScbHkyTeL3np6/Fn+lEiS6WBrJ0M2U4SZB9ESJkQc+EHsTS+EavwgaCLdwS/vE0/TXipOuC1q8HbqQLvQwn+bZuLPbCl2Q1bxQ4YXbbD+LVve0JL9QGt3Q3aThdoH07Qb/Nc4gEvLW7wKnGA6YodzG9sf2RcbLKQmUqgAAAAAElFTkSuQmCC"
width="300" height="180">
<script>
const a = document.getElementById("anh");
console.log("alt:", a.alt);
console.log("Kích thước đặt trong HTML:", a.getAttribute("width") + "x" + a.getAttribute("height"), "pixel");
console.log("Thẻ img có nội dung bên trong?", a.childNodes.length > 0);
</script>Phóng to bitmap lên nhiều lần thì thấy rõ các ô vuông (điểm ảnh): bitmap bị vỡ hạt khi phóng đại. Đó là nhược điểm lớn nhất của bitmap so với vector.
Thuộc tính alt, width, height
altcung cấp văn bản thay thế cho hình ảnh. Nếu vì lý do nào đó không xem được ảnh (kết nối chậm, lỗi ởsrc, hoặc người dùng dùng trình đọc màn hình) thìaltđược dùng. Thuộc tínhaltlà bắt buộc: một trang web sẽ không được công nhận là "chuẩn" nếu các thẻimgthiếu thuộc tính này.width,heightxác định chiều rộng, chiều cao ảnh, luôn dùng đơn vị pixel. Cũng có thể dùngstyle="width:500px;height:600px;". Nếu không xác định trước kích thước, trang web sẽ có hiệu ứng tải "nhấp nháy" khiến người dùng khó chịu (vì trình duyệt chưa biết chừa chỗ bao nhiêu cho ảnh).
<p>Ảnh bị lỗi đường dẫn — trình duyệt hiện văn bản alt:</p>
<img id="hong" src="khong-co-anh.jpg" alt="Hoa ở Chania" width="300" height="60">
<script>
const a = document.getElementById("hong");
console.log("src:", a.getAttribute("src"));
console.log("alt (văn bản thay thế):", a.alt);
console.log("Có alt không?", a.hasAttribute("alt"));
</script>Lưu ý:
altnên mô tả nội dung/ý nghĩa của ảnh ("Sinh viên đang thực hành lập trình"), không viết kiểu "ảnh 1" hay "hình". Với ảnh trang trí thuần túy có thể đểalt=""(rỗng) để trình đọc màn hình bỏ qua — nhưng vẫn phải có thuộc tính.
Ảnh làm liên kết và ảnh bản đồ (map)
Để dùng hình ảnh làm liên kết, đặt phần tử <img> bên trong phần tử <a>. Với một hình ảnh phức tạp có thể gắn nhiều liên kết vào các vị trí khác nhau bằng phần tử <map> (bản đồ ảnh): img khai báo usemap="#tên", map có name trùng tên, bên trong là các <area> với shape (rect, circle, poly), coords (tọa độ), href, alt.
<p>Ảnh làm liên kết:
<a href="tutorial.html" id="lk"><img alt="Hướng dẫn HTML" style="width:42px; height:42px; border:0"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAIAAAB1kpiRAAAAk0lEQVR42g3LEQOGQAwG4H7Jx/FxfJwkx+OTZDyeJPE4ScbHkyTeL3np6/Fn+lEiS6WBrJ0M2U4SZB9ESJkQc+EHsTS+EavwgaCLdwS/vE0/TXipOuC1q8HbqQLvQwn+bZuLPbCl2Q1bxQ4YXbbD+LVve0JL9QGt3Q3aThdoH07Qb/Nc4gEvLW7wKnGA6YodzG9sf2RcbLKQmUqgAAAAAElFTkSuQmCC"></a></p>
<h3>Bản đồ</h3>
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAIAAAB1kpiRAAAAk0lEQVR42g3LEQOGQAwG4H7Jx/FxfJwkx+OTZDyeJPE4ScbHkyTeL3np6/Fn+lEiS6WBrJ0M2U4SZB9ESJkQc+EHsTS+EavwgaCLdwS/vE0/TXipOuC1q8HbqQLvQwn+bZuLPbCl2Q1bxQ4YXbbD+LVve0JL9QGt3Q3aThdoH07Qb/Nc4gEvLW7wKnGA6YodzG9sf2RcbLKQmUqgAAAAAElFTkSuQmCC" alt="Bản đồ" usemap="#map" width="300" height="180" style="image-rendering:pixelated">
<map name="map">
<area shape="rect" coords="0,0,100,100" href="page1.html" alt="Khu vực 1">
<area shape="circle" coords="200,100,50" href="page2.html" alt="Khu vực 2">
<area shape="poly" coords="250,10,290,60,260,120,230,60" href="page3.html" alt="Khu vực 3">
</map>
<script>
console.log("Ảnh nằm trong liên kết tới:", document.getElementById("lk").getAttribute("href"));
document.querySelectorAll("area").forEach((a) =>
console.log(a.getAttribute("shape").padEnd(6), "->", a.getAttribute("href")));
</script>Đoạn mã dùng thẻ map để khai báo ba vùng: một hình chữ nhật, một hình tròn, một đa giác. Người đọc nhấp chuột vào ba vùng này sẽ được chuyển tới các trang khác nhau. Lưu ý: coords của rect là x1,y1,x2,y2, của circle là tâm x, tâm y, bán kính, của poly là chuỗi các cặp tọa độ.
2.2. Hình ảnh Vector
Ảnh SVG (Scalable Vector Graphics) dùng để thể hiện đồ họa vector hai chiều và rất phổ biến ngày nay. Ưu điểm: thu phóng tùy ý mà không giảm chất lượng. SVG tạo ra từ mã lệnh hoặc từ phần mềm vector như Adobe Illustrator, CorelDRAW. SVG dùng cú pháp của XML và được khai báo trong tài liệu HTML bằng thẻ <svg>; bên trong có các phần tử để vẽ đường, đa giác, vòng tròn, văn bản, ảnh.
Các phần tử vẽ hình cơ bản (giáo trình lần lượt trình bày):
| Phần tử | Vẽ gì | Thuộc tính chính |
|---|---|---|
<circle> |
Hình tròn | cx, cy (tâm), r (bán kính), stroke, stroke-width, fill |
<rect> |
Hình chữ nhật | x, y, width, height, rx, ry (bo góc), style |
<polygon> |
Đa giác | points (danh sách tọa độ), fill-rule |
<text> |
Chữ | x, y, font-family, font-size, fill |
<ellipse> |
Hình ellipse (bầu dục) | cx, cy, rx, ry |
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
</svg>
<svg width="400" height="60">
<rect width="400" height="60" style="fill:rgb(0,0,255);stroke-width:10;stroke:rgb(0,0,0)" />
</svg>
<svg width="200" height="180">
<rect x="25" y="10" rx="20" ry="20" width="150" height="150"
style="fill:red;stroke:black;stroke-width:5;opacity:1" />
</svg>
<svg width="200" height="200">
<polygon points="100,10 10,78 40,198 160,198 190,78"
style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;" />
</svg>
<svg width="200" height="100" xmlns="http://www.w3.org/2000/svg">
<text x="20" y="50" font-family="Arial" font-size="20" fill="blue">Hello SVG!</text>
</svg>
<svg width="200" height="100" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="100" cy="50" rx="80" ry="30" fill="yellow" />
</svg>
<script>
const ten = ["circle", "rect", "polygon", "text", "ellipse"];
ten.forEach((t) => console.log(t.padEnd(8), "x", document.querySelectorAll(t).length));
</script>Các phần tử này đặt cùng nhau trong một phần tử svg để tạo ra một hình ảnh. Ví dụ cuối của giáo trình ghép rect và polygon thành lá cờ Việt Nam; để ý thuộc tính viewBox, cho phép vẽ trên hệ tọa độ 30×20 rồi tự co giãn ra kích thước 370×200:
<svg width="370" height="200" viewBox="0 0 30 20" xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect width="30" height="20" fill="#da251d" />
<polygon points="15,4 11.47,14.85 20.71,8.15 9.29,8.15 18.53,14.85" fill="#ff0" />
</svg>
<script>
const s = document.querySelector("svg");
console.log("Khung hiển thị:", s.getAttribute("width") + "x" + s.getAttribute("height"));
console.log("Hệ tọa độ (viewBox):", s.getAttribute("viewBox"));
console.log("Số phần tử con:", s.children.length);
</script>Đồ họa vector rất quan trọng trong thiết kế web hiện đại. Thông thường biểu tượng và logo của website được thiết kế ở dạng vector và xuất ra tệp .svg hoặc mã SVG để nhúng. Đồ họa vector có thiết kế hiện đại, dung lượng nhỏ, không bị vỡ khi phóng to. Khi đưa ảnh SVG vào HTML có hai cách: dùng tệp ảnh (<img src="logo.svg" alt="Logo">) hoặc đặt mã ảnh trong thẻ <svg> (như trên) — cách thứ hai cho phép dùng CSS/JavaScript tác động lên từng phần của ảnh.
Lưu ý: Cần phân biệt SVG và Canvas: SVG là ngôn ngữ mô tả đồ họa bằng XML (mỗi hình là một phần tử trong cây DOM), còn Canvas vẽ đồ họa bằng JavaScript (kết quả là một mặt phẳng điểm ảnh). Canvas bạn sẽ gặp ở buổi 13.
Mẹo: Cùng một biểu tượng, bitmap phải xuất nhiều kích cỡ (16, 32, 180, 512 px) còn SVG chỉ cần một tệp. Đây là lý do các bộ biểu tượng giao diện hiện nay (như icon của Bootstrap) đều phát hành dạng SVG.
2.3. Âm thanh và phim
Website có thể bổ sung âm thanh để tăng sinh động. Các tệp âm thanh nhúng kèm phần điều khiển bằng thẻ <audio>. Phần tử <source> bên trong dùng để khai báo các nguồn nội dung sẽ được trình chiếu — trình duyệt thử lần lượt từng nguồn cho tới khi tìm được định dạng mình hỗ trợ. Đoạn chữ nằm giữa thẻ mở và thẻ đóng là nội dung dự phòng cho trình duyệt cũ.
Một đoạn phim đưa vào HTML bằng thẻ <video>, cũng khai báo nguồn bằng source:
<audio controls id="am">
<source src="horse.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
<br>
<video width="320" height="240" controls id="ph" poster="">
<source src="movie.mp4" type="video/mp4">
<source src="movie.ogg" type="video/ogg">
Your browser does not support the video tag.
</video>
<script>
// Tệp mẫu không có thật: ta chỉ kiểm tra cấu trúc khai báo
const am = document.getElementById("am"), ph = document.getElementById("ph");
console.log("audio có điều khiển (controls)?", am.hasAttribute("controls"));
console.log("Nguồn audio:", [...am.querySelectorAll("source")].map((s) => s.getAttribute("type")).join(", "));
console.log("Nguồn video:", [...ph.querySelectorAll("source")].map((s) => s.getAttribute("type")).join(", "));
console.log("Kích thước video:", ph.getAttribute("width") + "x" + ph.getAttribute("height"));
</script>Giáo trình lưu ý thêm: có thể đặt nhiều loại tệp đa phương tiện trong src của video như .swf (Macromedia Flash), .wmv (Windows Media Video), .mov (Apple QuickTime Movie), .mpeg (Moving Pictures Expert Group). Tuy nhiên trên web hiện nay nên ưu tiên các định dạng được trình duyệt hỗ trợ rộng rãi như MP4 (video), MP3 (âm thanh), cùng .ogg; Flash đã bị loại bỏ khỏi trình duyệt.
Các thuộc tính điều khiển nội dung đa phương tiện (Bảng 2‑3 của giáo trình):
| Thuộc tính | Mô tả |
|---|---|
autoplay |
Bật/tắt tính năng tự chạy |
loop |
Phát đi phát lại, hoặc chạy duy nhất một lần |
controls |
Hiển thị/tắt chức năng điều khiển video |
width/height |
Độ rộng/chiều cao của đối tượng (pixel) |
src |
Địa chỉ URL của đối tượng được nhúng |
preload |
Video được tải trước khi người dùng bấm chạy |
muted |
Tắt/bật tiếng của video |
Cảnh báo: Hầu hết trình duyệt chặn tự phát có tiếng:
autoplaythường chỉ chạy khi video được tắt tiếng (muted). Trang tự phát nhạc cũng khiến người dùng khó chịu và vi phạm nguyên tắc tính tiếp cận — hãy luôn đểcontrols.
Phụ đề với track và ảnh bìa với poster
Với video và âm thanh có phụ đề, khai báo tệp phụ đề bằng phần tử <track> (src tệp .vtt, kind="captions", srclang mã ngôn ngữ, label tên hiển thị). Thuộc tính poster của video xác định hình ảnh hiển thị khi video chưa được trình chiếu.
<video width="320" height="240" controls poster="bia.jpg">
<source src="movie.mp4" type="video/mp4">
<track src="captions_vtt.vtt" kind="captions" srclang="en" label="English">
<track src="captions_vtt_fr.vtt" kind="captions" srclang="fr" label="French">
Trình duyệt của bạn không hỗ trợ thẻ video.
</video>Nhúng video từ mạng bằng iframe
Tài liệu HTML cũng có thể phát video hoặc âm thanh từ các nguồn trên mạng, dùng chính các trang đó để phát. Đây là kỹ thuật dùng phần tử <iframe> để nhúng website khác vào tài liệu HTML. Ví dụ phát video YouTube (thay id bằng mã video thật; mã này cần mạng nên không chạy thử trong bài):
<iframe width="560" height="315" src="https://www.youtube.com/embed/id" title="YouTube"
frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe>Thuộc tính title của iframe quan trọng cho trình đọc màn hình; allowfullscreen cho phép xem toàn màn hình.
2.4. Tập tin PDF
Nhúng một tài liệu PDF trong HTML bằng thẻ <embed>:
<embed src="https://minh-blogs.web.app/files/article.pdf" width="500" height="375" type="application/pdf">Giáo trình lưu ý: <embed> cũng dùng để nhúng tài liệu HTML, video, âm thanh và cả ứng dụng khác như applet, nhưng điều này không còn phổ biến do nhiều hạn chế. Vì vậy: hiển thị hình ảnh nên dùng <img>; video và âm thanh dùng <video> và <audio>; hiển thị file HTML nên dùng <iframe>.
| Cần hiển thị | Phần tử nên dùng |
|---|---|
| Ảnh (bitmap, SVG tệp) | img |
| Đồ họa vector viết trực tiếp | svg |
| Âm thanh | audio |
| Video | video (+ source, track) |
| Trang HTML / video YouTube | iframe |
| Tài liệu PDF | embed |
Mở rộng: Nếu chỉ cần cho người dùng tải về hoặc mở PDF ở tab mới, dùng liên kết (
<a href="tai-lieu.pdf" target="_blank">) thường tốt hơnembedvì hoạt động ổn định trên điện thoại.
Bạn đã học xong hai phần lý thuyết. Phần tiếp theo là các bài thực hành của chương 2 trong giáo trình.
Thực hành theo giáo trình
Giáo trình: Chương 2 — mục "Thực hành" (Bài 2.1. Tạo form đăng ký; Bài 2.2. Danh mục sản phẩm; Bài 2.3. Tạo favicon) và mục "Bài tập tự thực hành" (Bài 2.4. Tạo giao diện với ảnh SVG; Bài 2.5. Tạo form).
Bài 2.1. Tạo form đăng ký
Mục tiêu: tạo một form đăng ký thu thập thông tin người dùng. Các bước theo giáo trình:
- Khởi tạo
formvớiaction(đường dẫn tới tài nguyên máy chủ nhận dữ liệu, ví dụsubmit.php) vàmethod="post". - Họ tên:
label+input type="text",forcủalabeltrùngidcủainput. - Email:
input type="email"(tự kiểm tra cú pháp). - Ngày:
input type="date". - Giới tính: các
input type="radio"cùng giá trịnameđể chỉ chọn một. - Quốc gia:
select+option. - Tiểu sử:
textareanhiều dòng. - Điều khoản:
input type="checkbox"yêu cầu đồng ý trước khi gửi. - Nút gửi:
input type="submit".
Bản chạy thử dưới đây làm đủ chín bước. Hãy thử bấm "Đăng ký" khi để trống họ tên hoặc chưa tích điều khoản:
<form action="submit.php" method="post" id="dk">
<p><label for="hoten">Họ và tên:</label><br>
<input type="text" id="hoten" name="hoten" required></p>
<p><label for="email">Email:</label><br>
<input type="email" id="email" name="email" required></p>
<p><label for="ngaysinh">Ngày sinh:</label><br>
<input type="date" id="ngaysinh" name="ngaysinh"></p>
<p>Giới tính:
<input type="radio" id="nam" name="gioitinh" value="nam"><label for="nam">Nam</label>
<input type="radio" id="nu" name="gioitinh" value="nu"><label for="nu">Nữ</label></p>
<p><label for="quocgia">Quốc gia:</label>
<select id="quocgia" name="quocgia">
<option value="vn">Việt Nam</option>
<option value="us">Hoa Kỳ</option>
<option value="jp">Nhật Bản</option>
</select></p>
<p><label for="tieusu">Tiểu sử:</label><br>
<textarea id="tieusu" name="tieusu" rows="3" cols="30"></textarea></p>
<p><input type="checkbox" id="dieukhoan" name="dieukhoan" required>
<label for="dieukhoan">Tôi đồng ý với điều khoản</label></p>
<input type="submit" value="Đăng ký">
</form>
<script>
const f = document.getElementById("dk");
f.addEventListener("submit", (e) => {
e.preventDefault();
console.log("Gửi bằng", f.method.toUpperCase(), "tới", f.getAttribute("action"));
for (const [k, v] of new FormData(f)) console.log(" ", k, "=", v);
});
console.log("Chưa điền gì — hợp lệ?", f.checkValidity());
document.getElementById("hoten").value = "Trần Thu Hà";
document.getElementById("email").value = "[email protected]";
document.getElementById("ngaysinh").value = "2005-11-20";
document.getElementById("nu").checked = true;
document.getElementById("quocgia").value = "jp";
document.getElementById("tieusu").value = "Yêu thích lập trình web.";
document.getElementById("dieukhoan").checked = true;
console.log("Điền đủ — hợp lệ?", f.checkValidity());
f.requestSubmit();
</script>Mẹo: Khi dựng một form lớn, hãy làm theo thứ tự "khung → điều khiển → nhãn → thuộc tính kiểm tra". Bạn sẽ tạo được cấu trúc đúng trước rồi mới tinh chỉnh; còn việc trình bày đẹp (căn lề, màu sắc) để dành cho CSS ở buổi 7.
Bài 2.2. Danh mục sản phẩm
Mục tiêu: tạo trang web hiển thị danh mục sản phẩm cùng danh sách sản phẩm tương tự có liên kết nhanh.
Danh mục sản phẩm: h1 "Danh mục sản phẩm"; table có viền (border="1") gồm ba cột "Sản phẩm", "Giá", "Tình trạng"; th là tiêu đề từng cột; mỗi tr là một sản phẩm; td chứa tên, giá, tình trạng.
Sản phẩm tương tự: h2 "Sản phẩm tương tự"; nav (đánh dấu phần chứa liên kết tới các phần khác của trang hoặc trang khác) và bên trong dùng ul, li, a.
<h1>Danh mục sản phẩm</h1>
<table border="1" id="sp">
<tr><th>Sản phẩm</th><th>Giá</th><th>Tình trạng</th></tr>
<tr><td>Laptop Aspire 5</td><td>15.990.000đ</td><td>Còn hàng</td></tr>
<tr><td>Chuột không dây</td><td>199.000đ</td><td>Còn hàng</td></tr>
<tr><td>Bàn phím cơ</td><td>890.000đ</td><td>Hết hàng</td></tr>
</table>
<h2>Sản phẩm tương tự</h2>
<nav>
<ul>
<li><a href="laptop-gaming.html">Laptop gaming</a></li>
<li><a href="laptop-van-phong.html">Laptop văn phòng</a></li>
<li><a href="phu-kien.html">Phụ kiện máy tính</a></li>
</ul>
</nav>
<script>
const hang = document.querySelectorAll("#sp tr");
console.log("Số dòng dữ liệu:", hang.length - 1);
console.log("Tiêu đề cột:", [...hang[0].children].map((c) => c.textContent).join(" | "));
console.log("Số liên kết trong nav:", document.querySelectorAll("nav a").length);
</script>Bài này ôn lại bảng, danh sách, liên kết và phần tử ngữ nghĩa nav của buổi 4, đặt cạnh những gì bạn vừa học về biểu mẫu.
Bài 2.3. Tạo favicon
Mục tiêu: tạo ảnh biểu tượng (favicon) cho website, bước này liên quan đến phần head bạn học ở buổi 3. Các bước theo giáo trình:
- Vào trang https://favicon.io, chọn Text > Ico để chuyển một chữ cái thành ảnh favicon (trang cung cấp ba cách tạo).
- Chọn một chữ cái phù hợp (ví dụ chữ F), chọn màu chữ và màu nền.
- Tải toàn bộ ảnh về, đặt trong thư mục
images/favicon. - Khai báo trong phần
headbằng các thẻlinkdưới đây. - Tạo tệp
Webmanifest.jsonnhư bên dưới.
<link rel="apple-touch-icon" sizes="180x180" href="images/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="images/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="images/favicon/favicon-16x16.png">
<link rel="manifest" href="/Webmanifest.json">{
"name": "",
"short_name": "",
"icons": [
{ "src": "images/favicon/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "images/favicon/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}Ví dụ dưới đây kiểm tra rằng các thẻ khai báo đã nằm đúng chỗ trong head (trang thật cần tệp ảnh tải về từ favicon.io):
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<title>Trang có favicon</title>
<link rel="apple-touch-icon" sizes="180x180" href="images/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="images/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="images/favicon/favicon-16x16.png">
<link rel="manifest" href="/Webmanifest.json">
</head>
<body>
<p>Xem biểu tượng trên thanh tab của trình duyệt.</p>
<script>
document.querySelectorAll("head link").forEach((l) =>
console.log(l.rel.padEnd(16), l.getAttribute("sizes") || "-", l.getAttribute("href")));
</script>
</body>
</html>Bài tập tự thực hành
Bài 2.4. Tạo giao diện với ảnh SVG
Viết mã HTML tạo giao diện có bốn biểu tượng SVG (ảnh tự thiết kế hoặc tải về). Giáo trình lưu ý: khi đưa ảnh SVG vào tài liệu HTML có thể dùng tệp ảnh hoặc mã ảnh đặt trong phần tử svg. Gợi ý lời giải dùng mã ảnh với bốn biểu tượng cơ bản (mặt cười, ngôi nhà, ngôi sao, trái tim):
<style>
.bieutuong { display: inline-block; text-align: center; margin: 8px; font-family: sans-serif; }
</style>
<div class="bieutuong">
<svg width="80" height="80" viewBox="0 0 80 80">
<circle cx="40" cy="40" r="34" fill="gold" stroke="orange" stroke-width="3" />
<circle cx="28" cy="32" r="4" /><circle cx="52" cy="32" r="4" />
<path d="M24 50 Q40 66 56 50" stroke="black" stroke-width="3" fill="none" />
</svg><br>Mặt cười
</div>
<div class="bieutuong">
<svg width="80" height="80" viewBox="0 0 80 80">
<polygon points="40,8 8,38 16,38 16,72 64,72 64,38 72,38" fill="#4caf50" />
<rect x="33" y="48" width="14" height="24" fill="#fff" />
</svg><br>Ngôi nhà
</div>
<div class="bieutuong">
<svg width="80" height="80" viewBox="0 0 80 80">
<polygon points="40,6 49,30 74,30 54,46 62,72 40,56 18,72 26,46 6,30 31,30" fill="#ffc107" stroke="#ff9800" stroke-width="2" />
</svg><br>Ngôi sao
</div>
<div class="bieutuong">
<svg width="80" height="80" viewBox="0 0 80 80">
<path d="M40 70 C-10 35 25 0 40 22 C55 0 90 35 40 70 Z" fill="#e53935" />
</svg><br>Trái tim
</div>
<script>
console.log("Số biểu tượng SVG:", document.querySelectorAll("svg").length);
document.querySelectorAll(".bieutuong").forEach((d) => console.log(d.textContent.trim()));
</script>Bạn có thể thử phóng to các biểu tượng bằng cách đổi width/height của svg thành 300: nét vẽ vẫn mịn, vì vector không vỡ hạt.
Bài 2.5. Tạo form
Tạo biểu mẫu nộp hồ sơ ứng tuyển để người dùng điền thông tin. Hãy vận dụng toàn bộ phần biểu mẫu: họ tên, email, số điện thoại (tel), ngày sinh (date), vị trí ứng tuyển (select), hình thức làm việc (radio), kỹ năng (checkbox), tệp CV (file với accept), thư giới thiệu (textarea) và nút gửi/nhập lại. Gợi ý lời giải:
<form action="/ung-tuyen" method="post" enctype="multipart/form-data" id="ut">
<fieldset>
<legend>Thông tin ứng viên</legend>
<p><label for="ten">Họ tên:</label> <input type="text" id="ten" name="ten" required></p>
<p><label for="mail">Email:</label> <input type="email" id="mail" name="email" required></p>
<p><label for="sdt">Điện thoại:</label>
<input type="tel" id="sdt" name="sdt" pattern="[0-9]{10}" title="10 chữ số" placeholder="0901234567"></p>
<p><label for="ns">Ngày sinh:</label> <input type="date" id="ns" name="ngaysinh" max="2006-12-31"></p>
</fieldset>
<fieldset>
<legend>Vị trí ứng tuyển</legend>
<p><label for="vt">Vị trí:</label>
<select id="vt" name="vitri">
<option value="">-- Chọn --</option>
<option value="fe">Lập trình Front-end</option>
<option value="be">Lập trình Back-end</option>
<option value="qa">Kiểm thử phần mềm</option>
</select></p>
<p>Hình thức:
<input type="radio" id="ft" name="hinhthuc" value="fulltime" checked><label for="ft">Toàn thời gian</label>
<input type="radio" id="pt" name="hinhthuc" value="parttime"><label for="pt">Bán thời gian</label></p>
<p>Kỹ năng:
<input type="checkbox" id="k1" name="kynang" value="html"><label for="k1">HTML</label>
<input type="checkbox" id="k2" name="kynang" value="css"><label for="k2">CSS</label>
<input type="checkbox" id="k3" name="kynang" value="js"><label for="k3">JavaScript</label></p>
</fieldset>
<p><label for="cv">Tệp CV (PDF):</label> <input type="file" id="cv" name="cv" accept=".pdf"></p>
<p><label for="tl">Thư giới thiệu:</label><br>
<textarea id="tl" name="thu" rows="4" cols="40" maxlength="500"></textarea></p>
<input type="submit" value="Nộp hồ sơ"> <input type="reset" value="Nhập lại">
</form>
<script>
const f = document.getElementById("ut");
f.addEventListener("submit", (e) => {
e.preventDefault();
for (const [k, v] of new FormData(f)) if (typeof v === "string") console.log(k, "=", v);
});
document.getElementById("ten").value = "Phạm Minh Khôi";
document.getElementById("mail").value = "[email protected]";
document.getElementById("sdt").value = "0901234567";
document.getElementById("vt").value = "fe";
document.getElementById("k1").checked = true;
document.getElementById("k3").checked = true;
console.log("Form hợp lệ?", f.checkValidity());
f.requestSubmit();
</script>Câu hỏi ôn tập
Giáo trình: Chương 2 — mục "Câu hỏi ôn tập lý thuyết" (câu 2.1–2.10, đáp án đối chiếu Phụ lục) và phần ôn tập bổ sung cho nội dung biểu mẫu, đa phương tiện của buổi này.
Phần A — Ôn tập chương 2 theo giáo trình (các câu này bao quát nội dung buổi 3–5):
2.1. Thẻ nào tạo ra chữ đậm mà KHÔNG mang ý nghĩa nhấn mạnh? A. strong · B. b · C. h1 · D. head
Đáp án: B (b chỉ in đậm về hình thức; strong mang ý nghĩa quan trọng).
2.2. Thành phần HTML nào tạo ra một ngắt dòng? A. br · B. break · C. hr · D. p
Đáp án: A (br; còn hr là đường kẻ ngang).
2.3. Thẻ nào định nghĩa một văn bản quan trọng? A. i · B. important · C. strong · D. em
Đáp án: C (strong).
2.4. Thẻ nào được sử dụng để nhấn mạnh văn bản? A. p · B. i · C. strong · D. em
Đáp án: D (em).
2.5. Thuộc tính nào dùng để hợp 2 hàng trong 1 bảng? A. rowpadding · B. cellpadding · C. rowspan · D. colspan
Đáp án: C (rowspan gộp hàng; colspan gộp cột).
2.6. Để tạo liên kết trong thẻ a dùng thuộc tính gì? A. href · B. src · C. link · D. ref
Đáp án: A (href; src dùng cho img, audio, video, script).
2.7. Đơn vị nào được dùng cho thuộc tính kích thước ảnh? A. Pixels · B. Centimeters · C. Dot · D. Inches
Đáp án: A (width và height của ảnh luôn tính bằng pixel).
2.8. Vì sao nên dùng các phần tử ngữ nghĩa để tổ chức cấu trúc trang? Đáp án: D — vì chúng mô tả rõ ý nghĩa của nội dung cho trình duyệt, công cụ tìm kiếm và trình đọc màn hình.
2.9. Thẻ nào tạo ra danh sách không có thứ tự? A. ul · B. ol · C. bullet · D. list
Đáp án: A (ul; ol là danh sách có thứ tự).
2.10. Đoạn mã nào ĐÚNG để tạo một đường dẫn? A. <a url="…"> · B. <a href="…"> · C. <a name="…"> · D. <a>http://…</a>
Đáp án: B.
Phần B — Ôn tập biểu mẫu và đa phương tiện (câu bổ sung của buổi này, đáp án suy ra từ nội dung chương):
B1. Biểu mẫu có ô <input type="text" value="Mickey"> (không có name) và ô <input type="text" name="lastname" value="Mouse">. Dữ liệu nào được gửi đi?
Đáp án: Chỉ lastname=Mouse. Trường không có thuộc tính name thì dữ liệu không được gửi.
B2. Phương thức gửi mặc định của form là gì? Khi nào tuyệt đối không dùng nó? Đáp án: Mặc định là GET (dữ liệu hiện trên URL). Không bao giờ dùng GET cho dữ liệu nhạy cảm (mật khẩu, thông tin cá nhân) — dùng POST.
B3. Làm thế nào để các radio button chỉ cho chọn một phương án?
Đáp án: Đặt cùng giá trị thuộc tính name cho tất cả radio trong nhóm.
B4. Phân biệt readonly và disabled.
Đáp án: readonly: không sửa được nhưng giá trị vẫn được gửi đi. disabled: vô hiệu hóa, dữ liệu không được gửi đi.
B5. label gắn với ô nhập bằng cách nào? Nêu ít nhất hai lợi ích.
Đáp án: Thuộc tính for của label có cùng giá trị với id của điều khiển (hoặc lồng điều khiển trong label). Lợi ích: trình đọc màn hình đọc nhãn; bấm vào nhãn chọn được radio/checkbox dễ hơn.
B6. Khác nhau giữa progress và meter?
Đáp án: progress thể hiện tiến độ của một tác vụ; meter thể hiện một đại lượng đo lường (tốc độ, dung lượng...).
B7. Vì sao thẻ img bắt buộc có alt, và width/height có tác dụng gì?
Đáp án: alt là văn bản thay thế khi ảnh không xem được (chậm, lỗi src, trình đọc màn hình); thiếu thì trang không đạt chuẩn. width/height (pixel) giúp trình duyệt chừa chỗ trước, tránh hiệu ứng tải "nhấp nháy".
B8. Phân biệt SVG và Canvas. Vì sao logo nên dùng SVG?
Đáp án: SVG mô tả đồ họa bằng XML (thẻ svg), Canvas vẽ bằng JavaScript. Logo dùng SVG vì dung lượng nhỏ, phóng to không giảm chất lượng.
B9. Chọn phần tử đúng: hiển thị video, âm thanh, trang HTML khác, tệp PDF?
Đáp án: video, audio, iframe, embed (theo thứ tự).
Sai lầm thường gặp
| Sai lầm | Hậu quả | Cách đúng |
|---|---|---|
Quên thuộc tính name trên ô nhập |
Dữ liệu của ô đó không được gửi đi | Mỗi điều khiển cần gửi phải có name |
Hai radio cùng nhóm nhưng khác name |
Chọn được cả hai, không còn "chỉ một" | Dùng chung một name cho cả nhóm |
Gửi mật khẩu bằng method="get" (hoặc quên method) |
Mật khẩu hiện trên URL, lưu trong lịch sử | Dùng method="post" |
label for="x" nhưng id của ô là y |
Bấm nhãn không chọn được ô; trình đọc màn hình không đọc nhãn | for trùng đúng id |
Dùng placeholder thay hoàn toàn cho label |
Chữ gợi ý mất khi gõ, khó tiếp cận | Luôn có nhãn; placeholder chỉ là ví dụ định dạng |
Quên thẻ đóng </textarea> hoặc </select> |
Phần HTML phía sau bị nuốt, form hỏng | Viết đủ cặp thẻ |
Dùng readonly để "bảo mật" dữ liệu |
Người dùng sửa được trong DevTools | Kiểm tra lại dữ liệu ở máy chủ |
Thiếu alt ở img, hoặc không khai báo width/height |
Trang không đạt chuẩn; ảnh "nhấp nháy" lúc tải | Luôn có alt; đặt kích thước pixel |
Đặt type="url" mà người dùng gõ thiếu https:// |
Bị từ chối bởi kiểm tra tự động | Thêm placeholder mẫu và thông báo title |
Dùng autoplay có tiếng cho video/nhạc |
Trình duyệt chặn, người dùng khó chịu | Thêm muted, luôn giữ controls |
Dùng embed/object cho mọi loại nội dung |
Hỗ trợ kém, khó tiếp cận | Dùng img, video, audio, iframe đúng mục đích |
Tóm tắt
- Form thu thập dữ liệu:
action(gửi đi đâu),method(GET mặc định, POST cho dữ liệu nhạy cảm),target, vànovalidate. - Điều khiển chính tạo bởi
input+type: văn bản và số (text, email, number, range, search, tel, password, url), thời gian (date, time, datetime-local, month, week), lựa chọn (radio, checkbox, select/option/optgroup, datalist), tập tin (file vớiaccept,multiple), nút bấm (button, reset, submit, image,button), và loại khác (color, hidden). - Điều khiển chỉ gửi đi khi có
name;disabledkhông gửi,readonlyvẫn gửi; checkbox không chọn thì không gửi. label(for=id),fieldset/legend,progress,meter,outputgiúp biểu mẫu rõ ràng, tiếp cận tốt và có phản hồi.- Thuộc tính kiểm tra dữ liệu ngay trên trình duyệt:
required,pattern(+title),min,max,step,maxlength,placeholder,autofocus,autocomplete,list. - Bitmap dùng
img(bắt buộcsrc,alt;width/heightbằng pixel), ảnh bản đồ dùngmap/area; vector dùngsvg(circle,rect,polygon,text,ellipse,viewBox) — không vỡ khi phóng to; khác với Canvas. - Âm thanh/phim:
audio,video,source,track,poster, thuộc tínhcontrols,autoplay,loop,muted,preload; nhúng trang ngoài bằngiframe; PDF bằngembed.