Now.js Framework Documentation
ScrollManager - ระบบจัดการการเลื่อน
ScrollManager - ระบบจัดการการเลื่อน
ภาพรวม
ScrollManager เป็นระบบจัดการการเลื่อนหน้าเว็บ (scrolling) ที่ครบครอบคลุมและทันสมัย ออกแบบมาเพื่อให้ประสบการณ์การเลื่อนหน้าที่ลื่นไหล รองรับ animation, waypoints, parallax, infinite scroll และฟีเจอร์ขั้นสูงอื่นๆ
จุดเด่น:
- การเลื่อนแบบสมูทที่ปรับแต่งได้
- Waypoint และ Intersection Observer
- การจดจำตำแหน่งการเลื่อนเมื่อย้อนกลับ
- รองรับอุปกรณ์พกพา (ทัชอีเวนต์)
- ฟีเจอร์ด้านการช่วยการเข้าถึง (ARIA, คีย์บอร์ด)
- การเพิ่มประสิทธิภาพ (throttle, debounce, requestAnimationFrame)
- เอฟเฟกต์ Parallax
- การเลื่อนแบบไม่สิ้นสุด
- แถบแสดงความคืบหน้าการเลื่อน
- การไฮไลต์ส่วนของหน้าอัตโนมัติ
- ระบบแอนิเมชันพร้อม easing หลากหลายรูปแบบ
การติดตั้งและนำเข้า
ScrollManager โหลดมาพร้อมกับ Now.js Framework และพร้อมใช้งานทันทีผ่าน window object:
// ไม่ต้อง import - พร้อมใช้งานทันที
console.log(window.ScrollManager); // อ็อบเจ็กต์ ScrollManager ที่ผูกไว้กับ windowการกำหนดค่า
ตัวเลือกการตั้งค่าหลัก
| ตัวเลือก | ประเภท | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
enabled |
Boolean | false |
เปิด/ปิดการทำงานของ ScrollManager |
core.offset |
Number | 0 |
ระยะห่างจากด้านบน (px) |
core.duration |
Number | 500 |
ระยะเวลา animation (ms) |
core.easing |
String | "easeInOutCubic" |
Easing function |
การตั้งค่าขั้นสูง
ตัวเลือกแอนิเมชัน
animation: {
enabled: true,
types: {
linear: t => t,
easeInOutCubic: t => ...,
easeOutQuart: t => ...,
easeInQuad: t => t * t
}
}ตัวเลือกด้านประสิทธิภาพ
performance: {
throttle: 16, // ระยะเวลา (ms) สำหรับการทำงานแบบ throttle
debounce: 100, // ระยะเวลา (ms) สำหรับการทำงานแบบ debounce
passive: true, // ใช้ passive event listeners
observer: true, // ใช้ IntersectionObserver
requestAnimationFrame: {
enabled: true,
maxFPS: 60,
skipThreshold: 16
},
batchSize: 5,
maxQueue: 100
}ตัวเลือกการกู้คืนตำแหน่ง
restoration: {
enabled: true,
key: 'scroll_positions',
ttl: 24 * 60 * 60 * 1000 // 24 ชั่วโมง
}ตัวเลือกสำหรับอุปกรณ์พกพา
mobile: {
enabled: true,
touchAction: 'pan-y',
momentumScroll: true,
pullToRefresh: false,
touchThreshold: 5
}ตัวเลือกการช่วยการเข้าถึง
accessibility: {
enabled: true,
announceChanges: true,
smoothFocus: true,
ariaLabels: true,
focusableSelectors: '...'
}ตัวเลือกของ Waypoint
waypoints: {
offset: 0,
threshold: 0.5,
once: false,
delay: 100
}ฟีเจอร์การเลื่อน
scroll: {
infinite: {
enabled: false,
threshold: 100,
loadMore: null
},
parallax: {
enabled: false,
speed: 0.5
},
progress: {
enabled: false,
color: 'var(--color-primary)',
height: '3px',
zIndex: 1000
},
snap: {
enabled: false,
type: 'y',
stop: true,
align: 'start'
},
section: {
highlight: true,
threshold: 0.5,
activeClass: 'active'
},
nav: {
updateHash: true,
highlightClass: 'active',
smoothScroll: true
}
}ตัวเลือกสำหรับ Smooth Scroll
smoothScroll: {
enabled: false,
autoScroll: false,
selector: 'a[href^="#"]',
excludeSelector: '.no-smooth',
hashChangeEnabled: true
}การ Initialize
// เริ่มต้นอัตโนมัติ (ต้องเปิด enabled: true)
ScrollManager.init({
enabled: true
});
// เริ่มต้นพร้อมการตั้งค่าที่ปรับแต่งเอง
ScrollManager.init({
enabled: true,
core: {
offset: 80,
duration: 800,
easing: 'easeOutQuart'
},
smoothScroll: {
enabled: true,
selector: 'a[href^="#"]'
},
waypoints: {
threshold: 0.7,
once: false
},
performance: {
throttle: 16,
debounce: 150
}
});เมธอด
init(options)
เริ่มต้นการทำงานของ ScrollManager
Parameters:
options(Object, optional): ตัวเลือก configuration
Returns: Promise<ScrollManager>
ตัวอย่าง:
await ScrollManager.init({
enabled: true,
core: { offset: 100, duration: 600 },
smoothScroll: { enabled: true }
});scrollTo(target, options)
เลื่อนไปยัง element หรือ selector ที่กำหนด
Parameters:
target(String|HTMLElement): CSS selector หรือ elementoptions(Object, optional): ตัวเลือกการเลื่อนoffset(Number): ระยะห่างจากด้านบนduration(Number): ระยะเวลา animationeasing(String): Easing function namefocus(Boolean): โฟกัส element หลังเลื่อนเสร็จ (default: true)
Returns: Promise<void>
ตัวอย่าง:
// เลื่อนไปยัง element
await ScrollManager.scrollTo('#section-about');
// เลื่อนพร้อม options
await ScrollManager.scrollTo('#contact', {
offset: 100,
duration: 1000,
easing: 'easeOutQuart'
});
// เลื่อนโดยใช้ element โดยตรง
const element = document.querySelector('.target');
await ScrollManager.scrollTo(element, {
offset: 80
});scrollToTop(options)
เลื่อนไปด้านบนสุดของหน้า
Parameters:
options(Object, optional): ตัวเลือกการเลื่อน
Returns: Promise<void>
ตัวอย่าง:
await ScrollManager.scrollToTop();
// พร้อม animation
await ScrollManager.scrollToTop({
duration: 800,
easing: 'easeInOutCubic'
});scrollToBottom(options)
เลื่อนไปด้านล่างสุดของหน้า
Parameters:
options(Object, optional): ตัวเลือกการเลื่อน
Returns: Promise<void>
ตัวอย่าง:
await ScrollManager.scrollToBottom();
await ScrollManager.scrollToBottom({
duration: 600
});scrollToContent(options)
เลื่อนไปยัง main content area
Parameters:
options(Object, optional): ตัวเลือกการเลื่อน
Returns: Promise<void>
ตัวอย่าง:
// เลื่อนไปยัง #main
await ScrollManager.scrollToContent();addWaypoint(id, element, options)
เพิ่ม waypoint สำหรับตรวจจับเมื่อ element เข้ามาในมุมมอง
Parameters:
id(String): Unique ID ของ waypointelement(HTMLElement): Element ที่ต้องการติดตามoptions(Object, optional): ตัวเลือก waypointoffset(Number): ระยะ offsetthreshold(Number): เปอร์เซ็นต์ที่ต้องปรากฏ (0-1)once(Boolean): ทำงานครั้งเดียวcallback(Function): Function ที่จะเรียกเมื่อ trigger
Returns: void
ตัวอย่าง:
const element = document.querySelector('.animate-on-scroll');
ScrollManager.addWaypoint('my-waypoint', element, {
threshold: 0.5,
once: true,
callback: (entry) => {
console.log('องค์ประกอบปรากฏแล้ว!');
element.classList.add('animated');
}
});removeWaypoint(id)
ลบ waypoint ที่มีอยู่
Parameters:
id(String): ID ของ waypoint ที่จะลบ
Returns: void
ตัวอย่าง:
ScrollManager.removeWaypoint('my-waypoint');cancelScroll()
ยกเลิกการเลื่อนที่กำลังทำงานอยู่
Returns: void
ตัวอย่าง:
ScrollManager.cancelScroll();getScrollPosition()
ดึงตำแหน่งการเลื่อนปัจจุบัน
Returns: Object - {x, y, percentX, percentY}
ตัวอย่าง:
const position = ScrollManager.getScrollPosition();
console.log(position);
// { x: 0, y: 500, percentX: 0, percentY: 25 }on(event, handler)
ลงทะเบียน event listener
Parameters:
event(String): ชื่อ eventhandler(Function): Handler function
Returns: void
ตัวอย่าง:
ScrollManager.on('scroll:complete', (data) => {
console.log('เลื่อนไปยัง:', data.element);
});
ScrollManager.on('waypoint:trigger', (data) => {
console.log('Waypoint ถูกเรียก:', data.id);
});off(event, handler)
ลบ event listener
Parameters:
event(String): ชื่อ eventhandler(Function): Handler function
Returns: void
ตัวอย่าง:
const handler = (data) => console.log(data);
ScrollManager.on('scroll:complete', handler);
// ใช้งาน...
ScrollManager.off('scroll:complete', handler);emit(eventName, data)
ส่ง custom event
Parameters:
eventName(String): ชื่อ eventdata(Any): ข้อมูลที่จะส่ง
Returns: void
ตัวอย่าง:
ScrollManager.emit('custom:scroll', {
position: 100,
target: 'section-about'
});cleanup()
ล้างข้อมูลและหยุดการทำงานทั้งหมด
Returns: void
ตัวอย่าง:
ScrollManager.cleanup();อีเวนต์
ScrollManager ส่ง events ต่างๆ ที่สามารถรับฟังได้:
| อีเวนต์ | คำอธิบาย | ข้อมูล |
|---|---|---|
scroll:initialized |
เริ่มต้นการทำงานเสร็จสิ้น | - |
scroll:start |
เริ่มการเลื่อน | {element, options} |
scroll:complete |
เลื่อนเสร็จสิ้น | {element, position} |
scroll:cancel |
การเลื่อนถูกยกเลิก | - |
scroll:progress |
กำลังเลื่อน (throttled) | {x, y, percentX, percentY} |
scroll:end |
เลื่อนเสร็จและหยุดนิ่ง | {x, y, percentX, percentY} |
scroll:direction |
ทิศทางการเลื่อนเปลี่ยน | {direction: 'up'\|'down'} |
scroll:animationComplete |
Animation เสร็จสิ้น | {startY, targetY, duration} |
scroll:reveal |
Element ถูก reveal | {element} |
waypoint:trigger |
Waypoint ถูก trigger | {id, entry} |
section:active |
Section ใหม่กลายเป็น active | {id} |
touch:move |
Touch move (mobile) | {diffY, diffX, originalEvent} |
scroll:error |
เกิด error | {error} |
ตัวอย่างการใช้งาน Events:
// ฟัง scroll complete
ScrollManager.on('scroll:complete', (data) => {
console.log('เลื่อนไปยัง:', data.element.id);
console.log('ตำแหน่ง:', data.position);
});
// ฟัง scroll direction
ScrollManager.on('scroll:direction', (data) => {
if (data.direction === 'down') {
header.classList.add('hidden');
} else {
header.classList.remove('hidden');
}
});
// ฟัง waypoint
ScrollManager.on('waypoint:trigger', (data) => {
console.log('รหัส Waypoint:', data.id);
});
// ฟัง scroll progress
ScrollManager.on('scroll:progress', (position) => {
progressBar.style.width = position.percentY + '%';
});Data Attributes บน HTML
ScrollManager รองรับ data attributes สำหรับควบคุมพฤติกรรม:
Waypoint
<!-- Waypoint พื้นฐาน -->
<div data-scroll-waypoint="section-1">
เนื้อหา
</div>
<!-- Waypoint พร้อม offset -->
<div data-scroll-waypoint="section-2"
data-scroll-offset="100">
เนื้อหา
</div>
<!-- Waypoint พร้อม callback -->
<div data-scroll-waypoint="section-3"
data-scroll-callback="onSectionVisible">
เนื้อหา
</div>การแสดงผลเมื่อเลื่อนถึง (Scroll Reveal)
<!-- Element จะได้ class 'revealed' เมื่อเข้ามาในมุมมอง -->
<div data-scroll-reveal>
Animate me!
</div>ส่วนของหน้าที่ติดตามการเลื่อน
<!-- ส่วนของหน้าที่จะถูกติดตามและไฮไลต์ -->
<section id="about" data-scroll-section>
เกี่ยวกับเรา
</section>
<section id="services" data-scroll-section>
บริการของเรา
</section>เมนูนำทางสำหรับการเลื่อน
<!-- ลิงก์เมนูนำทาง -->
<nav data-scroll-nav>
<a href="#home">Home</a>
<a href="#about">About</a>
<a href="#contact">Contact</a>
</nav>เอฟเฟกต์ Parallax
<!-- องค์ประกอบสำหรับ Parallax -->
<div data-parallax data-parallax-speed="0.5">
Background Layer
</div>ข้ามการใช้ Smooth Scroll
<!-- ไม่ใช้ smooth scroll กับ element นี้ -->
<a href="#skip" data-scroll-ignore>Skip smooth scroll</a>ตัวอย่างการใช้งาน
1. การเลื่อนแบบ Smooth เบื้องต้น
// เปิดใช้งาน smooth scrolling
ScrollManager.init({
enabled: true,
smoothScroll: {
enabled: true,
selector: 'a[href^="#"]'
},
core: {
offset: 80, // สำหรับ fixed header
duration: 600
}
});<nav>
<a href="#home">หน้าหลัก</a>
<a href="#about">เกี่ยวกับเรา</a>
<a href="#contact">ติดต่อเรา</a>
</nav>
<section id="home">...</section>
<section id="about">...</section>
<section id="contact">...</section>2. แอนิเมชันที่ทำงานเมื่อเลื่อนถึง
await ScrollManager.init({
enabled: true,
waypoints: {
threshold: 0.3,
once: true
}
});
// เพิ่ม waypoints
document.querySelectorAll('.animate-on-scroll').forEach((el, i) => {
ScrollManager.addWaypoint(`anim-${i}`, el, {
threshold: 0.3,
once: true,
callback: (entry) => {
el.classList.add('animated', 'fadeInUp');
}
});
});<div class="animate-on-scroll">
This will animate when scrolled into view
</div>
<div class="animate-on-scroll">
Another animated element
</div>.animate-on-scroll {
opacity: 0;
transform: translateY(50px);
transition: all 0.6s ease;
}
.animate-on-scroll.animated {
opacity: 1;
transform: translateY(0);
}3. ปุ่มเลื่อนกลับด้านบน
ScrollManager.init({
enabled: true,
core: { duration: 800 }
});
const scrollTopBtn = document.querySelector('.scroll-top');
// แสดงปุ่มเมื่อเลื่อนลง
ScrollManager.on('scroll:progress', (position) => {
if (position.y > 300) {
scrollTopBtn.classList.add('visible');
} else {
scrollTopBtn.classList.remove('visible');
}
});
// คลิกเพื่อกลับด้านบน
scrollTopBtn.addEventListener('click', () => {
ScrollManager.scrollToTop({
duration: 800,
easing: 'easeOutQuart'
});
});<button class="scroll-top">↑</button>.scroll-top {
position: fixed;
bottom: 20px;
right: 20px;
opacity: 0;
visibility: hidden;
transition: all 0.3s;
}
.scroll-top.visible {
opacity: 1;
visibility: visible;
}4. ซ่อนส่วนหัวเมื่อเลื่อนลง
ScrollManager.init({
enabled: true
});
const header = document.querySelector('.header');
ScrollManager.on('scroll:direction', (data) => {
if (data.direction === 'down') {
header.classList.add('header-hidden');
} else {
header.classList.remove('header-hidden');
}
});.header {
position: fixed;
top: 0;
width: 100%;
transition: transform 0.3s;
}
.header-hidden {
transform: translateY(-100%);
}5. ไฮไลต์ส่วนต่างๆ ในเมนูนำทาง
ScrollManager.init({
enabled: true,
scroll: {
section: {
highlight: true,
threshold: 0.5,
activeClass: 'active'
},
nav: {
updateHash: true,
highlightClass: 'active'
}
}
});
// เมนูนำทางจะได้รับคลาส active อัตโนมัติ
ScrollManager.on('section:active', (data) => {
// อัปเดตเมนูนำทาง
document.querySelectorAll('[data-scroll-nav] a').forEach(link => {
link.classList.remove('active');
});
const activeLink = document.querySelector(`[href="#${data.id}"]`);
if (activeLink) {
activeLink.classList.add('active');
}
});<nav data-scroll-nav>
<a href="#section1">ส่วนที่ 1</a>
<a href="#section2">ส่วนที่ 2</a>
<a href="#section3">ส่วนที่ 3</a>
</nav>
<section id="section1" data-scroll-section>...</section>
<section id="section2" data-scroll-section>...</section>
<section id="section3" data-scroll-section>...</section>6. แถบความคืบหน้าการเลื่อน
ScrollManager.init({
enabled: true,
scroll: {
progress: {
enabled: true,
color: '#4CAF50',
height: '4px',
zIndex: 9999
}
}
});
// หรือสร้าง custom progress bar
const progressBar = document.querySelector('.progress-bar');
ScrollManager.on('scroll:progress', (position) => {
progressBar.style.width = position.percentY + '%';
});<div class="scroll-progress">
<div class="progress-bar"></div>
</div>.scroll-progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: rgba(0,0,0,0.1);
z-index: 9999;
}
.progress-bar {
height: 100%;
background: #4CAF50;
width: 0%;
transition: width 0.1s;
}7. เอฟเฟกต์ Parallax
ScrollManager.init({
enabled: true,
scroll: {
parallax: {
enabled: true,
speed: 0.5
}
}
});
// ปรับแต่ง parallax เอง
const parallaxElements = document.querySelectorAll('[data-parallax]');
ScrollManager.on('scroll:progress', (position) => {
parallaxElements.forEach(el => {
const speed = parseFloat(el.dataset.parallaxSpeed) || 0.5;
const yPos = -(position.y * speed);
el.style.transform = `translateY(${yPos}px)`;
});
});<div class="hero">
<div class="hero-bg" data-parallax data-parallax-speed="0.5">
พื้นหลัง
</div>
<div class="hero-content" data-parallax data-parallax-speed="0.2">
เนื้อหา
</div>
</div>8. การเลื่อนแบบไม่สิ้นสุด
ScrollManager.init({
enabled: true,
scroll: {
infinite: {
enabled: true,
threshold: 200, // โหลดใหม่เมื่อเหลือ 200px ถึงด้านล่าง
loadMore: async () => {
console.log('กำลังโหลดเนื้อหาเพิ่มเติม...');
const newContent = await fetchMoreContent();
appendContent(newContent);
}
}
}
});
async function fetchMoreContent() {
const response = await fetch('/api/load-more');
return await response.json();
}
function appendContent(data) {
const container = document.querySelector('.content-container');
data.forEach(item => {
const div = document.createElement('div');
div.innerHTML = item.html;
container.appendChild(div);
});
}คุณสมบัติของสถานะ
| คุณสมบัติ | ประเภท | คำอธิบาย |
|---|---|---|
state.initialized |
Boolean | สถานะการเริ่มต้น |
state.isScrolling |
Boolean | กำลังเลื่อนอยู่หรือไม่ |
state.isLoading |
Boolean | กำลังโหลดข้อมูลหรือไม่ |
state.activeAnimation |
Number|null | RAF ID ของ animation ปัจจุบัน |
state.positions |
Map | เก็บตำแหน่งการเลื่อน |
state.waypoints |
Map | Waypoints ทั้งหมด |
state.observers |
Map | Observers (Intersection, Mutation, Resize) |
state.events |
Map | Event handlers |
state.history |
Array | ประวัติตำแหน่งการเลื่อน |
state.lastPosition |
Object|null | ตำแหน่งล่าสุด |
state.scrollDirection |
String|null | ทิศทางการเลื่อน ('up'|'down') |
state.activeSection |
String|null | Section ที่กำลังแอคทีฟ |
ตัวอย่าง:
// ตรวจสอบสถานะ
if (ScrollManager.state.initialized) {
console.log('พร้อมทำงาน');
}
if (ScrollManager.state.isScrolling) {
console.log('กำลังเลื่อนอยู่');
}
// ดูประวัติ
console.log(ScrollManager.state.history);
// ดู waypoints ทั้งหมด
ScrollManager.state.waypoints.forEach((waypoint, id) => {
console.log(id, waypoint);
});ฟังก์ชัน Easing
ScrollManager มีฟังก์ชัน easing ให้เลือกใช้:
| ฟังก์ชัน | คำอธิบาย | เหมาะสำหรับ |
|---|---|---|
linear |
ความเร็วคงที่ | การเคลื่อนไหวทั่วไป |
easeInOutCubic |
ช้า-เร็ว-ช้า | การเลื่อนทั่วไป (ค่าเริ่มต้น) |
easeOutQuart |
ช้าลงเรื่อยๆ | การหยุดอย่างนุ่มนวล |
easeInQuad |
เร่งค่อยๆ | การเริ่มต้นที่ช้า |
easeOutBack |
โค้งเลยแล้วถอยกลับ | เอฟเฟกต์พิเศษ |
ตัวอย่าง:
ScrollManager.scrollTo('#target', {
duration: 1000,
easing: 'easeOutQuart' // ใช้ easing function
});Custom Easing:
ScrollManager.config.animation.types.myCustomEasing = (t) => {
return t * t * (3 - 2 * t); // สมูทสเต็ป (smoothstep)
};
ScrollManager.scrollTo('#target', {
easing: 'myCustomEasing'
});การผสานการทำงาน
ใช้กับ RouterManager
// ScrollManager จะทำงานอัตโนมัติกับ RouterManager
RouterManager.init({
mode: 'history',
scrollToTop: true
});
ScrollManager.init({
enabled: true,
restoration: {
enabled: true // จดจำตำแหน่งเมื่อย้อนกลับ
}
});
// เมื่อเปลี่ยน route จะเลื่อนไปด้านบนอัตโนมัติ
RouterManager.on('route:changed', async (data) => {
if (data.scrollToTop !== false) {
await ScrollManager.scrollToTop();
}
});ใช้กับ ResponseHandler
// การตอบกลับจาก API สามารถควบคุมการเลื่อนได้
ResponseHandler.registerHandler('scrollTo', async (action) => {
await ScrollManager.scrollTo(action.target, action.options);
});
// ตัวอย่างการตอบกลับจาก API
{
"success": true,
"actions": [
{
"type": "scrollTo",
"target": "#success-message",
"options": {
"offset": 100,
"duration": 600
}
}
]
}ใช้กับ Animation Libraries
// ใช้กับ GSAP, AOS, Animate.css
ScrollManager.init({
enabled: true,
waypoints: { threshold: 0.3, once: true }
});
// การผสานการทำงานกับ AOS
document.querySelectorAll('[data-aos]').forEach((el, i) => {
ScrollManager.addWaypoint(`aos-${i}`, el, {
threshold: 0.3,
once: true,
callback: () => {
AOS.refresh();
}
});
});แนวทางปฏิบัติที่ดี
1. เปิด Performance Features
ScrollManager.init({
enabled: true,
performance: {
throttle: 16,
debounce: 100,
passive: true,
observer: true,
requestAnimationFrame: {
enabled: true
}
}
});2. ใช้ฟังก์ชัน Throttle และ Debounce
// ฟังก์ชัน throttle สำหรับเหตุการณ์ scroll
const handleScroll = ScrollManager.throttle(() => {
// ทำงานทุก 16ms
}, 16);
// ฟังก์ชัน debounce สำหรับเหตุการณ์ปรับขนาดหน้าต่าง
const handleResize = ScrollManager.debounce(() => {
// ทำงานหลังจากหยุด resize 150ms
}, 150);3. ล้างข้อมูลเมื่อไม่ใช้งาน
// ใน SPA เมื่อเปลี่ยนหน้า
RouterManager.on('route:before-change', () => {
ScrollManager.cleanup();
});
// หรือใน component lifecycle
onBeforeUnmount(() => {
ScrollManager.cleanup();
});4. ใช้ Waypoints แทนการ Listen Scroll Events
❌ ไม่แนะนำ:
window.addEventListener('scroll', () => {
const element = document.querySelector('.target');
const rect = element.getBoundingClientRect();
if (rect.top < window.innerHeight) {
element.classList.add('visible');
}
});✅ แนะนำ:
ScrollManager.addWaypoint('target', element, {
threshold: 0.5,
once: true,
callback: () => {
element.classList.add('visible');
}
});5. ตั้งค่า Offset สำหรับ Fixed Header
ScrollManager.init({
enabled: true,
core: {
offset: 80 // สูงของ header
}
});header {
position: fixed;
height: 80px;
}6. ใช้ Once สำหรับ Animation
// Animation ทำงานครั้งเดียว
ScrollManager.addWaypoint('anim', element, {
once: true, // ไม่ trigger ซ้ำ
callback: () => {
element.classList.add('animated');
}
});7. ตรวจสอบ Browser Support
if (!('IntersectionObserver' in window)) {
// ใช้ fallback
console.warn('เบราว์เซอร์ไม่รองรับ IntersectionObserver');
ScrollManager.init({
enabled: true,
performance: {
observer: false
}
});
}ข้อผิดพลาดที่พบบ่อย
1. ลืมเปิด enabled
// ❌ จะไม่ทำงาน
ScrollManager.init({
smoothScroll: { enabled: true }
});
// ✅ ต้องเปิด enabled: true
ScrollManager.init({
enabled: true,
smoothScroll: { enabled: true }
});2. ไม่ await async methods
// ❌ อาจมีปัญหา
ScrollManager.scrollTo('#target');
doSomethingElse(); // ทำงานทันที
// ✅ รอให้เสร็จก่อน
await ScrollManager.scrollTo('#target');
doSomethingElse(); // ทำงานหลังเลื่อนเสร็จ3. Target ไม่มีอยู่
// ❌ อาจ error
ScrollManager.scrollTo('#non-existent');
// ✅ ตรวจสอบก่อน
const target = document.querySelector('#my-target');
if (target) {
await ScrollManager.scrollTo(target);
}4. Waypoint ID ซ้ำกัน
// ❌ จะ throw error
ScrollManager.addWaypoint('my-id', element1);
ScrollManager.addWaypoint('my-id', element2); // จะเกิดข้อผิดพลาด!
// ✅ ใช้ ID ที่ unique
ScrollManager.addWaypoint('waypoint-1', element1);
ScrollManager.addWaypoint('waypoint-2', element2);5. ไม่ได้ล้างข้อมูล
// ❌ Memory leak ใน SPA
// ไม่ cleanup เมื่อเปลี่ยนหน้า
// ✅ ล้างข้อมูลเมื่อจำเป็น
RouterManager.on('route:before-change', () => {
ScrollManager.cleanup();
});ด้านประสิทธิภาพ
1. ใช้ RequestAnimationFrame
ScrollManager.init({
enabled: true,
performance: {
requestAnimationFrame: {
enabled: true,
maxFPS: 60
}
}
});2. ใช้ Passive Event Listeners
ScrollManager.init({
enabled: true,
performance: {
passive: true // ปรับปรุง scroll performance
}
});3. ใช้ IntersectionObserver
// จะมีประสิทธิภาพดีกว่าการ listen scroll events
ScrollManager.init({
enabled: true,
performance: {
observer: true
}
});4. จำกัดจำนวน Waypoints
// ❌ มาก เกินไป
document.querySelectorAll('.item').forEach((el, i) => {
ScrollManager.addWaypoint(`item-${i}`, el); // 1000+ waypoints
});
// ✅ ใช้ให้พอดี
document.querySelectorAll('.section').forEach((el, i) => {
ScrollManager.addWaypoint(`section-${i}`, el); // 10-20 waypoints
});5. Throttle Scroll Events
ScrollManager.init({
enabled: true,
performance: {
throttle: 16, // ~60fps
debounce: 100
}
});ด้านความปลอดภัย
1. ตรวจสอบตัวเลือก (Selector)
// ❌ อาจเกิดปัญหา
const userInput = getUserInput();
ScrollManager.scrollTo(userInput); // อันตราย!
// ✅ Validate ก่อนใช้
function safeScrollTo(selector) {
const element = document.querySelector(selector);
if (element && element.matches('[data-scrollable]')) {
ScrollManager.scrollTo(element);
}
}2. ตรวจสอบฟังก์ชัน Callback
// ✅ ตรวจสอบว่า callback เป็น function
ScrollManager.addWaypoint('id', element, {
callback: (entry) => {
if (typeof window.onWaypoint === 'function') {
window.onWaypoint(entry);
}
}
});3. จำกัด Scroll Queue
ScrollManager.init({
enabled: true,
performance: {
maxQueue: 100 // จำกัดคิวไม่ให้โต
}
});ความเข้ากันได้กับเบราว์เซอร์
| เบราว์เซอร์ | เวอร์ชัน | การรองรับ |
|---|---|---|
| Chrome | 60 ขึ้นไป | ✅ ครบถ้วน |
| Firefox | 55 ขึ้นไป | ✅ ครบถ้วน |
| Safari | 12 ขึ้นไป | ✅ ครบถ้วน |
| Edge | 79 ขึ้นไป | ✅ ครบถ้วน |
| iOS Safari | 12 ขึ้นไป | ✅ ครบถ้วน |
| Chrome Android | 80 ขึ้นไป | ✅ ครบถ้วน |
| IE | - | ❌ ไม่รองรับ |
ฟีเจอร์ที่จำเป็น:
- ES6 (async/await, Map, Set)
- requestAnimationFrame
- IntersectionObserver (ถ้าไม่มีจะใช้ fallback)
- ResizeObserver (ทางเลือก)
- MutationObserver (ทางเลือก)
Polyfill ที่แนะนำ:
<!-- สำหรับเบราว์เซอร์รุ่นเก่า -->
<script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver"></script>อ้างอิง API
สรุปเมธอด
| เมธอด | พารามิเตอร์ | ค่าที่ส่งกลับ | คำอธิบาย |
|---|---|---|---|
init(options) |
options?: Object |
Promise<ScrollManager> |
เริ่มต้นการทำงาน |
scrollTo(target, options) |
target: String\|Element, options?: Object |
Promise<void> |
เลื่อนไปยังเป้าหมาย |
scrollToTop(options) |
options?: Object |
Promise<void> |
เลื่อนไปด้านบนสุด |
scrollToBottom(options) |
options?: Object |
Promise<void> |
เลื่อนไปด้านล่างสุด |
scrollToContent(options) |
options?: Object |
Promise<void> |
เลื่อนไปยังพื้นที่เนื้อหา |
addWaypoint(id, element, options) |
id: String, element: Element, options?: Object |
void |
เพิ่ม waypoint |
removeWaypoint(id) |
id: String |
void |
ลบ waypoint |
cancelScroll() |
- | void |
ยกเลิกการเลื่อน |
getScrollPosition() |
- | Object |
ดึงตำแหน่งปัจจุบัน |
on(event, handler) |
event: String, handler: Function |
void |
ลงทะเบียนอีเวนต์ |
off(event, handler) |
event: String, handler: Function |
void |
ลบอีเวนต์ |
emit(eventName, data) |
eventName: String, data?: Any |
void |
ส่งอีเวนต์ |
cleanup() |
- | void |
ล้างข้อมูล |
throttle(func, limit) |
func: Function, limit: Number |
Function |
สร้างฟังก์ชันแบบ throttled |
debounce(func, wait) |
func: Function, wait: Number |
Function |
สร้างฟังก์ชันแบบ debounced |
สรุป
ScrollManager ให้:
- การเลื่อนที่นุ่มนวล - เคลื่อนหน้าจอด้วยแอนิเมชันที่ปรับแต่งได้
- Waypoint - ตรวจจับเมื่อองค์ประกอบเข้าสู่มุมมอง
- ประสิทธิภาพสูง - ใช้ requestAnimationFrame, throttle, debounce, IntersectionObserver
- การเข้าถึงที่ดี - รองรับคีย์บอร์ด, screen reader, ARIA attributes
- เหมาะกับอุปกรณ์พกพา - รองรับเหตุการณ์สัมผัสและการเลื่อนด้วยนิ้ว
- ยืดหยุ่น - ปรับแต่งได้สูง พร้อมอีเวนต์และ callback มากมาย
- ผสานการทำงาน - ใช้ร่วมกับ RouterManager และ ResponseHandler ได้อย่างราบรื่น
- ฟีเจอร์หลากหลาย - รองรับ Parallax, infinite scroll, แถบความคืบหน้า และการไฮไลต์ส่วนของหน้า
ตัวอย่างการทำงานจริง
ดูการทำงานของ ScrollManager ในตัวอย่าง One-Page Scrolling ที่ครบถ้วน:
ตัวอย่างนี้แสดงให้เห็น:
- ✨ การนำทางด้วยการเลื่อนแบบ smooth
- 🎨 เอฟเฟกต์ Parallax แบบหลายชั้น
- 👁️ แอนิเมชัน Waypoint
- 📊 แถบแสดงความคืบหน้าการเลื่อน
- 🔗 การไฮไลต์ส่วนและอัปเดต hash อัตโนมัติ
- 📱 การโต้ตอบด้วยทัชที่เหมาะกับมือถือ
- ⌨️ รองรับการนำทางด้วยคีย์บอร์ด
- 🎯 Demo แบบโต้ตอบพร้อมการติดตามแบบเรียลไทม์
เยี่ยมชมตัวอย่างเพื่อดูฟีเจอร์ทั้งหมดของ ScrollManager ทำงานร่วมกันในหน้า One-Page ที่สวยงาม
เอกสารที่เกี่ยวข้อง
- ตัวอย่าง One-Page - การสาธิตการเลื่อนแบบครบถ้วน
- RouterManager - การนำทางและ routing
- ComponentManager - วงจรชีวิตของคอมโพเนนต์
- เอกสาร Now.js - เอกสาร framework ฉบับสมบูรณ์