[Series 100 Bài Học HTML/CSS Thực Chiến - Bài 09]: Khởi Động Chương 2 — Làm Chủ CSS Grid Layout Dựng Cụm Hero Banner Bất Đối Xứng (Tỷ Lệ 2:1) Chuẩn Shopee
Chào mừng anh em chính thức bước sang Chương 2 với Bài 09 trong chuỗi 100 Bài Học Làm Chủ HTML/CSS Qua Dự Án Clone Shopee!
Ở Chương 1 (từ Bài 01 đến Bài 08), chúng ta đã chinh phục hoàn toàn thanh Sticky Header phức tạp của Shopee bằng vũ khí chủ lực là Flexbox. Hôm nay, khi bước xuống phần thân trang (<main>), thử thách đầu tiên đón chờ chúng ta chính là cụm Hero Banner (Khuyến mãi nổi bật) — khu vực "mặt tiền" thu hút hàng triệu lượt click mỗi ngày trên Shopee.
Nếu quan sát kỹ cụm Hero Banner của Shopee trên máy tính, bạn sẽ thấy một bố cục bất đối xứng 2 chiều rất đặc trưng:
-
Cột bên trái (Chiếm chiều ngang): Là một khung Banner Slider lớn kéo dài suốt chiều cao của cả 2 tầng.
-
Cột bên phải (Chiếm chiều ngang): Được chia đôi theo chiều dọc thành 2 tấm Banner tĩnh nhỏ xếp chồng khít lên nhau, ở giữa có một khe hở mảnh đúng
5px(0.5rem).
Để giải quyết bài toán chia lưới vừa theo hàng ngang vừa theo cột dọc như thế này một cách thanh lịch nhất, hôm nay chúng ta sẽ mở khóa vũ khí dàn trang mạnh nhất của CSS hiện đại: CSS Grid Layout (display: grid)!
1. Tại sao không dùng tiếp Flexbox mà phải dùng CSS Grid cho Hero Banner?
Nhiều bạn khi đã quen tay với display: flex ở Chương 1 sẽ thắc mắc: "Tại sao mình không tạo 1 thẻ div cha dùng flex-direction: row để chia 2 cột trái - phải, rồi bên trong cột phải lại tạo thêm 1 thẻ div con dùng flex-direction: column để xếp chồng 2 tấm ảnh nhỏ?"
Bạn hoàn toàn làm được như vậy với Flexbox! Tuy nhiên, cách đó buộc bạn phải đẻ thêm các thẻ <div class="wrapper"> thừa thãi trong HTML chỉ để phục vụ mục đích chia cột, và rất vất vả để căn cho tổng chiều cao của 2 ảnh nhỏ bên phải cộng với khe hở 5px bằng khít 100% chiều cao của ảnh lớn bên trái.
Hãy nhìn vào sơ đồ so sánh bản chất giữa Flexbox (1 chiều - 1D) và CSS Grid (2 chiều - 2D) dưới đây để hiểu tại sao các kỹ sư Frontend lại phân chia ranh giới sử dụng rất rõ ràng:

Sự khác biệt cốt lõi giữa hai công cụ này khi áp dụng vào dự án Shopee nằm ở bảng sau:
| Tiêu chí so sánh | Flexbox (display: flex) |
CSS Grid (display: grid) |
|---|---|---|
| Hệ quy chiếu không gian | 1 Chiều (1D): Chỉ điều khiển tốt 1 Hàng HOẶC 1 Cột tại một thời điểm. | 2 Chiều (2D): Điều khiển đồng thời cả Hàng (Row) VÀ Cột (Column) trong cùng một thẻ cha duy nhất. |
| Cấu trúc HTML | Phải lồng nhiều thẻ <div> bọc ngoài (Nested Flex) nếu muốn chia bố cục phức tạp. |
HTML phẳng (Flat HTML): Chỉ cần 1 thẻ cha bọc trực tiếp các phần tử con, CSS Grid tự lo việc sắp xếp và gộp ô! |
| Đơn vị chia tỷ lệ | Dùng flex: 1, flex: 2 kết hợp tính toán phần trăm width. |
Sở hữu đơn vị độc quyền fr (Fraction - Phần bằng nhau) tự động trừ đi khoảng hở gap trước khi chia! |
2. Giải phẫu lưới CSS Grid của Hero Banner Shopee: Đơn vị fr và Gộp dòng (grid-row)
Hãy tưởng tượng khu vực .hero-banner-grid của chúng ta là một bảng lưới gồm 2 cột và 2 dòng:
-
Chia cột (
grid-template-columns: 2fr 1fr): Đơn vịfr(Fraction) đại diện cho số phần của không gian trống. Khi viết2fr 1fr, trình duyệt tự động lấy tổng chiều rộng1200pxtrừ đi khe hởgap: 5pxở giữa (còn1195px), sau đó chia làm 3 phần bằng nhau:-
Cột 1 (Banner lớn bên trái) nhận 2 phần (
2fr) . -
Cột 2 (Hai Banner nhỏ bên phải) nhận 1 phần (
1fr) . Bạn không cần phải bấm máy tính hay lo vỡ khung khi thêmgap!
-
-
Gộp 2 dòng cho Banner lớn bên trái (
grid-row: 1 / 3hoặcspan 2): Trong CSS Grid, các đường kẻ ngang tạo nên dòng được đánh số thứ tự (Grid Lines) từ trên xuống dưới bắt đầu từ số1:-
Đường kẻ trên cùng của Banner là Line 1.
-
Đường kẻ giữa hai tấm ảnh nhỏ là Line 2.
-
Đường kẻ dưới đáy cùng của Banner là Line 3.
-
Hãy quan sát sơ đồ hệ thống đường kẻ (Grid Lines) và cách một phần tử chiếm trọn nhiều ô trong CSS Grid dưới đây:

Để tấm Banner lớn bên trái tự động kéo dài từ đỉnh xuống tận đáy (bằng tổng chiều cao của 2 tấm ảnh nhỏ bên phải cộng với khe hở 5px), chúng ta chỉ cần gắn cho nó thuộc tính:grid-row: 1 / 3; (Bắt đầu từ đường kẻ ngang số 1 và kết thúc ở đường kẻ ngang số 3) — hoặc viết ngắn gọn là grid-row: span 2; (Chiếm trọn 2 dòng)!
3. Thực hành: Code cụm Hero Banner Bất Đối Xứng Chuẩn Shopee
Bước 1: Dọn dẹp khung Full-Bleed trắng cho vùng Hero Banner
Trên trang chủ Shopee thật, khu vực Hero Banner (cùng dãy Icon dịch vụ bên dưới nó) nằm trong một dải nền màu trắng trải rộng 100% màn hình ngay dưới đáy Header, sau đó mới đến nền xám #f5f5f5 của các mục Danh Mục và Flash Sale.
Vì vậy, hãy mở file index.html, tìm đến thẻ <main> và cập nhật lại cấu trúc của .hero-banner-section như sau:
HTML
<!-- 2. VÙNG NỘI DUNG CHÍNH (MAIN CONTENT) -->
<main class="shopee-body">
<!-- KHỐI 1: VÙNG MẶT TIỀN HERO BANNER (Nền trắng trải rộng 100% màn hình) -->
<section class="hero-section-wrapper">
<div class="grid-container">
<!-- Lưới CSS Grid 2:1 chứa 3 tấm Banner -->
<div class="hero-banner-grid">
<!-- Ô 1: Banner Slider Lớn bên trái (Chiếm 2/3 chiều ngang & Gộp 2 dòng dọc) -->
<div class="hero-banner-main">
<a href="#" class="hero-banner-link">
<img src="https://placehold.co/800x235/ee4d2d/ffffff?text=SI%C3%8AU+SALE+11.11+-+GI%E1%BA%A2M+%C4%90%E1%BA%BEN+50%25+(800x235)" alt="Siêu Sale Shopee" class="hero-banner-img">
</a>
</div>
<!-- Ô 2: Banner Nhỏ Tầng Trên bên phải (Chiếm 1/3 chiều ngang, Dòng 1) -->
<div class="hero-banner-sub">
<a href="#" class="hero-banner-link">
<img src="https://placehold.co/400x115/ff6633/ffffff?text=SHOPEE+MALL+-+CH%C3%8DNH+H%C3%83NG+100%25" alt="Voucher Shopee Mall" class="hero-banner-img">
</a>
</div>
<!-- Ô 3: Banner Nhỏ Tầng Dưới bên phải (Chiếm 1/3 chiều ngang, Dòng 2) -->
<div class="hero-banner-sub">
<a href="#" class="hero-banner-link">
<img src="https://placehold.co/400x115/00bfa5/ffffff?text=FREESHIP+XTRA+-+BAO+SHIP+0%C4%90" alt="Freeship Xtra" class="hero-banner-img">
</a>
</div>
</div>
<!-- Nơi đặt dãy 8 Icon Dịch Vụ (Săn Sale, Mã Giảm Giá...) ở Bài 11 -->
</div>
</section>
<!-- KHỐI 2: CÁC SECTION NỘI DUNG TRÊN NỀN XÁM (Gom trong khung 1200px) -->
<div class="shopee-container grid-container">
<!-- Khu vực Danh mục ngành hàng -->
<section class="category-section">
<h2>Danh Mục</h2>
</section>
<!-- Khu vực Flash Sale -->
<section class="flash-sale-section">
<h2>Flash Sale</h2>
</section>
<!-- Khu vực Gợi ý hôm nay -->
<section class="daily-discover-section">
<h2>Gợi Ý Hôm Nay</h2>
<div class="product-grid">
<article class="product-card">
<h3>Áo Thun Nam Cotton Cao Cấp</h3>
<p class="product-price">159.000₫</p>
</article>
</div>
</section>
</div>
</main>
Bước 2: Viết CSS Grid chia tỷ lệ 2fr 1fr trong ./assets/css/main.css
Mở file main.css, tìm đến phần CSS của .shopee-container và các section bên dưới (đã viết ở Bài 02 và Bài 08) rồi cập nhật lại bằng đoạn code chuẩn chỉnh dưới đây:
CSS
/* ./assets/css/main.css */
/* --- CHƯƠNG 2: KHU VỰC HERO BANNER (CSS GRID LAYOUT) --- */
/* Dải nền trắng trải rộng toàn màn hình dưới đáy Header */
.hero-section-wrapper {
background-color: var(--white-color);
padding-top: 3rem; /* Cách đáy Header 30px chuẩn Shopee */
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--border-color);
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05);
}
/* Khung lưới CSS Grid 2 chiều */
.hero-banner-grid {
display: grid;
/* Chia 2 cột: Cột trái 2 phần (2fr), Cột phải 1 phần (1fr) */
grid-template-columns: 2fr 1fr;
/* Chia 2 dòng có chiều cao bằng nhau (mỗi dòng 115px -> Tổng cao 235px tính cả gap) */
grid-template-rows: 11.5rem 11.5rem;
/* Khe hở giữa các ô (cả ngang lẫn dọc) đúng 5px */
gap: 0.5rem;
}
/* Ô Banner lớn bên trái: Gộp 2 dòng dọc (Từ Line 1 đến Line 3) */
.hero-banner-main {
grid-column: 1 / 2; /* Nằm ở cột 1 */
grid-row: 1 / 3; /* Hoặc viết: grid-row: span 2; */
border-radius: 2px;
overflow: hidden; /* Cắt gọn góc ảnh theo border-radius 2px */
position: relative; /* Làm gốc tọa độ cho các nút điều hướng Slider ở Bài 10 */
}
/* Hai ô Banner nhỏ bên phải: Tự động lấp vào 2 ô trống còn lại ở Cột 2 */
.hero-banner-sub {
border-radius: 2px;
overflow: hidden;
}
.hero-banner-link {
display: block;
width: 100%;
height: 100%;
}
/* Ép ảnh luôn lấp đầy 100% diện tích ô Grid mà không bao giờ bị biến dạng */
.hero-banner-img {
width: 100%;
height: 100%;
object-fit: cover; /* Tự động căn tâm và cắt phần thừa nếu ảnh sai tỷ lệ */
transition: transform 0.3s ease;
}
/* Hiệu ứng phóng nhẹ sang trọng khi rê chuột vào 2 banner nhỏ bên phải */
.hero-banner-sub:hover .hero-banner-img {
transform: scale(1.02);
}
/* --- KHU VỰC CÁC SECTION THÂN TRANG TRÊN NỀN XÁM --- */
.shopee-container {
padding-top: 0.5rem;
padding-bottom: 5rem;
}
.category-section,
.flash-sale-section,
.daily-discover-section {
background-color: var(--white-color);
margin-top: 1.5rem;
padding: 2rem;
border-radius: 2px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
min-height: 35rem;
position: relative;
z-index: var(--z-elevate);
}

4. Giải mã dòng code: 3 Bí quyết giúp khối Banner không bao giờ bị lệch 1 Pixel
-
Sự ăn khớp toán học tuyệt đối giữa
grid-template-rowsvàgap:-
Bạn hãy nhìn vào phép tính chiều cao của
.hero-banner-grid: Chúng ta khai báo 2 dòng cao11.5rem(115px) và khoảng cách giữa hai dòng làgap: 0.5rem(5px). -
Nhờ
grid-row: 1 / 3, chiều cao thực tế của tấm Banner lớn bên trái sẽ tự động bằng: ! -
Dù người bán hàng có tải lên một tấm ảnh cao
500pxhay thấp100px, khung lưới vẫn giữ cứng kích thước235pxbên trái và hai khung115pxbên phải thẳng tắp như kẻ chỉ!
-
-
Cặp bài trùng
overflow: hiddentrên thẻ cha vàobject-fit: covertrên thẻ<img>:-
Nếu bạn đặt
border-radius: 2pxở thẻ<div class="hero-banner-main">, bản thân thẻ<div>đã bo tròn góc, nhưng tấm ảnh<img>hình chữ nhật sắc cạnh bên trong vẫn đâm xuyên qua 4 góc của thẻ<div>. Thêmoverflow: hiddengiúp "cắt gọt" 4 góc nhọn của tấm ảnh. -
Đồng thời, khi bạn thêm hiệu ứng phóng to
transform: scale(1.02)lúc:hover, chính thuộc tínhoverflow: hiddennày sẽ giữ cho tấm ảnh chỉ phóng to bên trong khung chứ không bị phình lem ra ngoài lấn vào khe hở5px!
-
-
Thuộc tính
gap: 0.5remthông minh hơnmargingấp 10 lần:-
Trước khi có CSS Grid, nếu muốn tạo khe hở
5pxở giữa, bạn phải viếtmargin-right: 5pxcho cột trái vàmargin-bottom: 5pxcho tấm ảnh nhỏ phía trên, rồi lại phải dùng:last-childđể xóamargin-bottomở tấm ảnh dưới. -
Với
gap: 0.5rem, CSS Grid hiểu rằng chỉ được tạo rãnh ngăn cách ở khe giữa các ô, hoàn toàn không đẩy lề ở 4 cạnh ngoài cùng!
-
5. Bài tập thực hành cuối bài & Hẹn gặp ở Bài 10!
Hãy lưu lại file index.html và main.css, sau đó nhấn F5 trên trình duyệt! Bạn sẽ thấy ngay dưới thanh Header màu cam là một cụm 3 Banner quảng cáo được chia tỷ lệ 2:1 chuẩn xác từng milimet. Hãy thử rê chuột vào 2 tấm Banner nhỏ bên phải để cảm nhận hiệu ứng zoom mượt mà!
Thử thách khám phá CSS Grid dành cho bạn trước khi sang Bài 10:
-
Mở công cụ Inspect (F12) trên Chrome, nhìn vào thẻ
<div class="hero-banner-grid">trong tab Elements, bạn sẽ thấy một nút nhỏ có chữgridnằm ngay cạnh thẻ. Hãy bấm chuột vào nútgridđó! Lập tức Chrome sẽ vẽ đè các đường kẻ网格 (Grid Lines số 1, 2, 3) và tô màu khe hởgapngay trên màn hình giúp bạn nhìn thấu suốt cấu trúc 2 chiều! -
Trong file
main.css, hãy thử đổi.hero-banner-maintừgrid-column: 1 / 2;sanggrid-column: 2 / 3;(và đổigrid-template-columns: 1fr 2fr;) xem tấm Banner lớn có lập tức nhảy sang bên phải và 2 tấm Banner nhỏ tự động dạt sang bên trái chỉ trong 1 nốt nhạc không nhé!
Ở Bài 10, chúng ta sẽ "thổi hồn" vào tấm Banner lớn bên trái (.hero-banner-main): Thiết kế các nút mũi tên điều hướng Trái/Phải (Prev / Next) tự động trượt ra khi rê chuột vào Banner, cùng dãy chấm tròn phân trang (Pagination Dots) ở đáy Banner!
All Rights Reserved