บทเรียน 57 · Frontend · Basic HTML & CSS

แท็กที่ใช้จริงทุกวัน — div, p, h1–h6, span, header/main/footer, img

~18 นาที · ควรผ่าน บทเรียน 56 (เว็บทำงานยังไง) มาก่อน · ไม่ต้องเคยเขียน HTML มาก่อน

สไลด์หน้า 13–19 เอาแท็กพื้นฐานมาวางเรียงกันหน้าละกลุ่ม อ่านแล้วเหมือน "รายการคำศัพท์"
แต่ถ้าจำเป็นรายการ คุณจะยังตอบไม่ได้อยู่ดีว่าเมื่อไหร่ใช้ <div> เมื่อไหร่ใช้ <span> ทั้งที่สไลด์บอกเองว่ามัน "คล้ายกัน"
บทนี้จึงไม่ได้เรียงตามสไลด์ แต่เริ่มจากเส้นแบ่งเส้นเดียวที่อธิบายทุกแท็กในบทนี้ได้หมด — แท็กที่กินทั้งบรรทัด กับแท็กที่แทรกอยู่ในบรรทัด
แล้วค่อยตอบคำถามที่สไลด์ไม่ได้ตอบ: ถ้า <header> หน้าตาเหมือน <div> เป๊ะ ๆ แล้วจะมีมันไว้ทำไม · และ alt ใน <img> ไม่ใช่ของประดับตรงไหน

🧰 ของที่ใช้ตรวจในบทนี้ (รันวันที่ 27 สิงหาคม 2026)

Chromium 148 (engine ของ Chrome / Edge / Brave) บน macOS
วัดด้วย getComputedStyle() และ getBoundingClientRect() ของจริงในหน้าเปล่า

ตัวเลขทุกตัวในบทนี้มาจากการวัดจริง ไม่ได้เขียนจากความจำ และกล่องสีเทาที่เขียนว่า "ผลลัพธ์จริง" คือหน้าเว็บย่อ ๆ ที่เบราว์เซอร์ของคุณเรนเดอร์เดี๋ยวนี้ จากโค้ดชุดเดียวกับกล่องดำข้างบนมันเป๊ะ ๆ — ไม่ใช่ภาพถ่าย

1 · เส้นแบ่งเส้นเดียวที่อธิบายได้ทั้งบท

แท็กใน HTML แบ่งพฤติกรรมตอนวางบนหน้าจอออกเป็นสองพันธุ์ใหญ่ ๆ:

ไม่ต้องเชื่อ — ดูของจริง โค้ดข้างล่างนี้ใส่พื้นหลังสีให้ทุกตัวเพื่อให้เห็น "ขอบเขต" ของแต่ละกล่อง:

<style>
  .a { background: #cfe3ff; }
  .b { background: #ffe0b3; }
</style>

<div class="a">div ตัวที่หนึ่ง</div>
<div class="b">div ตัวที่สอง</div>

<span class="a">span ตัวที่หนึ่ง</span>
<span class="b">span ตัวที่สอง</span>

สังเกตสามอย่าง:
① แถบสีของ div ลากยาวจนสุดขอบขวา ทั้งที่ข้อความสั้นนิดเดียว
② div สองอันอยู่คนละบรรทัด โดยไม่ต้องสั่งอะไรเลย
③ span สองอันอยู่บรรทัดเดียวกัน และแถบสีหุ้มพอดีตัวอักษร

ตัวเลขที่ผมวัดได้จริงในหน้าเปล่ากว้าง 482px: div ทั้งสองอันกว้าง 482px เท่ากันเป๊ะและอยู่คนละแถว · span สองอันกว้าง 95px กับ 94px (เท่าความยาวข้อความ) และอยู่แถวเดียวกัน

🧰 เทียบให้เห็นภาพ — ย่อหน้าในสมุด กับ ปากกาไฮไลต์

นึกถึงสมุดจดที่มีเส้นบรรทัด
· พันธุ์กินทั้งบรรทัด คือย่อหน้า — เขียนจบย่อหน้าหนึ่ง คุณขึ้นบรรทัดใหม่เสมอ ไม่ว่าย่อหน้านั้นจะยาวสิบบรรทัดหรือมีคำเดียว และย่อหน้านั้น "จอง" ความกว้างทั้งหน้ากระดาษไว้แล้ว ใครมาเขียนต่อข้าง ๆ ไม่ได้
· พันธุ์แทรกในบรรทัด คือปากกาไฮไลต์ — คุณระบายทับคำว่า "สำคัญมาก" กลางประโยค แล้วประโยคยังเป็นประโยคเดิม ไม่มีอะไรถูกดันลงบรรทัดใหม่ แถบสีก็หุ้มแค่คำนั้น ไม่ลากยาวไปทั้งบรรทัด

ที่ไม่เหมือนกัน: ปากกาไฮไลต์ระบายทับอะไรก็ได้ รวมทั้งลากทับทั้งย่อหน้ารวดเดียว — แต่ HTML มีกติกาเขียนไว้ว่าแท็กไหนอุ้มอะไรได้บ้าง และเบราว์เซอร์บังคับใช้กติกานั้นเงียบ ๆ โดยไม่ฟ้อง (จะเห็นตัวเป็น ๆ ในหัวข้อ 3 ตอนที่เอา <div> ไปใส่ใน <p>)
และปากกาไฮไลต์ "ความสูง" เท่าบรรทัดเสมอ สั่งให้สูงขึ้นไม่ได้ — ตรงนี้ analogy พูดถูกพอดี และเป็นกับดักข้อใหญ่ของ <span> ที่จะเจอในหัวข้อ 5

🗣 ศัพท์ที่เพิ่งเรียนไป
block-level element
ชื่อจริงของ "พันธุ์กินทั้งบรรทัด" · ในภาษา CSS คือ element ที่ display เป็น block · พูดกับ senior ว่า “ตัวนี้เป็น block เลยดันบรรทัดใหม่” จะสื่อกว่า “มันตกลงไปข้างล่างเอง”
inline element
ชื่อจริงของ "พันธุ์แทรกในบรรทัด" · display: inline · พิมพ์บอก AI ได้ว่า “this element is inline, so width and height are ignored — what should I use instead?” แล้วมันจะเสนอ inline-block หรือ flex ให้เอง
normal flow
กติกาเริ่มต้นที่เบราว์เซอร์ใช้วาง element เรียงกันลงมา ก่อนที่คุณจะไปสั่ง flex หรือ grid ทับ · เจอ layout เพี้ยน ให้ถามตัวเองก่อนว่า “ตอนนี้ยังเป็น normal flow อยู่ไหม” เพราะกฎ block/inline ในบทนี้ใช้ได้เฉพาะตอนยังเป็น normal flow

2 · <div> — กล่องเปล่าที่ไม่มีความหมาย

สไลด์หน้า 14 นิยามไว้ตรง ๆ ว่า "a plain box for grouping elements when no semantic meaning is needed"
ท่อนที่สำคัญที่สุดคือท่อนหลัง — <div> ไม่ได้แปลว่า "กล่อง" เฉย ๆ มันแปลว่า "กล่องที่ผมยืนยันว่าไม่มีความหมายอะไรเป็นพิเศษ"

โค้ดจากสไลด์หน้า 14 ตรง ๆ:

<div class="title">
  <p>Hello, My name is Joey Wasin</p>
  <p>I'm a software developer</p>
</div>

หน้าตาที่ได้เหมือนกันเป๊ะกับตอนที่ไม่มี <div> ครอบ — ลบบรรทัดแรกกับบรรทัดสุดท้ายทิ้ง แล้วเหลือแค่ <p> สองอัน ก็ได้ภาพเดียวกัน
แปลว่า <div> ในตัวมันเอง ไม่ทำอะไรกับหน้าตาเลยสักอย่าง ประโยชน์ของมันมีสองข้อล้วน ๆ:

  1. มีที่ให้แปะ class — เพื่อให้ CSS ในบทหน้าเล็งกลุ่มนี้ทั้งกลุ่มได้ทีเดียว
  2. มัดของให้อยู่ด้วยกัน — พอย้าย ก็ย้ายทั้งก้อน ไม่ต้องไล่ย้ายทีละ <p>
⚠️ กับดักของมือใหม่: ใส่ div ครอบทุกอย่างเพราะ "เผื่อไว้"

<div> ไม่ต้องเสียเงินซื้อ คนเลยใส่ซ้อนกันห้าชั้นโดยไม่รู้ตัว
ราคาที่จ่ายไม่ได้อยู่ที่ความเร็ว — อยู่ที่วันที่คุณกลับมาอ่านโค้ดตัวเองแล้วไม่รู้ว่าชั้นไหนทำอะไร
กฎง่าย ๆ ที่ใช้ได้จริง: ใส่ <div> ตอนที่คุณตอบได้ว่าจะแปะ class อะไรให้มัน ถ้ายังตอบไม่ได้ ยังไม่ต้องใส่

3 · <p> — ย่อหน้า และกับดักที่ทุกคนโดนครั้งแรก

สไลด์หน้า 15 บอกว่า <p> คือ "a block of text content"
คำว่า text content ตรงนี้ไม่ใช่คำบรรยายสวย ๆ — มันคือข้อบังคับ ที่เขียนไว้ใน spec ของ HTML และเบราว์เซอร์บังคับใช้จริงแบบเงียบ ๆ

ทายก่อนดู — ผมใส่กรอบสีให้ <p> เป็นแดง และ <div> เป็นน้ำเงิน แล้วเอา <div> ไปวางไว้กลาง <p>:

<style>
  p   { border: 2px solid #d33; padding: 6px; }
  div { border: 2px solid #06c; padding: 6px; }
</style>

<p>ข้อความก่อน<div>ฉันคือ div</div>ข้อความหลัง</p>

คุณจะเห็นกรอบแดงกี่กรอบ? และกรอบน้ำเงินอยู่ข้างในหรือข้างนอกกรอบแดง?
(คำใบ้: คำตอบที่ถูกมีตัวเลขที่คุณไม่ได้พิมพ์ลงไปเองอยู่ด้วย)

กรอบแดง 2 กรอบ และกรอบน้ำเงินอยู่ข้างนอก — ทั้งที่ในโค้ดคุณเขียนแค่ <p> เดียว

ผมอ่าน DOM ที่เบราว์เซอร์สร้างขึ้นจริงออกมาด้วย innerHTML ได้แบบนี้เป๊ะ ๆ:

<p>ข้อความก่อน</p><div>ฉันคือ div</div>ข้อความหลัง<p></p>

อ่านทีละก้าวว่าเบราว์เซอร์คิดอะไร:
① เจอ <div> ทั้งที่ยังอยู่ใน <p> → มันแอบปิด </p> ให้เอง เพราะ <p> รับ block ไว้ข้างในไม่ได้
② <div> จึงกลายเป็นพี่น้องของ <p> ไม่ใช่ลูก
③ คำว่า "ข้อความหลัง" หลุดออกมาลอย ๆ ไม่ได้อยู่ใน <p> ไหนเลย
④ พอเจอ </p> ที่คุณพิมพ์ไว้ ตอนนั้นไม่มี <p> เปิดค้างอยู่แล้ว เบราว์เซอร์เลยสร้าง <p></p> เปล่า ๆ ขึ้นมาปิดให้ — นั่นคือกรอบแดงอันที่สอง

จุดที่โหดคือ ไม่มี error ไม่มีคำเตือน ไม่มีอะไรแดงในคอนโซล หน้ายังเปิดได้ปกติ · คุณจะรู้ตัวก็ตอนที่เขียน CSS ว่า "ทำไม padding ที่ให้ p ไม่ครอบ div" หรือตอนที่ p:last-child ไปโดนกรอบเปล่าที่คุณไม่รู้ว่ามีอยู่

และนี่คือของจริงที่เบราว์เซอร์คุณเรนเดอร์เดี๋ยวนี้ — โค้ดชุดเดียวกับข้างบนเป๊ะ:

ท่าที่ถูกคือสลับข้าง — เอา <div> ครอบ <p> ไม่ใช่ <p> ครอบ <div> ซึ่งก็คือสิ่งที่สไลด์หน้า 14 ทำอยู่แล้วพอดี:

✗ p ครอบ div

<p>
  <div>…</div>
</p>

เบราว์เซอร์แอบแก้ให้ ได้ DOM ที่ไม่ตรงกับที่พิมพ์

✓ div ครอบ p

<div>
  <p>…</p>
</div>

DOM ที่ได้ตรงกับที่พิมพ์ทุกตัวอักษร

🗣 ศัพท์ที่เพิ่งเรียนไป
content model
กฎที่ spec ของ HTML เขียนไว้ว่า "แท็กนี้อนุญาตให้มีอะไรอยู่ข้างในได้บ้าง" · <p> มี content model ที่รับได้แค่ข้อความกับ inline element · ถาม AI ได้ตรง ๆ ว่า “what is the content model of the p element in the HTML spec?”
phrasing content
ชื่อกลุ่มของ "ของที่วางในประโยคได้" — ข้อความ · <span> · <a> · <strong> · <img> · คือกลุ่มเดียวที่ <p> ยอมรับ
error recovery (ของ HTML parser)
HTML ไม่มี syntax error — เจอโครงผิด parser จะ "ซ่อมให้" ตามกฎที่ spec เขียนไว้แทนที่จะฟ้อง · ประโยคที่ใช้ได้จริง: “มันไม่ error หรอก HTML parser มัน recover ให้ — ต้องไปดู DOM จริงใน DevTools” เป็นคำตอบของอาการ "โค้ดถูกแต่หน้าไม่ตรง" ครึ่งหนึ่งของโลก

4 · <h1> ถึง <h6> — ลำดับความสำคัญ ไม่ใช่ขนาดตัวอักษร

สไลด์หน้า 16 เขียนว่า "six levels of headings to prioritize the level of importance"
ประโยคนี้ตรงมาก แต่พอเห็นภาพจริงคนมักอ่านเป็นอย่างอื่น เพราะขนาดตัวอักษรบังเอิญเรียงจากใหญ่ไปเล็กพอดี

นี่คือขนาดเริ่มต้นจริงที่ผมวัดได้ในหน้าเปล่า (ไม่มี CSS อะไรเลย):

แท็กขนาดตัวอักษรเริ่มต้นเทียบกับข้อความปกติ (16px)
<h1>32px2 เท่า
<h2>24px1.5 เท่า
<h3>18.72px1.17 เท่า
<h4>16pxเท่ากันพอดี
<h5>13.28pxเล็กกว่า
<h6>10.72pxเล็กกว่ามาก

สังเกต <h4> ลงไป — ตัวเท่าหรือเล็กกว่าข้อความธรรมดา ถ้า "หัวข้อ" แปลว่า "ตัวใหญ่" <h5> กับ <h6> ก็จะไม่มีเหตุผลให้มีอยู่เลย
แถมขนาดพวกนี้เขียนทับได้หมดด้วย CSS บรรทัดเดียว:

<style>
  .tiny  { font-size: 12px; }
  .huge  { font-size: 30px; }
</style>

<h1 class="tiny">ฉันคือ h1 ที่ถูกสั่งให้เล็ก</h1>
<h6 class="huge">ฉันคือ h6 ที่ถูกสั่งให้ใหญ่</h6>

ตาเห็น <h6> ใหญ่กว่า <h1> ชัด ๆ แต่โปรแกรมยังอ่านว่า <h1> คือหัวข้อระดับ 1 และ <h6> คือระดับ 6 เหมือนเดิม
เลข 1–6 จึงไม่ใช่ขนาด มันคือความลึกของสารบัญ

🧰 เทียบให้เห็นภาพ — เลขหัวข้อในรายงาน

เอกสารราชการเขียนหัวข้อเป็น 1 → 1.1 → 1.1.1
เลขพวกนี้ไม่ได้บอกว่าตัวอักษรต้องใหญ่แค่ไหน มันบอกว่าข้อไหนอยู่ใต้ข้อไหน
<h1> คือ "1" · <h2> คือ "1.1" · <h3> คือ "1.1.1"
และเหมือนรายงานที่ดี — หนึ่งหน้ามี "ข้อ 1" ใหญ่สุดได้อันเดียว คือ <h1> ที่เป็นชื่อเรื่องของหน้านั้น

ที่ไม่เหมือนกัน: ในรายงานกระดาษ ถ้าคุณข้ามจาก "1" ไป "1.1.1" เลย คนอ่านยังเดาถูกว่ามันอยู่ใต้ข้อ 1 เพราะเห็นตัวเลขเต็ม ๆ พิมพ์อยู่ตรงนั้น
แต่ใน HTML ไม่มีเลขให้เห็น โปรแกรมอ่านหน้าจอได้ยินแค่ว่า "หัวข้อ ระดับสาม" ลอย ๆ แล้วต้องเดาเองว่ามันหายไปไหนหนึ่งชั้น — ข้ามระดับใน HTML จึงแพงกว่าข้ามเลขในรายงานเยอะ

⚠️ โค้ดในสไลด์หน้า 16 ข้ามระดับอยู่หนึ่งจุด

ลองไล่ลำดับหัวข้อในโค้ดของสไลด์: h1 → h2 → h2 → h4
ไม่มี h3 — <h4>Dec 2023 – Present</h4> โผล่ขึ้นมาใต้ <h2>Work Experience</h2> ตรง ๆ

หน้าเว็บพังไหม: ไม่พัง ตัวอักษรก็ดูดีด้วยซ้ำ (เพราะ h4 ขนาดพอดี ๆ)
แต่สารบัญที่โปรแกรมอ่านหน้าจอสร้างขึ้นจะมีชั้นที่หายไปหนึ่งชั้น
ถ้าอยากได้ขนาดของ h4 แต่ระดับของ h3 ทางที่ถูกคือใช้ <h3> แล้วสั่ง font-size เอา — เลือกระดับตามโครงเรื่อง แล้วค่อยจัดขนาดด้วย CSS ทีหลังเสมอ

5 · <span> — กล่องเปล่าแบบ inline

สไลด์หน้า 17 บอกว่า "Like a <div> element, but used inside a sentence"
ถ้าเทียบกับหัวข้อ 1 ก็แปลง่าย ๆ ว่า: <span> คือ <div> ที่เกิดมาเป็นพันธุ์แทรกในบรรทัด หน้าที่เหมือนกันเป๊ะ (เป็นที่แปะ class) ต่างกันแค่พันธุ์

โค้ดจากสไลด์หน้า 17:

<p>
  Hello, My name is
  <span class="joey-name" style="color: olive">Joey Wasin</span>
</p>

ชื่อเปลี่ยนสีเป็นเขียวมะกอกโดยที่ประโยคยังเป็นประโยคเดียว ไม่มีอะไรถูกดันลงบรรทัดใหม่ — นี่คือทั้งหมดที่ <span> มีให้

ทีนี้กับดักตัวจริง ลองสั่ง width กับ height ให้มันดู โค้ดข้างล่างใช้ class เดียวกันเป๊ะกับทั้ง <div> และ <span>:

<style>
  .box { background: #cfe3ff; width: 120px; height: 60px; }
</style>

<div class="box">div ที่สั่ง 120x60</div>
<span class="box">span ที่สั่ง 120x60</span>

<div> ได้กล่อง 120×60 ตามสั่ง · <span> ไม่สนใจทั้งสองค่า
ผมวัดของจริง: div ได้ 120×60 เป๊ะ · span ได้ 118×19 ซึ่งคือ "เท่าตัวอักษรพอดี" ทั้งกว้างและสูง
และเหมือนเดิม — ไม่มี error ไม่มีคำเตือน CSS ก็ยังอยู่ในไฟล์ครบทุกตัวอักษร มันแค่ไม่มีผล

แล้วถ้าอยากได้ทั้งสองอย่าง — อยู่ในบรรทัดเหมือน span แต่สั่งขนาดได้เหมือน div?
มีพันธุ์ที่สามชื่อ display: inline-block · จะเจอเต็ม ๆ ในบทเรื่อง CSS layout ตอนนี้แค่รู้ว่ามีทางออก พอ ไม่ต้องจำ
และมีข้อยกเว้นหนึ่งตัวที่จะเจอในหัวข้อ 7: <img> เป็น inline เหมือน <span> แต่สั่ง width/height ได้ผลจริง เพราะเนื้อของมันเป็นไฟล์ภาพจากข้างนอก ไม่ใช่ตัวอักษรในหน้า (ศัพท์ของมันคือ replaced element) · กฎ "inline แล้วสั่งขนาดไม่ได้" จึงใช้กับพวกที่เนื้อเป็นข้อความอย่าง <span> <a> <strong> เท่านั้น

6 · <header> <main> <footer> — ทำไมไม่ใช้ <div> ทุกอันไปเลย

สไลด์หน้า 18 บอกว่าสามตัวนี้ "represent areas of content in page"
คำถามที่สไลด์ไม่ได้ตอบและเป็นคำถามที่ถูกต้องที่สุดคือ: "แล้วมันต่างจาก <div> ตรงไหน"

คำตอบตรง ๆ ที่ต้องยอมรับก่อน คือ ตรงหน้าจอ: ไม่ต่างเลยแม้แต่พิกเซลเดียว ดูเอง:

<style>
  header, main, footer, div { background: #eef1f5; margin-bottom: 4px; }
</style>

<header>ฉันคือ header</header>
<main>ฉันคือ main</main>
<footer>ฉันคือ footer</footer>
<div>ฉันคือ div ธรรมดา</div>

สี่แถบเท่ากันหมด ผมวัดได้ 482px ทั้งสี่อัน เรียงลงมาเหมือนกันเป๊ะ และ display ของทั้งสี่คือ block เท่ากัน
ความต่างทั้งหมดจึงอยู่ในที่ที่ตามองไม่เห็น — และนั่นคือประเด็นทั้งหมดของหัวข้อนี้

🧰 เทียบให้เห็นภาพ — กล่องย้ายบ้านที่เขียนป้ายกับที่ไม่เขียน

ย้ายบ้านแล้วมีกล่องกระดาษ 20 ใบวางอยู่กลางห้อง เหมือนกันหมดทุกใบ
· ถ้าไม่เขียนอะไรข้างกล่อง = <div> ทั้งหมด — จะหาแก้วน้ำ ต้องเปิดไล่ทีละใบ
· ถ้าเขียนว่า "ครัว" "ห้องนอน" "เอกสารสำคัญ" = <header> <main> <footer> — กล่องหน้าตาเหมือนเดิมทุกอย่าง แต่เดินตรงไปหยิบใบที่ต้องการได้เลย

ที่ไม่เหมือนกัน: กล่องย้ายบ้านคุณเปิดดูเองได้ ไม่มีป้ายก็แค่เสียเวลา
แต่คนที่ใช้โปรแกรมอ่านหน้าจอ (screen reader) "เปิดดู" ทั้งหน้าพร้อมกันไม่ได้ — เขาได้ยินทีละชิ้นเรียงจากบนลงล่างเท่านั้น
ป้าย <main> คือทางลัดเดียวที่เขามีเพื่อกระโดดข้ามเมนู 30 ลิงก์ตรงไปที่เนื้อหา ไม่มีป้าย = ต้องฟังเมนูใหม่ทั้งชุดทุกหน้าที่เปิด

ผลที่ตามมาในทางปฏิบัติมีสามข้อ และทั้งสามข้อไม่มีข้อไหนโผล่บนหน้าจอเลย:

ใครได้ประโยชน์ได้อะไรถ้าใช้ <div> แทน
ผู้ใช้ screen reader กระโดดตรงไป <main> ได้ในคำสั่งเดียว ต้องไล่ฟังตั้งแต่ต้นหน้าใหม่ทุกครั้ง
Google และ search engine รู้ว่าเนื้อหาจริงของหน้าอยู่ตรงไหน แยกจากเมนูกับท้ายหน้า ต้องเดาเอง ซึ่งเดาผิดได้
คนที่มาแก้โค้ดต่อจากคุณ เห็นโครงหน้าครบใน 3 บรรทัด ไม่ต้องไล่อ่าน class ต้องอ่าน <div class="wrapper-outer"> แล้วเดาเอง
✅ กติกาที่ใช้ตัดสินใจได้จริงทุกครั้ง

ก่อนพิมพ์ <div> ให้ถามหนึ่งคำถาม: "ก้อนนี้มีชื่อเรียกในภาษาคนไหม"
ถ้ามี (หัวเรื่องหน้า · เนื้อหาหลัก · ท้ายหน้า · เมนู · บทความ) → ใช้แท็กที่มีชื่อนั้น
ถ้าไม่มี ("ก้อนที่ผมอยากจัดให้อยู่ด้วยกันเฉย ๆ") → <div> คือคำตอบที่ถูก และไม่ต้องรู้สึกผิด
<div> ไม่ใช่ของแย่ — มันแย่เฉพาะตอนที่มีแท็กที่ตรงกว่าให้ใช้แล้วไม่ใช้

🗣 ศัพท์ที่เพิ่งเรียนไป
semantic HTML
การเลือกแท็กตามความหมายของเนื้อหา ไม่ใช่ตามหน้าตาที่อยากได้ · ประโยคที่ใช้ได้ใน code review: “ตรงนี้เป็นเนื้อหาหลักของหน้า ใช้ <main> แทน <div> ดีกว่าไหม”
landmark
"จุดสังเกต" ที่ screen reader ใช้ทำเมนูลัดให้ผู้ใช้ · <header> <main> <footer> <nav> กลายเป็น landmark ให้เองโดยไม่ต้องตั้งค่าอะไร · พิมพ์ถาม AI ได้ว่า “list the landmark roles my page exposes and tell me which ones are missing”
accessibility (a11y)
การทำให้เว็บใช้ได้กับคนที่ใช้ไม่เหมือนเรา — ตาบอด · มือไม่สะดวก · เน็ตช้า · เขียนใน commit message ได้ว่า “a11y: replace layout divs with header/main/footer landmarks” เป็นคำที่ทีมฝั่งหน้าบ้านใช้กันทุกวัน

7 · <img> — และคำว่า alt ที่ไม่ใช่ของประดับ

สไลด์หน้า 19 บอกสองเรื่อง: ใช้ใส่รูป และ มันเป็น "self-closing tag" คือไม่มีแท็กปิด
เหตุผลง่ายมาก — แท็กปิดมีไว้บอกว่า "เนื้อข้างในจบตรงนี้" แต่ <img> ไม่มีเนื้อข้างใน ทุกอย่างของมันอยู่ใน attribute หมด จึงไม่มีอะไรให้ปิด

เขียนได้ทั้ง <img …> และ <img … /> — เบราว์เซอร์เข้าใจเหมือนกัน สไลด์ใช้แบบมี />

ในบทนี้ผมไม่ใช้ไฟล์รูปจากอินเทอร์เน็ต แต่ใช้รูปที่เขียนด้วยตัวอักษรล้วน ๆ ฝังอยู่ใน src เลย (เรียกว่า data URI) — ก็อปไปวางได้ ใช้ได้แม้ไม่มีเน็ต:

<img
  src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='64'%3E%3Crect width='64' height='64' rx='12' fill='%232f6feb'/%3E%3C/svg%3E"
  alt="สี่เหลี่ยมมุมมนสีน้ำเงิน" width="64" height="64">
<h1>Work Experience</h1>

สังเกตว่ารูปกับหัวข้ออยู่คนละบรรทัด ทั้งที่ในโค้ดเขียนติดกัน — เพราะ <img> เป็นพันธุ์แทรกในบรรทัด (display: inline) แต่หัวข้อเป็นพันธุ์กินทั้งบรรทัด จึงดันตัวเองลงมา · กฎจากหัวข้อ 1 ยังใช้ได้อยู่ทุกที่

สไลด์หน้า 19 ใช้ <h2> ตรงนี้ ผมเปลี่ยนเป็น <h1> ในกล่องตัวอย่าง เพราะกล่องนี้คือหน้าเว็บย่อ ๆ ที่จบในตัว ไม่ได้อยู่ใต้หัวข้ออะไร — หัวข้อบนสุดของหน้าจึงต้องเป็น <h1> ตามกฎในหัวข้อ 4
พฤติกรรมที่กำลังพิสูจน์ (block ดันบรรทัดใหม่) เหมือนกันทั้ง <h1> ถึง <h6> อยู่แล้ว

แล้ว alt คืออะไรกันแน่

alt คือข้อความที่ใช้แทนรูป เมื่อรูปไม่ได้ทำหน้าที่ของมัน ซึ่งเกิดขึ้นสองสถานการณ์ที่พบบ่อยกว่าที่คิด:

  1. คนที่มองไม่เห็นรูป — ผู้ใช้ screen reader จะได้ยิน alt อ่านออกมาแทนภาพ
  2. ตอนรูปโหลดไม่ขึ้น — เน็ตหลุด · ไฟล์ถูกลบ · พิมพ์ path ผิด · โดนไฟร์วอลล์บล็อก เบราว์เซอร์จะเอา alt มาแสดงตรงตำแหน่งรูปแทน

ทายก่อนดู — <img> สามอันนี้ชี้ไปหาไฟล์ที่ไม่มีอยู่จริงทั้งหมด ต่างกันแค่ alt:

<img src="ไม่มีไฟล์นี้.png" alt="แมวส้มนอนบนคีย์บอร์ด" width="200" height="60">
<img src="ไม่มีไฟล์นี้.png" alt="" width="200" height="60">
<img src="ไม่มีไฟล์นี้.png" width="200" height="60">

ผู้ใช้จะเห็นอะไรตรงตำแหน่งของแต่ละอัน? และอันที่ไม่มี alt เลย ต่างจากอันที่ alt="" ยังไง?

① มี alt ที่บรรยายภาพ → เห็นข้อความ "แมวส้มนอนบนคีย์บอร์ด" แทนที่รูป · ผมวัดกล่องได้ 171×26px คือมันหดลงมาเท่าข้อความ ไม่ใช่ 200×60 ตามที่สั่งไว้

② alt="" → เห็นที่ว่างเปล่า · กล่องยังเป็น 200×60 ตามที่สั่ง แต่ไม่มีตัวอักษรอะไรเลย · screen reader จะข้ามรูปนี้ไปเงียบ ๆ ซึ่งเป็นสิ่งที่ถูกต้องสำหรับรูปประดับที่ไม่ได้ให้ข้อมูลอะไร (เส้นคั่น · ลายพื้นหลัง)

③ ไม่มี alt เลย → หน้าจอเห็นคล้ายข้อ ② แต่ความหมายตรงข้ามกัน: screen reader ไม่รู้ว่ารูปนี้ควรข้ามหรือไม่ควรข้าม ส่วนใหญ่จึงเดาโดยอ่านชื่อไฟล์ออกมาดัง ๆ ผู้ใช้จะได้ยินประมาณ "ไม่มีไฟล์นี้ จุด พีเอ็นจี" ซึ่งไม่ช่วยอะไรเลย

สรุปที่ต้องจำ: alt="" กับ "ไม่ใส่ alt" ไม่ใช่สิ่งเดียวกัน
alt="" แปลว่า "ผมคิดแล้ว รูปนี้ข้ามได้" · ไม่ใส่เลยแปลว่า "ผมลืม"
ทุก <img> ต้องมี alt เสมอ — คำถามมีแค่ว่าจะใส่ว่าอะไร ไม่ใช่จะใส่ไหม

ของจริง — <img> สามอันชุดเดิม เติมแค่กรอบเส้นประเพื่อให้เห็นขอบเขตของแต่ละอัน:

<style>
  img { outline: 1px dashed #999; margin-right: 10px; }
</style>

<img src="ไม่มีไฟล์นี้.png" alt="แมวส้มนอนบนคีย์บอร์ด" width="200" height="60">
<img src="ไม่มีไฟล์นี้.png" alt="" width="200" height="60">
<img src="ไม่มีไฟล์นี้.png" width="200" height="60">

อันซ้ายสุดหดเหลือเท่าข้อความที่เขียนใน alt (วัดได้ 171×26) ส่วนอีกสองอันเป็นกล่องเปล่าขนาด 200×60 เท่ากันเป๊ะ
ทั้งสามเรียงต่อกันไปในบรรทัดเดียว เพราะ <img> เป็น inline ตามกฎในหัวข้อ 1 — ถ้าคุณอ่านบนจอแคบ (หรือย่อหน้าต่างให้แคบลง) จะเห็นมันตัดขึ้นบรรทัดใหม่เมื่อที่ไม่พอ เหมือนคำในประโยค ซึ่งก็คือพฤติกรรมของ inline เหมือนกัน ไม่ใช่คนละเรื่อง
อันที่สอง (alt="") กับอันที่สาม (ไม่ใส่ alt) ตาแยกไม่ออกเลย — และนั่นแหละคือประเด็น: ตาแยกไม่ออก แต่ screen reader แยกออก

⚠️ alt ในสไลด์หน้า 19 เป็นตัวอย่างของ alt ที่ไม่ช่วยอะไร

สไลด์เขียนว่า <img src="./assets/work-icon.png" alt="work-icon" />

alt ตัวนี้ลอกชื่อไฟล์มาเฉย ๆ คนที่มองไม่เห็นจะได้ยินคำว่า "work icon" ซึ่งบอกแค่ว่า "มีไอคอนอันหนึ่ง" — ไม่ได้บอกว่าไอคอนนั้นสื่ออะไร
ในกรณีนี้ ไอคอนวางอยู่ข้าง ๆ หัวข้อ <h2>Work Experience</h2> ที่พูดเรื่องเดียวกันอยู่แล้ว คำตอบที่ดีที่สุดจึงคือ alt="" — ปล่อยให้หัวข้อทำหน้าที่ ไม่ต้องให้ผู้ใช้ฟังคำเดิมสองรอบ
คำถามที่ใช้เขียน alt ให้ถูกทุกครั้ง: "ถ้ารูปนี้หายไป ผู้อ่านเสียข้อมูลอะไรไปบ้าง"
· เสียข้อมูล → เขียนข้อมูลนั้นลงไป · ไม่เสียอะไร (เพราะข้อความข้าง ๆ บอกอยู่แล้ว) → alt=""

🗣 ศัพท์ที่เพิ่งเรียนไป
void element
ชื่อจริงของ "self-closing tag" ที่สไลด์พูดถึง — แท็กที่ห้ามมีเนื้อข้างในและห้ามมีแท็กปิด · มีอยู่ไม่กี่ตัว: <img> <br> <hr> <input> <meta> <link> · ถ้าเผลอเขียน </img> เบราว์เซอร์จะเมินทิ้งเงียบ ๆ ไม่ฟ้อง
alt text
ข้อความแทนภาพ ไม่ใช่คำบรรยายภาพ · ถาม senior ได้ว่า “รูปนี้เป็น informative หรือ decorative” เพราะสองคำนี้ตัดสินได้ทันทีว่าจะเขียน alt ยาว ๆ หรือใส่ alt=""
data URI
วิธีฝังไฟล์ทั้งไฟล์ลงไปใน src ตรง ๆ แทนที่จะชี้ไปหาไฟล์ข้างนอก · ดีตรงไม่ต้องโหลดเพิ่ม แต่ใช้กับรูปเล็ก ๆ เท่านั้น เพราะมันบวมและ cache แยกไม่ได้ · บอก AI ว่า “inline this small SVG as a data URI” ได้เลย

8 · ทุกแท็กของบทนี้ในตารางเดียว

แท็กพันธุ์ใช้ตอนไหนจุดพลาดที่เจอบ่อยที่สุด
<div>blockมัดของเป็นก้อน เมื่อไม่มีแท็กที่มีความหมายตรงกว่าใส่ซ้อนกันโดยไม่มี class จะแปะ
<p>blockย่อหน้าข้อความเอา block ไปใส่ข้างใน แล้วเบราว์เซอร์แอบปิดให้
<h1>–<h6>blockหัวข้อ · เลือกระดับตามโครงเรื่องเลือกตามขนาดตัวอักษร แล้วข้ามระดับ
<span>inlineจับคำกลางประโยคเพื่อจัดสไตล์สั่ง width/height แล้วงงว่าไม่มีผล
<header>blockส่วนหัวของหน้า (หรือของบทความ)ใช้ <div> แทนทั้งที่ตัวจริงมีอยู่
<main>blockเนื้อหาหลัก · หน้าละหนึ่งอัน
<footer>blockท้ายหน้า · ลิขสิทธิ์ · ลิงก์ติดต่อ
<img>inlineใส่รูป · ไม่มีแท็กปิดลืม alt หรือใส่ alt เป็นชื่อไฟล์

โครงหน้าเว็บที่ประกอบครบจากสไลด์หน้า 18 หน้าตาเป็นแบบนี้ — และนี่คือโครงที่คุณจะใช้ซ้ำไปทั้งคอร์ส
(สไลด์ยังมี <h2>Introduce</h2> คั่นอยู่บนสุดของ <main> ด้วย ผมตัดออกเพื่อให้เห็นโครงสามชั้นชัด ๆ — ใส่กลับเข้าไปได้เลย):

<header>
  <h1>Resume</h1>
</header>
<main>
  <div class="joey-title">
    <p>Hello, My name is Joey Wasin</p>
    <p>I'm a software developer</p>
  </div>
</main>
<footer>
  <p>&copy; Copyright 2025</p>
</footer>

เห็น &copy; ในโค้ดแล้วออกมาเป็น © ไหม — นั่นเรียกว่า HTML entity · เป็นวิธีพิมพ์ตัวอักษรที่พิมพ์ตรง ๆ ไม่ได้หรือไม่สะดวก
ตัวที่ใช้บ่อยที่สุดสามตัวคือ &lt; (ได้ <) · &gt; (ได้ >) · &amp; (ได้ &)
คุณต้องใช้มันเวลาอยากโชว์โค้ด HTML บนหน้าเว็บ ไม่งั้นเบราว์เซอร์จะนึกว่าเป็นแท็กจริง

ทดสอบความเข้าใจ

1. เขียน <span class="box">ข้อความ</span> แล้วสั่ง CSS ว่า .box { width: 120px; height: 60px; } — จะเกิดอะไรขึ้น?

ผมวัดของจริงจากโค้ดชุดเดียวกันในหัวข้อ 5: div ได้ 120×60 เป๊ะ ส่วน span ได้ 118×19 ซึ่งเท่าตัวอักษรพอดี — ถูกเมินทั้งสองค่า
ตัวเลือกที่ 4 คือกับดักที่เนียนที่สุด เพราะฟังดูมีตรรกะ (inline = แนวนอน) และมันเกือบถูก — แต่ค่าที่วัดได้คือ 118px ไม่ใช่ 120px แปลว่า width ก็ไม่ได้มีผลเหมือนกัน
ตัวเลือกที่ 2 ผิดตรงจุดที่เป็นหัวใจของบทนี้ทั้งบท: ฝั่งหน้าบ้านแทบไม่มีอะไรฟ้องคุณ CSS ที่ไม่มีผลจะอยู่ในไฟล์ต่อไปเงียบ ๆ ไม่ต่างจาก HTML ที่โครงผิดในหัวข้อ 3
ตัวเลือกที่ 1 ผิดเพราะ CSS ไม่ได้มีผลเท่ากันกับทุก element — width/height ไม่มีผลกับ inline element ที่เนื้อเป็นข้อความอย่าง <span> (ข้อยกเว้นคือ <img> ซึ่งเป็น inline แต่สั่งขนาดได้ เพราะเนื้อเป็นไฟล์ภาพจากข้างนอก — เห็นของจริงในหัวข้อ 7)
จุดที่ต้องจำ: "สั่ง CSS แล้วไม่เกิดอะไรขึ้นเลย" คือสัญญาณอันดับหนึ่งว่า element นั้นเป็น inline อยู่ — เปิด DevTools ดู display ของมันก่อนทำอย่างอื่น

2. พิมพ์ <p>ก่อน<div>กลาง</div>หลัง</p> ลงไฟล์ แล้วเปิดดูใน DevTools — DOM ที่ได้เป็นยังไง?

ผมอ่าน innerHTML ของจริงออกมาได้เป๊ะ ๆ แบบนี้:
<p>ก่อน</p><div>กลาง</div>หลัง<p></p>
ครบทั้งสามอาการ: </p> ถูกแทรกให้เอง · คำว่า "หลัง" หลุดออกมาลอยนอก <p> · และ </p> ที่คุณพิมพ์ไว้กลายเป็น <p></p> เปล่า
ตัวเลือกที่ 2 คือความเข้าใจผิดที่พบบ่อยที่สุด — <p> มี content model ที่รับได้แค่ phrasing content ซึ่งไม่รวม <div>
ตัวเลือกที่ 3 ผิดเพราะ parser ไม่ลบเนื้อหาทิ้ง มันย้ายที่ให้เท่านั้น
ตัวเลือกที่ 4 ผิดที่แนวคิด: HTML ไม่มี parse error ให้ฟ้อง — spec เขียนวิธี "ซ่อม" ไว้ให้ทุกกรณี
จุดที่ต้องจำ: สิ่งที่คุณพิมพ์กับ DOM ที่เบราว์เซอร์สร้างอาจไม่ใช่อันเดียวกัน — เวลา CSS หรือ JavaScript ทำตัวประหลาด ให้ไปดู DOM จริงใน DevTools ก่อนเสมอ อย่าอ่านแต่ไฟล์ต้นฉบับ

3. เปลี่ยน <div id="main"> เป็น <main> ทั้งหน้า — อะไรเปลี่ยนไปบ้าง?

ผมวัดจริงในหัวข้อ 6: header · main · footer · div ได้ display: block เหมือนกันหมด และกว้าง 482px เท่ากันทั้งสี่ — ไม่ต่างกันสักพิกเซล
ตัวเลือกที่ 1 กับ 3 ผิดตรงที่คิดว่ามีสไตล์เริ่มต้นซ่อนอยู่ · ไม่มี
ตัวเลือกที่ 2 คือตัวที่อันตรายที่สุด เพราะถูกครึ่งเดียว — "ที่เห็น" ไม่เปลี่ยนจริง แต่คำว่า "ที่มองไม่เห็นก็ไม่เปลี่ยน" คือส่วนที่ผิด และเป็นความเชื่อที่ทำให้คนใช้ <div> ทั้งเว็บ
<main> กลายเป็น landmark ให้เอง ผู้ใช้ screen reader จึงกระโดดข้ามเมนูมาที่เนื้อหาได้ในคำสั่งเดียว
จุดที่ต้องจำ: ประโยชน์ของ semantic HTML ไม่โผล่บนหน้าจอของคุณ — ถ้าตัดสินใจโดยดูแค่ว่า "เปลี่ยนแล้วหน้าเหมือนเดิม" คุณจะเลือกผิดทุกครั้ง

4. หน้าเว็บมีไอคอนรูปกระเป๋าวางข้าง ๆ หัวข้อ <h2>Work Experience</h2> ที่บอกเรื่องเดียวกันอยู่แล้ว — ควรเขียน alt ว่าอะไร?

คำถามที่ใช้ตัดสินคือ "ถ้ารูปนี้หายไป ผู้อ่านเสียข้อมูลอะไร" — ในกรณีนี้ไม่เสียอะไรเลย เพราะหัวข้อข้าง ๆ พูดเรื่องเดียวกันอยู่แล้ว
ตัวเลือกที่ 3 คือกับดักตัวจริง เพราะเหตุผลของมันถูก (รูปนี้เป็นของประดับจริง) แต่วิธีทำผิด — alt="" กับ "ไม่ใส่ alt" ให้ผลคนละอย่าง: alt="" สั่งให้ screen reader ข้ามเงียบ ๆ ส่วนการไม่ใส่ทำให้มันไม่รู้ แล้วมักไปอ่านชื่อไฟล์ออกมาแทน
ตัวเลือกที่ 1 คือ alt ที่สไลด์หน้า 19 ใช้จริง — ผู้ใช้จะได้ยินคำว่า "work icon" ตามด้วยหัวข้อ "Work Experience" คือฟังเรื่องเดิมสองรอบ
ตัวเลือกที่ 4 ยิ่งแย่กว่า เพราะยาวกว่าและยังซ้ำอยู่ดี — alt คือข้อความแทนภาพ ไม่ใช่คำบรรยายภาพ
จุดที่ต้องจำ: ทุก <img> ต้องมี alt เสมอ คำถามมีแค่ว่าใส่ว่าอะไร ไม่ใช่ว่าจะใส่หรือไม่ใส่

ภารกิจ

🛠️ ภารกิจ — สร้าง resume.html ของตัวเอง แล้วพิสูจน์ว่ามันถูกจริง ไม่ใช่แค่ดูดี

เป้าหมายไม่ใช่ "หน้าออกมาสวย" — หน้าสวยพิสูจน์อะไรไม่ได้เลยในบทนี้ เพราะทุกกับดักที่เจอวันนี้ ไม่มีอันไหนทำให้หน้าดูผิด เป้าหมายคือเขียนคำสั่งตรวจที่บอกผ่าน/ไม่ผ่านได้ แล้วรันแล้วผ่านจริง

  1. ตั้งโครง: สร้างไฟล์ resume.html ใส่ <header> · <main> · <footer> อย่างละหนึ่ง
    ใน <header> ใส่ <h1> ชื่อคุณ · ใน <main> ใส่ <h2> อย่างน้อยสองหัวข้อ (เช่น เกี่ยวกับฉัน · ประสบการณ์)
  2. เติมเนื้อ: ใต้แต่ละ <h2> ใส่ <div class="…"> ครอบ <p> สองสามอัน · แล้วเลือกคำเดียวกลางประโยคมาห่อด้วย <span> แล้วเปลี่ยนสีมันด้วย style="color: …"
  3. ใส่รูปโดยไม่ใช้เน็ต: ก็อป data URI จากหัวข้อ 7 ไปใส่เป็น <img> หนึ่งอัน พร้อม alt ที่คุณตอบคำถามได้ว่าทำไมเขียนแบบนั้น
  4. ทำให้พัง แล้วดู DOM จริง: จงใจเอา <div> ไปใส่ใน <p> หนึ่งจุด
    เปิด DevTools (คลิกขวา → Inspect) แท็บ Elements แล้วเทียบกับไฟล์ที่คุณพิมพ์
    ต้องเห็น <p></p> เปล่าที่คุณไม่ได้พิมพ์ ถ้าไม่เห็น แปลว่าคุณวางไม่ตรงจุด
    เห็นแล้วให้แก้กลับ โดยสลับเป็น <div> ครอบ <p>
  5. เขียนชุดตรวจแล้วรันจริง: เปิด DevTools แท็บ Console แล้ววางทีละคำสั่ง (คำสั่งที่ 5 ยาว 3 บรรทัด ให้วางทั้งก้อนแล้วค่อยกด Enter)
    ทั้งห้าคำสั่งต้องได้ true ถ้าได้ false คือยังไม่ผ่าน ให้กลับไปแก้ HTML
    อย่าแก้คำสั่งตรวจให้มันผ่าน — นั่นคือการโกงตัวเอง
    // 1. มี main อันเดียว
    document.querySelectorAll('main').length === 1
    
    // 2. มี h1 อันเดียว
    document.querySelectorAll('h1').length === 1
    
    // 3. ทุกรูปมี alt (จะว่างก็ได้ แต่ต้องมี attribute)
    [...document.images].every(i => i.hasAttribute('alt'))
    
    // 4. ไม่มี p ที่ว่างเปล่า (แปลว่าไม่มีจุดที่ parser แอบซ่อมให้)
    [...document.querySelectorAll('p')].every(p => p.textContent.trim() !== '')
    
    // 5. หัวข้อไม่ข้ามระดับ (ห่างกันได้ไม่เกินทีละ 1 ขั้น)
    [...document.querySelectorAll('h1,h2,h3,h4,h5,h6')]
      .map(h => +h.tagName[1])
      .every((n, i, a) => i === 0 || n <= a[i - 1] + 1)
  6. ทดสอบตอนรูปโหลดไม่ขึ้น: เปลี่ยน src ของ <img> ให้ชี้ไปไฟล์ที่ไม่มีจริง แล้ว refresh
    อ่านหน้าตัวเองใหม่ทั้งหน้าแล้วตอบว่า "ยังรู้เรื่องไหม" — ถ้ายังรู้เรื่อง alt คุณเขียนถูก
    เสร็จแล้วเปลี่ยน src กลับ
  7. ท้าทาย: เพิ่มคำสั่งตรวจที่หกด้วยตัวเอง — ตรวจว่าไม่มี <div> ตัวไหนที่ไม่มี class (คำใบ้: [...document.querySelectorAll('div')].every(…))
    แล้วตัดสินใจเองว่ากฎข้อนี้ควรมีอยู่ไหม — มีเหตุผลทั้งสองฝั่ง ตอบให้ได้ว่าคุณเลือกฝั่งไหนเพราะอะไร

ตอนที่คุณเขียน <p>ก่อน<div>กลาง</div>หลัง</p> DOM ที่ได้จริงคือ <p>ก่อน</p><div>กลาง</div>หลัง<p></p>
<p></p> ตัวสุดท้ายไม่มีข้อความอยู่ข้างในเลย — textContent.trim() ของมันจึงเป็นสตริงว่าง และคำสั่งตรวจคืน false ทันที

นี่คือรูปแบบของ "การเขียนเทส" ที่ใช้ได้กับ HTML: คุณหาสิ่งที่วัดได้ซึ่งจะปรากฏก็ต่อเมื่อโค้ดผิด แล้วตรวจว่ามันไม่มีอยู่
เหมือนฝั่ง Go เป๊ะ ๆ — ต่างกันแค่ที่นี่คุณ assert บน DOM แทนที่จะ assert บนค่าที่ฟังก์ชันคืนมา
และเหมือนกับฝั่ง Go อีกข้อ: เทสที่ไม่เคยเห็นมันแดง คือเทสที่ยังพิสูจน์อะไรไม่ได้ ก่อนเชื่อคำสั่งที่ 4 ให้ลองใส่กับดักกลับเข้าไปแล้วดูว่ามันได้ false จริงไหม

ติดตรงไหนถามได้เลย — โดยเฉพาะสามคำถามนี้ ที่ถามตอนนี้คุ้มกว่าถามทีหลังมาก:
· "ก้อนนี้ควรใช้ <div> หรือมีแท็กที่ตรงกว่า"
· "รูปนี้ควรเขียน alt ว่าอะไร"
· "ทำไมสั่ง CSS แล้วไม่เกิดอะไรขึ้น" (คำตอบมักอยู่ในหัวข้อ 1 ของบทนี้)

อ้างอิง