Bảng điều khiển hành động hoặc nhóm nút bật lên là một mẫu hữu ích để người dùng truy cập vào chức năng bổ sung mà không chiếm nhiều không gian. Mẫu lớp phủ này có bố cục phức tạp vì bảng điều khiển phải luôn được liên kết với một phần tử kích hoạt trong khi vẫn phải thích ứng với các quy tắc ràng buộc của khung nhìn. Thông thường, việc này đòi hỏi các thư viện JavaScript phức tạp (như Popper.js hoặc Floating UI) để tính toán vị trí và xử lý xung đột.
Tính năng Định vị neo CSS cung cấp một cách khai báo, tối ưu hoá hiệu suất để xử lý hoàn toàn các mối quan hệ này trong CSS, cho phép trình duyệt quản lý logic định vị và tràn một cách tự nhiên.
1. Xác định mối quan hệ giữa nút và bảng điều khiển
Bước đầu tiên là tạo một nút kích hoạt để mở vùng chứa lớp phủ bằng Popover API.
Sự khác biệt BẮT BUỘC về khả năng hỗ trợ tiếp cận: Mẫu này mô hình hoá rõ ràng một nhóm nút hiển thị bên trong một cửa sổ bật lên thay vì một trình đơn ARIA thực sự. Đừng áp dụng role="menu" hoặc role="menuitem" trừ phi bạn triển khai đầy đủ hợp đồng điều hướng bằng bàn phím tương ứng (chẳng hạn như xử lý thao tác điều hướng bằng phím mũi tên không gian giữa các mục). Vì lý do tương tự, đừng thêm aria-haspopup vào điều kiện kích hoạt – giá trị của điều kiện kích hoạt (menu, listbox, tree, grid hoặc true cũ) là một lời hứa rằng mục tiêu sẽ hiển thị một vai trò phù hợp, nhưng mẫu này không làm như vậy.
<button popovertarget="action-panel">
Open Actions
</button>
<!-- Use the Popover API (`popover="auto"`) for the overlay to ensure it is placed in the top layer and handled accessibly by the browser. -->
<div id="action-panel" popover="auto" class="panel">
<button class="action-item" type="button">Edit</button>
</div>
Thao tác này sẽ tạo mối liên kết neo ngầm giữa nút và bảng điều khiển, nhờ đó, bảng điều khiển có thể được đặt ở vị trí tương ứng với nút mà không cần anchor-name trên nút hoặc position-anchor trên bảng điều khiển.
2. Định vị bằng position-area
Thay vì độ lệch top/left thủ công, hãy sử dụng position-area để đặt mục tiêu trên lưới 3x3 tương ứng với neo.
.panel {
/*
Position the panel below the anchor (block-end),
aligned to the start of the anchor and spanning to its end (span-inline-end).
*/
position-area: block-end span-inline-end;
/* Reset insets to allow the grid to take control */
inset: auto;
}
Ưu tiên từ khoá logic (span-inline-end, block-start) hơn từ khoá vật lý (left, top) để tự động hỗ trợ RTL và các chế độ viết khác nhau.
BẮT BUỘC: Đừng kết hợp từ khoá vật lý và từ khoá logic trong position-area.
3. Triển khai khả năng phục hồi biên (Dự phòng)
Để ngăn bảng điều khiển bị cắt ở cạnh màn hình, hãy xác định "chiến thuật thử" mà trình duyệt nên thử nếu vị trí mặc định bị tràn.
.panel {
/*
If the panel overflows the bottom, flip it to the top (flip-block).
If it overflows the inline edges, flip it horizontally (flip-inline).
If it overflows both (e.g., in a corner), flip on both axes (flip-block flip-inline).
*/
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}
Nếu trình đơn thả xuống hoặc trình đơn bật ra của bạn có mũi tên hoặc đường viền định hướng cần thay đổi kiểu tuỳ thuộc vào vị trí dự phòng đã chọn, hãy xem phần Chú thích nhận biết vị trí. Để biết cách gọi hộp thoại và cửa sổ bật lên theo cách khai báo mà không cần JavaScript, hãy xem bài viết Điều khiển hộp thoại và cửa sổ bật lên theo cách khai báo.
Chiến lược dự phòng
Phương án dự phòng và hỗ trợ trình duyệt cho Popover
Popover API chủ yếu là cải tiến tăng dần, nhưng các hành vi xác định của API này (quảng bá lớp trên cùng, đóng nhẹ và gọi popovertarget) không có hành vi tương đương chỉ dùng CSS. Các trình duyệt cũ cần có polyfill hoặc phương án dự phòng thủ công nếu bạn không muốn cung cấp polyfill.
Polyfill: Để hỗ trợ thuộc tính popover trong các trình duyệt cũ, hãy tải @oddbird/popover-polyfill theo điều kiện. BẮT BUỘC: Phát hiện tính năng bằng cách kiểm tra thuộc tính popover trên HTMLElement.prototype và chỉ tải polyfill khi thiếu tính năng hỗ trợ gốc — KHÔNG tải polyfill vô điều kiện.
Với trình đóng gói hoặc bản đồ nhập:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
Nếu không có trình đóng gói, hãy nhập từ CDN bên trong <script type="module">:
<script type="module">
if (!("popover" in HTMLElement.prototype)) {
import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
}
</script>
Lưu ý về kiểu: Polyfill không thể xác định lớp giả :popover-open thực, vì vậy, lớp này sẽ áp dụng lớp .\:popover-open. BẮT BUỘC: Kết hợp cả hai bằng :is() hoặc :where(), nếu không, các trình duyệt thiếu :popover-open sẽ loại bỏ toàn bộ quy tắc:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
Ngoài ra, đối với phương án dự phòng cũ không có polyfill, hãy sử dụng position: fixed và tính toán toạ độ theo cách thủ công thông qua getBoundingClientRect() hoặc dựa vào vị trí mặc định bằng inset: auto nếu điều đó phù hợp với trường hợp sử dụng.
Để hỗ trợ các trình duyệt không có tính năng định vị neo, bạn phải đặt một vị trí hợp lý. Theo mặc định, cửa sổ bật lên được căn giữa màn hình, điều này có thể phù hợp với trường hợp sử dụng của bạn.
Trong một số trường hợp sử dụng, bạn có thể dùng polyfill @oddbird/css-anchor-positioning để hỗ trợ một số trường hợp sử dụng định vị neo. Hệ thống không hỗ trợ neo ngầm định, vì vậy, bạn PHẢI thêm tên neo vào điều kiện kích hoạt. Ngoài ra, polyfill không hỗ trợ position-area trên cửa sổ bật lên, vì vậy, bạn PHẢI sử dụng anchor() trên các phần lồng ghép mong muốn.
<!-- MANDATORY: Conditionally install the anchor positioning polyfill -->
<script type="module">
if (!("anchorName" in document.documentElement.style)) {
await import("https://unpkg.com/@oddbird/css-anchor-positioning");
}
</script>
.panel {
/* Mandatory: use explicit anchor name */
position-anchor: --kebab-anchor;
/* Mandatory: use insets rather than position-area for positioning */
bottom: auto;
right: auto;
top: anchor(bottom);
left: anchor(left);
margin: 0;
}