/*! * baguettebox.js * @author feimosi * @version 1.8.2 * @url https://github.com/feimosi/baguettebox.js */ /* global define, module */ (function (root, factory) { 'use strict'; if (typeof define === 'function' && define.amd) { define(factory); } else if (typeof exports === 'object') { module.exports = factory(); } else { root.baguettebox = factory(); } }(this, function () { 'use strict'; // svg shapes used on the buttons var leftarrow = '' + '' + '', rightarrow = '' + '' + '', closex = '' + '' + '' + '' + ''; // global options and their defaults var options = {}, defaults = { captions: true, fullscreen: false, noscrollbars: false, titletag: false, buttons: 'auto', async: false, preload: 2, animation: 'slidein', aftershow: null, afterhide: null, // callback when image changes with `currentindex` and `imageselements.length` as parameters onchange: null, overlaybackgroundcolor: 'rgba(0,0,0,.8)' }; // object containing information about features compatibility var supports = {}; // dom elements references var overlay, slider, previousbutton, nextbutton, closebutton; // an array with all images in the current gallery var currentgallery = []; // current image index inside the slider var currentindex = 0; // touch event start position (for slide gesture) var touch = {}; // if set to true ignore touch events because animation was already fired var touchflag = false; // regex pattern to match image files var regex = /.+\.(gif|jpe?g|png|webp)/i; // object of all used galleries var data = {}; // array containing temporary images dom elements var imageselements = []; // the last focused element before opening the overlay var documentlastfocus = null; var overlayclickhandler = function(event) { // close the overlay when user clicks directly on the background if (event.target.id.indexof('baguette-img') !== -1) { hideoverlay(); } }; var previousbuttonclickhandler = function(event) { event.stoppropagation ? event.stoppropagation() : event.cancelbubble = true; // jshint ignore:line showpreviousimage(); }; var nextbuttonclickhandler = function(event) { event.stoppropagation ? event.stoppropagation() : event.cancelbubble = true; // jshint ignore:line shownextimage(); }; var closebuttonclickhandler = function(event) { event.stoppropagation ? event.stoppropagation() : event.cancelbubble = true; // jshint ignore:line hideoverlay(); }; var touchstarthandler = function(event) { touch.count++; if (touch.count > 1) { touch.multitouch = true; } // save x and y axis position touch.startx = event.changedtouches[0].pagex; touch.starty = event.changedtouches[0].pagey; }; var touchmovehandler = function(event) { // if action was already triggered or multitouch return if (touchflag || touch.multitouch) { return; } event.preventdefault ? event.preventdefault() : event.returnvalue = false; // jshint ignore:line var touchevent = event.touches[0] || event.changedtouches[0]; // move at least 40 pixels to trigger the action if (touchevent.pagex - touch.startx > 40) { touchflag = true; showpreviousimage(); } else if (touchevent.pagex - touch.startx < -40) { touchflag = true; shownextimage(); // move 100 pixels up to close the overlay } else if (touch.starty - touchevent.pagey > 100) { hideoverlay(); } }; var touchendhandler = function() { touch.count--; if (touch.count <= 0) { touch.multitouch = false; } touchflag = false; }; var trapfocusinsideoverlay = function(event) { if (overlay.style.display === 'block' && (overlay.contains && !overlay.contains(event.target))) { event.stoppropagation(); initfocus(); } }; // foreach polyfill for ie8 // http://stackoverflow.com/a/14827443/1077846 /* jshint ignore:start */ if (![].foreach) { array.prototype.foreach = function(callback, thisarg) { for (var i = 0; i < this.length; i++) { callback.call(thisarg, this[i], i, this); } }; } // filter polyfill for ie8 // https://gist.github.com/eliperelman/1031656 if (![].filter) { array.prototype.filter = function(a, b, c, d, e) { c = this; d = []; for (e = 0; e < c.length; e++) a.call(b, c[e], e, c) && d.push(c[e]); return d; }; } /* jshint ignore:end */ // script entry point function run(selector, useroptions) { // fill supports object supports.transforms = testtransformssupport(); supports.svg = testsvgsupport(); buildoverlay(); removefromcache(selector); bindimageclicklisteners(selector, useroptions); } function bindimageclicklisteners(selector, useroptions) { // for each gallery bind a click event to every image inside it var gallerynodelist = document.queryselectorall(selector); var selectordata = { galleries: [], nodelist: gallerynodelist }; data[selector] = selectordata; [].foreach.call(gallerynodelist, function(galleryelement) { if (useroptions && useroptions.filter) { regex = useroptions.filter; } // get nodes from gallery elements or single-element galleries var tagsnodelist = []; if (galleryelement.tagname === 'a') { tagsnodelist = [galleryelement]; } else { tagsnodelist = galleryelement.getelementsbytagname('a'); } // filter 'a' elements from those not linking to images tagsnodelist = [].filter.call(tagsnodelist, function(element) { return regex.test(element.href); }); if (tagsnodelist.length === 0) { return; } var gallery = []; [].foreach.call(tagsnodelist, function(imageelement, imageindex) { var imageelementclickhandler = function(event) { event.preventdefault ? event.preventdefault() : event.returnvalue = false; // jshint ignore:line prepareoverlay(gallery, useroptions); showoverlay(imageindex); }; var imageitem = { eventhandler: imageelementclickhandler, imageelement: imageelement }; bind(imageelement, 'click', imageelementclickhandler); gallery.push(imageitem); }); selectordata.galleries.push(gallery); }); } function clearcacheddata() { for (var selector in data) { if (data.hasownproperty(selector)) { removefromcache(selector); } } } function removefromcache(selector) { if (!data.hasownproperty(selector)) { return; } var galleries = data[selector].galleries; [].foreach.call(galleries, function(gallery) { [].foreach.call(gallery, function(imageitem) { unbind(imageitem.imageelement, 'click', imageitem.eventhandler); }); if (currentgallery === gallery) { currentgallery = []; } }); delete data[selector]; } function buildoverlay() { overlay = getbyid('baguettebox-overlay'); // check if the overlay already exists if (overlay) { slider = getbyid('baguettebox-slider'); previousbutton = getbyid('previous-button'); nextbutton = getbyid('next-button'); closebutton = getbyid('close-button'); return; } // create overlay element overlay = create('div'); overlay.setattribute('role', 'dialog'); overlay.id = 'baguettebox-overlay'; document.getelementsbytagname('body')[0].appendchild(overlay); // create gallery slider element slider = create('div'); slider.id = 'baguettebox-slider'; overlay.appendchild(slider); // create all necessary buttons previousbutton = create('button'); previousbutton.setattribute('type', 'button'); previousbutton.id = 'previous-button'; previousbutton.setattribute('aria-label', 'previous'); previousbutton.innerhtml = supports.svg ? leftarrow : '<'; overlay.appendchild(previousbutton); nextbutton = create('button'); nextbutton.setattribute('type', 'button'); nextbutton.id = 'next-button'; nextbutton.setattribute('aria-label', 'next'); nextbutton.innerhtml = supports.svg ? rightarrow : '>'; overlay.appendchild(nextbutton); closebutton = create('button'); closebutton.setattribute('type', 'button'); closebutton.id = 'close-button'; closebutton.setattribute('aria-label', 'close'); closebutton.innerhtml = supports.svg ? closex : '×'; overlay.appendchild(closebutton); previousbutton.classname = nextbutton.classname = closebutton.classname = 'baguettebox-button'; bindevents(); } function keydownhandler(event) { switch (event.keycode) { case 37: // left arrow showpreviousimage(); break; case 39: // right arrow shownextimage(); break; case 27: // esc hideoverlay(); break; } } function bindevents() { bind(overlay, 'click', overlayclickhandler); bind(previousbutton, 'click', previousbuttonclickhandler); bind(nextbutton, 'click', nextbuttonclickhandler); bind(closebutton, 'click', closebuttonclickhandler); bind(overlay, 'touchstart', touchstarthandler); bind(overlay, 'touchmove', touchmovehandler); bind(overlay, 'touchend', touchendhandler); bind(document, 'focus', trapfocusinsideoverlay, true); } function unbindevents() { unbind(overlay, 'click', overlayclickhandler); unbind(previousbutton, 'click', previousbuttonclickhandler); unbind(nextbutton, 'click', nextbuttonclickhandler); unbind(closebutton, 'click', closebuttonclickhandler); unbind(overlay, 'touchstart', touchstarthandler); unbind(overlay, 'touchmove', touchmovehandler); unbind(overlay, 'touchend', touchendhandler); unbind(document, 'focus', trapfocusinsideoverlay, true); } function prepareoverlay(gallery, useroptions) { // if the same gallery is being opened prevent from loading it once again if (currentgallery === gallery) { return; } currentgallery = gallery; // update gallery specific options setoptions(useroptions); // empty slider of previous contents (more effective than .innerhtml = "") while (slider.firstchild) { slider.removechild(slider.firstchild); } imageselements.length = 0; var imagesfiguresids = []; var imagescaptionsids = []; // prepare and append images containers and populate figure and captions ids arrays for (var i = 0, fullimage; i < gallery.length; i++) { fullimage = create('div'); fullimage.classname = 'full-image'; fullimage.id = 'baguette-img-' + i; imageselements.push(fullimage); imagesfiguresids.push('baguettebox-figure-' + i); imagescaptionsids.push('baguettebox-figcaption-' + i); slider.appendchild(imageselements[i]); } overlay.setattribute('aria-labelledby', imagesfiguresids.join(' ')); overlay.setattribute('aria-describedby', imagescaptionsids.join(' ')); } function setoptions(newoptions) { if (!newoptions) { newoptions = {}; } // fill options object for (var item in defaults) { options[item] = defaults[item]; if (typeof newoptions[item] !== 'undefined') { options[item] = newoptions[item]; } } /* apply new options */ // change transition for proper animation slider.style.transition = slider.style.webkittransition = (options.animation === 'fadein' ? 'opacity .4s ease' : options.animation === 'slidein' ? '' : 'none'); // hide buttons if necessary if (options.buttons === 'auto' && ('ontouchstart' in window || currentgallery.length === 1)) { options.buttons = false; } // set buttons style to hide or display them previousbutton.style.display = nextbutton.style.display = (options.buttons ? '' : 'none'); // set overlay color try { overlay.style.backgroundcolor = options.overlaybackgroundcolor; } catch (e) { // silence the error and continue } } function showoverlay(chosenimageindex) { if (options.noscrollbars) { document.documentelement.style.overflowy = 'hidden'; document.body.style.overflowy = 'scroll'; } if (overlay.style.display === 'block') { return; } bind(document, 'keydown', keydownhandler); currentindex = chosenimageindex; touch = { count: 0, startx: null, starty: null }; loadimage(currentindex, function() { preloadnext(currentindex); preloadprev(currentindex); }); updateoffset(); overlay.style.display = 'block'; if (options.fullscreen) { enterfullscreen(); } // fade in overlay settimeout(function() { overlay.classname = 'visible'; if (options.aftershow) { options.aftershow(); } }, 50); if (options.onchange) { options.onchange(currentindex, imageselements.length); } documentlastfocus = document.activeelement; initfocus(); } function initfocus() { if (options.buttons) { previousbutton.focus(); } else { closebutton.focus(); } } function enterfullscreen() { if (overlay.requestfullscreen) { overlay.requestfullscreen(); } else if (overlay.webkitrequestfullscreen) { overlay.webkitrequestfullscreen(); } else if (overlay.mozrequestfullscreen) { overlay.mozrequestfullscreen(); } } function exitfullscreen() { if (document.exitfullscreen) { document.exitfullscreen(); } else if (document.mozcancelfullscreen) { document.mozcancelfullscreen(); } else if (document.webkitexitfullscreen) { document.webkitexitfullscreen(); } } function hideoverlay() { if (options.noscrollbars) { document.documentelement.style.overflowy = 'auto'; document.body.style.overflowy = 'auto'; } if (overlay.style.display === 'none') { return; } unbind(document, 'keydown', keydownhandler); // fade out and hide the overlay overlay.classname = ''; settimeout(function() { overlay.style.display = 'none'; exitfullscreen(); if (options.afterhide) { options.afterhide(); } }, 500); documentlastfocus.focus(); } function loadimage(index, callback) { var imagecontainer = imageselements[index]; var galleryitem = currentgallery[index]; // return if the index exceeds prepared images in the overlay // or if the current gallery has been changed / closed if (imagecontainer === undefined || galleryitem === undefined) { return; } // if image is already loaded run callback and return if (imagecontainer.getelementsbytagname('img')[0]) { if (callback) { callback(); } return; } // get element reference, optional caption and source path var imageelement = galleryitem.imageelement; var thumbnailelement = imageelement.getelementsbytagname('img')[0]; var imagecaption = typeof options.captions === 'function' ? options.captions.call(currentgallery, imageelement) : imageelement.getattribute('data-caption') || imageelement.title; var imagesrc = getimagesrc(imageelement); // prepare figure element var figure = create('figure'); figure.id = 'baguettebox-figure-' + index; figure.innerhtml = '
' + '
' + '
' + '
'; // insert caption if available if (options.captions && imagecaption) { var figcaption = create('figcaption'); figcaption.id = 'baguettebox-figcaption-' + index; figcaption.innerhtml = imagecaption; figure.appendchild(figcaption); } imagecontainer.appendchild(figure); // prepare gallery img element var image = create('img'); image.onload = function() { // remove loader element var spinner = document.queryselector('#baguette-img-' + index + ' .baguettebox-spinner'); figure.removechild(spinner); if (!options.async && callback) { callback(); } }; image.setattribute('src', imagesrc); image.alt = thumbnailelement ? thumbnailelement.alt || '' : ''; if (options.titletag && imagecaption) { image.title = imagecaption; } figure.appendchild(image); // run callback if (options.async && callback) { callback(); } } // get image source location, mostly used for responsive images function getimagesrc(image) { // set default image path from href var result = image.href; // if dataset is supported find the most suitable image if (image.dataset) { var srcs = []; // get all possible image versions depending on the resolution for (var item in image.dataset) { if (item.substring(0, 3) === 'at-' && !isnan(item.substring(3))) { srcs[item.replace('at-', '')] = image.dataset[item]; } } // sort resolutions ascending var keys = object.keys(srcs).sort(function(a, b) { return parseint(a, 10) < parseint(b, 10) ? -1 : 1; }); // get real screen resolution var width = window.innerwidth * window.devicepixelratio; // find the first image bigger than or equal to the current width var i = 0; while (i < keys.length - 1 && keys[i] < width) { i++; } result = srcs[keys[i]] || result; } return result; } // return false at the right end of the gallery function shownextimage() { var returnvalue; // check if next image exists if (currentindex <= imageselements.length - 2) { currentindex++; updateoffset(); preloadnext(currentindex); returnvalue = true; } else if (options.animation) { slider.classname = 'bounce-from-right'; settimeout(function() { slider.classname = ''; }, 400); returnvalue = false; } if (options.onchange) { options.onchange(currentindex, imageselements.length); } return returnvalue; } // return false at the left end of the gallery function showpreviousimage() { var returnvalue; // check if previous image exists if (currentindex >= 1) { currentindex--; updateoffset(); preloadprev(currentindex); returnvalue = true; } else if (options.animation) { slider.classname = 'bounce-from-left'; settimeout(function() { slider.classname = ''; }, 400); returnvalue = false; } if (options.onchange) { options.onchange(currentindex, imageselements.length); } return returnvalue; } function updateoffset() { var offset = -currentindex * 100 + '%'; if (options.animation === 'fadein') { slider.style.opacity = 0; settimeout(function() { /* jshint -w030 */ supports.transforms ? slider.style.transform = slider.style.webkittransform = 'translate3d(' + offset + ',0,0)' : slider.style.left = offset; slider.style.opacity = 1; }, 400); } else { /* jshint -w030 */ supports.transforms ? slider.style.transform = slider.style.webkittransform = 'translate3d(' + offset + ',0,0)' : slider.style.left = offset; } } // css 3d transforms test function testtransformssupport() { var div = create('div'); return typeof div.style.perspective !== 'undefined' || typeof div.style.webkitperspective !== 'undefined'; } // inline svg test function testsvgsupport() { var div = create('div'); div.innerhtml = ''; return (div.firstchild && div.firstchild.namespaceuri) === 'http://www.w3.org/2000/svg'; } function preloadnext(index) { if (index - currentindex >= options.preload) { return; } loadimage(index + 1, function() { preloadnext(index + 1); }); } function preloadprev(index) { if (currentindex - index >= options.preload) { return; } loadimage(index - 1, function() { preloadprev(index - 1); }); } function bind(element, event, callback, usecapture) { if (element.addeventlistener) { element.addeventlistener(event, callback, usecapture); } else { // ie8 fallback element.attachevent('on' + event, function(event) { // `event` and `event.target` are not provided in ie8 event = event || window.event; event.target = event.target || event.srcelement; callback(event); }); } } function unbind(element, event, callback, usecapture) { if (element.removeeventlistener) { element.removeeventlistener(event, callback, usecapture); } else { // ie8 fallback element.detachevent('on' + event, callback); } } function getbyid(id) { return document.getelementbyid(id); } function create(element) { return document.createelement(element); } function destroyplugin() { unbindevents(); clearcacheddata(); unbind(document, 'keydown', keydownhandler); document.getelementsbytagname('body')[0].removechild(document.getelementbyid('baguettebox-overlay')); data = {}; currentgallery = []; currentindex = 0; } return { run: run, destroy: destroyplugin, shownext: shownextimage, showprevious: showpreviousimage }; }));