MediaWiki:Jquery.zoomImage.js

/* * @description Plugin for modalLayer functionality to get scaleable images. * Using jquery.mousewheeel.js and jquery.ivierwer.js jQuery plugins (see below) * Final function modalLayer_ShowIviewerZoomImage creates zoomable image + controls on a div#modal-fg * @requires $.resource * @requires MediaWiki:Common.css CSS for jQuery plugin iviewer, popup image * @requires $.jI18n {} * @requires initImageZooming and the following functions * @requires zoomImage * @requires modalLayer_Create * @requires modalLayer_ShowImage */

/* Check dependencies */ if (!$.jI18n) { console.log("Global resource dictionary $.jI18n is missing. Please have it defined it in MediaWiki:Commons.js"); } if (!$.resource) { console.log("Global resource function $.resource('reource-key') is missing. Please make sure this function is defined in MediaWiki:Commons.js"); }

/** * file jquery.mousewheeel.js * * Copyright (c) 2013 Brandon Aaron (http://brandon.aaron.sh) * Licensed under the MIT License (LICENSE.txt). * * @version 3.1.9 * @requires: jQuery 1.2.2+ * @param {object} factory * @returns {undefined} */ (function (factory) {   if ( typeof define === 'function' && define.amd ) {        // AMD. Register as an anonymous module.        define(['jquery'], factory);    } else if (typeof exports === 'object') {        // Node/CommonJS style for Browserify        module.exports = factory;    } else {        // Browser globals        factory(jQuery);    } }(function ($) {

var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'], toBind = ( 'onwheel' in document || document.documentMode >= 9 ) ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'], slice = Array.prototype.slice, nullLowestDeltaTimeout, lowestDelta;

if ( $.event.fixHooks ) { for ( var i = toFix.length; i; ) { $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks; }   }

var special = $.event.special.mousewheel = { version: '3.1.9',

setup: function { if ( this.addEventListener ) { for ( var i = toBind.length; i; ) { this.addEventListener( toBind[--i], handler, false ); }           } else { this.onmousewheel = handler; }           // Store the line height and page height for this particular element $.data(this, 'mousewheel-line-height', special.getLineHeight(this)); $.data(this, 'mousewheel-page-height', special.getPageHeight(this)); },

teardown: function { if ( this.removeEventListener ) { for ( var i = toBind.length; i; ) { this.removeEventListener( toBind[--i], handler, false ); }           } else { this.onmousewheel = null; }       },

getLineHeight: function(elem) { return parseInt($(elem)['offsetParent' in $.fn ? 'offsetParent' : 'parent'].css('fontSize'), 10); },

getPageHeight: function(elem) { return $(elem).height; },

settings: { adjustOldDeltas: true }   };

$.fn.extend({       mousewheel: function(fn) {            return fn ? this.bind('mousewheel', fn) : this.trigger('mousewheel');        },

unmousewheel: function(fn) { return this.unbind('mousewheel', fn); }   });

function handler(event) { var orgEvent  = event || window.event, args      = slice.call(arguments, 1), delta     = 0, deltaX    = 0, deltaY    = 0, absDelta  = 0; event = $.event.fix(orgEvent); event.type = 'mousewheel';

// Old school scrollwheel delta if ( 'detail'     in orgEvent ) { deltaY = orgEvent.detail * -1;      } if ( 'wheelDelta' in orgEvent ) { deltaY = orgEvent.wheelDelta;       } if ( 'wheelDeltaY' in orgEvent ) { deltaY = orgEvent.wheelDeltaY;     } if ( 'wheelDeltaX' in orgEvent ) { deltaX = orgEvent.wheelDeltaX * -1; }

// Firefox < 17 horizontal scrolling related to DOMMouseScroll event if ( 'axis' in orgEvent && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) { deltaX = deltaY * -1; deltaY = 0; }

// Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy delta = deltaY === 0 ? deltaX : deltaY;

// New school wheel delta (wheel event) if ( 'deltaY' in orgEvent ) { deltaY = orgEvent.deltaY * -1; delta = deltaY; }       if ( 'deltaX' in orgEvent ) { deltaX = orgEvent.deltaX; if ( deltaY === 0 ) { delta = deltaX * -1; } }

// No change actually happened, no reason to go any further if ( deltaY === 0 && deltaX === 0 ) { return; }

// Need to convert lines and pages to pixels if we aren't already in pixels // There are three delta modes: //  * deltaMode 0 is by pixels, nothing to do        //   * deltaMode 1 is by lines //  * deltaMode 2 is by pages if ( orgEvent.deltaMode === 1 ) { var lineHeight = $.data(this, 'mousewheel-line-height'); delta *= lineHeight; deltaY *= lineHeight; deltaX *= lineHeight; } else if ( orgEvent.deltaMode === 2 ) { var pageHeight = $.data(this, 'mousewheel-page-height'); delta *= pageHeight; deltaY *= pageHeight; deltaX *= pageHeight; }

// Store lowest absolute delta to normalize the delta values absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) );

if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta;

// Adjust older deltas if necessary if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) { lowestDelta /= 40; }       }

// Adjust older deltas if necessary if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) { // Divide all the things by 40! delta /= 40; deltaX /= 40; deltaY /= 40; }

// Get a whole, normalized value for the deltas delta = Math[ delta  >= 1 ? 'floor' : 'ceil' ](delta / lowestDelta); deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta); deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta);

// Add information to the event object event.deltaX = deltaX; event.deltaY = deltaY; event.deltaFactor = lowestDelta; // Go ahead and set deltaMode to 0 since we converted to pixels // Although this is a little odd since we overwrite the deltaX/Y // properties with normalized deltas. event.deltaMode = 0;

// Add event and delta to the front of the arguments args.unshift(event, delta, deltaX, deltaY);

// Clearout lowestDelta after sometime to better // handle multiple device types that give different // a different lowestDelta // Ex: trackpad = 3 and mouse wheel = 120 if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); } nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200);

return ($.event.dispatch || $.event.handle).apply(this, args); }

function nullLowestDelta { lowestDelta = null; }

function shouldAdjustOldDeltas(orgEvent, absDelta) { // If this is an older event and the delta is divisable by 120, // then we are assuming that the browser is treating this as an       // older mouse wheel event and that we should divide the deltas // by 40 to try and get a more usable deltaFactor. // Side note, this actually impacts the reported scroll distance // in older browsers and can cause scrolling to be slower than native. // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false. return special.settings.adjustOldDeltas && orgEvent.type === 'mousewheel' && absDelta % 120 === 0; }

}));

/** * @description file jquery.ivierwer.js: * * allows to view image in selected div with zoom controls and * possibility to move image in area by mouse. * * Plugin is dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. * * Docs available at http://wiki.github.com/can3p/iviewer * See MediaWiki:Common.js → modalLayer_ShowImage * @param {object} $ * @returns {undefined} */ (function ($) { 'use strict';  $.fn.iviewer  = function (o) {    return this.each(function  { $(this).data('viewer', new $iv(this, o)); }); };

var defaults = { /**   * start zoom value for image, not used now * may be equal to "fit" to fit image into container or scale in % **/   zoom: "fit", /**   * base value to scale image **/   zoom_base: 100, /**   * initial zomm when image is loaded (AP: added 2010-12-07 15:09:16) **/   zoom_init: 100, /**   * maximum zoom **/   zoom_max: 800, /**   * minimum zoom **/   zoom_min: 25, /**   * base of rate multiplier. * zoom is calculated by formula: zoom_base * zoom_delta^rate **/   zoom_delta: 1.4, /**   * if true plugin doesn't add its own controls **/   ui_disabled: false, /**   * if false, plugin doesn't bind resize event on window and this must * be handled manually **/   update_on_resize: true, /**   * event is triggered when zoom value is changed * @param int new zoom value * @return boolean if false zoom action is aborted **/   onZoom: null, /**   * callback is fired after plugin setup **/   initCallback: null, /**   * event is fired on drag begin * @param object coords mouse coordinates on the image * @return boolean if false is returned, drag action is aborted **/   onStartDrag: null, /**   * event is fired on drag action * @param object coords mouse coordinates on the image **/   onDrag: null, /**   * event is fired when mouse moves over image * @param object coords mouse coordinates on the image **/   onMouseMove: null, /**   * mouse click event * @param object coords mouse coordinates on the image **/   onClick: null, /**   * event is fired when image starts to load */   onStartLoad: null, /**   * event is fired, when image is loaded and initially positioned */   onFinishLoad: null };

$.iviewer = function (e, o) { var me = this;

/* object containing actual information about image * @img_object.object - jquery img object * @img_object.orig_{width|height} - original dimensions * @img_object.display_{width|height} - actual dimensions */   this.img_object = {}; this.zoom_object = {}; //object to show zoom status this.image_loaded = false;

//drag variables this.dx = 0; this.dy = 0; this.dragged = false;

this.settings = $.extend({}, defaults, o || {}); this.current_zoom = this.settings.zoom;

if (this.settings.src === null) { return; }   this.container = $(e);

this.update_container_info;

//init container this.container.css("overflow", "hidden");

if (this.settings.update_on_resize === true) { $(window).resize(function {        me.update_container_info;      }); }

this.img_object.x = 0; this.img_object.y = 0;

//init object this.img_object.object = $(" ") .css({position: "absolute", top: "0px", left: "0px"}) //this is needed, because chromium sets them auto otherwise //bind mouse events .mousedown(function (e) {return me.drag_start(e); }) .mousemove(function (e) {return me.drag(e); }) .mouseup(function (e) {return me.drag_end(e); }) .click(function (e) {return me.click(e); }) .mouseleave(function (e) {return me.drag_end(e); }) .mousewheel(function (ev, delta) {       //this event is there instead of containing div, because        //at opera it triggers many times on div        var zoom = (delta > 0) ? 1 : -1;        me.zoom_by(zoom);        return false;      });

this.img_object.object.prependTo(me.container); this.loadImage(this.settings.src);

if (!this.settings.ui_disabled) { this.createui; }

if (this.settings.initCallback) { this.settings.initCallback.call(this); } };

var $iv = $.iviewer;

$iv.fn = $iv.prototype = { iviewer : "0.4.2" }; $iv.fn.extend = $iv.extend = $.extend;

$iv.fn.extend({   loadImage: function (src) {      this.current_zoom = this.settings.zoom;      this.image_loaded = false;      var me = this;

if (this.settings.onStartLoad) { this.settings.onStartLoad.call(this); }

this.img_object.object.unbind('load') .removeAttr("src") .removeAttr("width") .removeAttr("height") .css({top: 0, left: 0}) .load(function {          me.image_loaded = true;          me.img_object.display_width = me.img_object.orig_width = this.width;          me.img_object.display_height = me.img_object.orig_height = this.height;

if (!me.container.hasClass("iviewer_cursor")) { me.container.addClass("iviewer_cursor"); }

if (me.settings.zoom === "fit") { me.fit; } else { me.set_zoom(me.settings.zoom); }

if (me.settings.onFinishLoad) { me.settings.onFinishLoad.call(me); }

//src attribute is after setting load event, or it won't work }).attr("src", src);   },

/**   * fits image in the container **/   fit: function  { var aspect_ratio = this.img_object.orig_width / this.img_object.orig_height, window_ratio = this.settings.width / this.settings.height, choose_left = (aspect_ratio > window_ratio), new_zoom = 0;

if (choose_left) { new_zoom = this.settings.width / this.img_object.orig_width * 100; } else { new_zoom = this.settings.height / this.img_object.orig_height * 100; }     this.set_zoom(new_zoom * this.settings.zoom_init / 100); },

/**   * center image in container **/   center: function  { this.setCoords(-Math.round((this.img_object.display_height - this.settings.height) / 2),                    -Math.round((this.img_object.display_width - this.settings.width) / 2)); },

/**   * move a point in container to the center of display area * @param x a point in container * @param y a point in container **/   moveTo: function (x, y) { var dx = x - Math.round(this.settings.width / 2), dy = y - Math.round(this.settings.height / 2), new_x = this.img_object.x - this.dx, new_y = this.img_object.y - this.dy;

this.setCoords(new_x, new_y); },

/**   * set coordinates of upper left corner of image object **/   setCoords: function (x, y) { // do nothing while image is still loading if (!this.image_loaded) { return; }

//check new coordinates to be correct (to be in rect) if (y > 0) { y = 0; }     if (x > 0) { x = 0; }     if (y + this.img_object.display_height < this.settings.height) { y = this.settings.height - this.img_object.display_height; }     if (x + this.img_object.display_width < this.settings.width) { x = this.settings.width - this.img_object.display_width; }     if (this.img_object.display_width <= this.settings.width) { x = -(this.img_object.display_width - this.settings.width) / 2; }     if (this.img_object.display_height <= this.settings.height) { y = -(this.img_object.display_height - this.settings.height) / 2; }

this.img_object.x = x;     this.img_object.y = y;

this.img_object.object.css("top", y + "px").css("left", x + "px"); },

/**   * convert coordinates on the container to the coordinates on the image (in original size) *   * @return object with fields x, y according to coordinates or false * if initial coords are not inside image **/   containerToImage : function (x, y) { if (x < this.img_object.x || y < this.img_object.y ||         x > this.img_object.x + this.img_object.display_width ||          y > this.img_object.y + this.img_object.display_height) { return false; }

return { x : $iv.descaleValue(x - this.img_object.x, this.current_zoom), y : $iv.descaleValue(y - this.img_object.y, this.current_zoom) };   },

/**   * convert coordinates on the image (in original size) to the coordinates on the container *   * @return object with fields x, y according to coordinates or false * if initial coords are not inside image **/   imageToContainer : function (x, y) { if (x > this.img_object.orig_width || y > this.img_object.orig_height) { return false; }     return { x : this.img_object.x + $iv.scaleValue(x, this.current_zoom), y : this.img_object.y + $iv.scaleValue(y, this.current_zoom) };   },

/**   * get mouse coordinates on the image * @param e - object containing pageX and pageY fields, e.g. mouse event object *   * @return object with fields x, y according to coordinates or false * if initial coords are not inside image **/   getMouseCoords : function (e) { var img_offset = this.img_object.object.offset; return { x : $iv.descaleValue(e.pageX - img_offset.left, this.current_zoom), y : $iv.descaleValue(e.pageY - img_offset.top, this.current_zoom) };   },

/**   * set image scale to the new_zoom * @param new_zoom image scale in % **/   set_zoom: function (new_zoom) { if (this.settings.onZoom && this.settings.onZoom.call(this, new_zoom) === false) { return; }

//do nothing while image is being loaded if (!this.image_loaded) { return; }

if (new_zoom < this.settings.zoom_min) { new_zoom = this.settings.zoom_min; } else if (new_zoom > this.settings.zoom_max) { new_zoom = this.settings.zoom_max; }

/* we fake these values to make fit zoom properly work */ var old_x, old_y; if (this.current_zoom === "fit") { old_x = Math.round(this.settings.width / 2 + this.img_object.orig_width / 2); old_y = Math.round(this.settings.height / 2 + this.img_object.orig_height / 2); this.current_zoom = 100; } else { old_x = -parseInt(this.img_object.object.css("left"), 10) + Math.round(this.settings.width / 2); old_y = -parseInt(this.img_object.object.css("top"), 10) + Math.round(this.settings.height / 2); }

var new_width = $iv.scaleValue(this.img_object.orig_width, new_zoom), new_height = $iv.scaleValue(this.img_object.orig_height, new_zoom), new_x = $iv.scaleValue($iv.descaleValue(old_x, this.current_zoom), new_zoom), new_y = $iv.scaleValue($iv.descaleValue(old_y, this.current_zoom), new_zoom);

new_x = this.settings.width / 2 - new_x; new_y = this.settings.height / 2 - new_y;

this.img_object.object.attr("width", new_width).attr("height", new_height); this.img_object.display_width = new_width; this.img_object.display_height = new_height;

this.setCoords(new_x, new_y);

this.current_zoom = new_zoom;

$.isFunction(this.settings.onAfterZoom) && this.settings.onAfterZoom.call(this, new_zoom); this.update_status; },

/**   * changes zoom scale by delta * zoom is calculated by formula: zoom_base * zoom_delta^rate * @param Integer delta number to add to the current multiplier rate number **/   zoom_by: function (delta) { var closest_rate = this.find_closest_zoom_rate(this.current_zoom), next_rate = closest_rate + delta, next_zoom = this.settings.zoom_base * Math.pow(this.settings.zoom_delta, next_rate); if (delta > 0 && next_zoom < this.current_zoom) { next_zoom *= this.settings.zoom_delta; }     if (delta < 0 && next_zoom > this.current_zoom) { next_zoom /= this.settings.zoom_delta; }     this.set_zoom(next_zoom); },

/**   * finds closest multiplier rate for value * basing on zoom_base and zoom_delta values from settings * @param Number value zoom value to examine **/   find_closest_zoom_rate: function (value) { if (value === this.settings.zoom_base) { return 0; }

function div(val1, val2) {return val1 / val2; } function mul(val1, val2) {return val1 * val2; }

var func = (value > this.settings.zoom_base) ? mul : div, sgn = (value > this.settings.zoom_base) ? 1 : -1,       mltplr = this.settings.zoom_delta, rate = 1;

while (Math.abs(func(this.settings.zoom_base, Math.pow(mltplr, rate)) - value) >         Math.abs(func(this.settings.zoom_base, Math.pow(mltplr, rate + 1)) - value)) { rate++; }     return sgn * rate; },

/* update scale info in the container */ update_status: function { if (!this.settings.ui_disabled) { var percent = Math.round(100 * this.img_object.display_height / this.img_object.orig_height); if (percent) { if (this.zoom_object.length) {// added to call on a created object this.zoom_object.html(percent + "%"); }       }      }    },

update_container_info: function { this.settings.height = this.container.height; this.settings.width = this.container.width; },

/**   *   callback for handling mousdown event to start dragging image **/   drag_start: function (e) { if (this.settings.onStartDrag &&       this.settings.onStartDrag.call(this, this.getMouseCoords(e)) === false) { return false; }

/* start drag event*/ this.dragged = true; this.container.addClass("iviewer_drag_cursor");

this.dx = e.pageX - this.img_object.x;     this.dy = e.pageY - this.img_object.y;      return false; },

/**   *   callback for handling mousmove event to drag image **/   drag: function (e) { this.settings.onMouseMove && this.settings.onMouseMove.call(this, this.getMouseCoords(e)); if (this.dragged) { this.settings.onDrag && this.settings.onDrag.call(this, this.getMouseCoords(e)); var ltop = e.pageY - this.dy; var lleft = e.pageX - this.dx; this.setCoords(lleft, ltop); return false; }   },

/**   *   callback for handling stop drag **/   drag_end: function (e) { this.container.removeClass("iviewer_drag_cursor"); this.dragged=false; },

click: function (e) { this.settings.onClick && this.settings.onClick.call(this, this.getMouseCoords(e)); },

/**   *   create zoom buttons info box **/   createui: function  { var me = this; $(" ").addClass("iviewer_zoom_in").addClass("iviewer_common") .addClass("iviewer_button") .mousedown(function {me.zoom_by(1); return false; }).appendTo(this.container); $(" ").addClass("iviewer_zoom_out").addClass("iviewer_common") .addClass("iviewer_button") .mousedown(function {me.zoom_by(-1); return false; }).appendTo(this.container); $(" ").addClass("iviewer_zoom_zero").addClass("iviewer_common") .addClass("iviewer_button") .mousedown(function {me.set_zoom(100); return false; }).appendTo(this.container); $(" ").addClass("iviewer_zoom_fit").addClass("iviewer_common") .addClass("iviewer_button") .mousedown(function {me.fit(this); return false; }).appendTo(this.container); this.zoom_object = $(" ").addClass("iviewer_zoom_status").addClass("iviewer_common") .appendTo(this.container); this.update_status; //initial status update } });

$iv.extend({   scaleValue: function (value, toZoom) {      return value * toZoom / 100;    },    descaleValue: function (value, fromZoom) {      return value * 100 / fromZoom;    }  }); })(jQuery);

/* * @description create zoomable image control on the current div#modal-fg */ function modalLayer_ShowIviewerZoomImage { var modalFG = $("#modal-fg"); // get the image object var newImg = modalFG.find("#modal-fg-wrapper img").get(0); // remove #iviewer_zoom_icon modalFG.find('#modal-fg-wrapper, #iviewer_zoom_icon').remove; // modify zoomcaption modalFG.find("#zoomcaption").attr({     style: "text-align:center; position:absolute;bottom:0px;left:0px;font-weight:bold;width:100%;background: rgba(255, 255, 255, 0.5);"    }) // add (–) to remove the caption .prepend(" (–) ") .parent.find('#removeCaption').click(function {      $('#zoomcaption').slideUp('slow');    }); // execute iviewer plugin modalFG.iviewer({   zoom_min: 10, // minimal zoom    zoom_init: 95,// initial image scale (1:1)    // ┌─  http://www.species-id.net/o/media/thumb/a/a7/Lamium_album_plants.jpg/200px-Lamium_album_plants.jpg     // └─> http://www.species-id.net/o/media/a/a7/Lamium_album_plants.jpg    src: newImg.src.replace(/(.+)\/thumb(\/[^\/]+\/[^\/]+\/[^\/]+)\/.+/, '$1$2'),    initCallback: function {      var object = this;      $("").click(function  {object.zoom_by(1); });      $("").click(function  {object.zoom_by(-1); });      $("").click(function  {object.fit; });      $("").click(function  {object.set_zoom(100); });      $("").click(function  {object.update_container_info; });    },    onFinishLoad: function  {      // add tool tips      modalFG.find("img").first .attr("title", $.resource("ZoomImage_toolTipMoveable")); // Next searching for CLASS, because iviewer generates class-div from IDs above! modalFG.find(".iviewer_zoom_in").attr("title", $.resource("ZoomImage_zoomIn")); modalFG.find(".iviewer_zoom_out").attr("title", $.resource("ZoomImage_zoomOut")); modalFG.find(".iviewer_zoom_fit").attr("title", $.resource("ZoomImage_fitToWindow")); modalFG.find(".iviewer_zoom_zero").attr("title", $.resource("ZoomImage_zoomToOrigSize")); modalFG.find(".iviewer_zoom_status").attr("title", $.resource("ZoomImage_zoomedTo")); } }); }// END modalLayer_ShowIviewerZoomImage

$(document).ready(function {// add to the resource string dictionary  $.extend(true, $.jI18n, { en: { ZoomImage_toolTipMoveable : "After zooming into (e.g. using the mouse wheel), images can be moved.", ZoomImage_zoomedTo :       "Current zoom factor", ZoomImage_fitToWindow :    "Fit into the window", ZoomImage_zoomIn :         "Zoom in", ZoomImage_zoomOut :        "Zoom out", ZoomImage_zoomToOrigSize : "Set to original size (1:1)", ZoomImage_removeCaption:   "Hide the image caption" },   de: { ZoomImage_toolTipMoveable : "Nach Vergrößerung (z.B. durch Mausrad) kann man das Bild verschieben.", ZoomImage_zoomedTo :       "Vergrößerungsfaktor", ZoomImage_fitToWindow :    "In Fenster einpassen", ZoomImage_zoomIn :         "Vergrößern", ZoomImage_zoomOut :        "Verkleinern", ZoomImage_zoomToOrigSize : "Zeige in Originalgröße (1:1)", ZoomImage_removeCaption:   "Bildbeschriftung verbergen" } }); });