Fundamentals
Pseudo-class Selectors
ใช้ pseudo-class — :hover, :focus, :active, :first-child, :nth-child(), :not() และ :checked/:disabled — เพื่อเลือก element ตามสถานะและตำแหน่งที่ HTML ไม่ได้เขียนไว้
1) บทบาท: เมื่อ style ต้องเปลี่ยนตามสถานะหรือตำแหน่งที่ HTML เขียนไม่ได้
selector ที่เรียนมาเลือก element ตามชื่อหรือโครงสร้างที่เขียนใน HTML แต่หน้าเว็บจริงมี style ที่ต้องเปลี่ยนตามสิ่งที่ HTML ไม่ได้บันทึกไว้ เช่น ตอนเมาส์ชี้ที่ปุ่ม ตอนช่องกรอกข้อมูลถูกโฟกัส หรือแถวที่ 2, 4, 6 ของตาราง การไปเพิ่ม class ทีละจุดเมื่อสถานะเปลี่ยน (เช่น เติม class="hovered" ตอนเมาส์ชี้) ต้องพึ่ง JavaScript — pseudo-class คือตัวเลือกที่เบราว์เซอร์คำนวณสถานะให้เอง โดยเราแค่เขียน rule ให้
- ปุ่มเปลี่ยนสีเมื่อเมาส์ชี้ และเปลี่ยนอีกสีขณะกดค้าง
- ช่องกรอกข้อมูลมีกรอบเด่นชัดเมื่อถูกโฟกัสด้วยแป้นพิมพ์
- รายการแรกของเมนูตัวหนา หรือแถวตารางสลับสีกันอัตโนมัติ
- ข้อความข้างช่องติ๊กเปลี่ยนสีเมื่อติ๊กถูกเลือก
2) รูปแบบ: selector:pseudo-class และกลุ่มสถานะผู้ใช้ (:hover / :focus / :active)
pseudo-class เขียนต่อท้าย selector ด้วยเครื่องหมาย : (colon ตัวเดียว) แล้วตามด้วยชื่อสถานะ — อ่านว่า "element ที่...อยู่ในสถานะนี้" เช่น a:hover คือ "ลิงก์ที่กำลังถูกเมาส์ชี้" กลุ่มแรกคือสถานะที่เกิดจากการโต้ตอบของผู้ใช้:
| pseudo-class | ตรงกับตอนไหน | ตัวอย่าง use case |
|---|---|---|
| :hover | เมาส์ชี้อยู่บน element | ปุ่มและลิงก์เปลี่ยนสีเมื่อชี้ |
| :focus | element กำลังถูกโฟกัส — คลิกหรือกด Tab มาที่มัน | ช่องกรอกข้อมูลมีกรอบเด่น — สำคัญสำหรับผู้ใช้แป้นพิมพ์ |
| :active | กำลังกดค้างอยู่ (ก่อนปล่อยเมาส์) | ปุ่มกดแล้วดู "ถูกกด" ทันที |
สถานะทั้งสามเขียนเหมือน rule ทั่วไป — แค่ selector ต่อท้ายด้วย :hover, :focus หรือ :active (หมายเหตุ: property อย่าง background และ font-weight ที่ใช้ในตัวอย่างและแบบฝึกหัด จะเรียนเต็มในบท Typography & Colors — บทนี้เน้นการเลือก element ด้วย pseudo-class)
3) กลุ่มตำแหน่ง: :first-child / :last-child / :nth-child()
กลุ่มที่สองเลือก element ตามตำแหน่งใน parent — ไม่ต้องรู้ชื่อหรือเติม class ให้รายการแต่ละตัว
| pseudo-class | ตรงกับตอนไหน | ตัวอย่าง |
|---|---|---|
| :first-child | ลูกคนแรกของ parent | รายการแรกตัวหนา |
| :last-child | ลูกคนสุดท้ายของ parent | รายการสุดท้ายไม่มีเส้นคั่นล่าง |
| :nth-child(2) | ลูกคนที่ 2 ของ parent | เลือกแถวใดแถวหนึ่ง |
| :nth-child(even) | ลูกคนที่ 2, 4, 6, ... (คู่) | แถวตารางสลับสี (zebra) |
| :nth-child(odd) | ลูกคนที่ 1, 3, 5, ... (คี่) | สลับสีอีกแบบ |
**ทายก่อนเปิด preview:** .menu มี <li> อยู่ 5 ตัว — ถ้าใช้ .menu li:nth-child(even) จะมีรายการไหนบ้างที่ได้พื้นสีเทาอ่อน?
นับจากลูกคนแรกของ parent เสมอ — ถ้าอยากได้ "li ตัวที่สองในทุก ul" ก็คือ nth-child(2) ของ ul นั้น
4) การยกเว้น: :not()
:not(selector) เลือก element ที่ไม่ตรงกับ selector ในวงเล็บ — ใช้แทนการเขียน "ทุกตัว ยกเว้น..." โดยไม่ต้องเพิ่ม class พิเศษให้ตัวที่ถูกยกเว้น กฎข้อเดียวที่ต้องจำ: selector ในวงเล็บต้องเป็น element, class หรือ pseudo-class อย่างง่าย
อ่านว่า "li ที่ไม่ใช่ตัวสุดท้าย" — ไม่ต้องแตะ HTML เพื่อเพิ่ม class ให้รายการสุดท้าย
5) สถานะฟอร์ม: :checked และ :disabled
กลุ่มสุดท้ายคือสถานะของ control ในฟอร์มที่เบราว์เซอร์จัดการให้เอง: **:checked** — checkbox หรือ radio ที่ถูกเลือก; ใช้คู่กับ adjacent sibling (+) เพื่อเปลี่ยนสไตล์ของ label ข้าง ๆ ได้ **:disabled** — ปุ่มหรือช่องกรอกที่ถูกปิดใช้งานด้วย attribute disabled ใน HTML; style ทำให้มันดู "จางและกดไม่ได้" โดยไม่ต้องพึ่ง JavaScript
ทั้งคู่เป็นการ "ตอบสนองสถานะฟอร์ม" ที่เคยต้องใช้ JavaScript ไล่เพิ่ม-ลบ class
6) เขียนผิด vs เขียนถูก: ลบ focus outline เพราะ "ไม่สวย"
ความผิดพลาดคลาสสิกของมือใหม่คือการลบ outline ตอน :focus เพราะคิดว่ากรอบประที่เบราว์เซอร์วาดให้ดูไม่เข้ากับดีไซน์ — แต่ outline คือตัวบอกผู้ใช้แป้นพิมพ์ว่ากำลังอยู่ที่ช่องไหน การลบทิ้งโดยไม่ใส่ของใหม่แทนทำให้เว็บใช้งานด้วยแป้นพิมพ์ไม่ได้ **ทายก่อนดูเฉลย:** ผู้ใช้ที่กด Tab ไปยังช่องกรอกข้อมูล — ฝั่งไหนที่เขายังรู้ว่าตัวเองโฟกัสอยู่ที่ช่องใด
จัดการ focus ยังไงไม่ให้พัง
ฝั่งซ้ายลบ outline ทิ้งอย่างเดียว — มองไม่เห็นตำแหน่งโฟกัส ฝั่งขวาเปลี่ยน outline ให้เข้ากับดีไซน์แทน จึงเห็นชัดทั้งผู้ใช้เมาส์และแป้นพิมพ์
ผู้ใช้ที่นำทางด้วยแป้นพิมพ์จะไม่รู้ว่าตัวเองอยู่ที่ช่องไหน — ถือเป็นปัญหาเรื่องการเข้าถึง
outline ใหม่ยังทำหน้าที่เดิม แต่สีและระยะห่างเข้ากับดีไซน์ — เก็บ focus ไว้เสมอ แค่ปรับให้สวย
Lab 1 — ทำตาม: ให้ control สื่อสถานะของผู้ใช้
ฟอร์มสมัครสมาชิกมีปุ่มบันทึก ช่องกรอกชื่อ และปุ่มที่ปิดใช้งานอยู่ — ให้เขียน rule ตามสเปกในแท็บ CSS เพื่อให้ control แต่ละแบบสื่อสถานะของตัวเอง: ปุ่มบันทึกเข้มขึ้นเมื่อ hover, ช่องชื่อมีกรอบโฟกัสสีน้ำเงิน และปุ่มที่ปิดใช้งานดูจางลง
Lab 2 — แก้: เส้นคั่นที่หลุดไปติดรายการสุดท้าย
เมนู 5 รายการต้องการเส้นคั่นระหว่างรายการเท่านั้น (รายการสุดท้ายต้องไม่มีเส้นใต้) รายการแรกตัวหนา และรายการคู่ได้พื้นสีอ่อน — แต่ starter เขียน .menu li แบบเหมารวม เส้นคั่นเลยหลุดไปติดรายการสุดท้าย และยังขาดแถบรายการคู่ ให้แก้ selector ของเส้นคั่นเป็น :not(:last-child) และเพิ่ม rule สำหรับรายการคู่
Lab 3 — แก้โจทย์: เลือก pseudo-class ให้ตรงกับแต่ละโซน
แผงสั่งซื้อมีสามโซนที่สเปกบอกเป็นผลลัพธ์ที่ต้องการ — คุณต้องเลือก pseudo-class เอง: รายการแรกของรายการสินค้าตัวหนา, ข้อความข้างช่องติ๊กเปลี่ยนเป็นสีเขียวเมื่อติ๊กถูก, และปุ่มส่งที่ปิดใช้งานดูจางลง
ตรวจความเข้าใจและก้าวต่อไป
ตอบคำถามสั้น ๆ เพื่อเช็กว่าคุณเลือก pseudo-class ให้ตรงกับสถานะหรือตำแหน่งที่ต้องการ และแยก :hover/:focus กับ :first-child/:nth-child() ได้หรือยัง ก่อนไปเรียนบทถัดไปของหัวข้อ selectors
ตรวจความเข้าใจ: Pseudo-class Selectors
ต้องการให้ปุ่มเปลี่ยนสีเมื่อผู้ใช้กด Tab มายังปุ่มนั้นด้วยแป้นพิมพ์ — ควรใช้ pseudo-class ใด
HTML: <ul><li>แรก</li><li>ที่สอง</li></ul> — ถ้าเขียน ul li:first-child หมายถึงรายการใด
ข้อใดใช้ :not() ได้อย่างถูกต้องตามที่เรียนในบทนี้ (เลือกทุกข้อที่ถูก)
pseudo-class เขียนนำหน้าด้วยเครื่องหมายอะไร เช่น ใน .menu li:first-child? ตอบเป็นเครื่องหมาย
- pseudo-class เขียนด้วยเครื่องหมาย : ตัวเดียวต่อท้าย selector — เลือกตามสถานะหรือตำแหน่งที่ HTML ไม่ได้เขียน
- กลุ่มสถานะผู้ใช้: :hover (ชี้), :focus (โฟกัส), :active (กำลังกด)
- กลุ่มตำแหน่ง: :first-child, :last-child, :nth-child(even/odd/ตัวเลข)
- :not() เลือกทุกตัวยกเว้น · :checked กับ :disabled ตอบสนองสถานะฟอร์ม
- ก้าวต่อไป: ในบทถัดไป 'Pseudo-element Selectors' เราจะเรียน selector ที่สร้างหรือเลือก "ส่วนย่อย" ของ element ด้วยเครื่องหมายโคลอนสองตัว เช่น ข้อความข้างหน้า ข้างหลัง หรือตัวอักษรแรก (สมมติฐานความรู้ก่อนหน้า: เขียน pseudo-class ด้วยเครื่องหมาย : และเลือก element ตามสถานะ/ตำแหน่งได้จากบทนี้ — เพราะบทถัดไปใช้เครื่องหมายโคลอนสองตัวที่ต่างจาก : และมักใช้ประกบกับ pseudo-class ที่เรียนมาแล้ว เช่น input:focus)