0

[Series 100 Bài Học HTML/CSS Thực Chiến - Bài 26]: Đại Phẫu Responsive Thân Trang Shopee — Kỹ Thuật Vuốt Ngang Mượt Như App (CSS Scroll Snap) & Co Giãn Lưới Sản Phẩm (6 -> 4 -> 2 Cột)

Chào mừng anh em đã bước sang Bài 26 trong chuỗi 100 Bài Học Làm Chủ HTML/CSS Qua Dự Án Clone Shopee!

Ở Bài 25, chúng ta đã thiết lập hệ thống 3 Breakpoints chuẩn mực trong responsive.css và biến hóa thanh Header Shopee từ giao diện Desktop (120px) thành giao diện Mobile App (68px) kèm thanh lọc nhanh 4 tab bám dính cực kỳ gọn gàng.

Tuy nhiên, khi bạn cuộn xuống Phần Thân Trang (<main class="shopee-body">) trên màn hình Điện thoại (375px) hoặc iPad (768px), bạn sẽ thấy hàng loạt khối nội dung đang bị "bóp nghẹt" đến biến dạng:

  1. Cụm Hero Banner (2fr 1fr) quá hẹp khiến 2 tấm banner nhỏ bên phải biến thành hai dải băng bé xíu không đọc nổi chữ.

  2. Dãy 8 Icon Dịch Vụ và Lưới 20 Danh Mục Ngành Hàng (10 cột) bị ép chồng chéo lên nhau; chữ "Thời Trang Nam", "Điện Thoại & Phụ Kiện" bị gãy thành 5–6 dòng dọc từng chữ cái một!

  3. Lưới 6 Sản Phẩm (Flash Sale & Gợi Ý Hôm Nay) trên màn hình 375px làm mỗi thẻ sản phẩm chỉ còn rộng vỏn vẹn 50px — ảnh bé bằng móng tay và giá tiền tràn ra ngoài khung!

Trong Bài 26 hôm nay, chúng ta sẽ giải quyết triệt để toàn bộ phần thân trang trên Tablet & Mobile bằng 2 vũ khí lợi hại nhất của Mobile Web hiện đại: Biến các danh sách bị tràn thành Thanh Vuốt Ngang Mượt Như App Native (overflow-x: auto + scroll-snap-type), và Hạ bậc cột CSS Grid (6 cột →\rightarrow 4 cột →\rightarrow 2 cột)!

1. Bí thuật "Vuốt Ngang Bắt Điểm" (CSS Scroll Snap): Biến Lưới Tĩnh thành Carousel Cảm Ứng Không Cần JS!

Trên màn hình PC rộng 1200px, chúng ta có đủ chỗ để bày cả 8 Icon dịch vụ hoặc 10 cột Danh mục ngành hàng lên cùng một hàng ngang. Nhưng trên màn hình điện thoại rộng chỉ 375px, bạn chỉ có thể hiển thị tối đa 4 đến 5 mục cùng lúc để ngón tay người dùng dễ bấm.

Vậy những mục còn lại (từ mục số 5 đến mục số 10) sẽ đi đâu? Trên ứng dụng Shopee Mobile, chúng được giấu sang bên phải mép màn hình để người dùng dùng ngón tay cái vuốt ngang (Horizontal Swipe)!

Thay vì phải nhúng các thư viện JavaScript nặng nề như Swiper.js hay Slick Slider, CSS hiện đại cung cấp bộ đôi thuộc tính scroll-snap-type (trên thẻ cha) và scroll-snap-align (trên thẻ con) giúp trình duyệt tự động "hít" các mục vào đúng mép màn hình mỗi khi người dùng ngừng vuốt tay!

Hãy quan sát sơ đồ minh họa cơ chế bắt điểm dừng (start, center, end) của thuộc tính scroll-snap-align khi cuộn ngang dưới đây:

image.png

Để biến một danh sách <ul> bình thường thành thanh trượt cảm ứng mượt như App iOS/Android, chúng ta kết hợp 4 dòng CSS vàng sau đây:

Các thuộc tính CSS tối ưu hóa thao tác vuốt ngang (Horizontal Scroll) trên Mobile & Tablet

Thuộc tính CSS Đặt ở đâu? Tác dụng thực chiến trên Mobile / Tablet
overflow-x: auto; Thẻ cha (.hero-service-list, .category-grid) Cho phép nội dung tràn sang bên phải và kích hoạt thao tác vuốt ngang bằng ngón tay trên màn hình cảm ứng.
scroll-snap-type: x mandatory; Thẻ cha Ra lệnh cho trục ngang (x) bắt buộc (mandatory) phải tự động hít vào mục gần nhất khi người dùng thả ngón tay ra (không bao giờ để một icon bị cắt đôi lơ lửng ở mép màn hình!).
scroll-snap-align: start; Thẻ con (.hero-service-item, .category-item) Điểm neo để mép trái của mục con khớp khít vào mép trái của khung nhìn.
::-webkit-scrollbar { display: none; } Thẻ cha Ẩn hoàn toàn thanh cuộn ngang màu xám thô kệch ở đáy danh sách, giúp giao diện sạch sẽ 100% như ứng dụng gốc!

2. Chiến lược "Hạ Bậc Cột" (Column Drop Strategy) cho Lưới Sản Phẩm & Footer

Đối với danh sách sản phẩm mua sắm (Gợi Ý Hôm Nay và Trang Danh Mục), người dùng có thói quen cuộn dọc từ trên xuống dưới vô tận. Do đó, chúng ta không dùng thanh vuốt ngang mà áp dụng chiến lược Giảm số cột theo từng mốc Breakpoint:

Plaintext

💻 DESKTOP (>= 1024px) : [SP1] [SP2] [SP3] [SP4] [SP5] [SP6]  --> repeat(6, 1fr) (6 cột/hàng)
📟 TABLET (740->1023px): [  SP1  ] [  SP2  ] [  SP3  ] [  SP4  ]  --> repeat(4, 1fr) (4 cột/hàng)
📱 MOBILE (<= 739px)   : [      SP1      ] [      SP2      ]  --> repeat(2, 1fr) (2 cột/hàng chuẩn Shopee App!)

Tại sao trên Mobile (<= 739px), mọi sàn Thương mại điện tử từ Shopee, Lazada, TikTok Shop đến Amazon đều chốt ở con số 2 cột sản phẩm / hàng (repeat(2, 1fr)) mà không phải 1 cột hay 3 cột?

  • Nếu để 1 cột: Ảnh sản phẩm quá to chiếm hết cả màn hình điện thoại, người dùng vuốt mỏi tay mới xem được 5 món đồ (Giảm khả năng so sánh giá).

  • Nếu để 3 cột: Chiều rộng mỗi thẻ trên màn hình 375px chỉ còn khoảng 115px — tên sản phẩm chỉ hiện được 2 chữ đã bị cắt ..., không đủ chỗ ghi chữ "Đã bán 4,2k".

  • 2 cột (repeat(2, 1fr)) là "tỷ lệ vàng" trên di động: Mỗi thẻ rộng khoảng 175px, vừa đủ để ảnh rõ nét, tên hiển thị đẹp 2 dòng và người dùng có thể so sánh trực tiếp 2 sản phẩm cạnh nhau!

3. Thực hành: Viết Code Responsive Thân Trang Trong ./assets/css/responsive.css

Ở Bài 25, chúng ta đã tạo file ./assets/css/responsive.css với 3 khung @media chuẩn. Bây giờ, hãy mở lại file responsive.css và bổ sung các quy tắc biến hóa phần Thân trang vào đúng 3 khu vực đó:

Bước 1: Cập nhật khu vực 1 — Chung cho cả Mobile & Tablet (max-width: 1023px)

Trong khối @media (max-width: 1023px) của file ./assets/css/responsive.css, bạn hãy bổ sung tiếp đoạn code dưới đây vào ngay bên dưới phần .header-cart-list::before:

CSS

/* Bổ sung tiếp vào bên trong @media (max-width: 1023px) của file ./assets/css/responsive.css */

    /* --- 1. TỐI ƯU HERO BANNER & THANH DỊCH VỤ TRÊN MOBILE/TABLET --- */
    .hero-section-wrapper {
        padding-top: 1.2rem;
    }

    /* Biến dãy 8 Icon Dịch vụ thành Thanh Vuốt Ngang (Scroll Snap) mượt mà */
    .hero-service-list {
        justify-content: flex-start;
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        gap: 0.8rem;
        padding-bottom: 0.6rem;
        -webkit-overflow-scrolling: touch; /* Tăng tốc độ quán tính khi vuốt trên iPhone/iPad */
    }

    /* Ẩn thanh cuộn ngang mặc định của trình duyệt */
    .hero-service-list::-webkit-scrollbar {
        display: none;
    }

    .hero-service-item {
        flex: 0 0 22%;              /* Mỗi icon chiếm 22% màn hình (Lộ nhẹ một phần icon thứ 5 để gợi ý vuốt!) */
        max-width: none;
        scroll-snap-align: start;
    }

    /* --- 2. TỐI ƯU LƯỚI 20 DANH MỤC NGÀNH HÀNG: VUỐT NGANG 2 TẦNG! --- */
    /* Ẩn nút mũi tên tròn của bản PC vì trên cảm ứng người dùng tự vuốt tay */
    .category-arrow-btn {
        display: none;
    }

    /* Bí thuật Grid Auto-Flow Column: Xếp 2 hàng và cho phép vuốt ngang sang phải! */
    .category-grid {
        grid-template-columns: unset;
        grid-auto-flow: column;     /* Ép các ô xếp theo cột dọc 2 tầng nối tiếp nhau sang phải */
        grid-template-rows: repeat(2, auto);
        grid-auto-columns: 22%;     /* Mỗi cột rộng 22% màn hình (Hiển thị ~4.5 cột cùng lúc) */
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        -webkit-overflow-scrolling: touch;
    }

    .category-grid::-webkit-scrollbar {
        display: none;
    }

    .category-item {
        scroll-snap-align: start;
    }

    /* --- 3. TỐI ƯU KHỐI FLASH SALE: VUỐT NGANG CÁC THẺ SALE --- */
    .flash-sale-grid {
        display: flex;              /* Chuyển từ Grid 6 cột cố định sang Flex vuốt ngang */
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        gap: 1.2rem;
        padding-bottom: 0.4rem;
    }

    .flash-sale-grid::-webkit-scrollbar {
        display: none;
    }

    .flash-sale-item {
        flex: 0 0 30%;              /* Mỗi sản phẩm Flash Sale chiếm 30% chiều ngang */
        scroll-snap-align: start;
    }

    /* --- 4. ẨN CỘT SIDEBAR BỘ LỌC TRÁI (2 CỘT) Ở CHƯƠNG 6 & MỞ RỘNG CỘT SẢN PHẨM RA 12 CỘT --- */
    .sidebar-col {
        display: none;              /* Trên Mobile/Tablet bộ lọc được đưa vào nút Drawer riêng */
    }

    .main-product-col {
        grid-column: span 12;       /* Cột sản phẩm chiếm trọn 100% (12/12 cột) màn hình! */
    }

    /* Điều chỉnh lại mốc bám dính của tiêu đề GỢI Ý HÔM NAY dưới Header Mobile (68px + 46px = 114px) */
    .daily-discover-header {
        top: calc(var(--header-height) + 4.6rem);
        height: 4.8rem;
    }

    /* --- 5. THU GỌN TRANG CHI TIẾT SẢN PHẨM & GIỎ HÀNG (CHƯƠNG 7 & 8) --- */
    .product-briefing {
        flex-direction: column;     /* Xếp chồng: Khung Ảnh ở trên, Thông tin mua hàng ở dưới */
        gap: 2rem;
    }

    .product-gallery {
        width: 100%;                /* Ảnh sản phẩm bung rộng 100% khung nhìn */
    }

    .product-summary {
        padding-right: 0;
    }

    .shop-info-bar {
        flex-direction: column;
        align-items: flex-start;
        gap: 2rem;
    }

    .shop-info__left {
        width: 100%;
        border-right: none;
        border-bottom: 1px solid rgba(0, 0, 0, 0.05);
        padding-right: 0;
        padding-bottom: 2rem;
    }

    .shop-info__right {
        width: 100%;
        padding-left: 0;
        column-gap: 2rem;
    }

    .product-content-layout {
        flex-direction: column;
    }

    .product-content__sidebar {
        width: 100%;
    }

Bước 2: Cập nhật khu vực 2 — Riêng cho Tablet / iPad (740px →\rightarrow 1023px)

Trong khối @media (min-width: 740px) and (max-width: 1023px) của file responsive.css, hãy bổ sung quy tắc chia 4 cột sản phẩm và 3 cột Footer cho màn hình máy tính bảng:

CSS

/* Bổ sung tiếp vào bên trong @media (min-width: 740px) and (max-width: 1023px) của ./assets/css/responsive.css */

    /* Trên iPad: Giữ nguyên lưới Hero Banner nhưng giảm chiều cao mỗi tầng xuống 95px cho cân đối */
    .hero-banner-grid {
        grid-template-rows: 9.5rem 9.5rem;
    }

    /* Lưới sản phẩm (Gợi Ý Hôm Nay & Danh Mục) trên iPad hiển thị 4 Cột / Hàng */
    .product-grid,
    .catalog-product-grid {
        grid-template-columns: repeat(4, 1fr);
    }

    /* Mega Footer trên iPad rút từ 5 cột xuống 3 cột */
    .footer-top-grid {
        grid-template-columns: repeat(3, 1fr);
    }

Bước 3: Cập nhật khu vực 3 — Riêng cho Điện thoại Mobile (max-width: 739px)

Trong khối @media (max-width: 739px) ở cuối file responsive.css, hãy bổ sung các quy tắc tối ưu riêng cho màn hình dọc của điện thoại:

CSS

/* Bổ sung tiếp vào bên trong @media (max-width: 739px) của ./assets/css/responsive.css */

    /* 1. Hero Banner trên Điện thoại: Chỉ giữ lại Banner Slider chính (1 cột), ẩn 2 banner phụ bên phải! */
    .hero-banner-grid {
        grid-template-columns: 1fr;
        grid-template-rows: 15rem;  /* Cao 150px vừa vặn màn hình điện thoại */
    }

    .hero-banner-main {
        grid-row: 1 / 2;
    }

    .hero-banner-sub {
        display: none;              /* Ẩn 2 banner nhỏ bên phải giống Shopee Mobile */
    }

    /* 2. Icon Dịch vụ & Danh mục trên màn hình nhỏ: Mỗi cột chiếm 23% (Hiện 4 cột + lấp ló cột 5) */
    .hero-service-item {
        flex: 0 0 23%;
    }

    .category-grid {
        grid-auto-columns: 28%;     /* Mỗi cột Danh mục rộng 28% màn hình điện thoại */
    }

    /* 3. Flash Sale trên Điện thoại: Mỗi sản phẩm chiếm 42% chiều ngang */
    .flash-sale-header {
        padding: 0 1.2rem;
    }

    .flash-sale-title {
        font-size: 1.6rem;
    }

    .flash-sale-body {
        padding: 1.2rem;
    }

    .flash-sale-item {
        flex: 0 0 42%;
    }

    /* 4. LƯỚI SẢN PHẨM TRÊN ĐIỆN THOẠI: Chuẩn 2 Cột / Hàng (repeat(2, 1fr)) */
    .product-grid,
    .catalog-product-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 0.8rem;                /* Khe hở 8px gọn gàng trên Mobile */
    }

    /* Nút "Xem thêm" co lại vừa khung màn hình điện thoại */
    .daily-discover-more-btn {
        min-width: 100%;
    }

    /* Thu gọn khoảng cách các số của Thanh Phân Trang (Pagination) để không tràn màn hình */
    .pagination {
        gap: 0.4rem;
    }

    .pagination-item__link {
        min-width: 3.2rem;
        height: 2.8rem;
        font-size: 1.6rem;
    }

    /* 5. Thu gọn Form Popup Đăng Ký / Đăng Nhập vừa khít màn hình điện thoại */
    .auth-form {
        width: calc(100vw - 2.4rem); /* Rộng bằng màn hình trừ đi 12px lề mỗi bên */
    }

    .auth-form__container {
        padding: 0 1.6rem;
    }

    .auth-form__socials {
        padding: 1.2rem 1.6rem;
        flex-direction: column;     /* Xếp chồng dọc các nút Facebook / Google trên màn hình hẹp */
    }

    /* 6. Mega Footer trên Điện thoại: Xếp thành 2 cột gọn gàng */
    .footer-top-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 1.6rem;
    }

    .footer-policy-list {
        flex-direction: column;
        gap: 1.2rem;
    }

    .footer-policy-item {
        border-right: none;
    }

image.png

4. Giải mã dòng code: 3 Tuyệt chiêu UX "Đáng đồng tiền bát gạo" trong Bài 26

  1. Nguyên lý "Peek-a-boo" (Lấp ló 22% / 28%) kích thích người dùng vuốt ngang:

    • Bạn có thắc mắc tại sao ở .hero-service-item chúng ta không chia tròn 25% (100%/4=25%100\% / 4 = 25\% để hiện chẵn 4 icon), mà lại đặt flex: 0 0 22% hoặc 28% ở .category-grid không?

    • Trong tâm lý học thiết kế Mobile UX, nếu bạn chia tròn 25% vừa khít 4 icon trên màn hình và lại ẩn đi thanh cuộn ngang (scrollbar), người dùng sẽ tưởng rằng danh sách chỉ có đúng 4 mục đó thôi và không bao giờ biết rằng có thể vuốt sang phải!

    • Đặt 22% hoặc 28% giúp mục thứ 5 bị cắt mất một nửa ở sát mép phải màn hình (Hiệu ứng Peek-a-boo / Affordance). Nhìn thấy nửa chiếc icon bị khuất, não bộ người dùng tự động hiểu ngay lập tức: "À! Còn nội dung ở bên phải, hãy lấy ngón tay gạt ngang qua!"

  2. Kỹ thuật grid-auto-flow: column + grid-template-rows: repeat(2, auto) trên Danh Mục Ngành Hàng:

    • Trên bản PC, 20 ngành hàng được chia cố định 10 cột (grid-template-columns: repeat(10, 1fr)). Nếu lên điện thoại bạn đổi thành repeat(4, 1fr), 20 ngành hàng sẽ rớt xuống thành 5 hàng dọc dài lê thê chiếm hết cả màn hình!

    • Bằng cách kết hợp grid-auto-flow: column và grid-template-rows: repeat(2, auto), CSS Grid sẽ giữ nguyên đúng 2 tầng (2 hàng ngang) như trên App Shopee, và tự động xếp các ngành hàng thứ 9, 10, 11... nối tiếp nhau về phía bên phải để người dùng vuốt ngang cả 2 tầng cùng lúc!

  3. Đơn vị 100vw kết hợp calc() cho Popup Modal trên điện thoại (.auth-form):

    • Ở Bài 16, chúng ta đặt .auth-form { width: 50rem; } (500px). Trên màn hình điện thoại rộng 375px, chiếc hộp 500px sẽ bị tràn mất cả 2 bên mép!

    • Đổi thành width: calc(100vw - 2.4rem); (Lấy 100% chiều rộng màn hình thiết bị trừ đi 24px) giúp cửa sổ Đăng Ký / Đăng Nhập luôn co giãn vừa vặn với mọi dòng điện thoại từ iPhone Mini đến Samsung Galaxy Fold, cách đều 2 mép trái phải đúng 12px!

5. Bài tập trải nghiệm thực tế & Hẹn gặp ở Bài 27!

Hãy lưu lại file ./assets/css/responsive.css, quay sang trình duyệt nhấn F5 và bật chế độ giả lập thiết bị di động (F12 →\rightarrow Ctrl + Shift + M), chọn iPhone 12 Pro (390px) hoặc iPad Air (820px):

  1. Hãy thử dùng chuột (trên chế độ mô phỏng cảm ứng, con trỏ chuột của bạn biến thành hình tròn ngón tay) kéo vuốt ngang qua lại trên dãy 8 Icon Dịch Vụ, Bảng 20 Danh Mục Ngành Hàng 2 tầng và Danh sách sản phẩm Flash Sale! Bạn sẽ cảm nhận được lực "hít" (scroll-snap) bắt dính từng cột vào mép trái mượt mà như đang lướt App Shopee thật trên điện thoại!

  2. Cuộn xuống mục Gợi Ý Hôm Nay: Các thẻ sản phẩm giờ đây xếp thành 2 cột cân đối trên iPhone (và 4 cột khi bạn chọn iPad Air), đầy đủ nhãn Yêu thích 3D, giá tiền và số lượng đã bán rõ ràng!

  3. Bấm thử vào nút Đăng Nhập hoặc mở Modal để thấy hộp thoại co gọn vừa khít màn hình điện thoại.

Thử thách khám phá dành cho bạn trước khi sang Bài 27:

  1. Hãy thử vào .category-grid trong responsive.css, tạm thời tắt dòng scroll-snap-type: x mandatory; đi rồi vuốt thử bảng Danh Mục để so sánh sự khác biệt giữa cuộn trôi tự do và cuộn có điểm neo bắt dính (Scroll Snap)!

  2. Trên màn hình Mobile (<= 739px), bảng Trang Giỏ Hàng (6 cột ở Bài 23) và Trang Thanh Toán (Bài 24) vẫn đang giữ cấu trúc nhiều cột của PC. Ở Bài 27 tiếp theo, chúng ta sẽ học cách biến một dòng bảng 6 cột của PC thành Thẻ Giỏ Hàng Rút Gọn (Mobile Cart Card) và hoàn thiện Thanh Điều Hướng Đáy Ứng Dụng (Mobile Bottom Tab Bar: Home | Mall | Live | Thông báo | Tôi)!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí