Bài 10: Positioning – Absolute, Relative, Fixed, Sticky

Cho đến bây giờ, bạn đã học Flexbox và Grid — hai cách sắp xếp bố cục hiện đại. Nhưng đôi khi bạn cần điều khiển chính xác vị trí của một phần tử — chẳng hạn như làm một button cố định ở góc, hoặc một thanh navigation dính ở đầu trang.

Bài này sẽ dạy bạn position — cách điều khiển vị trí phần tử bằng các giá trị relative, absolute, fixed, và sticky.

Position là gì?

position xác định cách phần tử được định vị trên trang. Có 5 giá trị chính:

Giá trịMô tảKhi nào dùng
static (mặc định)Sắp xếp theo luồng bình thường (block, inline…)Hầu hết các phần tử
relativeĐịnh vị dựa trên vị trí mặc định của nóDịch chuyển nhẹ, tạo positioned parent
absoluteĐịnh vị dựa trên positioned parentDropdown, modal, tooltip
fixedĐịnh vị dựa trên viewport (không cuộn theo)Navigation cố định, button floating
stickyKết hợp relative và fixed (dính khi cuộn)Header dính, bảng dính

Position: static (Mặc định) – Luồng bình thường

position: static; là giá trị mặc định. Phần tử sắp xếp theo luồng bình thường của HTML.

.box {
  position: static;  /* Mặc định */
  /* top, left, right, bottom, z-index không có tác dụng */
}

Position: relative – Dịch chuyển từ vị trí ban đầu

position: relative; cho phép bạn dịch chuyển phần tử so với vị trí ban đầu bằng top, left, right, bottom. Nhưng nó vẫn chiếm không gian ở vị trí cũ.

.box {
  position: relative;
  top: 20px;      /* Dịch xuống 20px */
  left: 30px;     /* Dịch sang phải 30px */
  /* Phần tử vẫn chiếm không gian ở vị trí cũ */
}

/* Hoặc */
.box {
  position: relative;
  top: -10px;     /* Dịch lên 10px */
  right: 15px;    /* Dịch sang trái 15px */
}

Khi nào dùng relative?

  • Dịch chuyển nhẹ một phần tử mà không ảnh hưởng đến layout
  • Tạo positioned parent: Để absolute child có thể định vị dựa trên nó
/* relative thường dùng để tạo "positioned context" cho absolute */
.container {
  position: relative;  /* Không dịch, chỉ tạo context */
}

.container .absolute-child {
  position: absolute;  /* Định vị dựa trên .container */
  top: 10px;
  left: 10px;
}

Position: absolute – Định vị dựa trên positioned parent

position: absolute; tách phần tử khỏi luồng bình thường và định vị nó dựa trên positioned parent gần nhất (parent có position khác static).

/* Parent: positioned container */
.parent {
  position: relative;  /* Bắt buộc! */
  width: 300px;
  height: 200px;
  background: lightgray;
}

/* Child: absolute positioned */
.child {
  position: absolute;
  top: 20px;
  left: 30px;
  width: 100px;
  height: 100px;
  background: blue;
  /* Định vị từ góc trên-trái của .parent */
}

Các thuộc tính định vị

Thuộc tínhÝ nghĩaVí dụ
topKhoảng cách từ trêntop: 20px;
bottomKhoảng cách từ dướibottom: 10px;
leftKhoảng cách từ tráileft: 30px;
rightKhoảng cách từ phảiright: 15px;

Ví dụ thực tế – Dropdown menu

<!-- HTML -->
<div class="dropdown">
  <button>Menu</button>
  <div class="dropdown-menu">
    <a href="#">Tùy chọn 1</a>
    <a href="#">Tùy chọn 2</a>
    <a href="#">Tùy chọn 3</a>
  </div>
</div>
/* CSS */
.dropdown {
  position: relative;  /* Positioned context */
  display: inline-block;
}

.dropdown button {
  padding: 10px 15px;
  background-color: #3498db;
  color: white;
  border: none;
  cursor: pointer;
  border-radius: 5px;
}

.dropdown-menu {
  position: absolute;  /* Định vị dựa trên .dropdown */
  top: 100%;          /* Dưới button */
  left: 0;            /* Sát trái button */
  background-color: white;
  border: 1px solid #ddd;
  border-radius: 5px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  min-width: 200px;
  display: none;      /* Ẩn mặc định */
  z-index: 10;        /* Hiển thị trên các phần tử khác */
}

.dropdown:hover .dropdown-menu {
  display: block;     /* Hiện khi hover */
}

.dropdown-menu a {
  display: block;
  padding: 12px 15px;
  color: #333;
  text-decoration: none;
  transition: background-color 0.3s;
}

.dropdown-menu a:hover {
  background-color: #f0f0f0;
}

Position: fixed – Cố định trên viewport

position: fixed; tách phần tử khỏi luồng bình thườngcố định nó trên viewport (không cuộn theo trang). Rất hữu ích cho navigation, button floating, modal…

/* Navigation cố định ở trên */
.navbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;  /* Hoặc width: 100%; */
  height: 60px;
  background-color: #2c3e50;
  color: white;
  z-index: 100;  /* Hiển thị trên tất cả */
}

/* Button floating ở góc */
.float-button {
  position: fixed;
  bottom: 20px;  /* Cách dưới 20px */
  right: 20px;   /* Cách phải 20px */
  width: 60px;
  height: 60px;
  background-color: #e74c3c;
  color: white;
  border-radius: 50%;  /* Hình tròn */
  cursor: pointer;
  z-index: 50;
}

/* Modal overlay - chiếm toàn màn hình */
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 200;
}

⚠️ Lưu ý khi dùng fixed

⚠️ Cảnh báo: Phần tử position: fixed:

  • Tách khỏi luồng (không chiếm không gian mặc định)
  • Không cuộn theo trang (cố định trên viewport)
  • Luôn nằm trên các phần tử khác (ngoại trừ những cái có z-index cao hơn)
  • Có thể che phủ nội dung nếu không quản lý z-index

Position: sticky – Dính khi cuộn

position: sticky; là sự kết hợp của relative và fixed. Phần tử sắp xếp bình thường (relative) cho đến khi bạn cuộn đến điểm nhất định, sau đó nó “dính” ở đó (fixed).

/* Header dính ở trên khi cuộn */
.table-header {
  position: sticky;
  top: 0;  /* Dính ở vị trí top: 0 */
  background-color: white;
  z-index: 10;
}

/* Sidebar dính ở bên */
.sidebar {
  position: sticky;
  top: 60px;  /* Dính dưới navbar fixed (60px) */
  max-height: calc(100vh - 60px);  /* Cao tối đa = viewport - navbar */
  overflow-y: auto;
}

Ví dụ thực tế – Bảng với header dính

<!-- HTML -->
<table class="data-table">
  <thead>
    <tr>
      <th>ID</th>
      <th>Tên</th>
      <th>Email</th>
      <th>Phòng ban</th>
    </tr>
  </thead>
  <tbody>
    <tr><td>1</td><td>Nguyễn A</td><td>a@email.com</td><td>IT</td></tr>
    <tr><td>2</td><td>Trần B</td><td>b@email.com</td><td>HR</td></tr>
    <!-- Thêm nhiều hàng... -->
  </tbody>
</table>
/* CSS */
.data-table {
  width: 100%;
  border-collapse: collapse;
}

.data-table thead {
  position: sticky;
  top: 0;  /* Dính ở đầu khi cuộn */
  background-color: #2c3e50;
  color: white;
  z-index: 10;
}

.data-table th,
.data-table td {
  padding: 12px;
  text-align: left;
  border-bottom: 1px solid #ddd;
}

.data-table tbody tr:hover {
  background-color: #f0f0f0;
}

Z-index – Độ ưu tiên hiển thị

z-index xác định thứ tự xếp chồng của các phần tử. Phần tử có z-index cao hơn sẽ hiển thị trên phần tử có z-index thấp hơn.

/* Thứ tự từ dưới lên trên */
.background {
  position: absolute;
  z-index: 1;
}

.middle {
  position: absolute;
  z-index: 10;
}

.foreground {
  position: absolute;
  z-index: 100;
}

/* Hoặc dùng negative z-index */
.behind {
  position: absolute;
  z-index: -1;  /* Nằm dưới background của element cha */
}

💡 Mẹo z-index:

  • Z-index chỉ hoạt động với positioned elements (relative, absolute, fixed, sticky)
  • Không cần dùng z-index quá cao (100, 1000…), chỉ cần đủ lớn hơn cái khác
  • Tạo một “z-index hierarchy”: navbar = 100, dropdown = 110, modal = 200…

Ví dụ hoàn chỉnh – Layout với fixed navbar + sticky sidebar

<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Positioning Demo</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>

  <!-- Fixed navbar ở trên -->
  <nav class="navbar">
    <div class="navbar-content">
      <h1 class="logo">Logo</h1>
      <ul class="nav-links">
        <li><a href="#">Trang chủ</a></li>
        <li><a href="#">Dịch vụ</a></li>
        <li><a href="#">Blog</a></li>
        <li><a href="#">Liên hệ</a></li>
      </ul>
    </div>
  </nav>

  <div class="main-wrapper">
    <!-- Sticky sidebar ở trái -->
    <aside class="sidebar">
      <div class="sidebar-content">
        <h3>Danh mục</h3>
        <ul>
          <li><a href="#">Bài viết mới</a></li>
          <li><a href="#">Phổ biến nhất</a></li>
          <li><a href="#">HTML</a></li>
          <li><a href="#">CSS</a></li>
          <li><a href="#">JavaScript</a></li>
        </ul>
      </div>
    </aside>

    <!-- Main content -->
    <main class="content">
      <article class="article">
        <h2>Tiêu đề bài viết</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit...</p>
        <p>Cuộn xuống để xem navbar dính và sidebar dính!</p>
        <!-- Thêm nội dung dài để tạo scroll -->
      </article>
    </main>
  </div>

  <!-- Float button ở góc phải dưới -->
  <button class="float-button">↑</button>

</body>
</html>
/* FILE: styles.css */

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: Arial, sans-serif;
  line-height: 1.6;
  color: #333;
}

/* Fixed navbar */
.navbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 60px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  z-index: 100;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.navbar-content {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 20px;
  height: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.logo {
  font-size: 24px;
  font-weight: bold;
}

.nav-links {
  display: flex;
  list-style: none;
  gap: 30px;
}

.nav-links a {
  color: white;
  text-decoration: none;
  transition: opacity 0.3s;
}

.nav-links a:hover {
  opacity: 0.8;
}

/* Main wrapper - cách trên do navbar fixed */
.main-wrapper {
  display: flex;
  margin-top: 60px;  /* Cách trên do navbar fixed */
  min-height: calc(100vh - 60px);
}

/* Sticky sidebar */
.sidebar {
  position: sticky;
  top: 60px;  /* Dính dưới navbar */
  width: 250px;
  background-color: #f9f9f9;
  border-right: 1px solid #ddd;
  max-height: calc(100vh - 60px);
  overflow-y: auto;
}

.sidebar-content {
  padding: 20px;
}

.sidebar-content h3 {
  color: #2c3e50;
  margin-bottom: 20px;
  font-size: 18px;
}

.sidebar-content ul {
  list-style: none;
}

.sidebar-content li {
  margin-bottom: 10px;
}

.sidebar-content a {
  color: #667eea;
  text-decoration: none;
  transition: color 0.3s;
  display: block;
  padding: 8px 10px;
  border-radius: 5px;
}

.sidebar-content a:hover {
  background-color: #e8e8ff;
  color: #764ba2;
}

/* Main content */
.content {
  flex: 1;
  padding: 40px;
}

.article {
  max-width: 800px;
  background-color: white;
  padding: 30px;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}

.article h2 {
  color: #2c3e50;
  margin-bottom: 20px;
  font-size: 28px;
}

.article p {
  color: #555;
  margin-bottom: 15px;
  text-align: justify;
}

/* Thêm nội dung dài để test scroll */
.article p::first-letter {
  font-size: 1.1em;
  font-weight: bold;
}

/* Float button ở góc phải dưới */
.float-button {
  position: fixed;
  bottom: 20px;
  right: 20px;
  width: 50px;
  height: 50px;
  background-color: #e74c3c;
  color: white;
  border: none;
  border-radius: 50%;
  font-size: 24px;
  cursor: pointer;
  z-index: 90;  /* Dưới navbar */
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
  transition: all 0.3s;
}

.float-button:hover {
  background-color: #c0392b;
  transform: scale(1.1);
}

/* Responsive */
@media (max-width: 768px) {
  .sidebar {
    position: relative;
    width: 100%;
    max-height: none;
    border-right: none;
    border-bottom: 1px solid #ddd;
  }

  .main-wrapper {
    flex-direction: column;
  }

  .nav-links {
    gap: 15px;
  }

  .content {
    padding: 20px;
  }
}

Bảng so sánh Positioning

PositionLuồngDựa trênCuộnKhi nào dùng
staticBình thườngLuồng HTMLTheoMặc định
relativeBình thường (dịch)Vị trí mặc địnhTheoDịch nhẹ, positioned parent
absoluteTáchPositioned parentTheoDropdown, modal, tooltip
fixedTáchViewportKhôngNavbar, float button, modal overlay
stickyBình thường → FixedViewport (khi dính)Theo (bình thường), không (khi dính)Header dính, sidebar dính

Tóm tắt

  • position: static: Mặc định, sắp xếp bình thường
  • position: relative: Dịch chuyển từ vị trí ban đầu, tạo positioned context
  • position: absolute: Định vị dựa trên positioned parent
  • position: fixed: Cố định trên viewport, không cuộn theo
  • position: sticky: Relative đến khi dính (fixed) khi cuộn
  • top, left, right, bottom: Các thuộc tính định vị
  • z-index: Độ ưu tiên hiển thị (chỉ hoạt động với positioned elements)
  • relative thường dùng làm “positioned parent” cho absolute
  • fixed + z-index: Quản lý navbar, modal, float button

🎯 Bài tập thực hành

Bài tập 1: Tạo dropdown menu với position absolute:

  • Button “Menu” giữa trang
  • Container button có position: relative
  • Dropdown menu có position: absolute, top: 100%, left: 0
  • Dropdown ẩn mặc định, hiện khi hover button
  • Dropdown có 5 link, styling đẹp

Bài tập 2: Tạo fixed navigation bar + float button:

  • Navbar cố định ở trên: position: fixed, top: 0, left: 0, right: 0
  • Nội dung chính cách trên (margin-top: 60px)
  • Float button ở góc phải dưới: position: fixed, bottom: 20px, right: 20px
  • Thêm z-index để navbar hiển thị trên button

Bài tập 3: Tạo bảng dữ liệu với header sticky:

  • Bảng có ít nhất 10 hàng dữ liệu
  • Header bảng: position: sticky, top: 0, background đặc biệt
  • Khi cuộn xuống, header dính ở đầu (không cuộn cùng)
  • Styling đẹp cho header, row, hover effect
  • Responsive: horizontal scroll trên mobile

Chúc bạn thành công! 🚀

Để lại bình luận

Email của bạn sẽ không được hiển thị.