Skip to main content
css 6 mins read Devs2.org

CSS Positioning: Hướng Dẫn Từ Cơ Bản Đến Nâng Cao

Khám phá tất cả các giá trị position trong CSS — static, relative, absolute, fixed, sticky — cùng stacking context, z-index và các ví dụ thực tế.

#CSS #Positioning #Web Development #Layout #Frontend

Minh họa CSS Positioning

Giới thiệu

CSS Positioning là một trong những kỹ năng nền tảng quan trọng nhất mà bất kỳ web developer nào cũng phải nắm vững. Nó quyết định cách các phần tử HTML được đặt trên trang web — và nếu bạn không hiểu rõ, bạn sẽ dễ dàng rơi vào tình huống “phần tử này đi đâu mất rồi?”

Trong bài viết này, chúng ta sẽ đi từ cơ bản đến nâng cao, khám phá tất cả 5 giá trị của thuộc tính position trong CSS:

  1. static — mặc định
  2. relative — tương đối
  3. absolute — tuyệt đối
  4. fixed — cố định
  5. sticky — bám dính

Kèm theo đó là các khái niệm quan trọng như stacking context, z-index, và containing block.

Position: Static (Mặc định)

Mọi phần tử HTML đều có position: static theo mặc định. Điều này có nghĩa là phần tử nằm trong normal document flow — các phần tử xếp chồng lên nhau theo thứ tự xuất hiện trong HTML.

/* Đây là giá trị mặc định, không cần khai báo */
.element {
  position: static;
}

Đặc điểm của static:

  • Phần tử nằm đúng vị trí trong luồng tài liệu
  • Các thuộc tính top, right, bottom, left, z-index không có tác dụng
  • Không thể dùng làm container cho phần tử con absolute

Position: Relative

position: relative giữ phần tử trong normal flow nhưng cho phép bạn dịch chuyển nó tương đối so với vị trí ban đầu.

.relative-box {
  position: relative;
  top: 20px;
  left: 30px;
}

Cách hoạt động

Khi bạn đặt position: relative và sử dụng top/right/bottom/left, phần tử sẽ dịch chuyển khỏi vị trí gốc nhưng vẫn chiếm không gian ban đầu. Các phần tử xung quanh không bị ảnh hưởng.

<div class="container">
  <div class="box box-1">Box 1 (static)</div>
  <div class="box box-2">Box 2 (relative)</div>
  <div class="box box-3">Box 3 (static)</div>
</div>
.box-2 {
  position: relative;
  top: 20px;
  left: 20px;
  background: #3b82f6;
}

Kết quả: Box 2 dịch xuống 20px và sang phải 20px, nhưng Box 3 vẫn nằm ngay dưới vị trí gốc của Box 2 — không dịch lên.

Ứng dụng quan trọng nhất

relative tạo containing block cho phần tử con absolute — đây là ứng dụng số 1. Chúng ta sẽ nói rõ ở phần absolute.

Position: Absolute

position: absolute loại bỏ phần tử khỏi normal flow và định vị nó dựa trên containing block gần nhất có position khác static.

.absolute-box {
  position: absolute;
  top: 0;
  right: 0;
}

Cách hoạt động

Khi một phần tử có position: absolute:

  • Nó không còn chiếm không gian trong layout
  • Các phần tử khác sẽ “đè lên” vị trí cũ của nó
  • Nó được định vị dựa trên ancestor gần nhất có position khác static
<div class="card">
  <div class="badge">NEW</div>
  <p>Nội dung card...</p>
</div>
.card {
  position: relative; /* Tạo containing block */
  width: 300px;
  padding: 1.5rem;
  border: 1px solid #e5e7eb;
  border-radius: 0.5rem;
}

.badge {
  position: absolute;
  top: -10px;
  right: -10px;
  background: #ef4444;
  color: white;
  padding: 0.25rem 0.5rem;
  border-radius: 9999px;
  font-size: 0.75rem;
  font-weight: 600;
}

Badge nằm ở góc trên cùng bên phải của card — một pattern cực kỳ phổ biến.

Containing Block là gì?

Containing block quyết định vị trí và kích thước của phần tử absolute. Nó được xác định như sau:

  • Nếu phần tử cha có position: relative | absolute | fixed | sticky, nó là containing block
  • Nếu không, trình duyệt tìm lên ancestors tiếp theo
  • Nếu không có ancestor nào, viewport là containing block
/* Absolute dựa trên viewport → nằm ở góc phải màn hình */
.no-relative-parent {
  position: absolute;
  top: 0;
  right: 0;
}

/* Absolute dựa trên .container → nằm ở góc phải của container */
.container {
  position: relative;
}
.no-relative-parent {
  position: absolute;
  top: 0;
  right: 0;
}

Kích thước với Absolute

Absolute elements có thể dùng top: 0; bottom: 0; left: 0; right: 0 để căng ra toàn bộ containing block:

.overlay {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
}

Đây là cách tạo overlay phủ toàn bộ container.

Position: Fixed

position: fixed cũng loại bỏ phần tử khỏi normal flow, nhưng nó được định vị tương đối với viewport — không phải containing block.

.fixed-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 60px;
  background: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  z-index: 1000;
}

Đặc điểm

  • Luôn cố định tại một vị trí trên màn hình, dù trang có scroll
  • Không chiếm không gian trong normal flow → phần tử dưới nó bị đè
  • Không bị ảnh hưởng bởi scroll của parent — chỉ scroll viewport
  • Thường dùng cho: header, sidebar, back-to-top button, chat widget

Lưu ý khi dùng Fixed Header

/* Cần padding-top cho body để tránh bị header che */
body {
  padding-top: 60px; /* Bằng chiều cao header */
}

CSS Transform thay đổi Fixed behavior

Một chi tiết quan trọng: nếu ancestor của fixed element có transform, perspective, hoặc filter khác none, thì nó không còn fixed theo viewport nữa — nó fixed theo ancestor đó.

.parent {
  transform: translateX(0); /* Vô tình thay đổi behavior của fixed child! */
}
.child {
  position: fixed; /* Sẽ không fixed theo viewport! */
  top: 0;
}

Đây là một “tính năng” gây ra nhiều bug khó chịu. Nếu fixed element không hoạt động như mong đợi, hãy kiểm tra ancestors có transform không.

Position: Sticky

position: sticky là sự kết hợp giữa relativefixed — phần tử hoạt động như relative cho đến khi scroll đến một ngưỡng nhất định, sau đó nó “bám dính” như fixed.

.sticky-nav {
  position: sticky;
  top: 0; /* Ngưỡng: bám khi scroll chạm top */
  background: white;
  z-index: 100;
}

Cách hoạt động

Sticky element có 4 giai đoạn:

  1. Relative phase: Nằm trong normal flow, chưa dính
  2. Sticky phase: Khi scroll đến ngưỡng (top: 0), nó dính vào viewport
  3. Container boundary: Khi container của nó scroll ra khỏi viewport, sticky element cũng đi theo
  4. Release: Khi container hoàn toàn rời khỏi viewport, sticky element được thả
<section class="section">
  <h2 class="section-title">Phần 1</h2>
  <p>Nội dung dài...</p>
</section>
<section class="section">
  <h2 class="section-title">Phần 2</h2>
  <p>Nội dung dài...</p>
</section>
.section-title {
  position: sticky;
  top: 0;
  background: #3b82f6;
  color: white;
  padding: 0.75rem 1rem;
  margin: 0;
}

Kết quả: mỗi tiêu đề section sẽ dính ở đầu viewport khi bạn scroll qua section đó, và bị đẩy đi bởi tiêu đề của section tiếp theo.

Điều kiện để Sticky hoạt động

  1. Phải có top, right, bottom, hoặc left — ít nhất một ngưỡng
  2. Container phải có chiều cao lớn hơn sticky element (để có không gian scroll)
  3. Container không được có overflow: hidden (trên container hoặc ancestors)
/* ❌ Không hoạt động — overflow: hidden trên parent */
.parent {
  overflow: hidden;
}
.sticky {
  position: sticky;
  top: 0;
}

/* ✅ Hoạt động */
.parent {
  overflow: visible; /* hoặc không có overflow */
}
.sticky {
  position: sticky;
  top: 0;
}

Z-Index và Stacking Context

z-index kiểm soát thứ tự xếp chồng của các phần tử trên trục Z (trước/sau). Nhưng không đơn giản như “số lớn hơn là ở trên”.

Khi nào Z-Index hoạt động?

Z-index chỉ có tác dụng trên các phần tử có position khác static:

/* ✅ Z-index hoạt động */
.relative { position: relative; z-index: 1; }
.absolute { position: absolute; z-index: 2; }
.fixed    { position: fixed;    z-index: 3; }
.sticky   { position: sticky;   z-index: 4; }

/* ❌ Z-index không hoạt động */
.static   { position: static;   z-index: 9999; }

Stacking Context là gì?

Stacking context là một không gian 3D riêng biệt, nơi các phần tử con được xếp chồng độc lập với phần tử bên ngoài.

Một stacking context mới được tạo khi:

/* Position + z-index */
.element {
  position: relative;
  z-index: 1;
}

/* Opacity nhỏ hơn 1 */
.element { opacity: 0.9; }

/* Transform */
.element { transform: scale(1); }

/* Filter */
.element { filter: blur(0); }

/* will-change */
.element { will-change: transform; }

/* mix-blend-mode */
.element { mix-blend-mode: multiply; }

/* contain: layout | paint | strict */
.element { contain: layout; }

Vấn đề kinh điển với Stacking Context

<div class="parent-1" style="position: relative; z-index: 1;">
  <div class="child" style="position: relative; z-index: 9999;">
    Tôi có z-index 9999!
  </div>
</div>
<div class="parent-2" style="position: relative; z-index: 2;">
  <p>Nhưng tôi nằm trên bạn</p>
</div>

.child có z-index 9999, nó vẫn nằm dưới .parent-2 vì nó thuộc stacking context của .parent-1 (z-index: 1), mà .parent-2 có z-index: 2 cao hơn.

Quy tắc vàng: Z-index chỉ có ý nghĩa trong cùng một stacking context.

Bảng So Sánh 5 Giá trị Position

Giá trịNormal FlowĐịnh vị dựa trênChiếm không gianScroll behavior
static✅ Có✅ CóTheo luồng
relative✅ CóVị trí gốc✅ CóTheo luồng
absolute❌ KhôngContaining block❌ KhôngTheo container
fixed❌ KhôngViewport (⚠️ trừ khi có transform)❌ KhôngCố định màn hình
sticky✅ Có (ban đầu)Viewport + container✅ CóDính khi scroll

Ví dụ Thực Tế

1. Modal Overlay

<div class="modal-overlay">
  <div class="modal">
    <button class="modal-close">&times;</button>
    <h2>Xác nhận</h2>
    <p>Bạn có chắc muốn xoá?</p>
    <div class="modal-actions">
      <button class="btn-cancel">Huỷ</button>
      <button class="btn-confirm">Xoá</button>
    </div>
  </div>
</div>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
}

.modal {
  position: relative;
  background: white;
  padding: 2rem;
  border-radius: 0.75rem;
  max-width: 400px;
  width: 90%;
}

.modal-close {
  position: absolute;
  top: 0.5rem;
  right: 0.75rem;
  background: none;
  border: none;
  font-size: 1.5rem;
  cursor: pointer;
  opacity: 0.5;
  transition: opacity 0.2s;
}

.modal-close:hover {
  opacity: 1;
}

2. Sticky Table Header

table {
  border-collapse: collapse;
  width: 100%;
}

thead th {
  position: sticky;
  top: 0;
  background: #1f2937;
  color: white;
  padding: 0.75rem 1rem;
  z-index: 10;
}

tbody tr:nth-child(even) {
  background: #f9fafb;
}

tbody td {
  padding: 0.5rem 1rem;
  border-bottom: 1px solid #e5e7eb;
}

3. Tooltip với Position Absolute

<button class="tooltip-trigger">
  Hover vào tôi
  <span class="tooltip-text">Đây là tooltip giải thích</span>
</button>
.tooltip-trigger {
  position: relative;
  padding: 0.5rem 1rem;
  background: #3b82f6;
  color: white;
  border: none;
  border-radius: 0.25rem;
  cursor: pointer;
}

.tooltip-text {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  background: #1f2937;
  color: white;
  padding: 0.5rem 0.75rem;
  border-radius: 0.25rem;
  font-size: 0.75rem;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s;
}

.tooltip-trigger:hover .tooltip-text {
  opacity: 1;
}

/* Mũi tên tooltip */
.tooltip-text::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: #1f2937;
}

4. Fixed Back-to-Top Button

.back-to-top {
  position: fixed;
  bottom: 2rem;
  right: 2rem;
  width: 48px;
  height: 48px;
  background: #3b82f6;
  color: white;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
  z-index: 1000;
  transition: transform 0.2s, opacity 0.2s;
}

.back-to-top:hover {
  transform: translateY(-4px);
}

.back-to-top.hidden {
  opacity: 0;
  pointer-events: none;
}

5. Sticky Section Navigator

.section-nav {
  position: sticky;
  top: 80px; /* 80px cho fixed header */
  max-height: calc(100vh - 80px);
  overflow-y: auto;
  width: 250px;
  padding: 1rem;
  border-left: 2px solid #e5e7eb;
}

.section-nav a {
  display: block;
  padding: 0.5rem 1rem;
  color: #6b7280;
  text-decoration: none;
  border-radius: 0.25rem;
  transition: background 0.2s, color 0.2s;
}

.section-nav a:hover,
.section-nav a.active {
  background: #eff6ff;
  color: #3b82f6;
}

Best Practices

1. Luôn đặt Position cho Container

Khi dùng position: absolute, hãy luôn đặt position: relative cho container nếu bạn muốn absolute dựa trên container đó:

/* ✅ Luôn làm rõ container */
.card {
  position: relative;
}
.card-badge {
  position: absolute;
  top: 0;
  right: 0;
}

2. Tránh Z-index Quá Cao

/* ❌ Z-index vô tội vạ */
.header { z-index: 99999; }
.modal { z-index: 999999; }
.toast { z-index: 9999999; }

/* ✅ Quản lý z-index có hệ thống */
:root {
  --z-dropdown: 100;
  --z-sticky: 200;
  --z-header: 300;
  --z-overlay: 400;
  --z-modal: 500;
  --z-toast: 600;
}

.header { z-index: var(--z-header); }
.modal { z-index: var(--z-modal); }
.toast { z-index: var(--z-toast); }

3. Cẩn Thận với Transform

Nếu bạn dùng transform trên parent, hãy nhớ rằng nó ảnh hưởng đến fixed children và tạo stacking context mới:

/* ⚠️ Transform vô tình thay đổi fixed behavior */
.page-wrapper {
  transform: translateZ(0); /* GPU acceleration */
  /* Tất cả fixed children bên trong sẽ không còn fixed theo viewport! */
}

4. Sticky và Overflow

/* ❌ Sticky không hoạt động */
.parent {
  overflow-x: hidden;
}
.child {
  position: sticky;
  top: 0;
}

/* ✅ Chỉ overflow: visible để sticky hoạt động */
.parent {
  overflow: visible;
}

5. Performance

  • position: fixedposition: sticky kích hoạt GPU compositing — thường nhanh
  • Tránh thay đổi top/left trong JavaScript animation — dùng transform: translate() thay thế
  • position: absolute là hiệu quả cho overlay/loading states

Debugging Tips

Khi positioning không hoạt động như mong đợi:

  1. Kiểm tra DevTools → Computed → Position có đúng không?
  2. Kiểm tra containing block: ancestor gần nhất có position khác static?
  3. Kiểm tra stacking context: ancestor có tạo stacking context mới không?
  4. Kiểm tra overflow: ancestors có overflow: hidden không?
  5. Kiểm tra transform: ancestor có transform không (ảnh hưởng fixed)?
/* Debug: highlight tất cả containing blocks */
* {
  outline: 1px solid rgba(59, 130, 246, 0.1);
}
*:is([style*="position: relative"], [style*="position: absolute"], 
  [style*="position: fixed"], [style*="position: sticky"]) {
  outline: 2px solid #3b82f6;
}

Kết luận

CSS Positioning là một trong những công cụ mạnh mẽ nhất trong tay web developer. Hiểu rõ 5 giá trị position và cách chúng tương tác với nhau sẽ giúp bạn:

  • ✅ Tạo layout phức tạp mà không cần JavaScript
  • ✅ Xây dựng UI components chuyên nghiệp (modal, tooltip, dropdown, badge)
  • ✅ Debug nhanh chóng khi gặp vấn đề về vị trí phần tử
  • ✅ Viết CSS sạch hơn, ít hack hơn

Hãy nhớ: relative để làm container, absolute để đặt con, fixed cho UI toàn trang, sticky cho navigation section. Và đừng quên stacking context — nó là nguyên nhân số 1 gây ra các vấn đề z-index khó hiểu!


Bài viết được đăng bởi Devs2.org. Nếu bạn thấy bài viết hữu ích, hãy chia sẻ với đồng nghiệp của bạn!

Recently Used Tools