Now.js Framework Documentation

Now.js Framework Documentation

MediaViewer

EN 04 Sep 2026 01:40

MediaViewer

Overview

MediaViewer is a component for displaying media (images, videos, YouTube) as a slideshow in Now.js Framework. It supports zoom, fullscreen, and touch gestures.

When to use:

  • Need image gallery/lightbox
  • Need video player
  • Need slideshow
  • Need media preview

Why use it:

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

Basic Usage

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

On a container that loads items from an API

Attribute Description
data-api Endpoint that returns the media items
data-api-method HTTP method of the request (GET by default)

On an element that opens the viewer

Attribute Description
data-src Media URL, used instead of the image src when the full file differs from the thumbnail
data-url Media URL, used when data-src is not set
data-thumbnail Thumbnail URL; the inner image src is used when omitted
data-caption Caption shown under the media; falls back to alt
data-type Media type such as image, video, youtube (image by default)
data-interval Auto-advance interval in milliseconds
data-max-zoom Maximum zoom level
data-zoom-step Zoom increment per 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 Reference

new MediaViewer(options)

Create MediaViewer instance

Returns: MediaViewer

viewer.show(items?, startIndex?)

Show 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()

Hide viewer

viewer.next()

Go to next item

viewer.prev()

Go to previous item

viewer.goTo(index)

Go to specific index

viewer.play()

Start slideshow

viewer.stop()

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 Styling

/* 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;
}

Real-World Examples

<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();

Events

These are DOM CustomEvents dispatched on the element, so listen with
element.addEventListener() and read the values from event.detail.

Event When Triggered event.detail
mediaviewer:loaded Media items loaded from the API {items, viewer}
mediaviewer:error Loading media items failed {error, viewer}

Common Pitfalls

⚠️ 1. YouTube Video ID

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

// ✅ Video ID only
{ type: 'youtube', videoId: 'abc123' }

⚠️ 2. Image Paths

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

// ✅ Absolute or from root
{ type: 'image', src: '/images/photo.jpg' }