Now.js Framework Documentation
PageNavigator
PageNavigator
ภาพรวม
PageNavigator โหลดหน้าที่ server เรนเดอร์ (เช่นหน้าเว็บของ CMS) เข้า element หลัก (Now.config.mainSelector, ปกติคือ #main) โดยไม่โหลดทั้งหน้าใหม่ และเปลี่ยน URL ใน address bar ตามปกติ
- คลิกลิงก์ภายในเว็บ → fetch URL เดิมพร้อม header
X-Partial-Page: 1→ server ตอบ JSON เฉพาะส่วนที่เปลี่ยน → สลับเนื้อหาใน#main, เปลี่ยน title และ meta ของหน้า,pushStateURL - เปิด URL ตรง ๆ (หรือ bot ของ search engine) ได้หน้าเต็มจาก server ตามปกติ — SEO ไม่เปลี่ยน
- อะไรที่ผิดคาด (network error, ไม่ใช่ JSON, ไม่มี header ตอบกลับ,
reload: true, leave guard ไม่ยอม) → โหลดหน้าเต็มแทน ลิงก์จึงไม่มีทาง "กดแล้วไม่เกิดอะไร"
ใช้เมื่อ: หน้าเว็บสร้างจาก server (PHP) และอยากได้ความเร็วแบบ SPA
ไม่ใช้เมื่อ: แอปที่ใช้ template ฝั่ง client — ใช้ RouterManager (ห้ามเปิดทั้งสองตัวพร้อมกัน เพราะดักลิงก์ชุดเดียวกัน)
การเปิดใช้
await Now.init({
router: {enabled: false},
navigator: {
enabled: true,
// path (นับจาก Now.basePath) ที่ให้โหลดแบบปกติ — string = ขึ้นต้นด้วย, หรือ RegExp
exclude: ['admin/', 'api/']
}
});| ตัวเลือก | ค่าเริ่มต้น | ความหมาย |
|---|---|---|
enabled |
false |
เปิดการดักลิงก์ |
header |
'X-Partial-Page' |
header ที่ส่งไปขอ partial และต้องได้กลับมาเป็น 1 |
exclude |
[] |
path ที่ไม่ดัก (string prefix หรือ RegExp) |
scrollToTop |
true |
หน้าใหม่เริ่มที่บนสุด (ถ้า URL มี #id จะเลื่อนไปที่ element นั้น) |
focusContent |
true |
ย้าย focus ไปที่ #main หลังโหลด (screen reader อ่านต่อจากเนื้อหาใหม่) |
progress |
true |
แถบโหลดด้านบนของหน้าต่าง (ดูหัวข้อแถบโหลด) |
transition |
false |
cross-fade ระหว่างหน้าด้วย View Transitions API (เฉพาะเบราว์เซอร์ที่รองรับ และไม่ทำเมื่อผู้ใช้ตั้ง reduced motion) |
สัญญากับ server
request มี header X-Partial-Page: 1 — server ต้องตอบ JSON พร้อม header X-Partial-Page: 1 (และควรใส่ Vary: X-Partial-Page, Cache-Control: no-store เพราะ URL เดียวกันตอบได้สองแบบ)
{
"content": "<div>…innerHTML ของ #main…</div>",
"title": "ชื่อหน้า",
"head": "<meta name=\"description\" content=\"…\" data-page-meta><link rel=\"canonical\" href=\"…\" data-page-meta>",
"styles": ["https://fonts.googleapis.com/css2?family=Kanit"],
"reload": false
}| key | ความหมาย |
|---|---|
content |
HTML ที่แทน innerHTML ของ #main (จำเป็น) |
title |
document.title |
head |
tag ใน <head> ที่ขึ้นกับหน้า — ทุก tag ต้องมี data-page-meta ตัวเก่าที่มี data-page-meta ถูกลบแล้วใส่ชุดใหม่ (หน้าเต็มก็ต้องใส่ data-page-meta ให้ tag ชุดเดียวกัน) |
styles |
stylesheet ที่หน้านี้ต้องใช้ เพิ่มครั้งเดียว ไม่ลบออก |
reload |
true = ตอบแบบ partial ไม่ได้ (เช่น หน้า maintenance, เปลี่ยนภาษา) → โหลดหน้าเต็ม |
| อื่น ๆ | ส่งต่อใน event page:loaded (detail.data) เช่นเมนูที่เลือก |
redirect ที่ server ทำ (เช่นไปหน้า login) fetch ตามให้ และ address bar แสดง URL ปลายทาง
ฟอร์ม
ฟอร์ม method="get" (เช่นช่องค้นหา) โหลดหน้าผลลัพธ์แบบเดียวกับลิงก์ — query ของ action ถูกแทนด้วยค่าในฟอร์ม (รวมปุ่มที่กด) ตามกติกาของ GET
ฟอร์ม POST, ฟอร์มที่ FormManager จัดการ (data-form), ฟอร์มที่มี target และฟอร์มใต้ data-navigate="false" ไม่ถูกแตะ
แถบโหลด
ระหว่างโหลดมีแถบเส้นบางที่ขอบบนของหน้าต่าง ทำงานสองจังหวะ
- เริ่มโหลด → วิ่งเร็วไปถึง 80% แล้วรอ
- หน้าใหม่เข้ามาแล้ว → เติมจากตำแหน่งปัจจุบันไป 100% แล้วจางหาย
หน้าส่วนใหญ่มาเร็ว แถบจึงวิ่งแค่ช่วงสั้น ๆ แล้วเต็มทันที ปรับได้ด้วยตัวแปร CSS (ใส่ใน CSS ของธีม):
:root {
--page-progress-color: var(--color-primary); /* สี */
--page-progress-height: 3px; /* ความหนา */
--page-progress-at: 0.8; /* จุดรอของจังหวะแรก (0–1) */
--page-progress-start: 0.5s; /* เวลาของจังหวะแรก */
--page-progress-end: 0.2s; /* เวลาเติมเต็มและเวลาจางหาย */
}ถ้าสีของแถบกลืนกับพื้นที่ขอบบนของหน้าต่าง (เช่น header เป็นสีหลักเดียวกัน — contrast ต่ำกว่า 2) แถบจะเปลี่ยนเป็นสีขาวหรือสีเข้มเองตามพื้น
element คือ <div class="page-progress"> (class is-loading = จังหวะแรก, is-done = จังหวะที่สอง) ถ้าลิงก์กลายเป็นการโหลดหน้าเต็ม แถบค้างที่ 80% จนหน้าเปลี่ยน — ถ้าหน้าไม่เปลี่ยน (เป็นไฟล์ดาวน์โหลด) สถานะโหลดจบเองภายใน 3 วินาที ปิดแถบได้ด้วย progress: false
ลิงก์ที่ไม่ถูกดัก
- มี
data-navigate="false"ที่ลิงก์หรือ element แม่ - มี
target(ที่ไม่ใช่_self),download,rel="external" - กด Ctrl/Cmd/Shift/Alt หรือคลิกปุ่มอื่นที่ไม่ใช่ปุ่มซ้าย
- คนละ origin, อยู่นอก
Now.basePath,mailto:/tel: - ไฟล์ (นามสกุลอื่นนอกจาก
.php,.html,.htm) เช่น.pdf,.zip - ลิงก์
#idในหน้าเดียวกัน - path ที่ตรงกับ
exclude
PageNavigator.isNavigable('/news/123'); // true
PageNavigator.isNavigable('/files/a.pdf'); // falsescript ในเนื้อหาใหม่
innerHTML ไม่รัน <script> — PageNavigator รันให้ตามลำดับหลังสลับเนื้อหา
- inline script รันทุกครั้ง
- script ที่มี
srcซึ่งโหลดอยู่แล้วในหน้า ไม่โหลดซ้ำ - listener ของ
DOMContentLoaded/loadที่ script ใหม่ลงทะเบียน ถูกเรียกหลัง script ทั้งหมดรันเสร็จ (event สองตัวนี้จะไม่เกิดอีกในเอกสารเดิม) — template เก่าที่เขียนdocument.addEventListener('DOMContentLoaded', …)จึงทำงานได้โดยไม่ต้องแก้
component, element และ form ใน #main ถูกทำลาย/สร้างใหม่ด้วยขั้นตอนเดียวกับ RouterManager.render()
ค่าตั้งของไซต์ที่ AppConfigManager ผูกกับหน้าตอนโหลดเต็มหน้า (data-text="data.web_title", data-attr="data-username:data.google_client_id" ของปุ่มเข้าระบบด้วยบัญชีโซเชียล, data-if …) ถูกผูกกับเนื้อหาใหม่ใน #main ด้วย ผ่าน AppConfigManager.applyBindings() ส่วน markup นอก #main คงเดิม
Event
| event | ที่มา | ข้อมูล |
|---|---|---|
route:changed |
EventManager |
{path, params: {}, query, hash, route: null, previous, isNotFoundPage, scroll: false} — event เดียวกับ router, component ที่รีเฟรชตาม router (MenuManager, EventCalendar …) ทำงานต่อได้เลย |
page:loaded |
EventManager และ document (CustomEvent) |
{url, previous, status, data} — data คือ JSON ทั้งก้อน |
content:rendered |
EventManager |
จาก RouterManager.render() |
// script ธรรมดาที่ไม่ได้ใช้ EventManager
document.addEventListener('page:loaded', e => {
console.log('now showing', e.detail.url);
});Leave guard
const off = PageNavigator.beforeLeave(url => {
// คืน false = ไม่สลับเนื้อหา ให้โหลดหน้าเต็มแทน → beforeunload ของหน้าทำงานตามปกติ
return !editor.hasUnsavedChanges();
});
off(); // ยกเลิกใช้กับทั้งคลิกลิงก์และปุ่ม Back/Forward
Back / Forward และตำแหน่ง scroll
- ตำแหน่ง scroll ถูกเก็บใน
history.state.scrollYและคืนให้เมื่อกด Back/Forward history.scrollRestorationถูกตั้งเป็นmanual(เนื้อหามาหลัง fetch) — reload หน้าก็คืนตำแหน่งให้- PageNavigator จัดตำแหน่งเอง:
route:changedที่ส่งออกมีscroll: falseเพื่อให้ ScrollManager ไม่เลื่อนไปที่#mainซ้ำ - ระหว่างโหลด
bodyมี classloadingและ#mainมีaria-busy="true"(หน้าตาให้ธีมกำหนดเอง)
API
| method | คำอธิบาย |
|---|---|
init(options) |
เริ่มทำงาน (Now.init เรียกให้จาก navigator config) |
navigate(url, {fromHistory, scrollY}) |
โหลดหน้าแบบ partial — คืน true เมื่อสลับเนื้อหาสำเร็จ |
isNavigable(href) |
URL นี้โหลดแบบ partial ได้หรือไม่ |
beforeLeave(guard) |
ลงทะเบียน leave guard คืนฟังก์ชันยกเลิก |