บีอีเอ็ม เช่นเดียวกับเทคนิคทั้งหมดในโลกของการพัฒนาส่วนหน้า การเขียน 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 นั้นเป็นสิ่งที่ดี!