0

Bóc trần "ma thuật" z-index trong CSS: Tại sao 99999 vẫn bị chìm nghỉm?

Cơn ác mộng mang tên z-index và những cú lừa

Hôm nay chúng ta sẽ tạm gác lại những câu chuyện về Database hay Backend để chuyển sang một chủ đề Frontend. Dù anh em chúng ta chuyên trị mảng hệ thống, logic, nhưng chắc chắn không ít lần phải "nhúng chàm" sửa lại cái giao diện. Và nhắc đến CSS, có một cơn ác mộng mang tên z-index.

Hãy thú thật đi, đã bao lần bạn thiết kế một cái modal popup hay một cái dropdown menu, nhưng nó lại bị một cái nút bấm hay một cái ảnh nào đó đè bẹp xí ở trên? Phản xạ đầu tiên của 99% anh em dev lúc đó là gì? Mở DevTools lên và "vã" ngay một câu thần chú: z-index: 9999;. Vẫn chưa nổi lên? Thêm vài số 9 nữa: z-index: 99999999;.

Nhưng trớ trêu thay, cái thẻ <div> đó vẫn chìm nghỉm một cách đầy thách thức. Tại sao một con số to chà bá như vậy lại bị đánh bại bởi một phần tử thậm chí chẳng thèm khai báo z-index? Hãy cùng mình lật tẩy những cú lừa của thuộc tính này.

1. Cú lừa thứ nhất: z-index không chơi với kẻ "tĩnh lặng" (Static)

Điều kiện tiên quyết, cũng là lỗi sơ đẳng nhất mà anh em hay mắc phải: z-index hoàn toàn vô dụng nếu phần tử đó có thuộc tính position: static (thuộc tính mặc định của mọi thẻ HTML).

Bạn có thể set z-index: 1000000 cho một cái <div>, nhưng nếu không khai báo position cho nó, trình duyệt sẽ coi như không thấy.

Kinh nghiệm: Để z-index có tác dụng, phần tử của bạn bắt buộc phải được kích hoạt một trong các trạng thái vị trí sau: relative, absolute, fixed, hoặc sticky. Hoặc, phần tử đó phải là con trực tiếp của một container đang dùng Flexbox hoặc CSS Grid.

2. Cú lừa thứ hai: "Gia phả" quan trọng hơn "Số má" (Stacking Context)

Đây chính là nguyên nhân gốc rễ giải thích tại sao z-index: 9999 vẫn thua z-index: 1. Trong CSS có một khái niệm cực kỳ quan trọng gọi là Stacking Context (Ngữ cảnh xếp chồng).

Hãy tưởng tượng Stacking Context giống như một gia đình.

  • Gia đình họ A có "địa vị" (z-index) là 1.
  • Gia đình họ B có "địa vị" (z-index) là 2.
  • Thằng con trai của gia đình A dù có làm "tổng thống" (z-index: 9999) ở trong nhà nó, thì khi bước ra ngoài xã hội, nó vẫn phải đứng dưới thằng con trai của gia đình B (dù thằng bé này chỉ có z-index: 1). Vì địa vị của gia đình B lớn hơn gia đình A.

Ví dụ thực tế:

<div class="header" style="position: relative; z-index: 1;">
    <!-- Nút menu có z-index cực khủng -->
    <div class="menu-dropdown" style="position: absolute; z-index: 9999;">
        Menu của tôi
    </div>
</div>
<div class="content" style="position: relative; z-index: 2;">
    <!-- Nội dung này sẽ đè lên cái Menu ở trên -->
    <div class="article">Bài viết này đè lên menu!</div>
</div>

Dù cái .menu-dropdownz-index là 9999, nó vĩnh viễn bị giam lỏng trong cái Stacking Context của thằng cha nó (.header với z-index: 1). Nó không bao giờ có thể ngoi lên đè được thằng .content (z-index: 2).

3. Cú lừa thứ ba: Những kẻ "phá bĩnh" âm thầm sinh ra Stacking Context

Đôi khi, bạn không hề thiết lập z-index hay position cho phần tử cha, nhưng phần tử con bên trong vẫn bị kẹt lại. Đó là vì trong CSS, có một số thuộc tính đặc biệt có khả năng âm thầm tạo ra một Stacking Context mới mà không báo trước.

Nếu bạn áp dụng bất kỳ thuộc tính nào dưới đây cho một thẻ <div>, nó sẽ tự động biến thành một "hộ gia đình" mới, nhốt toàn bộ z-index của các thẻ con vào trong:

  • opacity có giá trị nhỏ hơn 1 (ví dụ: opacity: 0.99).
  • Các hiệu ứng biến đổi: transform, filter, perspective, clip-path.
  • Thuộc tính isolation: isolate.
  • Các phần tử có mix-blend-mode khác normal.

Đây là lý do tại sao một cái Modal đang hiển thị đẹp đẽ, bỗng nhiên bạn thêm hiệu ứng mờ mờ (opacity) hoặc hiệu ứng trượt (transform: translateY) vào container bọc ngoài, cái Modal lập tức bị tụt xuống dưới các thành phần khác.

Lời kết: Code sao cho "Clean"?

Cuộc chiến với z-index là cuộc chiến của sự quy hoạch. Đừng bao giờ giải quyết bug UI bằng cách thêm số 9 vào z-index. Khi mọi thứ bị đè lên nhau, hãy bật tab Elements trong DevTools lên, đi ngược lên các thẻ cha để tìm xem thằng nào đang tạo ra Stacking Context và kìm hãm con của nó.

Kinh nghiệm xương máu:

  • Hạn chế sử dụng z-index bừa bãi. Hãy để các phần tử HTML đè lên nhau theo thứ tự render tự nhiên (thẻ viết sau sẽ đè thẻ viết trước).
  • Nếu phải dùng, hãy quy hoạch z-index một cách có hệ thống, ví dụ sử dụng CSS Variables (Biến CSS) để kiểm soát:
:root {
    --z-index-base: 10;
    --z-index-dropdown: 100;
    --z-index-sticky: 500;
    --z-index-modal: 1000;
    --z-index-tooltip: 9999;
}

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí