Bài 8.4: Thực hành responsive design

Chào mừng trở lại với hành trình chinh phục Front-end cùng FullhouseDev!

Trong thế giới số ngày nay, người dùng truy cập web từ vô số thiết bị với kích thước màn hình khác nhau – từ những chiếc smartphone nhỏ gọn, tablet, laptop cho đến màn hình desktop siêu rộng. Việc website của bạn hiển thị tốt và mang lại trải nghiệm nhất quán trên mọi thiết bị không còn là tùy chọn, mà là một yêu cầu bắt buộc. Đây chính là lúc Responsive Design (Thiết kế đáp ứng) tỏa sáng.

Ở bài này, chúng ta sẽ không chỉ nói về Responsive Design là gì, mà sẽ bắt tay vào thực hành để xây dựng những giao diện có khả năng "co giãn" linh hoạt. Chúng ta sẽ khám phá các công cụ đắc lực mà CSS cung cấp để biến ý tưởng Responsive thành hiện thực.

Hãy cùng đi sâu vào việc làm cho website của bạn thực sự sống độngthích ứng với mọi môi trường hiển thị!

1. Nền tảng Vững Chắc: Meta Viewport

Bước đầu tiên và quan trọng nhất để nói với trình duyệt rằng trang web của bạn sẵn sàng cho Responsive Design trên thiết bị di động là thêm thẻ meta viewport. Nếu thiếu nó, các trình duyệt di động có thể sẽ render trang web ở chế độ xem "desktop" rồi thu nhỏ lại, khiến chữ và các phần tử cực kỳ nhỏ và khó đọc.

Thêm dòng này vào phần <head> trong tệp HTML của bạn:

<meta name="viewport" content="width=device-width, initial-scale=1.0">
  • Giải thích code:
    • name="viewport": Cho trình duyệt biết đây là thẻ meta cấu hình viewport.
    • content="width=device-width, initial-scale=1.0": Đây là phần ma thuật.
      • width=device-width: Đặt chiều rộng của viewport bằng chiều rộng của thiết bị (tính bằng CSS pixels). Điều này đảm bảo 100% chiều rộng mà bạn định nghĩa trong CSS sẽ tương ứng với 100% chiều rộng màn hình thiết bị.
      • initial-scale=1.0: Đặt mức độ zoom ban đầu khi trang được tải. 1.0 nghĩa là không zoom gì cả (100%), hiển thị theo tỷ lệ thông thường.

Thiếu dòng này, mọi công sức CSS responsive của bạn trên di động có thể trở nên vô nghĩa. Hãy luôn nhớ thêm nó vào!

2. Sức Mạnh của Bố Cục Linh Hoạt: Flexbox và CSS Grid

Trước khi cần đến Media Queries (sẽ nói sau), chúng ta có thể đạt được sự linh hoạt đáng kinh ngạc chỉ với Flexbox và CSS Grid. Chúng được thiết kế để tạo ra các bố cục tự điều chỉnh dựa trên không gian có sẵn.

2.1. Flexbox (Hộp Linh Hoạt)

Flexbox tuyệt vời cho việc căn chỉnh các mục theo một hàng hoặc một cột, cho phép chúng co lại hoặc giãn ra để lấp đầy không gian hoặc tránh tràn.

Ví dụ: Tạo một thanh điều hướng ngang trên desktop, nhưng tự động chuyển thành dọc trên màn hình nhỏ.

<nav class="flex-nav">
  <a href="#">Trang chủ</a>
  <a href="#">Sản phẩm</a>
  <a href="#">Dịch vụ</a>
  <a href="#">Liên hệ</a>
</nav>
.flex-nav {
  display: flex; /* Biến container thành flex container */
  list-style: none;
  padding: 0;
  margin: 0;
  background-color: #f4f4f4;
  flex-wrap: wrap; /* Cho phép các mục xuống dòng nếu không đủ chỗ */
}

.flex-nav a {
  flex: 1; /* Mỗi mục sẽ cố gắng chiếm một phần bằng nhau */
  text-align: center;
  padding: 10px 15px;
  text-decoration: none;
  color: #333;
  border-right: 1px solid #ccc; /* Chỉ để minh họa */
}

.flex-nav a:last-child {
  border-right: none;
}

/* Khi không gian quá hẹp, các mục sẽ xuống dòng nhờ flex-wrap: wrap */
/* Chúng ta không cần media query ở đây để chúng tự động xuống dòng! */
  • Giải thích code:
    • display: flex;: Kích hoạt Flexbox cho container.
    • flex-wrap: wrap;: Đây là chìa khóa cho tính linh hoạt. Khi tổng chiều rộng của các a vượt quá chiều rộng của nav, các mục sẽ tự động xuống dòng mới.
    • flex: 1;: Đây là shorthand cho flex-grow: 1, flex-shrink: 1, flex-basis: 0%. Nó nói rằng mỗi mục a có thể lớn lên (flex-grow: 1), nhỏ lại (flex-shrink: 1), và kích thước cơ bản ban đầu là 0%. Kết quả là các mục sẽ cố gắng chia sẻ không gian một cách đồng đều trên một hàng. Khi xuống dòng, chúng sẽ chiếm 100% chiều rộng có sẵn trên hàng đó (trừ padding/gap).
2.2. CSS Grid (Lưới CSS)

CSS Grid là lựa chọn tuyệt vời cho bố cục hai chiều (hàng và cột), đặc biệt là bố cục trang tổng thể (layout structure). Nó cho phép bạn định nghĩa các "khu vực" hoặc "lưới" mà các phần tử con sẽ được đặt vào.

Ví dụ: Tạo một bố cục 3 cột trên desktop, 2 cột trên tablet, và 1 cột trên mobile chỉ với Grid và một chút điều chỉnh.

<div class="grid-container">
  <div class="grid-item">Mục 1</div>
  <div class="grid-item">Mục 2</div>
  <div class="grid-item">Mục 3</div>
  <div class="grid-item">Mục 4</div>
  <div class="grid-item">Mục 5</div>
  <div class="grid-item">Mục 6</div>
</div>
.grid-container {
  display: grid; /* Biến container thành grid container */
  /* Định nghĩa cột: lặp lại tự động các cột có kích thước tối thiểu 200px, tối đa 1fr */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 15px; /* Khoảng cách giữa các mục */
  padding: 15px;
  background-color: #e9e9e9;
}

.grid-item {
  background-color: #d4d4d4;
  padding: 20px;
  text-align: center;
  border-radius: 5px;
}

/* Với grid-template-columns: repeat(auto-fit, minmax(..., 1fr)),
   các mục sẽ tự động sắp xếp thành số cột tối đa có thể vừa
   với chiều rộng tối thiểu 200px. Khi không đủ chỗ, chúng sẽ
   tự động giảm số cột và giãn ra để lấp đầy. */
/* Một bố cục 3 cột, 2 cột, 1 cột có thể đạt được mà không cần media query phức tạp! */
  • Giải thích code:
    • display: grid;: Kích hoạt CSS Grid cho container.
    • grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));: Đây là công thức thần thánh cho bố cục lưới responsive tự động.
      • repeat(...): Lặp lại định nghĩa cột.
      • auto-fit: Tự động tạo ra nhiều cột nhất có thể vừa trong container mà không gây tràn.
      • minmax(200px, 1fr): Mỗi cột sẽ có chiều rộng tối thiểu200px. Nếu còn không gian thừa, nó sẽ mở rộng để chiếm một phần bằng nhau (1fr) của không gian đó. Khi không gian không đủ để tạo ra thêm một cột 200px, số lượng cột sẽ giảm xuống.

Flexbox và Grid là vũ khí bí mật giúp giảm thiểu sự phụ thuộc vào Media Queries cho nhiều kịch bản bố cục thông thường. Hãy ưu tiên sử dụng chúng!

3. Biến Ảnh và Video "Lỏng Lẻo": Flexible Media

Ảnh và video thường là thủ phạm chính gây tràn layout trên màn hình nhỏ nếu không được xử lý đúng cách. May mắn thay, việc làm cho chúng responsive lại rất đơn giản.

Áp dụng quy tắc CSS sau cho tất cả các thẻ <img>video:

img, video {
  max-width: 100%; /* Chiếm tối đa 100% chiều rộng của phần tử cha */
  height: auto;    /* Tự động tính chiều cao để giữ tỷ lệ khung hình */
  display: block;  /* (Tùy chọn) Loại bỏ khoảng trống dưới ảnh/video khi nằm trong dòng */
}
  • Giải thích code:
    • max-width: 100%;: Đây là dòng quan trọng nhất. Nó đảm bảo rằng hình ảnh hoặc video sẽ không bao giờ rộng hơn phần tử cha của nó. Nếu phần tử cha co lại, hình ảnh/video cũng sẽ co lại theo.
    • height: auto;: Khi chiều rộng được điều chỉnh bởi max-width, dòng này sẽ tự động tính lại chiều cao tương ứng để duy trì tỷ lệ khung hình ban đầu của hình ảnh/video, tránh bị méo.
    • display: block;: Theo mặc định, hình ảnh là phần tử inline. Dòng này chuyển nó thành block, giúp dễ dàng căn chỉnh và loại bỏ khoảng trống nhỏ phía dưới thường xuất hiện với ảnh inline.

Với quy tắc đơn giản này, ảnh và video của bạn sẽ tự động co giãn theo container của chúng, tránh làm hỏng bố cục trên màn hình nhỏ.

4. Công Cụ Điều Chỉnh Tinh Vi: Media Queries

Trong khi Flexbox và Grid giúp tạo bố cục linh hoạt, Media Queries cho phép bạn áp dụng các bộ quy tắc CSS khác nhau tùy thuộc vào các đặc điểm của thiết bị, chủ yếu là chiều rộng màn hình.

Cú pháp cơ bản của Media Query:

@media screen and (điều_kiện) {
  /* Các quy tắc CSS chỉ áp dụng khi điều_kiện đúng */
}
  • @media screen: Chỉ áp dụng cho màn hình (loại phổ biến nhất). Bạn có thể sử dụng print cho máy in, speech cho trình đọc màn hình, v.v.
  • and (điều_kiện): Chỉ định điều kiện cần thỏa mãn. Điều kiện phổ biến nhất liên quan đến chiều rộng:
    • (min-width: XXXpx): Áp dụng khi chiều rộng viewport lớn hơn hoặc bằng XXXpx.
    • (max-width: YYYpx): Áp dụng khi chiều rộng viewport nhỏ hơn hoặc bằng YYYpx.
    • (orientation: portrait): Áp dụng khi thiết bị ở chế độ dọc.
    • (orientation: landscape): Áp dụng khi thiết bị ở chế độ ngang.
4.1. Xác Định Breakpoints

Breakpoints (điểm ngắt) là các giá trị chiều rộng màn hình mà tại đó bố cục hoặc kiểu dáng của bạn cần thay đổi đáng kể. Thay vì dựa vào các kích thước thiết bị cụ thể (iPhone X, iPad Mini, v.v.), cách tiếp cận tốt hơn là để nội dung của bạn quyết định breakpoints. Quan sát khi nào bố cục của bạn bắt đầu trông kỳ cục hoặc bị vỡ, và đặt một breakpoint tại điểm đó.

Tuy nhiên, để tham khảo, các điểm ngắt phổ biến thường được sử dụng (ví dụ trong Bootstrap):

  • Extra small devices (Điện thoại): < 576px
  • Small devices (Điện thoại ngang, máy tính bảng dọc): ≥ 576px
  • Medium devices (Máy tính bảng ngang, laptop): ≥ 768px
  • Large devices (Laptop, desktop): ≥ 992px
  • Extra large devices (Desktop lớn): ≥ 1200px
4.2. Ví Dụ Thực Hành với Media Queries

Thay đổi kích thước font, ẩn/hiện phần tử, hoặc thay đổi bố cục cột là những ví dụ điển hình:

Ví dụ 1: Thay đổi kích thước font và padding

/* Default styles (cho màn hình nhỏ nhất - Mobile-First) */
body {
  font-size: 16px;
  padding: 10px;
}

/* Khi màn hình rộng ít nhất 768px (Tablet trở lên) */
@media screen and (min-width: 768px) {
  body {
    font-size: 18px; /* Tăng kích thước font */
    padding: 20px; /* Tăng padding */
  }
  h1 {
    font-size: 2.5em; /* Tăng kích thước tiêu đề */
  }
}

/* Khi màn hình rộng ít nhất 1200px (Desktop lớn trở lên) */
@media screen and (min-width: 1200px) {
  body {
    font-size: 20px; /* Tăng font size nữa */
  }
  .container {
    max-width: 1140px; /* Giới hạn chiều rộng nội dung chính */
    margin: 0 auto;    /* Căn giữa container */
  }
}
  • Giải thích code: Chúng ta bắt đầu với các kiểu dáng cho màn hình nhỏ nhất (Mobile-First). Sau đó, sử dụng min-width để thêm hoặc ghi đè các quy tắc khi màn hình đủ rộng. Ví dụ, font-size của body tăng dần từ 16px lên 18px rồi 20px khi màn hình lớn hơn.

Ví dụ 2: Thay đổi bố cục cột với Flexbox và Media Query

Kết hợp Flexbox/Grid với Media Query để tạo ra các bố cục hoàn chỉnh.

<div class="product-list">
  <div class="product-item">Sản phẩm A</div>
  <div class="product-item">Sản phẩm B</div>
  <div class="product-item">Sản phẩm C</div>
  <div class="product-item">Sản phẩm D</div>
</div>
/* Default styles (Mobile-First): Mỗi sản phẩm chiếm 100% chiều rộng */
.product-list {
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
}

.product-item {
  flex: 1 1 100%; /* Mặc định chiếm 100% chiều rộng */
  background-color: #eee;
  padding: 20px;
  box-sizing: border-box; /* Đảm bảo padding không làm tăng chiều rộng */
}

/* Khi màn hình rộng ít nhất 576px (Small devices trở lên) */
@media screen and (min-width: 576px) {
  .product-item {
    flex: 1 1 calc(50% - 15px); /* 2 cột, mỗi cột chiếm gần 50% trừ gap */
  }
}

/* Khi màn hình rộng ít nhất 992px (Large devices trở lên) */
@media screen and (min-width: 992px) {
  .product-item {
    flex: 1 1 calc(33.333% - 15px); /* 3 cột, mỗi cột chiếm gần 33.333% trừ gap */
  }
}
  • Giải thích code:
    • Ban đầu, mỗi .product-item sử dụng flex: 1 1 100% để chiếm toàn bộ chiều rộng trên một hàng (tức là 1 cột).
    • Ở breakpoint min-width: 576px, chúng ta ghi đè flex-basis thành calc(50% - 15px). calc() cho phép tính toán, chúng ta muốn mỗi mục chiếm 50% trừ đi nửa khoảng cách giữa chúng (hoặc cách xử lý gap khác tùy thuộc vào việc bạn dùng gap hay margin). Điều này khiến Flexbox sắp xếp chúng thành hai cột.
    • Tương tự, ở min-width: 992px, chúng ta đổi thành calc(33.333% - 15px) để có ba cột.

Đây là một cách hiệu quả để tạo bố cục nhiều cột thay đổi theo kích thước màn hình chỉ bằng việc điều chỉnh thuộc tính flex-basis trong Media Queries.

5. Đơn Vị Đo Lường Linh Hoạt: Relative Units

Khi xây dựng responsive design, việc sử dụng các đơn vị đo lường tương đối thay vì tuyệt đối là một bí quyết quan trọng. Đơn vị tuyệt đối (như px) sẽ giữ nguyên kích thước bất kể màn hình to hay nhỏ, trong khi đơn vị tương đối sẽ thay đổi kích thước dựa trên một tham chiếu khác (kích thước viewport, kích thước font của phần tử cha/gốc).

Các đơn vị tương đối phổ biến cho Responsive Design:

  • %: Tỷ lệ phần trăm so với kích thước của phần tử cha.
  • em: Tỷ lệ so với kích thước font của phần tử cha (hoặc chính phần tử đó nếu chưa được đặt).
  • rem: Tỷ lệ so với kích thước font của phần tử gốc (<html>). Thường được ưu tiên hơn em vì dễ quản lý hơn.
  • vw: Tỷ lệ phần trăm so với chiều rộng của viewport (1vw = 1% chiều rộng viewport).
  • vh: Tỷ lệ phần trăm so với chiều cao của viewport (1vh = 1% chiều cao viewport).

Ví dụ: Sử dụng rem cho font size và vw cho padding:

html {
  font-size: 16px; /* Đặt font size gốc */
}

body {
  font-size: 1rem; /* Bằng 16px */
  padding: 2vh;   /* Padding bằng 2% chiều cao viewport */
}

h1 {
  font-size: 2.5rem; /* Bằng 2.5 * 16px = 40px (dễ tính toán) */
}

.hero-section {
  width: 90vw; /* Chiếm 90% chiều rộng viewport */
  margin: 0 auto;
  padding: 5%; /* Padding bằng 5% chiều rộng phần tử cha (.hero-section) */
  background-color: lightyellow;
}
  • Giải thích code: Kích thước chữ được định nghĩa bằng rem, giúp chúng tự động tỷ lệ thuận nếu bạn thay đổi font-size của html trong Media Queries. Padding và width sử dụng vh, vw, và %, làm cho các kích thước này tự động thay đổi khi người dùng thay đổi kích thước cửa sổ trình duyệt hoặc xoay thiết bị.

Kết hợp các đơn vị tương đối với Flexbox, Grid và Media Queries mang lại sự linh hoạt tối đa cho giao diện của bạn.

6. Chiến Lược Tiếp Cận: Mobile-First hay Desktop-First?

Có hai chiến lược chính khi viết CSS responsive:

  • Desktop-First: Bạn viết CSS cho màn hình desktop trước (mặc định), sau đó sử dụng Media Queries với max-width để ghi đè các kiểu dáng cho màn hình nhỏ hơn.

    /* Default styles (cho Desktop) */
    .container {
      width: 960px;
      margin: 0 auto;
      /* ... */
    }
    
    /* Khi màn hình <= 768px (Tablet/Mobile) */
    @media screen and (max-width: 768px) {
      .container {
        width: 100%;
        padding: 0 15px;
        /* ... */
      }
    }
    
  • Mobile-First: Bạn viết CSS cho màn hình mobile trước (mặc định), sau đó sử dụng Media Queries với min-width để thêm hoặc mở rộng các kiểu dáng cho màn hình lớn hơn.

    /* Default styles (cho Mobile) */
    .container {
      width: 100%;
      padding: 0 15px;
      /* ... */
    }
    
    /* Khi màn hình >= 769px (Tablet/Desktop) */
    @media screen and (min-width: 769px) {
      .container {
        width: 768px; /* Hoặc dùng max-width/fluid */
        margin: 0 auto;
        /* ... */
      }
    }
    

Mobile-First thường được khuyên dùng hơn vì một số lý do:

  1. Ưu tiên thiết bị di động: Bắt đầu với mobile giúp bạn tập trung vào nội dung cốt lõi và trải nghiệm đơn giản trước.
  2. CSS hiệu quả hơn: Styles cho mobile thường đơn giản hơn. Khi mở rộng cho màn hình lớn hơn, bạn chỉ cần thêm hoặc điều chỉnh các quy tắc CSS thay vì phải ghi đè nhiều thứ phức tạp từ desktop xuống mobile. Điều này có thể giúp tệp CSS nhỏ gọn hơn.
  3. Hiệu năng: Thiết bị di động thường có tài nguyên hạn chế hơn. Việc tải và xử lý CSS mobile-first (chỉ áp dụng thêm styles khi cần cho màn hình lớn) có thể hiệu quả hơn việc tải toàn bộ CSS desktop rồi ghi đè cho mobile.

Trong các ví dụ ở trên, chúng ta đã thực hành theo chiến lược Mobile-First. Hãy cố gắng áp dụng nó vào các dự án của bạn.

7. Kết Hợp Các Kỹ Thuật: Xây Dựng Một Thành Phần Responsive Hoàn Chỉnh

Hãy cùng kết hợp các kỹ thuật đã học để tạo một ví dụ phức tạp hơn một chút: Một card sản phẩm với ảnh, tiêu đề, mô tả và nút, hiển thị khác nhau trên các kích thước màn hình.

<div class="product-card">
  <div class="card-image">
    <img src="placeholder.jpg" alt="Ảnh sản phẩm">
  </div>
  <div class="card-content">
    <h3>Tên Sản Phẩm</h3>
    <p>Mô tả ngắn gọn về sản phẩm. Nội dung này sẽ co giãn và thay đổi cách hiển thị trên các thiết bị khác nhau.</p>
    <button>Xem chi tiết</button>
  </div>
</div>
/* Default styles (Mobile-First) */
.product-card {
  border: 1px solid #ccc;
  border-radius: 8px;
  overflow: hidden; /* Cắt bỏ phần ảnh/nội dung tràn ra */
  margin: 20px auto;
  max-width: 300px; /* Giới hạn chiều rộng tối đa trên mobile */
  display: flex; /* Dùng Flexbox cho bố cục bên trong */
  flex-direction: column; /* Trên mobile: Ảnh trên, nội dung dưới */
}

.card-image img {
  max-width: 100%;
  height: auto;
  display: block;
  object-fit: cover; /* Đảm bảo ảnh phủ kín không gian mà không bị méo */
  /* height: 200px; Tùy chọn: đặt chiều cao cố định cho ảnh trên mobile nếu cần */
}

.card-content {
  padding: 15px;
  flex-grow: 1; /* Cho phép nội dung chiếm hết không gian còn lại nếu cần */
}

.card-content h3 {
  margin-top: 0;
}

.card-content button {
  display: block; /* Nút chiếm toàn bộ chiều rộng trên mobile */
  width: 100%;
  padding: 10px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  margin-top: 10px;
}

/* Tablet và Desktop: Ảnh bên trái, nội dung bên phải */
@media screen and (min-width: 600px) {
  .product-card {
    flex-direction: row; /* Chuyển sang bố cục ngang */
    max-width: 600px; /* Giới hạn chiều rộng tối đa trên màn hình lớn hơn */
  }

  .card-image {
    flex: 0 0 200px; /* Ảnh chiếm cố định 200px chiều rộng */
    /* Hoặc dùng flex: 1; và đặt chiều rộng tối đa cho ảnh nếu muốn nó co giãn */
  }

  .card-image img {
     /* Bỏ height cố định nếu có, object-fit cover vẫn hiệu quả */
  }

  .card-content {
    flex-grow: 1; /* Nội dung chiếm phần còn lại */
  }

  .card-content button {
    display: inline-block; /* Nút hiển thị inline hoặc flex item */
    width: auto; /* Chiều rộng nút tự động theo nội dung */
    margin-top: 0;
  }
}

/* Desktop lớn: Có thể thay đổi thêm, ví dụ: thêm box-shadow */
@media screen and (min-width: 900px) {
  .product-card {
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
  }
  .card-image {
      flex: 0 0 250px; /* Ảnh lớn hơn */
  }
}
  • Giải thích code:
    • Chúng ta bắt đầu với bố cục Mobile-First: .product-card là Flex Container với flex-direction: column (ảnh trên, nội dung dưới). Ảnh dùng max-width: 100%height: auto để co giãn. Nút chiếm toàn bộ chiều rộng (width: 100%).
    • Tại breakpoint min-width: 600px, chúng ta chuyển flex-direction sang row (ảnh bên trái, nội dung bên phải). Chiều rộng của ảnh được cố định hoặc điều chỉnh bằng Flexbox, nút không còn chiếm 100% chiều rộng nữa.
    • Tại breakpoint min-width: 900px, chúng ta thêm box-shadow và tăng nhẹ kích thước phần ảnh.

Ví dụ này minh họa cách kết hợp Flexbox cho bố cục, quy tắc cho ảnh linh hoạt và Media Queries để thay đổi các thuộc tính CSS quan trọng tại các điểm ngắt khác nhau, tạo ra một thành phần thực sự responsive.

Comments

There are no comments at the moment.