Jekyll One

QuickSearch

PhotoSwipe is a popular JavaScript image gallery library well-suited for desktops browsers and apps on mobile devices with touchscreens. PhotoSwipe provides a smooth and engaging way to showcase images on the web, especially on mobile devices. Here’s a breakdown of its key functionalities.

Displays images in a lightbox

When you click on an image, it overlays the current page with a zoomed-in view of the image. Easily zoom in and out of images and pan around them. View images in full-screen for an immersive experience

Supports galleries

You can link multiple images together, allowing users to easily browse through a collection.

Touch-friendly

Designed with intuitive touch gestures for navigation (swiping, pinching, etc.). Supports touch gestures like swiping, pinching, and dragging.

Responsive

Adapts to different screen sizes and orientations.

Customizable

Offers various options for customization, such as themes, transitions, and social sharing features.

Keyboard Navigation

Navigate through images using keyboard shortcuts.

Social Sharing

Easily share images on social media platforms.

PhotoSwipe comes with a rich API. The programming interface allows developers to create individual user interfaces (UI) for pagination, navigation and more. Find large number of complex examples on the pages of the UI Initiative project.

The rich API makes PhotoSwipe an excellent choice for web developers, not least for template systems like J1 Template. In the current version of the J1 template (version 2025.x), PhotoSwipe is the default slider and replaces the previously used carousels Owl Carousel, Slickslider, and Masterslider by their implementations based on PhotoSwipeJS.

The PhotoSwipe API documentation pages are based on version v11.2.1 for the current J1 template version 2025.x. The idea of providing this documentation is not to simply copy the original pages as duplicates. For better readability and usability, all pages are restructured and enhanced by code examples or improved description texts.

The documentation pages for the J1 Template project will be used for the AI-based chat client (planned for 4th quarter of 2025). The agent will be trained by all available documentation pages of the Template system to give users an easy-to-use source for using the J1 Template to create websites for their needs.

All previously used slider resources are available in version 2025.x for backward compatibility but are no longer used for the example pages of the built-in web.

PhotoSwipe Parameters

List of available PhotoSwipe options (parameters) alphabetically ordered. Find option settings also with the PhotoSwipe Documentation.

allowPanToNext

Boolean, true. Allow swipe navigation to the next slide when the current slide is zoomed. Does not apply to mouse events.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  allowPanToNext: false
});
lightbox.init();

arrowKeys

Boolean, true. Left/right arrow keys for navigation.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  arrowKeys: false
});
lightbox.init();

bgOpacity

Sets the background backdrop opacity. Should be always defined by bgOpacity option and not by CSS RGBA color.

Name Type Default Description | Example

bgOpacity

number

0.8

Sets the background backdrop opacity. Should be always defined by bgOpacity option and not by CSS RGBA color.

const lightbox = new PhotoSwipeLightbox({
  // ... other options
  bgOpacity: 0.2,
});
lightbox.init();

clickAction

imageClickAction, bgClickAction

Refer to click and tap actions page.

clickToCloseNonZoomable

Boolean, true. If image is not zoomable (for example, smaller than viewport) it can be closed by clicking on it.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  clickToCloseNonZoomable: false
});
lightbox.init();

closeOnVerticalDrag

Boolean, true. Vertical drag gesture to close the PhotoSwipe.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  pswpModule: PhotoSwipe,
  closeOnVerticalDrag: false
});
lightbox.init();

easing

String, 'cubic-bezier(.4,0,.22,1)'. CSS easing function for open/close/zoom transitions.

Find an Example here.

escKey

Boolean, true. Esc key to close.

errorMsg

String, 'The image cannot be loaded'.

Message to display when the image wasn’t able to load. If you need to display HTML - use contentErrorElement filter.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  errorMsg: 'The photo cannot be loaded'
});
lightbox.init();

getViewportSizeFn

Function {x: width, y: height}, undefined. A function that should return slide viewport width and height, in format {x: 100, y: 100}.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  getViewportSizeFn: function(options, pswp) {
    return {
      x: document.documentElement.clientWidth - 200,
      y: window.innerHeight
    };
  }
});
lightbox.init();

hideAnimationDuration

Number, 333.

Transition duration in milliseconds, can be 0.

Find an Example here.

Use options showAnimationDuration and hideAnimationDuration (Integer, default 333).

Option easing (String, default cubic-bezier(.4,0,.22,1)) accepts any CSS timing-function. It is applied to any zoom transition (including double-tap).

Both options can be modified dynamically while PhotoSwipe is opened.

In the example below transition duration is set to 1000ms (1s). Easing is defined dynamically (opening transition gets ease-out-back, zoom transitions gets ease-in-out-back, and closing transition gets ease-in-back):

const backEasing = {
  in:     'cubic-bezier(0.6, -0.28, 0.7, 1)',
  out:    'cubic-bezier(0.3, 0, 0.32, 1.275)',
  inOut:  'cubic-bezier(0.68, -0.55, 0.265, 1.55)'
};
const lightbox = new PhotoSwipeLightbox({
  gallery:'#gallery_id',
  pswpModule: PhotoSwipe,
  children:'a',
  showHideAnimationType: 'zoom',
  showAnimationDuration: 1000,
  hideAnimationDuration: 1000
});
lightbox.on('firstUpdate', () => {
  lightbox.pswp.options.easing = backEasing.out;
});
lightbox.on('initialZoomInEnd', () => {
  lightbox.pswp.options.easing = backEasing.inOut;
});
lightbox.on('close', () => {
  lightbox.pswp.options.easing = backEasing.in;
});
lightbox.init();

indexIndicatorSep

String, /. Used for slide count indicator ("1 of 10 ").

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  indexIndicatorSep: ' of '
});
lightbox.init();

loop

Boolean, true. If set to true you’ll be able to swipe from the last to the first image. Option is always false when there are less than 3 slides.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  loop: false
});
lightbox.init();

mainClass

String, undefined. Class that will be added to the root element of PhotoSwipe, may contain multiple separated by space. Example on Styling page.

appendToEl

DOMElement, document.body. Element to which PhotoSwipe dialog will be appended when it opens.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  appendToEl: document.querySelector('#__docusaurus')
});
lightbox.init();

maxWidthToAnimate

Integer, 4000. Maximum width of image to animate, if initial rendered image width is larger than this value - the opening/closing transition will be automatically disabled.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  maxWidthToAnimate: 800,
});
lightbox.init();

padding

Object, { top: 0, bottom: 0, left: 0, right: 0 }. Slide area padding (in pixels).

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  padding: { top: 20, bottom: 40, left: 100, right: 100 }
});
lightbox.init();

paddingFn

Function, should return padding object. The option is checked frequently, so make sure it’s performant. Overrides padding option if defined. For example:

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  paddingFn: (viewportSize, itemData, index) => {
    return {
      // check based on slide index
      top: index === 0 ? 100 : 0,
      // check based on viewport size
      bottom: viewportSize.x < 600 ? 0 : 200,
      // check based on image size
      left: itemData.w < 2000 ? 50 : 0,
      right: 0
    };
  }
});
lightbox.init();

pinchToClose

Boolean, true. Pinch touch gesture to close the gallery.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  pinchToClose: false
});
lightbox.init();

preload

Array, [1, 2]. Lazy loading of nearby slides based on direction of movement. Should be an array with two integers, first one - number of items to preload before the current image, second one - after the current image. Two nearby images are always loaded.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  preload: [1, 4]
});
lightbox.init();

preloaderDelay

Number (ms), 2000. Delay before the loading indicator will be displayed, if image is loaded during it - the indicator will not be displayed at all. Can be zero.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  preloaderDelay: 0
});
lightbox.init();

returnFocus

Boolean, true. Restore focus the last active element after PhotoSwipe is closed.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  returnFocus: false
});
lightbox.init();

showAnimationDuration

Number, 333. Transition duration in milliseconds, can be 0.

Find an Example here.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  escKey: false
});
lightbox.init();

spacing

Number, 0.1. Spacing between slides. Defined as ratio relative to the viewport width (0.1 = 10% of viewport).

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  spacing: 0.5, // 50% of viewport width
});
lightbox.init();

trapFocus

Boolean, true. Trap focus within PhotoSwipe element while it’s open.

tapAction

tapAction, doubleTapAction

Refer to click and tap actions page.

wheelToZoom

Boolean, undefined. By default PhotoSwipe zooms image with ctrl-wheel, if you enable this option - image will zoom just via wheel.

const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  wheelToZoom: true
});
lightbox.init();

zoomAnimationDuration

Number, 333. Transition duration in milliseconds, can be 0.

Find an Example here.

zoomLevel

initialZoomLevel, secondaryZoomLevel, maxZoomLevel

Refer to Adjusting zoom level page for more info. The default values are described there too.

Methods

You can access the PhotoSwipe core instance after the lightbox is opened. For example within the beforeOpen event.

Alternatively, you may use PhotoSwipe core without Lightbox, see example on Data Sources page.

const lightbox = new PhotoSwipeLightbox({
  // options...
});
lightbox.init();
lightbox.on('beforeOpen', () => {
  const pswp = lightbox.pswp;
  // go to slide by index
  pswp.goTo(3);
  // go to next slide
  pswp.next();
  // go to previous slide
  pswp.prev();
  // close the PhotoSwipe (with animation, if enabled)
  // PhotoSwipe will automatically destroy after it's closed
  pswp.close();
  // instantly close and destroy the PhotoSwipe
  pswp.destroy();
  // zoom slide to
  pswp.currSlide.zoomTo(
    1, // slide zoom level, 1 - original image size
    { x: 0, y: 0 }, // zoom center point
    2000, // transition duration, can be 0
    false // wether pan/zoom bounds should be ignored
  );
  // pan slide to
  pswp.currSlide.panTo(
    100, // x position
    100, // y position
  );
});

Dynamically adding or removing slides

PhotoSwipe parses and renders only nearby slides based on preload option (but not less than 2 nearby).

The data about slides is stored within pswp.options.dataSource and its structure depends on the source. If you initialize PhotoSwipe from DOM elements (via gallery and children options) the dataSource will be:

pswp.options.dataSource = {
  gallery: '#gallery_id',
  items: [
    <Child element>,
    // ...
    <Child element>
  ]
}

And if you pass an array as a dataSource, it’ll be kept the same way:

pswp.options.dataSource = [
  { src: 'image1.jpg', width: 100, height: 50 },
  // ...
  { src: 'image3.jpg', width: 100, height: 50 }
]

You may modify the pswp.options.dataSource array however you like, as push new items, replace, sort, pop, shift, etc.

If you’ve modified slides that are currently active (current, next or previous). call method refreshSlideContent(slideIndex) to reload a slide by index. For example:

const lightbox = new PhotoSwipeLightbox ({
  pswpModule: PhotoSwipe,
  dataSource: [
    { src: 'https://dummyimage.com/800x600/555/fff/?text=1', width: 800, height: 600 },
    // ...
    { src: 'https://dummyimage.com/800x600/555/fff/?text=5', width: 800, height: 600 },
  ]
});
lightbox.on('uiRegister', () => {
  const { pswp }  = lightbox;
  let replacedCount = 0;
  pswp.ui.registerElement({
    name: 'replaceCurrentSlide',
    className: 'pswp__button--test-button',
    order: 9,
    isButton: true,
    html: 'Replace Current Slide',
    onClick: (event, el) => {
      replacedCount++;
      pswp.options.dataSource[pswp.currSlide.index] = {
        src: 'https://dummyimage.com/800x600/555/fff/?text=New%20Slide%20' + replacedCount, width: 800,
        height: 600
      };
      pswp.refreshSlideContent(pswp.currSlide.index);
    }
  });
  let addedCount = 0;
  pswp.ui.registerElement({
    name: 'addSlide',
    className: 'pswp__button--test-button',
    order: 9,
    isButton: true,
    html: 'Add Slide',
    onClick: (event, el) => {
      addedCount++;
      pswp.options.dataSource.push({
        src: 'https://dummyimage.com/800x600/555/fff/?text=Added%20slide%20' + addedCount, width: 800,
        height: 600
      });
      pswp.refreshSlideContent(pswp.getNumItems() - 1);
    }
  });
});
lightbox.init();
document.querySelector('#btn-add-remove-test').onclick = () => {
  lightbox.loadAndOpen(0);
};

PhotoSwipe Lightbox

PhotoSwipe is a popular JavaScript library designed to create engaging and interactive image galleries. It is often called a lightbox because when you click on an image, it opens in a new, overlaying layer, darkening the background and focusing the image.

See PhotoSwipe Documentation. and PhotoSwipeLightbox Methods for examples.

Name Description

gallery

Element, NodeList, or CSS selector (string) for the gallery element, a swiper slider for example.

children

Element, NodeList, or CSS selector (string) for elements within a gallery, a swiper slider for example. If not defined or set to false, the gallery root node is used.

pswpModule

Function or Module. Should return import(), if you need to dynamic import.

Default: undefined

Example
pswpModule: PhotoSwipe

Or the PhotoSwipe Core module itself.

Example
const lightbox = new PhotoSwipeLightbox({
  pswpModule: PhotoSwipe
  // ...
});

preloadFirstSlide

Boolean, true. Loads the first slide image in parallel with PhotoSwipe Core (while PhotoSwipe is opening).

import PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';
const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  preloadFirstSlide: false
});
lightbox.init();

Translating

A list of options that might need a translation.

{
  closeTitle:         'Close',
  zoomTitle:          'Zoom',
  arrowPrevTitle:     'Previous',
  arrowNextTitle:     'Next',
  errorMsg:           'The image cannot be loaded',
  indexIndicatorSep:  '/'
}
Example
const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery_id',
  pswpModule: PhotoSwipe,
  children: 'a',
  closeTitle: 'Close the dialog',
  zoomTitle: 'Zoom the photo',
  arrowPrevTitle: 'Go to the previous photo',
  arrowNextTitle: 'Go to the next photo',
  errorMsg: 'The photo cannot be loaded',
  indexIndicatorSep: ' of ',
  preloadFirstSlide: false
});
lightbox.init();

Methods

init

init()

Initialize the lightbox, must be called once for each instance. It does not open the dialog, only binds events that would open it (by default just click). It also allows preloading images before the dialog is opened.

const lightbox = new PhotoSwipeLightbox({
  // options
});
// you may bind events here
lightbox.init();

destroy

destroy()

Unbinds all events, and closes PhotoSwipe if it is currently open. Once the instance is destroyed, it cannot be initialized again.

let lightbox = new PhotoSwipeLightbox ({
  gallery: '#gallery--test-destroy',
  pswpModule: PhotoSwipe,
  children: 'a',
  returnFocus: false
});
lightbox.init();
const button = document.createElement('button');
button.type = 'button';
button.innerText = 'destroy the photoswipe';
document.querySelector('#gallery--test-destroy').after(button);
button.onclick = () => {
  if (lightbox) {
    lightbox.destroy();
    lightbox = null;
  }
}

loadAndOpen

loadAndOpen(index, dataSource, point)

Open PhotoSwipe at a given index. Arguments:

  1. Index of a slide to open (number)

  2. Optional data source (DataSource).

  3. Optional Point (x: number, y: number) that determines where exactly the user clicked on the thumbnail. Bby default it is { x: e.clientX, y: e.clientY } of the corresponding click event).

If you use gallery & children options and you omit the second argument (do not provide data source) - PhotoSwipe will use the first gallery element. To select another gallery element you must define data source as { gallery: HTMLElement }, for example:

const lightbox = new PhotoSwipeLightbox ({
  gallery: '#gallery--with-button',
  pswpModule: PhotoSwipe,
  children: 'a',
});
lightbox.init();
const btn = document.createElement ('button');
btn.type = 'button';
btn.innerText = 'open second image';
document.querySelector('#gallery--with-button').after(btn);
btn.onclick = () => {
  lightbox.loadAndOpen(1, {
    gallery: document.querySelector ('#gallery--with-button')
  });
  // You also can just trigger the native click
  // document.querySelector('#gallery--with-button a:nth-of-type(2)').click();
};

Events

All events can be bound directly to lightbox, they’ll automatically map to PhotoSwipe core when it’s open.

const lightbox = new PhotoSwipeLightbox({
  // options...
});
lightbox.init();

Initialization

import PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';
const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery--test-init-events',
  children: 'a',
  pswpModule: () => import('/photoswipe/photoswipe.esm.js')
});
lightbox.on('beforeOpen', () => {
  console.log('beforeOpen');
  // photoswipe starts to open
});
lightbox.on('firstUpdate', () => {
  console.log('firstUpdate');
  // photoswipe keeps opening
  // you may modify initial index or basic DOM structure
});
lightbox.on('initialLayout', () => {
  console.log('initialLayout');
  // photoswipe measures size of various elements
  // if you need to read getBoundingClientRect of something - do it here
});
lightbox.on('change', () => {
  // triggers when slide is switched, and at initialization
  console.log('change');
});
lightbox.on('afterInit', () => {
  console.log('afterInit');
  // photoswipe fully initialized and opening transition is running (if available)
});
lightbox.on('bindEvents', () => {
  console.log('bindEvents');
  // photoswipe binds DOM events (such as pointer events, wheel, etc)
});
lightbox.init();

Opening or Closing transitions

The events will trigger even if transition is disabled.

import PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';
const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery--test-opening-closing-events',
  children: 'a',
  pswpModule: () => import('/photoswipe/photoswipe.esm.js')
});
lightbox.on('openingAnimationStart', () => {
  console.log('openingAnimationStart');
});
lightbox.on('openingAnimationEnd', () => {
  console.log('openingAnimationEnd');
});
lightbox.on('closingAnimationStart', () => {
  console.log('closingAnimationStart');
});
lightbox.on('closingAnimationEnd', () => {
  console.log('closingAnimationEnd');
});
lightbox.init();

Closing

import PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';
const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery--test-closing-events',
  children: 'a',
  pswpModule: () => import('/photoswipe/photoswipe.esm.js')
});
lightbox.on('close', () => {
  // PhotoSwipe starts to close, unbind most events here
  console.log('close');
});
lightbox.on('destroy', () => {
  // PhotoSwipe is fully closed, destroy everything
  console.log('destroy');
});
lightbox.init();

Pointer and Gesture

import PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';
const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery--test-pointer-events',
  children: 'a',
  pswpModule: () => import('/photoswipe/photoswipe.esm.js')
});
lightbox.on('pointerDown', (e) => {
  console.log('pointerDown', e.originalEvent);
});
lightbox.on('pointerMove', (e) => {
  console.log('pointerMove', e.originalEvent);
});
lightbox.on('pointerUp', (e) => {
  console.log('pointerUp', e.originalEvent);
});
lightbox.on('pinchClose', (e) => {
  // triggered when using pinch to close gesture
  // can be default prevented
  console.log('pinchClose', e.bgOpacity);
});
lightbox.on('verticalDrag', (e) => {
  // triggered when using vertical drag to close gesture
  // can be default prevented
  console.log('verticalDrag', e.panY);
});
lightbox.init();

Slide content

Refer to Custom Content section of docs for examples.

import PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';
import PhotoSwipe from '/photoswipe/photoswipe.esm.js';
const lightbox = new PhotoSwipeLightbox({
  gallery: '#gallery--test-content-events',
  children: 'a',
  pswpModule: PhotoSwipe
});
lightbox.on('contentInit', ({ content }) => {
  console.log('contentInit', content);
});
lightbox.on('contentLoad', ({ content, isLazy }) => {
  // content starts to load
  // can be default prevented
  // assign elements to `content.element`
  console.log('contentLoad', content, isLazy);
});
lightbox.on('contentLoadImage', ({ content, isLazy }) => {
  // similar to the previous one, but triggers only for image content
  // can be default prevented
  console.log('contentLoadImage', content, isLazy);
});
lightbox.on('loadComplete', ({ content, slide }) => {
  console.log('loadComplete', content);
});
lightbox.on('contentResize', ({ content, width, height }) => {
  // content will be resized
  // can be default prevented
  console.log('contentResize', content, width, height);
});
lightbox.on('imageSizeChange', ({ content, width, height, slide }) => {
  // content.element is image
  console.log('imageSizeChange', content, width, height, slide, slide.index);
});
lightbox.on('contentLazyLoad', ({ content }) => {
  // content start to lazy-load
  // can be default prevented
  console.log('contentLazyLoad', content);
});
lightbox.on('contentAppend', ({ content }) => {
  // content is added to dom
  // can be default prevented
  // content.slide.container.appendChild(content.element);
  console.log('contentAppend', content);
});
lightbox.on('contentActivate', ({ content }) => {
  // content becomes active (the current slide)
  // can be default prevented
  console.log('contentActivate', content);
});
lightbox.on('contentDeactivate', ({ content }) => {
  // content becomes inactive
  // can be default prevented
  console.log('contentDeactivate', content);
});
lightbox.on('contentRemove', ({ content }) => {
  // content is removed from DOM
  // can be default prevented
  console.log('contentRemove', content);
});
lightbox.on('contentDestroy', ({ content }) => {
  // content will be destroyed
  // can be default prevented
  console.log('contentDestroy', content);
});
lightbox.init();

Lightbox Captions

PhotoSwipe does not support captions out of the box, but a plugin is available. The plugin can automatically position the text below or aside the image, depending on the available space for small to medium-sized captions and only for images with the default fit scale mode.

Plugin demo

For accessibility, make sure that important captions are always available without PhotoSwipe - either use an alt attribute on thumbnails or aria-labelledby.

Initialization

The plugin has a single JS file photoswipe-caption-plugin.min.js and a single CSS file photoswipe-caption-plugin.css. The plugin can be initialized like so:

Base HTML Layout v1

Nihil voluptatem impedit officia voluptatem architecto voluptas nesciunt consequatur ea cupiditate aliquam exercitationem autem. Rerum eum exercitationem velit non praesentium voluptatum eaque voluptatem id ut. Et quo accusamus at quod id.

<!-- Slider main container (required) -->
<div id="gallery_id" class="swiper swiper-container">
  <!-- Slider wrapper element (required) -->
  <div class="swiper-wrapper">
    <!-- Slides (required) -->
    <div class="swiper-slide">
      <img src="path/to/image-1.jpg">
      <span class="pswp-caption-content">Caption: Image 1</span>
    </div>
    <!-- more slides -->
    <div class="swiper-slide">
      <img src="path/to/image-x.jpg">
      <span class="pswp-caption-content">Caption: Image X</span>
    </div>
  </div> <!-- END wrapper element -->
  <!-- Sniper Pagination (optional) -->
  <div class="swiper-pagination"></div>
  <!-- Sniper Navigation (optional) -->
  <div class="swiper-button-prev"></div>
  <div class="swiper-button-next"></div>
  <!-- Sniper Scrollbar (optional)-->
  <div class="swiper-scrollbar"></div>
</div> <!-- END Main container -->

Base HTML Layout v2

Optio expedita perspiciatis dignissimos quia qui debitis autem explicabo provident. Quis consequatur veniam reprehenderit aut non ab voluptatem rerum in dolor. Tempore quasi delectus expedita aut sit et nesciunt molestiae dolores amet earum quae.

<!-- Slider main container (required) -->
<div id="gallery_id" class="swiper swiper-container">
  <!-- Slider wrapper element (required) -->
    <ul class="swiper-wrapper">
      <!-- Slides (required) -->
      <li class="swiper-slide">
        <img src="path/to/image-1.jpg">
        <span class="pswp-caption-content">Caption: Image 1</span>
      </li>
      <!-- more slides -->
      <li class="swiper-slide">
        <img src="path/to/image-x.jpg">
        <span class="pswp-caption-content">Caption: Image X</span>
      </li>
    </ul> <!-- END swiper-wrapper (slides) -->
  <!-- Sniper Pagination (optional) -->
  <div class="swiper-pagination"></div>
  <!-- Sniper Navigation (optional) -->
  <div class="swiper-button-prev"></div>
  <div class="swiper-button-next"></div>
  <!-- Sniper Scrollbar (optional)-->
  <div class="swiper-scrollbar"></div>
</div> <!-- END Main container -->

Extended HTML Layout

Est voluptas ipsum adipisci voluptas. Ex aperiam dolores culpa sit dolorum et non corrupti necessitatibus. Accusamus animi mollitia officiis distinctio nisi et sit veniam.

<!-- Slider main container (required) -->
<div id="gallery_id" class="swiper swiper-container">
  <!-- Slider wrapper element (required) -->
  <ul class="swiper-wrapper">
    <!-- Slides (required) -->
    <li class="swiper-slide">
      <a href="path/to/image-1.jpg"
        data-pswp-width="<max_width>"
        data-pswp-height="<max_height>">
        <img src="path/to/image-1.jpg" alt="Image 1">
        <span class="pswp-caption-content">Caption: Image 1</span>
      </a>
    </li>
    <!-- more slides -->
    <li class="swiper-slide">
      <a href="path/to/image-x.jpg"
        data-pswp-width="<max_width>"
        data-pswp-height="<max_height>">
        <img src="path/to/image-x.jpg" alt="Image X">
        <span class="pswp-caption-content">Caption: Image X</span>
      </a>
    </li>
  </ul> <!-- END swiper-wrapper (slides) -->
  <!-- Sniper Pagination (optional) -->
  <div class="swiper-pagination"></div>
  <!-- Sniper Navigation (optional) -->
  <div class="swiper-button-prev"></div>
  <div class="swiper-button-next"></div>
  <!-- Sniper Scrollbar (optional)-->
  <div class="swiper-scrollbar"></div>
</div> <!-- END Main container -->

Sniper Initialization (Javascript)

Nihil magni vel pariatur et et sit ea autem debitis explicabo omnis sequi. Et distinctio odio est architecto occaecati et ipsam velit ea veritatis qui. Deserunt quos eos ullam perferendis sit aperiam aliquam quia rerum mollitia tempora et ullam.

<!-- Sniper Initialization (Javascript) -->
<script>
  const myLightbox = new PhotoSwipeLightbox({
    gallerySelector: '#my_gallery',
    pswpModule: PhotoSwipe,
    // .. other options
  });
  const captionPlugin = new PhotoSwipeDynamicCaption(myLightbox, {
    type: 'auto'
  });
  // init photoswipe core >>after<< the plugin is added
  myLightbox.init();
</script>

PhotoSwipe Caption Parameters

Fuga ut quam illum placeat nisi molestias consequuntur ut beatae. Eum accusantium rerum eum recusandae in. Qui vel qui aut voluptatibus ut labore non saepe architecto.

Name Type Default Description | Example

captionContent

string

.pswp-caption-content

Used to retrieve caption content. Can be a selector of the element from which caption content will be retrieved, if the element is not found, the plugin will try to use the thumbnail image alt attribute.

type

string

auto

Position type of the caption can be auto, below, or aside.

  • below, caption will always be placed below the image

  • aside, caption will always be placed on the right side of the image

  • auto, the plugin will try to automatically determine the best position (depending on available space)

mobileLayoutBreakpoint

number

600

Maximum window width at which mobile layout should be used, or a function that should return true if mobile layout should be used.

Example
mobileLayoutBreakpoint: (pswp, captionPlugin) => {
  return (window.innerWidth < 750);
}

horizontalEdgeThreshold

number

20

When the caption x position is less than this value, it’ll get class pswp__dynamic-caption—​on-hor-edge. You may use it to apply different styling, such as horizontal padding.

mobileCaptionOverlapRatio

number

0.3

Ratio defines the amount of horizontal empty space before the mobile caption switches to an “overlap” layout. For example, if it’s set to 0.3 - the caption will start overlapping the image when more than 30% of horizontal space is not occupied by an image. If you set it to 0 - the caption will always overlap. If you set it to 1 - the caption will constantly shift the image (unless it’s taller than the viewport).

verticallyCenterImage

boolean

false

If enabled, the image will always be vertically centered in the remaining space between the caption and the rest of the viewport. If set to false (default value) - the image will lift up only if the caption does not fit below.

Styling

The caption has class pswp__dynamic-caption.

It can be in one of these states:

  • Below the main image - pswp__dynamic-caption—​below.

  • Right side of the main image - pswp__dynamic-caption—​aside.

  • “Mobile” (by default just pinned to bottom) - pswp__dynamic-caption—​mobile

If the caption is near the left horizontal edge - it gets class pswp__dynamic-caption—​on-hor-edge.

Feel free to adjust styles in the plugin CSS file (and use media queries if you need to):

.pswp__dynamic-caption--aside {
  max-width: 300px;
  padding: 20px 15px 20px 20px;
  margin-top: 70px;
}
.pswp__dynamic-caption--below {
  max-width: 700px;
  padding: 15px 0 0;
}
.pswp__dynamic-caption--mobile {
  background: rgba(0, 0, 0, 0.5);
  padding: 10px 15px;
}

How auto positioning works

  • Check if there is more horizontal or vertical free space around the image.

  • If there is more free vertical space:

    • Set caption width to the width of the image

    • Add pswp__dynamic-caption—​below class, so the size can also be adjusted via CSS.

    • Measure caption height.

    • Check if the caption will fit without any adjustments to the image position.

      • If it does - just show the caption below the image.

      • If it doesn’t - reduce the pan area height by the height of the caption.

  • If there is more horizontal space:

    • Add pswp__dynamic-caption—​aside class, so the size can be adjusted via CSS.

    • Measure caption width.

    • Check if caption will fit on the right side without any adjustments of image position.

      • If it does - just show the caption aside from the image.

      • If it doesn’t - reduce the pan area width by the width of the caption.

If mobileLayoutBreakpoint requirements are met:

  • Measure caption height when it occupies 100% of width.

  • Reduce pan area height to fit the caption below the image.

  • Check the amount of free horizontal space after the adjustment.

  • If there is too much horizontal space (mobileCaptionOverlapRatio), just overlap the caption and keep the image at the default position.