ให้ AI ตรวจหน้าเว็บบน มือถือ เอง ด้วย สคริปต์ 30 บรรทัด แทนการนั่งไล่ดูทุกหน้า
AI มองจอไม่เห็น แต่วัดได้ สคริปต์เล็ก ๆ โหลดทุกหน้าใน iframe กว้าง 375px แล้วบอกว่าอะไรล้นจอ หัวข้อไหนตกขอบ ตัวหนังสือใน SVG ชนกรอบตรงไหน ใช้จริงก่อน deploy ทุกครั้ง
- การล้นจอบนมือถือเป็นตัวเลขที่วัดได้ 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เกินขอบจอ - หัวข้อตกขอบไหม =
h1h2และวลีที่เราห้ามตัดคำ (classnwที่ใช้กับคำทับศัพท์ไทย) ขวาเกินจอหรือไม่ - ตัวหนังสือในแผนภาพ SVG ชนกรอบไหม =
getBBox()ของ<text>เทียบกับ<rect>ที่มันอยู่ข้างใน
ที่เหลือคือทำให้ตรวจได้หลายหน้าโดยไม่ต้องเปิดทีละหน้า วิธีคือสร้าง <iframe> ซ่อนไว้ กว้าง 375px หนึ่งอันต่อหนึ่ง URL รอให้โหลดเสร็จ แล้วอ่าน iframe.contentDocument ทำได้เพราะ dev server เป็น origin เดียวกับหน้าที่เรายืนอยู่
สคริปต์ ทีละส่วน
ฟังก์ชันเดียว รับรายการ 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 ยาวตัดคำไม่ได้ ดันทั้งหน้าออกไป 133px | code { 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 นาที
- รัน dev server ของเว็บ เปิดหน้าใดก็ได้ในเบราว์เซอร์
- เปิด DevTools (F12) แท็บ Console วางฟังก์ชัน
checkMobileด้านบน - เรียก
console.table(await checkMobile(['/', '/about/']))ด้วย path ของเว็บคุณเอง - ถ้าช่อง
overflowไม่ใช่ 0 อ่านช่องproblemsแล้วตามไปแก้ตัวนั้นตรง ๆ - ถ้าใช้ 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 รันเองหลังแก้โค้ดทุกครั้ง แล้วแก้สิ่งที่พบต่อโดยไม่ต้องมีคนเตือน
คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ