Fundamentals
Specificity
แยกแยะว่า specificity คือน้ำหนักความเจาะจงของ selector และทำนายว่า rule ไหนชนะเมื่อหลาย rule ตรงกับ element เดียวกัน
1) ปัญหา: เขียน rule ถูกต้องแล้ว style ยังไม่ออก
สมมติว่าคุณเขียน CSS ให้ข้อความหนึ่งเป็นสีเทา แล้วมีอีก rule ที่เขียนทีหลังกำหนดสีน้ำเงินให้มัน แต่บนหน้าจอกลับยังเป็นสีที่คุณไม่ได้เลือกไว้เลย ลองเล่นตัวอย่างด้านล่างก่อน — สาม rule นี้ match กับ <p> ตัวแรกพร้อมกันทั้งสาม rule: **ทายก่อนเปิด preview:** ข้อความแรกซึ่งมีทั้ง id, class และเป็น <p> จะแสดงสีอะไร? หลายคนทายว่า "rule ที่เขียนทีหลังสุด" เพราะเข้าใจว่าการเขียนทับด้วยลำดับพอ — แต่ผลจริงอาจทำให้คุณแปลกใจ
- เพิ่ม rule ใหม่แล้วไม่ทำงาน — เพราะ rule เดิมมีน้ำหนักสูงกว่าที่ selector ใหม่จะเอาชนะได้
- ย้าย rule ไปไว้ท้ายไฟล์แล้วก็ยังไม่ชนะ — ลำดับการเขียนชนะได้เฉพาะ rule ที่น้ำหนักเท่ากัน
- เริ่มแปะ !important เพื่อเอาชนะทีละจุด — จนโค้ดทั้งไฟล์ควบคุมลำดับความสำคัญไม่ได้
2) แนวคิดหลัก: specificity คือน้ำหนักความเจาะจงของ selector
เมื่อหลาย rule match กับ element เดียวกัน เบราว์เซอร์ไม่ได้เลือกแบบสุ่ม และไม่ได้ดูว่าใครเขียน "ทีหลัง" — มันเทียบ**ความเจาะจงของ selector** ว่า rule ไหนระบุ element ได้เฉพาะเจาะจงกว่ากัน **Specificity (ความจำเพาะ)** คือน้ำหนักของ selector แต่ละแบบ: selector ที่เจาะจงกว่า เช่น ระบุ id ที่มีแค่องค์ประกอบเดียวบนหน้า จะชนะ selector ที่กว้างกว่า เช่น ชี้ที่ทุก <p> ในหน้า เพื่อให้เห็นภาพ ลองเทียบกับคำสั่งที่คุณได้รับ: คำสั่งที่ระบุตัวคุณโดยตรง ("พรุ่งนี้คุณสมชายใส่ชุดทางการ") ย่อมชนะประกาศที่แจกให้ทุกคน ("พนักงานแต่งกายสุภาพ") — ต่อให้ประกาศฉบับรวมออกมาทีหลังก็ตาม (เปรียบเทียบได้แค่หลัก "คำสั่งที่เจาะจงถึงตัวตนมากกว่าชนะ" — ไม่ครอบคลุมถึงการนับทบของ class หลายตัว หรือ inline style ที่อยู่เหนือ id ทั้งหมด จึงอย่าขยายอุปมานี้ไปยังรายละเอียดการนับ)
3) ลำดับน้ำหนัก: จาก element ไปจนถึง inline style
selector ทุกแบบจัดอยู่ในชั้นน้ำหนักชั้นใดชั้นหนึ่ง เรียงจากสูงสุดไปต่ำสุดดังนี้ (หมายเหตุ: ในตัวอย่างและแบบฝึกหัดจะมี property เช่น background ซึ่งจะเรียนเต็มในบท Typography & Colors — ที่นี่ใช้เพื่อดูผลลัพธ์ของ selector ที่ชนะ):
| ชั้นน้ำหนัก | ตัวอย่าง | ลำดับ |
|---|---|---|
| Inline style | style="color: red" | สูงสุด |
| ID selector | #header | สูง |
| Class / attribute / pseudo-class | .message / [type] / :hover (เรียนเต็มในบทที่ 8) | กลาง |
| Element / pseudo-element | p / ::before (เรียนเต็มในบทที่ 9) | ต่ำ |
| Universal selector | * | ต่ำสุด |
4) กลไกการตัดสิน: นับน้ำหนัก (a, b, c, d) แล้วเทียบซ้ายไปขวา
เพื่อตัดสินอย่างเป็นระบบ เบราว์เซอร์แปลง selector เป็นตัวเลข 4 หลัก (a, b, c, d) โดยนับจำนวนส่วนประกอบแต่ละชั้น: a = มี inline style หรือไม่ (1 ถ้ามี style="" ใน HTML) b = จำนวน ID selector c = จำนวน class, attribute selector และ pseudo-class d = จำนวน element selector และ pseudo-element แล้วจึงเทียบทีละหลักจากซ้ายไปขวา — หลักแรกที่ต่างกันเป็นตัวตัดสินว่าใครชนะ ไม่ต้องดูหลักที่เหลือ
เทียบจากซ้ายไปขวา: (0,1,0,0) ชนะ (0,0,2,0) เพราะหลัก b=1 มากกว่า b=0 — selector ยาวกว่าไม่ได้แปลว่าชนะเสมอ
- รวบรวม rule ทั้งหมดที่ match กับ element นั้น
- นับน้ำหนัก (a, b, c, d) ของ selector ในแต่ละ rule
- เทียบทีละหลักจากซ้ายไปขวา — หลักแรกที่ต่างกันตัดสินผู้ชนะ
- ถ้าเท่ากันทุกหลัก — rule ที่ประกาศทีหลังในไฟล์ CSS ชนะ (cascade order)
- นำ declaration ของ rule ที่ชนะไปใช้กับ element
5) ข้อยกเว้นที่ข้ามทุกน้ำหนัก: !important
มีคำสั่งเดียวที่ข้ามการเทียบน้ำหนักทั้งหมด: ต่อท้าย declaration ด้วย !important — rule นั้นจะชนะ rule อื่นทุก rule ที่ไม่ใช่ !important ด้วยกัน ไม่ว่า selector จะมีน้ำหนักเท่าไร !important มีไว้สำหรับกรณีที่คุณแตะ selector หรือ HTML ไม่ได้จริง ๆ เช่น ต้องเอาชนะสไตล์จากปลั๊กอินหรือโค้ดของคนอื่นชั่วคราว การใช้เป็นประจำจะทำลายระบบน้ำหนักทั้งไฟล์ — เมื่อ rule อื่นเริ่มใช้ !important บ้าง คุณก็ต้องสู้กันด้วย !important ต่อไปเรื่อย ๆ
6) ชวนทำนาย: 4 กรณีที่ rule ชนกัน
ก่อนเปิด preview ของตัวอย่างด้านล่าง ให้ทายผลของแต่ละกรณีไว้ก่อน โดยใช้หลัก 3 อย่างที่เพิ่งเรียน: ชั้นน้ำหนักของ selector, การเทียบ (a,b,c,d), และกรณี !important ที่ข้ามทุกชั้น **ทายก่อนเปิด preview:** ข้อความในกรณีที่ 1 จะเป็นสีอะไร? กรณีที่ 2? เส้นขอบในกรณีที่ 3? และข้อความในกรณีที่ 4?
7) เปรียบเทียบ: ยกน้ำหนักที่ selector หรือพึ่ง !important
เมื่อ rule หนึ่งชนะ rule ที่คุณอยากให้ชนะ คุณมีสองทาง: แก้ที่น้ำหนักของ selector ให้สูงขึ้น หรือแปะ !important ลงไปให้ชนะแบบข้ามชั้น — ดูผลต่างของสองทางนี้จากตัวอย่างเดียวกัน
ชนะด้วย !important กับชนะด้วยน้ำหนักที่ถูกต้อง
ทั้งสองฝั่งทำให้ข้อความเตือนเป็นสีแดง แต่ฝั่งขวายังอยู่ในระบบน้ำหนักปกติที่คาดเดาและไล่ที่หลังได้
ชนะจริง แต่น้ำหนักปกติของ selector ไม่มีความหมายในไฟล์นี้แล้ว — rule อื่นจะเอาชนะ .warning ไม่ได้อีกเลยจนกว่าจะใช้ !important สู้กันเอง
ถ้า element นั้นมี id หรือมี class ซ้อนอยู่ ให้ selector เจาะจงขึ้นจนชนะด้วยน้ำหนักปกติ — ยังเขียน rule ที่เจาะจงกว่าเดิมทับได้อีกในภายหลัง
Lab 1 — ทำตาม: ใช้ id rule ชนะ .text โดยไม่แก้ rule เดิม
ข้อความด้านล่างถูก .text { color: blue; } กำหนดให้เป็นสีน้ำเงิน แต่โจทย์ต้องการให้เป็นสีแดง — เหมือนตัวอย่างในหัวข้อเปรียบเทียบที่ผ่านมา ให้ทำตามรูปแบบนั้น: เพิ่ม rule ใหม่ที่ใช้ id ของข้อความ ซึ่งมีอยู่ใน HTML แล้ว
Lab 2 — ปรับ: เพิ่มน้ำหนัก selector ที่แพ้ให้ชนะ .notice
มี rule สอง rule ที่ specificity เท่ากัน (0,0,1,0) คือ .notice-strong กับ .notice — rule ที่เขียนทีหลังอย่าง .notice จึงชนะเสมอ ทำให้ข้อความที่ต้องเน้นสีเหลืองอมส้มกลายเป็นสีเทา ให้ปรับ rule .notice-strong ให้มีน้ำหนักชนะ .notice โดยไม่ต้องพึ่งลำดับการเขียน
Lab 3 — แก้เอง: เขียน rule ใหม่ให้ชนะสไตล์จากไลบรารี
ไลบรารีปุ่มของเว็บกำหนด .panel .btn { background: #94a3b8; } ไว้ทำให้ปุ่มทุกตัวในหน้าเป็นสีเทา — แต่ปุ่ม "ลบบัญชี" ต้องเป็นสีแดงเตือน และคุณแก้โค้ดไลบรารีไม่ได้ ให้เขียน rule ใหม่โดยเลือก hook จาก HTML ที่มีอยู่เพื่อชนะ rule นั้นด้วยน้ำหนักปกติ
ตรวจความเข้าใจและก้าวต่อไป
ตอบคำถามสั้น ๆ เพื่อเช็กว่าคุณแยกแยะ specificity ออกจากลำดับการเขียน และทำนายผู้ชนะระหว่าง rule ได้หรือยัง ก่อนไปเรียน cascade ในบทถัดไป
ตรวจความเข้าใจ: Specificity
หน้า HTML มี <p class="message" id="notice"> อยู่หนึ่งตัว และ CSS มี .message { color: blue; } กับ #notice { color: red; } — ข้อความนี้จะแสดงสีอะไร?
rule สอง rule นี้ rule ไหนชนะกัน: .nav .link { color: … } กับ .sidebar a { color: … }
ข้อใดต่อไปนี้เพิ่ม specificity ของ rule ได้จริง (เลือกทุกข้อที่ถูก)
selector #nav .link มีน้ำหนักเท่าไรในรูป (a, b, c, d)? ตอบเป็นตัวเลขคั่นด้วยลูกจุลภาค เช่น 0,0,1,1
- Specificity คือน้ำหนักความเจาะจงของ selector — เจาะจงมากชนะเจาะจงน้อย
- ลำดับน้ำหนักจากมากไปน้อย: inline style > ID > class/attribute/pseudo-class > element
- เทียบน้ำหนักเป็น (a, b, c, d) จากซ้ายไปขวา — ถ้าเท่ากันทุกหลัก rule ที่เขียนทีหลังชนะ
- !important ข้ามการเทียบน้ำหนักทั้งหมด — ใช้เฉพาะเมื่อแตะ selector หรือ HTML ไม่ได้
- ก้าวต่อไป: ในบทถัดไป 'Cascade & Inheritance' เราจะเรียนกลไกการตัดสินที่เหลือของเบราว์เซอร์ — ทั้ง cascade order และ inheritance ที่ส่งต่อค่าจากพ่อแม่สู่ลูกหลาน (สมมติฐานความรู้ก่อนหน้า: แยกแยะ specificity ได้จากบทนี้ — เพราะ cascade เริ่มตัดสินต่อเมื่อ specificity เท่ากัน)