Responsive Photo Gallery

February 27, 2019

This is a simple, responsive photo gallery, using HTML, CSS, and JavaScript. (It’s very similar to the one that I use on this website.)

Example:

JavaScript

(function($) {
  if(typeof($) != "function")
    return;

  /**
   * PhotoGallery class
   */
  let PhotoGallery = function(container) {
    //gallery element
    this.Container = container;

    //Our Photo URLs
    this.Photos = [];

    //Starting Index
    this.PhotoIndex = -1;

    /*
     * Photo Images
     * Create three, so we can pre-load the previous/next
     * photos for faster loading on the user's experience.
     */
    this.PhotoCurrent = $(document.createElement("img"));
    this.PhotoPrevious = $(document.createElement("img"));
    this.PhotoNext = $(document.createElement("img"));

    //Show next photo when user clicks the current photo
    this.PhotoCurrent.on('click', this.next.bind(this));

    //Modal Container
    this.ModalHolder = $(document.createElement("div"));
    this.ModalHolder.addClass('modal-holder');

    //Modal Content - Holds the current photo.
    this.ModalContent = $(document.createElement("div"));
    this.ModalContent.addClass('modal-content');
    this.ModalContent.append(this.PhotoCurrent);
    this.ModalHolder.append(this.ModalContent);

    //Modal controls - navigation, share buttons, etc.
    this.ModalControls = $(document.createElement("div"));
    this.ModalControls.addClass('modal-controls');
    this.ModalHolder.append( this.ModalControls );

    //Close Button
    this.ButtonClose = $(document.createElement("a"));
    this.ButtonClose.addClass('modal-close fas fa-times-circle');
    this.ButtonClose.attr('href', '#');
    this.ModalHolder.append(this.ButtonClose);
    this.ButtonClose.on('click', function(ev) {
      ev.preventDefault();
      this.close();
    }.bind(this));

    //Previous Button
    this.ButtonPrev = $(document.createElement("a"));
    this.ButtonPrev.addClass('fas fa-arrow-left');
    this.ButtonPrev.attr('href','#');
    this.ModalControls.append(this.ButtonPrev);
    this.ButtonPrev.on('click', function(ev) {
      ev.preventDefault();
      this.prev();
    }.bind(this));

    //Next button
    this.ButtonNext = $(document.createElement("a"));
    this.ButtonNext.addClass('fas fa-arrow-right');
    this.ButtonNext.attr('href','#');
    this.ModalControls.append(this.ButtonNext);
    this.ButtonNext.on('click', function(ev) {
      ev.preventDefault();
      this.next();
    }.bind(this));

    //Keyboard handler
    $(window).on('keyup', this.handleKeyUp.bind(this));

    //prepare our photos
    this.loadPhotos();

    //Add the gallery modal to the document
    $("body").append(this.ModalHolder);
  };

  /**
  * Loads the photos URLs to the gallery.
  */
  PhotoGallery.prototype.loadPhotos = function() {
    this.Photos.length = 0;
    this.PhotoIndex = -1;
    let i = 0;
    let that = this;
    this.Container.find('a').each(function() {
      $(this).data('photo-index', i);
      $(this).on('click', function(ev) {
         ev.preventDefault();
         ev.stopPropagation();
         that.show($(this).data('photo-index'));
         $(this).blur();
      });
      that.Photos.push($(this).attr('href'));
      i++;
      });
  };

  /**
   * Handles keyboard commands.
   * Commands are ignored if the modal is not displayed.
   */
  PhotoGallery.prototype.handleKeyUp = function(ev) {
    if(!this.ModalHolder.hasClass('shown'))
      return;
    //prevent default on known controls only
    switch(ev.keyCode) {
      case 37 :
      case 38 :
      case 39 :
      case 40 :
      case 33 :
      case 34 :
      case 32 :
      case 36 :
      case 35 :
      case 27 :
        ev.preventDefault();
        ev.stopPropagation();
      break;
      default :
        return;
      break;
   }

   switch(ev.keyCode) {
     //escape - close
     case 27 :
       this.close();
     break;

     //Next (space, right, down, pgdown)
     case 32 :
     case 40 :
     case 39 :
     case 34 :
       this.next();
     break;

     //Previous (left, up, pgup)
     case 33 :
     case 37 :
     case 38 :
       this.prev();
     break;

     //End - last Photo
     case 35 :
       this.show(this.Photos.length - 1);
     break;

     //Home - First Photo
     case 36 :
       this.show(0);
     break;
    }
  };

  /**
   * Closes the gallery's modal element.
   */
  PhotoGallery.prototype.close = function() {
    $(window).unbind('keyup', this.handleKeyUp.bind(this));
    this.ModalHolder.removeClass('shown');
  };

  /**
   * Displays the next photo.
   */
  PhotoGallery.prototype.next = function() {
    this.show(this.PhotoIndex + 1);
  };

  /**
   * Displays the previous photo.
   */
  PhotoGallery.prototype.prev = function() {
    this.show(this.PhotoIndex - 1);
  };

  /**
   * Displays the photo at the given index.
   * @param {Number} index
   */
  PhotoGallery.prototype.show = function(index) {
    //keep within bounds of the Photos
    if(index < 0)
      index = this.Photos.length - 1;
    else if(index >= this.Photos.length)
      index = 0;

    if(index < 0)
      index = 0;

    //ignore if photo does not exist
    if(!this.Photos[index])
       return;

    //set current photo, show modal
    this.PhotoCurrent.attr('src', this.Photos[index]);
    this.ModalHolder.addClass('shown');
    this.PhotoIndex = index;

    //load next photo
    var ni = index + 1;
    if(ni >= this.Photos.length)
       ni = 0;

    if(this.Photos[ni]) {
       this.PhotoNext.attr('src', this.Photos[ni]);
    }

    //load previous photo
    var pi = index - 1;
    if(pi < 0)
      pi = this.Photos.length - 1;

    if(this.Photos[pi]) {
      this.PhotoPrevious.attr('src', this.Photos[pi]);
    }
  };

  $(document).ready(function() {
    $(".gallery").each(function() {
      var gallery = new PhotoGallery($(this));
    });
  });

}(jQuery));

CSS

.gallery {
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  justify-content: center;
}

.gallery > figure {
  margin: 0;
  padding: 0;
  flex: 0 0 150px;
  height: 150px;
}

.gallery > figure > .gallery-icon {
  border: solid 1px #999999;
  width: 100%;
  height: 100%;
}

.gallery > figure > .gallery-icon > a {
  display: block;
  width: 100%;
  height: 100%;
  position: relative;
}

.gallery > figure > .gallery-icon > a > img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  transition: opacity 150ms linear;
}

.gallery > figure > .gallery-icon > a:hover > img {
  opacity: 0.9;
}

.modal-holder {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  margin: auto;
  width: 100%;
  height: 100%;
  z-index: 10;
  background-color: rgba(0,0,0,0.95);
  transform: scale(0,0);
  transition: transform 150ms linear;
  overflow: hidden;
}

.modal-holder.shown {
  transform: scale(1,1);
}

.modal-holder > .modal-content {
  position: absolute;
  width: calc(100% - 20px);
  height: calc(100% - 20px);
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  margin: auto;
  z-index: 1;
  transition: all 150ms linear;
}

.modal-holder > .modal-content > img {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  margin: auto;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  transition: all 150ms linear;
}

.modal-holder > .modal-controls {
  position: absolute;
  width: 100%;
  height: 50px;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  background-color: rgba(0,0,0,0.5);
  display: flex;
  flex-flow: row wrap;
}

.modal-holder > .modal-controls > a {
  flex: 1;
  font-size: 24px;
  color: #FFFFFF;
  text-align: center;
  line-height: 50px;
  height: 50px;
  text-decoration: none;
}

.modal-holder > a.modal-close {
  position: absolute;
  top: 5px;
  right: 5px;
  width: 50px;
  height: 50px;
  font-size: 24px;
  color: #FFFFFF;
  text-align: center;
  line-height: 50px;
  text-decoration: none;
  z-index: 3;
}

HTML

<div class="gallery">
  <figure>
    <div class="gallery-icon">
      <a href="whale1.jpg">
        <img src="whale1-thumbnail.jpg"/>
      </a>
    </div>
  </figure>
  <figure>
    <div class="gallery-icon">
      <a href="whale2.jpg">
        <img src="whale2-thumbnail.jpg"/>
      </a>
    </div>
  </figure>
  <figure>
    <div class="gallery-icon">
      <a href="whale3.jpg">
        <img src="whale3-thumbnail.jpg"/>
      </a>
    </div>
  </figure>
  <figure>
    <div class="gallery-icon">
      <a href="whale4.jpg">
        <img src="whale4-thumbnail.jpg"/>
      </a>
    </div>
  </figure>
  <figure>
    <div class="gallery-icon">
      <a href="whale5.jpg">
        <img src="whale5-thumbnail.jpg"/>
      </a>
    </div>
   </figure>
</div>

Download

Download responsive-photo-gallery.zip (2MB)