/** * * Zoomimage * Author: Stefan Petre www.eyecon.ro * */ (function($){ EYE.extend({ zoomimage: { libs: {}, types: /\.jpg|\.jpeg|\.png|\.gif|\.bmp/g, current: null, moved: false, pointer: {x:0,y:0}, diff: {x:0, y:0}, trackKey: false, //default options (many options are controled via CSS) defaults: { opacity: 0.3, //caption opacity border: 0, // border arround the image shadow: 6, // shadow size duration: 300, // animation duration prevent: 14, // pixels to move the mouse before the image is dragged controls: true, // display controls caption: true, // display caption hideSource: false, centered: false, className: false, onLoad: function(){return false}, beforeZoomIn: function(){return false}, onZoomIn: function(){return false}, beforeZoomOut: function(){return false}, onZoomOut: function(){return false}, onFocus: function(){return false}, controlsTrigger: 'focus', easing: 'linear', preload: 'click' }, // the template for the image's box template: [ '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '', '
', '', '', '
', '
', '
', '
' ], // handle click on the trigger click: function(e) { var el = this; el.blur(); // if the image was not preloaded yet then wait if (el.zoomimageCfg.loading === true) { return false; } //zoom it in if not zoomed already if (el.zoomimageCfg.zoomed == false) { EYE.zoomimage.zoomIn(el); //else zoom it out } else { EYE.zoomimage.zoomOut(el, false); } return false; }, // zoom in the image zoomIn: function(el) { //if the image was not loaded yet then wait if (el.zoomimageCfg.loaded === false) { //if the image is not preloading then start preloading if (el.zoomimageCfg.loading != true) { el.zoomimageCfg.loading = true; EYE.zoomimage.preload(el); } return; } //if the image is zoomed in then just focus it if (el.zoomimageCfg.zoomed == true) { EYE.zoomimage.focus(el); return; } el.zoomimageCfg.beforeZoomIn.apply(el,[el.zoomimageCfg.box]); var elPos = EYE.getPosition(el, true); var elHeight = el.offsetHeight; var elWidth = el.offsetWidth; var pos = EYE.getScroll(); var borderAndShadow = el.zoomimageCfg.border + el.zoomimageCfg.shadow; var width = el.zoomimageCfg.width + borderAndShadow * 2; var height = el.zoomimageCfg.height + borderAndShadow * 2; var screenRatio = pos.iw/pos.ih; var imageRatio = el.zoomimageCfg.width/el.zoomimageCfg.height; // if the image is bigger then the viewport then resize the image to fit if (screenRatio > imageRatio) { if (height > pos.ih) { height = pos.ih; width = parseInt(height * imageRatio,10); } } else if (width > pos.iw) { width = pos.iw; height = parseInt(width / imageRatio, 10); } //if the image should be centered then do that, else center to trigger's position but do not leave the viewport var top = el.zoomimageCfg.centered ? pos.t + parseInt((pos.ih - height)/2, 10) : Math.min( Math.max( pos.t, elPos.y + (elHeight - height)/2 - borderAndShadow ), pos.t + pos.ih - height ); var left = el.zoomimageCfg.centered ? pos.l + parseInt((pos.iw - width)/2, 10) : Math.min( Math.max( pos.l, elPos.x + (elWidth - width)/2 - borderAndShadow ), pos.l + pos.iw - width ); var imgWidth = width - borderAndShadow * 2; var imgHeight = height - borderAndShadow * 2; if(el.zoomimageCfg.hideSource === true) { el.style.visibility = 'hidden'; } //move the image's box and animated it $('#' + el.zoomimageCfg.box) .css({ top: elPos.y + 'px', left: elPos.x + 'px', width: elWidth + 'px', height: elHeight + 'px' }) .find('>div') .hide() .end() .find('img') .attr('src', el.zoomimageCfg.src) .css({ top: 0, left: 0, width: '100%', height: '100%', display: 'block', borderWidth: '0px' }) .end() .animate({ width: imgWidth, height: imgHeight, top: top + borderAndShadow, left: left + borderAndShadow }, el.zoomimageCfg.duration, el.zoomimageCfg.easing, function(){ $(this) .css({ top: top + 'px', left: left + 'px', width: width + 'px', height: height + 'px' }) .find('img') .css({ top: el.zoomimageCfg.shadow + 'px', left: el.zoomimageCfg.shadow + 'px', width: imgWidth + 'px', height: imgHeight + 'px', borderWidth: el.zoomimageCfg.border + 'px' }) .end() .find('>div:first') .find('div.zoomimage_sc') .css('height', height - el.zoomimageCfg.shadow*2 + 'px') .end() .show(); el.zoomimageCfg.zoomed = true; EYE.zoomimage.focus(el); el.zoomimageCfg.onZoomIn.apply(el,[el.zoomimageCfg.box]); }); }, //focus image and show gallery controls if it is part of a gallery showControls: function(el) { if(el == undefined) return; if (el.zoomimageCfg == undefined) { el = $('#' + $(el).attr('zoomimage')).get(0); } var height, imgWidth, borderAndShadow = el.zoomimageCfg.border + el.zoomimageCfg.shadow; $('#' + el.zoomimageCfg.box) .find('img') .each(function(){ imgWidth = parseInt($.curCSS(this, 'width'),10); }) .end() .get(0).zoomimageControls = true; // if it has caption then display it if(el.zoomimageCfg.caption) { $('#' + el.zoomimageCfg.box) .find('>div:eq(2)') .stop() .css({ bottom: borderAndShadow + 'px', left: borderAndShadow + 'px', width: imgWidth + 'px' }) .show() .each(function() { this.style.height = 'auto'; height = this.offsetHeight; this.style.height = '0'; }) .animate({height: height}, el.zoomimageCfg.duration); } //if it has controls then show them if(el.zoomimageCfg.controls) { // show controls only if it is part of a gallery if (EYE.zoomimage.libs[el.zoomimageCfg.lib] > 1) { $('#' + el.zoomimageCfg.box) .find('>div:eq(1)') .show() .each(function(){ if (!el.zoomimageCfg.controlsHeight) { el.zoomimageCfg.controlsHeight = this.offsetHeight; } this.style.height = '0'; }) .css({ top: borderAndShadow + 'px', left: borderAndShadow + 'px', width: imgWidth + 'px' }) .animate({height: el.zoomimageCfg.controlsHeight}, el.zoomimageCfg.duration); } } }, //zoom out the image and go to the next/previous if any zoomOut: function(el, goToNext) { var boxEl, elPos, borderAndShadow, elSize; // if the action was started by the trigger if (el.zoomimageCfg) { if (el.zoomimageCfg.zoomed === false) { return; } el.zoomimageCfg.beforeZoomOut.apply(el,[el.zoomimageCfg.box]); boxEl = document.getElementById(el.zoomimageCfg.box); // else try to find a link that has the same href as the image src } else { boxEl = el; el = $('a[href=' + $('img',boxEl).attr('src') + ']').get(0); } // the trigger was found so scale to image to trigger's size if (el) { elPos = EYE.getPosition(el, true); el.zoomimageCfg.zoomed = false; borderAndShadow = el.zoomimageCfg.border + el.zoomimageCfg.shadow; elSize = { width: el.offsetWidth, height: el.offsetHeight }; // the trigger was not found so scale the image to its center } else { borderAndShadow = EYE.zoomimage.defaults.border + EYE.zoomimage.defaults.shadow; elSize = { width: 0, height: 0 }; elPos = EYE.getPosition(boxEl, true); elPos.y += parseInt(boxEl.offsetHeight/2, 10); elPos.x += parseInt(boxEl.offsetWidth/2, 10); } $(boxEl) .css({ top: boxEl.offsetTop + borderAndShadow + 'px', left: boxEl.offsetLeft + borderAndShadow + 'px', width: boxEl.offsetWidth - borderAndShadow*2 + 'px', height: boxEl.offsetHeight - borderAndShadow*2 + 'px' }) .find('>div') .stop() .hide() .end() .find('img') .css({ top: 0, left: 0, width: '100%', height: '100%', borderWidth: '0px' }) .end() .animate( { top: elPos.y + 'px', left: elPos.x + 'px', width: elSize.width + 'px', height: elSize.height + 'px' }, // if the trigger was not found the use the default duration el ? el.zoomimageCfg.duration : EYE.zoomimage.defaults.duration, el.zoomimageCfg.easing, function() { //hide image and remove focus EYE.zoomimage.blur(); $(this).hide(); // if the trigger was found then aply callback and try to focus the next one zoomed if (el) { if(el.zoomimageCfg.hideSource === true) { el.style.visibility = 'visible'; } el.zoomimageCfg.onZoomOut.apply(el,[el.zoomimageCfg.box]); if (!goToNext) { EYE.zoomimage.focus($('div.zoomimage:visible:last').not(':animated').get(0)); } //the trigger was not found so remove the image since no trigger is present in the page } else { $(boxEl).stop().remove(); } } ); }, mouseOver: function(e) { var triggerEl = document.getElementById($(this).attr('zoomimage')); if (triggerEl.zoomimageCfg.zoomed === true && this.zoomimageControls == false) { EYE.zoomimage.showControls(triggerEl); } return false; }, mouseOut: function(e) { if ( !EYE.isChildOf(this, e.relatedTarget, this) ) { $(this) .find('>div:not(:first)') .stop() .hide(); this.zoomimageControls = false; } return false; }, // prepare for possible drag mouseDown: function(e) { // find the trigger var triggerEl = document.getElementById($(this).attr('zoomimage')); //if the trigger was found then prepare informations for drag if (triggerEl) { $.extend(EYE.zoomimage,{ current: this, prevent: triggerEl.zoomimageCfg.prevent, moved: false, diff: { x: e.pageX - this.offsetLeft, y: e.pageY - this.offsetTop }, pointer: { x: e.pageX , y: e.pageY } }); $(document) .bind('mousemove', EYE.zoomimage.mouseMove) .bind('mouseup', EYE.zoomimage.mouseUp); // if the trigger was not found then it is an orphan and zoom it out } else { $(this).zoomimageClear(); } return false; }, //do the drag if prevent distance was overtake mouseMove: function(e) { var diffX = Math.abs(EYE.zoomimage.pointer.x - e.pageX); var diffY = Math.abs(EYE.zoomimage.pointer.y - e.pageY); //the prevent distance was not reached yet so we check if it is reached already if (EYE.zoomimage.moved === false) { if ( diffX > EYE.zoomimage.prevent|| diffY > EYE.zoomimage.prevent) { EYE.zoomimage.moved = true; $(EYE.zoomimage.current).addClass('zoomimage_move'); if (!$(EYE.zoomimage.current).is('.zoomimage_focused')) { EYE.zoomimage.focus(EYE.zoomimage.current); } } // the prevent distance was overtake so the element can be moved } else { EYE.zoomimage.current.style.top = e.pageY - EYE.zoomimage.diff.y + 'px'; EYE.zoomimage.current.style.left = e.pageX - EYE.zoomimage.diff.x + 'px'; } return false; }, //the drag stops mouseUp: function (e) { $(EYE.zoomimage.current).removeClass('zoomimage_move'); EYE.zoomimage.current = null; $(document) .unbind('mousemove', EYE.zoomimage.mouseMove) .unbind('mouseup', EYE.zoomimage.mouseUp); return false; }, // click on image imageClick: function(e) { $(document) .unbind('mousemove', EYE.zoomimage.mouseMove) .unbind('mouseup', EYE.zoomimage.mouseUp); var el = document.getElementById($(this).attr('zoomimage')); // if the trigger was found if (el) { //if the image was not moved but was focused if (EYE.zoomimage.moved === false && $(this).is('.zoomimage_focused')) { // if the event target is a link then it was a click on one of the controls and go to the next image if ($(e.target).is('a')) { EYE.zoomimage.zoomNext(el, e.target.className == 'zoomimage_next' ? 1 : -1); var goToNext = true; // else just zoom it out } else { EYE.zoomimage.zoomOut(el, goToNext||false); } // just focus the image } else if(!$(this).is('.zoomimage_focused')) { EYE.zoomimage.focus(this); } //the trigger was not found so the image is orphan and zoom it out } else { $(this).zoomimageClear(); } return false; }, //zoom out any opened image and clear orphan images clear: function() { var subject = this; if (subject.size() == 0) { subject = $('div.zoomimage'); } return subject.each(function(){ var triggerEl = document.getElementById($(this).attr('zoomimage')); if (triggerEl) { EYE.zoomimage.zoomOut(triggerEl, false); } else { EYE.zoomimage.zoomOut(this, false); } }); }, // zoom the next image in gallery zoomNext: function(el, dir) { if(el.zoomimageCfg.zoomed === false) { return; } EYE.zoomimage.zoomOut(el, true); var nextImg = el.zoomimageCfg.iteration + dir; var lib = $(el).attr('zoomimage'); var maxImg = EYE.zoomimage.libs[lib]; if (nextImg < 0) { nextImg = maxImg - 1; } else if (nextImg >= maxImg) { nextImg = 0; } EYE.zoomimage.zoomIn($('a[zoomimage="' + lib + '"]').get(nextImg)); }, //hande any key pressed keyPressed: function(e) { var el = $('div.zoomimage_focused'); if (el.size() == 1) { var pressedKey = e.charCode || e.keyCode || -1; el = $('#' + $(el).attr('zoomimage')).get(0); var lib = $(el).attr('zoomimage'); switch (pressedKey) { //end case 35: // go to the last image in the gallery if (EYE.zoomimage.libs[lib] > 1 && EYE.zoomimage.libs[lib] - 1 != el.zoomimageCfg.iteration) { EYE.zoomimage.zoomNext(el, EYE.zoomimage.libs[lib] - el.zoomimageCfg.iteration - 1); return false; } break; //home case 36: // go to the first image in the gallery if (EYE.zoomimage.libs[lib] > 1 && el.zoomimageCfg.iteration != 0) { EYE.zoomimage.zoomNext(el, - el.zoomimageCfg.iteration); return false; } break; //down; case 40: //left case 37: //backspace case 8: //page up case 33: //p case 80: case 112: // go to the previous image in the gallery if (EYE.zoomimage.libs[lib] > 1) { EYE.zoomimage.zoomNext(el, -1); return false; } break; //up case 38: //right case 39: //page down case 34: //space case 32: //n case 110: case 78: // go to the next image in the gallery if (EYE.zoomimage.libs[lib] > 1) { EYE.zoomimage.zoomNext(el, 1); return false; } break; //escape case 27: // well zoome out the curent image EYE.zoomimage.zoomOut(el, false); return false; break; } } }, // focus on image focus: function(el) { if(el == undefined) return; if (el.zoomimageCfg == undefined) { el = $('#' + $(el).attr('zoomimage')).get(0); } else { var showControls = true; } // if another image is focused then remove focus EYE.zoomimage.blur(el); $('#' + el.zoomimageCfg.box) .not('.zoomimage_focused') .addClass('zoomimage_focused'); el.zoomimageCfg.onFocus.apply(el,[el.zoomimageCfg.box]); if (el.zoomimageCfg.controlsTrigger == 'focus' || showControls) { EYE.zoomimage.showControls(el); } }, //blur image blur: function(el) { $('div.zoomimage_focused') .not('#' + (el == undefined ? 'fakezoomimage' : el.zoomimageCfg.box)) .removeClass('zoomimage_focused') .each(function(){ this.zoomimageControls = false; }) .find('>div:not(:first)') .stop() .hide(); }, preload: function(el) { // place the loading aimation on top var boxEl = $('#' + el.zoomimageCfg.box).show(); boxEl.find('>div, img').hide(); var elPos = EYE.getPosition(el, true); boxEl .find('>div:last') .show() .end() .css({ top: elPos.y + 'px', left: elPos.x + 'px', width: el.offsetWidth + 'px', height: el.offsetHeight + 'px' }); // preload the image var preld= new Image(); preld.src = el.href; //if the image was laoded already if (preld.complete) { EYE.zoomimage.markPreloaded(preld, el); // else place a callback } else { preld.onload = function() { EYE.zoomimage.markPreloaded(preld, el); }; } }, markPreloaded: function(preld, el) { //mark image as loaded and remember the size and source $.extend(el.zoomimageCfg,{ loaded: true, width: preld.width, height: preld.height, src: preld.src }); // hide loading animation $('#' + el.zoomimageCfg.box) .find('div.zoomimage_loading') .hide(); //if the image waits to be enlarged then zoom in if (el.zoomimageCfg.loading) { el.zoomimageCfg.loading = false; EYE.zoomimage.zoomIn(el); } el.zoomimageCfg.onLoad.apply(el,[el.zoomimageCfg.box]); }, //constructor init: function(opt) { //generate a library key var libKey = parseInt(Math.random()*2000,10); //store the number of images in the library EYE.zoomimage.libs[libKey] = 0; opt = $.extend({lib:libKey}, EYE.zoomimage.defaults, opt||{}); return this.each(function(){ var jQEl = $(this); var el = this; //consider only the links pointing to an image if (el.href && el.href.toLowerCase().match(EYE.zoomimage.types) != null) { //store library options el.zoomimageCfg = $.extend({}, opt, { zoomed: false, loading: false, loaded: false, animated: false, src: el.href, iteration: EYE.zoomimage.libs[libKey], box: 'zoomimage_' + parseInt(Math.random() * 2000, 10) + '' }); //increment the number of images in the library EYE.zoomimage.libs[libKey]++; jQEl .bind('click', EYE.zoomimage.click) .attr('zoomimage', libKey) .attr('zoomimageBox', el.zoomimageCfg.box); var currId = jQEl.attr('id'); if (!currId) { currId = el.zoomimageCfg.box + '_trigger'; jQEl.attr('id', currId); } var titleAttr = $(el).attr('title'); if (titleAttr == '' || titleAttr == false) { el.zoomimageCfg.caption = false; } // generate the HTML for the image's box $(EYE.zoomimage.template.join('')) .attr('id', el.zoomimageCfg.box) .attr('zoomimage', currId) .addClass(el.zoomimageCfg.className) .appendTo(document.body) .bind('mousedown', EYE.zoomimage.mouseDown) .bind('click', EYE.zoomimage.imageClick) .each(function(){ this.zoomimageControls = false; if (el.zoomimageCfg.controlsTrigger != 'focus') { $(this) .bind('mouseover', EYE.zoomimage.mouseOver) .bind('mouseout', EYE.zoomimage.mouseOut); } }) .find('>div') .not(':first') .css('opacity', el.zoomimageCfg.opacity) .end() .filter('div:eq(2)') .html('

' + titleAttr + '

'); if (el.zoomimageCfg.preload == 'load') { EYE.zoomimage.preload(el); } if (EYE.zoomimage.trackKey === false) { EYE.zoomimage.trackKey = true; $(document).bind('keydown', EYE.zoomimage.keyPressed); } } }); } } }); $.fn.extend({ /** * Open all images found in 'href' attribute from each element specified in the selection. The images are grouped in galleries. The images are preloaded before any user interation. * @name zoomimage * @description Open all images found in 'href' attribute from each element specified in the selection. The images are grouped in galleries * @param Hash options A hash of parameters. All parameters are optional. * @option float opacity The opacity for the caption and controls. Default: 0.3 * @option int border Image's border. Default: 0 * @option int duration Animation duration. Default 300 * @option int prevent Pixes to move the mouse before the images is dragged (prevents accidental dragging). Default: 14 * @option boolean controls Whatever if the controls are displayed (if the image is not part of an libriry then the controls are not displayed) * @option boolean caption Whatever if the caption is displayed (the caption text is the text from 'title' atribute. Default: true * @option boolean centered Whatever if the image should be centered in the viewport or to the trigger. Default: false * @option string easing Animation easing. Default: linear * @option boolean hideSource Whatever to hide source when the image is opened. Default: false * @option string className CSS class to add to image's box. Default: false * @option string controlsTrigger 'focus' to show caption and controls when the box is focused or 'mouseover' to show controls and caption on mouse over. Default: 'focus' * @option string preload 'click' to preload the image when the trigger is clicked or 'load' to preload the image on document load. Default: 'click' * @option function onLoad Callback function triggered when the image was loaded * @option function beforeZoomIn Callback function triggered before the image is zoomed in * @option function onZoomIn Callback function triggered when the image is zooms in * @option function beforeZoomOut Callback function triggered before the image is zoomed out * @option function onZoomOut Callback function triggered when the image is zooms out * @option function onFocus Callback function triggered when the image is focused */ zoomimage: EYE.zoomimage.init, /** * Zooms out all opened images and removes orphans (when the trigger was not found) * To clear specific images use for slector 'div.zooimage[whatever]', else all the images are processed */ zoomimageClear: EYE.zoomimage.clear }); })(jQuery);