0

[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:

  1. Cột bên trái (Chiếm 23\frac{2}{3} 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.

  2. Cột bên phải (Chiếm 13\frac{1}{3} 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:

image.png

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ết 2fr 1fr, trình duyệt tự động lấy tổng chiều rộng 1200px trừ đi khe hở gap: 5px ở giữa (còn 1195px), 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) ≈796.67px\approx 796.67\text{px}.

    • Cột 2 (Hai Banner nhỏ bên phải) nhận 1 phần (1fr) ≈398.33px\approx 398.33\text{px}. Bạn không cần phải bấm máy tính hay lo vỡ khung khi thêm gap!

  • Gộp 2 dòng cho Banner lớn bên trái (grid-row: 1 / 3 hoặc span 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:

image.png

Để 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);
}

image.png

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

  1. Sự ăn khớp toán học tuyệt đối giữa grid-template-rows và 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 cao 11.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: 115px (Doˋng 1)+5px (Khe hở)+115px (Doˋng 2)=235px115\text{px (Dòng 1)} + 5\text{px (Khe hở)} + 115\text{px (Dòng 2)} = 235\text{px}!

    • Dù người bán hàng có tải lên một tấm ảnh cao 500px hay thấp 100px, khung lưới vẫn giữ cứng kích thước 235px bên trái và hai khung 115px bên phải thẳng tắp như kẻ chỉ!

  2. Cặp bài trùng overflow: hidden trên thẻ cha và object-fit: cover trê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êm overflow: hidden giú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ính overflow: hidden nà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!

  3. Thuộc tính gap: 0.5rem thông minh hơn margin gấ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ết margin-right: 5px cho cột trái và margin-bottom: 5px cho tấm ảnh nhỏ phía trên, rồi lại phải dùng :last-child để xóa margin-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:

  1. 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ữ grid nằm ngay cạnh thẻ. Hãy bấm chuột vào nút grid đó! Lập tức Chrome sẽ vẽ đè các đường kẻ网格 (Grid Lines số 1, 2, 3) và tô màu khe hở gap ngay trên màn hình giúp bạn nhìn thấu suốt cấu trúc 2 chiều!

  2. Trong file main.css, hãy thử đổi .hero-banner-main từ grid-column: 1 / 2; sang grid-column: 2 / 3; (và đổi grid-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

Viblo
Let's register a Viblo Account to get more interesting posts.