ประกอบเว็บด้วย HTML หลายหน้าเล็กๆ แทน SPA สไตล์ Jim Nielsen
Tutorial ทำตามแนวคิด Jim Nielsen ประกอบเว็บจาก HTML หลายหน้าเล็กๆ ใช้ navigation ของ browser แทน JavaScript

นักพัฒนาที่ทำงานกับ React หรือ Vue มานาน มักคุ้นเคยกับการเปิดเมนูด้วย JavaScript, สลับ State ภายใน Component และจัดการแอนิเมชันผ่าน CSS Transition ในหน้าเว็บหน้าเดียว (DOM เดียวกัน) รูปแบบนี้กลายเป็นมาตรฐานตั้งต้นของ Frontend ยุคใหม่ จนหลายคนอาจลืมไปแล้วว่า แท้จริงแล้วเบราว์เซอร์สามารถจัดการสิ่งเหล่านี้ได้ด้วยตัวเองมาตั้งแต่ต้น
เมื่อวันที่ 3 พฤษภาคม 2026 Jim Nielsen นักพัฒนาเว็บและเจ้าของบล็อก blog.jim-nielsen.com ได้ตีพิมพ์บทความชื่อ "Reminder: You Can Stitch Together Lots of Little HTML Pages With Navigations For Interactions" เพื่อสะท้อนมุมมองว่า การโต้ตอบ (Interaction) จำนวนมากบนเว็บไซต์ที่เราคุ้นชินกับการเขียนด้วย JavaScript สามารถทำได้ง่ายๆ เพียงแค่แยกออกเป็นไฟล์ HTML หลายๆ หน้า แล้วปล่อยให้ระบบ Navigation พื้นฐานของเบราว์เซอร์ทำหน้าที่แทน เสริมด้วย CSS View Transitions เพื่อให้การสลับหน้าเนียนตา
บทความนี้จะพาลงมือสร้างเว็บตัวอย่างตามแนวคิดของ Nielsen ทีละขั้นตอน โดยสร้างเว็บ 4 หน้าที่มีเมนูหลัก เปลี่ยนหน้าด้วยลิงก์ HTML ธรรมดา ใช้ View Transitions จัดการแอนิเมชัน และคุม History ของเบราว์เซอร์ด้วย document.referrer ซึ่งเมื่อทำเสร็จ เว็บจะสามารถทำงานได้สมบูรณ์แม้ผู้ใช้จะปิด JavaScript ก็ตาม
สิ่งที่ต้องเตรียมก่อนเริ่ม
ตัวอย่างนี้ใช้เพียงเว็บเบราว์เซอร์ โปรแกรมแก้ไขโค้ด และ Local Web Server พื้นฐาน โดยไม่ต้องติดตั้ง Framework หรือเครื่องมือ Build ซับซ้อน:
- เว็บเบราว์เซอร์ที่รองรับ View Transitions API: Chrome 111+, Safari 18+ หรือ Firefox 132+ ขึ้นไป (หากเปิดด้วยเบราว์เซอร์รุ่นเก่า เว็บจะยังคงเปลี่ยนหน้าได้ตามปกติ เพียงแต่จะไม่แสดงแอนิเมชัน)
- Text Editor: VS Code, Cursor, Zed หรือโปรแกรมแก้ไขข้อความทั่วไป
- Local Web Server: รันผ่าน Python หรือ Node.js (จำเป็นต้องเปิดผ่าน HTTP Server เพราะเบราว์เซอร์จำกัดการทำงานของ View Transitions บนโปรโตคอล
file://) - เวลาประมาณ 30 นาที
- ความรู้พื้นฐาน: เข้าใจโครงสร้าง HTML และคำสั่ง CSS พื้นฐาน
โครงสร้างโฟลเดอร์ของโปรเจกต์
เว็บตัวอย่างประกอบด้วย 4 หน้าหลัก ได้แก่ หน้าแรก, หน้าเมนู, หน้าเกี่ยวกับเรา และหน้าติดต่อ โดยแต่ละหน้าจะเป็นไฟล์ HTML แยกกันโดยสิ้นเชิง:
เริ่มจากการสร้างโฟลเดอร์และไฟล์ที่จำเป็น:
mkdir small-html-demo
cd small-html-demo
mkdir menu about contact
touch index.html menu/index.html about/index.html contact/index.html style.cssโครงสร้างโปรเจกต์จะมีหน้าตาแบบนี้:
small-html-demo/
├── index.html
├── style.css
├── menu/
│ └── index.html
├── about/
│ └── index.html
└── contact/
└── index.htmlการแยกโฟลเดอร์แล้วใช้ชื่อ index.html จะช่วยให้ URL บนเซิร์ฟเวอร์ดูสะอาดตา เช่น /menu/, /about/ และ /contact/ แทนที่จะลงท้ายด้วย .html
เปิดรัน Local Server
เนื่องจาก View Transitions API กำหนดให้ทำงานภายใต้ Same-origin Policy การทดสอบจึงต้องรันผ่านเซิร์ฟเวอร์จำลอง:
หากในเครื่องมี Python ให้เปิด Terminal เข้าไปที่โฟลเดอร์ small-html-demo แล้วรัน:
python3 -m http.server 8000หรือหากสะดวกใช้ Node.js สามารถใช้คำสั่ง:
npx serve -p 8000เมื่อเปิดเบราว์เซอร์ไปที่ http://localhost:8000 จะพบหน้าว่าง ซึ่งแปลว่าเซิร์ฟเวอร์พร้อมทำงานแล้ว
สร้างหน้าแรกพร้อมปุ่มเปิดเมนู
เปิดไฟล์ index.html ที่ Root โฟลเดอร์ แล้วใส่โค้ดดังนี้:
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="view-transition" content="same-origin">
<title>หน้าแรก Small HTML Demo</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<header>
<nav>
<a href="/menu/" class="menu-button">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M3 6h18M3 12h18M3 18h18" stroke="currentColor" stroke-width="2"/>
</svg>
<span>เมนู</span>
</a>
</nav>
<h1>Small HTML Demo</h1>
</header>
<main>
<p>เว็บตัวอย่างที่ใช้แค่ HTML กับ CSS View Transitions ไม่มี SPA framework ไม่มี router</p>
<p>กดที่ icon เมนูบนซ้ายเพื่อเปิดเมนู</p>
</main>
</body>
</html>จุดสำคัญในหน้านี้
- Meta Tag
view-transition: บรรทัด<meta name="view-transition" content="same-origin">คือการอนุญาตให้เบราว์เซอร์ทำแอนิเมชันสลับหน้าระหว่างหน้าเว็บใน Origin เดียวกัน หากไม่มีบรรทัดนี้ แอนิเมชันจะไม่ทำงาน - ปุ่มเมนูเป็นลิงก์ Anchor ธรรมดา: ลิงก์ไปยัง
/menu/โดยตรงโดยไม่ต้องผูก Event Listener หรือเขียนonclickช่วยให้เบราว์เซอร์จัดการ History ตามธรรมชาติ - ไร้ JavaScript: หน้าแรกทำงานได้โดยไม่ต้องโหลดไฟล์สคริปต์แม้แต่บรรทัดเดียว
สร้างหน้าเมนูพร้อมปุ่มปิดอัจฉริยะ
เปิดไฟล์ menu/index.html แล้วใส่โค้ดดังนี้:
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="view-transition" content="same-origin">
<title>เมนู Small HTML Demo</title>
<link rel="stylesheet" href="/style.css">
</head>
<body class="menu-page">
<header>
<nav>
<a href="/" class="close-button" onclick="document.referrer ? history.back() : window.location.href = '/'; return false;">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2"/>
</svg>
<span>ปิด</span>
</a>
</nav>
<h1>เมนู</h1>
</header>
<main>
<ul class="menu-list">
<li><a href="/">หน้าแรก</a></li>
<li><a href="/about/">เกี่ยวกับเว็บนี้</a></li>
<li><a href="/contact/">ติดต่อ</a></li>
</ul>
</main>
</body>
</html>หัวใจสำคัญของหน้านี้คือโค้ดสั้นๆ ในแอตทริบิวต์ onclick ของปุ่มปิด:
document.referrer ? history.back() : window.location.href = '/'; return false;โค้ดนี้ทำงาน 3 ขั้นตอน:
- ตรวจสอบ
document.referrer: เพื่อเช็กว่าผู้ใช้เปิดเข้ามาจากหน้าอื่นภายในเว็บ หรือพิมพ์ URL/menu/เข้ามาตรงๆ - เลือกการนำทางที่ถูกต้อง: หากมี Referrer ระบบจะเรียก
history.back()เพื่อย้อนกลับไปหน้าก่อนหน้าโดยไม่สร้างประวัติหน้าใหม่ แต่ถ้าไม่มี Referrer จะนำทางกลับไปยังหน้าแรก/ - ส่งค่า
return false: เพื่อระงับการทำงานตามปกติของแท็ก<a>ป้องกันการย้อนกลับซ้อนกับการเปลี่ยนหน้าใหม่
เพิ่มหน้า About และ Contact
เปิดไฟล์ about/index.html แล้วใส่โค้ด:
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="view-transition" content="same-origin">
<title>เกี่ยวกับเว็บนี้ Small HTML Demo</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<header>
<nav>
<a href="/menu/" class="menu-button">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M3 6h18M3 12h18M3 18h18" stroke="currentColor" stroke-width="2"/>
</svg>
<span>เมนู</span>
</a>
</nav>
<h1>เกี่ยวกับเว็บนี้</h1>
</header>
<main>
<p>เว็บนี้สร้างขึ้นเพื่อทดลองแนวคิดของ Jim Nielsen ที่เสนอให้ประกอบเว็บจาก HTML หลายหน้าเล็กๆ แทนการสร้าง SPA ขนาดใหญ่</p>
<p>ทุกหน้าทำงานได้แม้ปิด JavaScript และใช้ CSS View Transitions เป็น progressive enhancement สำหรับ browser สมัยใหม่</p>
</main>
</body>
</html>เปิดไฟล์ contact/index.html แล้วใส่โค้ด:
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="view-transition" content="same-origin">
<title>ติดต่อ Small HTML Demo</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<header>
<nav>
<a href="/menu/" class="menu-button">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path d="M3 6h18M3 12h18M3 18h18" stroke="currentColor" stroke-width="2"/>
</svg>
<span>เมนู</span>
</a>
</nav>
<h1>ติดต่อ</h1>
</header>
<main>
<p>หน้าติดต่อตัวอย่าง สร้างจาก HTML ปกติ ไม่มี form validation ด้วย JavaScript ในตัวอย่างนี้</p>
<p>หากต้องการ form ที่ส่ง data จริง ใช้ <code><form action="/submit" method="post"></code> ตามมาตรฐานเว็บ</p>
</main>
</body>
</html>จัดการสไตล์และเปิดใช้ View Transitions ใน CSS
เปิดไฟล์ style.css ในรูทโฟลเดอร์ แล้วใส่โค้ดดังนี้:
@view-transition {
navigation: auto;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, sans-serif;
background: #ffffff;
color: #1a1a1a;
min-height: 100vh;
}
body.menu-page {
background: #1a1a1a;
color: #ffffff;
}
header {
display: flex;
align-items: center;
gap: 16px;
padding: 24px;
border-bottom: 1px solid currentColor;
}
header h1 {
font-size: 20px;
font-weight: 500;
}
nav a {
display: inline-flex;
align-items: center;
gap: 8px;
text-decoration: none;
color: inherit;
padding: 8px 12px;
border-radius: 6px;
transition: background 0.15s;
}
nav a:hover {
background: rgba(0, 0, 0, 0.05);
}
body.menu-page nav a:hover {
background: rgba(255, 255, 255, 0.1);
}
main {
padding: 32px 24px;
max-width: 720px;
}
main p {
margin-bottom: 16px;
line-height: 1.7;
}
.menu-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 8px;
}
.menu-list a {
display: block;
padding: 16px 24px;
font-size: 24px;
text-decoration: none;
color: inherit;
border-radius: 8px;
transition: background 0.15s;
}
.menu-list a:hover {
background: rgba(255, 255, 255, 0.1);
}จุดชี้ขาดคือคำสั่งด้านบนสุดของไฟล์:
@view-transition {
navigation: auto;
}คำสั่งเพียง 3 บรรทัดนี้จะสั่งให้เบราว์เซอร์ถ่ายภาพ Snapshot ของหน้าเดิมและหน้าใหม่ จากนั้นทำการ Cross-fade แอนิเมชันให้โดยอัตโนมัติเมื่อมีการคลิกเปลี่ยนหน้าในเว็บไซต์
กำหนดแอนิเมชันเฉพาะส่วนด้วย Morph Transition
นอกจากการ Fade ทั้งหน้าแล้ว เรายังสามารถสั่งให้ Element สำคัญคงอยู่หรือเปลี่ยนรูปทรง (Morph) ได้ เช่น แถบ Header
เพิ่มโค้ดด้านล่างนี้ต่อท้ายไฟล์ style.css:
header {
view-transition-name: page-header;
}
::view-transition-old(page-header),
::view-transition-new(page-header) {
animation-duration: 0.3s;
animation-timing-function: ease-out;
}
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.4s;
}แอตทริบิวต์ view-transition-name: page-header จะแจ้งให้เบราว์เซอร์แยกการเรนเดอร์ส่วน Header ออกมา ทำให้ Header ปรับตำแหน่งและขนาดอย่างลื่นไหลแทนที่จะวูบหายไปพร้อมเนื้อหา
ทดสอบการทำงานเมื่อปิด JavaScript
จุดเด่นของแนวทางนี้คือระบบสามารถทำงานได้ในลักษณะ Graceful Degradation:
- เปิด DevTools ใน Chrome (กด F12 หรือ
Cmd+Option+I/Ctrl+Shift+I) - เปิด Command Palette (
Cmd+Shift+PหรือCtrl+Shift+P) พิมพ์disable javascriptแล้วกด Enter - รีเฟรชหน้าเว็บ จะพบว่าการคลิกเปิดเมนูและสลับหน้าต่างๆ ยังทำงานได้ตามปกติ ลิงก์ยังคงนำทางถูกต้อง แม้จะไม่มี JavaScript ทำงานอยู่เลยก็ตาม
ตรวจสอบความสะอาดของ Browser History
ข้อดีอีกประการคือ Browser History จะไม่รก:
- เปิดหน้าแรก แล้วคลิกเข้าไปที่หน้าเมนู
- กดปุ่มปิดเพื่อกลับหน้าแรก
- ทำซ้ำ 2-3 ครั้ง
- คลิกค้างที่ปุ่ม Back ของเบราว์เซอร์เพื่อดูรายการย้อนหลัง
จะพบว่าประวัติการเข้าชมมีเฉพาะหน้าหลัก โดยไม่มีหน้า /menu/ ซ้อนกันหลายรายการ เนื่องจาก history.back() จะลบประวัติของหน้านั้นออกโดยอัตโนมัติ ต่างจาก SPA หลายตัวที่การเปิดปิดเมนูมักจะเพิ่ม Entry เข้า History จนเต็ม
สรุปเปรียบเทียบกับ SPA แบบดั้งเดิม
ในโปรเจกต์นี้ เราใช้ JavaScript เพียงแค่บรรทัดเดียวในแอตทริบิวต์ onclick โดยไม่มีไลบรารีจัดการ State, ไม่มี Bundler และไม่มี Router Framework ใดๆ
หากสร้างเว็บแบบเดียวกันด้วยแนวคิด SPA ดั้งเดิม เราจะต้องติดตั้งทั้ง React, React Router และ Framer Motion ซึ่งทำให้ผู้ใช้ต้องดาวน์โหลดไฟล์ JavaScript ขนาดหลายร้อยกิโลไบต์ก่อนที่เว็บจะเริ่มแสดงผล แต่ด้วยวิธี Multi-page HTML นี้ ขนาดไฟล์ทั้งหมดที่ผู้ใช้ต้องโหลดมีเพียงไม่กี่กิโลไบต์เท่านั้น
แหล่งอ้างอิง
ชอบเรื่องแนวนี้ มีอีบุ๊คฟรีให้อ่านต่อ
Vibe Coding สำหรับคนไม่ใช่โปรแกรมเมอร์ ใช้ Claude Code สร้าง landing page, mini app และ prototype จริงโดยไม่ต้องเขียนโค้ด
กดสมัครแล้วเราจะส่งเทคนิค AI และของแจกใหม่ๆ ให้ทางอีเมล เลิกรับได้ตลอด
Vibecoding · The Developer's Playbook

ฉบับภาษาไทย 10 บท พา dev สร้าง Personal Finance Tracker (LINE OA + AI จัดหมวดอัตโนมัติ) ตั้งแต่โครงโปรเจกต์บรรทัดแรกจนแอปทำงานจริงบน server


