Allgemeine Anwendung

Die allgemeine Anwendung zielt auf einfache Webseiten ab, die ggf. auch ohne Einsatz eines CMS verwaltet werden. Beispiel ist die hier beschriebene ZIP-Bildergalerie.

Die ursprüngliche auf fancybox basierende Version ist weiterhin im Repository auf GitHub vorhanden, wird aber nicht mehr weiter verfolgt.

Die aktuelle Version verfolgt den Ansatz mit Swiper, einer unter MIT lizenzierter Javascript-Bibliothek. Hierzu ist die HTML-Seite swiper.html enthalten, die die ZIP-Bilder-Galerien gallery-1.zipgallery-2.zip und fat-gallery.zip verwendet:

Datei: swiper-excerpt.html (74 Zeilen)
<!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%&ensp;}{<b class="dim">&copy;%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%&ensp;}{<b class='dim'>&copy;%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;'>&copy;%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>

Dabei ist zu beachten:

  • Der erste Link "Test gallery one" verwendet die Standard-Einstellungen für die Beschriftung und Thumbnails aus der JavaScript-Datei zipGallery-swiper.js:
      dfltCaption = '{%localised%&ensp;}{<b class="dim">&copy;%copyright%} - %filename%</b>';
      dfltThumbSize = [50, 50];

     
  • Der zweite Link hat sowohl die Beschriftung als auch die Größe der Thumbnails individuell definiert:
      data-thumbsize="75x50"
      data-caption="{%title%&ensp;}{<b class='dim'>&copy;%copyright%</b>}"

     
  • Der dritte Link "fat gallery" verwendet ein Farb-Attribut um Teile der Beschriftung einzufärben:
      data-caption="%localised% <b style='color: #ff8800;'>&copy;%copyright%</b>"

Für die Thumbnail-Größe ist die Angabe einfach: "Breite x Höhe".

Was ist für die Bildunterschrift möglich?

Dazu schauen wir uns die Datei zipGallery-swiper.js an:

Datei: zipGallery-swiper.js (337 Zeilen)
/** * 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);

Hier sind in [40 - 52] die Schlagworte aufgeführt, die in dieser Version der JavaScript-Datei ausgewertet werden:

         * 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

Die Schlagworte filename, index und localised sind Pseudo-Schlagworte, sie sind in der IPTC-Beschreibung nicht vorhanden:

  • filename   ist der Name der Datei
  • index      ist der laufende Index (1 ... N) der Datei im ZIP-Archiv
  • localised  ist der an die Sprache der Website angepasste Titel des Bildes.

Für das Pseudo-Schlagwort localised wird im IPTC-Tag caption (Bildunterschrift) nachgesehen, ob dort ein Eintrag der Form

lc { loc_message } [lc { loc_message }]*

mit

  • lc           language code (Länder-Code) nach RFC5646
  • loc_message  localised message (lokalisierter Text)

vorhanden ist. Ist die Sprache der Website z.B. auf de eingestellt, wird der Eintrag

de { Das ist schön! } en { This is beautiful! } no { Det er pent! }

so ausgewertet, dass der Text "Das ist schön!" referenziert wird. Ist kein solcher Eintrag oder kein zur Sprache der Website passender Eintrag vorhanden, wird der Wert aus dem IPTC-Tag title referenziert.

Die anderen Schlagworte sind je nach Bildverwaltungsprogramm unterschiedlich belegt, mache sind auch nicht belegt. Mache Dich hierzu schlau beim Bildverwaltungsprogramm Deiner Wahl!

Die Bildunterschrift (Attribut data-caption des <a>-Tags ) besteht aus einer beliebigen HTML-Zeile (die auch <br>-Tags enthalten kann). Schlagworte werden mittels %schlagwort% eingebettet, also z.B. %filename% für den Dateinamen.

Da in der Bildunterschrift neben den Schlagworten auch Füllzeichen enthalten sein können, die aber nicht angezeigt werden sollen, wenn das entsprechende Schlagwort nicht besetzt (d.h. leer) ist, können Teile der Bildunterschrift mittels geschweiften Klammern ({}) gruppiert werden. Das ist bei der Standard-Bildunterschrift der Fall:

{%localised%&ensp;}

Hier wird der Bildtitel mit einem N-Leerzeichen (&ensp;) gruppiert, so dass das Leerzeichen dann angezeigt wird, wenn der Bildtitel besetzt ist. Das ist im Beispiel "Test gallery one" beim fast allen Bild der Fall. Beim fünften Bild wird das N-Leerzeichen nicht angezeigt, da der Bildtitel unbesetzt ist. 

Beschreibung des Codes

  • Die function isMobile() [11 - 14] prüft, ob ein mobiler Browser läuft.
  • Die function setFullScreen() [18 - 34] erlaubt das Auf- bzw. Abschalten des Full-Screen-Modes bei mobilen Browsern.
  • In [35 - 38] wird die Standardgröße für die Thumbnails definiert.
  • Der Code zur Auflösung von Schlagworten und der Gruppierungen befindet sich in der function get_title()[61- 85].
  • Die function alignGeometry() [89 - 141] erledigt die wichtige Aufgabe, die Elemente der Bildergalerie zu positionieren, da die Swiper-Bibliothek in einem modalen Fenster läuft. Die function alignGeometry() wird aufgerufen
    • nach dem Öffnen der Bildergalerie [249],
    • nach dem Verbergen oder dem Anzeigen der Thumbnails [284, 287] und
    • nach jeder Veränderung der Browser-Größe (window.resize event).
  • Die function setDownloadLink() [145 - 150] setzt für das aktuell angezeigte Galerie-Bild den Download-Link, sofern ein mobiler Browser läuft.

Der Rest des Codes [154ff] wird für jeden Link auf eine Bildergalerie (<a class="gallery">-Tag) ausgeführt:

  • Am Ziel (Attribut href) des Links wird die Endung .zip entfernt [158 - 161].
  • In [165 - 172] werden die Bildunterschrift und die Thumbnail-Größe sowie der (mobile) Download-Link initialisiert.
  • Es wird ein Clickhandler etabliert [175ff].

Clickhandler

In [176] wird die Ausführung weiterer Ereignisse verhindert, dann wird der Fullscreen-Mode etabliert [177] (bei mobilem Browser). Nachdem der Cursor auf "warten" (progress) gestellt ist [181], wird ein asynchroner AJAX-Request zum Auslesen der ZIP-Archiv-Informationen abgeschickt [182 - 185]. Die URL dazu ist

basis-url-des-zip-archives.zip/ReWriteDummy?info=true

Der Dateiname der Anforderung (ReWriteDummy) ist dabei unwichtig, durch den Parameter info=true wird die ZIP-Archiv-Information angefordert.

Bei erfolgreicher Übertragung [189ff] wird der Cursor auf "normal" gesetzt [190], und die Information ausgewertet. Ist die Größe der Information 0, enthält das verlinkte ZIP-Archiv keine gültigen Bilder (nur JPEG derzeit möglich!) [188, 189].

[194 - 208] erstellt das modale Fenster und die Swiper-Grundgerüste für die Bildergalerie mitsamt Thumbnails.

Nach Übernahme der Thumbnailgröße für die Darstellung [209] sowie Feststellung der Sprache [210 - 214] werden in einer for-loop alle Einträge der ZIP-Archiv-Informationen behandelt [215 - 245]:

  • Der Wert für das Pseudo-Schlagwort index wird generiert [217].
  • Das Pseudo-Schlagwort localised wird aus den IPTC-Schlagworten title und caption gebildet [218 - 231].
  • Beim Erstellen des HTML-Codes für ein Bild der Bildergalerie [232 - 241] wird die Geometrie des Bildes in zwei data-Attríbuten abgelegt (data-height und data-width). Diese Information wird in der function alignGeometry() verwendet.
  • HTML-Code für ein Thumbnail-Bild wird in [242 - 244] erstellt. Das Thumbnail-Bild selbst wird aus der JSON-Info entnommen.

Nachdem der HTML-Code für die Swiper-Bildergalerie erstellt und in das DOM eingebettet wurde, wird die Bildgeometrie am erstellten HTML-Code justiert [246]. Die Optionen für die Bildergalerie (swOpts) beinhaltet einerseits ausgewählte Konstanten (siehe Swiper-Dokumentation) als auch Code zur Erkennung von Auf/Abwärtsbewegungen [265 - 287]. Anhand dieser Bewegungen werden die Thumbnails aus- bzw. eingeblendet.

Die Swiper-Initialisierungen finden  in [294] und [295 - 300] statt. Die beiden BIldergalerien (obere und Thumbnails) werden in [301, 302] synchronisiert.

Zum Schluss wird ein Click- und ein ESC-Handler zum Schließen der Bildergalerie etabliert. Das Ende bildet der windows.resize-Handler.

 

Anwendungen

Wie funktioniert der Kern der ZIP Bilder-Galerie? Die Antwort liegt hier.
Die allgemeine auf Swiper basierende Anwendung zielt auf einfache Webseiten ab, die ggf. auch ohne…
Neben der allgemeinen Lösung der ZIP-Bildergalerie ist eine Version für das CMS concrete5…