Tại sao position: sticky của bạn không hoạt động ?

Position: sticky là một trong những thuộc tính CSS mạnh mẽ, dùng trong nhiều trường hợp để nội dung được hoạt động xuyên suốt nhưng cũng rất dễ “không hoạt động” nếu hiểu sai bản chất


1. position: sticky hoạt động như thế nào?

Một element có position: sticky sẽ:

  1. Hoạt động như position: relative cho đến khi
  2. Một cạnh (top / bottom / left / right) chạm tới offset đã chỉ định trong vùng scroll
  3. Sau đó nó sẽ “dính” (stick) tại vị trí đó

📌 Quan trọng: Sticky luôn bị giới hạn trong phạm vi của parent (không vượt ra ngoài).

Ví dụ minh họa

.header {
  position: sticky;
  top: 0;
  background: white;
  z-index: 10;
}

Header này sẽ cuộn bình thường cho đến khi cạnh trên chạm vị trí 0px từ đỉnh viewport, sau đó sẽ “dính” lại ở đó khi bạn tiếp tục cuộn xuống.


2. Điều kiện bắt buộc để sticky hoạt động

2.1. Phải có ít nhất một offset

Sai:

.sticky {
  position: sticky;
}

Đúng:

.sticky {
  position: sticky;
  top: 0;
}

Tailwind CSS:

<div class="sticky top-0">...</div>

2.2. Parent không được có overflow: hidden | auto | scroll

Nếu bất kỳ cha nào có overflow khác visible → sticky bị vô hiệu hóa.

Sai:

.parent {
  overflow: hidden;
}

Fix:

  • Xoá overflow
  • Hoặc đưa sticky ra ngoài container đó
  • Hoặc áp dụng overflow cho một container bên trong

2.3. Parent phải đủ cao để có scroll

Sticky chỉ kích hoạt khi có scroll thật.

Sai:

<div style="height: 100px">
  <div class="sticky">Short parent</div>
</div>

Đúng:

<div style="min-height: 200vh">
  <div class="sticky">Tall parent</div>
</div>

2.4. Parent không được dùng transform

Nếu parent có:

  • transform: translateZ(0);
  • transform: scale(1);
  • hoặc animation/transition với transform

→ sticky không hoạt động.

Giải pháp: Di chuyển transform ra khỏi parent, hoặc áp dụng cho element khác.

2.5. z-index có thể làm bạn tưởng sticky không chạy

Sticky vẫn hoạt động nhưng bị che bởi các element khác.

.sticky {
  position: sticky;
  top: 0;
  z-index: 10;
  background: white; /* Quan trọng để không bị trong suốt */
}

Tailwind CSS:

<div class="sticky top-0 z-10 bg-white">...</div>

3. Vì sao top: 30px không hoạt động nhưng bottom: 30px lại được?

Đây là bẫy rất phổ biến và gây nhầm lẫn nhiều nhất.

Nguyên nhân cốt lõi

Sticky chỉ hoạt động nếu cạnh được chỉ định CÓ KHẢ NĂNG chạm tới offset đó khi scroll.

  • top: 30px → cạnh trên phải chạm 30px từ đỉnh vùng scroll
  • bottom: 30px → cạnh dưới phải chạm 30px từ đáy vùng scroll

👉 Trong layout của bạn:

  • Cạnh trên không bao giờ chạm được 30px → top không hoạt động
  • Nhưng cạnh dưới có thể chạm → bottom hoạt động

4. Các tình huống gây ra hiện tượng này

4.1. Element đã nằm sẵn gần top parent

<div class="parent">
  <div class="sticky" style="position: sticky; top: 30px">
    Tôi đã nằm ở đây từ đầu
  </div>
</div>

Phân tích:

  • Nếu sticky ban đầu đã nằm ≤ 30px từ top
  • top: 30px → không có thời điểm kích hoạt
  • Browser coi như position: relative

Trong khi đó:

  • Khi scroll, cạnh dưới vẫn có thể chạm đáy viewport
  • bottom hoạt động bình thường

4.2. Parent không đủ cao

Sticky bị giới hạn trong parent.

  • Khoảng cách từ sticky → đỉnh parent quá ngắn → top không kích hoạt
  • Khoảng cách tới đáy parent vẫn còn → bottom hoạt động
<div style="height: 200px"> <!-- Parent quá ngắn -->
  <div style="position: sticky; top: 30px">
    Không đủ khoảng cách để stick
  </div>
</div>

4.3. Scroll container không phải window

.wrapper {
  height: 100vh;
  overflow-y: auto;
}

top được tính theo .wrapper, không phải theo màn hình.

Nếu sticky đã gần top của wrapper → top không ăn.


5. Minh họa trực quan

top không hoạt động

┌─────────────┐
│  viewport   │
│             │
│   30px ↓    │ ← Mốc sticky mong muốn
│  [sticky]   │ ← Nhưng element đã nằm sẵn ở đây
│   content   │
│      ⋮      │
└─────────────┘

Không có lúc nào cạnh trên chạm mốc 30px → Sticky không kích hoạt

bottom hoạt động

┌─────────────┐
│   content   │
│      ⋮      │
│  [sticky]   │ ← Element bắt đầu ở đây
│             │
│   30px ↑    │ ← Khi scroll, cạnh dưới sẽ chạm mốc này
│  viewport   │
└─────────────┘

Cạnh dưới có lúc chạm 30px → Sticky kích hoạt


6. Cách fix top: 30px không hoạt động

✅ Cách 1: Tạo khoảng cách ban đầu

.sticky {
  margin-top: 40px; /* Đẩy xuống để có khoảng cách */
  position: sticky;
  top: 30px;
}

✅ Cách 2: Đặt sticky thấp hơn trong DOM

<div class="parent">
  <div style="height: 100px"></div> <!-- Spacer -->
  <div class="sticky" style="position: sticky; top: 30px">
    Bây giờ có khoảng cách để stick
  </div>
</div>

✅ Cách 3: Kiểm tra overflow của parent

.parent {
  overflow: visible; /* Hoặc bỏ hoàn toàn */
}

✅ Cách 4: Debug nhanh bằng outline

.sticky {
  position: sticky;
  top: 30px;
  outline: 2px solid red; /* Giúp nhìn rõ khi nào sticky kích hoạt */
}

Khi bạn scroll, nếu outline màu đỏ “dính” lại ở vị trí 30px từ trên xuống, nghĩa là sticky đã hoạt động.


7. Checklist debug sticky (90% fix được vấn đề)

Khi sticky không hoạt động, hãy check theo thứ tự:

  • top / bottom / left / right được khai báo?
  • Parent không có overflow: hidden/auto/scroll?
  • Có scroll thật (parent đủ cao)?
  • Parent không dùng transform?
  • z-indexbackground nếu cần?
  • Cạnh được chỉ định có khả năng chạm offset không?

Debug tool nhanh

/* Thêm vào element sticky để debug */
.sticky {
  outline: 3px dashed red;
  outline-offset: -3px;
}

/* Thêm vào parent để kiểm tra */
.parent {
  outline: 2px solid blue;
}

8. Ví dụ thực tế: Sticky header

❌ Không hoạt động

<div style="overflow: hidden"> <!-- Lỗi: overflow -->
  <header style="position: sticky; top: 0">
    Menu
  </header>
  <main>Content...</main>
</div>

✅ Hoạt động đúng

<div> <!-- Không có overflow -->
  <header style="position: sticky; top: 0; z-index: 10; background: white">
    Menu
  </header>
  <main style="min-height: 200vh">
    Content...
  </main>
</div>

Với Tailwind CSS

<div>
  <header class="sticky top-0 z-10 bg-white shadow">
    <nav class="container mx-auto">Menu</nav>
  </header>
  <main class="min-h-[200vh]">
    Content...
  </main>
</div>

9. Ví dụ nâng cao: Sticky sidebar

<div class="flex gap-4">
  <!-- Sidebar sticky -->
  <aside class="w-64">
    <div class="sticky top-4">
      <nav>Navigation links...</nav>
    </div>
  </aside>
  
  <!-- Main content -->
  <main class="flex-1 min-h-[200vh]">
    Long content...
  </main>
</div>

Tailwind CSS:

<div class="flex gap-4">
  <aside class="w-64">
    <div class="sticky top-4">
      <nav>Navigation</nav>
    </div>
  </aside>
  <main class="flex-1 min-h-[200vh]">Content</main>
</div>

10. Lưu ý khi dùng với framework

Nuxt / Vue

<template>
  <div class="container">
    <!-- ❌ Tránh -->
    <div style="overflow-x: hidden">
      <header class="sticky top-0">Header</header>
    </div>
    
    <!-- ✅ Đúng -->
    <header class="sticky top-0 z-10 bg-white">Header</header>
  </div>
</template>

<style scoped>
.container {
  /* Không dùng overflow ở đây */
}
</style>

React / Next.js

export default function Layout({ children }) {
  return (
    <div className="min-h-screen">
      <header className="sticky top-0 z-10 bg-white shadow">
        <nav>Menu</nav>
      </header>
      <main className="container mx-auto">
        {children}
      </main>
    </div>
  )
}

11. Kết luận

position: sticky không hề lỗi, chỉ là nó rất nghiêm ngặt về điều kiện kích hoạt.

Nguyên tắc vàng

“Cạnh này có thực sự chạm được offset đó khi scroll không?”

Nếu top không hoạt động nhưng bottom lại được, 99% là do cạnh trên không bao giờ chạm được offset đã chỉ định.

Lời khuyên cuối

  • Nếu bạn đang dùng Nuxt / Tailwind / layout phức tạp, chỉ cần sai 1 class overflow-hidden là sticky hỏng hoàn toàn
  • Luôn test sticky với outline hoặc background màu nổi để debug
  • Đọc kỹ các điều kiện ở phần 2, đặc biệt là overflowtransform
  • Khi gặp lỗi, check lại chiều cao parentvị trí ban đầu của element

💡 Pro tip: Tạo một component/utility class sticky chuẩn cho project và tái sử dụng, tránh phải debug nhiều lần.


Chúc bạn code vui! 🚀

0 Shares:
Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like

Tối ưu cách viết CSS

Table of Contents Hide BasicCSS Pre-ProcessorsSASSSCSSSự khác biệt cơ bản của SCSS và SASSSử dụng variablesMixinsNestedKĩ thuật OOCSSBEMTổng…