/*
* jQuery Popeye 2.1 - http://dev.herr-schuessler.de/jquery/popeye/
*
* converts a HTML image list in image gallery with inline enlargement
*
* Copyright (C) 2008 - 2011 Christoph Schuessler (schreib@herr-schuessler.de)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
*/
(function ($) {
////////////////////////////////////////////////////////////////////////////
//
// $.fn.popeye
// popeye definition
//
////////////////////////////////////////////////////////////////////////////
$.fn.popeye = function (options) {
'use strict';
// build main options before element iteration
//----------------------------------------------------------------------
var opts = $.extend({}, $.fn.popeye.defaults, options);
////////////////////////////////////////////////////////////////////////////////
//
// firebug console output
// @param text String the debug message
// @param type String the message type [info | warn] (optional)
//
////////////////////////////////////////////////////////////////////////////////
function debug(text, type) {
if (window.console && window.console.log && opts.debug) {
if (type === 'info' && window.console.info) {
window.console.info(text);
}
else if (type === 'warn' && window.console.warn) {
window.console.warn(text);
}
else {
window.console.log(text);
}
}
}
// let's go!
//----------------------------------------------------------------------
return this.each(function () {
// first thing to do: make ppy html visible
$(this).addClass('ppy-active');
// cache object
var $self = $(this),
// images
img = $self.find('.ppy-imglist > li > a > img'),
a = $self.find('.ppy-imglist > li > a'),
tot = img.length,
// single image mode
singleImageMode = (tot === 1),
// flag for moueover check
ismouseover = false,
// start in compact mode
enlarged = false,
// counter vars
cur = 0, // array index of currently displayed image
// extra classes
eclass = 'ppy-expanded', //class to be applied to enlarged popeye-box
lclass = 'ppy-loading', //class to be applied to stage while loading image
sclass = 'ppy-single-image', //class to be applied to popeye-box if there's only one image to display
// html nodes
ppyPlaceholder = $('<div class="ppy-placeholder"></div>'),
ppyStageWrap = $('<div class="ppy-stagewrap"></div>'),
ppyCaptionWrap = $('<div class="ppy-captionwrap"></div>'),
ppyOuter = $self.find('.ppy-outer'),
ppyStage = $self.find('.ppy-stage'),
ppyNav = $self.find('.ppy-nav'),
ppyPrev = $self.find('.ppy-prev'),
ppyNext = $self.find('.ppy-next'),
ppySwitchEnlarge = $self.find('.ppy-switch-enlarge'),
ppySwitchCompact = $self.find('.ppy-switch-compact').addClass('ppy-hidden'),
ppyPlay = $self.find('.ppy-play'),
ppyPause = $self.find('.ppy-pause').addClass('ppy-hidden'),
ppyCaption = $self.find('.ppy-caption'),
ppyText = $self.find('.ppy-text'),
ppyCounter = $self.find('.ppy-counter'),
ppyCurrent = $self.find('.ppy-current'),
ppyTotal = $self.find('.ppy-total'),
// css objects
cssSelf = {
position: 'absolute',
width: 'auto',
height: 'auto',
margin: 0,
top: 0,
left: (opts.direction === 'right') ? 0 : 'auto',
right: (opts.direction === 'left') ? 0 : 'auto'
},
cssStage = {
height: ppyStage.height(),
width: ppyStage.width()
},
cssCaption = {
height: ppyCaption.height()
},
cssPlaceholder = {
'height': (opts.caption === 'hover' || false) ? ppyOuter.outerHeight() : $self.outerHeight(),
'width': (opts.caption === 'hover' || false) ? ppyOuter.outerWidth() : $self.outerWidth(),
'float': $self.css('float'),
'margin-top': $self.css('margin-top'),
'margin-right': $self.css('margin-right'),
'margin-bottom': $self.css('margin-bottom'),
'margin-left': $self.css('margin-left')
};
// make caption array from caption element or alt tag
var cap = [];
for (var i = 0; i < img.length; i++) {
var extcap = $self.find('.ppy-imglist li').eq(i).find('.ppy-extcaption');
cap[i] = extcap.length > 0 ? extcap.html() : img[i].alt;
}
// check for html errors
if (!ppyStage.length || !ppyNav.length || !ppyOuter.length) {
debug('$.fn.popeye: Incorrect HTML structure', 'warn');
}
// check for images
else if (tot === 0) {
debug('$.fn.popeye: No images found', 'warn');
}
// no errors, setup done!
//------------------------------------------------------------------
else {
singleImageMode ? debug('$.fn.popeye -> SingleImageMode started') : debug('$.fn.popeye -> ' + tot + ' thumbnails found.');
init();
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.showThumb
// show thumbnail
// @param i Int the index of the thumbnail to show (optional)
// @param transition Bool show transition between images (optional)
//
////////////////////////////////////////////////////////////////////
function showThumb(i, transition) {
// optional parameters
transition = transition || false;
i = i || cur;
// set selected thumb as background image of stage
var cssStageImage = {
backgroundImage: 'url(' + img[i].src + ')'
};
// bogus animation css for IE
var cssTemp = {
height: '+=0'
};
// if we are in enlarged mode, return to thumb mode
if (enlarged) {
hideCaption();
// fade image out and compact stage with transition
ppyStage.fadeTo((opts.duration / 2), 0).animate(cssStage, {
queue: false,
duration: opts.duration,
easing: opts.easing,
complete: function () {
enlarged = false;
debug('$.fn.showThumb: Entering COMPACT MODE', 'info');
// remove extra styling and reset z-index
$self.removeClass(eclass);
$self.css('z-index', '').parent().css('z-index', '');
// switch buttons
ppySwitchEnlarge.removeClass('ppy-hidden');
ppySwitchCompact.addClass('ppy-hidden');
// recursive function call
showThumb();
// fade the stage back in
$(this).fadeTo((opts.duration / 2), 1);
}
});
}
else {
// if we navigate from one image to the next, fade out the stage
if (transition) {
// fade out image so that background shines through
// background can contain loading gfx
ppyStageWrap.addClass(lclass);
ppyStage.fadeTo((opts.duration / 2), 0);
// once thumb has loadded...
var thumbPreloader = new Image();
thumbPreloader.onload = function () {
debug('$.fn.popeye.showThumb: Thumbnail ' + i + ' loaded', 'info');
// remove loading indicator
ppyStageWrap.removeClass(lclass);
// add all upcoming animations to the queue so that
// they won't start when the preolader has loaded but when the fadeOut has finished
ppyStage.animate(cssTemp, 1, 'linear', function () {
// set the new image
ppyStage.css(cssStageImage);
// fade the stage back in
$(this).fadeTo((opts.duration / 2), 1);
// update counter and caption
if (opts.caption === 'hover' && ismouseover) {
showCaption(cap[i]);
}
else if (opts.caption === 'permanent') {
updateCaption(cap[i]);
}
updateCounter();
});
// fix IE animated gif bug
thumbPreloader.onload = function () {
};
};
// preload thumb
thumbPreloader.src = img[i].src;
}
// or just drag the image to the stage
else {
ppyStage.css(cssStageImage);
updateCounter();
//if(ismouseover) {
showCaption(cap[i], true);
//}
}
// preload big image for instant availability
var preloader = new Image();
preloader.onload = function () {
debug('$.fn.popeye.showThumb: Image ' + i + ' loaded', 'info');
preloader.onload = function () {
};
};
preloader.src = a[i].href;
}
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.showImage
// show large image
// @param i Int the index of the image to show (optional)
//
////////////////////////////////////////////////////////////////////
function showImage(i) {
// optional parameter i
i = i || cur;
// fade out image so that background shines through
// background can contain loading gfx
ppyStageWrap.addClass(lclass);
ppyStage.fadeTo((opts.duration / 2), 0);
// if there are multiple popeyes opened at the same time,
// make sure the current one gets a higher z-index
var allPpy = $('.' + eclass);
allPpy.css('z-index', opts.zindex - 1);
//both ppy and placeholder (for IE7) need high z-index
$self.css('z-index', opts.zindex).parent().css('z-index', opts.zindex);
// once image has loadded...
var preloader = new Image();
preloader.onload = function () {
// remove loading class
ppyStageWrap.removeClass(lclass);
// set css
var cssStageTo = {
width: preloader.width,
height: preloader.height
};
var cssStageIm = {
backgroundImage: 'url(' + a[i].href + ')',
backgroundPosition: 'left top'
};
hideCaption();
// show transitional animation
ppyStage.animate(cssStageTo, {
queue: false,
duration: opts.duration,
easing: opts.easing,
complete: function () {
if (opts.navigation === 'hover' && ismouseover) {
showNav();
}
enlarged = true;
debug('$.fn.popeye.showImage: Entering ENLARGED MODE', 'info');
// add extra class, expanded box can be styled accordingly
$self.addClass(eclass);
// switch buttons
ppySwitchCompact.removeClass('ppy-hidden');
ppySwitchEnlarge.addClass('ppy-hidden');
updateCounter();
// set new bg image and fade it in
$(this).css(cssStageIm).fadeTo((opts.duration / 2), 1);
// show caption
showCaption(cap[i]);
preloadNeighbours();
}
});
};
// preload image
preloader.src = a[i].href;
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.updateCounter
// update image counter
// @param i Int the index of the image (optional)
//
////////////////////////////////////////////////////////////////////
function updateCounter(i) {
// optional parameter
i = i || cur;
ppyTotal.text(tot); // total images
ppyCurrent.text(i + 1); // current image number
debug('$.fn.popeye.updateCounter: Displaying image ' + (i + 1) + ' of ' + tot);
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.preloadNeighbours
// preload next and previos image
// @param i Int the index of the current image (optional)
//
////////////////////////////////////////////////////////////////////
function preloadNeighbours(i) {
// optional parameter
i = i || cur;
var preloaderNext = new Image();
var preloaderPrev = new Image();
var neighbour = i;
// next image
if (neighbour < ( tot - 1)) {
neighbour++;
} else {
neighbour = 0;
}
preloaderNext.src = a[neighbour].href;
// previous image
neighbour = i;
if (neighbour <= 0) {
neighbour = tot - 1;
} else {
neighbour--;
}
preloaderPrev.src = a[neighbour].href;
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.showNav
//
////////////////////////////////////////////////////////////////////
function showNav() {
ppyNav.stop().fadeTo(150, opts.opacity);
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.hideNav
//
////////////////////////////////////////////////////////////////////
function hideNav() {
ppyNav.stop().fadeTo(150, 0);
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.updateCaption
// @param caption String the caption string
//
////////////////////////////////////////////////////////////////////
function updateCaption(caption) {
if (opts.caption) {
// update text box
ppyText.html(caption);
}
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.showCaption
// @param caption String the caption string
// @param force Boolean force caption display even if caption string is empty
//
////////////////////////////////////////////////////////////////////
function showCaption(caption, force) {
// if caption string is not empty...
if (caption && opts.caption) {
updateCaption(caption);
debug('$.fn.popeye.showCaption -> ppyCaptionWrap.outerHeight(true): ' + ppyCaptionWrap.outerHeight(true));
// make caption box visible
var cssTempCaption = {
visibility: 'visible'
};
ppyCaption.css(cssTempCaption);
if (opts.caption === 'permanent' && !enlarged) {
// return to original caption height
ppyCaption.css(cssCaption);
}
else {
// or animate it to its childs height
ppyCaption.animate({'height': ppyCaptionWrap.outerHeight(true)}, {
queue: false,
duration: 90,
easing: opts.easing
});
}
}
// if there's no caption to show...
else if (!caption && !force) {
hideCaption();
}
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.hideCaption
//
////////////////////////////////////////////////////////////////////
function hideCaption() {
// css to hide caption but allow its inner text box to expand to content height
var cssTempCaption = {
visibility: 'hidden',
overflow: 'hidden'
};
// slide up caption box and hide it when done
ppyCaption.animate({'height': '0px'}, {
queue: false,
duration: 90,
easing: opts.easing,
complete: function () {
ppyCaption.css(cssTempCaption);
}
});
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.previous
// show previous image
//
////////////////////////////////////////////////////////////////////
function previous() {
if (cur <= 0) {
cur = tot - 1;
} else {
cur--;
}
if (enlarged) {
showImage(cur);
}
else {
showThumb(cur, true);
}
return cur;
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.next
// show next image
//
////////////////////////////////////////////////////////////////////
function next() {
if (cur < ( tot - 1)) {
cur++;
} else {
cur = 0;
}
if (enlarged) {
showImage(cur);
}
else {
showThumb(cur, true);
}
return cur;
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.init
// setup of popeye DOM and events
//
////////////////////////////////////////////////////////////////////
function init() {
// popeye dom setup
//--------------------------------------------------------------
// add css
ppyPlaceholder.css(cssPlaceholder);
$self.css(cssSelf);
// wrap popeye in placeholder
$self.wrap(ppyPlaceholder);
// wrap stage in container for extra styling (e.g. loading gfx)
ppyStageWrap = ppyStage.wrap(ppyStageWrap).parent();
// wrap caption contents in wrapper (can't use wrap() here...)
ppyCaptionWrap = ppyCaption.wrapInner(ppyCaptionWrap).children().eq(0);
// display first image
showThumb();
// add event handlers
//--------------------------------------------------------------
// hover behaviour for navigation
if (opts.navigation == 'hover') {
hideNav();
$self.hover(
function () {
showNav();
},
function () {
hideNav();
}
);
ppyNav.hover(
function () {
showNav();
},
function () {
hideNav();
}
);
}
if (!singleImageMode) {
// previous image button
ppyPrev.click(function (e) {
e.stopPropagation();
previous();
});
// next image button
ppyNext.click(function (e) {
e.stopPropagation();
next();
});
}
else {
$self.addClass(sclass);
ppyPrev.remove();
ppyNext.remove();
ppyPlay.remove();
ppyPause.remove();
ppyCounter.remove();
}
// hover behaviour for caption
if (opts.caption === 'hover') {
hideCaption();
$self.hover(
function () {
showCaption(cap[cur]);
},
function () {
hideCaption(true);
}
);
}
// enlarge image button
ppySwitchEnlarge.click(function (e) {
e.stopPropagation();
showImage();
return false;
});
// compact image button
ppySwitchCompact.click(function (e) {
e.stopPropagation();
showThumb(cur);
return false;
});
ppyStage.click(function () {
if (enlarged) {
showThumb(cur);
}
else {
showImage();
}
});
//play button
ppyPlay.click(function (e) {
e.stopPropagation();
// switch buttons
ppyPause.removeClass('ppy-hidden');
ppyPlay.addClass('ppy-hidden');
// initiate Slideshow
var slideshow;
slideshow = window.setInterval(function () {
next();
}, opts.slidespeed);
return false;
});
//pause button
ppyPause.click(function (e) {
e.stopPropagation();
// switch buttons
ppyPlay.removeClass('ppy-hidden');
ppyPause.addClass('ppy-hidden');
// stop Slideshow
window.clearInterval(slideshow);
return false;
});
// mouseover flag
$self.mouseenter(function () {
ismouseover = true;
}).mouseleave(function () {
ismouseover = false;
});
// start autoslide
if (opts.autoslide) {
ppyPlay.trigger('click');
}
}
});
};
////////////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.defaults
// set default options
//
////////////////////////////////////////////////////////////////////////////
$.fn.popeye.defaults = {
navigation: 'hover', //visibility of navigation - can be 'permanent' or 'hover'
caption: 'hover', //visibility of caption, based on image title - can be false, 'permanent' or 'hover'
zindex: 10000, //z-index of the expanded popeye-box. enter a z-index that works well with your site and doesn't overlay your site's navigational elements like dropdowns
direction: 'right', //direction that popeye-box opens, can be 'left' or 'right'
duration: 240, //duration of transitional effect when enlarging or closing the box
opacity: 0.8, //opacity of navigational overlay (only applicable if 'navigation' is set to 'hover'
easing: 'swing', //easing type, can be 'swing', 'linear' or any of jQuery Easing Plugin types (Plugin required)
slidespeed: 2000, //timespan that one image is shown in a slideshow
autoslide: false, //should the slideshow start automatically?
debug: false
};
// end of closure, bind to jQuery Object
})(jQuery);
////////////////////////////////////////////////////////////////////////////////
//
// avoid content flicker for non-js user agents
// (in order to use this, the js-files have to be included in the head of the
// html file!)
//
////////////////////////////////////////////////////////////////////////////////
jQuery('head').append('<style type="text/css"> .ppy-imglist { position: absolute; top: -1000em; left: -1000em; } </style>');
|