Trước đây, để áp dụng các bố cục khác nhau cho một thành phần dựa trên nội dung của thành phần đó, bạn cần phải sử dụng JavaScript hoặc logic có điều kiện trong ngôn ngữ tạo mẫu HTML để chèn các lớp đối tượng sửa đổi (như .card--has-image hoặc .card--text-only).
Lớp giả :has() loại bỏ nhu cầu này bằng cách đóng vai trò là bộ chọn mẹ. Bộ chọn này cho phép bạn tạo kiểu có điều kiện cho một phần tử vùng chứa dựa trên sự hiện diện hoặc vắng mặt của các phần tử con cụ thể.
Khi sử dụng :has(), bạn có thể dễ dàng xác định các biến thể bố cục riêng biệt hoàn toàn trong CSS dựa trên nội dung DOM thực tế của một thành phần. Bạn cũng có thể kết hợp thuộc tính này với :not() để nhắm mục tiêu rõ ràng đến trường hợp không có nội dung nhằm xác định bố cục mặc định.
Triển khai kiểu vùng chứa dựa trên nội dung
BẮT BUỘC: Bạn phải sử dụng bộ chọn :has() trên phần tử vùng chứa để phát hiện sự hiện diện của nội dung con cụ thể.
Cách tạo một thành phần thay đổi bố cục dựa trên nội dung:
- Xác định kiểu mặc định: Áp dụng kiểu bố cục cơ bản cho phần tử vùng chứa (ví dụ: một ngăn xếp một cột đơn giản).
- Áp dụng cơ chế ghi đè dựa trên nội dung: Nhắm mục tiêu vùng chứa bằng
:has([child-selector])và áp dụng kiểu bố cục mới khi nội dung đó xuất hiện (ví dụ: lưới nhiều cột).
Ví dụ: Một thành phần thẻ chuyển sang bố cục song song nếu có hình ảnh.
/* 1. Define the default state on the component container */
/* This applies when there is NO image */
.article-card {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.5rem;
border: 1px solid #ccc;
border-radius: 8px;
}
/* 2. Apply styles when a specific child element is present */
/* MANDATORY: Target the container and use :has() to check for the descendant */
.article-card:has(img) {
/* Change the layout to a row if an image exists */
flex-direction: row;
align-items: center;
}
/* Optional: Style the image itself (no :has() needed here) */
.article-card img {
width: 150px;
height: auto;
border-radius: 4px;
}
/* You can also combine :has() and :not() to explicitly target the ABSENCE of content */
/* This selects a card that DOES NOT have an image */
.article-card:not(:has(img)) {
/* E.g., apply a different background color for text-only cards */
background-color: #f9f9f9;
}
<!-- Assume an <h1> precedes these components in the document layout -->
<!-- A card WITH an image (will use row layout) -->
<article class="article-card">
<img src="thumbnail.jpg" alt="Article thumbnail" />
<div class="content">
<h2>Card With Image</h2>
<p>This card lays out its content horizontally.</p>
</div>
</article>
<!-- A card WITHOUT an image (will use default column layout) -->
<article class="article-card">
<div class="content">
<h2>Text-Only Card</h2>
<p>This card lays out its content vertically, and gets its background color from the :not(:has()) rule.</p>
</div>
</article>
Mẹo về hiệu suất: Khi sử dụng :has(), hãy đặt phạm vi của bộ chọn cho vùng chứa thành phần cụ thể nhất có thể (như .article-card). Tránh liên kết bộ chọn với các phần tử cấp cao như body:has(img) nếu các thay đổi về kiểu được bản địa hoá cao, vì các truy vấn :has() rộng có thể kích hoạt nhiều lần tính toán lại bố cục hơn.
Chiến lược dự phòng
Nếu kiểu bố cục dựa trên nội dung là yếu tố quan trọng đối với trải nghiệm người dùng hoặc thiết kế trang, thì bạn phải cung cấp một phương án dự phòng cho các trình duyệt không hỗ trợ bộ chọn :has(). Đối với các hiệu ứng trang trí thuần tuý, :has() có thể được sử dụng làm tính năng cải tiến tăng dần mà không cần dự phòng.
BẮT BUỘC: Khi triển khai phương án dự phòng cho các bố cục quan trọng, bạn phải sử dụng @supports not selector(:has(*)) trong CSS để xác định phương án dự phòng dựa trên lớp truyền thống (ví dụ: .has-image).
Không giống như kiểu dựa trên trạng thái tương tác, sự hiện diện của nội dung thường được biết đến tại thời điểm kết xuất. Phương án dự phòng hiệu quả nhất là yêu cầu công cụ tạo mẫu phía máy chủ (hoặc trình tạo trang web tĩnh) chèn một lớp như .has-image vào vùng chứa nếu phần tử con (như hình ảnh) tồn tại trong dữ liệu.
Nếu không thể sử dụng tính năng kết xuất phía máy chủ, bạn phải sử dụng một tập lệnh nhỏ có CSS.supports() để phát hiện nội dung và thêm lớp khi tải hoặc sau khi chèn nội dung động.
/* Fallback CSS for older browsers */
/* We check if the browser DOES NOT support the :has() selector */
@supports not selector(:has(*)) {
/* Define a traditional modifier class that applies the exact same layout overrides */
.article-card.has-image {
flex-direction: row;
align-items: center;
}
.article-card:not(.has-image) {
background-color: #f9f9f9;
}
}
/* Fallback JavaScript for older browsers (if not using SSR to add the class) */
/* Check for support before running the script to avoid unnecessary work in modern browsers */
if (!CSS.supports('selector(:has(*))')) {
// Find all components that need checking
const cards = document.querySelectorAll('.article-card');
cards.forEach(card => {
// If the critical content exists, manually add the fallback class
if (card.querySelector('img')) {
card.classList.add('has-image');
}
});
}