Bài 3.2: Cách sử dụng Flexbox cho layout

Chào mừng trở lại với chuỗi bài về Lập trình Web Front-end! Nếu như trước đây việc căn chỉnh, sắp xếp các phần tử trên trang web (gọi tắt là layout) thường khiến các lập trình viên "đau đầu" với những kỹ thuật như float, position, hay thậm chí là sử dụng table (dù không đúng mục đích), thì Flexbox đã xuất hiện như một vị cứu tinh. Flexbox (Flexible Box Module) là một mô hình bố cục một chiều trong CSS, được thiết kế để giúp bạn dễ dàng phân phối không gian và căn chỉnh các phần tử theo một trục (ngang hoặc dọc).

Nó là một công cụ cực kỳ mạnh mẽlinh hoạt, đặc biệt hữu ích khi xây dựng các thành phần giao diện đáp ứng (responsive) mà không cần quá nhiều thao tác phức tạp với Media Queries. Hãy cùng đi sâu vào cách sử dụng nó!

Khái niệm cốt lõi: Container và Items

Để sử dụng Flexbox, bạn cần nắm rõ hai khái niệm chính:

  1. Flex Container: Là phần tử cha mà bạn muốn quản lý layout của các phần tử con trực tiếp bên trong nó. Để biến một phần tử thành Flex Container, bạn chỉ cần thiết lập thuộc tính display của nó là flex hoặc inline-flex.
  2. Flex Items: Là các phần tử con trực tiếp bên trong Flex Container. Flexbox chỉ áp dụng trực tiếp lên các phần tử con này, không ảnh hưởng đến các cháu hoặc chắt của Container.
<div class="flex-container">
  <div class="flex-item">Item 1</div>
  <div class="flex-item">Item 2</div>
  <div class="flex-item">Item 3</div>
</div>
.flex-container {
  display: flex; /* Biến div này thành Flex Container */
  /* Các thuộc tính Flexbox cho Container sẽ ở đây */
}

.flex-item {
  /* Các thuộc tính Flexbox cho Item sẽ ở đây */
}

Khi bạn đặt display: flex; cho .flex-container, ngay lập tức các phần tử .flex-item bên trong nó sẽ trở thành Flex Items và được sắp xếp theo một trục mặc định.

Các thuộc tính áp dụng cho Flex Container

Đây là những thuộc tính bạn sẽ sử dụng để điều khiển cách các Flex Items bên trong Container được sắp xếp và phân phối không gian.

1. flex-direction

Thuộc tính này xác định trục chính (main axis) của Flex Container và hướng sắp xếp của Flex Items trên trục đó.

  • row (mặc định): Các items được sắp xếp theo chiều ngang (từ trái sang phải theo mặc định).
  • row-reverse: Các items được sắp xếp theo chiều ngang, nhưng ngược lại (từ phải sang trái theo mặc định).
  • column: Các items được sắp xếp theo chiều dọc (từ trên xuống dưới).
  • column-reverse: Các items được sắp xếp theo chiều dọc, nhưng ngược lại (từ dưới lên trên).
.container-row {
  display: flex;
  flex-direction: row; /* Sắp xếp ngang */
}

.container-column {
  display: flex;
  flex-direction: column; /* Sắp xếp dọc */
}

Giải thích: flex-direction là nền tảng đầu tiên, quyết định "chiều" mà Flexbox hoạt động. row cho layout ngang (phổ biến cho thanh điều hướng), column cho layout dọc (phổ biến cho danh sách hoặc các phần tử xếp chồng).

2. flex-wrap

Nếu các Flex Items không đủ chỗ trên một dòng (hoặc cột nếu flex-directioncolumn), thuộc tính này sẽ xác định liệu chúng có được xuống dòng (hoặc cột) hay không.

  • nowrap (mặc định): Các items sẽ cố gắng nằm trên một dòng duy nhất, có thể bị thu nhỏ kích thước.
  • wrap: Các items sẽ xuống dòng khi không còn đủ chỗ trên trục chính.
  • wrap-reverse: Các items xuống dòng, nhưng các dòng mới sẽ được xếp ngược lại.
.container-wrap {
  display: flex;
  flex-wrap: wrap; /* Cho phép xuống dòng khi hết chỗ */
  width: 300px; /* Giới hạn chiều rộng để dễ thấy hiệu ứng wrap */
}

.container-nowrap {
  display: flex;
  flex-wrap: nowrap; /* Không xuống dòng, các item có thể bị co lại */
  width: 300px;
}

Giải thích: flex-wrap: wrap; cực kỳ quan trọng để tạo ra layout đáp ứng. Khi kích thước màn hình nhỏ lại, các items sẽ tự động "nhảy" xuống dòng mới thay vì bị ép co lại hoặc tràn ra ngoài.

3. justify-content

Thuộc tính này dùng để căn chỉnh các Flex Items dọc theo trục chính (main axis) của Container, sau khi tính toán hết không gian trống còn lại.

  • flex-start (mặc định): Căn chỉnh các items về phía đầu trục chính.
  • flex-end: Căn chỉnh các items về phía cuối trục chính.
  • center: Căn chỉnh các items vào giữa trục chính.
  • space-between: Phân phối không gian trống đều giữa các items. Item đầu tiên nằm ở đầu trục, item cuối cùng nằm ở cuối trục.
  • space-around: Phân phối không gian trống đều xung quanh mỗi item. Khoảng trống ở hai đầu (trước item đầu và sau item cuối) bằng một nửa khoảng trống giữa các item.
  • space-evenly: Phân phối không gian trống đều giữa tất cả các item và ở hai đầu. Tất cả các khoảng trống đều bằng nhau.
.container-justify {
  display: flex;
  flex-direction: row; /* Giả sử trục chính là ngang */
  justify-content: center; /* Căn giữa theo chiều ngang */
  height: 50px; /* Để dễ nhìn */
  border: 1px dashed #ccc;
}

.item {
  width: 50px;
  height: 40px;
  background-color: lightblue;
  margin: 5px;
}

Giải thích: justify-content là "người" quản lý khoảng cách và vị trí của các item trên trục chính. Muốn căn giữa một hàng/cột? Dùng center. Muốn giãn đều các nút trong thanh điều hướng? Dùng space-between hoặc space-around/space-evenly.

4. align-items

Thuộc tính này dùng để căn chỉnh các Flex Items dọc theo trục phụ (cross axis) của Container (trục vuông góc với trục chính).

  • stretch (mặc định, nếu items không có kích thước cố định trên trục phụ): Kéo giãn các items để lấp đầy Container dọc theo trục phụ.
  • flex-start: Căn chỉnh các items về phía đầu trục phụ.
  • flex-end: Căn chỉnh các items về phía cuối trục phụ.
  • center: Căn chỉnh các items vào giữa trục phụ.
  • baseline: Căn chỉnh các items dựa trên đường baseline của nội dung văn bản bên trong chúng.
.container-align {
  display: flex;
  flex-direction: row; /* Trục chính ngang, trục phụ dọc */
  align-items: center; /* Căn giữa theo chiều dọc */
  height: 100px; /* Container cao hơn items */
  border: 1px dashed #ccc;
}

.item {
  width: 50px;
  /* Không set height cố định để thấy stretch hoặc set height khác nhau */
  background-color: lightcoral;
  margin: 5px;
}

.item:nth-child(2) {
  height: 30px; /* Item này nhỏ hơn */
}
.item:nth-child(3) {
  height: 60px; /* Item này lớn hơn */
}

Giải thích: align-items giải quyết bài toán căn chỉnh dọc (khi trục chính là ngang) hoặc ngang (khi trục chính là dọc) cho tất cả các items một cách đồng nhất. Căn giữa một div vào giữa màn hình? Kết hợp justify-content: center;align-items: center; trên container của nó!

5. align-content

Thuộc tính này dùng để căn chỉnh các dòng (lines) Flex Items dọc theo trục phụ (cross axis) của Container khi các items bị xuống dòng (flex-wrap: wrap;). Nó tương tự như justify-content nhưng áp dụng cho các dòng thay vì từng item đơn lẻ, và hoạt động trên trục phụ.

  • stretch (mặc định): Kéo giãn các dòng để lấp đầy không gian trống dọc trục phụ.
  • flex-start: Căn chỉnh các dòng về phía đầu trục phụ.
  • flex-end: Căn chỉnh các dòng về phía cuối trục phụ.
  • center: Căn chỉnh các dòng vào giữa trục phụ.
  • space-between: Phân phối không gian trống giữa các dòng. Dòng đầu ở đầu trục phụ, dòng cuối ở cuối trục phụ.
  • space-around: Phân phối không gian trống xung quanh mỗi dòng.
.container-align-content {
  display: flex;
  flex-wrap: wrap; /* Bắt buộc phải có wrap để align-content có hiệu lực */
  height: 200px; /* Container đủ cao để có không gian trống */
  border: 1px dashed #ccc;
  align-content: space-between; /* Giãn đều khoảng trống giữa các dòng */
}

.item {
  width: 80px; /* Các item đủ rộng để wrap */
  height: 40px;
  background-color: lightgreen;
  margin: 5px;
}
/* Thêm nhiều item trong HTML để đảm bảo chúng wrap */

Giải thích: align-content chỉ phát huy tác dụng khi bạn có nhiều hơn một dòng (hoặc cột) items do flex-wrap. Nó giúp bạn kiểm soát cách các khối dòng đó được đặt trong container.

6. gap, row-gap, column-gap

Các thuộc tính này định nghĩa khoảng cách (gap) giữa các Flex Items.

  • gap: Shorthand cho cả row-gapcolumn-gap.
  • row-gap: Khoảng cách giữa các dòng (khi wrap).
  • column-gap: Khoảng cách giữa các cột/items trên cùng một dòng.
.container-gap {
  display: flex;
  flex-wrap: wrap;
  gap: 10px; /* Khoảng cách 10px giữa các item */
  /* Hoặc:
  row-gap: 15px;
  column-gap: 8px;
  */
  border: 1px dashed #ccc;
}

.item {
  width: 80px;
  height: 40px;
  background-color: lightblue;
  /* Không cần margin cho item nữa khi dùng gap */
}

Giải thích: gap là một bổ sung tuyệt vời, giúp tạo khoảng cách giữa các items mà không cần dùng margin (có thể gây ra vấn đề ở các cạnh ngoài cùng). Nó đơn giản và hiệu quả hơn.

Các thuộc tính áp dụng cho Flex Items

Những thuộc tính này được thiết lập trên từng Flex Item riêng lẻ để điều chỉnh hành vi của nó trong Flex Container.

1. order

Thuộc tính này cho phép bạn thay đổi thứ tự trực quan của một Flex Item, bất kể vị trí của nó trong mã nguồn HTML. Mặc định, tất cả items có order: 0. Các item có order nhỏ hơn sẽ xuất hiện trước.

  • Nhận giá trị là một số nguyên (-1, 0, 1, 2, ...).
.container-order {
  display: flex;
}

.item {
  width: 60px;
  height: 50px;
  background-color: lightgreen;
  margin: 5px;
  text-align: center;
  line-height: 50px;
}

.item:nth-child(1) { order: 2; } /* Item 1 xuất hiện thứ 3 */
.item:nth-child(2) { order: 3; } /* Item 2 xuất hiện thứ 4 */
.item:nth-child(3) { order: 1; } /* Item 3 xuất hiện thứ 2 */
.item:nth-child(4) { order: 0; } /* Item 4 xuất hiện đầu tiên */

Giải thích: order rất hữu ích cho thiết kế đáp ứng, cho phép bạn thay đổi thứ tự các phần tử trên màn hình nhỏ so với màn hình lớn mà không cần thay đổi cấu trúc HTML.

2. flex-grow

Thuộc tính này xác định khả năng mở rộng (tăng kích thước) của một Flex Item để lấp đầy không gian trống còn lại trên trục chính.

  • Nhận giá trị là một số không âm (0, 1, 2, ...).
  • Mặc định là 0 (item không mở rộng).
  • Nếu tất cả items có flex-grow: 1, chúng sẽ chia đều không gian trống.
  • Nếu một item có flex-grow: 2 và các item khác có flex-grow: 1, item đó sẽ nhận gấp đôi lượng không gian trống so với các item còn lại.
.container-grow {
  display: flex;
  width: 400px; /* Container đủ rộng để có không gian trống */
  border: 1px dashed #ccc;
}

.item {
  width: 80px; /* Kích thước ban đầu */
  height: 50px;
  background-color: lightblue;
  margin: 5px;
}

.item:nth-child(2) {
  flex-grow: 1; /* Item 2 sẽ mở rộng để lấp đầy không gian */
}

.item:nth-child(3) {
  flex-grow: 2; /* Item 3 mở rộng gấp đôi Item 2 */
}

Giải thích: flex-grow giúp bạn dễ dàng tạo ra các cột hoặc hàng có kích thước linh hoạt, tự động điều chỉnh để sử dụng hết không gian có sẵn.

3. flex-shrink

Thuộc tính này xác định khả năng co lại (giảm kích thước) của một Flex Item khi không có đủ không gian trên trục chính.

  • Nhận giá trị là một số không âm (0, 1, 2, ...).
  • Mặc định là 1 (item có thể co lại).
  • Nếu flex-shrink: 0, item sẽ không co lại dưới kích thước ban đầu của nó (trừ khi kích thước ban đầu không xác định).
  • Các items có flex-shrink lớn hơn sẽ co lại nhiều hơn.
.container-shrink {
  display: flex;
  width: 200px; /* Container hẹp hơn tổng kích thước items */
  border: 1px dashed #ccc;
}

.item {
  width: 100px; /* Kích thước ban đầu, tổng là 300px */
  height: 50px;
  background-color: lightcoral;
  margin: 5px;
}

.item:nth-child(2) {
  flex-shrink: 0; /* Item 2 sẽ KHÔNG co lại */
  background-color: lightgreen;
}

Giải thích: flex-shrink kiểm soát hành vi của items khi bị "chật chội". Sử dụng flex-shrink: 0; cho những items mà bạn không bao giờ muốn chúng bị thu nhỏ (ví dụ: logo trong header).

4. flex-basis

Thuộc tính này xác định kích thước ban đầu của một Flex Item trước khi không gian trống được phân phối theo flex-grow hoặc khi cần co lại theo flex-shrink.

  • Nhận giá trị là một độ dài (px, %, rem, auto, etc.).
  • Mặc định là auto (kích thước dựa vào nội dung hoặc thuộc tính width/height của item).
  • Nếu flex-basis được đặt, nó sẽ ưu tiên hơn width/height (theo chiều của trục chính).
.container-basis {
  display: flex;
  width: 400px; /* Container đủ rộng */
  border: 1px dashed #ccc;
}

.item {
  /* Không set width */
  height: 50px;
  background-color: lightgreen;
  margin: 5px;
}

.item:nth-child(1) {
  flex-basis: 100px; /* Kích thước ban đầu 100px */
}

.item:nth-child(2) {
  flex-basis: 150px; /* Kích thước ban đầu 150px */
}

.item:nth-child(3) {
  flex-basis: auto; /* Kích thước dựa vào nội dung hoặc width/height (nếu có) */
  width: 50px; /* Width này sẽ được dùng nếu basis: auto */
}

Giải thích: flex-basis giúp bạn định rõ kích thước "mong muốn" ban đầu của item. Khi kết hợp với flex-growflex-shrink, nó cho phép kiểm soát rất tốt cách items thay đổi kích thước.

5. flex (Shorthand)

Thuộc tính flex là shorthand cho flex-grow, flex-shrink, và flex-basis theo thứ tự: flex: <'flex-grow'> <'flex-shrink'> <'flex-basis'>;.

  • Các giá trị phổ biến:
    • flex: auto; (1 1 auto)
    • flex: initial; (0 1 auto)
    • flex: none; (0 0 auto)
    • flex: 1; (1 1 0%) - Đây là cách viết tắt cho flex: 1 1 0;. Items sẽ co giãn từ kích thước 0 và chia đều không gian trống (hoặc bù lấp không gian âm). Rất hữu ích để tạo các cột có độ rộng bằng nhau.
    • flex: <positive number>; (ví dụ: flex: 2; tương đương 2 1 0%)
.container-flex-shorthand {
  display: flex;
  width: 400px;
  border: 1px dashed #ccc;
}

.item {
  height: 50px;
  background-color: lightblue;
  margin: 5px;
}

.item:nth-child(1) {
  flex: 1; /* Tương đương flex: 1 1 0%; */
}

.item:nth-child(2) {
  flex: 2; /* Tương đương flex: 2 1 0%; Nhận gấp đôi không gian so với item flex: 1 */
}

.item:nth-child(3) {
  flex: none; /* Tương đương flex: 0 0 auto; Không co giãn */
  width: 50px; /* Kích thước cố định */
}

Giải thích: Sử dụng shorthand flex giúp code gọn gàng hơn rất nhiều. flex: 1; là một pattern rất phổ biến để tạo các items có kích thước linh hoạt, chia đều không gian.

6. align-self

Thuộc tính này cho phép bạn ghi đè (override) thuộc tính align-items của Container cho một Flex Item cụ thể.

  • Nhận các giá trị tương tự align-items: auto (mặc định, theo Container), flex-start, flex-end, center, stretch, baseline.
.container-align-self {
  display: flex;
  flex-direction: row;
  align-items: flex-start; /* Container căn chỉnh ở đầu trục phụ */
  height: 100px;
  border: 1px dashed #ccc;
}

.item {
  width: 60px;
  height: 40px;
  background-color: lightcoral;
  margin: 5px;
}

.item:nth-child(2) {
  align-self: center; /* Item 2 căn giữa riêng */
  background-color: lightgreen;
}

.item:nth-child(3) {
  align-self: flex-end; /* Item 3 căn cuối riêng */
  background-color: lightblue;
}

Giải thích: Khi bạn muốn một item nào đó có vị trí khác biệt trên trục phụ so với phần còn lại, align-self là công cụ bạn cần.

Ví dụ thực tế: Xây dựng thanh điều hướng (Navbar)

Flexbox là lựa chọn lý tưởng để xây dựng các thanh điều hướng.

<nav class="navbar">
  <div class="logo">Logo</div>
  <ul class="nav-links">
    <li><a href="#">Trang chủ</a></li>
    <li><a href="#">Sản phẩm</a></li>
    <li><a href="#">Giới thiệu</a></li>
    <li><a href="#">Liên hệ</a></li>
  </ul>
</nav>
.navbar {
  display: flex; /* Biến navbar thành flex container */
  justify-content: space-between; /* Giãn đều logo và nav-links */
  align-items: center; /* Căn giữa theo chiều dọc */
  background-color: #f2f2f2;
  padding: 10px 20px;
}

.logo {
  font-weight: bold;
  font-size: 1.5em;
}

.nav-links {
  display: flex; /* Biến ul thành flex container để quản lý li */
  list-style: none; /* Bỏ dấu chấm đầu dòng */
  padding: 0;
  margin: 0;
  gap: 20px; /* Khoảng cách giữa các item li */
  /* Thay thế gap bằng margin nếu cần hỗ trợ trình duyệt cũ hơn: */
  /* margin-left: 20px; */
}

.nav-links li a {
  text-decoration: none;
  color: #333;
}

Giải thích:

  • Chúng ta đặt display: flex; cho .navbar để quản lý .logo.nav-links.
  • justify-content: space-between; đẩy .logo sang trái và .nav-links sang phải.
  • align-items: center; giúp cả logonav-links được căn thẳng hàng ở giữa theo chiều dọc trong thanh navbar.
  • Chúng ta cũng đặt display: flex; cho .nav-links để quản lý các li bên trong nó.
  • gap: 20px; tạo khoảng cách giữa các mục menu li.

Chỉ với vài dòng CSS Flexbox, bạn đã có một thanh điều hướng hoạt động tốt!

Ví dụ thực tế: Tạo các cột bằng nhau trong Grid đơn giản

Flexbox không phải là Grid (sẽ có bài về Grid sau!), nhưng nó có thể tạo ra layout dạng cột đơn giản rất hiệu quả.

<div class="grid-container">
  <div class="grid-item">Item 1</div>
  <div class="grid-item">Item 2</div>
  <div class="grid-item">Item 3</div>
</div>
.grid-container {
  display: flex; /* Biến container thành flex */
  gap: 10px; /* Khoảng cách giữa các cột */
  border: 1px dashed #ccc;
  padding: 10px; /* Padding để khoảng cách không chạm viền */
}

.grid-item {
  flex: 1; /* Mỗi item sẽ có flex-grow: 1, flex-shrink: 1, flex-basis: 0%. */
          /* Điều này khiến chúng chia đều không gian và có chiều rộng bằng nhau. */
  background-color: lightblue;
  height: 100px;
  text-align: center;
  line-height: 100px; /* Căn giữa nội dung theo chiều dọc */
}

Giải thích:

  • display: flex; biến .grid-container thành flex container.
  • gap: 10px; tạo khoảng cách giữa các .grid-item.
  • flex: 1; trên mỗi .grid-item là "phép màu" ở đây. Nó đảm bảo rằng tất cả các items sẽ cố gắng có kích thước ban đầu là 0 (flex-basis: 0%) và sau đó chia đều toàn bộ không gian có sẵn (flex-grow: 1). Kết quả là các cột có chiều rộng bằng nhau, tự động điều chỉnh theo chiều rộng của container.

Comments

There are no comments at the moment.