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

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

Hỗ trợ trình duyệt

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

Nguồn

Tuy nhiên, thông số chứa 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 thành phần mẹ. Kể từ Chromium 111, bạn có thể áp dụng tính năng chứa kiểu cho các giá trị thuộc tính tuỳ chỉnh và truy vấn phần tử mẹ để biết giá trị của một thuộc tính tuỳ chỉnh.

Hỗ trợ trình duyệt

  • Chrome: 111.
  • Edge: 111.
  • Firefox: không được hỗ trợ.
  • Safari: 18.

Nguồn

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

Thông số kỹ thuật của Mô-đun chứa 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 bất kỳ kiểu nào từ một thành phần 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, truy vấn 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 khi 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 truy vấn kiểu với thuộc tính tuỳ chỉnh CSS:

Bắt đầu sử dụng truy vấn kiểu

Giả sử chúng ta có 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. Điều 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 thành phần mẹ trực tiếp hay gián tiếp.

Truy vấn phần tử mẹ trực tiếp

Sơ đồ của truy vấn kiểu.

Không giống như với truy vấn kiểu, bạn không cần áp dụng tính năng chứa 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 các kiểu của phần tử mẹ trực tiếp. Tuy nhiên, chúng ta cần áp dụng kiểu (giá trị thuộc tính tuỳ chỉnh trong trường hợp này) cho một vùng chứa (.card-container trong trường hợp này) 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 vòng lặp vô hạn.

Để truy vấn trực tiếp một phần tử 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; 
    ...
  }
}

Bạn có thể nhận thấy rằng truy vấn kiểu bao bọc truy vấn bằng style(). Việc này giúp phân biệt các giá trị kích thước với các kiểu. Ví dụ: bạn có thể viết truy vấn cho chiều rộng của vùng chứa là @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à 200px. 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ác truy vấn kiểu. Đó là lý do bạn nên sử dụng trình bao bọc style() để làm rõ sự khác biệt: @container style(min-width: 200px) { … }.

Định kiểu 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 container-name cho phần tử đó. 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 container-name cho .card-list và tham chiếu kiểu đó trong truy vấn kiểu.

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

Nhìn chung, bạn nên đặt tên cho các vùng chứa để làm rõ nội dung bạn đang truy vấn 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 hữu ích của tính năng này là nếu bạn muốn trực tiếp tạo kiểu 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, thì các thành phần này không thể truy vấn trực tiếp vùng chứa đó.

Nhưng tất cả những điều này sẽ có ý nghĩa hơn nhiều trong thực tế. Hãy cùng xem một số ví dụ:

Truy vấn kiểu 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;hết hàng&quot; và thẻ &quot;hế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ể sử 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 nhất định. Ví dụ này cho thấy một nhóm thẻ sản phẩm có cùng 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 "Hàng tồn kho thấp", được kích hoạt bằng một thuộc tính tuỳ chỉnh có tên --detail. Ngoài ra, nếu sản phẩm có trạng thái "Số lượng còn ít", thì sản phẩm đó sẽ có nền đường viền màu đỏ đậm. Loại thông tin này có thể được máy chủ hiển thị và có thể á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à sử 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;
  }
}

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

Thẻ thời tiết

Ví dụ trước đã sử dụng một thuộc tính tuỳ chỉnh duy nhất có nhiều giá trị có thể áp dụng kiểu. Tuy nhiên, bạn cũng có thể kết hợp các thuộc tính này 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 sau:

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

Để tạo kiểu cho hiệu ứng chuyển màu nền và biểu tượng cho các thẻ này, hãy tìm các đặc điểm thời tiết, chẳng hạn như "có 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ác đặc điểm riêng biệt của thẻ đó. Tuy nhiên, 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 toán tử kết hợp and giống như đối với truy vấn nội dung nghe nhìn. Ví dụ: một ngày có cả mây và nắng sẽ có dạ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;
  }
}

Phân 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ẽ 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 đ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. Và đ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ể hiển thị kiểu có mây một phần.

Bạn có thể cập nhật 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 <parentElem>.style.setProperty('--myProperty’, <value>). I

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

Định kiểu cho thẻ bằng cách sử dụng truy vấn kiểu, JavaScript dùng để cập nhật 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 nêu 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 từ các truy vấn vùng chứa để giúp bạn xây dựng giao diện linh động, thích ứng. Riêng về truy vấn kiểu, vẫn còn một vài vấn đề chưa được giải quyết. Một trong số đó là việc triển khai truy vấn kiểu cho các kiểu CSS ngoài các thuộc tính tuỳ chỉnh. Đây đã là một phần của cấp thông số kỹ thuật hiện tại, nhưng chưa được triển khai trong bất kỳ trình duyệt nào. Dự kiến, việc đánh giá ngữ cảnh boolean sẽ được thêm vào cấp độ thông số kỹ thuật hiện tại khi vấn đề chưa được giải quyết, trong khi truy vấn phạm vi được lên kế hoạch cho cấp độ tiếp theo của thông số kỹ thuật.