← Snippets - Script

Photo Gallery Lightbox

The followiing utilizes the simpleLightbox jquery plugin and allows you to wrap any collection of images in a parent class and have those images load in a lightbox environment when clicked, and allow you to cycle through all of the subsequent images inside the same parent div.

How To

Scripts

You will be adding one premade plugin, and two custom scripts. The first script runs on the initial load of the page, and then the lightbox script runs after the DOM loads, so that all of the images are already wrapped before the lightbox plugin is ever initialized. And this ensures that none of this will happen inside the CMS and cause havoc and pain.

Plugin

Install the jquery.simpleLightbox.js script in the /scripts/plugins folder of your project, and include this file in the scripts.mustache file.

(function(root, factory) {

    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.SimpleLightbox = factory();
    }

}(this, function() {

    function assign(target) {

        for (var i = 1; i < arguments.length; i++) {

            var obj = arguments[i];

            if (obj) {
                for (var key in obj) {
                    obj.hasOwnProperty(key) && (target[key] = obj[key]);
                }
            }

        }

        return target;

    }

    function addClass(element, className) {

        if (element && className) {
            element.className += ' ' + className;
        }

    }

    function removeClass(element, className) {

        if (element && className) {
            element.className = element.className.replace(
                new RegExp('(\\s|^)' + className + '(\\s|$)'), ' '
            ).trim();
        }

    }

    function parseHtml(html) {

        var div = document.createElement('div');
        div.innerHTML = html.trim();

        return div.childNodes[0];

    }

    function matches(el, selector) {

        return (el.matches || el.matchesSelector || el.msMatchesSelector).call(el, selector);

    }

    function getWindowHeight() {

        return 'innerHeight' in window
            ? window.innerHeight
            : document.documentElement.offsetHeight;

    }

    function SimpleLightbox(options) {

        this.init.apply(this, arguments);

    }

    SimpleLightbox.defaults = {

        // add custom classes to lightbox elements
        elementClass: '',
        elementLoadingClass: 'slbLoading',
        htmlClass: 'slbActive',
        closeBtnClass: '',
        nextBtnClass: '',
        prevBtnClass: '',
        loadingTextClass: '',

        // customize / localize controls captions
        closeBtnCaption: 'Close',
        nextBtnCaption: 'Next',
        prevBtnCaption: 'Previous',
        loadingCaption: 'Loading...',

        bindToItems: true, // set click event handler to trigger lightbox on provided $items
        closeOnOverlayClick: true,
        closeOnEscapeKey: true,
        nextOnImageClick: true,
        showCaptions: true,

        captionAttribute: 'title', // choose data source for library to glean image caption from
        urlAttribute: 'href', // where to expect large image

        startAt: 0, // start gallery at custom index
        loadingTimeout: 100, // time after loading element will appear

        appendTarget: 'body', // append elsewhere if needed

        beforeSetContent: null, // convenient hooks for extending library behavoiur
        beforeClose: null,
        afterClose: null,
        beforeDestroy: null,
        afterDestroy: null,

        videoRegex: new RegExp(/youtube.com|vimeo.com/) // regex which tests load url for iframe content

    };

    assign(SimpleLightbox.prototype, {

        init: function(options) {

            options = this.options = assign({}, SimpleLightbox.defaults, options);

            var self = this;
            var elements;

            if (options.$items) {
                elements = options.$items.get();
            }

            if (options.elements) {
                elements = [].slice.call(
                    typeof options.elements === 'string'
                        ? document.querySelectorAll(options.elements)
                        : options.elements
                );
            }

            this.eventRegistry = {lightbox: [], thumbnails: []};
            this.items = [];
            this.captions = [];

            if (elements) {

                elements.forEach(function(element, index) {

                    self.items.push(element.getAttribute(options.urlAttribute));
                    self.captions.push(element.getAttribute(options.captionAttribute));

                    if (options.bindToItems) {

                        self.addEvent(element, 'click', function(e) {

                            e.preventDefault();
                            self.showPosition(index);

                        }, 'thumbnails');

                    }

                });

            }

            if (options.items) {
                this.items = options.items;
            }

            if (options.captions) {
                this.captions = options.captions;
            }

        },

        addEvent: function(element, eventName, callback, scope) {

            this.eventRegistry[scope || 'lightbox'].push({
                element: element,
                eventName: eventName,
                callback: callback
            });

            element.addEventListener(eventName, callback);

            return this;

        },

        removeEvents: function(scope) {

            this.eventRegistry[scope].forEach(function(item) {
                item.element.removeEventListener(item.eventName, item.callback);
            });

            this.eventRegistry[scope] = [];

            return this;

        },

        next: function() {

            return this.showPosition(this.currentPosition + 1);

        },

        prev: function() {

            return this.showPosition(this.currentPosition - 1);

        },

        normalizePosition: function(position) {

            if (position >= this.items.length) {
                position = 0;
            } else if (position < 0) {
                position = this.items.length - 1;
            }

            return position;

        },

        showPosition: function(position) {

            var newPosition = this.normalizePosition(position);

            if (typeof this.currentPosition !== 'undefined') {
                this.direction = newPosition > this.currentPosition ? 'next' : 'prev';
            }

            this.currentPosition = newPosition;

            return this.setupLightboxHtml()
                .prepareItem(this.currentPosition, this.setContent)
                .show();

        },

        loading: function(on) {

            var self = this;
            var options = this.options;

            if (on) {

                this.loadingTimeout = setTimeout(function() {

                    addClass(self.$el, options.elementLoadingClass);

                    self.$content.innerHTML =
                        '<p class="slbLoadingText ' + options.loadingTextClass + '">' +
                            options.loadingCaption +
                        '</p>';
                    self.show();

                }, options.loadingTimeout);

            } else {

                removeClass(this.$el, options.elementLoadingClass);
                clearTimeout(this.loadingTimeout);

            }

        },

        prepareItem: function(position, callback) {

            var self = this;
            var url = this.items[position];

            this.loading(true);

            if (this.options.videoRegex.test(url)) {

                callback.call(self, parseHtml(
                    '<div class="slbIframeCont"><iframe class="slbIframe" frameborder="0" allowfullscreen src="' + url + '"></iframe></div>')
                );

            } else {

                var $imageCont = parseHtml(
                    '<div class="slbImageWrap"><img class="slbImage" src="' + url + '" /></div>'
                );

                this.$currentImage = $imageCont.querySelector('.slbImage');

                if (this.options.showCaptions && this.captions[position]) {
                    $imageCont.appendChild(parseHtml(
                        '<div class="slbCaption">' + this.captions[position] + '</div>')
                    );
                }

                this.loadImage(url, function() {

                    self.setImageDimensions();

                    callback.call(self, $imageCont);

                    self.loadImage(self.items[self.normalizePosition(self.currentPosition + 1)]);

                });

            }

            return this;

        },

        loadImage: function(url, callback) {

            if (!this.options.videoRegex.test(url)) {

                var image = new Image();
                callback && (image.onload = callback);
                image.src = url;

            }

        },

        setupLightboxHtml: function() {

            var o = this.options;

            if (!this.$el) {

                this.$el = parseHtml(
                    '<div class="slbElement ' + o.elementClass + '">' +
                        '<div class="slbOverlay"></div>' +
                        '<div class="slbWrapOuter">' +
                            '<div class="slbWrap">' +
                                '<div class="slbContentOuter">' +
                                    '<div class="slbContent"></div>' +
                                    '<button type="button" title="' + o.closeBtnCaption + '" class="slbCloseBtn ' + o.closeBtnClass + '">×</button>' +
                                    (this.items.length > 1
                                        ? '<div class="slbArrows">' +
                                             '<button type="button" title="' + o.prevBtnCaption + '" class="prev slbArrow' + o.prevBtnClass + '">' + o.prevBtnCaption + '</button>' +
                                             '<button type="button" title="' + o.nextBtnCaption + '" class="next slbArrow' + o.nextBtnClass + '">' + o.nextBtnCaption + '</button>' +
                                          '</div>'
                                        : ''
                                    ) +
                                '</div>' +
                            '</div>' +
                        '</div>' +
                    '</div>'
                );

                this.$content = this.$el.querySelector('.slbContent');

            }

            this.$content.innerHTML = '';

            return this;

        },

        show: function() {

            if (!this.modalInDom) {

                document.querySelector(this.options.appendTarget).appendChild(this.$el);
                addClass(document.documentElement, this.options.htmlClass);
                this.setupLightboxEvents();
                this.modalInDom = true;

            }

            return this;

        },

        setContent: function(content) {

            var $content = typeof content === 'string'
                ? parseHtml(content)
                : content
            ;

            this.loading(false);

            this.setupLightboxHtml();

            removeClass(this.$content, 'slbDirectionNext');
            removeClass(this.$content, 'slbDirectionPrev');

            if (this.direction) {
                addClass(this.$content, this.direction === 'next'
                    ? 'slbDirectionNext'
                    : 'slbDirectionPrev'
                );
            }

            if (this.options.beforeSetContent) {
                this.options.beforeSetContent($content, this);
            }

            this.$content.appendChild($content);

            return this;

        },

        setImageDimensions: function() {

            if (this.$currentImage) {
                this.$currentImage.style.maxHeight = getWindowHeight() + 'px';
            }

        },

        setupLightboxEvents: function() {

            var self = this;

            if (this.eventRegistry.lightbox.length) {
                return this;
            }

            this.addEvent(this.$el, 'click', function(e) {

                var $target = e.target;

                if (matches($target, '.slbCloseBtn') || (self.options.closeOnOverlayClick && matches($target, '.slbWrap'))) {

                    self.close();

                } else if (matches($target, '.slbArrow')) {

                    matches($target, '.next') ? self.next() : self.prev();

                } else if (self.options.nextOnImageClick && self.items.length > 1 && matches($target, '.slbImage')) {

                    self.next();

                }

            }).addEvent(document, 'keyup', function(e) {

                self.options.closeOnEscapeKey && e.keyCode === 27 && self.close();

                if (self.items.length > 1) {
                    (e.keyCode === 39 || e.keyCode === 68) && self.next();
                    (e.keyCode === 37 || e.keyCode === 65) && self.prev();
                }

            }).addEvent(window, 'resize', function() {

                self.setImageDimensions();

            });

            return this;

        },

        close: function() {

            if (this.modalInDom) {

                this.runHook('beforeClose');
                this.removeEvents('lightbox');
                this.$el && this.$el.parentNode.removeChild(this.$el);
                removeClass(document.documentElement, this.options.htmlClass);
                this.modalInDom = false;
                this.runHook('afterClose');

            }

            this.direction = undefined;
            this.currentPosition = this.options.startAt;

        },

        destroy: function() {

            this.close();
            this.runHook('beforeDestroy');
            this.removeEvents('thumbnails');
            this.runHook('afterDestroy');

        },

        runHook: function(name) {

            this.options[name] && this.options[name](this);

        }

    });

    SimpleLightbox.open = function(options) {

        var instance = new SimpleLightbox(options);

        return options.content
            ? instance.setContent(options.content).show()
            : instance.showPosition(instance.options.startAt);

    };

    SimpleLightbox.registerAsJqueryPlugin = function($) {

        $.fn.simpleLightbox = function(options) {

            var lightboxInstance;
            var $items = this;

            return this.each(function() {
                if (!$.data(this, 'simpleLightbox')) {
                    lightboxInstance = lightboxInstance || new SimpleLightbox($.extend({}, options, {$items: $items}));
                    $.data(this, 'simpleLightbox', lightboxInstance);
                }
            });

        };

        $.SimpleLightbox = SimpleLightbox;

    };

    if (typeof window !== 'undefined' && window.jQuery) {
        SimpleLightbox.registerAsJqueryPlugin(window.jQuery);
    }

    return SimpleLightbox;

}));

scripts.js

In your scripts.js file, include the following function in the $(function() { block of code:

This script will wrap all the images inside your gallery in the neccessary code needed to make the lightbox work. Since these are images inside content areas, this script must run outside of the CMS only, and prevents the client froim having to manually add these tags–which it is basically taking the source of the image and wrapping the image in a <a> tag with the href set to the source of the image and also appending a rel='simpleLightbox[gallery]' attribute.

if(top == self) { 
	$("div.gallery img").each(function(){ var src = $(this).attr("src"); var title = $(this).attr("alt"); if(title) { title = "title=\""+title+"\""; } $(this).wrap('<a href="'+src+'" rel="simpleLightbox[gallery]"'+title+'></a>'); }); 
}

And lastly include the following script inside the non-CMS block of code (the else statement of starting at around line 8). This second script initializes the actual lightbox plugin on all of the images that have just been wrapped.

$("a[rel^='simpleLightbox']").simpleLightbox({ showCaptions: false

Now, all you have to do is wrap all of your content areas that are supposed to contain images in a class in the JS function above $("div.gallery img").each(function(){ which is “.gallery”. This can be different, but make sure you update the javascript if needed.

Styles

Install the simpleLightbox.scss file in your scss/custom/plugins/ folder.

Include the file in the main.scss file to include the necessary styles to make the box work.

.slbOverlay, .slbWrapOuter, .slbWrap {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}
.slbActive {
  body {
    overflow:hidden;
  }
}
.slbOverlay {
  overflow: hidden;
  z-index: 2000;
  background-color: #000;
  opacity: 0.7;
  -webkit-animation: slbOverlay 0.5s;
  -moz-animation: slbOverlay 0.5s;
  animation: slbOverlay 0.5s;
}

.slbWrapOuter {
  overflow-x: hidden;
  overflow-y: auto;
  z-index: 2010;
}

.slbWrap {
  position: absolute;
  text-align: center;
}

.slbWrap:before {
  content: "";
  display: inline-block;
  height: 100%;
  vertical-align: middle;
}

.slbContentOuter {
  position: relative;
  display: inline-block;
  vertical-align: middle;
  margin: 0px auto;
  padding: 0 1em;
  box-sizing: border-box;
  z-index: 2020;
  text-align: left;
  max-width: 100%;
}

.slbContentEl .slbContentOuter {
  padding: 5em 1em;
}

.slbContent {
  position: relative;
}

.slbContentEl .slbContent {
  -webkit-animation: slbEnter 0.3s;
  -moz-animation: slbEnter 0.3s;
  animation: slbEnter 0.3s;
  background-color: $white;
  box-shadow: 0 0.2em 1em rgba(0, 0, 0, 0.4);
}

.slbImageWrap {
  -webkit-animation: slbEnter 0.3s;
  -moz-animation: slbEnter 0.3s;
  animation: slbEnter 0.3s;
  position: relative;
}

.slbImageWrap:after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 5em;
  bottom: 5em;
  display: block;
  z-index: -1;
  box-shadow: 0 0.2em 1em rgba(0, 0, 0, 0.6);
  background-color: $white;
}

.slbDirectionNext .slbImageWrap {
  -webkit-animation: slbEnterNext 0.4s;
  -moz-animation: slbEnterNext 0.4s;
  animation: slbEnterNext 0.4s;
}

.slbDirectionPrev .slbImageWrap {
  -webkit-animation: slbEnterPrev 0.4s;
  -moz-animation: slbEnterPrev 0.4s;
  animation: slbEnterPrev 0.4s;
}

.slbImage {
  width: auto;
  max-width: 100%;
  height: auto;
  display: block;
  line-height: 0;
  box-sizing: border-box;
  padding: 5em 0;
  margin: 0 auto;
}

.slbCaption {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  word-wrap: normal;
  font-size: $font-size-base;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0.71429em 0;
  color: $white;
  text-align: center;
}

.slbCloseBtn, .slbArrow {
  margin: 0;
  padding: 0;
  border: 0;
  cursor: pointer;
  background: none;
}

.slbCloseBtn::-moz-focus-inner, .slbArrow::-moz-focus-inner {
  padding: 0;
  border: 0;
}

.slbCloseBtn:hover, .slbArrow:hover {
  opacity: 0.5;
}

.slbCloseBtn:active, .slbArrow:active {
  opacity: 0.8;
}

.slbCloseBtn {
  -webkit-animation: slbEnter 0.3s;
  -moz-animation: slbEnter 0.3s;
  animation: slbEnter 0.3s;
  font-size: 3em;
  width: 1.66667em;
  height: 1.66667em;
  line-height: 1.66667em;
  position: absolute;
  right: -0.33333em;
  top: 0;
  color: $white;
  color: rgba(255, 255, 255, 0.7);
  text-align: center;
}

.slbLoading .slbCloseBtn {
  display: none;
}

.slbLoadingText {
  font-size: 1.4em;
  color: $white;
  color: rgba(255, 255, 255, 0.9);
}

.slbArrows {
  position: fixed;
  top: 50%;
  left: 0;
  right: 0;
}

.slbLoading .slbArrows {
  display: none;
}

.slbArrow {
  position: absolute;
  top: 50%;
  margin-top: -5em;
  width: 5em;
  height: 10em;
  opacity: 0.7;
  text-indent: -999em;
  overflow: hidden;
}

.slbArrow:before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -0.8em 0 0 -0.8em;
  border: 0.8em solid transparent;
}

.slbArrow.next {
  right: 0;
}

.slbArrow.next:before {
  border-left-color: $white;
}

.slbArrow.prev {
  left: 0;
}

.slbArrow.prev:before {
  border-right-color: $white;
}

.slbIframeCont {
  width: 80em;
  height: 0;
  overflow: hidden;
  padding-top: 56.25%;
  margin: 5em 0;
}

.slbIframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  box-shadow: 0 0.2em 1em rgba(0, 0, 0, 0.6);
  background: #000;
}

@-webkit-keyframes slbOverlay {
  from {
    opacity: 0;
  }
  to {
    opacity: 0.7;
  }
}

@-moz-keyframes slbOverlay {
  from {
    opacity: 0;
  }
  to {
    opacity: 0.7;
  }
}

@keyframes slbOverlay {
  from {
    opacity: 0;
  }
  to {
    opacity: 0.7;
  }
}

@-webkit-keyframes slbEnter {
  from {
    opacity: 0;
    -webkit-transform: translate3d(0, -1em, 0);
  }
  to {
    opacity: 1;
    -webkit-transform: translate3d(0, 0, 0);
  }
}

@-moz-keyframes slbEnter {
  from {
    opacity: 0;
    -moz-transform: translate3d(0, -1em, 0);
  }
  to {
    opacity: 1;
    -moz-transform: translate3d(0, 0, 0);
  }
}

@keyframes slbEnter {
  from {
    opacity: 0;
    -webkit-transform: translate3d(0, -1em, 0);
    -moz-transform: translate3d(0, -1em, 0);
    -ms-transform: translate3d(0, -1em, 0);
    -o-transform: translate3d(0, -1em, 0);
    transform: translate3d(0, -1em, 0);
  }
  to {
    opacity: 1;
    -webkit-transform: translate3d(0, 0, 0);
    -moz-transform: translate3d(0, 0, 0);
    -ms-transform: translate3d(0, 0, 0);
    -o-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
}

@-webkit-keyframes slbEnterNext {
  from {
    opacity: 0;
    -webkit-transform: translate3d(4em, 0, 0);
  }
  to {
    opacity: 1;
    -webkit-transform: translate3d(0, 0, 0);
  }
}

@-moz-keyframes slbEnterNext {
  from {
    opacity: 0;
    -moz-transform: translate3d(4em, 0, 0);
  }
  to {
    opacity: 1;
    -moz-transform: translate3d(0, 0, 0);
  }
}

@keyframes slbEnterNext {
  from {
    opacity: 0;
    -webkit-transform: translate3d(4em, 0, 0);
    -moz-transform: translate3d(4em, 0, 0);
    -ms-transform: translate3d(4em, 0, 0);
    -o-transform: translate3d(4em, 0, 0);
    transform: translate3d(4em, 0, 0);
  }
  to {
    opacity: 1;
    -webkit-transform: translate3d(0, 0, 0);
    -moz-transform: translate3d(0, 0, 0);
    -ms-transform: translate3d(0, 0, 0);
    -o-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
}

@-webkit-keyframes slbEnterPrev {
  from {
    opacity: 0;
    -webkit-transform: translate3d(-4em, 0, 0);
  }
  to {
    opacity: 1;
    -webkit-transform: translate3d(0, 0, 0);
  }
}

@-moz-keyframes slbEnterPrev {
  from {
    opacity: 0;
    -moz-transform: translate3d(-4em, 0, 0);
  }
  to {
    opacity: 1;
    -moz-transform: translate3d(0, 0, 0);
  }
}

@keyframes slbEnterPrev {
  from {
    opacity: 0;
    -webkit-transform: translate3d(-4em, 0, 0);
    -moz-transform: translate3d(-4em, 0, 0);
    -ms-transform: translate3d(-4em, 0, 0);
    -o-transform: translate3d(-4em, 0, 0);
    transform: translate3d(-4em, 0, 0);
  }
  to {
    opacity: 1;
    -webkit-transform: translate3d(0, 0, 0);
    -moz-transform: translate3d(0, 0, 0);
    -ms-transform: translate3d(0, 0, 0);
    -o-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);
  }
}

Tags: bs3, bs4, lightbox, gallery, js, javascript, jquery, plugin, script, sass, scss