ฝึกฝน Cascade ด้วย BEM และตัวเลือก CSS สมัยใหม่ PlatoBlockchain Data Intelligence ค้นหาแนวตั้ง AI.

ฝึกฝน Cascade ด้วย BEM และ Modern CSS Selectors

บีอีเอ็ม เช่นเดียวกับเทคนิคทั้งหมดในโลกของการพัฒนาส่วนหน้า การเขียน CSS ในรูปแบบ BEM สามารถโพลาไรซ์ได้ แต่อย่างน้อยก็ใน Twitter ของฉัน - หนึ่งในวิธีการ CSS ที่ชอบดีกว่า

ส่วนตัวผมมองว่า BEM นั้นดีและน่าใช้ครับ แต่ฉันก็เข้าใจว่าทำไมคุณถึงไม่ทำ

ไม่ว่าคุณจะมีความคิดเห็นอย่างไรเกี่ยวกับ BEM แต่ก็ให้ประโยชน์หลายประการ ที่สำคัญที่สุดคือช่วยหลีกเลี่ยงการปะทะกันของลักษณะเฉพาะใน CSS Cascade นั่นเป็นเพราะว่า หากใช้อย่างถูกต้อง ตัวเลือกใดๆ ที่เขียนในรูปแบบ BEM ควรมีคะแนนความจำเพาะเท่ากัน (0,1,0). ฉันได้ออกแบบ CSS สำหรับเว็บไซต์ขนาดใหญ่จำนวนมากในช่วงหลายปีที่ผ่านมา (เช่น รัฐบาล มหาวิทยาลัย และธนาคาร) และในโครงการขนาดใหญ่เหล่านี้ที่ฉันพบว่า BEM โดดเด่นจริงๆ การเขียน CSS จะสนุกมากขึ้นเมื่อคุณมั่นใจว่ารูปแบบที่คุณเขียนหรือแก้ไขนั้นไม่ส่งผลกระทบต่อส่วนอื่นๆ ของไซต์

มีข้อยกเว้นที่ยอมรับได้โดยสิ้นเชิงในการเพิ่มความเฉพาะเจาะจง ตัวอย่างเช่น: the :hover และ :focus ชั้นเรียนหลอก เหล่านั้นมีคะแนนความจำเพาะของ 0,2,0. อีกประการหนึ่งคือองค์ประกอบหลอก - เช่น ::before และ ::after — ซึ่งมีคะแนนความจำเพาะของ 0,1,1. สำหรับส่วนที่เหลือของบทความนี้ สมมติว่าเราไม่ต้องการให้ครีปมีความเฉพาะเจาะจงอื่นใด 🤓

แต่ฉันไม่ได้มาที่นี่เพื่อขาย BEM ให้คุณ ฉันต้องการพูดคุยเกี่ยวกับวิธีที่เราสามารถใช้ควบคู่ไปกับตัวเลือก CSS สมัยใหม่ได้ ลองคิดดู :is(), :has(), :where()และอื่น ๆ — เพื่อให้ได้มาซึ่งความเสมอภาค ข้อมูลเพิ่มเติม การควบคุมของ น้ำตก.

สิ่งนี้เกี่ยวกับตัวเลือก CSS สมัยใหม่อย่างไร

พื้นที่ ข้อมูลจำเพาะ CSS Selectors ระดับ 4 ทำให้เรามีวิธีการใหม่ (ish) ที่มีประสิทธิภาพในการเลือกองค์ประกอบ บางส่วนของรายการโปรดของฉันรวมถึง :is(), :where()และ :not()ซึ่งแต่ละเบราว์เซอร์รองรับโดยเบราว์เซอร์สมัยใหม่ทั้งหมด และปลอดภัยที่จะใช้ในเกือบทุกโครงการในปัจจุบัน

:is() และ :where() โดยพื้นฐานแล้วเป็นสิ่งเดียวกันยกเว้นผลกระทบต่อความเฉพาะเจาะจง โดยเฉพาะ :where() มีคะแนนความจำเพาะเสมอ 0,0,0. ใช่แม้กระทั่ง :where(button#widget.some-class) ไม่มีความเฉพาะเจาะจง ในขณะเดียวกันความเฉพาะเจาะจงของ :is() เป็นองค์ประกอบในรายการอาร์กิวเมนต์ที่มีความเฉพาะเจาะจงสูงสุด ดังนั้นเราจึงมีความแตกต่างของการโต้เถียงแบบคาสเคดระหว่างตัวเลือกสมัยใหม่สองตัวที่เราสามารถทำงานได้

ที่ทรงพลังอย่างเหลือเชื่อ :has() คลาสหลอกเชิงสัมพันธ์ก็เช่นกัน ได้รับการสนับสนุนเบราว์เซอร์อย่างรวดเร็ว (และเป็นคุณสมบัติใหม่ที่ใหญ่ที่สุดของ CSS ตั้งแต่นั้นมา ตะแกรงในความเห็นต่ำต้อยของฉัน). อย่างไรก็ตาม ในขณะที่เขียน เบราว์เซอร์รองรับ :has() ยังไม่ดีพอสำหรับใช้ในการผลิต

ฝากติดหนึ่งในคลาสหลอกเหล่านั้นใน BEM ของฉันและ...

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

อ๊ะ! ดูคะแนนความเฉพาะเจาะจงนั้นไหม โปรดจำไว้ว่า ด้วย BEM เราต้องการให้ตัวเลือกของเราทุกคนมีคะแนนความเฉพาะเจาะจงเท่ากับ 0,1,0. ทำไม 0,2,0 แย่? พิจารณาตัวอย่างเดียวกันนี้ ขยาย:

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

แม้ว่าตัวเลือกที่สองจะอยู่ลำดับสุดท้ายในลำดับต้นทาง ความจำเพาะสูงกว่าของตัวเลือกแรก (0,2,0) ชนะและสีของ .something--special องค์ประกอบจะถูกตั้งค่าเป็น red. นั่นคือสมมติว่า BEM ของคุณเขียนอย่างถูกต้องและองค์ประกอบที่เลือกมีทั้ง .something คลาสพื้นฐานและ .something--special คลาสตัวดัดแปลงใช้กับมันใน HTML

ใช้อย่างไม่ระมัดระวัง คลาสปลอมเหล่านี้สามารถส่งผลกระทบต่อ Cascade ในรูปแบบที่คาดไม่ถึง และความไม่สอดคล้องกันเหล่านี้สามารถสร้างความปวดหัวตามมาได้ โดยเฉพาะกับโค้ดเบสที่ใหญ่และซับซ้อนมากขึ้น

แดง แล้วตอนนี้ล่ะ?

จำสิ่งที่ฉันพูดเกี่ยวกับ :where() และความจริงที่ว่าความจำเพาะของมันคือศูนย์? เราสามารถใช้มันเพื่อประโยชน์ของเรา:

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

ส่วนแรกของตัวเลือกนี้ (.something) ได้รับคะแนนความจำเพาะตามปกติของ 0,1,0. แต่ :where() — และทุกสิ่งในนั้น — มีความเฉพาะเจาะจงของ 0ซึ่งจะไม่เพิ่มความเฉพาะเจาะจงของตัวเลือกอีกต่อไป

:where() ทำให้เราสามารถทำรังได้

ผู้คนที่ไม่สนใจเรื่องความเฉพาะเจาะจงเท่าฉัน (และนั่นอาจเป็นคนจำนวนมาก ถ้าพูดตามตรง) ค่อนข้างดีเมื่อพูดถึงการทำรัง ด้วยการกดแป้นพิมพ์อย่างไร้กังวล เราอาจจบลงด้วย CSS แบบนี้ (โปรดทราบว่าฉันใช้ Sass เพื่อความกระชับ):

.card { ... }

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

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

ในตัวอย่างนี้ เรามี .card ส่วนประกอบ. เมื่อเป็นการ์ด "เด่น" (โดยใช้ไฟล์ .card--featured class) ชื่อและรูปภาพของการ์ดจะต้องมีสไตล์แตกต่างกัน แต่เป็นเรา ตอนนี้ โปรดทราบว่าโค้ดข้างต้นส่งผลให้คะแนนความเฉพาะเจาะจงไม่สอดคล้องกับส่วนที่เหลือของระบบของเรา

เนิร์ดที่เจาะจงเป็นพิเศษอาจทำสิ่งนี้แทน:

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

ก็ไม่เลวใช่มั้ย? ตรงไปตรงมา นี่เป็น CSS ที่สวยงาม

มีข้อเสียใน HTML แม้ว่า ผู้เขียน BEM ที่มีประสบการณ์อาจรับรู้อย่างเจ็บปวดเกี่ยวกับตรรกะเทมเพลตที่เทอะทะซึ่งจำเป็นสำหรับการใช้คลาสตัวดัดแปลงแบบมีเงื่อนไขกับหลายองค์ประกอบ ในตัวอย่างนี้ เทมเพลต HTML จำเป็นต้องเพิ่มเงื่อนไข --featured คลาสตัวแก้ไขเป็นสามองค์ประกอบ (.card, .card__titleและ .card__img) แม้ว่าอาจจะมากกว่านั้นในตัวอย่างในโลกแห่งความเป็นจริง นั่นเป็นจำนวนมาก if งบ

พื้นที่ :where() ตัวเลือกสามารถช่วยเราเขียนตรรกะเทมเพลตน้อยลงมาก — และคลาส BEM น้อยลงในการบูต — โดยไม่ต้องเพิ่มระดับความเฉพาะเจาะจง

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

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

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

นี่คือสิ่งเดียวกัน แต่ใน Sass (สังเกตการต่อท้าย เครื่องหมายแอมเปอร์แซนด์):

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

คุณควรเลือกใช้วิธีนี้แทนการใช้คลาสตัวดัดแปลงกับองค์ประกอบย่อยต่างๆ หรือไม่นั้นเป็นเรื่องของความชอบส่วนตัว แต่อย่างน้อย :where() ให้เราเลือกได้เลย!

แล้ว HTML ที่ไม่ใช่ BEM ล่ะ

เราไม่ได้อยู่ในโลกที่สมบูรณ์แบบ บางครั้งคุณต้องจัดการกับ HTML ที่อยู่นอกเหนือการควบคุมของคุณ ตัวอย่างเช่น สคริปต์ของบุคคลที่สามที่แทรก HTML ที่คุณต้องการจัดรูปแบบ มาร์กอัปนั้นมักไม่ได้เขียนด้วยชื่อคลาส BEM ในบางกรณีสไตล์เหล่านั้นไม่ได้ใช้คลาสเลยนอกจาก ID!

อีกครั้งหนึ่ง :where() มีหลังของเรา โซลูชันนี้ค่อนข้างแฮ็กเล็กน้อย เนื่องจากเราจำเป็นต้องอ้างอิงคลาสขององค์ประกอบที่ใดที่หนึ่งซึ่งอยู่ถัดไปจากแผนผัง DOM ที่เรารู้ว่ามีอยู่

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

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

การอ้างอิงองค์ประกอบหลักนั้นค่อนข้างเสี่ยงและจำกัด จะเกิดอะไรขึ้นถ้าคลาสพาเรนต์นั้นเปลี่ยนไปหรือไม่มีด้วยเหตุผลบางอย่าง ควรใช้วิธีแก้ปัญหาที่ดีกว่า (แต่อาจแฮ็คพอ ๆ กัน) :is() แทนที่. โปรดจำไว้ว่าความเฉพาะเจาะจงของ :is() เท่ากับตัวเลือกที่เจาะจงที่สุดในรายการตัวเลือก

ดังนั้น แทนที่จะอ้างอิงถึงคลาสที่เรารู้จัก (หรือหวังว่าจะมี!) อยู่ด้วย :where()ดังตัวอย่างข้างต้น เราสามารถอ้างอิงถึงคลาสที่สร้างขึ้นและ แท็ก

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

ปัจจุบัน body จะช่วยเราเลือกของเรา #widget องค์ประกอบและการมีอยู่ของ .dummy-class ชั้นในเหมือนกัน :is() จะช่วยให้ body เลือกคะแนนความจำเพาะเดียวกันกับชั้นเรียน (0,1,0)…และการใช้ :where() ตรวจสอบให้แน่ใจว่าตัวเลือกไม่ได้เจาะจงมากไปกว่านั้น

แค่นั้นแหละ!

นั่นเป็นวิธีที่เราสามารถใช้ประโยชน์จากคุณลักษณะการจัดการความเฉพาะเจาะจงที่ทันสมัยของ :is() และ :where() pseudo-classes ควบคู่ไปกับการป้องกันการชนกันเฉพาะที่เราได้รับเมื่อเขียน CSS ในรูปแบบ BEM และในอนาคตอันใกล้นี้ ครั้งเดียว :has() ได้รับการสนับสนุน Firefox (ขณะนี้ได้รับการสนับสนุนหลังการตั้งค่าสถานะ ณ เวลาที่เขียน) เราอาจต้องการจับคู่กับ :where() เพื่อเลิกทำความเฉพาะเจาะจง

ไม่ว่าคุณจะทุ่มเทให้กับการตั้งชื่อ BEM หรือไม่ก็ตาม ฉันหวังว่าเราจะเห็นพ้องต้องกันว่าการมีความสม่ำเสมอใน Selector นั้นเป็นสิ่งที่ดี!

ประทับเวลา:

เพิ่มเติมจาก เคล็ดลับ CSS