CSS
Fundamentals
Specificity — กฎตัดสินว่า Style ไหนชนะ
เคยเขียน CSS แล้วสงสัยว่าทำไม style ไม่ออกตามที่คิด? คำตอบส่วนใหญ่อยู่ที่ Specificity — กฎที่ Browser ใช้ตัดสินว่าเมื่อหลาย rule ชนกัน rule ไหนจะมีผลจริง
Specificity คืออะไร
Specificity hierarchy — ยิ่งเจาะจงมาก ยิ่งมีน้ำหนักสูง และชนะ selector ที่กว้างกว่า
เวลาคุณเขียน CSS หลาย rule อาจ match กับ element เดียวกันได้พร้อมกัน เช่น มีทั้ง p, .message, และ #special ที่กำหนด color ให้กับ <p> เดียวกัน Browser ต้องตัดสินว่าจะใช้ color ไหน — และ Specificity คือกฎที่ Browser ใช้ในการตัดสินนั้น Specificity (ความจำเพาะ) คือ "น้ำหนัก" ของ selector แต่ละแบบ selector ที่เจาะจงกว่า จะมีน้ำหนักมากกว่า และชนะ selector ที่กว้างกว่า พูดง่าย ๆ ว่า: ยิ่งเจาะจงมาก ยิ่งชนะ
ทำไม Specificity ถึงสำคัญ
Specificity เป็นสาเหตุอันดับต้น ๆ ที่ทำให้ผู้เริ่มต้นงงว่า "CSS ทำไมไม่ออกตามที่คิด" การเข้าใจ Specificity ช่วยให้คุณ:
- Debug CSS ได้เร็วขึ้น — รู้ทันทีว่า style ไม่ออกเพราะ rule อื่นมี specificity สูงกว่า
- เขียน CSS ได้มั่นใจขึ้น — รู้ว่า rule ที่เขียนจะชนะหรือแพ้ rule อื่น
- ลดการใช้ !important แบบไม่จำเป็น — ซึ่งทำให้โค้ดบำรุงรักษายาก
- วางโครงสร้าง CSS ได้ถูกต้อง — เลือก selector ให้เหมาะกับสถานการณ์
ตัวอย่างจากชีวิตจริง
ลองนึกภาพว่าคุณทำงานในบริษัท และได้รับคำสั่งจาก 3 ระดับพร้อมกัน: 📢 ประกาศทั่วบริษัท: "พนักงานทุกคนแต่งกายสุภาพ" → นี่เหมือน Element Selector — ใช้กับทุกคน กว้างมาก น้ำหนักต่ำ 👤 หัวหน้ากลุ่ม Marketing บอก: "ทีม Marketing ใส่เสื้อสีน้ำเงิน" → นี่เหมือน Class Selector — เจาะจงกว่า ใช้กับกลุ่มเฉพาะ 📋 ผู้จัดการโดยตรงบอกคุณชื่อ "คุณสมชาย": "คุณสมชาย วันนี้ใส่ชุดทางการ" → นี่เหมือน ID Selector — เจาะจงถึงคนคนเดียว น้ำหนักสูงที่สุด คุณสมชายจะทำตามคำสั่งไหน? — คำสั่งที่เจาะจงถึงตัวเองมากที่สุดแน่นอน CSS ก็ทำงานแบบเดียวกัน — ลองดูตัวอย่างด้านล่าง สังเกตว่าข้อความได้รับ style จาก rule ไหน
แนวคิดหลัก: ลำดับน้ำหนักของ Selector
น้ำหนัก Specificity — Inline style มีน้ำหนักสูงกว่า ID, ID สูงกว่า Class, Class สูงกว่า Element
CSS มีลำดับน้ำหนักของ selector ดังนี้ จากสูงสุดไปต่ำสุด:
| ประเภท Selector | ตัวอย่าง | น้ำหนัก (a,b,c,d) | ลำดับ |
|---|---|---|---|
| Inline Style | style="color: red" | (1,0,0,0) | สูงสุด |
| ID Selector | #header | (0,1,0,0) | สูง |
| Class / Attribute / Pseudo-class | .message / [type] / :hover | (0,0,1,0) | กลาง |
| Element / Pseudo-element | p / h1 / ::before | (0,0,0,1) | ต่ำ |
| Universal Selector | * | (0,0,0,0) | ต่ำสุด |
การคำนวณ Specificity แบบ (a, b, c, d)
Browser คำนวณ specificity ของแต่ละ selector เป็น tuple 4 ตัว: (a, b, c, d) a = จำนวน Inline Style (1 ถ้ามี style="" โดยตรงใน HTML, มิฉะนั้นเป็น 0) b = จำนวน ID selector (#id) c = จำนวน Class, Attribute selector, และ Pseudo-class (.class / [attr] / :hover) d = จำนวน Element selector และ Pseudo-element (p / h1 / ::before) วิธีเปรียบเทียบ: เทียบทีละตำแหน่ง จากซ้ายไปขวา — ตัวไหนมากกว่าชนะ ตัวอย่าง:
เปรียบเทียบ specificity ทีละตำแหน่ง: (0,1,0,0) ชนะ (0,0,2,0) เพราะ b=1 มากกว่า b=0
/* (0,0,0,1) — 1 element */
p { color: gray; }
/* (0,0,1,0) — 1 class */
.message { color: blue; }
/* (0,1,0,0) — 1 ID */
#special { color: red; }
/* (0,1,1,1) — 1 ID + 1 class + 1 element */
#special .message p { color: orange; }
/* (0,0,2,0) — 2 class */
.nav .link { color: green; }
/* (0,0,1,1) — 1 class + 1 element */
p.message { color: purple; }ตัวอย่างเชิงเทคนิค — กรณีชนกัน
ดูตัวอย่างด้านล่าง แล้วลองทายก่อนว่าแต่ละ element จะได้ style ไหน จากนั้น Preview ด้านขวาจะแสดงผลจริง กรณีที่ 1: Element vs Class → Class ชนะ (0,0,1,0 > 0,0,0,1) กรณีที่ 2: Class vs ID → ID ชนะ (0,1,0,0 > 0,0,1,0) กรณีที่ 3: หลาย Class รวมกัน → ค่า c บวกสะสม กรณีที่ 4: !important → ทำลายกฎ specificity ปกติ ควรใช้เท่าที่จำเป็นจริง ๆ
/* กรณีที่ 1: Element (0,0,0,1) vs Class (0,0,1,0) */
p { color: gray; } /* แพ้ */
.highlight { color: blue; } /* ชนะ */
/* กรณีที่ 2: Class (0,0,1,0) vs ID (0,1,0,0) */
.title { font-size: 14px; } /* แพ้ */
#main-title { font-size: 28px; } /* ชนะ */
/* กรณีที่ 3: 1 class (0,0,1,0) vs 2 class (0,0,2,0) */
.box { border: 1px solid gray; } /* แพ้ */
.card.box { border: 2px solid blue; } /* ชนะ — c=2 */
/* กรณีที่ 4: !important — ข้ามกฎ specificity ทั้งหมด */
#special { color: green; } /* แพ้ — แม้ ID สูง */
p { color: red !important; } /* ชนะ — !important เสมอ */การทำงานทีละขั้นตอน
เมื่อ Browser ต้องตัดสิน style สุดท้ายของ element หนึ่ง มันทำตามขั้นตอนนี้:
- 1. รวบรวม CSS rule ทั้งหมดที่ match กับ element นั้น — ทุก rule ที่ selector จับ element ได้
- 2. คำนวณ specificity ของแต่ละ rule — นับจำนวน ID, Class, Element ใน selector
- 3. เปรียบเทียบ specificity — rule ที่มีค่า (a,b,c,d) สูงกว่าชนะ
- 4. ถ้า specificity เท่ากัน — rule ที่ประกาศทีหลังในไฟล์ CSS ชนะ (Cascade order)
- 5. ถ้า rule ใดมี !important — rule นั้นชนะทุกอย่าง ยกเว้น !important อีกตัวที่มี specificity สูงกว่า
- 6. นำ style จาก rule ที่ชนะไปใช้กับ element — ผลลัพธ์คือสิ่งที่เห็นบนหน้าจอ
จุดที่ผู้เริ่มต้นมักสับสน
- ❌ คิดว่ากฎที่อยู่ล่างสุดชนะเสมอ — ผิด! ลำดับการเขียนชนะได้เฉพาะเมื่อ specificity เท่ากันเท่านั้น
- ❌ ใช้ ID selector มากเกินไป — ID มี specificity สูงมาก ทำให้ override ทีหลังยาก
- ❌ ใช้ !important แก้ทุกปัญหา — สร้างปัญหามากกว่าแก้ เพราะถ้าทุกอย่างใช้ !important ก็ต้องสู้กันด้วย !important อีก
- ❌ คิดว่า selector ยาวกว่าชนะเสมอ — ไม่จริง! p.message (0,0,1,1) แพ้ #id (0,1,0,0)
- ❌ สับสน Specificity กับ Cascade — Specificity คือน้ำหนักของ selector, Cascade คือลำดับการประกาศ (คนละเรื่อง)
Specificity vs Cascade — สองเรื่องที่ต้องไม่สับสน
Specificity และ Cascade มักถูกสับสนกัน เพราะทั้งคู่ใช้ในการตัดสินว่า style ไหนชนะ แต่เป็นคนละกลไกกัน:
| หัวข้อ | Specificity | Cascade (ลำดับการประกาศ) |
|---|---|---|
| คืออะไร | น้ำหนักของ selector | ลำดับการเขียนใน CSS |
| ใช้เมื่อ | selector ต่างชนิดกัน (ID, Class, Element) | selector มี specificity เท่ากัน |
| ชนะโดย | selector ที่เจาะจงกว่า | rule ที่เขียนทีหลัง |
| ตัวอย่าง | #id > .class > p | .red ที่อยู่ล่างกว่าชนะ .red อีกตัว |
| ระวัง | ID specificity สูงมาก override ยาก | ลำดับสำคัญเฉพาะเมื่อ specificity เท่ากัน |
สรุปท้ายบท
สูตรจำง่าย: Inline > ID > Class > Element เมื่อหลาย rule ชนกัน Browser ใช้ Specificity ตัดสิน — ยิ่งเจาะจงมากยิ่งชนะ
- Specificity คือน้ำหนักของ selector — ยิ่งเจาะจงมากยิ่งมีน้ำหนักสูง
- ลำดับน้ำหนัก: Inline style > ID (#) > Class/Attribute/Pseudo-class (.) > Element (p, h1, ...)
- คำนวณด้วย tuple (a,b,c,d) — นับจากซ้ายไปขวา ตัวแรกที่ต่างกันตัดสินผล
- ถ้า specificity เท่ากัน — กฎที่เขียนทีหลังชนะ (Cascade order)
- !important ข้ามกฎ specificity ทั้งหมด — ใช้เท่าที่จำเป็นจริง ๆ เท่านั้น
- ปัญหา CSS ไม่ออก มักเกิดจาก rule อื่นมี specificity สูงกว่า — ไม่ใช่แค่ลำดับการเขียน
แบบฝึกหัด: แก้ Specificity โดยไม่ใช้ !important
โจทย์: ข้อความด้านล่างถูกกำหนดสีน้ำเงินด้วย .text แต่เราต้องการให้เป็นสีแดง กติกา: ห้ามใช้ !important และห้ามแก้ไข .text { color: blue; } วิธีแก้: เพิ่ม HTML attribute id="highlight-text" ให้กับ <p> แล้วเพิ่ม CSS rule #highlight-text { color: red; } ด้านล่าง