🧰 ของที่ใช้ตรวจในบทนี้ (รันวันที่ 27 สิงหาคม 2026)
Google Chrome (headless) บน macOS
// วัดด้วย getBoundingClientRect() และ getComputedStyle()
ตัวเลขทุกตัวในบทนี้ (348px · 40px · 45px · 20px …) มาจากการวัดจริง ไม่ได้คำนวณในหัว
กล่อง ผลลัพธ์จริง ที่เห็นในหน้านี้คือ <iframe> ที่รันโค้ดชุดเดียวกับกล่องโค้ดข้างบนมันเป๊ะ ๆ
ไม่มี CSS ของหน้าเว็บนี้เจือปน
1 · ทุกอย่างบนหน้าเว็บคือกล่องสี่เหลี่ยม
ประโยคเดียวที่ต้องเชื่อก่อนเริ่ม: ไม่ว่าคุณจะเขียน element อะไร เบราว์เซอร์มองมันเป็นสี่เหลี่ยมเสมอ
ตัวอักษรกลม ๆ ปุ่มขอบมน รูปวงกลม — ข้างในเบราว์เซอร์มันคือสี่เหลี่ยมทั้งหมด แค่ทาสีให้ดูไม่เหมือน
และสี่เหลี่ยมหนึ่งอันไม่ได้มีชั้นเดียว มันมีสี่ชั้นซ้อนกัน จากในออกนอก:
+-----------------------------------------+
| MARGIN |
| +-----------------------------------+ |
| | BORDER | |
| | +-----------------------------+ | |
| | | PADDING | | |
| | | +---------------------+ | | |
| | | | CONTENT | | | |
| | | +---------------------+ | | |
| | | | | |
| | +-----------------------------+ | |
| | | |
| +-----------------------------------+ |
| |
+-----------------------------------------+
| ชั้น | คืออะไร | พื้นหลังทาถึงไหม |
|---|---|---|
| content | ตัวข้อความ รูป หรือกล่องลูกที่อยู่ข้างใน | ทา |
| padding | ที่ว่างด้านใน ระหว่างเนื้อหากับขอบกล่อง | ทา |
| border | เส้นกรอบ — มีความหนาจริง กินที่จริง | เป็นเส้นเอง |
| margin | ที่ว่างด้านนอก ระหว่างกล่องนี้กับกล่องอื่น | ไม่ทา — โปร่งใสเสมอ |
ช่องขวาสุดของตารางนี้คือวิธีดูด้วยตาเปล่าว่าอันไหนคือ padding อันไหนคือ margin — ทาสีพื้นให้กล่อง แล้วดูว่าสีขยายตามไปด้วยหรือไม่ ถ้าขยาย = padding ถ้าไม่ขยาย = margin
<style>
.wrap { background: #eef2f7; border: 2px dashed #74c0fc; }
.pad { background: #ffd8a8; padding: 24px; }
.mar { background: #a5d8ff; margin: 24px; }
</style>
<div class="wrap">
<div class="pad">padding: 24px — พื้นสีส้มขยายออกไปด้วย</div>
<div class="mar">margin: 24px — พื้นสีฟ้าไม่ขยาย ที่ว่างเป็นสีของพ่อ</div>
</div>
สังเกตให้ดี: กล่องส้มกับกล่องฟ้าเว้นที่ไว้เท่ากันคือ 24px แต่หน้าตาต่างกันคนละเรื่อง — ส้มกลืนที่ว่างเข้ามาเป็นของตัวเอง ส่วนฟ้ายกที่ว่างนั้นให้พ่อ (พื้นเทา) ไป
นึกถึงกล่องพัสดุที่วางเรียงกันบนชั้น:
· content = ของที่อยู่ข้างใน เช่น แก้วน้ำ
· padding = โฟมกันกระแทกที่ยัดไว้รอบแก้ว — อยู่ในกล่อง ยัดเยอะกล่องก็ใหญ่ขึ้น
· border = ผนังกระดาษของกล่องเอง — บางหรือหนาก็ได้ แต่มีความหนาจริง
· margin = ช่องว่างที่คุณเว้นไว้รอบนอกกล่อง ไม่ให้มันชนกล่องข้าง ๆ
เอาไปใช้ตัดสินใจได้เลย: อยากให้ข้อความห่างจากขอบกล่องตัวเอง → padding · อยากให้กล่องห่างจากกล่องอื่น → margin
ที่ไม่เหมือนกัน: ช่องว่างบนชั้นวางของจริง ถ้าคุณเว้น 5 ซม. และคนข้าง ๆ เว้น 3 ซม.
ระยะห่างจริงคือ 8 ซม. — แต่ margin ในแนวตั้งไม่บวกกันแบบนั้น
มันเลือกเอาค่ามากกว่าค่าเดียว (จะได้เห็นของจริงในหัวข้อ 5)
อีกจุด: โฟมกันกระแทกยัดเข้าไปแล้วกล่องข้างนอกไม่โตขึ้นก็ได้ ถ้าคุณยัดของน้อยลง —
แต่ padding ในค่าเริ่มต้นของ CSS ดันกล่องให้ใหญ่ขึ้นเสมอ (หัวข้อ 4)
- box model
- โมเดลที่บอกว่ากล่องหนึ่งกล่องประกอบด้วยสี่ชั้น content · padding · border · margin และแต่ละชั้นกินที่เท่าไหร่ · ถาม senior ได้ว่า “อันนี้ควรแก้ที่ padding หรือ margin” เป็นคำถามที่คนทำเว็บทุกคนเข้าใจทันที ต่างจากการถามว่า “ทำไมมันชิดกันจัง”
- padding
- ที่ว่างด้านในกล่อง ระหว่างเนื้อหากับขอบ · พื้นหลังทาถึง · พิมพ์บอก AI ได้ว่า “give the card 24px of inner padding, not margin” การระบุคำว่า inner ทำให้ไม่ต้องเถียงกันรอบสอง
- margin
- ที่ว่างด้านนอกกล่อง ระหว่างกล่องนี้กับกล่องอื่น · โปร่งใสเสมอ · พิมพ์บอก AI ได้ว่า “add 16px of vertical spacing between these cards” แล้วค่อยดูว่ามันเลือกใช้ margin หรือ gap
- border
- เส้นกรอบของกล่อง มีทั้งความหนา (
width) รูปแบบ (style) และสี (color) · จำไว้ว่ามันกินที่จริง ไม่ใช่เส้นวาดทับ — จุดนี้จะกลับมาหลอกคุณในหัวข้อ 4
2 · เขียน padding กับ margin — สี่แบบที่เจอในโค้ดจริง
สไลด์หน้า 43 กับ 44 โชว์รูปแบบการเขียนไว้ครบแล้ว ทั้งสอง property ใช้กติกาเดียวกันเป๊ะ เปลี่ยนแค่ชื่อ ดังนั้นจำครั้งเดียวได้ทั้งคู่:
| เขียนแบบ | ตัวอย่าง | แปลว่า |
|---|---|---|
| 1 ค่า | padding: 64px; | ทั้งสี่ด้าน 64 |
| 2 ค่า | padding: 120px 64px; | บน/ล่าง 120 · ซ้าย/ขวา 64 |
| 4 ค่า | padding: 120px 64px 120px 64px; | บน · ขวา · ล่าง · ซ้าย (วนตามเข็มนาฬิกา) |
| แยกด้าน | padding-top: 120px; | ระบุทีละด้าน มีครบสี่ตัว -top -right -bottom -left |
เคล็ดจำลำดับ 4 ค่า: เริ่มที่บน แล้วเดินตามเข็มนาฬิกา — บน → ขวา → ล่าง → ซ้าย
ส่วนแบบ 2 ค่าคือ "แนวตั้งก่อน แนวนอนทีหลัง" ซึ่งเป็นแบบที่เจอบ่อยที่สุดในโค้ดจริง
เพราะการ์ดส่วนใหญ่อยากให้บนล่างกับซ้ายขวาไม่เท่ากัน
margin มีของแถมที่ padding ไม่มีอยู่สองอย่าง:
รับค่าติดลบได้ (ดึงกล่องให้ทับกัน) และรับคำว่า auto ได้
ซึ่ง margin: 0 auto; คือวิธีจัดกล่องให้อยู่กลางแนวนอนที่คุณจะเจอในทุกโปรเจกต์
3 · border — สามอย่างในบรรทัดเดียว
border เป็น shorthand ที่รวมสามค่าไว้ในบรรทัดเดียว: ความหนา · รูปแบบ · สี
ที่ต้องรู้คือ ถ้าไม่ใส่ style เส้นจะไม่ขึ้นเลย — ค่าเริ่มต้นของ border-style คือ none
ใส่ border: 4px red; อย่างเดียวจึงไม่เห็นอะไรทั้งนั้น
<style>
.b1 { border: 4px solid #495057; padding: 8px; margin-bottom: 12px; }
.b2 { border: 4px dashed #e8590c; padding: 8px; margin-bottom: 12px; }
.b3 { border-width: 4px;
border-style: solid;
border-color: #1971c2;
border-radius: 12px;
padding: 8px; }
</style>
<div class="b1">border: 4px solid #495057;</div>
<div class="b2">border: 4px dashed #e8590c;</div>
<div class="b3">เขียนแยกสามบรรทัด + border-radius</div>
สองบรรทัดแรกกับสามบรรทัดหลังให้ผลเหมือนกัน ต่างกันแค่ความยาว —
ในโค้ดจริงคนใช้ shorthand เกือบตลอด แล้วค่อยใช้แบบแยกเมื่อต้องแก้เฉพาะบางส่วนทีหลัง
เช่น border-color: red; ตอน hover
4 · กับดักที่หนึ่ง — width: 100% แล้วล้น
นี่คือกับดักที่มือใหม่เจอเป็นอันดับหนึ่ง และเจอภายในชั่วโมงแรกที่เริ่มจัดหน้า
ลองทายก่อนดูเฉลย:
.frame { width: 300px; }
.card { width: 100%;
padding: 20px;
border: 4px solid red; }
/* .card เป็นลูกของ .frame */
กล่อง .card จะกว้างจริง ๆ กี่ pixel?
348px — ไม่ใช่ 300 (ผมวัดด้วย getBoundingClientRect() ใน Chrome ได้ 348 เป๊ะ)
เพราะ width: 100% ในค่าเริ่มต้นของ CSS หมายถึง ความกว้างของ content เท่านั้น
ส่วน padding กับ border ไปบวกเพิ่มข้างนอก:
300 (content) + 20×2 (padding) + 4×2 (border) = 348
ผลคือกล่องลูกกว้างกว่ากล่องพ่อ 48px แล้วล้นออกไป
<style>
.frame { width: 300px; border: 2px dashed #adb5bd; }
.card { width: 100%;
padding: 20px;
border: 4px solid #c92a2a;
background: #ffe3e3; }
</style>
<div class="frame">
<div class="card">width: 100% + padding 20 + border 4 → ล้นออกนอกกรอบ</div>
</div>
เส้นประเทาคือกรอบ 300px · กล่องแดงคือลูกที่สั่ง width: 100% —
จะเห็นชัดว่ามันโผล่พ้นเส้นประออกไปทั้งสองข้าง
ทางแก้คือบรรทัดเดียว: box-sizing: border-box;
ซึ่งเปลี่ยนความหมายของ width จาก "ความกว้างของ content" เป็น
"ความกว้างรวม padding และ border แล้ว"
<style>
.frame { width: 300px; border: 2px dashed #adb5bd; }
.card { width: 100%;
padding: 20px;
border: 4px solid #2f9e44;
background: #d3f9d8;
box-sizing: border-box; }
</style>
<div class="frame">
<div class="card">เพิ่ม box-sizing: border-box บรรทัดเดียว → พอดี 300px</div>
</div>
คราวนี้กล่องเขียวพอดีกับเส้นประเป๊ะ · ผมวัดได้ 300 ตรง ๆ
และที่ว่างข้างในหดจาก 300 เหลือ 300 − 20×2 − 4×2 = 252 —
นั่นคือสิ่งที่ border-box ทำ: มันไม่ได้ทำให้ padding หายไป มันเบียดที่ของ content แทน
คุณโทรสั่งช่างว่า "เอากว้าง 300" ช่างมีสองวิธีตีความ:
· วัดเฉพาะแผ่นกระจก — กระจก 300 พอดี แต่พอใส่กรอบเข้าไป
ของที่ต้องยัดเข้าช่องหน้าต่างจริง ๆ กลายเป็น 348 ใส่ไม่ลง
· วัดรวมกรอบ — ของทั้งชิ้น 300 พอดี ใส่ลงช่องแน่นอน แค่กระจกข้างในเล็กลงหน่อย
ค่าเริ่มต้นของ CSS คือวิธีแรก (content-box) · border-box คือวิธีที่สอง
และนี่คือเหตุผลที่โปรเจกต์จริงเกือบทุกโปรเจกต์เขียน
* { box-sizing: border-box; } ไว้บรรทัดแรกของไฟล์ CSS แล้วไม่ต้องคิดเรื่องนี้อีกเลย
ที่ไม่เหมือนกัน: ช่างจริงจะโทรกลับมาถามว่า "พี่หมายถึงรวมกรอบหรือเปล่าครับ" —
แต่ CSS ไม่ถาม มันเลือกวิธีแรกให้เงียบ ๆ แล้วปล่อยของล้นออกมา
และไม่มี error ไม่มี warning ใน console ด้วย — คุณรู้ตัวตอนเห็น scrollbar แนวนอนโผล่มาเท่านั้น
- box-sizing
- property ที่บอกว่า
width/heightที่คุณเขียน หมายถึงวัดถึงชั้นไหน · ถาม senior ได้ว่า “โปรเจกต์นี้ตั้ง box-sizing: border-box ไว้ทั่วทั้งไฟล์หรือยัง” เป็นคำถามที่ตอบได้ใน 5 วินาที และประหยัดเวลาคุณทั้งวัน - content-box
- ค่าเริ่มต้น —
widthนับเฉพาะ content · padding กับ border บวกเพิ่มข้างนอก · คือคำอธิบายว่าทำไม 100% ถึงล้น เวลาอธิบายให้คนอื่นฟังให้ใช้คำนี้ - border-box
widthนับรวม padding และ border แล้ว · content จะหดเอง · พิมพ์บอก AI ได้ว่า “apply box-sizing: border-box globally with the universal selector”- overflow (ล้น)
- อาการที่เนื้อในกว้าง/สูงเกินกล่องที่ครอบมันอยู่ · ถ้าเจอ scrollbar แนวนอนโผล่มาโดยไม่ได้ตั้งใจ ให้สงสัย box-sizing เป็นอันดับแรก
5 · กับดักที่สอง — margin สองอันบนล่างกัน "ยุบรวมกัน"
กับดักนี้เจอทีหลัง แต่ทำให้คนงงนานกว่า เพราะมันไม่มีอะไรล้น ไม่มีอะไรพัง แค่ระยะห่างที่ได้ไม่ตรงกับที่สั่ง แล้วคนก็ไปนั่งเพิ่มตัวเลขจนกว่าจะดูดี
.top { margin-bottom: 40px; }
.bot { margin-top: 24px; }
/* .top กับ .bot เป็น div ธรรมดา วางบนล่างติดกัน */
ช่องว่างระหว่างสองกล่องนี้กว้างกี่ pixel?
40px — ไม่ใช่ 64 (ผมวัดระยะจากขอบล่างของกล่องบนถึงขอบบนของกล่องล่างใน Chrome ได้ 40)
margin ในแนวตั้งของสองกล่องที่ติดกันไม่บวกกัน —
มันยุบรวมเป็นก้อนเดียว แล้วเอาค่าที่มากกว่า
ในที่นี้คือ max(40, 24) = 40 · ตัวเลข 24 ที่คุณพิมพ์ไว้ไม่มีผลใด ๆ เลย
คนส่วนใหญ่ไม่รู้เรื่องนี้ แล้วไปแก้ 24 เป็น 30 เป็น 36 — ก็ยังไม่เห็นอะไรขยับ
เพราะทุกค่าที่น้อยกว่า 40 ให้ผลเหมือนกันหมด
<style>
.stack { background: #ffec99; border: 1px solid #f59f00; }
.box { background: #d0ebff; border: 1px solid #74c0fc; }
.top { margin-bottom: 40px; }
.bot { margin-top: 24px; }
</style>
<div class="stack">
<div class="box top">กล่องบน — margin-bottom: 40px</div>
<div class="box bot">กล่องล่าง — margin-top: 24px</div>
</div>
แถบเหลืองตรงกลางคือช่องว่างที่ได้จริง 40px
ทีนี้ดูกล่องถัดไป — โค้ดเหมือนเดิมทุกตัวอักษร เพิ่มแค่สองบรรทัดที่ .stack:
<style>
.stack { background: #ffec99; border: 1px solid #f59f00;
display: flex; flex-direction: column; }
.box { background: #d0ebff; border: 1px solid #74c0fc; }
.top { margin-bottom: 40px; }
.bot { margin-top: 24px; }
</style>
<div class="stack">
<div class="box top">กล่องบน — margin-bottom: 40px</div>
<div class="box bot">กล่องล่าง — margin-top: 24px</div>
</div>
แถบเหลืองกว้างขึ้นชัดเจน — ผมวัดได้ 64px คือ 40 + 24 บวกกันจริง ๆ
เพราะ ข้างในกล่อง flex ไม่มีการยุบรวม margin · นี่คือหนึ่งในเหตุผลที่คนสมัยนี้
เลิกใช้ margin จัดระยะระหว่างกล่อง แล้วหันไปใช้ gap ของ flex/grid แทน (เรื่องของบทเรียน 61)
การยุบรวมไม่ได้เกิดแค่ระหว่างพี่น้อง มันเกิดระหว่างพ่อกับลูกคนแรกด้วย —
ถ้าพ่อไม่มี padding และไม่มี border คั่นไว้
margin-top ของลูกจะเด้งออกไปอยู่นอกพ่อ แทนที่จะดันลูกลงมาข้างใน
ผมวัดของจริงมาแล้ว: พ่อ div เปล่า ๆ ที่มีลูกหนึ่งตัวสั่ง margin-top: 30px →
ขอบบนของพ่ออยู่ตำแหน่งเดียวกับขอบบนของลูกเป๊ะ และพ่อสูงแค่ 18px (สูงเท่าบรรทัดข้อความ)
คือ 30px นั้นไม่ได้อยู่ในตัวพ่อเลย
พอเติม padding-top: 1px ให้พ่อ ระยะจากขอบพ่อถึงขอบลูกกลายเป็น 31px ทันที (1 + 30) —
1 pixel เดียวหยุดการยุบได้
เจออาการ "ใส่ margin-top ให้ลูกแล้วทั้งก้อนเลื่อนลงไปทั้งอัน" เมื่อไหร่ ให้นึกถึงข้อนี้
- margin collapsing (การยุบรวมของ margin)
- margin แนวตั้งที่มาชนกันจะรวมเป็นก้อนเดียว ใช้ค่าที่มากที่สุด ไม่บวกกัน · เกิดเฉพาะแนวตั้ง ไม่เกิดแนวนอน · ถาม senior ได้ว่า “ตรงนี้ margin มัน collapse หรือเปล่า” แทนที่จะบอกว่า “ระยะห่างมันแปลก ๆ”
- gap
- property ที่ใช้กับ flex/grid สำหรับเว้นระยะระหว่างลูก · ไม่ยุบรวม ไม่ทะลุออกนอกพ่อ · พิมพ์บอก AI ได้ว่า “use flex gap instead of margins for the spacing between these cards” แล้วปัญหาทั้งหัวข้อนี้หายไปพร้อมกันหมด
- flex container
- กล่องพ่อที่สั่ง
display: flex· ลูกของมันเปลี่ยนกติกาการวางตัวใหม่หมด รวมทั้งเลิกยุบ margin · รายละเอียดเต็ม ๆ อยู่บทถัดไป
6 · หน่วย — px % rem em vh vw
สไลด์หน้า 46 ลิสต์หน่วยไว้หกตัว แต่ไม่ได้บอกว่าเลือกอันไหนเมื่อไหร่ ซึ่งคือส่วนที่ทำให้พลาดจริง มาไล่ทีละตัวว่า "ค่านี้อ้างอิงจากอะไร":
| หน่วย | อ้างอิงจาก | ใช้ตอนไหน |
|---|---|---|
px | ไม่อ้างอิงอะไร — เป็นค่าคงที่ | ความหนาเส้น · ระยะเล็ก ๆ ที่ไม่ควรขยับ |
% | ขนาดของกล่องพ่อ | ความกว้างที่ต้องยืดตามพ่อ |
rem | font-size ของ <html> (ปกติ 16px) | ค่าเริ่มต้นที่ควรใช้ สำหรับ font-size และระยะห่าง |
em | font-size ของตัวมันเอง — ยกเว้นตอนใช้กับ font-size ที่จะอ้างจากพ่อ | ระยะที่ต้องโตตามตัวอักษร เช่น padding ของปุ่ม |
vw | 1% ของความกว้างหน้าจอ | ของที่ต้องเต็มจอ เช่น hero banner |
vh | 1% ของความสูงหน้าจอ | section สูงเต็มจอ (100vh) |
สไลด์เขียนว่า "Relative to the Parent's Font Size (em)" — จริงแค่ครึ่งเดียว
คำนี้ถูกเฉพาะตอนใช้ em กับ property font-size เท่านั้น
ถ้าใช้กับอย่างอื่น เช่น padding: 1em มันอ้างจาก font-size ของตัวมันเอง ไม่ใช่ของพ่อ
ผมวัดของจริง: ลูกที่ font-size: 32px อยู่ในพ่อที่ font-size: 20px
แล้วสั่ง padding: 1em → ได้ 32px ไม่ใช่ 20px
จำแบบนี้ง่ายกว่า: em = "เท่ากับตัวหนังสือของกล่องนี้" ส่วนที่ font-size
ต้องอ้างจากพ่อ ก็เพราะตอนนั้นตัวมันเองยังไม่มีค่าให้อ้าง
ความต่างระหว่าง em กับ rem เห็นชัดที่สุดตอนซ้อนกันหลายชั้น:
<style>
.card { font-size: 20px; }
.em1 { font-size: 1.5em; }
.em2 { font-size: 1.5em; }
.rem1 { font-size: 1.5rem; }
</style>
<div class="card">พ่อตั้ง font-size: 20px
<div class="em1">1.5em ของฉัน = 30px
<div class="em2">1.5em ซ้อนอีกชั้น = 45px</div>
</div>
<div class="rem1">1.5rem = 24px เสมอ (16 × 1.5)</div>
</div>
ตัวเลขในกล่องนั้นไม่ใช่ที่ผมเขียนเอา — ผมอ่านจาก getComputedStyle() จริง:
30px · 45px · 24px
em ทบต้น — ชั้นที่สองคือ 1.5 ของ 30 ไม่ใช่ 1.5 ของ 20
ซ้อนสี่ชั้นเมื่อไหร่ก็คำนวณในหัวไม่ทันแล้ว
ส่วน rem อ้างจากรากเดียวเสมอ ซ้อนกี่ชั้นก็ยัง 24px
คนต่อคิวยาว ๆ มีสองวิธีบอกว่าตัวเองอยู่ตรงไหน:
· rem = "ผมอยู่ห่างจากประตูร้าน 24 เมตร" — อ้างจากจุดตั้งต้นจุดเดียว ที่ทุกคนใช้ร่วมกัน
ใครขยับก็ไม่กระทบ
· em = "ผมอยู่ห่างจากคนข้างหน้า 1.5 เท่าของช่วงแขนเขา" —
ถ้าคนข้างหน้าขยับ คุณขยับตาม และคนข้างหลังคุณก็ขยับต่อ ต่อกันเป็นทอด ๆ
เอาไปใช้ได้เลย: ระยะห่างและขนาดตัวอักษรทั่วไปให้ใช้ rem ·
เก็บ em ไว้ใช้กับของที่อยากให้โตตามตัวหนังสือของมันเอง เช่น padding ของปุ่ม —
ปุ่มตัวใหญ่ก็อยากได้ padding ใหญ่ตาม โดยไม่ต้องเขียนเพิ่ม
ที่ไม่เหมือนกัน: คนต่อคิวรู้ตัวว่าขยับตามคนข้างหน้า —
แต่ em ที่ซ้อนกันสามชั้นไม่มีอะไรบอกคุณเลย คุณเห็นในไฟล์ CSS แค่ 1.5em
เท่ากันทุกชั้น ต้องเปิด DevTools ดู computed value เท่านั้นถึงจะรู้ว่าจริง ๆ มันบานเป็น 45px แล้ว
อีกจุด: หลักกิโลอยู่นิ่งตลอด แต่รากของ rem ขยับได้ —
ถ้าผู้ใช้ตั้งขนาดตัวอักษรในเบราว์เซอร์ให้ใหญ่ขึ้น 16 จะกลายเป็น 20 แล้วทั้งเว็บโตตาม
ซึ่งเป็นข้อดี ไม่ใช่ข้อเสีย — คนสายตาไม่ดีถึงจะอ่านเว็บคุณได้
ส่วน % มีกับดักของตัวเองอยู่หนึ่งอัน ที่แทบไม่มีใครเตือน:
<style>
.frame { width: 200px; height: 300px; border: 2px dashed #adb5bd; }
.p { padding: 10%; background: #e5dbff; }
</style>
<div class="frame">
<div class="p">padding: 10% ได้ 20px ทุกด้าน — บนล่างก็คิดจากความกว้าง 200px</div>
</div>
พ่อกว้าง 200 สูง 300 · ถ้า padding-top: 10% คิดจากความสูง มันควรได้ 30px
แต่ผมวัด getComputedStyle() ได้ 20px ทั้งบนและซ้าย —
เพราะ % ของ padding กับ margin คิดจากความกว้างของพ่อเสมอ ทุกด้าน
ฟังดูไม่มีเหตุผล แต่มันคือกติกาที่ทำให้เทคนิคจัดสัดส่วนภาพ 16:9 ทำงานได้ —
และเป็นเหตุผลที่คนไม่ค่อยใช้ % กับระยะห่าง
7 · font-size · font-family · color · background
สี่ตัวนี้ (สไลด์หน้า 38–42) ตรงไปตรงมาที่สุดในบท ไม่มีกับดักซ่อน มีแค่สองอย่างที่ควรรู้:
font-familyเขียนเป็นรายการสำรองคั่นด้วยจุลภาค เบราว์เซอร์ไล่จากซ้ายไปขวา ตัวไหนมีในเครื่องก็ใช้ตัวนั้น — ตัวสุดท้ายควรเป็นชื่อกลุ่มอย่างsans-serifไว้กันเหนียว ชื่อฟอนต์ที่มีเว้นวรรคต้องใส่เครื่องหมายคำพูดbackground-colorกับbackground-imageอยู่คนละชั้น ไม่ได้แทนที่กัน — image อยู่บน color อยู่ล่าง ถ้ารูปโหลดไม่ขึ้นหรือมีส่วนโปร่งใส จะเห็นสีข้างล่างโผล่มา ใส่ทั้งคู่จึงเป็นเรื่องปกติ ไม่ใช่เขียนซ้ำซ้อน
<style>
.hero { font-family: "Noto Sans Thai", sans-serif;
font-size: 20px;
color: #ffffff;
background-color: #364fc7;
background-image: linear-gradient(135deg, #364fc7, #c2255c);
padding: 24px;
border-radius: 12px; }
.hero .small { font-size: 0.75rem; color: #dbe4ff; }
</style>
<div class="hero">ข้อความนี้ใช้ font-size · color · background-image พร้อมกัน
<div class="small">บรรทัดนี้ 0.75rem = 12px และสีจางลง</div>
</div>
ทำไมตัวอย่างนี้ใช้ linear-gradient() แทนรูป —
สไลด์หน้า 42 เขียน background-image: url("images/example-image.png");
ซึ่งถูกต้องทุกอย่าง แต่เว็บบทเรียนนี้ตั้งใจให้เปิดได้แม้ไม่มีเน็ต จึงไม่มีรูปจากภายนอกเลยสักรูป
linear-gradient() นับเป็น "รูป" ในสายตา CSS เต็มตัว — ใช้กับ background-image ได้ตรง ๆ
และไม่ต้องโหลดไฟล์อะไรเลย เวลาทำตัวอย่างหรือ mockup จึงสะดวกกว่ามาก
สไลด์หน้า 41 เขียน background-color ไว้สองบรรทัดในกฎเดียวกัน:
lightslategray แล้วตามด้วย #ffff00
แต่ภาพตัวอย่างข้าง ๆ เป็นสีเทา — ซึ่งเป็นสีของบรรทัดบน
ตามกติกาการทับซ้อน (cascade) ที่คุณเรียนไปในบทเรียน 59
เมื่อ property เดียวกันเขียนซ้ำในกฎเดียวกันและน้ำหนักเท่ากัน บรรทัดล่างชนะ
ผลจริงจึงต้องเป็นสีเหลือง
ผมอ่าน getComputedStyle() มาแล้ว ได้ rgb(255, 255, 0) — เหลือง
สไลด์คงตั้งใจให้สองบรรทัดนั้นเป็น "ตัวเลือกสองแบบ" มากกว่าจะให้ใส่พร้อมกัน แต่เขียนติดกันแบบนั้นแล้วมันไม่ใช่ตัวเลือก มันคือการทับ
<style>
.item { background-color: lightslategray;
background-color: #ffff00;
padding: 16px; }
</style>
<div class="item">สองบรรทัดในกฎเดียวกัน — บรรทัดล่างชนะ</div>
8 · display เบื้องต้น — กล่องนี้เป็นกล่องแบบไหน
ก่อนจะไปเรื่อง flexbox ในบทหน้า ต้องรู้ก่อนว่า display ตอบคำถามว่า
"กล่องนี้วางตัวยังไงเทียบกับกล่องรอบ ๆ" — สี่ค่าที่ต้องรู้ตอนนี้:
| ค่า | วางตัวยังไง | width/height ใช้ได้ไหม | ใครเป็นแบบนี้อยู่แล้ว |
|---|---|---|---|
block | ขึ้นบรรทัดใหม่เสมอ · กว้างเต็มพ่อ | ได้ | div · p · h1 |
inline | ไหลไปกับข้อความ ไม่ขึ้นบรรทัดใหม่ | ไม่ได้ — โดนเมิน | span · a · strong |
inline-block | ไหลไปกับข้อความ แต่ตั้งขนาดได้ | ได้ | button · input · select · textarea |
none | หายไปจากหน้าเว็บ ไม่กินที่เลย | ไม่มีความหมาย | ไม่มี |
ช่องขวาสุดของแถว inline-block มักถูกเขียนผิดว่า "ไม่มีใครเป็นโดยกำเนิด" —
ผมอ่าน getComputedStyle().display ใน Chrome มาแล้ว: button · input ·
select · textarea (ของจากบทฟอร์ม)
เป็น inline-block มาตั้งแต่แรก ส่วน img เป็น inline
ที่ต้องสั่งเองคือพวก div กับ span ธรรมดา ซึ่งไม่มีตัวไหนเกิดมาเป็น inline-block
<style>
.tag { width: 120px; height: 40px;
background: #ffd8a8; border: 1px solid #e8590c; }
.a { display: inline; }
.b { display: inline-block; }
.c { display: block; }
.d { display: none; }
</style>
<span class="tag a">inline</span>
<span class="tag b">inline-block</span>
<span class="tag c">block</span>
<span class="tag d">none</span>
ทั้งสี่กล่องสั่ง width: 120px; height: 40px; เหมือนกันเป๊ะ แต่ผลต่างกันสี่แบบ:
· inline — กล่องแรก แคบพอดีข้อความ ตัวเลข 120 กับ 40 ถูกเมินทั้งคู่
(ผมวัดกล่องนี้ในหน้านี้ได้ 39.89 × 20 คือความกว้างของข้อความ 37.89
บวก border 1px สองข้าง — กล่อง inline ยังรับ border กับ padding แนวนอนอยู่
ที่มันเมินคือ width กับ height เท่านั้น ·
ตัวเลขนี้ขึ้นกับฟอนต์ในเครื่องคุณ จะไม่ตรงเป๊ะก็ไม่แปลก — ที่ต้องตรงคือมันไม่ใช่ 120 × 40)
· inline-block — กล่องที่สอง อยู่บรรทัดเดียวกับตัวแรก และได้ขนาดตามสั่งจริง
(วัดขอบนอกได้ 122 × 42 คือ 120 + 1×2 กับ 40 + 1×2 ที่บวก border เข้ามา —
content-box จากหัวข้อ 4 กลับมาอีกครั้ง)
· block — กล่องที่สาม เด้งลงบรรทัดใหม่
· none — ไม่มีกล่องที่สี่ ทั้งที่เขียน <span> ไว้ใน HTML จริง ๆ
display: none ต่างจาก "ซ่อนไว้"
display: none ทำให้ element หายไปจากการจัดหน้าโดยสิ้นเชิง ไม่กินที่แม้แต่ pixel เดียว
ผมวัดของจริง: กล่องสูง 30px สามใบเรียงกัน ใบกลางสั่ง none → รวมสูง 60px ไม่ใช่ 90px
ที่ต้องระวังคือ โปรแกรมอ่านหน้าจอก็อ่านไม่เจอด้วย — ถ้าอยากซ่อนแค่ทางสายตาแต่ยังให้คนตาบอดเข้าถึงได้
none ไม่ใช่คำตอบ · และถ้าอยากให้มันหายแต่ยังกินที่เดิม ต้องใช้ visibility: hidden; แทน
- block-level element
- กล่องที่ขึ้นบรรทัดใหม่และกว้างเต็มพ่อ · ตั้ง
width/heightได้ · ถาม senior ได้ว่า “ตัวนี้เป็น block หรือ inline อยู่” เป็นคำถามแรกที่ควรถามเวลาwidthที่ใส่ไปแล้วไม่มีผล - inline element
- กล่องที่ไหลไปกับข้อความ · เมิน
widthและheight· พิมพ์บอก AI ได้ว่า “this span is inline so width has no effect — should it be inline-block?” การชี้สาเหตุมาด้วยทำให้ได้คำตอบที่ใช้ได้เลย - inline-block
- ลูกผสม — อยู่ในบรรทัดเดียวกับข้อความได้ แต่ตั้งขนาดได้เหมือน block · เป็นวิธีทำ badge หรือปุ่มเล็ก ๆ ที่แทรกในประโยค
- display: none
- เอา element ออกจากการจัดหน้าทั้งหมด · ต่างจาก
visibility: hiddenที่ยังกินที่เดิมอยู่ · ถาม senior ได้ว่า “ตรงนี้ควรใช้ display:none หรือ visibility:hidden” คำตอบขึ้นกับว่าอยากให้ที่ว่างยุบตามหรือไม่
9 · สรุปเป็นลำดับการตัดสินใจ
อยากเว้นที่...
|
+-- ระหว่างเนื้อหากับขอบกล่องตัวเอง? ----> padding
|
+-- ระหว่างกล่องนี้กับกล่องอื่น? --------> margin (ระวังยุบรวม)
| หรือ gap ถ้าพ่อเป็น flex/grid
|
+-- อยากได้เส้นกรอบด้วย? --------------> border (อย่าลืม style)
ตั้ง width/height แล้วผลไม่ตรง...
|
+-- ล้นออกนอกพ่อ? --------------------> box-sizing: border-box
|
+-- ไม่มีผลเลย? ----------------------> element เป็น inline อยู่
ทดสอบความเข้าใจ
1. .frame กว้าง 400px · ลูกข้างในเขียน width: 100%; padding: 16px; border: 2px solid black; โดยไม่ตั้ง box-sizing — กล่องลูกกว้างจริงเท่าไหร่?
ค่าเริ่มต้นของ CSS คือ box-sizing: content-box ซึ่งแปลว่า
width นับเฉพาะชั้น content ส่วน padding กับ border ไปบวกข้างนอก:
400 + 16×2 + 2×2 = 436
ผมวัดชุดเดียวกันด้วย getBoundingClientRect() ใน Chrome จริง (กรอบ 300 · padding 20 · border 4)
ได้ 348 ซึ่งตรงกับสูตร 300 + 40 + 8 เป๊ะ
ตัวเลือกที่ 1 คือกับดักตัวจริง เพราะเป็นสิ่งที่ทุกคนคาดหวังเวลาเขียน 100% —
แต่มันคือพฤติกรรมของ border-box ซึ่งต้องสั่งเอง ไม่ได้มาให้ฟรี
ตัวเลือกที่ 2 ผิดตรงที่คิดว่าเบราว์เซอร์ช่วยกันล้นให้ — มันไม่ช่วยอะไรเลย ปล่อยล้นเงียบ ๆ ไม่มี warning
ตัวเลือกที่ 4 สลับบทบาทของ padding กับ border ซึ่งกลับด้านกัน — ในโหมด content-box บวกทั้งคู่
จุดที่ต้องจำ: width: 100% ในค่าเริ่มต้นไม่ได้แปลว่า "เต็มพ่อพอดี"
แต่แปลว่า "content เต็มพ่อพอดี" — ทุกอย่างที่ห่ออยู่ข้างนอกจึงล้นออกไปหมด
2. div สองอันวางบนล่างติดกัน อันบนสั่ง margin-bottom: 40px อันล่างสั่ง margin-top: 24px — แล้วมีคนแก้ 24px เป็น 30px จะเกิดอะไรขึ้น?
ผมวัดของจริงใน Chrome ด้วย getBoundingClientRect():
ระยะจากขอบล่างกล่องบนถึงขอบบนกล่องล่าง = 40
เพราะ margin แนวตั้งที่มาชนกันยุบรวมเป็นก้อนเดียว แล้วใช้ max(40, 24) = 40
แก้ 24 เป็น 30 ก็ยังได้ max(40, 30) = 40 — ไม่ขยับ และต้องแก้จนเกิน 40 ถึงจะเห็นผล
ตัวเลือกที่ 1 คือสมมติฐานที่ทุกคนใช้ตอนแรก และเป็นเหตุผลที่คนนั่งงงอยู่นาน —
ถ้ามันบวกกันจริง แก้ทีเดียวก็จบ
ตัวเลือกที่ 3 สับสนกับเรื่อง cascade (บรรทัดล่างทับบรรทัดบน) ซึ่งเป็นคนละเรื่อง —
นี่เป็น property คนละตัวบน element คนละตัว ไม่มีอะไรทับอะไร
ตัวเลือกที่ 4 ผิดตรงที่คิดว่ากล่องล่างคุมฝ่ายเดียว — ทั้งคู่ส่งค่าเข้ามาแข่งกัน ตัวที่มากกว่าชนะ
ผมพิสูจน์อีกทางแล้วด้วย: เอาพ่อมาสั่ง display: flex; flex-direction: column;
ระยะเดียวกันกลายเป็น 64 ทันที เพราะข้างในกล่อง flex ไม่มีการยุบรวม
จุดที่ต้องจำ: ถ้าแก้ตัวเลข margin แล้วหน้าจอไม่ขยับ อย่าเพิ่มตัวเลขมั่ว —
ให้สงสัย margin collapsing ก่อน แล้วเปลี่ยนไปใช้ gap จบเรื่องเร็วกว่า
3. <html> เป็น 16px · .card ตั้ง font-size: 20px · ลูกใน .card ตั้ง font-size: 1.5em · ลูกของลูกตั้ง font-size: 1.5em อีกชั้น — ลูกของลูกได้กี่ px?
ผมอ่าน getComputedStyle().fontSize ของทั้งสามชั้นมาแล้ว:
20px → 30px → 45px
ชั้นแรก 1.5 × 20 = 30 · ชั้นสอง 1.5 × 30 = 45 —
ไม่ใช่ 1.5 × 20 อีกครั้ง นี่คือความหมายของคำว่า "ทบต้น"
ตัวเลือกที่ 1 กับ 3 เอาพฤติกรรมของ rem มาใส่ em —
rem ต่างหากที่อ้างจาก <html> เสมอ และในโจทย์นี้ 1.5rem จะได้ 24px (ผมวัดแล้วเช่นกัน)
ตัวเลือกที่ 2 คือคำตอบที่ถูกสำหรับชั้นเดียว — พอซ้อนชั้นที่สองมันก็ไม่ใช่แล้ว
ซึ่งคือกับดักตัวจริง เพราะคนที่ทดสอบแค่ชั้นเดียวจะเข้าใจผิดว่าตัวเองเข้าใจถูก
จุดที่ต้องจำ: ในไฟล์ CSS คุณเห็นแค่ 1.5em เหมือนกันทุกชั้น
ตัวเลขจริงจึงดูจากโค้ดไม่ออก ต้องเปิด DevTools อ่าน computed value —
ถ้าไม่อยากเจอปัญหานี้ ใช้ rem เป็นค่าเริ่มต้น แล้วใช้ em เฉพาะที่ตั้งใจ
4. <span> ตัวหนึ่งเขียน width: 200px; height: 60px; background: orange; แต่บนหน้าจอมันแคบพอดีข้อความ — สาเหตุคืออะไร?
span · a · strong เป็น display: inline มาแต่กำเนิด
และกล่อง inline ไม่รับ width กับ height — เบราว์เซอร์อ่านเจอแล้วเมินเงียบ ๆ
ผมวัดของจริงจากกล่องตัวอย่างในหัวข้อ 8 ของหน้านี้: span ที่สั่ง
width: 120px; height: 40px ได้ขนาดจริง 39.89 × 20 — เท่าข้อความ (บวก border 1px สองข้าง)
· พอเปลี่ยนเป็น display: inline-block ได้ 122 × 42 คือ 120 × 40 ตามสั่ง บวก border 1px สองข้าง
ตัวเลือกที่ 2 ไม่มีอยู่จริงใน CSS — element ไม่ได้เลือกหน่วยที่ตัวเองรับได้
ตัวเลือกที่ 3 สับสนกับ cascade เหมือนกัน — background กับ width เป็นคนละ property
ไม่มีทางทับกันไม่ว่าจะเรียงลำดับยังไง
ตัวเลือกที่ 4 ฟังดูน่าเชื่อเพราะเพิ่งเรียน box-sizing ไป แต่ box-sizing
แค่เปลี่ยนว่า width วัดถึงชั้นไหน ไม่ได้ทำให้ width เป็นศูนย์ —
และมันไม่ช่วยอะไรเลยถ้ากล่องยังเป็น inline อยู่
จุดที่ต้องจำ: เจอ width ที่ "ใส่แล้วไม่มีอะไรเกิดขึ้น" ให้เช็ค display ก่อนเสมอ
ก่อนจะไปสงสัยว่า selector ไม่โดนหรือค่าถูกทับ
ภารกิจนี้ไม่ได้ให้ "ลองดูว่าสวยไหม" — ให้วัดตัวเลขออกมาแล้วเทียบกับที่คุณทำนายไว้
ซึ่งคือวิธีพิสูจน์ของฝั่ง CSS (แทนที่ของฝั่ง Go ที่ใช้เทส)
สร้างไฟล์ card.html ไฟล์เดียวจบ ไม่ต้องมี CSS แยก
- วางโครง: ทำ
<div class="frame">ที่width: 360pxและมีborder: 2px dashed grayเพื่อให้เห็นขอบ
ข้างในใส่<div class="card">ที่มีหัวเรื่อง (<h3>) กับย่อหน้า (<p>) - ทำให้มันล้นก่อน: สั่ง
.cardว่าwidth: 100%; padding: 24px; border: 3px solid teal;
เขียนคำทำนายลงกระดาษก่อน ว่ากล่องจะกว้างกี่ px (ใช้สูตร content + padding×2 + border×2) - วัดของจริง: เปิดไฟล์ในเบราว์เซอร์ → คลิกขวาที่การ์ด → Inspect →
แท็บ Computed → เลื่อนลงล่างสุดจะเห็นภาพ box model
ที่บอกตัวเลขทั้งสี่ชั้น
ตัวเลขที่วัดได้ต้องตรงกับที่ทำนาย ถ้าไม่ตรง แปลว่าเข้าใจผิดตรงไหนสักที่ — หาให้เจอก่อนไปข้อถัดไป - แก้ให้พอดี: เติม
box-sizing: border-box;บรรทัดเดียว แล้ววัดซ้ำ — ต้องได้360พอดี และช่องว่างข้างในต้องหดลง
บันทึกตัวเลขก่อนแก้กับหลังแก้ไว้ทั้งคู่ นี่คือ "ผลเทส" ของคุณ - ทำกับดักที่สองให้เกิด: ใช้การ์ดใบเดิม (ที่มี
<h3>กับ<p>อยู่ข้างในแล้ว) เติมสองกฎนี้:h3 { margin-bottom: 32px; }กับp { margin-top: 16px; }
ทำนายก่อนว่าช่องว่างระหว่างh3กับpจะกว้างกี่ px — คิดจากกติกาในหัวข้อ 5 ไม่ใช่ 32 + 16 — แล้ววัดด้วย DevTools ยืนยัน
จากนั้นสั่ง.card { display: flex; flex-direction: column; }ทำนายตัวเลขใหม่ก่อน แล้วค่อยวัด — ตัวเลขต้องเปลี่ยน และต้องเปลี่ยนไปเป็นค่าที่คุณทายไว้ - ปิดท้าย: เปลี่ยน
padding: 24pxเป็นpadding: 1.5remแล้ววัดว่ายังได้24pxเท่าเดิมไหม (16 × 1.5)
แล้วลองเปลี่ยนเป็น1.5emดูว่าตัวเลขเปลี่ยนไหม และอธิบายให้ได้ว่าทำไม
เกณฑ์ว่าผ่าน: คุณมีตัวเลขที่วัดได้จริงครบทุกข้อ และทุกตัวตรงกับที่ทำนายไว้ล่วงหน้า
ถ้ามีข้อไหนที่ทำนายผิด นั่นคือข้อที่มีค่าที่สุดในภารกิจนี้ — กลับไปอ่านหัวข้อนั้นซ้ำ
แล้วถามมาได้เลยว่าทำไมมันไม่เป็นอย่างที่คิด ผมอยากรู้ว่าตรงไหนอธิบายยังไม่ชัดพอ
อ้างอิง
- MDN — The box model — อ่านหัวข้อ Parts of a box กับ The alternative CSS box model สองหัวข้อนี้ครอบคลุมหัวข้อ 1 และ 4 ของบทนี้ทั้งหมด และมีภาพ box model แบบทางการให้ดู
- MDN —
box-sizing— อ่านหัวข้อ Values เพื่อดูนิยามของcontent-boxกับborder-boxตรง ๆ แล้วเลื่อนลงไปดูตัวอย่างที่กดสลับสองโหมดเทียบกันได้ - MDN — Mastering margin collapsing — อ่านรายการสามข้อที่ขึ้นต้นว่า Adjacent siblings / No content separating parent and descendants / Empty blocks คือสามกรณีที่การยุบรวมเกิดขึ้น · หัวข้อ 5 ของบทนี้ครอบสองข้อแรก ข้อที่สามยังไม่ได้พูดถึง
- MDN — CSS values and units —
อ่านตารางในหัวข้อ Relative length units ซึ่งบอกชัดว่า
emกับremอ้างอิงจากอะไร (และเป็นที่มาของกล่องเตือนเรื่องสไลด์หน้า 46 ในบทนี้) - MDN —
display— อ่านหัวข้อ Outer display types ก็พอสำหรับตอนนี้ (blockกับinline) ส่วน Inner display types คือเรื่องของบท flexbox linear-gradient()— อ่านสามตัวอย่างแรก ก็ใช้ทำพื้นหลังสวย ๆ ได้แล้วโดยไม่ต้องมีไฟล์รูปเลย