The general application is suited for basic web sites possibly not managed by a CMS. Example is the here explained version.
Initially the ZIP image gallery was fancybox based. The fancybox based version is still available at the Repository at GitHub but no longer enhanced or supported.
The current version utilises Swiper licensed under MIT. The example HTML page is swiper.html utilising ZIP image galleries gallery-1.zip
, gallery-2.zip
and fat-gallery.zip
:
<!DOCTYPE HTML>
<html lang="en">
<head>
<!-- Add swiper main JS and CSS files -->
<script type="text/javascript" src="swiper.3.4.2/js/swiper.jquery.js"></script>
<link rel="stylesheet" type="text/css" href="swiper.3.4.2/css/swiper.min.css" media="screen" />
<!-- Add zipGallery JS and CSS files -->
<script type="text/javascript" src="js/zipGallery-swiper.js"></script>
<link rel="stylesheet" type="text/css" media="screen" href="css/zipGallery-swiper.css" />
<title>ZIP gallery for swiper</title>
</head>
<body>
<div id="content">
<div id="sitetitle">
<h1>ZIP gallery for swiper</h1>
</div>
<div class="section">
<p>
ZipGallery is based on PHP code to extract a ZIP archive containing images of a gallery
in conjunction with an AJAX driven front end. The image gallery itself is based on the
<a href="http://idangero.us/swiper/" target="_blank">swiper</a> jQuery plugin.
</p>
</div>
<div class="section">
<p>
Click at one of the gallery links to see how things work!
</p>
<ul>
<li><a class="gallery" href="http://tdsystem.eu/external/zipgallery/galleries/gallery-1.zip">Test gallery one</a>
try "gallery one" localised <span class="toggle">en</span></li>
<li><a class="gallery" href="http://tdsystem.eu/external/zipgallery/galleries/gallery-2.zip"
data-thumbsize="75x50"
data-caption="{%title% }{<b class='dim'>©%copyright%</b>}">2nd test gallery</a></li>
<li><a class="gallery" href="http://tdsystem.eu/external/zipgallery/galleries/fat-gallery.zip"
data-caption="%localised% <b style='color: #ff8800;'>©%copyright%</b>">fat gallery</a></li>
</ul>
<p>Please note:</p>
<ul>
<li>The first link "Test gallery one" utilises standard caption and thumbnail size
defined in JavaScript file <code><a href="js/zipGallery-swiper.js" target="_blank">zipGallery-swiper.js</a></code>:<br/>
<code>
dfltThumbSize = [50, 50];<br/>
dfltCaption = '{%localised% }{<b class="dim">©%copyright%} - %filename%</b>';
</code>
</li>
<li>The second link has custom defined caption and thumbnail size:<br/>
<code>
data-thumbsize="75x50"<br/>
data-caption="{%title% }{<b class='dim'>©%copyright%</b>}"
</code>
</li>
<li>The "fat gallery" link sets a colour attribute to parts of the caption:<br/>
<code>
data-caption="%localised% <b style='color: #ff8800;'>©%copyright%</b>"
</code>
</li>
</ul>
<br />
<hr />
</div>
<div class="section">
<a class="contact" href="mailto:info(at)tdsystem.eu">© 2016 TDSystem Beratung & Training</a>
</div>
</div>
</body>
</html>
- The first link "Test gallery one" is based on the default settings for caption and thumbnail size from JavaScript file
zipGallery-swiper.js
:
dfltCaption = '{%localised% }{<b class="dim">©%copyright%} - %filename%</b>';
dfltThumbSize = [50, 50];
- The second link is set up with distinct caption and thumbnail size:
data-thumbsize="75x50"
data-caption="{%title% }{<b class='dim'>©%copyright%</b>}"
- The third link "fat gallery" sets a colour attribute for some parts of the caption:
data-caption="%localised% <b style='color: #ff8800;'>©%copyright%</b>"
Thumbnail size is set up by "width x height". Quiet easy.
What's about the caption?
Let's have a look at file zipGallery-swiper.js
:
/**
* swiper controller jQuery library for ZIP Image Gallery
*
* Copyright 2017 - TDSystem Beratung & Training, Thomas Dausner
*/
(function($) {
$(document).ready(function() {
/*
* test for mobile browser
*/
var isMobile = function() {
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
return /android|ipad|iphone|ipod|windows phone/i.test(userAgent);
};
/*
* browser independent full screen toggle
*/
var setFullScreen = function(on) {
if (isMobile()) {
var doc = window.document;
var docEl = doc.documentElement;
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
if (on) {
if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement && requestFullScree)
requestFullScreen.call(docEl);
}
else if (cancelFullScreen) {
cancelFullScreen.call(doc);
}
}
};
var thumbNails = {
width: 50,
height: 50
};
/*
* IPTC keys for image caption
*
* Pseudo (non IPTC) keywords:
* filename index localised
*
* IPTC keywords:
* authorByline authorTitle caption
* captionWriter category cdate
* city copyright country
* headline OTR photoSource
* source specialInstructions state
* subcategories subject title
* urgency
*
* Order and presence are defined in the 'dfltCaption' string.
*/
var dfltCaption = '{%localised% }{<b class="dim">©%copyright%} - %index% - %filename%</b>';
var dfltThumbSize = [ thumbNails.width, thumbNails.height ];
/*
* make caption from template and iptc tags
*/
var get_title = function (iptc, captionTpl) {
var caption = '';
var groups = captionTpl.split(/[{}]/);
for (var grp = 0; grp < groups.length; grp++) {
var capGrp = groups[grp];
if (capGrp !== '') {
var keys = capGrp.match(/%\w+%/g);
var keyFound = false;
for (var j = 0; j < keys.length; j++) {
var tag = keys[j].replace(/%/g, '');
var re = new RegExp(keys[j]);
if (iptc[tag] !== undefined) {
capGrp = capGrp.replace(re, iptc[tag]);
keyFound = true;
} else {
capGrp = capGrp.replace(re, '');
}
}
if (keyFound)
caption += capGrp;
}
}
return caption;
};
/*
* align image and text geometry
*/
var thumbsBorders = 2;
var thumbsHeight = thumbNails.height + thumbsBorders;
var vp = {};
var alignGeometry = function(thumbsOff) {
if (thumbsOff === true) {
thumbsHeight = 0;
$('a.download').addClass('no-thumbs');
}
else if (thumbsOff === false) {
thumbsHeight = thumbNails.height + thumbsBorders;
$('a.download').removeClass('no-thumbs');
}
vp = {
fullHeight: document.documentElement.clientHeight,
width: document.documentElement.clientWidth
};
vp.height = vp.fullHeight - thumbsHeight;
$('div.gallery-top div.swiper-slide').each(function() {
var $sl = $(this);
var img = {
orgHeight: $sl.data('height'),
orgWidth: $sl.data('width'),
height: 0,
width: 0
};
var ratio = img.orgWidth / img.orgHeight;
var left, top;
if (vp.height * ratio < vp.width) {
img.width = vp.height * ratio;
img.height = vp.height;
left = (vp.width - img.width) / 2;
top = 0;
} else {
img.width = vp.width;
img.height = vp.width / ratio;
left = 0;
top = (vp.height - img.height) / 2;
}
$sl.css({
top: top + 'px'
});
$('img', $sl).css({
height: img.height + 'px',
width: img.width + 'px'
});
$('div.text', $sl).css({
left: left + 'px'
});
$('div.gallery-top').css({
minHeight: vp.height
});
});
};
/*
* set download link
*/
var setDownloadLink = function(slider) {
var $img = $('img', slider.slides[slider.realIndex]);
var url = $img.data('src') === undefined ? $img.attr('src') : $img.data('src');
var file = url.replace(/.*\//, '');
$('a.download').attr('href', url).attr('download', file);
};
/*
* process all links identifying ZIP gallery files
*/
$('a.gallery').each(function() {
//
// remove file extension '.zip'
//
var $a = $(this);
var zipUrl = $a.attr('href');
var galleryName = zipUrl.replace('.zip', '');
$a.attr('href', galleryName);
//
// initilaisation of caption and thumbs
//
var captionTpl = $a.data('caption') === undefined ? dfltCaption : $a.data('caption');
var tns = dfltThumbSize;
if ($a.data('thumbsize') !== undefined) {
tns = $a.data('thumbsize').split('x');
}
thumbNails.width = parseInt(tns[0]);
thumbNails.height = parseInt(tns[1]);
//
// set click handler
//
$a.click(function(e) {
e.preventDefault();
setFullScreen(true);
//
// get info
//
$('body, a').css('cursor', 'progress');
$.ajax( {
type: 'GET',
url: zipUrl + '/ReWriteDummy?info=true&tnw=' + thumbNails.width + '&tnh=' + thumbNails.height,
dataType: 'json',
success: function(info) {
$('body, a').css('cursor', '');
if (info.length === 0) {
alert('Die Gallerie <' + galleryName + '> enthält keine gültigen Bilder.');
} else {
//
// prepare swiper gallery
//
$('body').append('<div id="zipGallery">'
+ '<div class="close"></div>'
+ '<a class="download swiper-button-next swiper-button-white"> </a>'
+ '<div class="swiper-container gallery-top">'
+ '<div class="swiper-wrapper">'
+ '</div>'
+ '<div class="swiper-button-next swiper-button-white"></div>'
+ '<div class="swiper-button-prev swiper-button-white"></div>'
+ '</div>'
+ '<div class="swiper-container gallery-thumbs">'
+ '<div class="swiper-wrapper">'
+ '</div>'
+ '</div>'
+ '</div>'
);
var thumbStyle = 'width: ' + thumbNails.width + 'px; height:' + thumbNails.height + 'px;';
var lang = $('html').attr('lang');
if (lang === undefined) {
lang = lang || window.navigator.language || window.navigator.browserLanguage || window.navigator.userLanguage;
lang = lang.substr(0, 2);
}
for (var idx = 0; idx < info.length; idx++) {
var comp = info[idx].exif.COMPUTED;
info[idx].iptc.index = idx + 1;
var localised = info[idx].iptc.title;
var caption = info[idx].iptc.caption;
if (caption !== undefined) {
var loc = caption.toString().split(/[{}]/);
if (loc.length > 0 && loc.length % 2 == 1) {
for (var lidx = 0; lidx < loc.length; lidx += 2) {
if (loc[lidx].trim() == lang) {
localised = loc[lidx + 1].trim();
break;
}
}
}
}
info[idx].iptc.localised = localised;
$('.gallery-top .swiper-wrapper')
.append('<div class="swiper-slide swiper-zoom-container" '
+ 'data-height="' + comp.Height + '" data-width="' + comp.Width + '">'
+ '<img data-src="' + zipUrl + '/' + info[idx].name + '" class="swiper-lazy">'
+ '<div class="swiper-lazy-preloader"></div>'
+ '<div class="text">'
+ '<p>' + get_title(info[idx].iptc, captionTpl) + '</p>'
+ '</div>'
+ '</div>'
);
$('.gallery-thumbs .swiper-wrapper')
.append('<div class="swiper-slide" style="' + thumbStyle
+ ' background-image:url(data:image/jpg;base64,' + info[idx].thumbnail + ')"></div>');
}
alignGeometry(false);
/*
* open image gallery
*/
var swOpts = {
keyboardControl: true,
mousewheelControl: true,
// not with thumbnails loop: true,
nextButton: '.swiper-button-next',
prevButton: '.swiper-button-prev',
zoom: true,
zoomMax: 5,
preloadImages: false,
lazyLoading: true,
lazyLoadingInPrevNext: true,
speed: 400,
spaceBetween: 10,
onInit: setDownloadLink,
onSlideChangeEnd: setDownloadLink,
onTouchStart: function(swiper, event) {
if (event.x === undefined)
event = event.changedTouches[0];
swOpts.touch.x = event.screenX;
swOpts.touch.y = event.screenY;
},
onTouchEnd: function(swiper, event) {
if (event.x === undefined)
event = event.changedTouches[0];
var dx = swOpts.touch.x - event.screenX;
var dy = swOpts.touch.y - event.screenY;
if (swOpts.touch.y > vp.height / 2 && Math.abs(dy) / vp.height >= 0.2) {
// start gesture in lower half of viewport, min 20% offset
var ratio = dy / Math.abs(dx);
if (ratio < -2.0) {
// gesture down
alignGeometry(true);
} else if (ratio > 2.0) {
// gesture up
alignGeometry(false);
}
}
},
touch: { x: 0, y: 0 }
};
if (!isMobile()) {
swOpts.lazyLoadingInPrevNextAmount = 10;
swOpts.slidesPerView = 'auto';
}
var galleryTop = new Swiper('.gallery-top', swOpts);
var galleryThumbs = new Swiper('.gallery-thumbs', {
centeredSlides: true,
slidesPerView: 'auto',
touchRatio: 0.8,
slideToClickedSlide: true
});
galleryTop.params.control = galleryThumbs;
galleryThumbs.params.control = galleryTop;
/*
* gallery close click/escape handler
*/
$('#zipGallery div.close').click(function() {
$('#zipGallery').remove();
setFullScreen(false);
});
$(document).keydown(function(evt) {
evt = evt || window.event;
var isEscape = false;
if ("key" in evt) {
isEscape = (evt.key == "Escape" || evt.key == "Esc");
} else {
isEscape = (evt.keyCode == 27);
}
if (isEscape) {
$('#zipGallery').remove();
setFullScreen(false);
}
});
/*
* window resize handler
*/
$(window).resize(alignGeometry);
}
},
error: function( xhr, statusText, err ) {
$('body, a').css('cursor', '');
alert('Fehler beim Laden der Info aus Datei <' + zipUrl + ">\n" + statusText);
}
});
});
});
});
}) (window.jQuery);
Lines [40 - 52] do list the keywords being evaluated in the present JavaScript file version:
* IPTC keys for image caption
*
* Pseudo (non IPTC) keywords:
* filename index localised
*
* IPTC keywords:
* authorByline authorTitle caption
* captionWriter category cdate
* city copyright country
* headline OTR photoSource
* source specialInstructions state
* subcategories subject title
* urgency
Keywords filename, index and localised are virtual keywords not defined in the IPTC specification:
- filename is the name of the file
- index is index (1 ... N) of file in ZIP archive
- localised is a web site local language controlled image title
The value for virtual keyword localised is constructed from IPTC field caption, if present. The field caption should have the format
lc { loc_message } [lc { loc_message }]*
having
- lc language code as of RFC5646
- loc_message localised message
With web site language set to for example en a caption entry
de { Das ist schön! } en { This is beautiful! } no { Det er pent! }
is evaluated to image title "This is beautiful!". In lack of such an entry in caption IPTC field or with no match to lc value the content of IPTC field title is utilised.
Other fields (keywords) are filled with data or not, depending on the image management program of your choice. For the mapping of IPTC keywords to data entry fields have a look at the documentation of your fancied image management program!
An image caption (attribute data-caption
of <a>
tag
) is a single HTML coded line. It may contain <br> tags. Keywords are embedded following format %keyword%. Thus %filename% represents the image file name.
Captions may contain fillers. Those fillers should not display if a keyword isn't set (is empty). To suppress the fillers in that case parts of the caption can be grouped by curly braces ({}). Have a look at the default caption:
{%localised% }
The localised image title is grouped with an n-space ( 
). The n-space filler is displayed if the localised image title isn't empty. In case of "Test gallery one" most images do have image titles set. The image with index 5 has no title set and the n-space hence is suppressed.
Code comments
- Function isMobile() [11 - 14] checks presence of a mobile browser.
- Function setFullScreen() [18 - 34] enables or disables full screen mode for mobile browsers.
- [35 - 38] defines default thumbnail size.
- Code for resolution of IPTC keywords is contained in function get_title()[61- 85].
- Function alignGeometry() [89 - 141] is the core piece for setting positions and width/height of images, as the Swiper plugin is executed in a modal window. This function is called
- next to initialisation of an image gallery [249],
- after show or hide of thumbnails [284, 287] and
- resulting of browser window resizing (window.resize event).
- Function setDownloadLink() [145 - 150] sets current image download link (on mobile browsers).
Other code [154 et seq.] is executed for each ZIP image gallery link (<a class="gallery"> tag):
- At link target (attribute href) the suffix .zip is removed [158 - 161].
- Lines [165 - 172] do set image title and thumbnail sizes. A mobile download link is initialised.
- A click handler is established [175 et seq.].
Click handler
Line [176] terminates execution of other click event handlers. Full s screen mode is established [177] for mobile browsers. Next to setting the cursor to "wait" (progress) [181] an asynchronous AJAX request is issued for gaining the ZIP archive file information [182- 185]. Request URL is
base-url-of-zip-archive.zip/ReWriteDummy?info=true
The request file name (ReWriteDummy
) is just a dummy. The parameter info=true
demands ZIP archive information.
On AJAX success [186 et seq.] the cursor is set to "normal" [187] and the retrieved information processed. On information size 0 the linked ZIP archive doesn't contain any valid image files. As of now the ZIP archive may solely contain JPEG images [188, 189].
Lines [194 - 208] do set up the modal window and Swiper frames for the image gallery including thumbnails. Thumbnail size for CSS style attributes [209] is set. Localisation is detected [210 - 214].
In a for
-loop all entries are processed [215 - 245]:
- Index value for virtual keyword index is set [217].
- Field value for virtual keyword localised is computed from IPTC fields title and caption [218 - 231].
- While creating HTML code for an image [232 - 241] the image geometry data is set as two data attributes (data-height and data-width), information demanded in function alignGeometry().
- HTMl code for a thumbnail is set in [242 - 244]. Thumbnail image data is taken from JSON.
After creation of HTML code for the Swiper based image gallery and embedding into DOM the image and other position and sizes are adjusted [246]. Swiper options set in var swOpts contain selected constants (see Swiper documentation) as well as code for recognition of up or down cursor moves [265 - 287]. These cursor moves are used to control visibility of thumbnails (move down := hide, move up := show).
Swiper initialisations take place in [294] and [295 - 300]. Both image galleries (upper and thumbnail galleries) are synchronised in [301, 302].
Eventually a click handler and an ESC handler for closing of the ZIP image gallery are established accompanied by a windows.resize handler.