/* * Magnify portion of image where mouse is hovering over. * * Usage: * Add tags to your document which have a data-largesrc attribute identifying source of a * large picture to use in magnifier. * Example image with magnifier * * Manual usage: * Setup existing image in document with magnifier. * $('#image_id').imageMagnifier( large_image_url [, options ]); * * Replace existing image in document with a new image and large image in magnifier. * $('#image_id').imageMagnifier( image_url, large_image_url ); * * Setup existing image in document with a magnifier whose name is automatically derived from * the image's src attribute. * $('#image_id').imageMagnifier( {largesrc: function(src) { return src.replace(/\.jpg/,'-large.jpg'); }} ); * * Replace existing image in document with a new image and with large image in magnifier * automatically derived from image name. Simply changing the image src attribute triggers a change in magnifier. * $('#image_id').prop('src','new_image_url'); * * Setup existing image in document with a magnifier whose name is automatically derived from the * surrounding tag's target. The image element is "this" inside of the largesrc function. * $('#image_id').imageMagnifier( {largesrc: function() { return $(this).closest('a').prop('href'); }} ); * * Define a button that turns magnifier(s) on/off. * * * You can tell which image elements have been set up with a magnifier by whether they have a * 'imageMagnifier' data object. * * jquery.imageMagnifier.js v0.65 * Copyright (c) 2012-2021 Mack Pexton (mackpexton.com) * License: http://www.opensource.org/licenses/mit-license.php */ (function($){ var im = 'imageMagnifier', // Class names of pieces and parts im_image = im + '_image', im_loader = im + '_loader', im_magnifier = im + '_magnifier', im_container = im + '_container', fast = 'fast'; // actual fadeIn/Out speed $.imageMagnifier = { options: { // default settings for magnifier image: '', // url of image displayed on page magnifier: { image: '', // url of image displayed through magnifier width: 150, height: 150, top: 0, // initial top coordinate of magnifier midpoint left: 0, // initial left coordinate of magnifier midpoint zIndex: 2, // enough to hover over picture shadow: 20, // drop shadow width radius: null, // automatically computed to be circle if null cursor: 'none', // cursor used when magnifier is moving frozen: false // magnifier frozen in place till user clicks or drags it }, loader: { //image: 'images/loader.gif' // url of image displayed while images are loading // Following is 32x32 animated spinner image: 'data:image/gif;base64,R0lGODlhIAAgAOMAAP///wAAAMbGxoSEhLa2tpqamjY2NlZWVtjY2OTk5Ly8vB4eHgQEBP///////////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgAPACwAAAAAIAAgAAAE6vDJSWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBV8IFwMFPRBmBkSj+rBFZogCASwBDEY/CZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB+A4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6+Ho7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq+B6Q/tuetcaBPnW6+O7w/HpIiK9SaVK5GgV543tzjgGcghAgAh+QQBCgAPACwAAAAAIAAgAAAE7vDJSSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuDqMdAZKYUZ6iFMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesX3I5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rD+IPmsfB3uPoD6++G+w48edZPK+M6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkEAQoADwAsAAAAACAAIAAABOfwyUmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE+G+cD4ntpWkZQj1JIiZIoh7FFyHI0UxQz1uj8SOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140H0klUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm+FNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojw+F117i4nlLnY5ztRLsnOk+aV+oJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkEAQoADwAsAAAAACAAIAAABO7wyUkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJbaHIkekKGQkWyKHkvhKsd7DRmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0/VNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMPWVWAGYsPdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAEKAA8ALAAAAAAgACAAAATz8MlJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8j4QSNeF1Er4MNFn4SRSDARWroD4ETg1iVwuHjY91kYc1mwruwXKC9gmsJXliGxc+XiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1APygwLlJtPNA8L19DARdPzBOWSm1brJBi45soRAWQPAkrQIykShQ9wVhHCwCQCACH5BAEKAA8ALAAAAAAgACAAAATr8MlJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71eRTeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30/iI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWDwnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE/jiuL04RGEBgwWhShRgQExHBAAh+QQBCgAPACwAAAAAIAAgAAAE7/DJSWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9XkU3sqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR+ipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EPCcUGkIgFzgwZ0QsSBcXHiQvOwg/dEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAEKAA8ALAAAAAAgACAAAATw8MlJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71eRTeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWDwnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQD/US7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAEKAA8ALAAAAAAgACAAAATr8MlJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71eRTeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY+Yip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNg8IxRpbFAgfPQSqpbgGBqUD1w9XeCYp1AYZ19JJOYgH1KwP4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd+MFCN6HAAIKgNggY0KtEBAAh+QQBCgAPACwAAAAAIAAgAAAE6PDJSWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9XkU3sqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJea8DEIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4Pt7gHiRpFaLNrrq8HNg8JA70PWxQIH1+vsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzD3aoG9nxPi5d+jYUqfAhhykOFwJWiAAAIfkEAQoADwAsAAAAACAAIAAABPDwyUlpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJ8IHIFSkLGeoRTNhIsFehRww2CQLKF0tYGKYSg+ygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0+bm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h+Kr0SJ8MFihpNbx+4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX+BP0XJLD/GzTkAuD+qb0WT5D37OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOw==' }, largesrc: null, // set to a function to automatically determine url from image src callback: null, // function(top,left) called when magnifier is positioned on image callbackKeyDown: null, // function(top,left) called when a key is pressed while positioning magnifier enabled: null // automatically set to current global enabled state if null }, autoinit: true, // set to true to automatically init() when document loads init: function(opt) { // Automatically set up images with a data-largesrc attribute. $('img[data-largesrc]').imageMagnifier(opt); }, enabled: true, // current global state enable: function(img) { if (img) { $(img).each(function() { if (! $.hasData(this)) return; var $this = $(this), current_settings = $this.data(im); current_settings.enabled = true; if (! current_settings.finished) { $this.trigger('load'); // re-execute load handler in enabled state } else if (current_settings.frozen) { $('.'+im_magnifier,$this.parent()).fadeIn(fast); } }); } else { // enable all this.enable('.'+im_image); this.enabled = true; } }, disable: function(img) { if (img) { $(img).each(function() { var $this = $(this), current_settings = $this.data(im); current_settings.enabled = false; $('.'+im_magnifier,$this.parent()).fadeOut(fast); $('.'+im_loader,$this.parent()).hide(); }); } else { // disable all this.disable('.'+im_image); this.enabled = false; } }, toggle: function(img) { var e=this.enable, d=this.disable; if (img) { $(img).each(function() { if ($(this).data(im) && $(this).data(im).enabled) { d(img); } else { e(img); } }); } else { if (this.enabled) { d(); } else { e(); } } } }; $.fn.wrapTightly = function (wrapper) { this.each(function(){ var $this = $(this), $wrapper = $this.wrap(wrapper).parent(); // Transfer margins to wrapper $wrapper.css({ 'margin-left': $this.css('margin-left'), 'margin-right': $this.css('margin-right'), 'margin-top': $this.css('margin-top'), 'margin-bottom': $this.css('margin-bottom'), width: $this.outerWidth() + 'px', height: $this.outerHeight() + 'px' }); $this.css({margin: 0}); }); return this; }; function setup_image_magnifier($image,src,largesrc,settings) { // Pieces and parts var $container = null, $loader = null, $loaderImage = null, $magnifier = null, $magnifierImage = null, // Reference coordinates container_top, container_left, // Required measurments image_width, image_height, magnifier_image_width, magnifier_image_height, image_dist_left, image_dist_top, // padding and border width // Position formula invariants half_magnifier_width, half_magnifier_height, image_magnifier_width_ratio, image_magnifier_height_ratio, // Magnifier movement flags frozen = false, moved = false, was_frozen, // Saved variables last_position, current_settings; // Functions function is_enabled() { return current_settings.enabled; } function is_ready() { return current_settings && current_settings.imageReady && current_settings.magnifierReady; } function freeze(setting) { var prev_setting = frozen; frozen = setting === false ? false : true; current_settings.frozen = frozen; return prev_setting; } function mouse_loc(event) { // Mouse location over image return { top: event.pageY - container_top - image_dist_top, left: event.pageX - container_left - image_dist_left }; } function magnifier_loc(p) { // Return position in large image corresponding to the p position. return { top: Math.floor(p.top / image_magnifier_width_ratio), left: Math.floor(p.left / image_magnifier_height_ratio) }; } function compute_position_magnifier_invariants() { // Compute invariant variables for position_magnifier() container_top = $container.offset().top; container_left = $container.offset().left; half_magnifier_width = $magnifier.width() / 2; half_magnifier_height = $magnifier.height() / 2; image_dist_top = parseInt($image.css('padding-top'),10) + parseInt($image.css('border-top-width'),10); image_dist_left = parseInt($image.css('padding-left'),10) + parseInt($image.css('border-left-width'),10); image_magnifier_width_ratio = $image.width() / $magnifierImage.prop('width'); image_magnifier_height_ratio = $image.height() / $magnifierImage.prop('height'); } function position_magnifier(top,left) { magnifier_top = top - half_magnifier_height + image_dist_top, magnifier_left = left - half_magnifier_width + image_dist_left, magnifier_image_top = half_magnifier_height - (top / image_magnifier_height_ratio), magnifier_image_left = half_magnifier_width - (left / image_magnifier_width_ratio); $magnifier.css({ top: magnifier_top + 'px', left: magnifier_left + 'px', 'background-position': magnifier_image_left + 'px ' + magnifier_image_top + 'px' }); } function position_magnifier_handler(event,force) { if (frozen && ! force) return; last_position = mouse_loc(event); position_magnifier(last_position.top,last_position.left); } function callback_handler(event) { var p = mouse_loc(event); return current_settings.callback.call($image[0],p,magnifier_loc(p),event) } function callbackKeyDown_handler(event) { if (frozen) return; return current_settings.callbackKeyDown.call($image[0],last_position,magnifier_loc(last_position),event) } // // Setup container around image for magnifer. // $container = $image.wrapTightly('
').parent() .addClass(im_container) .css({ display: 'inline-block', position: 'relative', overflow: 'hidden' }) .on('mouseenter.im',function(){ current_settings = $image.data(im); if (is_enabled() && is_ready()) { $container.off('mousemove.im'); $loader.hide(); $magnifier.show(); $container.on('mousemove.im',position_magnifier_handler); if (current_settings.callbackKeyDown) $(document).on('keydown.im',callbackKeyDown_handler); if (! frozen) $magnifier.css({cursor:current_settings.magnifier.cursor}); } }) .on('mouseleave.im',function(){ if (! is_ready()) return; if (! frozen) $magnifier.hide(); $container.off('mousemove.im',position_magnifier_handler); if (current_settings.callbackKeyDown) $(document).off('keydown.im',callbackKeyDown_handler); }) .on('mousemove.im',function(){ if (! is_enabled()) return; if (! is_ready()) return; $(this).trigger('mouseenter.im'); }); // // Setup loader indicator. // $loader = $('
') .addClass(im_loader) .css({ 'position': 'absolute', 'margin-top': '30%', 'margin-left': '50%', 'text-align': 'left' }); $loaderImage = $('') .attr({ src: settings.loader.image }) .on('load.im',function() { $loader.append($loaderImage); }) .on('error.im',function() { $loader.text('Loading...'); }); $container.prepend($loader); // // Setup magnifier with large background image // $magnifier = $('
') .addClass(im_magnifier) .css({ width: settings.magnifier.width + 'px', height: settings.magnifier.height + 'px', display: 'none', position: 'absolute', border: '1px solid #666', 'z-index': settings.magnifier.zIndex, 'box-shadow': '0 0 ' + settings.magnifier.shadow + 'px #333', 'border-radius': settings.magnifier.radius + 'px', 'background-repeat': 'no-repeat', 'background-position': (settings.magnifier.left - (settings.magnifier.width/2)) + 'px ' + (settings.magnifier.top - (settings.magnifier.height/2)) + 'px' }) .on('mousedown.im',function(event) { position_magnifier_handler(event,true); was_frozen = freeze(false); moved = false; $(this).css({cursor:current_settings.magnifier.cursor}); event.stopImmediatePropagation(); }) .on('mousemove.im',function() { moved = true; }) .on('mouseup.im',function(event) { if (moved) { freeze() } else { freeze(!was_frozen) } if (frozen) $(this).css({cursor:$image.css('cursor')}); if (frozen && event.which == 1 && current_settings.callback) { // left mouse button if (callback_handler(event) === false) freeze(false); } }) .on('click.im',function(event) { return false; // gobble event }); $magnifierImage = $('') .on('load.im',function(event) { current_settings = $image.data(im); $magnifier.css('background-image','url('+this.src+')'); $loader.hide(); compute_position_magnifier_invariants(); if (is_enabled()) { if (current_settings.magnifier.top || current_settings.magnifier.left) { // Show magnifier when initial position is set. position_magnifier(current_settings.magnifier.top, current_settings.magnifier.left); current_settings.magnifier.top = current_settings.magnifier.left = 0; // reset $magnifier.fadeIn(fast); if (settings.magnifier.frozen !== false) freeze(); } else { // Initially position magnifier in center of picture so magnifier always appears. position_magnifier(250,175); current_settings.magnifier.top = current_settings.magnifier.left = 0; // reset $magnifier.fadeIn(fast); freeze(false); } } else { freeze(false); } current_settings.magnifierReady = true; current_settings.finished = true; }) .on('error.im',function() { current_settings = $image.data(im); current_settings.magnifierReady = current_settings.imageReady = false; });; $container.append($magnifier); // // Setup image to initiate magnifier upon load. // $image .addClass(im_image) .css({cursor:'default'}) .on('mousedown.im',function(event) { if (is_enabled() && is_ready()) { position_magnifier_handler(event,true); if ($.browser && $.browser.webkit) freeze(false); // allow drag and drop if (! frozen) $magnifier.css({cursor:current_settings.magnifier.cursor}); return false; } }) .on('click.im',function(event) { // If computer too slow to position magnifier so it can receive the click event, it gets triggered here. if (is_enabled()) return false; // gobble event, safety }) .on('load.im',function() { var $this = $(this); current_settings = $this.data(im); $this.fadeIn(fast); $magnifier.hide(); // Resize surrounding container $container.width($this.outerWidth()); $container.height($this.outerHeight()); $loader.width($this.outerWidth()); current_settings.imageReady = true; // Avoid downloading large image if not enabled. current_settings.finished = false; if (! is_enabled()) { $loader.hide(); return; } // Load magnifier image $loader.show(); var magnifier_image_src = (typeof current_settings.largesrc === 'function') ? current_settings.largesrc.call(this,this.src) : $this.data('largesrc'); if ($magnifierImage.prop('src') == magnifier_image_src) { $magnifierImage.trigger('load'); // manually trigger load event } else { $magnifierImage.prop('src',magnifier_image_src); } }) .on('error.im',function() { current_settings = $(this).data(im); current_settings.imageReady = current_settings.magnifierReady = false; }); // // Save settings and flags for magnifer with each image. // $image.data(im,$.extend({frozen:frozen,finished:false,imageReady:false,magnifierReady:false},settings)); $(window).resize(function(){ compute_position_magnifier_invariants(); }); } $.fn.imageMagnifier = function() { var settings = {}, // Arguments src = null, largesrc = null, options = {}, nargs = arguments.length; if (nargs > 0) { if (typeof arguments[nargs-1] == 'object') { options = arguments[--nargs]; } if (nargs == 1) { largesrc = arguments[0]; } else if (nargs >= 2) { src = arguments[0]; largesrc = arguments[1]; } } $.extend(true, settings, $.imageMagnifier.options, options); settings.enabled = settings.enabled !== null ? settings.enabled : $.imageMagnifier.enabled; settings.magnifier.radius = settings.magnifier.radius !== null ? settings.magnifier.radius : settings.magnifier.height/2; return this.each(function() { var $this = $(this), image_src = src !== null ? src : settings.image ? settings.image : $this.prop('src'), magnifier_image_src = largesrc ? largesrc : settings.magnifier.image ? settings.magnifier.image : typeof settings.largesrc === 'function' ? settings.largesrc.call(this,image_src) : $this.data('largesrc') ? $this.data('largesrc') : ''; if (! magnifier_image_src) return; // no large image to show in magnifier $this.data('largesrc',magnifier_image_src); // save largesrc for later if (! $this.data(im)) { setup_image_magnifier($this,image_src,magnifier_image_src,settings); if ($this.prop('src') == image_src) { if ($this.prop('complete')) $this.trigger('load'); // manually trigger load event } else { $this.fadeOut(fast); $this.prop('src',image_src); } } else { // Switch images $this.data(im,$.extend(true,{},$this.data(im),{magnifier:{top:0,left:0}},options)); $('.'+im_magnifier,$this.parent()).hide(); $('.'+im_loader,$this.parent()).show(); $this.fadeOut(fast, function(){ if ($this.prop('src') == image_src) { // Manually trigger load event only if image was previously found if ($this.data(im).imageReady) $this.trigger('load'); } else { // Trigger a reload of the magnifier by assigning image src $this.prop('src',image_src); } }); } }); return this; }; // Search for images with data-largesrc attributes and auto-initialize them. $(window).on('load.im',function() { if ($.imageMagnifier.autoinit) { $.imageMagnifier.init() } }); })(jQuery);