Cách sử dụng truy vấn vùng chứa ngay bây giờ

Gần đây, Chris Coyier đã viết một bài đăng trên blog đặt câu hỏi:

Hiện tại, tất cả công cụ trình duyệt đều hỗ trợ truy vấn vùng chứa, tại sao không có nhiều nhà phát triển sử dụng những truy vấn này hơn?

Bài đăng của Chris liệt kê một số lý do tiềm ẩn (ví dụ: thiếu nhận thức, thói quen cũ dần chết đi), nhưng có một lý do cụ thể nổi bật.

Một số nhà phát triển cho biết họ muốn sử dụng truy vấn vùng chứa ngay bây giờ nhưng nghĩ rằng họ không thể vì họ vẫn phải hỗ trợ các trình duyệt cũ.

Như bạn có thể đoán từ tiêu đề, chúng tôi cho rằng hầu hết các nhà phát triển đều có thể sử dụng truy vấn vùng chứa ngay bây giờ—trong phiên bản sản xuất—ngay cả khi bạn phải hỗ trợ các trình duyệt cũ hơn. Bài đăng này sẽ hướng dẫn bạn phương pháp mà chúng tôi đề xuất để thực hiện việc này.

Một hướng tiếp cận thực tế

Nếu muốn sử dụng truy vấn vùng chứa trong mã của mình ngay bây giờ, nhưng muốn trải nghiệm giống nhau trong tất cả trình duyệt, bạn có thể triển khai tính năng dự phòng dựa trên JavaScript cho các trình duyệt không hỗ trợ truy vấn vùng chứa.

Tiếp đến, câu hỏi sẽ trở thành: quy trình dự phòng toàn diện đến mức nào?

Như với mọi giải pháp dự phòng, thách thức là phải cân bằng hợp lý giữa mức độ hữu ích và hiệu suất. Đối với các tính năng CSS, thường không thể hỗ trợ API đầy đủ (xem lý do không sử dụng polyfill). Tuy nhiên, bạn có thể đi xa hơn nữa bằng cách xác định bộ chức năng cốt lõi mà hầu hết các nhà phát triển muốn sử dụng, sau đó tối ưu hoá tính năng dự phòng chỉ cho những tính năng đó.

Nhưng "tập hợp chức năng cốt lõi" là gì mà hầu hết các nhà phát triển muốn cho truy vấn vùng chứa là gì? Để trả lời câu hỏi đó, hãy cân nhắc cách hầu hết các nhà phát triển xây dựng trang web thích ứng hiện bằng truy vấn nội dung đa phương tiện.

Khá nhiều hệ thống thiết kế và thư viện thành phần hiện đại đều được chuẩn hoá theo nguyên tắc ưu tiên thiết bị di động, được triển khai bằng cách sử dụng một tập hợp các điểm ngắt được xác định trước (chẳng hạn như SM, MD, LG, XL). Theo mặc định, các thành phần được tối ưu hoá để hiển thị tốt trên màn hình nhỏ, sau đó các kiểu được phân lớp có điều kiện để hỗ trợ một tập hợp cố định chiều rộng màn hình lớn hơn. (Bạn có thể xem tài liệu về BootstrapTailwind để biết các ví dụ về vấn đề này.)

Phương pháp này phù hợp với các hệ thống thiết kế dựa trên vùng chứa cũng như với các hệ thống thiết kế dựa trên khung nhìn, vì trong hầu hết các trường hợp, điều liên quan đến nhà thiết kế không phải là độ lớn của màn hình hoặc khung nhìn, mà chỉ là không gian có sẵn cho thành phần trong bối cảnh đã bố trí. Nói cách khác, thay vì các điểm ngắt liên quan đến toàn bộ khung nhìn (và có thể áp dụng cho toàn bộ trang), điểm ngắt sẽ áp dụng cho các vùng nội dung cụ thể, chẳng hạn như thanh bên, hộp thoại phương thức hoặc nội dung bài đăng.

Nếu bạn có thể làm việc trong các hạn chế của phương pháp dựa trên điểm ngắt, ưu tiên thiết bị di động (hầu hết các nhà phát triển hiện đang làm), thì việc triển khai phương pháp dự phòng dựa trên vùng chứa cho phương pháp đó sẽ dễ dàng hơn đáng kể so với việc triển khai hỗ trợ đầy đủ cho mọi tính năng truy vấn vùng chứa đơn lẻ.

Phần tiếp theo giải thích chính xác cách tất cả quy trình này hoạt động, cùng với hướng dẫn từng bước cho bạn biết cách triển khai trên trang web hiện tại.

Cách hoạt động

Bước 1: cập nhật kiểu thành phần để dùng quy tắc @container thay vì quy tắc @media

Trong bước đầu tiên này, hãy xác định bất kỳ thành phần nào trên trang web của bạn mà bạn cho rằng sẽ hưởng lợi từ việc định kích thước dựa trên vùng chứa thay vì định kích thước dựa trên khung nhìn.

Bạn chỉ nên bắt đầu với một hoặc hai thành phần để xem chiến lược này hoạt động như thế nào, nhưng nếu bạn muốn chuyển đổi 100% thành phần sang định kiểu dựa trên vùng chứa thì cũng tốt! Điều tuyệt vời của chiến lược này là bạn có thể áp dụng dần nếu cần.

Sau khi đã xác định các thành phần muốn cập nhật, bạn cần phải thay đổi từng quy tắc @media trong các thành phần đó CSS với một quy tắc @container.

Dưới đây là ví dụ về giao diện của thành phần đó trên thành phần .photo-gallery, theo mặc định là một cột duy nhất, sau đó sử dụng các quy tắc @media để cập nhật bố cục của nó để trở thành 2 và 3 cột trong các điểm ngắt MD và XL (tương ứng):

.photo-gallery {
  display: grid;
  grid-template-columns: 1fr;
}

/* Styles for the `MD` breakpoint */
@media (min-width: 800px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/* Styles for the `XL` breakpoint */
@media (min-width: 1200px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

Để cập nhật thành phần .photo-gallery nhằm sử dụng các quy tắc @container, trước tiên hãy thay thế chuỗi @media bằng chuỗi @container trong CSS. Ngữ pháp của hai quy tắc này đủ tương tự để trong nhiều trường hợp, bạn có thể chỉ cần thay đổi điều này.

Tuỳ thuộc vào thiết kế của trang web, có thể bạn cũng cần cập nhật điều kiện về kích thước, đặc biệt là khi quy tắc @media của trang web đang đưa ra một số giả định nhất định về không gian sẽ dành cho các thành phần cụ thể ở nhiều kích thước khung nhìn.

Ví dụ: nếu kiểu cho CSS .photo-gallery tại điểm ngắt MDXL trong ví dụ trước giả định rằng thanh bên rộng 200 pixel sẽ được hiển thị tại các điểm ngắt đó, thì điều kiện kích thước cho quy tắc @container phải ít hơn khoảng 200 pixel (giả sử "vùng chứa" cho thành phần .photo-gallery sẽ không có thanh bên.

Nhìn chung, để chuyển đổi CSS .photo-gallery từ quy tắc @media thành quy tắc @container, bạn có thể thực hiện toàn bộ các thay đổi như sau:

/* Before, using the original breakpoint sizes: */
@media (min-width: 800px) { /* ... */ }
@media (min-width: 1200px) { /* ... */ }

/* After, with the breakpoint sizes reduced by 200px: */
@container (min-width: 600px) { /* ... */ }
@container (min-width: 1000px) { /* ... */ }

Lưu ý rằng bạn không phải thay đổi bất kỳ kiểu nào trong khối khai báo, vì các kiểu đó phản ánh cách thành phần hiển thị thay vì thời điểm áp dụng các kiểu cụ thể.

Sau khi cập nhật kiểu thành phần từ quy tắc @media thành quy tắc @container, bước tiếp theo là định cấu hình các phần tử vùng chứa.

Bước 2: thêm các phần tử vùng chứa vào HTML của bạn

Bước trước đã xác định kiểu thành phần dựa trên kích thước của phần tử vùng chứa. Bước tiếp theo là xác định những phần tử trên trang của bạn nên là những phần tử vùng chứa có kích thước tương ứng với quy tắc @container.

Bạn có thể khai báo một phần tử bất kỳ là phần tử vùng chứa trong CSS bằng cách đặt thuộc tính container-type của phần tử đó thành size hoặc inline-size. Nếu các quy tắc vùng chứa của bạn dựa trên chiều rộng, thì inline-size thường là giá trị bạn muốn sử dụng.

Hãy xem xét một trang web có cấu trúc HTML cơ bản sau:

<body>
  <div class="sidebar">...</div>
  <div class="content">...</div>
</body>

Để tạo các phần tử .sidebar.content trên vùng chứa của trang web này, hãy thêm quy tắc sau vào CSS của bạn:

.content, .sidebar {
  container-type: inline-size;
}

Đối với các trình duyệt hỗ trợ truy vấn vùng chứa, CSS này là tất cả những gì bạn cần để tạo kiểu thành phần được xác định ở bước trước có liên quan đến vùng nội dung chính hoặc thanh bên, tuỳ thuộc vào phần tử nào chứa chúng.

Tuy nhiên, đối với các trình duyệt không hỗ trợ truy vấn vùng chứa, bạn vẫn cần thực hiện thêm một số thao tác.

Bạn cần thêm một số mã phát hiện thời điểm kích thước của các phần tử vùng chứa thay đổi, sau đó cập nhật DOM dựa trên những thay đổi đó theo cách mà CSS của bạn có thể kết nối.

May mắn là mã cần thiết để thực hiện việc đó là rất nhỏ và có thể được tóm tắt hoàn toàn vào một thành phần dùng chung mà bạn có thể sử dụng trên mọi trang web và trong bất kỳ vùng nội dung nào.

Đoạn mã sau đây xác định một phần tử <responsive-container> có thể sử dụng lại. Phần tử này tự động theo dõi các thay đổi về kích thước và thêm các lớp điểm ngắt mà CSS của bạn có thể tạo kiểu dựa trên:

// A mapping of default breakpoint class names and min-width sizes.
// Redefine these (or add more) as needed based on your site's design.
const defaultBreakpoints = {SM: 400, MD: 600 LG: 800, XL: 1000};

// A resize observer that monitors size changes to all <responsive-container>
// elements and calls their `updateBreakpoints()` method with the updated size.
const ro = new ResizeObserver((entries) => {
  entries.forEach((e) => e.target.updateBreakpoints(e.contentRect));
});

class ResponsiveContainer extends HTMLElement {
  connectedCallback() {
    const bps = this.getAttribute('breakpoints');
    this.breakpoints = bps ? JSON.parse(bps) : defaultBreakpoints;
    this.name = this.getAttribute('name') || '';
    ro.observe(this);
  }
  disconnectedCallback() {
    ro.unobserve(this);
  }
  updateBreakpoints(contentRect) {
    for (const bp of Object.keys(this.breakpoints)) {
      const minWidth = this.breakpoints[bp];
      const className = this.name ? `${this.name}-${bp}` : bp;
      this.classList.toggle(className, contentRect.width >= minWidth);
    }
  }
}

self.customElements.define('responsive-container', ResponsiveContainer);

Mã này hoạt động bằng cách tạo một ResizeObserver tự động theo dõi các thay đổi về kích thước đối với bất kỳ phần tử <responsive-container> nào trong DOM. Nếu thay đổi kích thước khớp với một trong các kích thước điểm ngắt đã xác định, thì một lớp có tên điểm ngắt đó sẽ được thêm vào phần tử (và bị xoá nếu điều kiện không còn khớp).

Ví dụ: nếu width của phần tử <responsive-container> nằm trong khoảng từ 600 đến 800 pixel (dựa trên các giá trị điểm ngắt mặc định được thiết lập trong mã), thì lớp SMMD sẽ được thêm như sau:

<responsive-container class="SM MD">...</responsive-container>

Các lớp này cho phép bạn xác định kiểu dự phòng cho trình duyệt không hỗ trợ truy vấn vùng chứa (xem bước 3: thêm kiểu dự phòng vào CSS).

Để cập nhật mã HTML trước đó nhằm sử dụng phần tử vùng chứa này, hãy thay đổi phần tử <div> của thanh bên và nội dung chính thành phần tử <responsive-container>:

<body>
  <responsive-container class="sidebar">...</responsive-container>
  <responsive-container class="content">...</responsive-container>
</body>

Trong hầu hết trường hợp, bạn chỉ cần sử dụng phần tử <responsive-container> mà không cần tuỳ chỉnh, nhưng nếu cần tuỳ chỉnh, bạn có thể sử dụng các lựa chọn sau:

  • Kích thước điểm ngắt tuỳ chỉnh: Mã này sử dụng một nhóm tên lớp điểm ngắt mặc định và kích thước chiều rộng tối thiểu, nhưng bạn sẽ thay đổi các giá trị mặc định này thành bất cứ thứ gì bạn muốn. Bạn cũng có thể ghi đè các giá trị này theo từng phần tử bằng thuộc tính breakpoints.
  • Vùng chứa được đặt tên: Mã này cũng hỗ trợ các vùng chứa được đặt tên bằng cách truyền thuộc tính name. Điều này có thể quan trọng nếu bạn cần lồng các phần tử vùng chứa. Hãy xem phần các giới hạn để biết thêm chi tiết.

Dưới đây là ví dụ đặt cả hai tuỳ chọn cấu hình này:

<responsive-container
  name='sidebar'
  breakpoints='{"bp4":400,"bp5":500,"bp6":600,"bp7":700,"bp8":800,"bp9":900,"bp10":1000}'>
</responsive-container>

Cuối cùng, khi gói mã này, hãy đảm bảo bạn sử dụng chức năng phát hiện tính năng và import() động để chỉ tải mã nếu trình duyệt không hỗ trợ truy vấn vùng chứa.

if (!CSS.supports('container-type: inline-size')) {
  import('./path/to/responsive-container.js');
}

Bước 3: Thêm kiểu dự phòng vào CSS của bạn

Bước cuối cùng trong chiến lược này là thêm kiểu dự phòng cho các trình duyệt không nhận ra kiểu được xác định trong quy tắc @container. Bạn có thể thực hiện việc này bằng cách sao chép các quy tắc đó bằng cách sử dụng các lớp điểm ngắt được đặt trong phần tử <responsive-container>.

Tiếp tục với ví dụ về .photo-gallery trước đó, kiểu dự phòng cho hai quy tắc @container có thể có dạng như sau:

/* Container query styles for the `MD` breakpoint. */
@container (min-width: 600px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/* Fallback styles for the `MD` breakpoint. */
@supports not (container-type: inline-size) {
  :where(responsive-container.MD) .photo-gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/* Container query styles for the `XL` breakpoint. */
@container (min-width: 1000px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

/* Fallback styles for the `XL` breakpoint. */
@supports not (container-type: inline-size) {
  :where(responsive-container.XL) .photo-gallery {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

Trong mã này, mỗi quy tắc @container có một quy tắc tương đương khớp có điều kiện với phần tử <responsive-container> nếu có lớp điểm ngắt tương ứng.

Phần của bộ chọn khớp với phần tử <responsive-container> được bao bọc trong bộ chọn lớp giả chức năng :where(), để duy trì tính cụ thể của bộ chọn dự phòng tương đương với tính cụ thể của bộ chọn ban đầu trong quy tắc @container.

Mỗi quy tắc dự phòng cũng được bao bọc trong một phần khai báo @supports. Mặc dù điều này không thực sự cần thiết để tính năng dự phòng hoạt động, nhưng điều này có nghĩa là trình duyệt hoàn toàn bỏ qua các quy tắc này nếu nó hỗ trợ truy vấn vùng chứa, điều này có thể cải thiện hiệu suất so khớp kiểu nói chung. Điều này cũng có thể cho phép các công cụ bản dựng hoặc CDN xoá những nội dung khai báo đó nếu biết trình duyệt hỗ trợ các truy vấn vùng chứa và không cần những kiểu dự phòng đó.

Nhược điểm chính của chiến lược dự phòng này là bạn phải lặp lại khai báo kiểu hai lần, việc này vừa tẻ nhạt vừa dễ xảy ra lỗi. Tuy nhiên, nếu đang sử dụng bộ tiền xử lý CSS, bạn có thể tóm tắt mã đó vào một trình trộn tạo cả quy tắc @container và mã dự phòng cho bạn. Dưới đây là một ví dụ về cách sử dụng Sass:

@use 'sass:map';

$breakpoints: (
  'SM': 400px,
  'MD': 600px,
  'LG': 800px,
  'XL': 1000px,
);

@mixin breakpoint($breakpoint) {
  @container (min-width: #{map.get($breakpoints, $breakpoint)}) {
    @content();
  }
  @supports not (container-type: inline-size) {
    :where(responsive-container.#{$breakpoint}) & {
      @content();
    }
  }
}

Sau đó, khi có kiểu kết hợp này, bạn có thể cập nhật kiểu thành phần .photo-gallery ban đầu thành kiểu như thế này để loại bỏ hoàn toàn việc trùng lặp:

.photo-gallery {
  display: grid;
  grid-template-columns: 1fr;

  @include breakpoint('MD') {
    grid-template-columns: 1fr 1fr;
  }

  @include breakpoint('XL') {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

Đó là tất cả những gì cần làm!

Tóm tắt

Tóm lại, dưới đây là cách cập nhật mã của bạn để sử dụng truy vấn vùng chứa ngay bây giờ với tính năng dự phòng trên nhiều trình duyệt.

  1. Các thành phần nhận dạng mà bạn muốn định kiểu tương ứng với vùng chứa của thành phần đó và cập nhật các quy tắc @media trong CSS của các thành phần đó để sử dụng quy tắc @container. Ngoài ra (nếu bạn chưa thực hiện), hãy chuẩn hoá một nhóm tên điểm ngắt để khớp với các điều kiện về kích thước trong quy tắc vùng chứa của bạn.
  2. Thêm JavaScript hỗ trợ phần tử <responsive-container> tuỳ chỉnh, sau đó thêm phần tử <responsive-container> vào bất kỳ vùng nội dung nào trên trang mà bạn muốn các thành phần tương ứng.
  3. Để hỗ trợ các trình duyệt cũ hơn, hãy thêm kiểu dự phòng vào CSS sao cho khớp với các lớp điểm ngắt được tự động thêm vào phần tử <responsive-container> trong HTML của bạn. Tốt nhất là sử dụng trình kết hợp bộ tiền xử lý CSS để tránh phải viết cùng một kiểu hai lần.

Điều tuyệt vời của chiến lược này là chi phí thiết lập một lần, nhưng sau đó bạn không mất thêm công sức để thêm các thành phần mới và xác định kiểu tương ứng với vùng chứa cho các thành phần đó.

Xem tính năng trong thực tế

Có lẽ cách tốt nhất để hiểu rõ tất cả các bước này kết hợp với nhau như thế nào là xem bản minh hoạ cách hoạt động của các bước này.

Video về một người dùng tương tác với các truy vấn về vùng chứa trang web minh hoạ. Người dùng đổi kích thước các vùng nội dung để cho thấy cách các kiểu thành phần cập nhật dựa trên kích thước của vùng nội dung chứa các kiểu đó.

Bản minh hoạ này là phiên bản cập nhật của một trang web được tạo vào năm 2019 (trước khi các truy vấn vùng chứa tồn tại) để giúp minh hoạ lý do tại sao truy vấn vùng chứa lại cần thiết trong việc xây dựng thư viện thành phần thực sự thích ứng.

Vì trang web này đã có các kiểu được xác định cho một loạt "thành phần thích ứng", nên đây là một lựa chọn hoàn hảo để thử nghiệm chiến lược được giới thiệu ở đây trên một trang web không quan trọng. Hoá ra việc cập nhật thực sự khá đơn giản và hầu như không cần thay đổi kiểu trang web ban đầu.

Bạn có thể xem mã nguồn minh hoạ đầy đủ trên GitHub và nhớ xem cụ thể CSS của thành phần minh hoạ để biết cách xác định kiểu dự phòng. Nếu bạn chỉ muốn thử nghiệm hành vi dự phòng thì đã có bản minh hoạ chỉ dự phòng bao gồm biến thể đó – ngay cả trong các trình duyệt hỗ trợ truy vấn vùng chứa.

Hạn chế và những điểm có thể cải thiện

Như đã đề cập ở đầu bài đăng này, chiến lược được nêu ở đây hoạt động hiệu quả cho phần lớn trường hợp sử dụng mà các nhà phát triển thực sự quan tâm khi tiếp cận các truy vấn vùng chứa.

Tuy nhiên, có một số trường hợp sử dụng nâng cao hơn mà chiến lược này cố ý không hỗ trợ, sẽ được giải quyết trong phần tiếp theo:

Đơn vị truy vấn vùng chứa

Thông số kỹ thuật cho các truy vấn vùng chứa xác định một số đơn vị mới, tất cả đều liên quan đến kích thước của vùng chứa. Mặc dù có thể hữu ích trong một số trường hợp, nhưng phần lớn thiết kế thích ứng có thể thực hiện được thông qua các phương tiện hiện có, chẳng hạn như tỷ lệ phần trăm hoặc sử dụng bố cục lưới hoặc linh hoạt.

Điều đó có nghĩa là nếu cần sử dụng đơn vị truy vấn vùng chứa, bạn có thể dễ dàng hỗ trợ thêm cho các đơn vị đó bằng cách sử dụng thuộc tính tuỳ chỉnh. Cụ thể, bằng cách xác định thuộc tính tuỳ chỉnh cho mỗi đơn vị được sử dụng trên phần tử vùng chứa, như sau:

responsive-container {
  --cqw: 1cqw;
  --cqh: 1cqh;
}

Sau đó, bất cứ khi nào bạn cần truy cập vào các đơn vị truy vấn vùng chứa, hãy sử dụng các thuộc tính đó, thay vì sử dụng chính đơn vị:

.photo-gallery {
  font-size: calc(10 * var(--cqw));
}

Sau đó, để hỗ trợ các trình duyệt cũ hơn, hãy đặt giá trị cho các thuộc tính tuỳ chỉnh đó trên phần tử vùng chứa trong lệnh gọi lại ResizeObserver.

class ResponsiveContainer extends HTMLElement {
  // ...
  updateBreakpoints(contentRect) {
    this.style.setProperty('--cqw', `${contentRect.width / 100}px`);
    this.style.setProperty('--cqh', `${contentRect.height / 100}px`);

    // ...
  }
}

Điều này cho phép bạn "chuyển" các giá trị đó từ JavaScript đến CSS, sau đó bạn sẽ có toàn bộ sức mạnh của CSS (ví dụ: calc(), min(), max(), clamp()) để thao tác chúng khi cần thiết.

Hỗ trợ thuộc tính logic và chế độ ghi

Bạn có thể đã nhận thấy việc sử dụng inline-size thay vì width trong phần khai báo @container ở một số ví dụ về CSS. Bạn cũng có thể nhận thấy các đơn vị cqicqb mới (cho kích thước cùng dòng và khối theo thứ tự tương ứng). Những tính năng mới này phản ánh việc CSS chuyển sang thuộc tính và giá trị logic thay vì thuộc tính vật lý hoặc giá trị định hướng.

Rất tiếc, các API như Resize Observer vẫn báo cáo các giá trị trong widthheight. Vì vậy, nếu thiết kế của bạn cần sử dụng tính linh hoạt cho các thuộc tính logic, thì bạn cần tự tìm ra điều đó.

Mặc dù bạn có thể tạo chế độ viết bằng cách sử dụng phương thức như getComputedStyle() truyền vào phần tử vùng chứa, nhưng việc này sẽ gây ra hao tổn và không thực sự là cách hay để phát hiện xem chế độ viết có thay đổi hay không.

Vì lý do này, cách tốt nhất là chính phần tử <responsive-container> nên chấp nhận thuộc tính chế độ ghi mà chủ sở hữu trang web có thể đặt (và cập nhật) khi cần. Để triển khai việc này, bạn làm theo phương pháp tương tự đã trình bày trong phần trước, rồi hoán đổi widthheight nếu cần.

Vùng chứa lồng nhau

Thuộc tính container-name cho phép bạn đặt tên cho vùng chứa mà sau đó bạn có thể tham chiếu trong quy tắc @container. Các vùng chứa được đặt tên rất hữu ích nếu bạn có các vùng chứa lồng trong vùng chứa và bạn cần các quy tắc nhất định để chỉ khớp với các vùng chứa nhất định (không chỉ vùng chứa cấp trên gần nhất).

Chiến lược dự phòng nêu tại đây sử dụng bộ kết hợp con cháu để tạo kiểu cho các phần tử phù hợp với một số lớp điểm ngắt. Điều này có thể bị lỗi nếu bạn có các vùng chứa lồng nhau, vì bất kỳ số lượng lớp điểm ngắt nào từ nhiều đối tượng cấp trên của phần tử vùng chứa đều có thể khớp với một thành phần nhất định cùng một lúc.

Ví dụ: ở đây có 2 phần tử <responsive-container> gói thành phần .photo-gallery, nhưng vì vùng chứa bên ngoài lớn hơn vùng chứa bên trong nên chúng sẽ được thêm vào các lớp điểm ngắt khác nhau.

<responsive-container class="SM MD LG">
  ...
  <responsive-container class="SM">
    ...
    <div class="photo-gallery">...</div class="photo-gallery">
  </responsive-container>
</responsive-container>

Trong ví dụ này, lớp MDLG trên vùng chứa bên ngoài sẽ ảnh hưởng đến các quy tắc kiểu khớp với thành phần .photo-gallery, nhưng quy tắc này không phù hợp với hành vi của các truy vấn vùng chứa (vì các quy tắc này chỉ khớp với vùng chứa đối tượng cấp trên gần nhất).

Để xử lý vấn đề này, hãy áp dụng một trong hai cách sau:

  1. Hãy đảm bảo bạn luôn đặt tên cho mọi vùng chứa mà bạn đang lồng, sau đó đảm bảo các lớp điểm ngắt có tiền tố là tên vùng chứa đó để tránh xung đột.
  2. Sử dụng bộ kết hợp con thay vì bộ kết hợp con trong các bộ chọn dự phòng (cách này có một chút hạn chế).

Phần vùng chứa lồng nhau của trang web minh hoạ có một ví dụ về cách hoạt động này bằng cách sử dụng các vùng chứa có tên, cùng với Sass Mixin mà nó sử dụng trong mã để tạo kiểu dự phòng cho cả quy tắc @container có tên và chưa đặt tên.

Còn những trình duyệt không hỗ trợ :where(), Phần tử tuỳ chỉnh hoặc Trình quan sát đổi kích thước thì sao?

Mặc dù có vẻ tương đối mới, nhưng những API này đã được hỗ trợ trên tất cả các trình duyệt trong hơn 3 năm và đều là một phần của Baseline được cung cấp rộng rãi.

Vì vậy, trừ khi bạn có dữ liệu cho thấy một phần đáng kể khách truy cập trang web của bạn ở trên các trình duyệt không hỗ trợ một trong những tính năng này, thì không có lý do gì để không tự do sử dụng chúng mà không có tính năng dự phòng.

Ngay cả khi đó, đối với trường hợp sử dụng cụ thể này, điều tồi tệ nhất có thể xảy ra là tính năng dự phòng sẽ không hoạt động cho một tỷ lệ rất nhỏ người dùng của bạn, có nghĩa là họ sẽ thấy chế độ xem mặc định thay vì chế độ xem được tối ưu hoá cho kích thước vùng chứa.

Chức năng của trang web vẫn phải hoạt động và đó là điều thực sự quan trọng.

Tại sao bạn không chỉ sử dụng đoạn mã polyfill truy vấn vùng chứa?

Các tính năng của CSS rất khó để polyfill và thường đòi hỏi phải triển khai lại toàn bộ trình phân tích cú pháp CSS và logic phân tầng của trình phân tích cú pháp trong JavaScript. Do đó, các tác giả CSS polyfill phải thực hiện nhiều đánh đổi hầu như luôn đi kèm với nhiều hạn chế về tính năng cũng như chi phí hiệu suất đáng kể.

Vì những lý do này, bạn thường không nên sử dụng CSS polyfill trong quá trình sản xuất, bao gồm cả container-query-polyfill từ Google Chrome Labs, vốn không còn được duy trì (và chủ yếu chỉ dùng cho mục đích minh hoạ).

Chiến lược dự phòng được thảo luận ở đây có ít hạn chế hơn, yêu cầu ít mã hơn và sẽ hoạt động tốt hơn đáng kể so với bất kỳ polyfill truy vấn vùng chứa nào.

Thậm chí bạn có cần triển khai tính năng dự phòng cho các trình duyệt cũ hơn không?

Nếu lo ngại về bất kỳ hạn chế nào được đề cập ở đây, bạn nên tự hỏi liệu mình có thực sự cần triển khai tính năng dự phòng ngay từ đầu hay không. Suy cho cùng, cách dễ nhất để tránh những giới hạn này là chỉ sử dụng tính năng này mà không có phương án dự phòng nào. Nói thật trong nhiều trường hợp, đó có thể là một lựa chọn hoàn toàn hợp lý.

Theo caniuse.com, các truy vấn về vùng chứa được 90% người dùng Internet toàn cầu hỗ trợ. Đối với nhiều người đọc bài đăng này, con số này có thể cao hơn một chút đối với cơ sở người dùng của họ. Vì vậy, điều quan trọng cần lưu ý là hầu hết người dùng sẽ thấy phiên bản truy vấn vùng chứa của giao diện người dùng. Và với 10% người dùng không sử dụng ứng dụng này, sẽ không có chuyện họ sẽ bị hỏng trải nghiệm. Khi làm theo chiến lược này, trong trường hợp xấu nhất những người dùng này sẽ thấy trang web mặc định hoặc "thiết bị di động" bố cục cho một số thành phần, đây không phải là tận thế.

Khi đưa ra phương án đánh đổi, bạn nên tối ưu hoá cho phần lớn người dùng, thay vì mặc định sử dụng phương pháp mẫu số chung phổ biến nhất nhằm mang lại cho tất cả người dùng trải nghiệm nhất quán nhưng tương đương.

Vì vậy, trước khi giả định rằng bạn không thể sử dụng các truy vấn vùng chứa do thiếu sự hỗ trợ của trình duyệt, hãy thực sự dành thời gian để xem xét trải nghiệm sẽ như thế nào nếu bạn thực sự chọn sử dụng chúng. Sự đánh đổi sẽ xứng đáng, ngay cả khi không có bất kỳ phương án dự phòng nào.

Hướng đến tương lai

Hy vọng bài đăng này đã thuyết phục bạn rằng hiện có thể sử dụng truy vấn vùng chứa trong phiên bản chính thức và bạn không phải đợi hàng năm trời cho đến khi tất cả trình duyệt không hỗ trợ hoàn toàn biến mất.

Mặc dù chiến lược được nêu ở đây có thể đòi hỏi nhiều công sức hơn, nhưng chiến lược đó phải đơn giản và dễ hiểu để hầu hết mọi người có thể áp dụng trên trang web của họ. Tuy nhiên, tất nhiên vẫn có chỗ để giúp bạn dễ áp dụng hơn nữa. Bạn nên hợp nhất nhiều phần riêng biệt thành một thành phần duy nhất (được tối ưu hoá cho một khung hoặc ngăn xếp cụ thể) để xử lý mọi công việc keo dán cho bạn. Nếu bạn tạo ra công cụ như thế này, hãy cho chúng tôi biết để chúng tôi có thể giúp bạn quảng bá ứng dụng đó!

Cuối cùng, ngoài các truy vấn vùng chứa, còn có rất nhiều tính năng CSS và giao diện người dùng thú vị hiện có thể tương tác trên tất cả các công cụ trình duyệt chính. Với tư cách là một cộng đồng, hãy tìm hiểu cách chúng ta có thể thực sự sử dụng các tính năng đó ngay bây giờ để người dùng có thể được hưởng lợi.


Nội dung cập nhật (ngày 25 tháng 7 năm 2024): ban đầu là hướng dẫn trong "Bước 1" đề xuất rằng các truy vấn phương tiện và truy vấn vùng chứa có thể sử dụng cùng điều kiện kích thước. Điều này thường đúng nhưng không phải lúc nào cũng đúng (vì một số lý do đã được chỉ ra chính xác). Hướng dẫn mới cập nhật sẽ làm rõ vấn đề này và đưa ra các trường hợp ví dụ mà điều kiện về kích thước có thể cần thay đổi.

Bản cập nhật (ngày 2 tháng 7 năm 2024): ban đầu, tất cả các ví dụ về mã CSS đều sử dụng Sass (để nhất quán với đề xuất cuối cùng). Dựa trên ý kiến phản hồi của độc giả, chúng tôi đã cập nhật một vài CSS đầu tiên thành CSS thuần tuý và Sass chỉ được dùng trong các mã mẫu yêu cầu sử dụng hỗn hợp.