Bài 15: Mini Project – Landing Page 1 trang (HTML + CSS + JS)
🎯 Mục tiêu
- Vận dụng HTML semantic + CSS (Flex/Grid) để dựng landing page
- Biết tạo menu cuộn đến section (anchor link)
- Biết làm nút “Lên đầu trang” bằng JavaScript
- Biết tạo hiệu ứng scroll mượt
- (Nâng cao) Hiển thị trạng thái menu đang chọn
📘 Mô tả sản phẩm
Bạn sẽ làm một Landing Page gồm các phần:
- Header + Menu
- Hero (giới thiệu) + nút CTA
- Features (3–6 tính năng)
- Gallery (lưới ảnh giả lập)
- Contact (form)
- Footer
💻 Code mẫu (Landing Page hoàn chỉnh)
<!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.
✍️ Bài tập
- Đổi nội dung “Features” thành đúng khóa học của bạn (HTML/CSS/JS).
- Thêm section mới “Pricing” (bảng giá) và thêm vào menu.
- Đổi gallery thành ảnh thật (dùng
<img>). - (Nâng cao) Làm menu dạng nút hamburger trên mobile.
✅ Đáp án gợi ý
1) Thêm section Pricing (gợi ý)
<a href="#pricing">Pricing</a>
<section id="pricing">
<h2>Pricing</h2>
...
</section>
📌 Danh sách bình luận