Bắt đầu với Truy vấn kiểu

Khả năng truy vấn kích thước nội tuyến của phần tử mẹ và các giá trị đơn vị truy vấn vùng chứa gần đây đã đạt được sự hỗ trợ ổn định trong tất cả các công cụ trình duyệt hiện đại.

Browser Support

  • Chrome: 105.
  • Edge: 105.
  • Firefox: 110.
  • Safari: 16.

Source

Tuy nhiên, thông số kỹ thuật về việc hạn chế không chỉ bao gồm các truy vấn về kích thước mà còn cho phép truy vấn các giá trị kiểu của phần tử mẹ. Kể từ Chromium 111, bạn sẽ có thể áp dụng tính năng hạn chế kiểu cho các giá trị thuộc tính tuỳ chỉnh và truy vấn một phần tử mẹ để biết giá trị của một thuộc tính tuỳ chỉnh.

Browser Support

  • Chrome: 111.
  • Edge: 111.
  • Firefox: 151.
  • Safari: 18.

Điều này có nghĩa là chúng ta có quyền kiểm soát logic nhiều hơn nữa đối với các kiểu trong CSS, đồng thời cho phép tách biệt tốt hơn logic và lớp dữ liệu của một ứng dụng khỏi các kiểu của ứng dụng đó.

Thông số kỹ thuật CSS Containment Module Level 3 (Mô-đun ngăn chặn CSS cấp 3) (bao gồm các truy vấn về kích thước và kiểu) cho phép truy vấn mọi kiểu từ một phần tử mẹ, bao gồm cả các cặp thuộc tính và giá trị như font-weight: 800. Tuy nhiên, trong quá trình triển khai tính năng này, các truy vấn về kiểu hiện chỉ hoạt động với các giá trị thuộc tính tuỳ chỉnh CSS. Điều này vẫn rất hữu ích cho việc kết hợp các kiểu và tách dữ liệu khỏi thiết kế. Hãy xem cách bạn sử dụng các truy vấn về kiểu với các thuộc tính tuỳ chỉnh CSS:

Làm quen với truy vấn kiểu

Giả sử chúng ta có đoạn mã HTML sau:

<ul class="card-list">
  <li class="card-container">
    <div class="card">
      ...
    </div>
  </li>
</ul>

Để sử dụng truy vấn kiểu, trước tiên, bạn phải thiết lập một phần tử vùng chứa. Việc này đòi hỏi một phương pháp hơi khác, tuỳ thuộc vào việc bạn đang truy vấn một thành phần mẹ trực tiếp hay gián tiếp.

Truy vấn đối tượng cha mẹ trực tiếp

Sơ đồ về một truy vấn kiểu.

Không giống như truy vấn kiểu, bạn không cần áp dụng tính năng ngăn chặn bằng cách sử dụng thuộc tính container-type hoặc container cho .card-container để .card có thể truy vấn kiểu của thành phần mẹ trực tiếp. Tuy nhiên, chúng ta cần áp dụng các kiểu (trong trường hợp này là các giá trị thuộc tính tuỳ chỉnh) cho một vùng chứa (trong trường hợp này là .card-container) hoặc bất kỳ phần tử nào chứa phần tử mà chúng ta đang tạo kiểu trong DOM. Chúng ta không thể áp dụng các kiểu mà chúng ta đang truy vấn trên phần tử trực tiếp mà chúng ta đang tạo kiểu bằng truy vấn đó vì điều này có thể gây ra các vòng lặp vô hạn.

Để trực tiếp truy vấn một thành phần mẹ, bạn có thể viết:

/* styling .card based on the value of --theme on .card-container */
@container style(--theme: warm) {
  .card {
    background-color: wheat;
    border-color: brown; 
    ...
  }
}

Có thể bạn đã nhận thấy rằng truy vấn kiểu sẽ bao bọc truy vấn bằng style(). Điều này là để phân biệt các giá trị kích thước với kiểu. Ví dụ: bạn có thể viết một truy vấn cho chiều rộng của vùng chứa dưới dạng @container (min-width: 200px) { … }. Thao tác này sẽ áp dụng các kiểu nếu vùng chứa mẹ có chiều rộng tối thiểu là 200 px. Tuy nhiên, min-width cũng có thể là một thuộc tính CSS và bạn có thể truy vấn giá trị CSS của min-width bằng cách sử dụng truy vấn kiểu. Đó là lý do bạn sẽ dùng trình bao bọc style() để làm rõ sự khác biệt: @container style(min-width: 200px) { … }.

Tạo kiểu cho các thành phần mẹ không trực tiếp

Nếu muốn truy vấn kiểu cho bất kỳ phần tử nào không phải là phần tử mẹ trực tiếp, bạn cần cung cấp cho phần tử đó một container-name. Ví dụ: chúng ta có thể áp dụng kiểu cho .card dựa trên kiểu của .card-list bằng cách cung cấp cho .card-list một container-name và tham chiếu đến container-name đó trong truy vấn kiểu.

/* styling .card based on the value of --moreGlobalVar on .card-list */
@container cards style(--moreGlobalVar: value) {
  .card {
    ...
  }
}

Thông thường, bạn nên đặt tên cho các vùng chứa để biết rõ bạn đang truy vấn nội dung gì và dễ dàng truy cập vào các vùng chứa đó hơn. Một ví dụ về trường hợp này là nếu bạn muốn tạo kiểu trực tiếp cho các phần tử trong .card. Nếu không có vùng chứa được đặt tên trên .card-container, họ sẽ không thể truy vấn trực tiếp vùng chứa đó.

Tuy nhiên, tất cả những điều này sẽ dễ hiểu hơn nhiều khi thực hành. Hãy cùng xem một số ví dụ:

Cụm từ tìm kiếm theo phong cách trong thực tế

Hình ảnh minh hoạ có nhiều thẻ sản phẩm, một số thẻ có thẻ &quot;mới&quot; hoặc &quot;còn ít hàng&quot; và thẻ &quot;còn ít hàng&quot; có nền màu đỏ.

Truy vấn kiểu đặc biệt hữu ích khi bạn có một thành phần có thể dùng lại với nhiều biến thể hoặc khi bạn không kiểm soát được tất cả các kiểu nhưng cần áp dụng các thay đổi trong một số trường hợp. Ví dụ này cho thấy một nhóm thẻ sản phẩm dùng chung thành phần thẻ. Một số thẻ sản phẩm có thêm thông tin/ghi chú như "Mới" hoặc "Còn ít hàng", được kích hoạt bằng một thuộc tính tuỳ chỉnh có tên là --detail. Ngoài ra, nếu một sản phẩm có "Số lượng thấp", sản phẩm đó sẽ có nền viền màu đỏ đậm. Loại thông tin này có khả năng được máy chủ kết xuất và có thể được áp dụng cho các thẻ thông qua các kiểu nội tuyến như sau:

 <div class="product-list">
  <div class="product-card-container" style="--detail: new">
    <div class="product-card">
      <div class="media">
        <img .../>
      <div class="comment-block"></div>
    </div>
  </div>
  <div class="meta">
    ...
  </div>
  </div>
  <div class="product-card-container" style="--detail: low-stock">
    ...
  </div>
  <div class="product-card-container">
    ...
  </div>
  ...
</div>

Với dữ liệu có cấu trúc này, bạn có thể truyền các giá trị đến --detail và dùng thuộc tính tuỳ chỉnh CSS này để áp dụng các kiểu:

@container style(--detail: new) {
  .comment-block {
    display: block;
  }
  
  .comment-block::after {
    content: 'New';
    border: 1px solid currentColor;
    background: white;
    ...
  }
}

@container style(--detail: low-stock) {
  .comment-block {
    display: block;
  }
  
  .comment-block::after {
    content: 'Low Stock';
    border: 1px solid currentColor;
    background: white;
    ...
  }
  
  .media-img {
    border: 2px solid brickred;
  }
}

Đoạn mã trên cho phép chúng ta áp dụng một chip cho --detail: low-stock--detail: new, nhưng có thể bạn đã nhận thấy một số điểm dư thừa trong khối mã. Hiện tại, không có cách nào để chỉ truy vấn sự hiện diện của --detail bằng @container style(--detail), điều này sẽ cho phép chia sẻ các kiểu tốt hơn và ít lặp lại hơn. Nhóm công tác hiện đang thảo luận về khả năng này.

Thẻ thời tiết

Ví dụ trước sử dụng một thuộc tính tuỳ chỉnh duy nhất với nhiều giá trị có thể có để áp dụng kiểu. Tuy nhiên, bạn cũng có thể kết hợp bằng cách sử dụng và truy vấn nhiều thuộc tính tuỳ chỉnh. Hãy xem ví dụ về thẻ thời tiết này:

Bản minh hoạ thẻ thời tiết.

Để tạo kiểu cho các biểu tượng và hiệu ứng chuyển màu nền của những thẻ này, hãy tìm các đặc điểm thời tiết, chẳng hạn như "nhiều mây", "mưa" hoặc "nắng":

@container style(--sunny: true) {
  .weather-card {
    background: linear-gradient(-30deg, yellow, orange);
  }
  
  .weather-card:after {
    content: url(<data-uri-for-demo-brevity>);
    background: gold;
  }
}

Bằng cách này, bạn có thể tạo kiểu cho từng thẻ dựa trên đặc điểm riêng của thẻ đó. Nhưng bạn cũng có thể tạo kiểu cho các tổ hợp đặc điểm (thuộc tính tuỳ chỉnh) bằng cách sử dụng bộ kết hợp and theo cách tương tự như đối với truy vấn nội dung nghe nhìn. Ví dụ: một ngày vừa có mây vừa có nắng sẽ trông như sau:

@container style(--sunny: true) and style(--cloudy: true) {
    .weather-card {
      background: linear-gradient(24deg, pink, violet);
    }
  
  .weather-card:after {
      content: url(<data-uri-for-demo-brevity>);
      background: violet;
  }
}

Tách dữ liệu khỏi thiết kế

Trong cả hai bản minh hoạ này, việc tách lớp dữ liệu (DOM sẽ được hiển thị trên trang) khỏi các kiểu được áp dụng sẽ mang lại lợi ích về cấu trúc. Các kiểu được viết dưới dạng các biến thể có thể có trong kiểu thành phần, trong khi một điểm cuối có thể gửi dữ liệu mà sau đó sẽ dùng để tạo kiểu cho thành phần. Bạn có thể sử dụng một giá trị duy nhất, chẳng hạn như trong trường hợp đầu tiên, cập nhật giá trị --detail hoặc nhiều biến, chẳng hạn như trong trường hợp thứ hai (đặt --rainy hoặc --cloudy hoặc --sunny. Điều tuyệt vời nhất là bạn cũng có thể kết hợp các giá trị này. Việc kiểm tra cả --sunny--cloudy có thể cho thấy kiểu thời tiết nhiều mây một phần.

Bạn có thể cập nhật các giá trị thuộc tính tuỳ chỉnh thông qua JavaScript một cách liền mạch, trong khi thiết lập mô hình DOM (tức là trong khi tạo thành phần trong một khung) hoặc cập nhật bất cứ lúc nào bằng cách sử dụng <parentElem>.style.setProperty('--myProperty’, <value>). I

Sau đây là một bản minh hoạ cập nhật --theme của một nút và áp dụng các kiểu bằng cách sử dụng truy vấn kiểu và thuộc tính tuỳ chỉnh đó (--theme) trong một vài dòng mã:

Tạo kiểu cho thẻ bằng cách sử dụng các truy vấn kiểu, JavaScript dùng để cập nhật các giá trị thuộc tính tuỳ chỉnh là:

const themePicker = document.querySelector('#theme-picker')
const btnParent = document.querySelector('.btn-section');

themePicker.addEventListener('input', (e) => {
  btnParent.style.setProperty('--theme', e.target.value);
})

Các tính năng được trình bày chi tiết trong bài viết này chỉ là bước khởi đầu. Bạn có thể mong đợi nhiều điều hơn nữa từ các truy vấn vùng chứa để giúp bạn tạo giao diện động, thích ứng. Đối với các truy vấn về kiểu cụ thể, vẫn còn một số vấn đề chưa được giải quyết. Một là việc triển khai các truy vấn kiểu cho kiểu CSS ngoài các thuộc tính tuỳ chỉnh. Đây đã là một phần của cấp độ quy cách hiện tại, nhưng chưa được triển khai trong bất kỳ trình duyệt nào. Hoạt động đánh giá bối cảnh boolean dự kiến sẽ được thêm vào cấp độ thông số kỹ thuật hiện tại khi vấn đề nổi bật được giải quyết, trong khi truy vấn theo dải được lên kế hoạch cho cấp độ tiếp theo của thông số kỹ thuật.