Điều chỉnh tầng bằng BEM và Bộ chọn CSS hiện đại PlatoBlockchain Data Intelligence. Tìm kiếm dọc. Ái.

Điều chỉnh Cascade với BEM và Bộ chọn CSS hiện đại

BEM. Giống như tất cả các kỹ thuật trong thế giới phát triển front-end, viết CSS ở định dạng BEM có thể phân cực. Nhưng nó - ít nhất là trong bong bóng Twitter của tôi - một trong những phương pháp CSS được yêu thích hơn.

Cá nhân tôi thấy BEM tốt và tôi nghĩ bạn nên sử dụng nó. Nhưng tôi cũng hiểu tại sao bạn có thể không.

Bất kể ý kiến ​​của bạn về BEM là gì, nó mang lại một số lợi ích, lợi ích lớn nhất là nó giúp tránh xung đột về tính đặc hiệu trong CSS Cascade. Đó là bởi vì, nếu được sử dụng đúng cách, bất kỳ bộ chọn nào được viết ở định dạng BEM sẽ có cùng điểm cụ thể (0,1,0). Tôi đã thiết kế CSS cho nhiều trang web quy mô lớn trong nhiều năm (nghĩ rằng chính phủ, trường đại học và ngân hàng) và chính trên những dự án lớn hơn này, tôi đã thấy rằng BEM thực sự tỏa sáng. Viết CSS thú vị hơn nhiều khi bạn tự tin rằng phong cách bạn đang viết hoặc chỉnh sửa không ảnh hưởng đến một số phần khác của trang web.

Thực tế có những trường hợp ngoại lệ khi việc thêm tính đặc hiệu được coi là hoàn toàn chấp nhận được. Ví dụ: các :hover:focus các lớp giả. Những người có một số điểm cụ thể của 0,2,0. Một cái khác là các phần tử giả - như ::before::after - có điểm đặc hiệu là 0,1,1. Tuy nhiên, đối với phần còn lại của bài viết này, hãy giả sử rằng chúng ta không muốn bất kỳ sự leo thang cụ thể nào khác. 🤓

Nhưng tôi không thực sự ở đây để bán cho bạn trên BEM. Thay vào đó, tôi muốn nói về cách chúng ta có thể sử dụng nó cùng với các bộ chọn CSS hiện đại — hãy nghĩ xem :is(), :has(), :where(), v.v. - để đạt được thậm chí chi tiết kiểm soát dòng thác.

Đây là gì về bộ chọn CSS hiện đại?

Sản phẩm Thông số bộ chọn CSS cấp 4 cung cấp cho chúng tôi một số cách (ish) mới mạnh mẽ để chọn các phần tử. Một số mục yêu thích của tôi bao gồm :is(), :where():not(), mỗi trình duyệt đều được hỗ trợ bởi tất cả các trình duyệt hiện đại và an toàn để sử dụng trên hầu hết mọi dự án hiện nay.

:is():where() về cơ bản là giống nhau ngoại trừ cách chúng tác động đến tính đặc hiệu. Đặc biệt, :where() luôn có điểm đặc hiệu là 0,0,0. Vâng, thậm chí :where(button#widget.some-class) không có tính đặc hiệu. Trong khi đó, đặc thù của :is() là phần tử trong danh sách đối số của nó có tính đặc hiệu cao nhất. Vì vậy, chúng ta đã có sự khác biệt giữa Cascade-wrangling giữa hai bộ chọn hiện đại mà chúng ta có thể làm việc cùng.

Vô cùng mạnh mẽ :has() lớp giả quan hệ cũng là nhanh chóng nhận được sự hỗ trợ của trình duyệt (và là tính năng mới lớn nhất của CSS kể từ lưới, theo ý kiến ​​​​khiêm tốn của tôi). Tuy nhiên, tại thời điểm viết bài này, trình duyệt hỗ trợ cho :has() vẫn chưa đủ tốt để sử dụng trong sản xuất.

Để tôi dán một trong những lớp giả đó vào BEM của tôi và…

/* ❌ specificity score: 0,2,0 */
.something:not(.something--special) {
  /* styles for all somethings, except for the special somethings */
}

Rất tiếc! Xem điểm cụ thể đó? Hãy nhớ rằng, với BEM, lý tưởng nhất là chúng tôi muốn tất cả các bộ chọn của mình đều có điểm cụ thể là 0,1,0. Tại sao lại là 0,2,0 xấu? Hãy xem xét ví dụ tương tự này, được mở rộng:

.something:not(.something--special) {
  color: red;
}
.something--special {
  color: blue;
}

Mặc dù bộ chọn thứ hai đứng cuối cùng trong thứ tự nguồn, nhưng độ đặc hiệu cao hơn của bộ chọn thứ nhất (0,2,0) chiến thắng, và màu sắc của .something--special các phần tử sẽ được đặt thành red. Nghĩa là, giả sử BEM của bạn được viết đúng cách và phần tử được chọn có cả .something lớp cơ sở và .something--special lớp công cụ sửa đổi được áp dụng cho nó trong HTML.

Được sử dụng một cách bất cẩn, các lớp giả này có thể tác động đến Cascade theo những cách không mong muốn. Và chính những kiểu mâu thuẫn này có thể khiến bạn đau đầu, đặc biệt là trên các cơ sở mã lớn hơn và phức tạp hơn.

Đặng. Thế bây giờ thì thế nào?

Hãy nhớ những gì tôi đã nói về :where() và thực tế là độ đặc hiệu của nó bằng không? Chúng tôi có thể sử dụng điều đó để lợi thế của chúng tôi:

/* ✅ specificity score: 0,1,0 */
.something:where(:not(.something--special)) {
  /* etc. */
}

Phần đầu tiên của bộ chọn này (.something) nhận được điểm đặc hiệu thông thường của nó là 0,1,0. Nhưng :where() - và mọi thứ bên trong nó - có đặc điểm là 0, điều này không làm tăng thêm tính đặc hiệu của bộ chọn.

:where() cho phép chúng tôi làm tổ

Những người không quan tâm nhiều như tôi về tính cụ thể (và công bằng mà nói, đó có lẽ là rất nhiều người) đã làm khá tốt khi nói đến việc làm tổ. Với một số thao tác gõ bàn phím vô tư, chúng ta có thể kết thúc với CSS như thế này (lưu ý rằng tôi đang sử dụng Sass cho ngắn gọn):

.card { ... }

.card--featured {
  /* etc. */  
  .card__title { ... }
  .card__title { ... }
}

.card__title { ... }
.card__img { ... }

Trong ví dụ này, chúng ta có một .card thành phần. Khi đó là một thẻ "nổi bật" (sử dụng .card--featured class), tiêu đề và hình ảnh của thẻ cần được tạo kiểu khác. Nhưng, như chúng ta tại bạn biết đấy, mã ở trên dẫn đến điểm cụ thể không phù hợp với phần còn lại của hệ thống của chúng tôi.

Thay vào đó, một mọt sách đặc biệt khó tính có thể đã làm điều này:

.card { ... }
.card--featured { ... }
.card__title { ... }
.card__title--featured { ... }
.card__img { ... }
.card__img--featured { ... }

Điều đó không quá tệ, phải không? Thành thật mà nói, đây là CSS đẹp.

Tuy nhiên, có một nhược điểm trong HTML. Các tác giả BEM dày dạn kinh nghiệm có lẽ nhận thức sâu sắc về logic khuôn mẫu phức tạp cần thiết để áp dụng có điều kiện các lớp bổ trợ cho nhiều phần tử. Trong ví dụ này, mẫu HTML cần thêm điều kiện --featured lớp sửa đổi thành ba phần tử (.card, .card__title.card__img) mặc dù có lẽ còn nhiều hơn trong một ví dụ thực tế. đó là rất nhiều if các câu lệnh.

Sản phẩm :where() bộ chọn có thể giúp chúng tôi viết ít logic mẫu hơn — và ít lớp BEM hơn để khởi động — mà không làm tăng thêm mức độ cụ thể.

.card { ... }
.card--featured { ... }

.card__title { ... }
:where(.card--featured) .card__title { ... }

.card__img { ... }
:where(.card--featured) .card__img { ... }

Đây là điều tương tự nhưng trong Sass (lưu ý dấu dấu và):

.card { ... }
.card--featured { ... }
.card__title { 
  /* etc. */ 
  :where(.card--featured) & { ... }
}
.card__img { 
  /* etc. */ 
  :where(.card--featured) & { ... }
}

Việc bạn có nên chọn cách tiếp cận này thay vì áp dụng các lớp bổ trợ cho các phần tử con khác nhau hay không là vấn đề sở thích cá nhân. Nhưng ít nhất :where() cho chúng ta sự lựa chọn ngay bây giờ!

Thế còn HTML không phải BEM thì sao?

Chúng ta không sống trong một thế giới hoàn hảo. Đôi khi bạn cần xử lý HTML nằm ngoài tầm kiểm soát của mình. Chẳng hạn, tập lệnh của bên thứ ba đưa vào HTML mà bạn cần tạo kiểu. Đánh dấu đó thường không được viết bằng tên lớp BEM. Trong một số trường hợp, các kiểu đó hoàn toàn không sử dụng các lớp ngoài ID!

Một lần nữa, :where() có lưng của chúng tôi. Giải pháp này hơi rắc rối, vì chúng ta cần tham chiếu lớp của một phần tử ở đâu đó trên cây DOM mà chúng ta biết là có tồn tại.

/* ❌ specificity score: 1,0,0 */
#widget {
  /* etc. */
}

/* ✅ specificity score: 0,1,0 */
.page-wrapper :where(#widget) {
  /* etc. */
}

Mặc dù vậy, việc tham chiếu một phần tử cha cảm thấy hơi rủi ro và hạn chế. Điều gì sẽ xảy ra nếu lớp cha mẹ đó thay đổi hoặc không có vì lý do nào đó? Một giải pháp tốt hơn (nhưng có lẽ không kém phần nguy hiểm) là sử dụng :is() thay vì. Hãy nhớ rằng, tính đặc hiệu của :is() bằng với bộ chọn cụ thể nhất trong danh sách bộ chọn của nó.

Vì vậy, thay vì tham chiếu đến một lớp mà chúng ta biết (hoặc hy vọng!) tồn tại với :where(), như trong ví dụ trên, chúng ta có thể tham chiếu một lớp đã tạo và tag.

/* ✅ specificity score: 0,1,0 */
:is(.dummy-class, body) :where(#widget) {
  /* etc. */
}

Mãi mãi body sẽ giúp chúng tôi lựa chọn của chúng tôi #widget nguyên tố và sự có mặt của .dummy-class lớp bên trong giống nhau :is() cung cấp cho các body bộ chọn điểm cụ thể giống như một lớp (0,1,0)… và việc sử dụng :where() đảm bảo bộ chọn không nhận được bất kỳ điều gì cụ thể hơn thế.

Đó là nó!

Đó là cách chúng tôi có thể tận dụng các tính năng quản lý tính đặc hiệu hiện đại của :is():where() các lớp giả cùng với tính năng ngăn va chạm cụ thể mà chúng tôi nhận được khi viết CSS ở định dạng BEM. Và trong một tương lai không xa, hàng loạt :has() giành được sự hỗ trợ của Firefox (nó hiện được hỗ trợ đằng sau một lá cờ tại thời điểm viết bài), chúng tôi có thể sẽ muốn ghép nối nó với :where() để hoàn tác tính đặc hiệu của nó.

Cho dù bạn có tập trung vào việc đặt tên BEM hay không, tôi hy vọng chúng ta có thể đồng ý rằng có sự nhất quán trong tính đặc hiệu của bộ chọn là một điều tốt!

Dấu thời gian:

Thêm từ Thủ thuật CSS