Now.js Framework Documentation

Now.js Framework Documentation

MediaViewer

TH 04 Sep 2026 01:40

MediaViewer

ภาพรวม

MediaViewer คือ component สำหรับแสดง media (images, videos, YouTube) แบบ slideshow ใน Now.js Framework รองรับ zoom, fullscreen และ touch gestures

ใช้เมื่อ:

  • ต้องการ image gallery/lightbox
  • ต้องการ video player
  • ต้องการ slideshow
  • ต้องการ media preview

ทำไมต้องใช้:

  • ✅ Multiple media types (image, video, YouTube)
  • ✅ Slideshow with thumbnails
  • ✅ Zoom และ pan
  • ✅ Touch gestures (swipe)
  • ✅ Keyboard controls
  • ✅ Fullscreen support
  • ✅ Accessibility (ARIA)

การใช้งานพื้นฐาน

HTML Declarative

<div data-component="media-viewer">
  <img src="/images/photo1.jpg" alt="Photo 1">
  <img src="/images/photo2.jpg" alt="Photo 2">
  <img src="/images/photo3.jpg" alt="Photo 3">
</div>

With Mixed Media

<div data-component="media-viewer">
  <img src="/images/photo.jpg" alt="Photo">
  <video src="/videos/clip.mp4"></video>
  <div data-youtube="dQw4w9WgXcQ"></div>
</div>

JavaScript API

const viewer = new MediaViewer({
  items: [
    { type: 'image', src: '/photo1.jpg', caption: 'Photo 1' },
    { type: 'image', src: '/photo2.jpg', caption: 'Photo 2' },
    { type: 'video', src: '/video.mp4', caption: 'Video' },
    { type: 'youtube', videoId: 'dQw4w9WgXcQ', caption: 'YouTube' }
  ],
  showThumbnails: true,
  loop: true
});

viewer.show();

Media Item Types

Type Properties
image src, caption, alt
video src, caption, poster
youtube videoId, caption
iframe src, caption
html content, caption

Data Attributes

บน container ที่โหลดรายการจาก API

Attribute คำอธิบาย
data-api endpoint ที่โหลดรายการสื่อ
data-api-method HTTP method ของคำขอ (GET เป็นค่าเริ่มต้น)

บนอีลิเมนต์ที่เปิด viewer

Attribute คำอธิบาย
data-src URL ของสื่อ ใช้แทน src ของรูปเมื่อต้องการไฟล์ต้นฉบับคนละไฟล์กับรูปย่อ
data-url URL ของสื่อ ใช้เมื่อไม่ได้ตั้ง data-src
data-thumbnail URL ของรูปย่อ ถ้าไม่ระบุจะใช้ src ของรูปข้างใน
data-caption คำบรรยายใต้สื่อ ถ้าไม่ระบุจะใช้ alt
data-type ชนิดสื่อ เช่น image, video, youtube (image เป็นค่าเริ่มต้น)
data-interval ระยะเวลาเล่นสไลด์อัตโนมัติ หน่วยมิลลิวินาที
data-max-zoom ระดับซูมสูงสุด
data-zoom-step ระยะที่ซูมเพิ่มต่อหนึ่งครั้ง

Options

new MediaViewer({
  // Media items array
  items: [],

  // Show thumbnail strip
  showThumbnails: true,

  // Loop slideshow
  loop: true,

  // Enable zoom on images
  enableZoom: true,

  // Auto-play slideshow
  autoPlay: false,
  autoPlayInterval: 5000,

  // Show controls
  showNavigation: true,
  showCounter: true,
  showCaption: true,

  // Keyboard shortcuts
  keyboard: true,

  // Close on backdrop click
  closeOnBackdrop: true,

  // Callbacks
  onOpen: () => {},
  onClose: () => {},
  onChange: (index) => {}
});

API อ้างอิง

new MediaViewer(options)

สร้าง MediaViewer instance

Returns: MediaViewer

viewer.show(items?, startIndex?)

แสดง viewer

Parameter Type Description
items array Media items (optional)
startIndex number Starting index (default: 0)
viewer.show();
viewer.show(items, 2);  // Start at index 2

viewer.hide()

ซ่อน viewer

viewer.next()

ไปรูปถัดไป

viewer.prev()

ไปรูปก่อนหน้า

viewer.goTo(index)

ไปยัง index ที่กำหนด

viewer.play()

เริ่ม slideshow

viewer.stop()

หยุด slideshow

Keyboard Shortcuts

Key Action
→ Next item
← Previous item
Space Play/Pause slideshow
Escape Close viewer
+ / = Zoom in
- Zoom out
0 Reset zoom
F Toggle fullscreen

Touch Gestures

Gesture Action
Swipe Left Next item
Swipe Right Previous item
Pinch Zoom
Double-tap Toggle zoom
Swipe Down Close viewer

การจัดรูปแบบ CSS

/* Viewer container */
.media-viewer {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.95);
  z-index: 9999;
  display: flex;
  flex-direction: column;
}

.media-viewer-content {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

/* Main media */
.media-viewer-main img,
.media-viewer-main video {
  max-width: 90%;
  max-height: 80vh;
  object-fit: contain;
}

/* Navigation */
.media-viewer-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: rgba(255,255,255,0.1);
  color: white;
  border: none;
  padding: 16px;
  cursor: pointer;
}

.media-viewer-prev { left: 16px; }
.media-viewer-next { right: 16px; }

/* Thumbnails */
.media-viewer-thumbnails {
  display: flex;
  gap: 8px;
  padding: 16px;
  overflow-x: auto;
  justify-content: center;
}

.media-viewer-thumb {
  width: 60px;
  height: 60px;
  object-fit: cover;
  opacity: 0.5;
  cursor: pointer;
}

.media-viewer-thumb.active {
  opacity: 1;
  outline: 2px solid white;
}

/* Caption */
.media-viewer-caption {
  text-align: center;
  color: white;
  padding: 16px;
}

/* Close button */
.media-viewer-close {
  position: absolute;
  top: 16px;
  right: 16px;
  background: none;
  border: none;
  color: white;
  font-size: 24px;
  cursor: pointer;
}

ตัวอย่างการใช้งานจริง

<div class="product-gallery">
  <div class="main-image" id="main-image">
    <img src="/products/shoe-1.jpg" alt="Main">
  </div>
  <div class="thumbnails" data-component="media-viewer">
    <img src="/products/shoe-1.jpg" alt="View 1">
    <img src="/products/shoe-2.jpg" alt="View 2">
    <img src="/products/shoe-3.jpg" alt="View 3">
    <video src="/products/shoe-video.mp4" poster="/products/shoe-poster.jpg"></video>
  </div>
</div>

Photo Album

// Open viewer from grid
document.querySelectorAll('.photo-grid img').forEach((img, index) => {
  img.addEventListener('click', () => {
    const items = Array.from(document.querySelectorAll('.photo-grid img')).map(img => ({
      type: 'image',
      src: img.src.replace('thumb', 'full'),
      caption: img.alt
    }));

    const viewer = new MediaViewer({ items });
    viewer.show(null, index);
  });
});
const videos = [
  { type: 'youtube', videoId: 'video1', caption: 'Tutorial Part 1' },
  { type: 'youtube', videoId: 'video2', caption: 'Tutorial Part 2' },
  { type: 'video', src: '/local-video.mp4', caption: 'Demo' }
];

const viewer = new MediaViewer({
  items: videos,
  showThumbnails: true
});

document.getElementById('watch-btn').onclick = () => viewer.show();

เหตุการณ์

เหตุการณ์เหล่านี้เป็น DOM CustomEvent ที่ dispatch บนอีลิเมนต์ จึงต้องรับด้วย
element.addEventListener() และอ่านค่าจาก event.detail

Event เมื่อเกิด event.detail
mediaviewer:loaded โหลดรายการสื่อจาก API สำเร็จ {items, viewer}
mediaviewer:error โหลดรายการสื่อไม่สำเร็จ {error, viewer}

ข้อควรระวัง

⚠️ 1. YouTube Video ID

// ❌ Full URL
{ type: 'youtube', videoId: 'https://youtube.com/watch?v=abc123' }

// ✅ Video ID เท่านั้น
{ type: 'youtube', videoId: 'abc123' }

⚠️ 2. Image Paths

// ❌ Relative to current page
{ type: 'image', src: 'photo.jpg' }

// ✅ Absolute หรือ from root
{ type: 'image', src: '/images/photo.jpg' }

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