Skip to content
VistaView v2

Image Story Extension

The Image Story extension allows you to display rich HTML content (stories, descriptions, captions) below images in the lightbox. Content is loaded on-demand and cached for performance.

import { vistaView } from 'vistaview';
import { imageStory } from 'vistaview/extensions/image-story';
import 'vistaview/style.css';
import 'vistaview/styles/extensions/image-story.css';

vistaView({
  elements: '#gallery > a',
  extensions: [
    imageStory({
      getStory: async (index) => {
        const response = await fetch(`/api/stories/${index}`);
        const data = await response.json();
        return { content: data.html };
      },
    }),
  ],
});
<link rel="stylesheet" href="https://unpkg.com/vistaview/main/dist/style.css" />
<link rel="stylesheet" href="https://unpkg.com/vistaview/extensions/image-story/dist/style.css" />
<script src="https://unpkg.com/vistaview/main/dist/vistaview.umd.js"></script>
<script src="https://unpkg.com/vistaview/extensions/image-story/dist/main.umd.cjs"></script>

<script>
  VistaView.vistaView({
    elements: '#gallery > a',
    extensions: [
      VistaView.imageStory({
        getStory: async (index) => {
          const response = await fetch(`/api/stories/${index}`);
          const data = await response.json();
          return { content: data.html };
        },
      }),
    ],
  });
</script>

A function that returns a promise resolving to a story object:

getStory: (imageIndex: number) => Promise<StoryResult>;

StoryResult interface:

interface StoryResult {
  content: string; // HTML content (will be sanitized)
  onLoad?: () => void; // Called when story is displayed
  onUnload?: () => void; // Called when navigating away
}

Maximum number of stories to keep in memory. Default: 5

imageStory({
  getStory: async (index) => {
    /* ... */
  },
  maxStoryCache: 10, // Keep 10 stories cached
});
  • Rich HTML content - Display formatted text, images, links, etc.
  • On-demand loading - Stories are fetched only when needed
  • Smart caching - Recently viewed stories are cached
  • Auto-sanitization - HTML is automatically sanitized using DOMPurify
  • Expandable panel - Users can expand/collapse the story
  • Lifecycle hooks - onLoad/onUnload for custom behavior
import { imageStory } from 'vistaview/extensions/image-story';

vistaView({
  elements: '#gallery > a',
  extensions: [
    imageStory({
      getStory: async (index) => ({
        content: `
          <h2>Image ${index + 1}</h2>
          <p>This is the story for image ${index + 1}.</p>
        `,
      }),
    }),
  ],
});
imageStory({
  getStory: async (index) => {
    try {
      const response = await fetch(`/api/stories/${index}`);
      const data = await response.json();

      return {
        content: `
          <h2>${data.title}</h2>
          <p>${data.description}</p>
          <p><small>By ${data.author} on ${data.date}</small></p>
        `,
      };
    } catch (error) {
      return {
        content: '<p>Story could not be loaded.</p>',
      };
    }
  },
});
imageStory({
  getStory: async (index) => {
    const response = await fetch(`/api/stories/${index}`);
    const data = await response.json();

    return {
      content: data.html,
      onLoad: () => {
        console.log(`Story ${index} loaded`);
        // Track analytics
        gtag('event', 'story_view', { story_id: index });
      },
      onUnload: () => {
        console.log(`Story ${index} unloaded`);
        // Cleanup any event listeners
      },
    };
  },
});
const stories = [
  {
    title: 'Sunset at the Beach',
    content: 'A beautiful evening captured at the coast...',
  },
  {
    title: 'Mountain Peak',
    content: 'Hiking adventure to the summit...',
  },
];

imageStory({
  getStory: async (index) => ({
    content: `
      <h2>${stories[index].title}</h2>
      <p>${stories[index].content}</p>
    `,
  }),
});

The extension includes default styles, but you can customize:

/* Story container */
.vvw-story {
  /* Positioned at bottom of lightbox */
}

/* Story text area */
.vvw-story-text {
  background: rgba(0, 0, 0, 0.8);
  backdrop-filter: blur(10px);
  color: white;
  padding: 16px;
  border-radius: 8px;
}

/* Expanded state */
.vvw-story-text.expanded {
  max-height: 400px;
  overflow-y: auto;
}

/* Expand/collapse button */
.vvw-story-button {
  background: rgba(255, 255, 255, 0.1);
  border: none;
  color: white;
  cursor: pointer;
}

The extension automatically sanitizes HTML content using DOMPurify to prevent XSS attacks. However, you should still:

  1. Validate input on your server
  2. Use Content Security Policy (CSP) headers
  3. Sanitize user-generated content before storing

Stories are cached in memory to avoid redundant API calls:

imageStory({
  getStory: async (index) => {
    // This is only called once per image (until cache eviction)
    return await fetchStory(index);
  },
  maxStoryCache: 10, // Adjust based on memory constraints
});

Stories are only fetched when the user navigates to an image, not during initialization.

When the cache exceeds maxStoryCache, the oldest entries are evicted (FIFO).

  • ESM: 33.60 KB (10.84 KB gzip)
  • UMD: 25.28 KB (9.81 KB gzip)
  • CSS: 2.52 KB (0.72 KB gzip)

Note: Includes DOMPurify for HTML sanitization.

GitHubnpmllms.txtContext7

© 2026 • MIT License