บทความ › เว็บและแอป

ให้ AI ตรวจหน้าเว็บบน มือถือ เอง ด้วย สคริปต์ 30 บรรทัด แทนการนั่งไล่ดูทุกหน้า

AI มองจอไม่เห็น แต่วัดได้ สคริปต์เล็ก ๆ โหลดทุกหน้าใน iframe กว้าง 375px แล้วบอกว่าอะไรล้นจอ หัวข้อไหนตกขอบ ตัวหนังสือใน SVG ชนกรอบตรงไหน ใช้จริงก่อน deploy ทุกครั้ง

กลาง อ่าน 12 นาที 21 ตุลาคม 2569

อ่านจบแล้วจะรู้
  • การล้นจอบนมือถือเป็นตัวเลขที่วัดได้ scrollWidth กับ getBoundingClientRect บอกได้ชัดกว่าตามอง
  • iframe กว้าง 375px บน dev server ทำให้ตรวจได้หลายสิบหน้าสองภาษาในลูปเดียว โดยไม่ต้องเปิดทีละหน้า
  • ตัวการส่วนใหญ่คือ inline code ยาว แท็บในบล็อกโค้ด และฟอนต์หัวข้อกว้าง แก้ด้วย CSS ไม่กี่บรรทัด
  • สคริปต์ตัดสินไม่ได้ว่า "สวย" ไหม จบด้วย screenshot หนึ่งภาพให้คนมองสั้น ๆ

กฎข้อหนึ่งของเว็บนี้คือ ก่อนบอกว่างาน UI เสร็จ ต้องดูในเบราว์เซอร์ที่ความกว้าง 375px ทั้งสองภาษา ปัญหาคือคนที่แก้โค้ดส่วนใหญ่เป็น AI coding agent ซึ่งไม่มีตา ส่วนคนที่มีตาก็ลืมดูบ่อย บทความนี้เล่าวิธีที่ใช้จริงกับ 6amdev.com และเว็บพี่น้องอีกสี่เว็บ คือเปลี่ยนคำว่า “ดู” เป็น “วัด” ด้วย JavaScript ประมาณ 30 บรรทัดที่รันในเบราว์เซอร์ แล้วให้ AI รันเองก่อน deploy ทุกครั้ง

ทดสอบกับ Chromium รุ่นปัจจุบัน (ตุลาคม 2026) บน dev server ของ Astro (npm run dev ที่ localhost:4321) ใช้ได้กับเว็บ static ทุกแบบที่รันบนเครื่องตัวเอง

ปัญหา: AI มองไม่เห็น ส่วนคนลืม

AI agent แก้ CSS ได้ เขียน Markdown ได้ แต่ถ้าถามว่า “หน้านี้บนมือถือล้นจอไหม” มันตอบจากการอ่านโค้ด ไม่ใช่จากของจริง และคำตอบมักเป็น “น่าจะไม่ล้น” ซึ่งไม่มีค่าอะไร

ฝั่งคนก็ไม่ดีกว่ามาก เว็บนี้มีบทความสองภาษาเกือบสามสิบเรื่อง รวมหน้าหมวด หน้าซีรีส์ หน้าแรก ทุกครั้งที่แก้ global.css หนึ่งบรรทัด หน้าไหนก็พังได้ ไม่มีใครเปิดดูครบทุกหน้าทุกครั้งจริง ๆ

หน้าที่พังบนมือถือมักพังแบบเงียบ หน้ายังโหลดได้ ตัวหนังสือยังอ่านได้ แค่เลื่อนซ้ายขวาได้นิดหนึ่ง คนทำไม่สังเกต คนอ่านรู้ทันที

แนวคิด: วัดแทนการมอง

สิ่งที่ตามองหาบนมือถือ แปลเป็นตัวเลขได้เกือบหมด

  • หน้าเลื่อนซ้ายขวาได้ไหม = documentElement.scrollWidth มากกว่า clientWidth หรือเปล่า
  • ตัวไหนล้น = องค์ประกอบที่ getBoundingClientRect().right เกินขอบจอ
  • หัวข้อตกขอบไหม = h1 h2 และวลีที่เราห้ามตัดคำ (class nw ที่ใช้กับคำทับศัพท์ไทย) ขวาเกินจอหรือไม่
  • ตัวหนังสือในแผนภาพ SVG ชนกรอบไหม = getBBox() ของ <text> เทียบกับ <rect> ที่มันอยู่ข้างใน

ที่เหลือคือทำให้ตรวจได้หลายหน้าโดยไม่ต้องเปิดทีละหน้า วิธีคือสร้าง <iframe> ซ่อนไว้ กว้าง 375px หนึ่งอันต่อหนึ่ง URL รอให้โหลดเสร็จ แล้วอ่าน iframe.contentDocument ทำได้เพราะ dev server เป็น origin เดียวกับหน้าที่เรายืนอยู่

375 px1231หัวข้อยาวเกินจอ2โค้ดในบรรทัดดันหน้าให้กว้าง3ป้ายใน SVG ชนขอบกล่องวัดด้วย getBoundingClientRect และ getBBox ไม่ต้องมอง
สามอย่างที่ตัวเลขบอกได้ โดย AI ไม่ต้องมองจอ

สคริปต์ ทีละส่วน

ฟังก์ชันเดียว รับรายการ URL คืนตารางผล รันจาก console ของหน้าใดก็ได้บน dev server หรือให้ AI agent รันผ่าน browser tool ที่สั่ง JavaScript บนหน้าได้

// รันบนหน้าใดก็ได้ของ dev server (origin เดียวกัน) คืนหนึ่งแถวต่อหนึ่ง URL
async function checkMobile(urls, width = 375) {
  const rows = [];
  for (const url of urls) {
    // 1. โหลดหน้าใน iframe ซ่อนไว้ กว้างเท่ามือถือ แล้วรอจนโหลดเสร็จ
    const f = document.createElement('iframe');
    f.style.cssText = `position:fixed;left:-9999px;width:${width}px;height:800px`;
    f.src = url;
    document.body.appendChild(f);
    await new Promise(r => (f.onload = r));
    const d = f.contentDocument, W = d.documentElement.clientWidth, problems = [];
    // 2. หน้าเลื่อนซ้ายขวาได้กี่ px
    const overflow = Math.max(0, d.documentElement.scrollWidth - W);
    // 3. หาตัวการ: ทุกตัวที่ขอบขวาเกินจอ ข้าม pre (บล็อกโค้ดเลื่อนได้ตั้งใจ) เอาเฉพาะตัวลึกสุด
    const wide = [...d.querySelectorAll('body *')].filter(e =>
      !e.closest('pre') && e.getBoundingClientRect().right > W + 1);
    wide.filter(e => !wide.some(c => c !== e && e.contains(c)))
      .forEach(e => problems.push(`${e.tagName.toLowerCase()}: ${e.textContent.trim().slice(0, 50)}`));
    // 4. หัวข้อและวลีห้ามตัดคำ ที่ตกขอบ
    d.querySelectorAll('h1, h2, .nw').forEach(e => {
      if (e.getBoundingClientRect().right > W + 1) problems.push(`heading: ${e.textContent.trim().slice(0, 50)}`);
    });
    // 5. แผนภาพ SVG: text ที่หลุด viewBox หรือกว้างกว่ากล่องที่มันอยู่ (เผื่อขอบ 2px)
    const svgs = d.querySelectorAll('figure.diagram svg');
    svgs.forEach(svg => {
      const vb = svg.viewBox.baseVal, rects = [...svg.querySelectorAll('rect')].map(r => r.getBBox());
      svg.querySelectorAll('text').forEach(t => {
        const b = t.getBBox(), cx = b.x + b.width / 2, cy = b.y + b.height / 2;
        const box = rects.find(r => cx >= r.x && cx <= r.x + r.width && cy >= r.y && cy <= r.y + r.height);
        const outside = b.x < vb.x || b.x + b.width > vb.x + vb.width;
        const wider = box && (b.x < box.x + 2 || b.x + b.width > box.x + box.width - 2);
        if (outside || wider) problems.push(`svg: ${t.textContent}`);
      });
    });
    // 6. หนึ่งแถวต่อหน้า แล้วเก็บกวาด
    rows.push({ url, overflow, diagrams: svgs.length, problems });
    f.remove();
  }
  return rows;
}

เรียกใช้กับทั้งสองภาษาในลูปเดียว แล้วพิมพ์เป็นตาราง

const slugs = ['security-headers-nginx', 'money-never-float', 'roblox-ep4-complete-obby'];
const urls = ['/', '/en/', ...slugs.flatMap(s => [`/blog/${s}/`, `/en/blog/${s}/`])];
console.table(await checkMobile(urls));

จุดที่ควรรู้ในแต่ละส่วน

ส่วน 1 iframe ซ่อนด้วย left:-9999px ไม่ใช่ display:none เพราะองค์ประกอบใน iframe ที่ display:none จะไม่มีขนาด วัดอะไรไม่ได้

ส่วน 2 clientWidth ของ documentElement อาจน้อยกว่า 375 เล็กน้อยถ้ามี scrollbar แนวตั้ง ถึงอ่านค่าจริงแทนที่จะใช้ตัวเลข 375 ตรง ๆ

ส่วน 3 ถ้าพิมพ์ทุกตัวที่ล้น จะได้ main article p และ code ซ้อนกันสี่บรรทัดสำหรับปัญหาเดียว การเก็บเฉพาะตัวที่ไม่มีลูกล้นด้วย ทำให้เหลือแค่ตัวการจริง ส่วน pre ข้ามทั้งก้อน เพราะบล็อกโค้ดเรามี overflow-x:auto ให้เลื่อนในตัวเองอยู่แล้ว

ส่วน 5 หา rect ที่ครอบจุดกึ่งกลางของ text แล้วเทียบช่วง x เผื่อขอบ 2px ถ้า text ไม่ได้อยู่ในกล่องไหน (เช่น ป้ายบนลูกศร) ตรวจแค่ว่าหลุด viewBox ไหม

สิ่งที่มันจับได้จริง

ผลจากการใช้งานจริงช่วงสองสัปดาห์ที่ผ่านมา ทุกรายการพบก่อน deploy

สิ่งที่สคริปต์รายงานสาเหตุทางแก้
หน้าบทความ Roblox กว้าง 508px code: otherPart.Parent:FindFirstChildWhichIsA("Humanoid")inline code ยาวตัดคำไม่ได้ ดันทั้งหน้าออกไป 133pxcode { overflow-wrap: anywhere } เฉพาะ inline ไม่ใช่ใน pre
บล็อกโค้ดเลื่อนขวาไกลผิดปกติไฟล์ Lua ใช้แท็บ เบราว์เซอร์แสดงแท็บเป็น 8 ช่องpre { tab-size: 4 }
td: Humanoid … ตารางล้นตารางมี display:block; overflow-x:auto อยู่แล้ว ตัวการคือ inline code ในช่องที่เป็น <strong>หายไปเองหลังแก้ข้อแรก ไม่ต้องแตะตาราง
heading: … หน้าแรกฉบับอังกฤษของเว็บพี่น้องหัวข้อ hero ใช้ฟอนต์ display ตัวกว้าง คำอังกฤษยาวไม่ตัดแก้ใน CSS ที่ใช้ร่วมกัน ทุกเว็บได้พร้อมกัน
svg: … แผนภาพเส้นทาง obbyป้ายข้อความทับขอบกล่องและทับลูกศร มองผ่าน ๆ ไม่เห็นเลื่อน x ของ text ใน scripts/diagrams.py แล้วรันใหม่

สังเกตแถวที่สาม ถ้าดูด้วยตาจะโทษตาราง แล้วเสียเวลาแก้ CSS ของตาราง แต่ตัวที่ล้นลึกสุดคือ code ข้างใน strong พอแก้ inline code ตารางก็หายล้นตาม นี่คือประโยชน์ของการเก็บเฉพาะตัวลึกสุด

หลายรายการในตารางเป็นปัญหาที่ “เกือบไม่เห็น” บนจอใหญ่ หน้าล้นไป 20px ดูในเดสก์ท็อปไม่มีทางรู้ แต่ตัวเลขไม่โกหก

สิ่งที่สคริปต์ตัดสินไม่ได้

สคริปต์ตอบได้แค่ว่า “ล้นไหม ชนไหม” ไม่ได้ตอบว่า “ดูดีไหม” ระยะห่างแปลก สีซีดในธีมมืด ปุ่มเล็กจนกดยาก รูปที่ถูกครอปจนความหมายหาย พวกนี้ยังต้องใช้ตาคน

วิธีที่เราใช้คือให้ AI agent รันสคริปต์จนตารางสะอาด แล้วถ่าย screenshot ที่ความกว้าง 375px หนึ่งภาพต่อภาษา ส่งมาให้คนมองไม่กี่วินาที งานตาคนลดจาก “ไล่ดูสามสิบหน้า” เหลือ “มองสองภาพ”

อีกข้อจำกัดคือ iframe อ่านข้างในได้เฉพาะ origin เดียวกัน จึงใช้กับ dev server ของเราเท่านั้น ใช้ตรวจเว็บคนอื่น หรือเว็บจริงของเราที่มี X-Frame-Options: DENY ไม่ได้ ถ้าต้องตรวจเว็บจริง ให้เปิดหน้านั้นตรง ๆ แล้วรันส่วนที่ 2 ถึง 5 กับ document ปัจจุบันแทน

ลองทำใน 10 นาที

  1. รัน dev server ของเว็บ เปิดหน้าใดก็ได้ในเบราว์เซอร์
  2. เปิด DevTools (F12) แท็บ Console วางฟังก์ชัน checkMobile ด้านบน
  3. เรียก console.table(await checkMobile(['/', '/about/'])) ด้วย path ของเว็บคุณเอง
  4. ถ้าช่อง overflow ไม่ใช่ 0 อ่านช่อง problems แล้วตามไปแก้ตัวนั้นตรง ๆ
  5. ถ้าใช้ AI coding agent ที่มี browser tool เพิ่มกฎในไฟล์คำสั่งของโปรเจกต์ว่า ก่อนบอกว่างาน UI เสร็จ ให้รันฟังก์ชันนี้กับทุกหน้าที่แก้ แล้วแปะตารางผลมาด้วย

ข้อ 5 คือจุดที่เปลี่ยนเกม จากที่ต้องมีคนจำว่าต้องตรวจ กลายเป็น AI ตรวจเองทุกครั้งเพราะเป็นกฎของโปรเจกต์ และเมื่อมันเห็นตัวการชัด ๆ ในตาราง มันแก้ต่อได้เลยในรอบเดียว

ข้อผิดพลาดที่เจอบ่อย

  • ซ่อน iframe ด้วย display:none แล้วทุกค่าเป็น 0 คิดว่าผ่าน
  • อ่าน contentDocument ก่อน onload ได้ null หรือหน้าว่าง
  • ตรวจแค่ภาษาเดียว ภาษาอังกฤษล้นบ่อยกว่าเพราะคำยาวและฟอนต์ display กว้างกว่า
  • ไม่ข้าม pre แล้วรายงานบล็อกโค้ดทุกก้อนเป็นปัญหา ทั้งที่ตั้งใจให้เลื่อนได้
  • แก้ที่ตัวนอก (ตาราง, section) ทั้งที่ตัวที่ล้นจริงคือ inline code ข้างใน
  • ใช้ overflow-x: hidden กับ body เพื่อปิดอาการ หน้าไม่เลื่อนแล้ว แต่ข้อความส่วนที่ล้นถูกตัดหายไปเลย
  • รันสคริปต์ครั้งเดียวตอนทำเว็บเสร็จ แล้วไม่รันอีกหลังเพิ่มบทความใหม่ ซึ่งคือตอนที่ inline code ยาว ๆ มักโผล่มา

คำถามที่พบบ่อย

ทำไมไม่ใช้ DevTools โหมดมือถือหรือ Lighthouse

ใช้ได้ แต่ต้องมีคนเปิดทีละหน้า และ Lighthouse ไม่บอกว่าองค์ประกอบไหนล้น สคริปต์นี้ตรวจทุก URL ในลูปเดียวและชี้ตัวการให้ AI แก้ต่อได้ทันที จึงเหมาะกับการทำซ้ำก่อน deploy

ใช้กับเว็บคนอื่นหรือเว็บที่ขึ้นจริงแล้วได้ไหม

อ่านข้างใน iframe ได้เฉพาะหน้าที่มาจาก origin เดียวกัน จึงใช้กับ dev server ของเราเอง ถ้าเว็บจริงมี X-Frame-Options DENY ก็ฝังไม่ได้ด้วย ให้เปิดหน้านั้นตรง ๆ แล้วรันส่วนตรวจกับ document ปัจจุบันแทน

ต้องใช้ AI ไหม

ไม่จำเป็น วางฟังก์ชันใน console ของ DevTools แล้วเรียกด้วยมือได้ แต่จุดที่คุ้มคือให้ AI รันเองหลังแก้โค้ดทุกครั้ง แล้วแก้สิ่งที่พบต่อโดยไม่ต้องมีคนเตือน

คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ

อ่านต่อ

#เว็บกลาง

สี่เว็บจาก เอนจิน เดียว แชร์โค้ดด้วย Astro monorepo แต่หน้าตาไม่เหมือนกัน

แยก core/ กับโฟลเดอร์ต่อเว็บ ให้สี่เว็บใช้ layout i18n และ deploy ชุดเดียว ต่างกันแค่สี ฟอนต์ และหน้าแรก ผ่าน design tokens พร้อมบั๊กจริงที่แก้ครั้งเดียวได้ทั้งสี่เว็บ

อ่าน 15 นาที
#เว็บกลาง

CSS anchor positioning ใช้ได้ทุก browser แล้ว: tooltip และเมนู ไม่ต้องเขียน JS

anchor positioning เป็น Baseline ตั้งแต่ Firefox 147 ผูก tooltip หรือเมนูกับปุ่มด้วย CSS ล้วน หลบขอบจอเองด้วย position-try-fallbacks พร้อมทางสำรองสำหรับ browser เก่า

อ่าน 9 นาที
#เว็บเริ่มต้น

เปิด dialog และ popover ไม่ต้องเขียน JS: command และ commandfor ใน HTML

ปุ่ม HTML สั่งเปิดปิด dialog และ popover ได้เองด้วย command กับ commandfor ใช้ได้ทุกเบราว์เซอร์หลักตั้งแต่ธันวาคม 2025 ค่าที่มีให้ คำสั่งของเราเอง และทางสำรอง

อ่าน 11 นาที