🧰 ของที่ใช้ตรวจในบทนี้
Google Chrome (headless) — วัดขนาดจริงด้วย getBoundingClientRect()
CSS Flexible Box Layout Module Level 1 /* มาตรฐานที่เบราว์เซอร์ทำตาม */
กล่องที่เขียนว่า "ผลลัพธ์จริง" ทุกกล่องคือ <iframe>
ที่เอาโค้ดในกล่อง <pre> ข้างบนก้อนเดียวกันเป๊ะไปเรนเดอร์
ไม่ได้วาดรูปประกอบ — ถ้าอยากลองแก้ค่า ก๊อปโค้ดไปวางในไฟล์ .html ของคุณได้เลย
1 · ก่อนอื่น — ปัญหาที่ Flexbox มาแก้
ลองนึกถึงงานที่ง่ายที่สุดในโลกเว็บ: เอากล่องสามกล่องมาวางเรียงกันในแนวนอน
ฟังดูเหมือนควรจะง่าย แต่ถ้ายังไม่ใส่อะไรเลย <div> จะทำแบบนี้:
<style>
.box > div { background: #4338ca; color: #fff; padding: 8px 14px; border-radius: 6px; }
</style>
<div class="box">
<div>A</div>
<div>B</div>
<div>C</div>
</div>
สามกล่องซ้อนกันลงมาเป็นชั้น ๆ ไม่ใช่เรียงข้างกัน — เพราะ <div> เป็น
block ซึ่งมีนิสัยว่า "ฉันกินความกว้างเต็มบรรทัด แล้วคนถัดไปไปขึ้นบรรทัดใหม่"
ทีนี้เติมบรรทัดเดียวเข้าไปที่กล่องแม่ (ไม่ใช่ที่กล่องลูก):
<style>
.box { display: flex; }
.box > div { background: #4338ca; color: #fff; padding: 8px 14px; border-radius: 6px; }
</style>
<div class="box">
<div>A</div>
<div>B</div>
<div>C</div>
</div>
บรรทัดเดียวคือ display: flex; แล้วกล่องลูกทั้งสามยอมมายืนเรียงกันทันที
นี่คือหัวใจข้อแรกที่ต้องจำให้ขึ้นใจ: display: flex ใส่ที่กล่องแม่
แล้วมันไปเปลี่ยนพฤติกรรมของลูก — ไม่ใช่ของตัวมันเอง
ลองนึกถึงคนสามคนที่เดินเข้าไปในห้องแคบ ๆ ที่มีทางเดินเดียว
ไม่มีป้ายไม่มีเส้นบอกให้ยืนต่อกันข้าง ๆ · ทุกคนก็ได้แต่ยืนต่อกันลงมาเป็นแถวเดียว
คนละบรรทัด เพราะไม่มีใครบอกให้ยืนเคียงกัน — นั่นคือ <div> แบบ block
อย่างที่เห็นในกล่องผลลัพธ์ข้างบน
พอเจ้าของห้องขีดเส้นต่อคิวลงบนพื้น คนสามคนเดิมนั้นก็จัดแถวเองทันที
โดยที่คุณไม่ต้องไปบอกเป็นรายคนว่าใครยืนตรงไหน
display: flex ก็คือเส้นบนพื้น — คุณสั่งที่ห้อง ไม่ได้สั่งที่คน
แต่คนทุกคนในห้องเปลี่ยนพฤติกรรมตาม
ที่ไม่เหมือนกัน: คนต่อคิวจริง ๆ ไม่ยืดตัวให้กว้างขึ้นเพื่อกินที่ว่างที่เหลือในห้อง และไม่หดตัวลงเมื่อห้องแคบ — แต่ flex item ทำทั้งสองอย่าง (นั่นคือหัวข้อ 9 ของบทนี้) ถ้าเอาภาพ "คนต่อคิว" ไปใช้ต่อโดยไม่รู้ข้อนี้ คุณจะงงว่าทำไมกล่องกว้างไม่เท่าที่สั่ง
2 · แกนหลัก (main axis) กับแกนรอง (cross axis)
ทุกอย่างที่เหลือในบทนี้ตั้งอยู่บนแนวคิดเดียว และถ้าเข้าใจแนวคิดนี้ผิด จะผิดยาวไปทั้งบท:
กล่อง flex มีทิศทางอยู่สองทิศ ไม่ใช่ "แนวนอน" กับ "แนวตั้ง" แต่เป็น "ตามแถว" กับ "ขวางแถว"
- แกนหลัก — ทิศที่กล่องลูกเรียงต่อกันไป
- แกนรอง — ทิศที่ตั้งฉากกับแกนหลัก คือทิศ "ความหนา" ของแถว
สังเกตให้ดี: ภาพขวา แกนหลักชี้ลง ไม่ใช่ชี้ขวาแล้ว
และคำว่า "แกนหลัก" ไม่ได้ผูกกับ "แนวนอน" เลยแม้แต่นิดเดียว — มันผูกกับ
ทิศที่ของเรียงต่อกัน ซึ่งเปลี่ยนได้ด้วย flex-direction
สี่คำนี้คือคำที่ dev ทุกคนใช้เวลาพูดถึง layout — ใช้คำให้ตรง จะได้คำตอบที่ตรง
- flex container
- กล่องแม่ที่ถูกสั่ง
display: flex· มันคือตัวที่ออกคำสั่ง · ประโยคที่ใช้ได้เลย: "ตัว container มันเป็น flex อยู่แล้วหรือยังครับ ผมจะได้รู้ว่าต้องใส่display: flexเพิ่มไหม" - flex item
- ลูกชั้นแรกของ flex container เท่านั้น — ลูกของลูกไม่นับ ·
ประโยคที่ใช้ได้เลย: "อันนี้มันไม่ใช่ flex item ครับ มันเป็นหลานอีกชั้นหนึ่ง
flex: 1เลยไม่ทำงาน" — นี่คือสาเหตุอันดับหนึ่งที่flex"ใส่แล้วไม่เห็นผล" - main axis (แกนหลัก)
- ทิศที่ flex item เรียงต่อกันไป · กำหนดด้วย
flex-direction· พิมพ์บอก AI ได้ว่า: "container นี้ flex-direction เป็น column อยู่ แกนหลักคือแนวตั้ง ช่วยจัดของให้ชิดล่างให้หน่อย" — บอกแบบนี้ AI จะเลือก property ถูกตัวตั้งแต่รอบแรก - cross axis (แกนรอง)
- ทิศที่ตั้งฉากกับแกนหลักเสมอ ·
row→ แกนรองคือแนวตั้ง ·column→ แกนรองคือแนวนอน · ประโยคที่ใช้ได้เลย: "ผมอยากจัดกลางตามแกนรอง ต้องใช้align-itemsใช่ไหมครับ"
3 · flex-direction — ตัวที่ย้ายแกนหลัก
สไลด์หน้า 49 บอกไว้สั้น ๆ ว่า "Establishes the main-axis direction of content" ซึ่งแปลตรงตัวว่า "เป็นตัวตั้งว่าแกนหลักชี้ไปทางไหน" · มีสี่ค่าให้เลือก:
<style>
.box { display: flex; gap: 6px; background: #eef2ff; border: 2px dashed #a5b4fc;
padding: 6px; margin-bottom: 10px; }
.box > div { background: #4338ca; color: #fff; padding: 6px 12px; border-radius: 6px; }
.cap { margin: 0 0 3px; font: 13px monospace; color: #475569; }
</style>
<p class="cap">row (ค่าเริ่มต้น)</p>
<div class="box" style="flex-direction: row"><div>1</div><div>2</div><div>3</div></div>
<p class="cap">row-reverse</p>
<div class="box" style="flex-direction: row-reverse"><div>1</div><div>2</div><div>3</div></div>
<p class="cap">column</p>
<div class="box" style="flex-direction: column"><div>1</div><div>2</div><div>3</div></div>
<p class="cap">column-reverse</p>
<div class="box" style="flex-direction: column-reverse"><div>1</div><div>2</div><div>3</div></div>
row (ค่าเริ่มต้น)
row-reverse
column
column-reverse
สังเกตอย่างหนึ่งในสองกล่องล่าง: พอเป็น column แล้วกล่อง 1 2 3
กว้างเต็มแถบไปเลย ทั้งที่โค้ดไม่ได้สั่งความกว้างไว้เลยสักบรรทัด
นั่นไม่ใช่บั๊ก — มันคือ align-items ที่ทำงานเป็นค่าเริ่มต้นอยู่
และพอแกนรองหมุนมาเป็นแนวนอน "การยืดเต็มแกนรอง" ก็เลยกลายเป็น "ยืดเต็มความกว้าง"
รายละเอียดของเรื่องนี้อยู่ในหัวข้อ 5 · ตอนนี้แค่จำไว้ว่าคุณเห็นมันตั้งแต่ยังไม่ได้สั่งอะไร
| ค่า | แกนหลักชี้ไปทาง | ใช้ตอนไหน |
|---|---|---|
row (ค่าเริ่มต้น) | ซ้าย → ขวา | navbar · แถวปุ่ม · แถวการ์ด |
row-reverse | ขวา → ซ้าย | อยากให้ของชิ้นแรกใน HTML ไปโผล่ขวาสุด |
column | บน → ล่าง | ฟอร์ม · sidebar · การ์ดที่มีหัว-เนื้อ-ท้าย |
column-reverse | ล่าง → บน | กล่องแชตที่ข้อความใหม่อยู่ล่างสุด |
reverse สลับแค่ที่ตา ไม่ได้สลับที่หู
row-reverse เปลี่ยนแค่ลำดับที่วาดบนจอ
แต่ลำดับใน HTML ยังเหมือนเดิม — โปรแกรมอ่านหน้าจอ (screen reader) และการกด
Tab ยังไล่ตามลำดับใน HTML อยู่ดี
ผลคือคนที่ใช้คีย์บอร์ดจะเห็น focus กระโดดจากขวาไปซ้ายแบบงง ๆ
ถ้าลำดับสำคัญ ให้แก้ที่ HTML ไม่ใช่ที่ CSS
4 · justify-content — จัดตำแหน่งตามแกนหลัก
สไลด์หน้า 51 เขียนว่า "Defines alignment along the main-axis" และเสริมว่ามันคือตัวที่
"ช่วยกระจายพื้นที่ว่างที่เหลือในบรรทัด"
พูดเป็นภาษาคน: เมื่อของทั้งหมดวางเสร็จแล้วยังมีที่ว่างเหลือ ที่ว่างนั้นไปอยู่ตรงไหน
<style>
.box { display: flex; gap: 6px; background: #eef2ff; border: 2px dashed #a5b4fc;
padding: 6px; margin-bottom: 10px; }
.box > div { background: #4338ca; color: #fff; padding: 6px 12px; border-radius: 6px; }
.cap { margin: 0 0 3px; font: 13px monospace; color: #475569; }
</style>
<p class="cap">flex-start (ค่าเริ่มต้น)</p>
<div class="box" style="justify-content: flex-start"><div>1</div><div>2</div><div>3</div></div>
<p class="cap">flex-end</p>
<div class="box" style="justify-content: flex-end"><div>1</div><div>2</div><div>3</div></div>
<p class="cap">center</p>
<div class="box" style="justify-content: center"><div>1</div><div>2</div><div>3</div></div>
<p class="cap">space-between</p>
<div class="box" style="justify-content: space-between"><div>1</div><div>2</div><div>3</div></div>
<p class="cap">space-around</p>
<div class="box" style="justify-content: space-around"><div>1</div><div>2</div><div>3</div></div>
<p class="cap">space-evenly</p>
<div class="box" style="justify-content: space-evenly"><div>1</div><div>2</div><div>3</div></div>
flex-start (ค่าเริ่มต้น)
flex-end
center
space-between
space-around
space-evenly
| ค่า | ที่ว่างไปกองอยู่ตรงไหน |
|---|---|
flex-start (ค่าเริ่มต้น) | ท้ายแถวทั้งหมด — ของจึงชิดหัวแถว |
flex-end | หัวแถวทั้งหมด — ของจึงชิดท้ายแถว |
center | แบ่งครึ่งไปหัวแถวกับท้ายแถวเท่ากัน |
space-between | แทรกอยู่ระหว่างของเท่านั้น หัวกับท้ายไม่มีเลย |
space-around | ทุกกล่องได้ที่ว่างรอบตัวเท่ากัน → ขอบนอกจึงดูบางกว่าช่องกลางครึ่งหนึ่ง |
space-evenly | ทุกช่องเท่ากันหมด รวมช่องริมสองข้าง |
space-around กับ space-evenly คือคู่ที่คนสับสนบ่อยที่สุด
เลื่อนกลับไปดูกล่องผลลัพธ์สองอันล่างสุดเทียบกัน จะเห็นว่า space-around
ขอบซ้ายขวาบางกว่าช่องตรงกลาง เพราะแต่ละกล่องได้ที่ว่าง "ครึ่งหนึ่งต่อข้าง"
แล้วช่องกลางเป็นครึ่งของสองกล่องมาบวกกัน
5 · align-items — จัดตำแหน่งตามแกนรอง
สไลด์หน้า 52 สรุปไว้ดีมากในบรรทัดเดียว: "Like justify-content in cross-axis version"
คือมันคือของอย่างเดียวกัน แค่คนละแกน · เริ่มจากค่าเริ่มต้นก่อน ซึ่งเป็นค่าที่คนไม่ค่อยรู้ว่ามันทำอะไร:
<style>
.box { display: flex; gap: 6px; height: 80px; background: #eef2ff;
border: 2px dashed #a5b4fc; padding: 6px; margin-bottom: 10px; }
.box > div { background: #4338ca; color: #fff; padding: 6px 12px; border-radius: 6px; }
.cap { margin: 0 0 3px; font: 13px monospace; color: #475569; }
</style>
<p class="cap">stretch (ค่าเริ่มต้น) — กล่องลูกยืดเต็มความสูงเอง</p>
<div class="box" style="align-items: stretch"><div>1</div><div>2</div><div>3</div></div>
<p class="cap">flex-start</p>
<div class="box" style="align-items: flex-start"><div>1</div><div>2</div><div>3</div></div>
stretch (ค่าเริ่มต้น) — กล่องลูกยืดเต็มความสูงเอง
flex-start
กล่องแม่สูง 80px ส่วนกล่องลูกไม่ได้ตั้งความสูงไว้เลย
แต่ในกล่องบน ลูกทั้งสามสูงเต็ม 80px — นั่นคือ stretch ทำงานอยู่
โดยที่คุณไม่ได้สั่ง เพราะมันคือพฤติกรรมเริ่มต้น
พอสั่ง flex-start ลูกถึงจะยุบลงมาเหลือเท่าเนื้อหาของตัวเอง
ที่เหลืออีกสามค่า:
<style>
.box { display: flex; gap: 6px; height: 80px; background: #eef2ff;
border: 2px dashed #a5b4fc; padding: 6px; margin-bottom: 10px; }
.box > div { background: #4338ca; color: #fff; padding: 6px 12px; border-radius: 6px; }
.big { font-size: 28px; }
.cap { margin: 0 0 3px; font: 13px monospace; color: #475569; }
</style>
<p class="cap">center</p>
<div class="box" style="align-items: center"><div>1</div><div class="big">2</div><div>3</div></div>
<p class="cap">flex-end</p>
<div class="box" style="align-items: flex-end"><div>1</div><div class="big">2</div><div>3</div></div>
<p class="cap">baseline — เส้นฐานตัวอักษรตรงกัน ไม่ใช่ขอบกล่องตรงกัน</p>
<div class="box" style="align-items: baseline"><div>1</div><div class="big">2</div><div>3</div></div>
center
flex-end
baseline — เส้นฐานตัวอักษรตรงกัน ไม่ใช่ขอบกล่องตรงกัน
รายละเอียดที่ทำให้คุณดูรู้จริง: ค่าเริ่มต้นจริง ๆ ของ align-items
ตามมาตรฐานคือคำว่า normal ไม่ใช่ stretch —
แต่ normal เมื่ออยู่ใน flex container ให้ผลเหมือน stretch
เวลาคุยกันเร็ว ๆ พูดว่า "ค่าเริ่มต้นคือ stretch" ได้เลย ไม่มีใครติด
แต่ถ้าเปิด DevTools แล้วเห็นคำว่า normal จะได้ไม่ตกใจว่าใครไปแก้
6 · กับดักใหญ่ของบทนี้ — ค่าเดิมเป๊ะ แต่ row กับ column ให้ผลคนละเรื่อง
ถึงตรงนี้คนส่วนใหญ่จะจำแบบนี้: "justify-content = ซ้ายขวา · align-items = บนล่าง"
ซึ่งถูกครึ่งเดียว และครึ่งที่ผิดคือครึ่งที่จะกัดคุณตอนดึกที่สุด
ทายก่อนดูผล — ผมเอา CSS ก้อนนี้ไปใส่กล่องสองกล่องที่มีลูกเหมือนกันเป๊ะ
ต่างกันแค่กล่องขวาเติม flex-direction: column เข้าไปหนึ่งบรรทัด
.box { /* ทั้งสองกล่องใช้ก้อนนี้ร่วมกัน — flex-direction เป็น row ตามค่าเริ่มต้น */
display: flex;
justify-content: flex-end;
align-items: center;
}
.col { /* กล่องขวาได้คลาสนี้เพิ่มมาคลาสเดียว */
flex-direction: column;
}
กล่อง 1 2 3 ในกล่องขวาจะไปกองอยู่ตรงไหน?
ก. ชิดขวา และอยู่กลางแนวตั้ง — เหมือนกล่องซ้ายทุกประการ
ข. ชิดล่าง และอยู่กลางแนวนอน
ค. ชิดขวาล่าง มุมขวาล่างพอดี
ง. อยู่กลางกล่องทั้งแนวตั้งและแนวนอน
คำตอบคือ ข้อ ข. — ชิดล่าง และอยู่กลางแนวนอน
ทั้งที่ค่า justify-content กับ align-items
ไม่ได้ถูกแตะเลยแม้แต่ตัวเดียว
เหตุผลอยู่ที่ประโยคเดียว: justify-content ทำงานตามแกนหลักเสมอ
และ flex-direction: column เพิ่งหมุนแกนหลักให้ชี้ลง
"ชิดท้ายแถว" จึงกลายเป็น "ชิดล่าง" · ส่วน align-items: center
ที่เคยหมายถึงกลางแนวตั้ง ก็กลายเป็นกลางแนวนอน เพราะแกนรองหมุนตามไปด้วย
ดูของจริงเทียบกัน — โค้ดชุดเดียว ต่างกันแค่คลาส col:
<style>
.box { display: flex; justify-content: flex-end; align-items: center;
gap: 8px; height: 180px; background: #eef2ff;
border: 2px dashed #a5b4fc; padding: 8px; }
.col { flex-direction: column; }
.box > div { background: #4338ca; color: #fff; padding: 8px 14px; border-radius: 6px; }
.pair { display: flex; gap: 12px; }
.pair > div { flex: 1; }
.cap { margin: 0 0 4px; font: 13px monospace; color: #475569; }
</style>
<div class="pair">
<div>
<p class="cap">flex-direction: row (ค่าเริ่มต้น)</p>
<div class="box"><div>1</div><div>2</div><div>3</div></div>
</div>
<div>
<p class="cap">flex-direction: column</p>
<div class="box col"><div>1</div><div>2</div><div>3</div></div>
</div>
</div>
flex-direction: row (ค่าเริ่มต้น)
flex-direction: column
นึกถึงสายพานลำเลียงกระเป๋าที่สนามบิน · คุณเป็นคนคุมสายพาน และมีปุ่มอยู่สองปุ่ม:
· ปุ่มแรก "ดันของไปกองที่ปลายสายพาน"
· ปุ่มที่สอง "จัดของให้อยู่กลางความกว้างของสายพาน"
ทีนี้มีคนมาหมุนสายพานทั้งเส้น 90 องศา จากแนวนอนเป็นแนวดิ่ง
ปุ่มทั้งสองปุ่มยังทำงานเหมือนเดิมทุกอย่าง คุณไม่ได้กดอะไรเพิ่ม —
แต่ "ปลายสายพาน" ที่เคยอยู่ทางขวา ตอนนี้อยู่ข้างล่าง
และ "ความกว้างของสายพาน" ที่เคยวัดจากบนลงล่าง ตอนนี้วัดจากซ้ายไปขวา
justify-content คือปุ่มแรก · align-items คือปุ่มที่สอง ·
flex-direction คือคนที่มาหมุนสายพาน
ที่ไม่เหมือนกัน: กระเป๋าบนสายพานจริงไม่ยืดออกจนเต็มความกว้างสายพาน
แต่ flex item ทำแบบนั้นเป็นค่าเริ่มต้น (stretch ในหัวข้อ 5) ·
และสายพานจริงมีเส้นเดียว ส่วน flex container ขึ้นบรรทัดใหม่ได้ถ้าสั่ง flex-wrap
- justify-content
- จัดตำแหน่งตามแกนหลัก · วิธีจำที่ไม่หลอกคุณ: justify = ไปตามแถว
ไม่ใช่ justify = แนวนอน · ประโยคที่ใช้ได้เลย:
"ตรงนี้ผมอยากได้ชิดท้ายแถวครับ ซึ่ง container เป็น column อยู่ ก็คือชิดล่าง —
ใช้
justify-content: flex-endถูกไหมครับ" - align-items
- จัดตำแหน่งตามแกนรอง คือทิศที่ขวางแถว · ค่าเริ่มต้นคือยืดเต็ม (
stretch) · ประโยคที่ใช้ได้เลย: "กล่องมันสูงไม่เท่ากันเพราะalign-itemsยังเป็น stretch อยู่หรือเปล่าครับ" - จัดกลางทั้งสองแกน (centering)
- งานที่คนถามกันมากที่สุดในโลก CSS — คำตอบคือ
display: flex; justify-content: center; align-items: center;ซึ่งใช้ได้เหมือนกันทั้ง row และ column เพราะcenterของทั้งสองแกนคือจุดเดียวกัน · พิมพ์บอก AI ได้ว่า: "ขอจัดกลางทั้งสองแกนด้วย flexbox" — สั้นและไม่กำกวม
7 · gap · row-gap · column-gap — ระยะห่างระหว่างกล่อง
สมัยก่อนถ้าอยากให้กล่องมีระยะห่างกัน ต้องไปใส่ margin ที่กล่องลูกทีละอัน
แล้วมานั่งลบ margin ของอันสุดท้ายทิ้งเพราะมันเกินมา · gap มาแก้เรื่องนี้ทั้งหมด:
สั่งครั้งเดียวที่กล่องแม่ ได้ช่องว่างระหว่างกล่องเท่านั้น ขอบนอกไม่โดน
<style>
.box { display: flex; flex-wrap: wrap; width: 280px; gap: 8px 16px;
background: #eef2ff; border: 2px dashed #a5b4fc; padding: 8px; }
.box > div { width: 100px; text-align: center; background: #4338ca;
color: #fff; padding: 8px 0; border-radius: 6px; }
</style>
<div class="box"><div>1</div><div>2</div><div>3</div><div>4</div></div>
โค้ดข้างบนเขียนว่า gap: 8px 16px; ซึ่งเป็นรูปย่อของสองค่าพร้อมกัน
ลำดับคือ แถว ก่อน คอลัมน์:
| เขียนแบบนี้ | เท่ากับ | อ่านว่า |
|---|---|---|
gap: 8px; | row-gap: 8px; column-gap: 8px; | ห่างเท่ากันทุกทิศ |
gap: 8px 16px; | row-gap: 8px; column-gap: 16px; | ห่างระหว่างบรรทัด 8 · ระหว่างกล่องในบรรทัด 16 |
row-gap: 8px; | — | ตั้งเฉพาะระยะห่างระหว่างบรรทัด |
column-gap: 16px; | — | ตั้งเฉพาะระยะห่างระหว่างกล่องในบรรทัดเดียวกัน |
row-gap ไม่ได้แปลว่า "ระยะห่างของแถวที่เรียงแนวนอน"
ชื่อมันหลอก · row-gap คือช่องว่างที่คั่นระหว่างแถว
ซึ่งวัดกันในแนวตั้ง (เมื่อ flex-direction: row)
ในกล่องผลลัพธ์ข้างบน ช่องว่างแนวตั้งระหว่างสองบรรทัดคือ 8px
ส่วนช่องว่างแนวนอนระหว่างกล่อง 1 กับ 2 คือ 16px — ผมวัดจริงได้ตามนี้เป๊ะ
ถ้าจำไม่ได้ ให้ใช้ gap: 8px; ค่าเดียวไปก่อน แล้วค่อยแยกทีหลังตอนต้องการจริง ๆ
8 · flex-wrap — ยอมขึ้นบรรทัดใหม่ไหม
สไลด์หน้า 50 บอกว่า "By default, items will fit onto one line" — ประโยคนี้สำคัญกว่าที่ตาเห็น เพราะคำว่า "fit onto one line" แปลว่า เบราว์เซอร์จะยัดทุกอย่างลงหนึ่งบรรทัดให้ได้ ไม่ว่าจะต้องบีบแค่ไหน
<style>
.box { display: flex; width: 260px; gap: 8px; background: #eef2ff;
border: 2px dashed #a5b4fc; padding: 8px; margin-bottom: 10px; }
.box > div { width: 100px; text-align: center; background: #4338ca;
color: #fff; padding: 8px 0; border-radius: 6px; }
.cap { margin: 0 0 4px; font: 13px monospace; color: #475569; }
</style>
<p class="cap">nowrap (ค่าเริ่มต้น) — 4 กล่อง กล่องละ 100px ในที่ว่าง 260px</p>
<div class="box" style="flex-wrap: nowrap"><div>1</div><div>2</div><div>3</div><div>4</div></div>
<p class="cap">wrap</p>
<div class="box" style="flex-wrap: wrap"><div>1</div><div>2</div><div>3</div><div>4</div></div>
nowrap (ค่าเริ่มต้น) — 4 กล่อง กล่องละ 100px ในที่ว่าง 260px
wrap
กล่องบนคือค่าเริ่มต้น nowrap — ผมสั่งให้ทุกกล่องกว้าง 100px ทั้งสี่กล่อง
แต่ที่ว่างมีแค่ 260px
ผลจริงที่วัดได้: แต่ละกล่องเหลือ 59px ซึ่งมาจาก
(260 − 8×3) ÷ 4 = 59 — คือเบราว์เซอร์บีบให้ทุกกล่องเท่ากัน
แทนที่จะปล่อยให้ล้น
พอเติม flex-wrap: wrap; เข้าไป กล่องได้ 100px ตามที่สั่งจริง ๆ
แล้วกล่องที่ไม่พอก็ตกไปบรรทัดใหม่
width ของคุณ "ไม่ทำงาน"
ถ้าวันไหนคุณตั้ง width: 300px ให้การ์ด แล้วเปิดดูได้ 180px
ให้เดาข้อนี้ก่อนเป็นข้อแรก: มันอยู่ใน flex container ที่เป็น nowrap
แล้วโดนบีบ ไม่ใช่ CSS ของคุณเขียนผิด
ทางแก้มีสามทาง เลือกตามเจตนา: เติม flex-wrap: wrap ที่แม่ ·
เติม flex-shrink: 0 ที่ลูกที่ห้ามหด · หรือใช้ min-width กันไม่ให้หดต่ำกว่าที่รับได้
9 · flex-grow · flex-shrink · flex-basis — และ flex: 1 ย่อมาจากอะไร
สามตัวนี้ใส่ที่กล่องลูก (ต่างจากทุกตัวก่อนหน้าที่ใส่ที่แม่) และมันตอบคำถามคนละข้อ:
| property | ตอบคำถามว่า | ค่าเริ่มต้น |
|---|---|---|
flex-basis | กล่องนี้ออกตัวที่ขนาดเท่าไหร่ ก่อนจะเริ่มแบ่งที่ว่าง | auto (= เท่าเนื้อหา) |
flex-grow | ถ้ามีที่ว่างเหลือ กล่องนี้ขอรับไปกี่ส่วน | 0 (= ไม่รับ ไม่โต) |
flex-shrink | ถ้าที่ไม่พอ กล่องนี้ยอมหดกี่ส่วน | 1 (= ยอมหด) |
9.1 · flex-grow — แบ่ง "ที่ว่างที่เหลือ" ไม่ใช่แบ่งความกว้างทั้งหมด
<style>
.box { display: flex; gap: 8px; background: #eef2ff; border: 2px dashed #a5b4fc;
padding: 8px; margin-bottom: 10px; }
.box > div { text-align: center; background: #4338ca; color: #fff;
padding: 8px 0; border-radius: 6px; }
.cap { margin: 0 0 4px; font: 13px monospace; color: #475569; }
</style>
<p class="cap">ทุกกล่อง flex-grow: 1</p>
<div class="box">
<div style="flex-grow: 1">1</div>
<div style="flex-grow: 1">1</div>
<div style="flex-grow: 1">1</div>
</div>
<p class="cap">กล่องกลาง flex-grow: 2</p>
<div class="box">
<div style="flex-grow: 1">1</div>
<div style="flex-grow: 2">2</div>
<div style="flex-grow: 1">1</div>
</div>
ทุกกล่อง flex-grow: 1
กล่องกลาง flex-grow: 2
กล่องบนคือ 1 : 1 : 1 ได้เท่ากันสามส่วน · กล่องล่างคือ 1 : 2 : 1
ตรงกับรูปในสไลด์หน้า 54 เป๊ะ
แต่ระวังคำว่า "เป็น 2 เท่า" — flex-grow: 2 ไม่ได้แปลว่า
"กว้างเป็นสองเท่าของเพื่อน" มันแปลว่า "ได้ส่วนแบ่งจากที่ว่างที่เหลือ เป็นสองเท่า"
ในกล่องข้างบนเนื้อหาแต่ละกล่องเล็กมาก ที่ว่างที่เหลือจึงเกือบเท่ากับความกว้างทั้งหมด
ผลเลยดูเหมือนสองเท่าพอดี · แต่พอเนื้อหายาวไม่เท่ากันเมื่อไหร่ ภาพจะเปลี่ยนทันที —
ซึ่งคือเรื่องของหัวข้อ 9.3
9.2 · flex-shrink — ใครยอมหด ใครไม่ยอม
<style>
.box { display: flex; width: 300px; gap: 8px; background: #eef2ff;
border: 2px dashed #a5b4fc; padding: 8px; margin-bottom: 10px; }
.box > div { width: 200px; text-align: center; background: #4338ca;
color: #fff; padding: 8px 0; border-radius: 6px; }
.cap { margin: 0 0 4px; font: 13px monospace; color: #475569; }
</style>
<p class="cap">ทั้งคู่ flex-shrink: 1 (ค่าเริ่มต้น) — ยอมหดเท่ากัน</p>
<div class="box">
<div>shrink: 1</div>
<div>shrink: 1</div>
</div>
<p class="cap">กล่องซ้าย flex-shrink: 0 — ไม่ยอมหด อีกกล่องเลยรับหดคนเดียว</p>
<div class="box">
<div style="flex-shrink: 0">shrink: 0</div>
<div>shrink: 1</div>
</div>
ทั้งคู่ flex-shrink: 1 (ค่าเริ่มต้น) — ยอมหดเท่ากัน
กล่องซ้าย flex-shrink: 0 — ไม่ยอมหด อีกกล่องเลยรับหดคนเดียว
กล่องแม่กว้าง 300px ส่วนลูกสองกล่องขอ 200px คนละกล่อง — รวมเป็น
400px ซึ่งเกินมา
· กล่องบน ทั้งคู่ flex-shrink: 1 (ค่าเริ่มต้น) →
วัดได้กล่องละ 146px เท่ากัน คือ (300 − 8) ÷ 2 ทั้งคู่ยอมหดเท่า ๆ กัน
· กล่องล่าง ซ้ายสั่ง flex-shrink: 0 →
ซ้ายได้ 200px เต็มตามที่ขอ ส่วนขวาเหลือ 92px คือ 300 − 8 − 200
รับภาระหดไปคนเดียวทั้งหมด
สไลด์หน้า 55 มีหมายเหตุตัวเล็กว่า "Negative number is invalid" —
แปลว่า ใส่ค่าติดลบไม่ได้ ทั้ง flex-grow และ flex-shrink
ถ้าใส่ -1 เบราว์เซอร์จะทิ้งบรรทัดนั้นทั้งบรรทัด
แล้วกลับไปใช้ค่าเริ่มต้น · ไม่มี error ขึ้นใน Console ให้เห็นเลย
ที่เดียวที่จะเห็นคือแผง Styles ใน DevTools ซึ่งจะขีดฆ่าบรรทัดนั้นทิ้งไว้ให้ดู
ถ้าไม่ได้เปิดดูก็จะไม่รู้เลยว่าบรรทัดที่เขียนไปไม่ได้ทำงาน
แล้วถ้าที่ไม่พอจริง ๆ มันหดได้ไม่จำกัดเลยหรือ? ลองดู:
<style>
.box { display: flex; width: 240px; gap: 8px; background: #eef2ff;
border: 2px dashed #a5b4fc; padding: 8px; }
.box > div { background: #4338ca; color: #fff; padding: 8px 12px; border-radius: 6px; }
</style>
<div class="box">
<div>Loooooooooooooooooong</div>
<div>BBB</div>
</div>
ไม่ได้ · กล่องซ้ายหดลงไปได้ถึงระดับหนึ่งแล้วหยุด — คือหยุดที่ความกว้างของคำที่
ตัดบรรทัดไม่ได้ ("Loooo…" เป็นคำเดียวยาว ๆ ที่ไม่มีที่ให้ตัด)
พอหดต่อไม่ได้ ทั้งแถวก็เลยล้นทะลุกรอบเส้นประออกไป
ซึ่งเป็นภาพที่คุณจะเจอบ่อยมากเวลาเอา URL ยาว ๆ หรือชื่อไฟล์ยาว ๆ ไปใส่ในการ์ด
9.3 · flex-basis และคำตอบว่า flex: 1 คืออะไร
สไลด์หน้า 56 บอกว่า flex-basis คือ
"ขนาดตั้งต้นของกล่องก่อนที่ Flex Container จะคำนวณพื้นที่ที่เหลือ" — ตรงตัวเลย
<style>
.box { display: flex; width: 400px; gap: 8px; background: #eef2ff;
border: 2px dashed #a5b4fc; padding: 8px; margin-bottom: 10px; }
.box > div { text-align: center; background: #4338ca; color: #fff;
padding: 8px 0; border-radius: 6px; }
.cap { margin: 0 0 4px; font: 13px monospace; color: #475569; }
</style>
<p class="cap">กล่องซ้าย flex-basis: 150px + flex-grow: 1</p>
<div class="box">
<div style="flex-basis: 150px; flex-grow: 1">basis 150px</div>
<div style="flex-grow: 1">grow 1</div>
</div>
<p class="cap">กล่องซ้าย flex-basis: 50% + flex-grow: 1</p>
<div class="box">
<div style="flex-basis: 50%; flex-grow: 1">basis 50%</div>
<div style="flex-grow: 1">grow 1</div>
</div>
กล่องซ้าย flex-basis: 150px + flex-grow: 1
กล่องซ้าย flex-basis: 50% + flex-grow: 1
กล่องซ้ายในทั้งสองแถวกว้างกว่ากล่องขวาชัดเจน ทั้งที่ทั้งคู่มี flex-grow: 1 เหมือนกัน
เพราะกล่องซ้ายออกตัวจาก 150px (แถวบน) และ 50% ของกล่องแม่ (แถวล่าง)
ก่อนที่จะเริ่มแบ่งที่ว่างที่เหลือกัน
ทีนี้มาถึงบรรทัดที่คุณจะพิมพ์บ่อยที่สุดในชีวิต — flex: 1
ทายก่อนดูผล — กล่องแม่กว้าง 300px · gap: 8px ·
ลูกสองกล่อง เนื้อหายาวไม่เท่ากัน ("สั้น" กับ "ยาวกว่ามาก")
ผมทำสองรอบ รอบแรกใส่ flex-grow: 1 ให้ลูกทั้งคู่
รอบสองเปลี่ยนเป็น flex: 1 ให้ลูกทั้งคู่
สองรอบนี้ให้ผลเหมือนกันหรือต่างกัน แล้วต่างยังไง?
ต่างกัน และต่างแบบเห็นชัดด้วยตาเปล่า — นี่คือตัวเลขที่ผมวัดจริงจากเบราว์เซอร์:
| เขียนแบบ | กล่อง "สั้น" | กล่อง "ยาวกว่ามาก" |
|---|---|---|
flex-grow: 1 | 116px | 176px |
flex: 1 | 146px | 146px |
flex: 1 ได้ 146px เท่ากันเป๊ะ ซึ่งคือ (300 − 8) ÷ 2
— ตัวเลขที่ไม่ขึ้นกับเนื้อหาเลย
ส่วน flex-grow: 1 ได้ไม่เท่ากัน เพราะแต่ละกล่องออกตัวจากขนาดเนื้อหาของตัวเอง
ก่อน แล้วค่อยเอาที่ว่างที่เหลือมาหารสอง — กล่องที่เนื้อหายาวกว่าจึงยังกว้างกว่าอยู่ดี
(ตัวเลข 116 กับ 176 ขึ้นกับฟอนต์ในเครื่องคุณ จะขยับได้นิดหน่อย
แต่ข้อที่ไม่ขยับคือมันไม่เท่ากัน)
<style>
.box { display: flex; width: 300px; gap: 8px; background: #eef2ff;
border: 2px dashed #a5b4fc; padding: 8px; margin-bottom: 10px; }
.box > div { background: #4338ca; color: #fff; padding: 8px 12px; border-radius: 6px; }
.cap { margin: 0 0 4px; font: 13px monospace; color: #475569; }
</style>
<p class="cap">flex-grow: 1 เฉย ๆ (flex-basis ยังเป็น auto)</p>
<div class="box">
<div style="flex-grow: 1">สั้น</div>
<div style="flex-grow: 1">ยาวกว่ามาก</div>
</div>
<p class="cap">flex: 1 (= grow 1 · shrink 1 · basis 0)</p>
<div class="box">
<div style="flex: 1">สั้น</div>
<div style="flex: 1">ยาวกว่ามาก</div>
</div>
flex-grow: 1 เฉย ๆ (flex-basis ยังเป็น auto)
flex: 1 (= grow 1 · shrink 1 · basis 0)
ความต่างทั้งหมดอยู่ที่ flex-basis ตัวเดียว เพราะ flex คือ
รูปย่อของสาม property ที่เขียนติดกัน:
| เขียนย่อ | เท่ากับเขียนเต็ม | แปลว่า |
|---|---|---|
flex: 1; |
flex-grow: 1; flex-shrink: 1; flex-basis: 0%; |
ลืมขนาดเนื้อหาไปเลย แล้วหารที่ว่างทั้งกล่องเท่า ๆ กัน |
flex: auto; |
flex-grow: 1; flex-shrink: 1; flex-basis: auto; |
เริ่มจากขนาดเนื้อหา แล้วค่อยแบ่งที่ว่างที่เหลือ |
flex: none; |
flex-grow: 0; flex-shrink: 0; flex-basis: auto; |
ขนาดเท่าเนื้อหาตายตัว ไม่โต ไม่หด |
flex: 0 1 auto; |
— | ค่าเริ่มต้นของทุก flex item ที่คุณไม่ได้สั่งอะไรเลย |
ตัวที่เซอร์ไพรส์คนมากที่สุดคือ flex-basis: 0% ที่แอบมากับ flex: 1
เขียน flex: 1 คุณกำลังบอกเบราว์เซอร์ว่า "ถือว่ากล่องนี้กว้างศูนย์นะ
แล้วค่อยเอาที่ว่างทั้งหมดมาหาร" ซึ่งเป็นเหตุผลที่มันได้เท่ากันเป๊ะเสมอ
ถ้าคุณอยากได้ "แบ่งเท่ากัน" ให้ใช้ flex: 1 · ถ้าอยากได้ "เนื้อหาสั้นก็แคบหน่อย"
ให้ใช้ flex: auto หรือ flex-grow: 1 เฉย ๆ
นึกถึงร้านหนึ่งที่มีหุ้นส่วนสามคน ตกลงกันไว้สามเรื่อง:
· เงินที่แต่ละคนลงตอนเปิดร้าน — ใครลงเท่าไหร่จดไว้ตรงนั้น = flex-basis
· กำไรที่เหลือหลังจากคืนทุนแล้ว แบ่งกันกี่ส่วน = flex-grow
· ถ้าขาดทุน ใครรับภาระกี่ส่วน = flex-shrink
flex: 1 ก็คือข้อตกลงแบบ "ไม่ต้องสนว่าใครลงเท่าไหร่ (basis: 0)
หารกำไรเท่ากันหมด (grow: 1) ขาดทุนก็หารเท่ากัน (shrink: 1)"
— ยุติธรรมแบบเรียบง่าย และเป็นเหตุผลที่มันได้ผลลัพธ์เท่ากันเป๊ะทุกครั้ง
ที่ไม่เหมือนกัน: หุ้นส่วนที่ขาดทุนหนัก ๆ ติดลบได้ไม่มีพื้น แต่ flex item หดได้ถึงแค่ระดับหนึ่งแล้วหยุด — คือหยุดที่ความกว้างของคำที่ตัดบรรทัดไม่ได้ อย่างที่เห็นในกล่อง "Loooo…" ในหัวข้อ 9.2 · พอหดต่อไม่ได้มันจะเลือกล้นออกนอกกรอบ แทนที่จะหดต่อ ซึ่งเป็นพฤติกรรมที่หุ้นส่วนในโลกจริงทำไม่ได้
- shorthand property (รูปย่อ)
- property เดียวที่ตั้งค่าให้หลาย property พร้อมกัน ·
flexย่อสามตัว · ข้อควรระวังคือมันเขียนทับ property ที่คุณตั้งไว้ก่อนหน้าด้วย แม้คุณจะไม่ได้พิมพ์ตัวนั้น · ประโยคที่ใช้ได้เลย: "ผมว่าflex: 1มันไปทับflex-basisที่ตั้งไว้ข้างบนหรือเปล่าครับ" - flex-basis
- ขนาดตั้งต้นตามแกนหลัก — เป็นความกว้างเมื่อ
rowและเป็นความสูงเมื่อcolumn(จุดนี้คนลืมบ่อยมาก) · พิมพ์บอก AI ได้ว่า: "container เป็น column ผมอยากล็อกความสูงตั้งต้นของ item ใช้flex-basisได้ไหม" - min-content
- ความกว้างที่เล็กที่สุดที่เนื้อหายังอยู่ได้โดยไม่ล้น —
ปกติคือความกว้างของคำที่ยาวที่สุดที่ตัดบรรทัดไม่ได้ · flex item จะไม่หดต่ำกว่านี้ ·
ประโยคที่ใช้ได้เลย: "มันไม่หดต่อเพราะติด min-content ครับ ต้องใส่
min-width: 0ถึงจะยอมหดต่อ" — ประโยคนี้ประโยคเดียวช่วยคุณได้เป็นสิบครั้ง
10 · สรุป — ตัวไหนใส่ที่แม่ ตัวไหนใส่ที่ลูก
นี่คือเส้นแบ่งที่ทำให้คน debug flexbox เป็นกับไม่เป็นต่างกัน: ถ้าใส่ผิดที่ มันจะไม่ error แต่จะไม่มีอะไรเกิดขึ้นเลย
| ใส่ที่ กล่องแม่ (flex container) | ใส่ที่ กล่องลูก (flex item) |
|---|---|
display: flex | flex-grow |
flex-direction | flex-shrink |
flex-wrap | flex-basis |
justify-content | flex (รูปย่อของสามตัวบน) |
align-items | align-self (แหกค่าของแม่เฉพาะตัวนี้) |
gap · row-gap · column-gap | order (สลับลำดับที่วาด) |
1. ใส่ flex: 1 แล้วเงียบ → กล่องนั้นไม่ใช่ลูกชั้นแรกของ flex container
(เป็นหลานอีกชั้น) หรือกล่องแม่ยังไม่ได้ display: flex
2. ใส่ justify-content แล้วไม่ขยับ → ไม่มีที่ว่างเหลือให้กระจาย
เพราะของเต็มแถวพอดีอยู่แล้ว
3. ใส่ align-items: center แล้วไม่เห็นความต่าง →
กล่องแม่ไม่ได้สูงกว่าเนื้อหา จึงไม่มีที่ว่างในแนวแกนรองให้จัด · ลองใส่ height ให้แม่ดูก่อน
10.1 · เอาทุกอย่างมารวมกัน — navbar ของจริง
ทั้งบทนี้มาบรรจบกันที่ของชิ้นเดียวที่คุณเห็นบนเว็บทุกเว็บ: แถบเมนูด้านบน ที่มีชื่อแบรนด์กับเมนูชิดซ้าย แล้วปุ่มเข้าสู่ระบบไปอยู่ขวาสุด
<style>
.bar { display: flex; align-items: center; gap: 12px;
background: #1e293b; color: #fff; padding: 10px 16px; border-radius: 8px; }
.brand { font-weight: 700; }
.spacer { flex: 1; }
.bar a { color: #cbd5e1; text-decoration: none; }
</style>
<div class="bar">
<span class="brand">PEA DevPool</span>
<a href="#">บทเรียน</a>
<a href="#">สรุป</a>
<span class="spacer"></span>
<a href="#">เข้าสู่ระบบ</a>
</div>
หัวใจของมันคือ <span class="spacer"> ที่ไม่มีเนื้อหาอะไรเลย
แต่ตั้ง flex: 1 ไว้
เพราะ flex: 1 คือ "ไม่สนขนาดเนื้อหา ขอที่ว่างที่เหลือทั้งหมด"
(หัวข้อ 9.3) มันจึงพองออกจนเต็มช่องว่าง แล้วดันทุกอย่างที่อยู่หลังมันไปชิดขวาโดยปริยาย
ส่วน align-items: center คือตัวที่ทำให้ข้อความทุกชิ้นอยู่กลางแถบตามแนวตั้ง
ไม่ว่าตัวอักษรจะสูงไม่เท่ากันแค่ไหน
งานนี้ใช้เวลาประมาณ 15 นาที และเป็นงานที่คุณจะได้ทำซ้ำอีกเป็นร้อยครั้งในชีวิตจริง
สร้างไฟล์ navbar.html เปล่า ๆ หนึ่งไฟล์ แล้วทำตามลำดับ
ห้ามข้ามขั้น — ประเด็นของภารกิจนี้คือเห็นว่าแต่ละบรรทัดเปลี่ยนอะไร
- วาง
<div>แม่หนึ่งอัน ข้างในมี: ชื่อแบรนด์ · ลิงก์ "บทเรียน" · ลิงก์ "สรุป" · ลิงก์ "เข้าสู่ระบบ"
ยังไม่ต้องใส่ CSS อะไรเลย เปิดดูแล้วจดไว้ว่าเห็นเป็นแบบไหน - ใส่
display: flex;ที่กล่องแม่ · เปิดดูอีกรอบแล้วจดว่าเปลี่ยนไปยังไง - ใส่
gap: 12px;และalign-items: center;· ลองลบalign-itemsออกแล้วใส่กลับ ดูว่าต่างกันตรงไหน (ถ้าไม่ต่าง แปลว่ากล่องแม่ยังไม่มีความสูงเกินเนื้อหา — ใส่padding: 10px 16px;ก่อน) - โจทย์จริงมาถึงตรงนี้: ทำให้ "เข้าสู่ระบบ" ไปอยู่ขวาสุด
โดยที่อีกสามอันยังอยู่ชิดซ้าย — และต้องทำให้ได้สองวิธีที่ต่างกัน
· วิธีที่หนึ่ง ใช้ตัวคั่นแบบเดียวกับ navbar ในหัวข้อ 10.1
· วิธีที่สอง ห้ามเพิ่ม element ใหม่แม้แต่ตัวเดียว — ต้องแก้ที่ CSS ล้วน ๆ · ลองเองให้สุดก่อนค่อยกดเฉลย - ขั้นพิสูจน์ — ห้ามข้าม: เปิด DevTools (คลิกขวา → Inspect) แล้วทำสองอย่าง
(ก) คลิกที่กล่องแม่ ดูว่ามีป้ายflexเล็ก ๆ ขึ้นข้าง ๆ ไหม — กดที่ป้ายนั้น Chrome จะวาดเส้นแกนให้ดูเลยว่าแกนหลักชี้ทางไหน
(ข) ที่ช่อง Computed หาflex-growของตัวคั่นที่คุณใส่ ต้องอ่านได้เป็น1ไม่ใช่0— ถ้าเป็น0แปลว่าบรรทัดที่คุณเขียนไม่ได้มีผลจริง ต้องหาว่าโดนอะไรทับ - ขั้นทดสอบว่ามันทนจริง: ย่อหน้าต่างเบราว์เซอร์ให้แคบลงเรื่อย ๆ
จนเมนูเริ่มเบียดกัน · จดไว้ว่ามันหดหรือล้น แล้วอธิบายให้ตัวเองฟังว่าทำไม
โดยใช้คำว่า
flex-shrinkกับmin-contentในคำอธิบาย
วิธีที่หนึ่ง — ตัวดันช่องว่าง (spacer) คือของที่โชว์ไว้ในหัวข้อ 10.1 แล้ว:
<span class="spacer"></span> เปล่า ๆ ที่ตั้ง flex: 1
วางคั่นไว้ก่อน "เข้าสู่ระบบ"
วิธีที่สอง — ไม่เพิ่ม element เลย ใส่บรรทัดเดียวที่ลิงก์ "เข้าสู่ระบบ":
.login { margin-left: auto; } /* ที่ว่างที่เหลือไปกองข้างซ้ายของมันทั้งหมด */
margin: auto ใน flexbox มีพลังพิเศษที่ไม่มีนอก flexbox:
มันดูดที่ว่างที่เหลือเข้ามาเป็นของตัวเอง ก่อนที่ justify-content
จะได้แบ่งด้วยซ้ำ
วิธีนี้ดีกว่าในแง่ accessibility เพราะไม่ได้เพิ่ม element เปล่า ๆ
ที่ไม่มีความหมายเข้าไปให้โปรแกรมอ่านหน้าจอต้องข้าม
ยังมีวิธีที่สามอีก เผื่อเจอในโค้ดคนอื่น: ห่อสามอันแรกไว้ใน
<div> เดียว แล้วใส่ justify-content: space-between; ที่แม่ —
เหมาะเมื่อมี "กลุ่มซ้าย" กับ "กลุ่มขวา" ชัดเจน
แต่ที่สำคัญกว่าทั้งสามวิธีคือข้อ 5 —
ถ้ายังไม่ได้เปิด DevTools ยืนยัน แปลว่ายังไม่รู้ว่ามันทำงานเพราะอะไร รู้แค่ว่ามันดูถูก
❓ ติดตรงไหนถามได้เลย — โดยเฉพาะสามคำถามนี้ที่ถามกันบ่อยและตอบได้ไม่ยาก:
"ทำไม flex: 1 ของผมไม่ทำงาน" ·
"gap กับ margin ควรใช้ตัวไหนดี" ·
"เมื่อไหร่ควรใช้ Grid แทน Flexbox"
ถามมาพร้อมโค้ดที่ไม่ทำงานจริง ๆ จะตอบได้ตรงกว่าถามลอย ๆ มาก
เช็คความเข้าใจ
1. กล่องแม่ตั้ง display: flex; flex-direction: column; justify-content: center; — ลูกจะไปอยู่ตรงไหน?
ในหัวข้อ 6 มีกล่องผลลัพธ์จริงสองกล่องวางคู่กัน ใช้ค่าเดียวกันเป๊ะ ต่างกันแค่ flex-direction
— กล่อง column ของไปกองอยู่ด้านล่าง ไม่ใช่ด้านขวา
justify-content ทำงานตามแกนหลักเสมอ และ column
หมุนแกนหลักให้ชี้ลง คำว่า "กลางแถว" จึงกลายเป็น "กลางแนวตั้ง"
ตัวเลือกที่ 1 คือความเข้าใจผิดที่พบบ่อยที่สุดในบทนี้ — มันถูกเฉพาะตอน row
ซึ่งเป็นค่าเริ่มต้น คนเลยเข้าใจผิดได้นานหลายเดือนโดยไม่มีอะไรมาสะกิด
ตัวเลือกที่ 3 ผิดเพราะการจัดกลางสองแกนต้องใช้ align-items: center ควบมาด้วย
property เดียวคุมได้แกนเดียวเสมอ
ตัวเลือกที่ 4 ผิดชัดเจน — ทุก property ของ flex container ใช้ได้กับทุกค่าของ flex-direction
จุดที่ต้องจำ: อย่าจำว่า "justify = แนวนอน" ให้จำว่า
"justify = ตามแถว" แล้วถามต่อทุกครั้งว่า "แถวนี้มันเรียงไปทางไหน"
2. flex: 1; เป็นรูปย่อของอะไร?
ตัวที่ชี้ขาดคือ flex-basis ที่กลายเป็น 0% ซึ่งแปลว่า
ไม่นับขนาดเนื้อหาเป็นจุดตั้งต้นเลย — ทุกกล่องจึงได้เท่ากันเป๊ะ
ผลวัดจริงในหัวข้อ 9.3 ยืนยันข้อนี้: flex: 1 ได้ 146px เท่ากันทั้งคู่
ส่วน flex-grow: 1 เฉย ๆ ได้ 116px กับ 176px
ตัวเลือกที่ 1 คือความหมายของ flex: auto ไม่ใช่ flex: 1 —
ต่างกันแค่ flex-basis ตัวเดียว แต่ผลบนจอต่างกันชัดเจน
ตัวเลือกที่ 2 ผิดสองที่: flex-shrink: 0 เป็นค่าของ flex: none
(ซึ่งเต็ม ๆ คือ 0 0 auto) ไม่ใช่ของ flex: 1 — เขียนแบบนั้นกล่องจะไม่ยอมหด
เมื่อที่ไม่พอ แล้วล้นออกนอกกรอบแทน · และ flex-basis ก็ยังเป็น auto อยู่
ตัวเลือกที่ 4 มี flex-grow: 0 ซึ่งขัดกับเจตนาทั้งหมดของ flex: 1
ที่ตั้งใจให้มันโต
จุดที่ต้องจำ: flex: 1 = "หารเท่ากัน ไม่สนเนื้อหา" ·
flex: auto = "เนื้อหาเยอะก็ได้กว้างกว่า" — เลือกให้ตรงเจตนา ไม่ใช่พิมพ์ตามความเคยชิน
3. กล่องแม่ display: flex; กว้าง 260px · gap: 8px · มีลูก 4 กล่องที่ตั้ง width: 100px ทุกกล่อง และไม่ได้ตั้ง flex-wrap — เกิดอะไรขึ้น?
ค่าเริ่มต้นของ flex-wrap คือ nowrap ซึ่งแปลว่า
"ยัดลงหนึ่งบรรทัดให้ได้" เบราว์เซอร์จึงเรียกใช้ flex-shrink
ที่มีค่าเริ่มต้นเป็น 1 อยู่แล้วมาบีบทุกกล่องเท่า ๆ กัน
เลขจริงคือ (260 − 8×3) ÷ 4 = 59 ตรงกับที่วัดได้ในกล่องผลลัพธ์ของหัวข้อ 8
ตัวเลือกที่ 1 คือสิ่งที่จะเกิดถ้าลูกตั้ง flex-shrink: 0 ไว้ด้วย
เท่านั้น — ค่าเริ่มต้นไม่ใช่แบบนั้น
ตัวเลือกที่ 2 คือพฤติกรรมของ flex-wrap: wrap ซึ่งต้องสั่งเอง
ไม่ได้มาให้ฟรี
ตัวเลือกที่ 4 ไม่มีในโลก CSS — ไม่มีอะไรทำให้ element หายไปเงียบ ๆ นอกจาก
display: none
จุดที่ต้องจำ: เจอ width ที่ "ไม่ทำงาน" ให้ดูก่อนว่ามันเป็น
flex item อยู่ในกล่อง nowrap หรือเปล่า — 9 ใน 10 ครั้งคือข้อนี้
4. กล่องแม่ display: flex; สูง 200px · กล่องลูกไม่ได้ตั้ง height ไว้เลย และแม่ก็ไม่ได้ตั้ง align-items — ลูกจะสูงเท่าไหร่?
ในหัวข้อ 5 กล่องผลลัพธ์อันบนคือหลักฐาน: กล่องแม่สูง 80px ลูกไม่ได้ตั้งความสูงเลย
แต่ลูกสูงเต็มกล่อง — พอเปลี่ยนเป็น flex-start ถึงยุบลงมา
รายละเอียดที่ทำให้คุณดูรู้จริง: ค่าเริ่มต้นตามมาตรฐานคือคำว่า normal
ซึ่งเมื่ออยู่ในกล่อง flex ให้ผลเหมือน stretch —
ตัวเลือกที่ 2 จึงถูกทั้งในทางปฏิบัติและตรงกับที่วัดได้จริง
ตัวเลือกที่ 1 กับ 3 ผิดที่ค่าเริ่มต้น — ทั้ง center และ flex-end
ต้องสั่งเองทั้งคู่ ไม่มีอันไหนมาเป็นค่าตั้งต้น
ตัวเลือกที่ 4 ผิดหลักการ เพราะ align-items คุมทุก flex item
ในกล่องพร้อมกัน ถ้าอยากให้ตัวเดียวต่างจากเพื่อน ต้องใช้ align-self ที่ตัวนั้น
จุดที่ต้องจำ: กล่องลูกใน flexbox สูงเท่ากันโดยไม่ต้องสั่ง
ซึ่งคือเหตุผลที่แถวการ์ดใน flexbox ดูเรียบร้อยกว่าที่คุณคาดเสมอ —
และเป็นเหตุผลที่บางทีการ์ดสูงเกินจนดูแปลก ทั้งที่คุณไม่ได้สั่งอะไร
อ้างอิง
- MDN — Basic concepts of flexbox — อ่านหัวข้อ "The two axes of flexbox" ให้จบก่อนหัวข้ออื่น มันมีรูปแกนหลัก/แกนรองที่อธิบายกับดักในหัวข้อ 6 ของบทนี้ได้ในภาพเดียว
- MDN —
flex(shorthand) — อ่านหัวข้อ "Constituent properties" กับตัวอย่างflex: <number>ตรงนั้นเขียนไว้ชัดว่าflex: 1ขยายเป็น1 1 0% - MDN —
align-items— ดูบรรทัด Initial value ในตาราง Formal definition จะเห็นคำว่าnormalไม่ใช่stretchซึ่งคือรายละเอียดที่พูดถึงในหัวข้อ 5 - W3C — CSS Flexible Box Layout §7.1.1
flex— อ่านย่อหน้าที่ขึ้นต้นว่า "flex: <number>" นี่คือมาตรฐานตัวจริงที่เบราว์เซอร์ทำตาม ใช้อ้างอิงเวลาเถียงกันได้ - CSS-Tricks — A Complete Guide to Flexbox —
ใช้เป็นแผ่นภาพ ไม่ใช่บทเรียน · เปิดค้างไว้ตอนทำภารกิจ
แล้วเลื่อนหาภาพของ
justify-contentกับalign-itemsเทียบกับที่คุณเขียน