Skip to content

Repository files navigation

SmartPhoto

npm version npm download GitHub license

The most easy to use responsive image viewer especially for mobile devices

See https://appleple.github.io/SmartPhoto/ for complete docs and demos
If you are Japasese, See here https://www.appleple.com/blog/javascript/smartphoto-js.html instead.

Feature

  • Intuitive gestures such as pinch-in/pinch-out/drag/swipe
  • Use Accelerometer to move images
  • Accessible from keyboards and screen-readers
  • Show pictures via URL hash
  • Can make photo groups

Installation

via npm

npm install smartphoto --save

or yarn

yarn add smartphoto

Usage

require

const SmartPhoto = require('smartphoto');

smartphoto.js

document.addEventListener('DOMContentLoaded',function(){
    new SmartPhoto(".js-smartphoto");
});

jquery-smartphoto.js

$(function(){
    $(".js-smartphoto").SmartPhoto();
});

Basic Standalone Usage

<a href="./assets/large-bear.jpg" class="js-smartphoto" data-caption="bear" data-id="bear" data-group="0">
  <img src="./assets/bear.jpg" width="360"/>
</a>
<a href="./assets/large-camel.jpg" class="js-smartphoto" data-caption="camel" data-id="camel" data-group="0">
  <img src="./assets/camel.jpg" width="360"/>
</a>
<a href="./assets/large-rhinoceros.jpg" class="js-smartphoto" data-caption="rhinoceros" data-id="sai" data-group="0">
  <img src="./assets/rhinoceros.jpg" width="360"/>
</a>
<link rel="stylesheet" href="./css/smartphoto.min.css">
<script src="./js/smartphoto.js"></script>
<script>
document.addEventListener('DOMContentLoaded',function(){
  new SmartPhoto(".js-smartphoto");
});
</script>

When SmartPhoto is constructed with a CSS selector string (as above), clicks are handled via a single delegated listener, so elements added to the page after construction (e.g. by Ajax/infinite scroll) are picked up automatically just by clicking them — no need to call addItem()/addNewItem() manually. Right before a photo is opened, SmartPhoto also reconciles that photo's group against the current DOM: newly appended matching elements are added, and elements that have since been removed from the DOM are dropped from the group (remaining indices are recalculated). This auto-detection only applies to the selector-string form; when a NodeList/Element[] is passed (or in data source mode below), add/remove items explicitly via addItem()/addNewItem().

A few things to keep in mind:

  • Reconciliation only happens right before a photo is opened (via a click, show(), or hash restoration) — not continuously. If an element disappears from the DOM while the viewer is already open and you next()/prev() through the same session, that removal isn't reflected until the viewer is opened again.
  • It does not support replacing an entire container's innerHTML (which recreates existing elements too, as brand-new DOM nodes) — that produces duplicate items, since the old and new elements aren't recognized as the same one. Only appending/removing individual elements is supported; if you regenerate the whole container, call destroy() and construct a new instance instead.
  • Changing data-group on an element that has already been opened/registered has no effect (the group is fixed at first registration). Changing it before the element is first interacted with is picked up correctly.
  • If multiple instances are built with overlapping selectors, avoid relying on distinguishing exactly which instance handles a click for elements that could match either.

Programmatic usage (data source mode)

Instead of scanning <a> elements in the page, you can pass an array of slide objects directly (inspired by yet-another-react-lightbox). This is useful when your images come from an API or a JS-rendered list.

const photo = new SmartPhoto([
  { src: "/img/bear-large.jpg", thumb: "/img/bear.jpg", caption: "bear", id: "bear" },
  { src: "/img/camel-large.jpg", thumb: "/img/camel.jpg", caption: "camel", id: "camel", width: 1200, height: 800 },
]);

photo.show(0);       // open by index
photo.show("camel");  // or by id
photo.next();
photo.prev();
photo.hide();
photo.on("change", () => { /* ... */ }); // same event contract as HTML mode

Slide fields:

field required description
src yes full-size image URL (equivalent to href in HTML mode)
thumb no thumbnail URL used in the nav strip. Falls back to src
caption no caption text (equivalent to data-caption)
alt no image alt text. Falls back to caption, then src
id no identifier used by show(id) and the URL hash. Falls back to the index
group no group name (equivalent to data-group). Falls back to "nogroup"
width / height no natural image size in px. When given, SmartPhoto skips the preload used to measure the image

show(indexOrId, options) also accepts options.group (which group to open) and options.trigger (the element to animate from / return focus to). Both HTML mode and data source mode share the exact same public API, options, and events.

Option

variable description default
arrows prev/next arrows true
nav navigation images at the bottom true
showAnimation animate the open/close transition true
verticalGravity apply device-tilt gravity to the vertical axis too (in addition to horizontal) false
useOrientationApi use the accelerometer (deviceorientation) to move a zoomed image false
useHistoryApi update the URL hash (#group=…&photo=…) via the History API true
swipeTopToClose close the viewer on an upward swipe false
swipeBottomToClose close the viewer on a downward swipe true
swipeOffset minimum swipe distance (px) to trigger navigation/close 100
swipeVelocity minimum swipe speed (px/ms) that triggers navigation even below swipeOffset (fast flicks) 0.5
headerHeight height (px) reserved for the header when fitting images 60
footerHeight height (px) reserved for the footer when fitting images 60
resizeStyle resize images to fill/fit on the screen 'fit'
animationSpeed animation speed (ms) when switching/opening/closing images 450
forceInterval frequency (ms) to apply force to images 10
registance friction applied to the inertia scroll of a zoomed image 0.5
loadOffset number of neighboring slides to preload around the current one 2
lazyAttribute attribute read for a lazy-loaded thumbnail (HTML mode only) 'data-src'
classNames override any of the generated CSS class names see source
message override screen-reader text (gotoNextImage / gotoPrevImage / closeDialog / carouselLabel) see source

Hide parts

document.addEventListener('DOMContentLoaded',function(){
    new SmartPhoto(".js-smartphoto",{
        arrows: false,
        nav: false
    });
});

Fit/Fill Option

You can choose if you want to scale images to fit/fill

document.addEventListener('DOMContentLoaded',function(){
  new SmartPhoto(".js-smartphoto",{
      resizeStyle: 'fit'
  });
});

Event

// when the modal opened
photo.on('open',function(){
    console.log('open');
});
// when the modal closed
photo.on('close',function(){
    console.log('close');
});
// when all images are loaded
photo.on('loadall',function(){
    console.log('loadall');
});
// when photo is changed
photo.on('change',function(){
    console.log('change');
});
// when swipe started
photo.on('swipestart',function(){
    console.log('swipestart');
});
// when swipe ended
photo.on('swipeend',function(){
    console.log('swipeend');
});
// when zoomed in
photo.on('zoomin',function(){
    console.log('zoomin');
});
// when zoomed out
photo.on('zoomout',function(){
    console.log('zoomout');
});

Methods

method description
on(event, listener) subscribe to one of the events listed above
destroy() remove the viewer and all of its event listeners
[Symbol.dispose]() same as destroy(). Lets a using declaration destroy the instance automatically when it goes out of scope: { using photo = new SmartPhoto(...); }
gotoSlide(index) go to the slide at index within the current group
hidePhoto(dir?) close the viewer. dir is 'bottom' (default) or 'top' and controls the close animation direction
zoomPhoto() / zoomOutPhoto() zoom the current image in/out programmatically
addNewItem(element) register a new <a> thumbnail element (HTML mode). Only needed when constructed with a NodeList/Element[] or otherwise not using a selector string — with a selector string, elements added later are auto-detected on click (see above)
show(indexOrId?, options?) open the viewer, by index or id. Works in both HTML mode and data source mode. options.group picks the group; options.trigger sets the element to animate from and to return focus to
hide() alias of hidePhoto()
next() / prev() go to the next/previous slide. No-op at the start/end of the group
addItem(slideOrElement) add a new item. Accepts a slide object (data source mode, where explicit registration is always required) or an Element (HTML mode, same caveat as addNewItem)
currentIndex (getter) the index currently displayed within its group

CSS Custom Properties

property description default
--smartphoto-animation-speed animation speed when switching/opening/closing images. Overridden per-instance by the animationSpeed JS option 450ms
--smartphoto-animation-function easing function used for animations ease-out
--smartphoto-backdrop-color backdrop color when viewing images rgba(0, 0, 0, 1)
--smartphoto-header-color header color rgba(0, 0, 0, .2)

Set these on .smartphoto (or :root) to override the defaults, no rebuild required:

.smartphoto {
  --smartphoto-animation-speed: 450ms;
  --smartphoto-animation-function: ease-in-out;
  --smartphoto-backdrop-color: rgba(0, 0, 0, 0.9);
  --smartphoto-header-color: rgba(0, 0, 0, 0.4);
}

Download

Download ZIP

Github

https://github.com/appleple/SmartPhoto

License

Code and documentation copyright 2017 by appleple, Inc. Code released under the MIT License.

About

The most easy to use responsive image viewer especially for mobile devices

Topics

Resources

Stars

897 stars

Watchers

25 watching

Forks

Releases

Packages

Used by

Contributors

Languages