Máy Tính CSS Specificity
Tính CSS specificity cho mọi selector. Phân tách màu, so sánh nhiều selector, xử lý :is, :not, :has, :where theo chuẩn W3C.
| Thành Phần | Ví Dụ | Trọng Số |
|---|---|---|
| a ID | #header, #main | (1, 0, 0) |
| b Class / Thuộc tính / Pseudo-class | .btn, [type="text"], :hover, :nth-child(2) | (0, 1, 0) |
| c Type / Pseudo-element | div, p, ::before, ::placeholder | (0, 0, 1) |
| — | *, :where(...), +, >, ~ | (0, 0, 0) |
| — | :is(A, B), :not(A), :has(A) | max của đối số |
Máy Tính CSS Specificity — Phân Tích Selector, Tính (a, b, c)
Dán bất kỳ selector CSS nào — đơn giản hay phức hợp, có combinator, pseudo-class, pseudo-element, hoặc :is/:not/:where/:has hiện đại — và máy tính trả về bộ ba specificity W3C. Phần phân tách cho thấy từng token đóng góp vào slot nào, và chế độ so sánh xếp hạng nhiều selector để bạn nhìn thấy ngay rule nào thắng cascade.
CSS specificity là gì trong một đoạn?
Khi hai rule CSS cùng đặt một thuộc tính trên cùng phần tử, trình duyệt cần luật phân định. Luật đầu tiên là *origin và importance* (user-agent < author < user, rồi !important đảo lại). Sau đó là *specificity*: mỗi selector có một trọng số biểu diễn bằng ba số — (a, b, c) — và tuple lớn hơn thắng, so sánh từ trái sang phải.
Ba slot đếm:
- **a** — số ID selector (`#foo`)
- **b** — số class selector (`.bar`), attribute selector (`[type=text]`), và pseudo-class (`:hover`)
- **c** — số type selector (`div`) và pseudo-element (`::before`)
Một selector như `#nav .item a:hover` có một ID, hai mục class/pseudo-class, và một type — specificity (1, 2, 1). Nó thắng `.menu .item a:focus` (0, 3, 1) vì slot đầu thắng bất chấp các slot sau cao thế nào.
`:is()`, `:not()`, và `:has()` ảnh hưởng specificity thế nào?
Cả ba đều đóng góp specificity *lớn nhất* của các đối số, không phải 0, không phải tổng.
Ví dụ:
```
:is(.btn, #primary) { color: red; }
```
Specificity được quyết định bởi `#primary` (1, 0, 0), không phải `.btn` (0, 1, 0). Toàn bộ token `:is(...)` đóng góp (1, 0, 0).
Cùng quy tắc áp dụng cho `:not(...)` và `:has(...)` — chúng lấy selector nặng nhất trong dấu ngoặc. Đó là lý do `:not(#admin)` là selector nặng dù nhìn vô hại; nó là specificity (1, 0, 0) tự thân.
Hành vi này định nghĩa trong CSS Selectors Level 4. `:not()` cũ hơn (Level 3, dạng đơn) theo cùng quy tắc, nên hầu hết trình duyệt luôn đồng thuận.
Còn `:where(...)` thì sao?
`:where(...)` là cánh cửa thoát: nó đóng góp **specificity bằng không** bất kể bên trong là gì.
```
:where(#header, .nav, h1) { color: blue; }
```
Specificity = (0, 0, 0). Điều này có chủ ý. `:where()` cho phép bạn viết một style cơ sở mà các selector gộp chia sẻ mà không khóa các override trong tương lai — bất cứ thứ gì target cùng phần tử với dù chỉ một class cũng sẽ thắng.
Mẫu phổ biến là bọc các rule reset hoặc mặc định trong `:where(...)` để chúng thực tế không có trọng số specificity, trong khi style component do tác giả vẫn tự do ghi đè bằng selector thông thường. Được thêm trong CSS Selectors Level 4 (2018) và ship trong mọi trình duyệt evergreen vào 2021.
Tại sao đôi khi thấy `(a, b, c, d)` với bốn slot?
Bài viết cũ thêm slot thứ tư ở đầu cho **inline style** (thuộc tính `style="..."`), cho (1, 0, 0, 0) cho bất kỳ khai báo inline nào. W3C đã bỏ điều này khỏi tuple specificity chính thức vì inline style là một lớp origin riêng biệt — nó thắng mọi quyết định cascade ở mức author trước khi specificity được so sánh.
Thực tế:
1. Khai báo `!important` thắng (lớp origin).
2. Inline style thắng mọi selector không có `!important`.
3. Selector sau đó xếp theo (a, b, c) — ID, class/attr/pseudo-class, type/pseudo-element.
4. Nếu specificity hòa, rule khai báo cuối cùng trong source order thắng.
Máy tính này theo quy ước ba slot hiện đại. Nếu so với tutorial cũ, hãy thêm số 0 đầu — `(0, 1, 2, 1)` và `(1, 2, 1)` mô tả cùng một selector.
Universal selector `*` hay combinator có thêm specificity không?
Không. Không có cái nào sau đây đóng góp specificity:
- `*` (universal selector)
- ` ` (descendant combinator, viết bằng khoảng trắng)
- `>` (child combinator)
- `+` (adjacent sibling combinator)
- `~` (general sibling combinator)
- `||` (column combinator, cho bảng — Level 4)
Một selector như `* > * + *` có specificity (0, 0, 0) — ba universal và hai combinator, đều không trọng số. Combinator mô tả *quan hệ*, không phải cái được match, nên không thêm trọng số.
Đó là lý do `body > main p` (0, 0, 3) hòa với `h1 ~ h2 ~ p` (0, 0, 3) — cùng ba type selector, khác combinator, cùng specificity. Cascade rồi quyết theo source order.
Khác nhau giữa pseudo-class và pseudo-element là gì?
**Pseudo-class** mô tả một trạng thái của phần tử:
- `:hover`, `:focus`, `:active`, `:checked`, `:disabled`
- `:nth-child(2)`, `:nth-of-type(odd)`, `:first-child`
- `:lang(en)`, `:dir(rtl)`, `:state(custom)`
Chúng đóng góp vào slot **b** — cùng trọng số class selector.
**Pseudo-element** mô tả một phần con ảo của phần tử:
- `::before`, `::after`, `::first-line`, `::first-letter`
- `::placeholder`, `::marker`, `::backdrop`, `::selection`
Chúng đóng góp vào slot **c** — cùng trọng số type selector.
Khác biệt cú pháp là một dấu hai chấm (`:hover`) so với hai dấu hai chấm (`::before`). CSS2 dùng một dấu hai chấm cho bốn pseudo-element cổ nhất (`:before`, `:after`, `:first-line`, `:first-letter`) — máy tính này chấp nhận cả hai dạng và phân loại bốn cái đó là pseudo-element trong cả hai trường hợp.
Làm sao giảm specificity khi đang đấu với stylesheet khác?
Vài kỹ thuật theo mức độ tăng dần:
1. **Dùng `:where()`** — bọc phần nặng của selector trong `:where()` để chúng không đóng góp gì. `:where(.legacy) .button` → (0, 1, 0) thay vì (0, 2, 0). Tốt nhất cho stylesheet reset.
2. **Bỏ ID khỏi selector** — refactor `#nav .item` thành `.nav .item` nếu markup cho phép. ID là thành phần nặng nhất.
3. **Dùng một class duy nhất** — selector phẳng như `.btn-primary` thắng vì hòa ở (0, 1, 0) với hầu hết style của tác giả, để source order quyết.
4. **Nhân đôi class** — `.btn.btn` (0, 2, 0) là một hack quen thuộc để tăng specificity *mà không* cần ID.
5. **Dùng cascade layer** — `@layer base, components, utilities;` cho phép kiểm soát file nào thắng bất kể specificity. Layer khai báo sau thắng, không tranh cãi.
6. **Dùng `!important`** — biện pháp cuối. Nó hoạt động nhưng tạo chuỗi override mong manh.
Quy tắc hiện đại là: tối thiểu hóa specificity trong CSS component (class đơn), bọc rule legacy/reset trong `:where()`, và dùng cascade layer cho tách tooling/utility.
Máy tính này có riêng tư và offline không?
Có. Mọi thứ được tính cục bộ trong trình duyệt bởi vài trăm dòng JavaScript:
- Parser selector viết tay, không gọi CSS engine ngoài.
- Không telemetry, không analytics cho phép tính này.
- Nút Ví Dụ điền textarea bằng chuỗi nội bộ; không tải gì.
- Trang chia sẻ asset thông thường của site (Bootstrap CSS, icon) nhưng không API bên thứ ba nào được liên lạc cho phép toán specificity thực tế.
Bạn có thể kiểm tra bằng DevTools → Network và quan sát khi gõ hoặc bấm Tính — không request nào nổ. Điều này cũng có nghĩa máy tính chạy offline sau khi trang đã tải, tiện khi audit stylesheet trên máy bay hoặc môi trường tín hiệu yếu.
Tính Năng Chính
- Tuple (a, b, c) đúng chuẩn W3C cho mọi selector CSS
- Phân tách màu làm nổi bật token nào đóng góp vào slot nào
- Chế độ so sánh nhiều selector, sắp xếp theo specificity giảm dần
- Xử lý đúng :is(), :not(), :has() — specificity max của đối số
- :where(...) được nhận diện là specificity bằng không
- Pseudo-element (::before, ::after, ::first-line) phân loại tách khỏi pseudo-class
- Chấp nhận pseudo-element CSS2 một dấu hai chấm cổ (:before, :after)
- Universal selector * và combinator (>, +, ~, khoảng trắng) tính đúng là không
- Attribute selector, bao gồm [attr=value] và [attr^=value]
- Functional pseudo-class :nth-child(), :nth-of-type(), :lang(), :dir()
- Preset Ví Dụ tải 10 selector đại diện để so sánh tức thì
- Phát hiện hòa — làm nổi bật selector có specificity bằng nhau
- JavaScript thuần, không CSS engine ngoài
- Hoạt động offline sau lần tải đầu
- 100% phía client — selector của bạn ở trong trình duyệt
