CustomStateSet
Baseline
2024
Newly available
Since May 2024, this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.
Giao diện CustomStateSet của Document Object Model lưu trữ danh sách các trạng thái cho một phần tử tùy chỉnh tự trị, và cho phép thêm hoặc xóa các trạng thái khỏi tập hợp.
Giao diện này có thể được sử dụng để hiển thị các trạng thái nội bộ của một phần tử tùy chỉnh, cho phép sử dụng chúng trong các bộ chọn CSS bởi code sử dụng phần tử đó.
Thuộc tính phiên bản
CustomStateSet.size-
Trả về số lượng giá trị trong
CustomStateSet.
Phương thức phiên bản
CustomStateSet.add()-
Thêm một giá trị vào tập hợp.
CustomStateSet.clear()-
Xóa tất cả phần tử khỏi đối tượng
CustomStateSet. CustomStateSet.delete()-
Xóa một giá trị khỏi đối tượng
CustomStateSet. CustomStateSet.entries()-
Trả về một iterator mới chứa các giá trị cho mỗi phần tử trong
CustomStateSettheo thứ tự chèn vào. CustomStateSet.forEach()-
Thực thi hàm được cung cấp cho mỗi giá trị trong đối tượng
CustomStateSet. CustomStateSet.has()-
Trả về
Booleanxác nhận xem phần tử có mặt với giá trị đã cho hay không. CustomStateSet.keys()-
Tên gọi khác của
CustomStateSet.values(). CustomStateSet.values()-
Trả về một đối tượng iterator mới tạo ra các giá trị cho mỗi phần tử trong đối tượng
CustomStateSettheo thứ tự chèn vào.
Mô tả
Các phần tử HTML tích hợp có thể có các trạng thái khác nhau, chẳng hạn như "enabled" và "disabled", "checked" và "unchecked", "initial", "loading" và "ready". Một số trạng thái này là công khai và có thể được đặt hoặc truy vấn bằng thuộc tính/attribute, trong khi các trạng thái khác là nội bộ và không thể đặt trực tiếp. Dù là bên ngoài hay bên trong, các trạng thái phần tử thường có thể được chọn và tạo kiểu bằng CSS pseudo-class làm bộ chọn.
CustomStateSet cho phép các nhà phát triển thêm và xóa trạng thái cho các phần tử tùy chỉnh tự trị (nhưng không phải các phần tử dẫn xuất từ phần tử tích hợp).
Các trạng thái này sau đó có thể được sử dụng làm bộ chọn pseudo-class trạng thái tùy chỉnh tương tự như các pseudo-class cho phần tử tích hợp.
Đặt trạng thái phần tử tùy chỉnh
Để CustomStateSet có sẵn, một phần tử tùy chỉnh trước tiên phải gọi HTMLElement.attachInternals() để gắn đối tượng ElementInternals.
CustomStateSet sau đó được trả về bởi ElementInternals.states.
Lưu ý rằng ElementInternals không thể gắn vào phần tử tùy chỉnh dựa trên phần tử tích hợp, vì vậy tính năng này chỉ hoạt động cho các phần tử tùy chỉnh tự trị (xem github.com/whatwg/html/issues/5166).
Phiên bản CustomStateSet là một đối tượng giống Set có thể giữ một tập hợp có thứ tự các giá trị trạng thái.
Mỗi giá trị là một định danh tùy chỉnh.
Các định danh có thể được thêm vào tập hợp hoặc xóa khỏi tập hợp.
Nếu một định danh có trong tập hợp thì trạng thái cụ thể đó là true, còn nếu bị xóa thì trạng thái đó là false.
Các phần tử tùy chỉnh có trạng thái với nhiều hơn hai giá trị có thể biểu diễn chúng bằng nhiều trạng thái boolean, trong đó chỉ một trạng thái là true (có trong CustomStateSet) tại một thời điểm.
Các trạng thái có thể được sử dụng trong phần tử tùy chỉnh nhưng không thể truy cập trực tiếp bên ngoài thành phần tùy chỉnh.
Tương tác với CSS
Bạn có thể chọn một phần tử tùy chỉnh đang ở một trạng thái cụ thể bằng cách sử dụng pseudo-class trạng thái tùy chỉnh :state().
Định dạng của pseudo-class này là :state(my-state-name), trong đó my-state-name là trạng thái được định nghĩa trong phần tử.
Pseudo-class trạng thái tùy chỉnh chỉ khớp với phần tử tùy chỉnh nếu trạng thái là true (tức là nếu my-state-name có trong CustomStateSet).
Ví dụ, CSS sau khớp với phần tử tùy chỉnh labeled-checkbox khi CustomStateSet của phần tử chứa trạng thái checked, và áp dụng border solid cho checkbox:
labeled-checkbox:state(checked) {
border: solid;
}
CSS cũng có thể được dùng để khớp trạng thái tùy chỉnh trong shadow DOM của phần tử tùy chỉnh bằng cách chỉ định :state() trong hàm pseudo-class :host().
Ngoài ra, pseudo-class :state() có thể được dùng sau pseudo-element ::part() để khớp với shadow part của phần tử tùy chỉnh đang ở một trạng thái cụ thể.
Warning:
Các trình duyệt chưa hỗ trợ :state() sẽ sử dụng <dashed-ident> CSS để chọn các trạng thái tùy chỉnh, và cú pháp này hiện đã lỗi thời.
Để biết cách hỗ trợ cả hai cách tiếp cận, hãy xem phần Tương thích với cú pháp <dashed-ident> bên dưới.
Ví dụ
>Khớp trạng thái tùy chỉnh của phần tử checkbox tùy chỉnh
Ví dụ này, được điều chỉnh từ thông số kỹ thuật, minh họa một phần tử checkbox tùy chỉnh có trạng thái nội bộ "checked".
Trạng thái này được ánh xạ đến trạng thái tùy chỉnh checked, cho phép áp dụng kiểu dáng bằng pseudo-class trạng thái tùy chỉnh :state(checked).
JavaScript
Đầu tiên chúng ta định nghĩa lớp LabeledCheckbox kế thừa từ HTMLElement.
Trong hàm khởi tạo, chúng ta gọi phương thức super(), thêm listener cho sự kiện click, và gọi this.attachInternals() để gắn một đối tượng ElementInternals.
Phần lớn công việc còn lại được để lại cho connectedCallback(), được gọi khi phần tử tùy chỉnh được thêm vào trang.
Nội dung của phần tử được định nghĩa bằng phần tử <style> hiển thị văn bản [] hoặc [x] theo sau bởi nhãn.
Điểm đáng chú ý ở đây là pseudo-class trạng thái tùy chỉnh được sử dụng để chọn văn bản cần hiển thị: :host(:state(checked)).
class LabeledCheckbox extends HTMLElement {
constructor() {
super();
this._boundOnClick = this._onClick.bind(this);
this.addEventListener("click", this._boundOnClick);
// Attach an ElementInternals to get states property
this._internals = this.attachInternals();
}
connectedCallback() {
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `<style>
:host {
display: block;
}
:host::before {
content: "[ ]";
white-space: pre;
font-family: monospace;
}
:host(:state(checked))::before {
content: "[x]";
}
</style>
<slot>Label</slot>
`;
}
get checked() {
return this._internals.states.has("checked");
}
set checked(flag) {
if (flag) {
this._internals.states.add("checked");
} else {
this._internals.states.delete("checked");
}
}
_onClick(event) {
// Toggle the 'checked' property when the element is clicked
this.checked = !this.checked;
}
static isStateSyntaxSupported() {
return CSS.supports("selector(:state(checked))");
}
}
customElements.define("labeled-checkbox", LabeledCheckbox);
// Display a warning to unsupported browsers
if (!LabeledCheckbox.isStateSyntaxSupported()) {
if (!document.getElementById("state-warning")) {
const warning = document.createElement("div");
warning.id = "state-warning";
warning.style.color = "red";
warning.textContent = "This feature is not supported by your browser.";
document.body.insertBefore(warning, document.body.firstChild);
}
}
Trong lớp LabeledCheckbox:
- Trong
get checked()vàset checked()chúng ta sử dụngElementInternals.statesđể lấyCustomStateSet. - Phương thức
set checked(flag)thêm định danh"checked"vàoCustomStateSetnếu flag được đặt và xóa định danh nếu flag làfalse. - Phương thức
get checked()chỉ kiểm tra xem thuộc tínhcheckedcó được định nghĩa trong tập hợp hay không. - Giá trị thuộc tính được bật/tắt khi phần tử được click.
Sau đó chúng ta gọi phương thức define() trên đối tượng trả về bởi Window.customElements để đăng ký phần tử tùy chỉnh:
customElements.define("labeled-checkbox", LabeledCheckbox);
HTML
Sau khi đăng ký phần tử tùy chỉnh, chúng ta có thể sử dụng phần tử trong HTML như sau:
<labeled-checkbox>You need to check this</labeled-checkbox>
CSS
Cuối cùng chúng ta sử dụng pseudo-class trạng thái tùy chỉnh :state(checked) để chọn CSS khi checkbox được tích.
labeled-checkbox {
border: dashed red;
}
labeled-checkbox:state(checked) {
border: solid;
}
Kết quả
Click vào phần tử để thấy border khác nhau được áp dụng khi trạng thái checked của checkbox được bật/tắt.
Khớp trạng thái tùy chỉnh trong shadow part của phần tử tùy chỉnh
Ví dụ này, được điều chỉnh từ thông số kỹ thuật, minh họa rằng các trạng thái tùy chỉnh có thể được dùng để nhắm vào shadow part của phần tử tùy chỉnh để tạo kiểu. Shadow part là các phần của shadow tree được hiển thị có chủ đích cho các trang sử dụng phần tử tùy chỉnh.
Ví dụ tạo ra phần tử tùy chỉnh <question-box> hiển thị một câu hỏi kèm theo checkbox có nhãn "Yes".
Phần tử sử dụng <labeled-checkbox> được định nghĩa trong ví dụ trước cho checkbox.
JavaScript
Đầu tiên, chúng ta định nghĩa lớp phần tử tùy chỉnh QuestionBox, kế thừa từ HTMLElement.
Như thường lệ, hàm khởi tạo trước tiên gọi phương thức super().
Tiếp theo, chúng ta gắn một shadow DOM tree vào phần tử tùy chỉnh bằng cách gọi attachShadow().
class QuestionBox extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `<div><slot>Question</slot></div>
<labeled-checkbox part="checkbox">Yes</labeled-checkbox>
`;
}
}
Nội dung của shadow root được đặt bằng innerHTML.
Điều này định nghĩa một phần tử <slot> chứa văn bản nhắc mặc định "Question" cho phần tử.
Sau đó chúng ta định nghĩa một phần tử tùy chỉnh <labeled-checkbox> với văn bản mặc định "Yes".
Checkbox này được hiển thị như một shadow part của question box với tên checkbox bằng thuộc tính part.
Lưu ý rằng code và kiểu dáng cho phần tử <labeled-checkbox> hoàn toàn giống như trong ví dụ trước, và do đó không được lặp lại ở đây.
Tiếp theo, chúng ta gọi phương thức define() trên đối tượng trả về bởi Window.customElements để đăng ký phần tử tùy chỉnh với tên question-box:
customElements.define("question-box", QuestionBox);
HTML
Sau khi đăng ký phần tử tùy chỉnh, chúng ta có thể sử dụng phần tử trong HTML như sau.
<!-- Question box with default prompt "Question" -->
<question-box></question-box>
<!-- Question box with custom prompt "Continue?" -->
<question-box>Continue?</question-box>
CSS
Khối CSS đầu tiên khớp với shadow part được hiển thị có tên checkbox bằng bộ chọn ::part(), tạo kiểu màu red theo mặc định.
question-box::part(checkbox) {
color: red;
}
Khối thứ hai theo ::part() với :state(), để khớp với các checkbox part đang ở trạng thái checked:
question-box::part(checkbox):state(checked) {
color: green;
outline: dashed 1px green;
}
Kết quả
Click vào bất kỳ checkbox nào để thấy màu thay đổi từ red sang green với đường viền khi trạng thái checked bật.
Trạng thái nội bộ không phải boolean
Ví dụ này cho thấy cách xử lý trường hợp phần tử tùy chỉnh có thuộc tính nội bộ với nhiều giá trị có thể.
Phần tử tùy chỉnh trong trường hợp này có thuộc tính state với các giá trị cho phép: "loading", "interactive" và "complete".
Để thực hiện điều này, chúng ta ánh xạ mỗi giá trị đến trạng thái tùy chỉnh của nó và tạo code để đảm bảo chỉ có định danh tương ứng với trạng thái nội bộ được đặt.
Bạn có thể thấy điều này trong triển khai của phương thức set state(): chúng ta đặt trạng thái nội bộ, thêm định danh cho trạng thái tùy chỉnh khớp vào CustomStateSet, và xóa các định danh liên quan đến tất cả các giá trị khác.
JavaScript
class ManyStateElement extends HTMLElement {
constructor() {
super();
this._boundOnClick = this._onClick.bind(this);
this.addEventListener("click", this._boundOnClick);
// Attach an ElementInternals to get states property
this._internals = this.attachInternals();
}
connectedCallback() {
this.state = "loading";
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `<style>
:host {
display: block;
font-family: monospace;
}
:host::before {
content: "[ unknown ]";
white-space: pre;
}
:host(:state(loading))::before {
content: "[ loading ]";
}
:host(:state(interactive))::before {
content: "[ interactive ]";
}
:host(:state(complete))::before {
content: "[ complete ]";
}
</style>
<slot>Click me</slot>
`;
}
get state() {
return this._state;
}
set state(stateName) {
// Set internal state to passed value
// Add identifier matching state and delete others
if (stateName === "loading") {
this._state = "loading";
this._internals.states.add("loading");
this._internals.states.delete("interactive");
this._internals.states.delete("complete");
} else if (stateName === "interactive") {
this._state = "interactive";
this._internals.states.delete("loading");
this._internals.states.add("interactive");
this._internals.states.delete("complete");
} else if (stateName === "complete") {
this._state = "complete";
this._internals.states.delete("loading");
this._internals.states.delete("interactive");
this._internals.states.add("complete");
}
}
_onClick(event) {
// Cycle the state when element clicked
if (this.state === "loading") {
this.state = "interactive";
} else if (this.state === "interactive") {
this.state = "complete";
} else if (this.state === "complete") {
this.state = "loading";
}
}
static isStateSyntaxSupported() {
return CSS.supports("selector(:state(loading))");
}
}
customElements.define("many-state-element", ManyStateElement);
if (!LabeledCheckbox.isStateSyntaxSupported()) {
if (!document.getElementById("state-warning")) {
const warning = document.createElement("div");
warning.id = "state-warning";
warning.style.color = "red";
warning.textContent = "This feature is not supported by your browser.";
document.body.insertBefore(warning, document.body.firstChild);
}
}
HTML
Sau khi đăng ký phần tử mới, chúng ta thêm nó vào HTML.
Điều này tương tự như ví dụ minh họa trạng thái boolean đơn, ngoại trừ chúng ta không chỉ định giá trị và sử dụng giá trị mặc định từ slot (<slot>Click me</slot>).
<many-state-element></many-state-element>
CSS
Trong CSS chúng ta sử dụng ba pseudo-class trạng thái tùy chỉnh để chọn CSS cho mỗi giá trị trạng thái nội bộ: :state(loading), :state(interactive), :state(complete).
Lưu ý rằng code phần tử tùy chỉnh đảm bảo chỉ một trong các trạng thái tùy chỉnh này có thể được định nghĩa tại một thời điểm.
many-state-element:state(loading) {
border: dotted grey;
}
many-state-element:state(interactive) {
border: dashed blue;
}
many-state-element:state(complete) {
border: solid green;
}
Kết quả
Click vào phần tử để thấy border khác nhau được áp dụng khi trạng thái thay đổi.
Tương thích với cú pháp <dashed-ident>
Trước đây, các phần tử tùy chỉnh có trạng thái tùy chỉnh được chọn bằng <dashed-ident> thay vì hàm :state().
Các phiên bản trình duyệt không hỗ trợ :state() sẽ ném ra lỗi khi nhận được ident không có tiền tố gạch đôi.
Nếu cần hỗ trợ các trình duyệt này, hãy sử dụng khối try...catch để hỗ trợ cả hai cú pháp, hoặc sử dụng <dashed-ident> làm giá trị trạng thái và chọn nó bằng cả bộ chọn CSS :--my-state và :state(--my-state).
Sử dụng khối try...catch
Code này cho thấy cách dùng try...catch để cố gắng thêm định danh trạng thái không sử dụng <dashed-ident>, và fallback sang <dashed-ident> nếu có lỗi xảy ra.
JavaScript
class CompatibleStateElement extends HTMLElement {
constructor() {
super();
this._internals = this.attachInternals();
}
connectedCallback() {
// The double dash is required in browsers with the
// legacy syntax, not supplying it will throw
try {
this._internals.states.add("loaded");
} catch {
this._internals.states.add("--loaded");
}
}
}
CSS
compatible-state-element:is(:--loaded, :state(loaded)) {
border: solid green;
}
Sử dụng định danh có tiền tố gạch đôi
Một giải pháp thay thế là sử dụng <dashed-ident> trong JavaScript.
Nhược điểm của cách này là các dấu gạch phải được đưa vào khi sử dụng cú pháp CSS :state().
JavaScript
class CompatibleStateElement extends HTMLElement {
constructor() {
super();
this._internals = this.attachInternals();
}
connectedCallback() {
// The double dash is required in browsers with the
// legacy syntax, but works with the modern syntax
this._internals.states.add("--loaded");
}
}
CSS
compatible-state-element:is(:--loaded, :state(--loaded)) {
border: solid green;
}
Thông số kỹ thuật
| Specification |
|---|
| HTML> # customstateset> |