Now.js Framework Documentation

Now.js Framework Documentation

ScrollManager - ระบบจัดการการเลื่อน

TH 27 Nov 2025 14:16

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 หรือ element
  • options (Object, optional): ตัวเลือกการเลื่อน
    • offset (Number): ระยะห่างจากด้านบน
    • duration (Number): ระยะเวลา animation
    • easing (String): Easing function name
    • focus (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 ของ waypoint
  • element (HTMLElement): Element ที่ต้องการติดตาม
  • options (Object, optional): ตัวเลือก waypoint
    • offset (Number): ระยะ offset
    • threshold (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): ชื่อ event
  • handler (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): ชื่อ event
  • handler (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): ชื่อ event
  • data (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 ที่ครบถ้วน:

ตัวอย่าง One-Page Scrolling

ตัวอย่างนี้แสดงให้เห็น:

  • ✨ การนำทางด้วยการเลื่อนแบบ smooth
  • 🎨 เอฟเฟกต์ Parallax แบบหลายชั้น
  • 👁️ แอนิเมชัน Waypoint
  • 📊 แถบแสดงความคืบหน้าการเลื่อน
  • 🔗 การไฮไลต์ส่วนและอัปเดต hash อัตโนมัติ
  • 📱 การโต้ตอบด้วยทัชที่เหมาะกับมือถือ
  • ⌨️ รองรับการนำทางด้วยคีย์บอร์ด
  • 🎯 Demo แบบโต้ตอบพร้อมการติดตามแบบเรียลไทม์

เยี่ยมชมตัวอย่างเพื่อดูฟีเจอร์ทั้งหมดของ ScrollManager ทำงานร่วมกันในหน้า One-Page ที่สวยงาม

เอกสารที่เกี่ยวข้อง