📚 Khóa học Web tĩnh (HTML • CSS • JavaScript)
Trang điều hướng trung tâm – tìm bài nhanh, học đúng lộ trình.
Bạn đã hoàn thành 20 bài, bao gồm:
<div class="finish">
<h2>🎓 Bạn đã hoàn thành khóa học Web tĩnh!</h2>
<p>
HTML • CSS • JavaScript • Blogspot
</p>
<p>
🚀 Hãy tiếp tục xây dựng dự án của riêng bạn.
</p>
</div>
Nếu bạn đọc được đến đây, nghĩa là bạn đã đi rất xa. Kiến thức chỉ thực sự có giá trị khi bạn dùng nó để tạo ra sản phẩm.
Chúc bạn tiếp tục học tốt, làm được nhiều dự án, và nếu cần – hãy quay lại chỉnh sửa, nâng cấp khóa học này 💪
localStorage✅ Trang này có thể thay cho Menu bài 16 (nâng cấp hơn).
<div class="hub" id="hub">
<header class="top">
<div class="brand">
<div class="logo">⚡</div>
<div>
<h1>Course Hub – Web tĩnh</h1>
<p>HTML • CSS • JavaScript • Blogspot Ready</p>
</div>
</div>
<div class="actions">
<button class="btn ghost" id="btnTheme">🌙 Dark mode</button>
<a class="btn primary" href="LINK_MENU_KHOA_HOC" target="_blank">Mở Menu (cũ)</a>
</div>
</header>
<section class="panel">
<div class="stats">
<div class="stat"><b id="doneCount">0</b><br>Bài đã học</div>
<div class="stat"><b>19</b><br>Tổng bài</div>
<div class="stat"><b>5</b><br>Mini Project</div>
<div class="stat"><b id="now">--:--:--</b><br>Giờ hiện tại</div>
</div>
<div class="bar">
<input id="search" type="text" placeholder="Tìm bài... (vd: grid, fetch, quiz, seo)">
<button class="btn dark" id="btnReset">Reset</button>
</div>
<div class="tabs" id="tabs">
<button class="tab active" data-tab="all">Tất cả</button>
<button class="tab" data-tab="html">HTML</button>
<button class="tab" data-tab="css">CSS</button>
<button class="tab" data-tab="js">JavaScript</button>
<button class="tab" data-tab="project">Project</button>
<button class="tab" data-tab="publish">Xuất bản</button>
</div>
<div class="list" id="list"></div>
<div class="foot">
<span id="hint">Gợi ý: bấm “Đã học” để lưu tiến độ.</span>
</div>
</section>
</div>
<style>
:root{
--bg:#f7f8fa;
--card:#ffffff;
--text:#111;
--muted:#555;
--border:#e5e5e5;
--soft:#f3f4f6;
--primary:#2563eb;
--dark:#111;
}
.hub.dark{
--bg:#0b1220;
--card:#0f172a;
--text:#e5e7eb;
--muted:#cbd5e1;
--border:#1f2937;
--soft:#111827;
--primary:#60a5fa;
--dark:#e5e7eb;
}
.hub{
background:var(--bg);
color:var(--text);
font-family: Arial, Helvetica, sans-serif;
line-height:1.7;
max-width: 1060px;
margin: 16px auto;
padding: 0;
border-radius: 18px;
}
.top{
display:flex;
justify-content: space-between;
gap: 12px;
align-items:center;
padding: 16px;
background:var(--card);
border:1px solid var(--border);
border-radius: 18px;
}
.brand{
display:flex;
gap: 12px;
align-items:center;
}
.logo{
width: 44px;
height: 44px;
border-radius: 14px;
display:flex;
align-items:center;
justify-content:center;
background: var(--soft);
border:1px solid var(--border);
font-size: 22px;
}
.brand h1{ margin:0; font-size: 20px; }
.brand p{ margin:0; color:var(--muted); font-size: 13px; }
.actions{
display:flex;
gap: 10px;
flex-wrap:wrap;
justify-content:flex-end;
}
.panel{
margin-top: 12px;
padding: 16px;
background:var(--card);
border:1px solid var(--border);
border-radius: 18px;
}
.stats{
display:grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.stat{
background:var(--soft);
border:1px solid var(--border);
border-radius: 16px;
padding: 10px 12px;
text-align:center;
}
.stat b{ font-size: 18px; }
.bar{
display:flex;
gap: 10px;
flex-wrap:wrap;
justify-content:center;
margin: 12px 0 10px;
}
input{
padding:10px 12px;
border:1px solid var(--border);
border-radius: 12px;
min-width: 280px;
width: min(520px, 100%);
background:var(--card);
color:var(--text);
outline:none;
}
.btn{
padding: 10px 12px;
border-radius: 12px;
border:0;
cursor:pointer;
font-weight: 800;
text-decoration:none;
display:inline-block;
text-align:center;
}
.btn.primary{ background:var(--primary); color:#fff; }
.btn.dark{ background:var(--dark); color: #fff; }
.hub.dark .btn.dark{ background: #e5e7eb; color:#0b1220; }
.btn.ghost{ background:var(--soft); border:1px solid var(--border); color:var(--text); }
.tabs{
display:flex;
gap: 8px;
flex-wrap:wrap;
justify-content:center;
margin-bottom: 12px;
}
.tab{
padding: 8px 10px;
border-radius: 999px;
border:1px solid var(--border);
background:var(--card);
color:var(--text);
cursor:pointer;
font-weight: 800;
font-size: 13px;
}
.tab.active{
background: var(--primary);
color:#fff;
border-color: transparent;
}
.list{
display:flex;
flex-direction:column;
gap: 10px;
}
.item{
display:flex;
justify-content: space-between;
gap: 12px;
flex-wrap:wrap;
align-items:center;
background:var(--card);
border:1px solid var(--border);
border-radius: 16px;
padding: 12px;
}
.left{
flex:1;
min-width: 240px;
}
.title{
font-weight: 900;
color:var(--text);
text-decoration:none;
}
.title:hover{ text-decoration: underline; }
.meta{
margin-top: 2px;
font-size: 13px;
color:var(--muted);
}
.pill{
display:inline-block;
margin-right: 6px;
font-size: 12px;
padding: 2px 10px;
border-radius: 999px;
background: var(--soft);
border:1px solid var(--border);
color:var(--text);
}
.right{
display:flex;
gap: 8px;
flex-wrap:wrap;
}
.done .title{ color: #16a34a; }
.hub.dark .done .title{ color: #86efac; }
.foot{
margin-top: 12px;
text-align:center;
color:var(--muted);
}
@media (max-width: 900px){
.top{ flex-direction:column; align-items:flex-start; }
.actions{ width:100%; justify-content:flex-start; }
.stats{ grid-template-columns: 1fr 1fr; }
}
</style>
<script>
// ===== DATA: danh sách bài (bạn thay link thật vào url) =====
const LESSONS = [
{ id:"bai-01", no:1, title:"Bài 1: Trình soạn thảo + cấu trúc HTML cơ bản", tag:"html", url:"LINK_BAI_01", key:"editor html co ban" },
{ id:"bai-02", no:2, title:"Bài 2: Các phần tử HTML cơ bản", tag:"html", url:"LINK_BAI_02", key:"heading p div span list" },
{ id:"bai-03", no:3, title:"Bài 3: Bảng trong HTML", tag:"html", url:"LINK_BAI_03", key:"table thead tbody tr td" },
{ id:"bai-04", no:4, title:"Bài 4: Link • Ảnh • Audio • Video", tag:"html", url:"LINK_BAI_04", key:"a img audio video iframe" },
{ id:"bai-05", no:5, title:"Bài 5: Form cơ bản", tag:"html", url:"LINK_BAI_05", key:"form input textarea select" },
{ id:"bai-06", no:6, title:"Bài 6: Semantic HTML + bố cục", tag:"html", url:"LINK_BAI_06", key:"semantic header nav main section footer" },
{ id:"bai-07", no:7, title:"Bài 7: CSS Selector + Box Model", tag:"css", url:"LINK_BAI_07", key:"css selector box model margin padding" },
{ id:"bai-08", no:8, title:"Bài 8: CSS Flexbox", tag:"css", url:"LINK_BAI_08", key:"flex justify align wrap gap" },
{ id:"bai-09", no:9, title:"Bài 9: CSS Grid + Responsive", tag:"css", url:"LINK_BAI_09", key:"grid repeat minmax responsive" },
{ id:"bai-10", no:10, title:"Bài 10: JavaScript cơ bản (DOM + Event)", tag:"js", url:"LINK_BAI_10", key:"js dom event let const" },
{ id:"bai-11", no:11, title:"Bài 11: Mini Project – To-do List", tag:"project", url:"LINK_BAI_11", key:"todo project dom" },
{ id:"bai-12", no:12, title:"Bài 12: Mini Project – Quiz trắc nghiệm", tag:"project", url:"LINK_BAI_12", key:"quiz radio cham diem" },
{ id:"bai-13", no:13, title:"Bài 13: Array + Object (CRUD mini)", tag:"js", url:"LINK_BAI_13", key:"array object crud filter map splice" },
{ id:"bai-14", no:14, title:"Bài 14: Fetch API", tag:"js", url:"LINK_BAI_14", key:"fetch api json async await" },
{ id:"bai-15", no:15, title:"Bài 15: Mini Project – Landing Page", tag:"project", url:"LINK_BAI_15", key:"landing one page scroll" },
{ id:"bai-16", no:16, title:"Bài 16: Menu khóa học + Template bài học", tag:"publish", url:"LINK_BAI_16", key:"menu template blogspot" },
{ id:"bai-17", no:17, title:"Bài 17: SEO + tối ưu tốc độ", tag:"publish", url:"LINK_BAI_17", key:"seo performance lazy alt title" },
{ id:"bai-18", no:18, title:"Bài 18: Xuất bản + Analytics + checklist", tag:"publish", url:"LINK_BAI_18", key:"analytics ga4 publish checklist" },
{ id:"bai-19", no:19, title:"Bài 19: Capstone – Course Hub 1 trang", tag:"project", url:"#", key:"capstone hub" }
];
const hub = document.getElementById("hub");
const listEl = document.getElementById("list");
const searchEl = document.getElementById("search");
const btnReset = document.getElementById("btnReset");
const btnTheme = document.getElementById("btnTheme");
const doneCountEl = document.getElementById("doneCount");
const nowEl = document.getElementById("now");
const hint = document.getElementById("hint");
const KEY_DONE = "hub_done_v1";
const KEY_THEME = "hub_theme_v1";
function getDone(){
try{ return JSON.parse(localStorage.getItem(KEY_DONE) || "[]"); }
catch(e){ return []; }
}
function setDone(arr){
localStorage.setItem(KEY_DONE, JSON.stringify(arr));
}
function setTheme(isDark){
hub.classList.toggle("dark", isDark);
localStorage.setItem(KEY_THEME, isDark ? "dark" : "light");
btnTheme.textContent = isDark ? "☀️ Light mode" : "🌙 Dark mode";
}
function getTheme(){
return (localStorage.getItem(KEY_THEME) || "light") === "dark";
}
function tagLabel(tag){
if(tag==="html") return "HTML";
if(tag==="css") return "CSS";
if(tag==="js") return "JS";
if(tag==="project") return "PROJECT";
if(tag==="publish") return "XUẤT BẢN";
return "ALL";
}
function render(items){
const done = getDone();
listEl.innerHTML = "";
if(items.length === 0){
listEl.innerHTML = "<div class='meta'>Không tìm thấy bài phù hợp.</div>";
return;
}
items.forEach(it => {
const row = document.createElement("div");
row.className = "item" + (done.includes(it.id) ? " done" : "");
row.innerHTML = `
<div class="left">
<span class="pill">${tagLabel(it.tag)}</span>
<a class="title" href="${it.url}" target="_blank">${it.title}</a>
<div class="meta">Từ khóa: ${it.key}</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="${it.id}">${done.includes(it.id) ? "Đã học ✓" : "Đã học"}</button>
<button class="btn primary copy" data-url="${it.url}">Copy link</button>
</div>
`;
listEl.appendChild(row);
});
// gắn sự kiện
listEl.querySelectorAll(".mark").forEach(btn => {
btn.addEventListener("click", () => {
const id = btn.dataset.id;
const done = getDone();
const idx = done.indexOf(id);
if(idx === -1) done.push(id);
else done.splice(idx, 1);
setDone(done);
applyFilters(); // render lại theo bộ lọc hiện tại
updateDoneCount();
});
});
listEl.querySelectorAll(".copy").forEach(btn => {
btn.addEventListener("click", async () => {
const url = btn.dataset.url;
try{
await navigator.clipboard.writeText(url);
btn.textContent = "Đã copy ✓";
setTimeout(() => btn.textContent = "Copy link", 900);
} catch(e){
alert("Không copy được. Bạn hãy copy thủ công: " + url);
}
});
});
}
function updateDoneCount(){
doneCountEl.textContent = getDone().length;
}
// ===== Filters =====
let currentTab = "all";
function applyFilters(){
const key = searchEl.value.trim().toLowerCase();
let items = LESSONS.slice();
if(currentTab !== "all"){
items = items.filter(x => x.tag === currentTab);
}
if(key){
items = items.filter(x => (x.title + " " + x.key).toLowerCase().includes(key));
}
render(items);
}
document.querySelectorAll(".tab").forEach(tab => {
tab.addEventListener("click", () => {
document.querySelectorAll(".tab").forEach(t => t.classList.remove("active"));
tab.classList.add("active");
currentTab = tab.dataset.tab;
applyFilters();
hint.textContent = "Đang lọc: " + tagLabel(currentTab === "all" ? "all" : currentTab);
});
});
searchEl.addEventListener("input", applyFilters);
btnReset.addEventListener("click", () => {
searchEl.value = "";
currentTab = "all";
document.querySelectorAll(".tab").forEach(t => t.classList.remove("active"));
document.querySelector('.tab[data-tab="all"]').classList.add("active");
hint.textContent = "Gợi ý: bấm “Đã học” để lưu tiến độ.";
applyFilters();
});
// ===== Theme =====
btnTheme.addEventListener("click", () => {
setTheme(!hub.classList.contains("dark"));
});
// ===== Clock =====
setInterval(() => {
nowEl.textContent = new Date().toLocaleTimeString("vi-VN");
}, 1000);
// Init
setTheme(getTheme());
updateDoneCount();
applyFilters();
</script>
✅ Bạn chỉ cần thay link LINK_BAI_01…LINK_BAI_18 và LINK_MENU_KHOA_HOC là dùng được ngay.
Progress % (gợi ý nhanh)
const percent = Math.round(getDone().length / LESSONS.length * 100);
alt, có loading="lazy"1) Không nhúng mã lạ
2) Tránh XSS (ở mức cơ bản)
Khi hiển thị dữ liệu người dùng nhập (form demo), tránh dùng innerHTML
nếu không cần thiết.
// TỐT
out.textContent = userInput;
// HẠN CHẾ (chỉ dùng khi bạn kiểm soát nội dung)
out.innerHTML = "<b>" + safeText + "</b>";
3) Không lưu thông tin nhạy cảm
localStorageBước 1: Tạo GA4 → lấy Measurement ID (dạng G-XXXX).
Bước 2: Blogspot → Theme → Edit HTML.
Bước 3: Dán trước thẻ </head>:
<!-- Google Analytics GA4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXX');
</script>
👉 Sau 24–48h, bạn sẽ thấy lượt xem bài học, thời gian ở lại trang.
Gợi ý nâng cấp tiếp theo
<div class="finish">
<h2>🎉 Chúc mừng!</h2>
<p>
Bạn đã hoàn thành khóa học Web tĩnh (HTML • CSS • JavaScript).
</p>
<p>
👉 Hãy quay lại Menu khóa học để ôn tập hoặc chia sẻ cho bạn bè.
</p>
</div>
<style>
.finish{
margin: 20px 0;
padding: 16px 18px;
border-radius: 16px;
background:#ecfdf5;
border:1px solid #bbf7d0;
text-align:center;
}
.finish h2{ margin-top:0; }
</style>
Bạn đã hoàn thành 18 bài về thiết kế Web tĩnh bằng HTML, CSS, JavaScript và triển khai thực tế trên Blogspot.
🚀 Từ đây, bạn có thể tiếp tục học Front-end nâng cao (Responsive sâu hơn, ES6+, framework…).
SEO (Search Engine Optimization) là tối ưu để Google hiểu và xếp hạng trang của bạn tốt hơn.
1) Các thẻ SEO quan trọng
<title> – Tiêu đề trang (rất quan trọng)<meta name="description"><h1> – chỉ nên có 1<h2> → <h3> – cấu trúc nội dungalt cho ảnh
<title>Khóa học Web tĩnh HTML CSS JavaScript</title>
<meta name="description" content="Học HTML CSS JavaScript cơ bản, chuẩn Blogspot">
2) Cấu trúc heading đúng
<h1>Bài 17: SEO cho Web tĩnh</h1>
<h2>SEO là gì</h2>
<h3>On-page SEO</h3>
1) CSS & JS
</body>)2) Ảnh (rất hay bị lỗi)
alt
<img src="banner.jpg"
alt="Khóa học Web tĩnh HTML CSS JS"
width="800"
height="400"
loading="lazy">
3) Lazy loading
Thuộc tính loading="lazy" giúp ảnh/video chỉ tải khi người dùng cuộn tới.
<!doctype html>
<html lang="vi">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bài 17 – SEO cho Web tĩnh | Khóa học HTML CSS JS</title>
<meta name="description"
content="Học cách tối ưu SEO và tốc độ cho web tĩnh HTML CSS JavaScript, chuẩn Blogspot">
<style>
body{
font-family: Arial;
margin:0;
background:#f7f8fa;
line-height:1.7;
color:#111;
}
.wrap{
max-width:900px;
margin:20px auto;
background:#fff;
border:1px solid #e5e5e5;
border-radius:16px;
padding:18px;
}
h1,h2,h3{ line-height:1.4; }
img{
max-width:100%;
border-radius:12px;
border:1px solid #e5e5e5;
}
.note{
background:#f3f4f6;
border:1px solid #e5e7eb;
border-radius:12px;
padding:10px 12px;
margin:12px 0;
}
</style>
</head>
<body>
<article class="wrap">
<h1>Bài 17: Tối ưu SEO cho Web tĩnh</h1>
<p>
Đây là ví dụ một trang bài học được viết theo chuẩn SEO,
phù hợp để đăng trên Blogspot.
</p>
<h2>1. SEO On-page</h2>
<p>
On-page SEO là tối ưu ngay trong nội dung HTML.
</p>
<h3>Thẻ tiêu đề và mô tả</h3>
<p>
Google đọc thẻ <code>title</code> và <code>description</code> đầu tiên.
</p>
<h2>2. Tối ưu hình ảnh</h2>
<img src="https://via.placeholder.com/800x400"
alt="Ví dụ tối ưu ảnh SEO"
loading="lazy">
<div class="note">
Ảnh có alt + lazy loading giúp SEO và tốc độ tốt hơn.
</div>
<h2>3. Trải nghiệm người dùng</h2>
<p>
Trang tải nhanh, chữ dễ đọc, mobile-friendly → Google đánh giá cao.
</p>
</article>
<script>
// JS tối thiểu – không ảnh hưởng SEO
console.log("SEO page loaded");
</script>
</body>
</html>
👉 Đây là mẫu trang bài học chuẩn SEO bạn có thể dùng cho các bài sau.
site:tenblog.blogspot.commeta description cho Bài 1 → Bài 5.<title>) cho hấp dẫn hơn.Sau Bài 17, bạn đã có:
👉 Bạn tạo 1 Trang mới trong Blogspot (ví dụ: “Khóa học Web tĩnh”) rồi dán nguyên khối code dưới đây.
Sau đó chỉ cần thay LINK_BAI_01… thành link bài viết thật.
<div class="course">
<h1 class="course-title">📚 Khóa học Web tĩnh (HTML • CSS • JavaScript)</h1>
<div class="course-top">
<input id="q" class="course-search" type="text" placeholder="Tìm bài học... (vd: flex, grid, form)">
<button id="btnReset" class="btn dark">Hiện tất cả</button>
</div>
<div class="course-note">
✅ Click vào tiêu đề để mở bài • ⭐ Bấm “Đã học” để đánh dấu • 🔗 Copy link để chia sẻ
</div>
<div class="course-list" id="list">
<!-- MẪU 1 ITEM: sửa href thành link thật của Blogspot -->
<div class="item" data-key="bai 1 editor html css js co ban">
<div class="left">
<a class="title" href="LINK_BAI_01" target="_blank">Bài 1: Trình soạn thảo + cấu trúc HTML cơ bản</a>
<div class="meta">HTML cơ bản • bố cục • ví dụ đơn giản</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-01">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_01">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 2 phan tu html co ban heading p div span">
<div class="left">
<a class="title" href="LINK_BAI_02" target="_blank">Bài 2: Các phần tử HTML cơ bản</a>
<div class="meta">Heading • Paragraph • List • Div/Span</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-02">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_02">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 3 bang html table thead tbody tr td">
<div class="left">
<a class="title" href="LINK_BAI_03" target="_blank">Bài 3: Bảng trong HTML</a>
<div class="meta">table • thead • tbody • tr/td • border</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-03">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_03">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 4 media link image audio video iframe">
<div class="left">
<a class="title" href="LINK_BAI_04" target="_blank">Bài 4: Link • Ảnh • Audio • Video</a>
<div class="meta">a/img/audio/video/iframe</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-04">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_04">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 5 form input textarea select button">
<div class="left">
<a class="title" href="LINK_BAI_05" target="_blank">Bài 5: Form cơ bản</a>
<div class="meta">input • textarea • select • validation</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-05">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_05">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 6 semantic header nav main section article aside footer">
<div class="left">
<a class="title" href="LINK_BAI_06" target="_blank">Bài 6: Semantic HTML + bố cục trang</a>
<div class="meta">header/nav/main/section/article/aside/footer</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-06">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_06">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 7 css selector box model padding margin border">
<div class="left">
<a class="title" href="LINK_BAI_07" target="_blank">Bài 7: CSS Selector + Box Model</a>
<div class="meta">tag/class/id • padding • border • margin</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-07">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_07">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 8 flexbox justify align gap wrap">
<div class="left">
<a class="title" href="LINK_BAI_08" target="_blank">Bài 8: CSS Flexbox</a>
<div class="meta">display:flex • justify-content • align-items • wrap</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-08">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_08">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 9 grid repeat minmax responsive">
<div class="left">
<a class="title" href="LINK_BAI_09" target="_blank">Bài 9: CSS Grid + Responsive</a>
<div class="meta">grid-template-columns • repeat • minmax</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-09">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_09">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 10 js bien kieu du lieu dom su kien">
<div class="left">
<a class="title" href="LINK_BAI_10" target="_blank">Bài 10: JavaScript cơ bản (DOM + Event)</a>
<div class="meta">let/const • string/number • DOM • click/input</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-10">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_10">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 11 todo list project">
<div class="left">
<a class="title" href="LINK_BAI_11" target="_blank">Bài 11: Mini Project – To-do List</a>
<div class="meta">Thêm/xóa/đánh dấu • DOM • event</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-11">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_11">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 12 quiz trac nghiem cham diem">
<div class="left">
<a class="title" href="LINK_BAI_12" target="_blank">Bài 12: Mini Project – Quiz trắc nghiệm</a>
<div class="meta">radio • chấm điểm • tô đúng/sai</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-12">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_12">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 13 array object crud them xoa tim">
<div class="left">
<a class="title" href="LINK_BAI_13" target="_blank">Bài 13: Array + Object (CRUD mini)</a>
<div class="meta">push/filter/map/splice • render list</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-13">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_13">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 14 fetch api json render">
<div class="left">
<a class="title" href="LINK_BAI_14" target="_blank">Bài 14: Fetch API</a>
<div class="meta">fetch • async/await • loading/error</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-14">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_14">Copy link</button>
</div>
</div>
<div class="item" data-key="bai 15 landing page one page scroll menu">
<div class="left">
<a class="title" href="LINK_BAI_15" target="_blank">Bài 15: Mini Project – Landing Page</a>
<div class="meta">section • smooth scroll • back to top • menu active</div>
</div>
<div class="right">
<button class="btn ghost mark" data-id="bai-15">Đã học</button>
<button class="btn primary copy" data-url="LINK_BAI_15">Copy link</button>
</div>
</div>
</div>
<div class="course-footer" id="status">0/15 bài đã học</div>
</div>
<style>
.course{max-width:980px;margin:16px auto;font-family:Arial;line-height:1.7}
.course-title{text-align:center;margin:0 0 14px}
.course-top{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin-bottom:10px}
.course-search{padding:10px 12px;border:1px solid #ddd;border-radius:12px;min-width:280px}
.course-note{background:#f3f4f6;border:1px solid #e5e7eb;border-radius:14px;padding:10px 12px;color:#111;margin-bottom:12px}
.course-list{display:flex;flex-direction:column;gap:10px}
.item{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:center;background:#fff;border:1px solid #e5e5e5;border-radius:16px;padding:12px}
.left{min-width:240px;flex:1}
.title{font-weight:800;color:#111;text-decoration:none}
.title:hover{text-decoration:underline}
.meta{color:#555;font-size:13px;margin-top:3px}
.right{display:flex;gap:8px;flex-wrap:wrap}
.btn{padding:9px 12px;border-radius:12px;border:0;cursor:pointer;font-weight:800}
.btn.primary{background:#2563eb;color:#fff}
.btn.dark{background:#111;color:#fff}
.btn.ghost{background:#f3f4f6;border:1px solid #e5e7eb;color:#111}
.done .title{color:#16a34a}
.course-footer{margin-top:12px;text-align:center;color:#555}
</style>
<script>
const q = document.getElementById("q");
const list = document.getElementById("list");
const status = document.getElementById("status");
const btnReset = document.getElementById("btnReset");
const KEY = "course_done_v1";
function getDone(){
try{ return JSON.parse(localStorage.getItem(KEY) || "[]"); }
catch(e){ return []; }
}
function setDone(arr){
localStorage.setItem(KEY, JSON.stringify(arr));
}
function updateCount(){
const done = getDone();
status.textContent = done.length + "/15 bài đã học";
}
function applyDoneUI(){
const done = getDone();
document.querySelectorAll(".mark").forEach(btn => {
const id = btn.dataset.id;
const item = btn.closest(".item");
const isDone = done.includes(id);
item.classList.toggle("done", isDone);
btn.textContent = isDone ? "Đã học ✓" : "Đã học";
});
updateCount();
}
// Tìm kiếm
q.addEventListener("input", () => {
const key = q.value.trim().toLowerCase();
document.querySelectorAll(".item").forEach(it => {
const hay = (it.dataset.key || "").toLowerCase();
it.style.display = (!key || hay.includes(key)) ? "flex" : "none";
});
});
btnReset.addEventListener("click", () => {
q.value = "";
q.dispatchEvent(new Event("input"));
});
// Mark “đã học”
document.querySelectorAll(".mark").forEach(btn => {
btn.addEventListener("click", () => {
const id = btn.dataset.id;
const done = getDone();
const idx = done.indexOf(id);
if(idx === -1) done.push(id);
else done.splice(idx, 1);
setDone(done);
applyDoneUI();
});
});
// Copy link
document.querySelectorAll(".copy").forEach(btn => {
btn.addEventListener("click", async () => {
const url = btn.dataset.url;
try{
await navigator.clipboard.writeText(url);
btn.textContent = "Đã copy ✓";
setTimeout(() => btn.textContent = "Copy link", 900);
} catch(e){
alert("Không copy được. Bạn hãy copy thủ công: " + url);
}
});
});
applyDoneUI();
</script>
✅ Cách thay link: mở từng bài viết Blogspot → copy URL → dán vào chỗ LINK_BAI_01, LINK_BAI_02...
👉 Mỗi bài giảng là 1 bài viết/1 trang trên Blogspot. Bạn chỉ cần copy template dưới đây, rồi thay nội dung từng phần.
<div class="lesson">
<h1 class="lesson-title">[TÊN BÀI] – [CHỦ ĐỀ]</h1>
<section class="lesson-box">
<h2>🎯 Mục tiêu</h2>
<ul>
<li>...</li>
</ul>
</section>
<section class="lesson-box">
<h2>📘 Lý thuyết ngắn</h2>
<p>...</p>
</section>
<section class="lesson-box">
<h2>💻 Ví dụ</h2>
<pre class="code"><code>
<!-- dán code ví dụ ở đây -->
</code></pre>
</section>
<section class="lesson-box">
<h2>✍️ Bài tập</h2>
<ol>
<li>...</li>
</ol>
</section>
<section class="lesson-box">
<h2>✅ Đáp án gợi ý</h2>
<pre class="code"><code>
<!-- gợi ý đáp án ở đây -->
</code></pre>
</section>
</div>
<style>
.lesson{max-width:900px;margin:20px auto;font-family:Arial,Helvetica,sans-serif;line-height:1.7}
.lesson-title{text-align:center;margin-bottom:24px}
.lesson-box{border:1px solid #e5e5e5;border-radius:12px;padding:16px 18px;margin-bottom:18px;background:#fff}
.lesson-box h2{margin-top:0}
.code{background:#f8f9fb;color:#111;border:1px solid #ddd;border-radius:10px;padding:14px;overflow-x:auto;font-size:14px}
.code code{font-family:Consolas,monospace}
code{background:#eee;padding:2px 6px;border-radius:6px}
</style>
Bạn sẽ làm một Landing Page gồm các phần:
<!doctype html>
<html lang="vi">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bài 15 - Landing Page</title>
<style>
*{ box-sizing:border-box; }
html{ scroll-behavior: smooth; }
body{
margin:0;
font-family: Arial;
background:#f7f8fa;
line-height: 1.7;
color:#111;
}
/* ===== HEADER ===== */
header{
position: sticky;
top:0;
z-index: 10;
background:#111;
color:#fff;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.nav{
max-width: 1100px;
margin: 0 auto;
padding: 12px 16px;
display:flex;
align-items:center;
justify-content: space-between;
gap: 12px;
}
.brand{
font-weight: 800;
letter-spacing: 0.4px;
}
.menu{
display:flex;
gap: 12px;
flex-wrap:wrap;
justify-content: flex-end;
}
.menu a{
color:#fff;
text-decoration:none;
font-weight: 700;
padding: 6px 10px;
border-radius: 10px;
opacity: .9;
}
.menu a:hover{ background: rgba(255,255,255,0.12); opacity:1; }
.menu a.active{ background:#2563eb; opacity:1; }
/* ===== SECTIONS ===== */
.wrap{
max-width: 1100px;
margin: 0 auto;
padding: 18px 16px 40px;
}
section{
background:#fff;
border:1px solid #e5e5e5;
border-radius: 16px;
padding: 18px;
margin-top: 16px;
}
/* ===== HERO ===== */
.hero{
display:grid;
grid-template-columns: 1.2fr 0.8fr;
gap: 16px;
align-items:center;
overflow:hidden;
}
.hero h1{ margin:0 0 8px; font-size: 30px; }
.hero p{ margin:0; color:#333; }
.cta{
display:flex;
gap: 10px;
margin-top: 12px;
flex-wrap:wrap;
}
.btn{
display:inline-block;
padding: 10px 14px;
border-radius: 12px;
border:0;
cursor:pointer;
font-weight: 800;
text-decoration:none;
text-align:center;
}
.btn.primary{ background:#2563eb; color:#fff; }
.btn.dark{ background:#111; color:#fff; }
.hero-card{
border:1px solid #e5e5e5;
border-radius: 16px;
padding: 14px;
background: #f3f4f6;
}
.kpi{
display:grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-top: 10px;
}
.kpi .item{
background:#fff;
border:1px solid #e5e5e5;
border-radius: 14px;
padding: 12px;
text-align:center;
}
.kpi b{ font-size: 18px; }
/* ===== FEATURES ===== */
.grid3{
display:grid;
grid-template-columns: repeat(3, 1fr);
gap: 14px;
}
.feature{
border:1px solid #e5e5e5;
border-radius: 16px;
padding: 14px;
background:#fff;
}
.feature h3{ margin:0 0 8px; }
.feature p{ margin:0; color:#333; }
/* ===== GALLERY ===== */
.gallery{
display:grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
}
.ph{
height: 130px;
border-radius: 16px;
border:1px solid #e5e5e5;
background:#e5e7eb;
display:flex;
align-items:center;
justify-content:center;
font-weight: 800;
}
/* ===== CONTACT ===== */
.form{
display:grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
label{ font-weight: 700; display:block; margin-bottom: 6px; }
input, textarea{
width:100%;
padding: 10px 12px;
border:1px solid #ddd;
border-radius: 12px;
outline:none;
font-size: 14px;
}
textarea{ grid-column: 1 / -1; resize: vertical; min-height: 120px; }
.form .full{ grid-column: 1 / -1; }
.msg{
margin-top: 10px;
padding: 10px 12px;
border-radius: 12px;
background:#f3f4f6;
border:1px solid #e5e7eb;
color:#111;
}
/* ===== FOOTER ===== */
footer{
text-align:center;
padding: 18px 0 30px;
color:#555;
font-size: 14px;
}
/* ===== BACK TO TOP ===== */
#toTop{
position: fixed;
right: 16px;
bottom: 16px;
display:none;
padding: 10px 12px;
border-radius: 999px;
border:0;
cursor:pointer;
background:#111;
color:#fff;
font-weight: 800;
}
/* ===== RESPONSIVE ===== */
@media (max-width: 900px){
.hero{ grid-template-columns: 1fr; }
.grid3{ grid-template-columns: 1fr; }
.form{ grid-template-columns: 1fr; }
.menu{ justify-content:center; }
}
</style>
</head>
<body>
<header>
<div class="nav">
<div class="brand">Web Tĩnh Course</div>
<nav class="menu" id="menu">
<a href="#home" class="active">Home</a>
<a href="#features">Features</a>
<a href="#gallery">Gallery</a>
<a href="#contact">Contact</a>
</nav>
</div>
</header>
<div class="wrap">
<section id="home" class="hero">
<div>
<h1>Landing Page 1 trang – web tĩnh</h1>
<p>
Bạn đang xem một mẫu landing page dùng HTML + CSS + JavaScript.
Nó phù hợp để nhúng Blogspot, làm giới thiệu khóa học, sản phẩm, dịch vụ.
</p>
<div class="cta">
<a class="btn primary" href="#contact">Đăng ký ngay</a>
<button class="btn dark" id="btnDemo">Xem thông báo</button>
</div>
<div class="msg" id="heroMsg">Sẵn sàng.</div>
</div>
<div class="hero-card">
<b>Tóm tắt nhanh</b>
<div class="kpi">
<div class="item"><b>15</b><br>Bài học</div>
<div class="item"><b>3</b><br>Mini Project</div>
<div class="item"><b>HTML</b><br>Cấu trúc</div>
<div class="item"><b>JS</b><br>Tương tác</div>
</div>
</div>
</section>
<section id="features">
<h2>Features</h2>
<div class="grid3">
<div class="feature">
<h3>1) Dễ học</h3>
<p>Bố cục rõ ràng, code ngắn gọn, thực hành ngay.</p>
</div>
<div class="feature">
<h3>2) Chuẩn Blogspot</h3>
<p>Nhúng được vào bài viết/Trang mà không cần server.</p>
</div>
<div class="feature">
<h3>3) Có tương tác</h3>
<p>JavaScript xử lý nút bấm, form, và hiệu ứng.</p>
</div>
</div>
</section>
<section id="gallery">
<h2>Gallery</h2>
<p style="margin-top:0;color:#333">Mẫu lưới ảnh giả lập bằng CSS Grid.</p>
<div class="gallery">
<div class="ph">Ảnh 1</div>
<div class="ph">Ảnh 2</div>
<div class="ph">Ảnh 3</div>
<div class="ph">Ảnh 4</div>
<div class="ph">Ảnh 5</div>
<div class="ph">Ảnh 6</div>
</div>
</section>
<section id="contact">
<h2>Contact</h2>
<p style="margin-top:0;color:#333">Form demo (không gửi server), JS chỉ kiểm tra và hiển thị.</p>
<form id="contactForm" class="form">
<div>
<label for="cname">Họ tên</label>
<input id="cname" type="text" placeholder="Nhập họ tên..." required>
</div>
<div>
<label for="cemail">Email</label>
<input id="cemail" type="email" placeholder="vd: abc@gmail.com" required>
</div>
<div class="full">
<label for="cmsg">Nội dung</label>
<textarea id="cmsg" placeholder="Bạn muốn tư vấn gì?" required></textarea>
</div>
<div class="full">
<button class="btn primary" type="submit">Gửi</button>
<span class="msg" id="contactMsg">Chưa gửi.</span>
</div>
</form>
</section>
<footer>
© 2026 • Landing Page Demo • HTML/CSS/JS
</footer>
</div>
<button id="toTop" title="Lên đầu trang">↑</button>
<script>
// ===== 1) Nút demo thông báo =====
const btnDemo = document.getElementById("btnDemo");
const heroMsg = document.getElementById("heroMsg");
btnDemo.addEventListener("click", () => {
const t = new Date().toLocaleTimeString("vi-VN");
heroMsg.textContent = "Bạn vừa bấm nút lúc: " + t;
});
// ===== 2) Form contact (demo) =====
const form = document.getElementById("contactForm");
const contactMsg = document.getElementById("contactMsg");
form.addEventListener("submit", (e) => {
e.preventDefault();
const name = document.getElementById("cname").value.trim();
const email = document.getElementById("cemail").value.trim();
const message = document.getElementById("cmsg").value.trim();
if(name.length < 2){
contactMsg.textContent = "❌ Tên quá ngắn.";
return;
}
if(message.length < 5){
contactMsg.textContent = "❌ Nội dung quá ngắn.";
return;
}
contactMsg.textContent = "✅ Đã nhận: " + name + " (" + email + ")";
form.reset();
});
// ===== 3) Back to Top =====
const toTop = document.getElementById("toTop");
window.addEventListener("scroll", () => {
if(window.scrollY > 400) toTop.style.display = "block";
else toTop.style.display = "none";
highlightMenu();
});
toTop.addEventListener("click", () => {
window.scrollTo({ top: 0, behavior: "smooth" });
});
// ===== 4) Highlight menu theo section đang xem =====
const menuLinks = Array.from(document.querySelectorAll("#menu a"));
const sections = menuLinks
.map(a => document.querySelector(a.getAttribute("href")))
.filter(Boolean);
function highlightMenu(){
const y = window.scrollY + 120; // bù header
let currentId = "home";
sections.forEach(sec => {
if(sec.offsetTop <= y) currentId = sec.id;
});
menuLinks.forEach(a => {
const id = a.getAttribute("href").replace("#","");
a.classList.toggle("active", id === currentId);
});
}
highlightMenu();
</script>
</body>
</html>
👉 Copy code → lưu thành bai15.html → mở bằng Chrome.
Cuộn trang để thấy menu active đổi theo section + nút “↑” hiện khi cuộn xuống.
<img>).1) Thêm section Pricing (gợi ý)
<a href="#pricing">Pricing</a>
<section id="pricing">
<h2>Pricing</h2>
...
</section>
fetch() để lấy dữ liệu dạng JSONthen() hoặc async/await (mình dùng async/await cho dễ học)1) API là gì?
API là nơi cung cấp dữ liệu cho ứng dụng. Web của bạn có thể “xin dữ liệu” từ API rồi hiển thị ra giao diện. Ví dụ: danh sách bài viết, sản phẩm, đơn hàng...
2) Fetch API
fetch("https://example.com/api")
.then(res => res.json())
.then(data => console.log(data));
3) Async/Await (dễ đọc hơn)
async function load(){
const res = await fetch(url);
const data = await res.json();
}
Lưu ý: Nếu mở file .html offline, một số trình duyệt vẫn cho gọi API HTTPS bình thường.
Nếu máy bạn chặn, hãy thử chạy bằng Live Server hoặc đưa lên Blogspot.
<!doctype html>
<html lang="vi">
<head>
<meta charset="utf-8">
<title>Bài 14 - Fetch API</title>
<style>
body{
font-family: Arial;
background:#f7f8fa;
padding: 24px;
line-height: 1.7;
}
.app{
max-width: 900px;
margin: 0 auto;
background:#fff;
border:1px solid #e5e5e5;
border-radius: 14px;
padding: 18px;
}
h1{ margin-top:0; text-align:center; }
.toolbar{
display:flex;
gap:10px;
flex-wrap:wrap;
justify-content: center;
margin: 12px 0 16px;
}
button{
padding:10px 14px;
border:0;
border-radius: 10px;
cursor:pointer;
background:#2563eb;
color:#fff;
font-weight:700;
}
button.gray{ background:#111; }
input{
padding:10px 12px;
border:1px solid #ddd;
border-radius: 10px;
min-width: 240px;
}
.status{
padding: 10px 12px;
border-radius: 12px;
background:#f3f4f6;
border:1px solid #e5e7eb;
margin-bottom: 12px;
}
.list{
display:grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 14px;
}
.card{
border:1px solid #e5e5e5;
border-radius: 12px;
padding: 14px;
background:#fff;
}
.card h3{
margin: 0 0 8px;
font-size: 16px;
}
.card p{
margin: 0;
color:#333;
font-size: 14px;
}
.muted{ color:#555; }
</style>
</head>
<body>
<div class="app">
<h1>🌐 Fetch API - Danh sách bài viết</h1>
<div class="toolbar">
<button id="btnLoad">Tải dữ liệu</button>
<button class="gray" id="btnClear">Xóa hiển thị</button>
<input id="search" type="text" placeholder="Tìm theo tiêu đề...">
</div>
<div class="status" id="status">
<span class="muted">Chưa tải dữ liệu.</span>
</div>
<div class="list" id="list"></div>
</div>
<script>
const API_URL = "https://jsonplaceholder.typicode.com/posts";
const btnLoad = document.getElementById("btnLoad");
const btnClear = document.getElementById("btnClear");
const search = document.getElementById("search");
const statusEl = document.getElementById("status");
const listEl = document.getElementById("list");
let posts = [];
function setStatus(text){
statusEl.innerHTML = text;
}
function render(list){
listEl.innerHTML = "";
if(list.length === 0){
listEl.innerHTML = "<p class='muted'>Không có dữ liệu để hiển thị.</p>";
return;
}
list.forEach(p => {
const div = document.createElement("div");
div.className = "card";
div.innerHTML = `
<h3>${p.id}. ${p.title}</h3>
<p>${p.body}</p>
`;
listEl.appendChild(div);
});
}
async function loadPosts(){
try{
setStatus("⏳ Đang tải dữ liệu...");
const res = await fetch(API_URL);
if(!res.ok){
throw new Error("HTTP " + res.status);
}
const data = await res.json();
// lấy 12 bài cho gọn
posts = data.slice(0, 12);
setStatus("✅ Tải thành công: <b>" + posts.length + "</b> bài viết.");
render(posts);
} catch(err){
setStatus("❌ Lỗi khi tải dữ liệu: <b>" + err.message + "</b>");
}
}
function applySearch(){
const key = search.value.trim().toLowerCase();
if(!key){
render(posts);
return;
}
const filtered = posts.filter(p => p.title.toLowerCase().includes(key));
render(filtered);
}
btnLoad.addEventListener("click", loadPosts);
btnClear.addEventListener("click", () => {
posts = [];
listEl.innerHTML = "";
search.value = "";
setStatus("<span class='muted'>Đã xóa hiển thị.</span>");
});
search.addEventListener("input", applySearch);
</script>
</body>
</html>
👉 Copy code → lưu thành bai14.html → mở bằng Chrome.
Bấm “Tải dữ liệu”, sau đó gõ tìm kiếm theo tiêu đề.
https://jsonplaceholder.typicode.com/users và hiển thị tên + email.1) Đổi sang users
const API_URL = "https://jsonplaceholder.typicode.com/users";
...
div.innerHTML = `
<h3>${u.name}</h3>
<p>${u.email}</p>
`;
2) Lấy thời gian tải (gợi ý)
const t = new Date();
const timeText = t.toLocaleTimeString("vi-VN");
setStatus("✅ Tải xong lúc: <b>" + timeText + "</b>");
push, filter, map, find, splicelocalStorage1) Array (Mảng): danh sách nhiều phần tử.
const nums = [1, 2, 3];
nums.push(4); // thêm cuối
2) Object (Đối tượng): lưu dữ liệu theo cặp key–value.
const product = { id: 1, name: "Nước suối", price: 10000 };
console.log(product.name); // "Nước suối"
3) Mảng object (rất hay gặp trong web)
const products = [
{ id: 1, name: "Cà phê", price: 25000 },
{ id: 2, name: "Trà sữa", price: 35000 }
];
4) Các hàm mảng quan trọng
map(): biến đổi mảng → mảng mớifilter(): lọc theo điều kiệnfind(): tìm 1 phần tửsplice(): xóa/sửa tại vị trí index
<!doctype html>
<html lang="vi">
<head>
<meta charset="utf-8">
<title>Bài 13 - Array & Object CRUD</title>
<style>
body{
font-family: Arial;
background:#f7f8fa;
padding: 24px;
line-height: 1.7;
}
.app{
max-width: 860px;
margin: 0 auto;
background:#fff;
border:1px solid #e5e5e5;
border-radius: 14px;
padding: 18px;
}
h1{ margin-top:0; text-align:center; }
.grid{
display:grid;
grid-template-columns: 1fr 1fr 1fr auto;
gap: 10px;
margin: 12px 0;
}
@media (max-width: 800px){
.grid{ grid-template-columns: 1fr 1fr; }
}
input{
padding:10px 12px;
border:1px solid #ddd;
border-radius: 10px;
width:100%;
box-sizing:border-box;
}
button{
padding:10px 14px;
border:0;
border-radius: 10px;
cursor:pointer;
background:#2563eb;
color:#fff;
font-weight:700;
}
button.gray{ background:#111; }
button.red{ background:#ef4444; }
.toolbar{
display:flex;
gap:10px;
flex-wrap:wrap;
align-items:center;
justify-content: space-between;
margin-top: 8px;
}
.table{
width: 100%;
border-collapse: collapse;
margin-top: 12px;
}
.table th, .table td{
border: 1px solid #ddd;
padding: 10px;
text-align:left;
vertical-align: top;
}
.table th{
background:#111;
color:#fff;
}
.badge{
display:inline-block;
padding:2px 10px;
border-radius: 999px;
background:#eef2ff;
border:1px solid #dbeafe;
font-size: 12px;
margin-right: 6px;
}
.msg{
margin-top: 10px;
padding: 10px 12px;
border-radius: 12px;
background:#f3f4f6;
border:1px solid #e5e7eb;
color:#111;
}
</style>
</head>
<body>
<div class="app">
<h1>🧾 Quản lý sản phẩm (Array + Object)</h1>
<div class="grid">
<input id="name" type="text" placeholder="Tên sản phẩm (vd: Trà sữa)">
<input id="price" type="number" placeholder="Giá (vd: 35000)">
<input id="category" type="text" placeholder="Danh mục (vd: Đồ uống)">
<button id="btnAdd">Thêm</button>
</div>
<div class="toolbar">
<div>
<span class="badge">Thêm</span>
<span class="badge">Xóa</span>
<span class="badge">Tìm kiếm</span>
</div>
<div style="display:flex; gap:10px; flex-wrap:wrap;">
<input id="search" type="text" placeholder="Tìm theo tên..." style="min-width:220px">
<button class="gray" id="btnClear">Xóa hết</button>
</div>
</div>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Tên</th>
<th>Danh mục</th>
<th>Giá</th>
<th>Thao tác</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
<div class="msg" id="msg">Sẵn sàng.</div>
</div>
<script>
// ====== 1) Dữ liệu ban đầu (mảng object) ======
let products = [
{ id: 1, name: "Cà phê", category: "Đồ uống", price: 25000 },
{ id: 2, name: "Trà sữa", category: "Đồ uống", price: 35000 },
{ id: 3, name: "Snack", category: "Ăn vặt", price: 15000 }
];
// ====== 2) Lấy element ======
const elName = document.getElementById("name");
const elPrice = document.getElementById("price");
const elCategory = document.getElementById("category");
const btnAdd = document.getElementById("btnAdd");
const elSearch = document.getElementById("search");
const btnClear = document.getElementById("btnClear");
const tbody = document.getElementById("tbody");
const msg = document.getElementById("msg");
// ====== 3) Helpers ======
function formatMoney(n){
return Number(n).toLocaleString("vi-VN") + " đ";
}
function setMsg(text){
msg.textContent = text;
}
function nextId(){
// lấy id lớn nhất + 1
const maxId = products.length ? Math.max(...products.map(p => p.id)) : 0;
return maxId + 1;
}
// ====== 4) Render ======
function render(list){
tbody.innerHTML = "";
if(list.length === 0){
tbody.innerHTML = '<tr><td colspan="5">Không có dữ liệu.</td></tr>';
return;
}
list.forEach(p => {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${p.id}</td>
<td>${p.name}</td>
<td>${p.category}</td>
<td>${formatMoney(p.price)}</td>
<td><button class="red" data-id="${p.id}">Xóa</button></td>
`;
tbody.appendChild(tr);
});
// gắn sự kiện xóa cho các nút vừa render
tbody.querySelectorAll("button[data-id]").forEach(btn => {
btn.addEventListener("click", () => {
const id = Number(btn.dataset.id);
removeById(id);
});
});
}
// ====== 5) Thêm ======
function addProduct(){
const name = elName.value.trim();
const category = elCategory.value.trim();
const price = Number(elPrice.value);
if(name.length < 2){
setMsg("❌ Tên sản phẩm quá ngắn.");
return;
}
if(!category){
setMsg("❌ Vui lòng nhập danh mục.");
return;
}
if(!price || price <= 0){
setMsg("❌ Giá không hợp lệ.");
return;
}
const p = { id: nextId(), name, category, price };
products.push(p); // push vào mảng
elName.value = "";
elCategory.value = "";
elPrice.value = "";
setMsg("✅ Đã thêm: " + p.name);
applySearch(); // render theo filter hiện tại
}
// ====== 6) Xóa ======
function removeById(id){
const idx = products.findIndex(p => p.id === id);
if(idx === -1) return;
const removed = products[idx];
products.splice(idx, 1); // xóa 1 phần tử tại idx
setMsg("🗑️ Đã xóa: " + removed.name);
applySearch();
}
// ====== 7) Tìm kiếm ======
function applySearch(){
const key = elSearch.value.trim().toLowerCase();
if(!key){
render(products);
return;
}
const filtered = products.filter(p => p.name.toLowerCase().includes(key));
render(filtered);
}
// ====== 8) Xóa hết ======
function clearAll(){
if(!confirm("Bạn chắc chắn muốn xóa hết dữ liệu?")) return;
products = [];
setMsg("Đã xóa hết dữ liệu.");
applySearch();
}
// ====== 9) Gắn sự kiện ======
btnAdd.addEventListener("click", addProduct);
elSearch.addEventListener("input", applySearch);
btnClear.addEventListener("click", clearAll);
// ====== 10) Render lần đầu ======
render(products);
</script>
</body>
</html>
👉 Copy code → lưu thành bai13.html → mở bằng Chrome.
Thử thêm sản phẩm, gõ tìm kiếm, bấm xóa từng dòng.
products vào localStorage và tải lại vẫn còn.1) Lưu localStorage (gợi ý)
// Lưu
localStorage.setItem("products", JSON.stringify(products));
// Tải
products = JSON.parse(localStorage.getItem("products") || "[]");
2) Sắp xếp tăng dần
products.sort((a,b) => a.price - b.price);
render(products);
Bạn sẽ làm một trang Quiz có 5 câu hỏi:
name).
<!doctype html>
<html lang="vi">
<head>
<meta charset="utf-8">
<title>Bài 12 - Quiz trắc nghiệm</title>
<style>
body{
font-family: Arial;
background:#f7f8fa;
padding: 24px;
line-height: 1.7;
}
.app{
max-width: 760px;
margin: 0 auto;
background:#fff;
border:1px solid #e5e5e5;
border-radius: 14px;
padding: 18px;
}
h1{ margin-top:0; text-align:center; }
.q{
border:1px solid #e5e5e5;
border-radius: 12px;
padding: 14px;
margin: 12px 0;
background:#fff;
}
.q.correct{ background:#ecfdf5; border-color:#bbf7d0; }
.q.wrong{ background:#fff1f2; border-color:#fecdd3; }
.answers label{
display:block;
padding: 6px 0;
cursor:pointer;
}
.row{
display:flex;
gap: 10px;
flex-wrap: wrap;
justify-content: center;
margin-top: 14px;
}
button{
padding: 10px 14px;
border:0;
border-radius: 10px;
cursor:pointer;
background:#2563eb;
color:#fff;
font-weight: 700;
}
button.gray{ background:#111; }
.result{
margin-top: 14px;
padding: 12px;
border-radius: 12px;
background:#f3f4f6;
border:1px solid #e5e7eb;
}
small{ color:#555; }
</style>
</head>
<body>
<div class="app">
<h1>🧠 Quiz trắc nghiệm</h1>
<p>Chọn đáp án đúng rồi bấm <b>Nộp bài</b>.</p>
<div id="quiz"></div>
<div class="row">
<button id="btnSubmit">Nộp bài</button>
<button class="gray" id="btnReset">Làm lại</button>
</div>
<div class="result" id="result">
<small>Kết quả sẽ hiển thị ở đây.</small>
</div>
</div>
<script>
// 1) Dữ liệu câu hỏi (có thể thêm bớt dễ dàng)
const questions = [
{
text: "HTML dùng để làm gì?",
options: ["Tạo cấu trúc", "Làm đẹp", "Tạo tương tác", "Lưu dữ liệu"],
answer: 0
},
{
text: "CSS dùng để làm gì?",
options: ["Chấm điểm", "Làm đẹp giao diện", "Tạo server", "Tạo database"],
answer: 1
},
{
text: "Thẻ nào tạo liên kết?",
options: ["<img>", "<a>", "<p>", "<div>"],
answer: 1
},
{
text: "Trong JS, từ khóa khai báo biến có thể đổi giá trị là?",
options: ["const", "let", "varrr", "fixed"],
answer: 1
},
{
text: "Thuộc tính nào bắt buộc nhập form?",
options: ["required", "checked", "href", "alt"],
answer: 0
}
];
const quizEl = document.getElementById("quiz");
const resultEl = document.getElementById("result");
const btnSubmit = document.getElementById("btnSubmit");
const btnReset = document.getElementById("btnReset");
// 2) Render câu hỏi ra HTML
function renderQuiz(){
quizEl.innerHTML = "";
questions.forEach((q, i) => {
const box = document.createElement("div");
box.className = "q";
box.setAttribute("data-index", i);
let html = "<h3>Câu " + (i+1) + ": " + q.text + "</h3>";
html += '<div class="answers">';
q.options.forEach((opt, j) => {
html +=
'<label>' +
'<input type="radio" name="q' + i + '" value="' + j + '"> ' +
opt +
'</label>';
});
html += "</div>";
box.innerHTML = html;
quizEl.appendChild(box);
});
resultEl.innerHTML = "<small>Kết quả sẽ hiển thị ở đây.</small>";
}
// 3) Chấm điểm
function grade(){
let correct = 0;
const boxes = document.querySelectorAll(".q");
boxes.forEach((box, i) => {
box.classList.remove("correct", "wrong");
const checked = document.querySelector('input[name="q' + i + '"]:checked');
const userAnswer = checked ? Number(checked.value) : -1;
if(userAnswer === questions[i].answer){
correct++;
box.classList.add("correct");
} else {
box.classList.add("wrong");
}
});
const total = questions.length;
const score = Math.round((correct / total) * 10);
resultEl.innerHTML =
"✅ Đúng: <b>" + correct + "/" + total + "</b> câu" +
"<br>🎯 Điểm: <b>" + score + "/10</b>";
}
// 4) Reset
function resetQuiz(){
renderQuiz();
}
btnSubmit.addEventListener("click", grade);
btnReset.addEventListener("click", resetQuiz);
// chạy lần đầu
renderQuiz();
</script>
</body>
</html>
👉 Copy code → lưu thành bai12.html → mở bằng Chrome.
Bấm “Nộp bài” để chấm điểm và xem câu đúng/sai được tô màu.
questions.1) Thang điểm 100
const score = Math.round((correct / total) * 100);
2) Kiểm tra bỏ trống
let unanswered = 0;
const checked = document.querySelector('input[name="q' + i + '"]:checked');
if(!checked) unanswered++;
if(unanswered > 0){
resultEl.innerHTML = "⚠️ Bạn chưa làm hết: " + unanswered + " câu.";
return;
}
3) Hiển thị đáp án đúng (gợi ý)
// Sau khi chấm, bạn có thể thêm dòng:
box.innerHTML += "<p><b>Đáp án đúng:</b> " + questions[i].options[questions[i].answer] + "</p>";
📌 Danh sách bình luận