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)