* imgAreaSelect jQuery plugin
* Copyright (c) 2008-2013 Michal Wojciechowski (odyniec.net)
* Dual licensed under the MIT (MIT-LICENSE.txt)
* and GPL (GPL-LICENSE.txt) licenses.
* https://github.com/odyniec/imgareaselect
* Math functions will be used extensively, so it's convenient to make a few
* Create a new HTML div element
* @return A jQuery object representing the new element
* imgAreaSelect initialization
* A HTML image element to attach the plugin to
$.imgAreaSelect = function (img, options) {
/* jQuery object representing the image */
/* Has the image finished loading? */
$border = div().add(div()).add(div()).add(div()),
/* Outer area (four divs) */
$outer = div().add(div()).add(div()).add(div()),
/* Handles (empty by default, initialized in setOptions()) */
* Additional element to work around a cursor problem in Opera
/* Image position (relative to viewport) */
/* Image offset (as returned by .offset()) */
imgOfs = { left: 0, top: 0 },
/* Image dimensions (as returned by .width() and .height()) */
* jQuery object representing the parent element that the plugin
* elements are appended to
/* Parent element offset (as returned by .offset()) */
parOfs = { left: 0, top: 0 },
/* Base z-index for plugin elements */
/* Plugin elements position */
/* X/Y coordinates of the starting point for move/resize operations */
/* Horizontal and vertical scaling factors */
/* Current resize mode ("nw", "se", etc.) */
/* Selection area constraints */
minWidth, minHeight, maxWidth, maxHeight,
/* Aspect ratio to maintain (floating point number) */
/* Are the plugin elements currently displayed? */
/* Current selection (relative to parent element) */
/* Current selection (relative to scaled image) */
selection = { x1: 0, y1: 0, x2: 0, y2: 0, width: 0, height: 0 },
docElem = document.documentElement,
ua = navigator.userAgent,
/* Various helper variables used throughout the code */
$p, d, i, o, w, h, adjusted;
* Translate selection coordinates (relative to scaled image) to viewport
* coordinates (relative to parent element)
* Translate selection X to viewport X
return x + imgOfs.left - parOfs.left;
* Translate selection Y to viewport Y
return y + imgOfs.top - parOfs.top;
* Translate viewport coordinates to selection coordinates
* Translate viewport X to selection X
return x - imgOfs.left + parOfs.left;
* Translate viewport Y to selection Y
return y - imgOfs.top + parOfs.top;
* Translate event coordinates (relative to document) to viewport
* Get event X and translate it to viewport X
return max(event.pageX || 0, touchCoords(event).x) - parOfs.left;
* Get event Y and translate it to viewport Y
return max(event.pageY || 0, touchCoords(event).y) - parOfs.top;
* Get X and Y coordinates of a touch event
* @return Coordinates object
function touchCoords(event) {
var oev = event.originalEvent || {};
if (oev.touches && oev.touches.length)
return { x: oev.touches[0].pageX, y: oev.touches[0].pageY };
* Get the current selection
* If set to <code>true</code>, scaling is not applied to the
* @return Selection object
function getSelection(noScale) {
var sx = noScale || scaleX, sy = noScale || scaleY;
return { x1: round(selection.x1 * sx),
y1: round(selection.y1 * sy),
x2: round(selection.x2 * sx),
y2: round(selection.y2 * sy),
width: round(selection.x2 * sx) - round(selection.x1 * sx),
height: round(selection.y2 * sy) - round(selection.y1 * sy) };
* Set the current selection
* X coordinate of the upper left corner of the selection area
* Y coordinate of the upper left corner of the selection area
* X coordinate of the lower right corner of the selection area
* Y coordinate of the lower right corner of the selection area
* If set to <code>true</code>, scaling is not applied to the
function setSelection(x1, y1, x2, y2, noScale) {
var sx = noScale || scaleX, sy = noScale || scaleY;
selection.width = selection.x2 - selection.x1;
selection.height = selection.y2 - selection.y1;
* Recalculate image and parent offsets
* Do not adjust if image has not yet loaded or if width is not a
* positive number. The latter might happen when imgAreaSelect is put
* on a parent element which is then hidden.
if (!imgLoaded || !$img.width())
* Get image offset. The .offset() method returns float values, so they
imgOfs = { left: round($img.offset().left), top: round($img.offset().top) };
/* Get image dimensions */
imgWidth = $img.innerWidth();
imgHeight = $img.innerHeight();
imgOfs.top += ($img.outerHeight() - imgHeight) >> 1;
imgOfs.left += ($img.outerWidth() - imgWidth) >> 1;
/* Set minimum and maximum selection area dimensions */
minWidth = round(options.minWidth / scaleX) || 0;
minHeight = round(options.minHeight / scaleY) || 0;
maxWidth = round(min(options.maxWidth / scaleX || 1<<24, imgWidth));
maxHeight = round(min(options.maxHeight / scaleY || 1<<24, imgHeight));
* Workaround for jQuery 1.3.2 incorrect offset calculation, originally
* observed in Safari 3. Firefox 2 is also affected.
if ($().jquery == '1.3.2' && position == 'fixed' &&
!docElem['getBoundingClientRect'])
imgOfs.top += max(document.body.scrollTop, docElem.scrollTop);
imgOfs.left += max(document.body.scrollLeft, docElem.scrollLeft);
/* Determine parent element offset */
parOfs = /absolute|relative/.test($parent.css('position')) ?
{ left: round($parent.offset().left) - $parent.scrollLeft(),
top: round($parent.offset().top) - $parent.scrollTop() } :
{ left: $(document).scrollLeft(), top: $(document).scrollTop() } :
* Check if selection area is within image boundaries, adjust if
if (selection.x2 > imgWidth || selection.y2 > imgHeight)
* If set to <code>false</code>, this instance's keypress
* event handler is not activated
function update(resetKeyPress) {
/* If plugin elements are hidden, do nothing */
* Set the position and size of the container box and the selection area
$box.css({ left: viewX(selection.x1), top: viewY(selection.y1) })
.add($area).width(w = selection.width).height(h = selection.height);
* Reset the position of selection area, borders, and handles (IE6/IE7
* position them incorrectly if we don't do this)
$area.add($border).add($handles).css({ left: 0, top: 0 });
/* Set border dimensions */
.width(max(w - $border.outerWidth() + $border.innerWidth(), 0))
.height(max(h - $border.outerHeight() + $border.innerHeight(), 0));
/* Arrange the outer area elements */
$($outer[0]).css({ left: left, top: top,
width: selection.x1, height: imgHeight });
$($outer[1]).css({ left: left + selection.x1, top: top,
width: w, height: selection.y1 });
$($outer[2]).css({ left: left + selection.x2, top: top,
width: imgWidth - selection.x2, height: imgHeight });
$($outer[3]).css({ left: left + selection.x1, top: top + selection.y2,
width: w, height: imgHeight - selection.y2 });
w -= $handles.outerWidth();
h -= $handles.outerHeight();
switch ($handles.length) {
$($handles[4]).css({ left: w >> 1 });
$($handles[5]).css({ left: w, top: h >> 1 });
$($handles[6]).css({ left: w >> 1, top: h });
$($handles[7]).css({ top: h >> 1 });
$handles.slice(1,3).css({ left: w });
$handles.slice(2,4).css({ top: h });
if (resetKeyPress !== false) {
* Need to reset the document keypress event handler -- unbind the
if ($.imgAreaSelect.onKeyPress != docKeyPress)
$(document).off($.imgAreaSelect.keyPress,
$.imgAreaSelect.onKeyPress);
* Set the document keypress event handler to this instance's
$(document).on( $.imgAreaSelect.keyPress, function() {
$.imgAreaSelect.onKeyPress = docKeyPress;
* Internet Explorer displays 1px-wide dashed borders incorrectly by
* filling the spaces between dashes with white. Toggling the margin
* property between 0 and "auto" fixes this in IE6 and IE7 (IE8 is still
* broken). This workaround is not perfect, as it requires setTimeout()
* and thus causes the border to flicker a bit, but I haven't found a
* Note: This only happens with CSS borders, set with the borderWidth,
* borderOpacity, borderColor1, and borderColor2 options (which are now
* deprecated). Borders created with GIF background images are fine.
if (msie && $border.outerWidth() - $border.innerWidth() == 2) {
$border.css('margin', 0);
setTimeout(function () { $border.css('margin', 'auto'); }, 0);
* Do the complete update sequence: recalculate offsets, update the
* elements, and set the correct values of x1, y1, x2, and y2.
* If set to <code>false</code>, this instance's keypress
* event handler is not activated
function doUpdate(resetKeyPress) {
x1 = viewX(selection.x1); y1 = viewY(selection.y1);
x2 = viewX(selection.x2); y2 = viewY(selection.y2);
* Hide or fade out an element (or multiple elements)
* A jQuery object containing the element(s) to hide/fade out
* Callback function to be called when fadeOut() completes
function hide($elem, fn) {
options.fadeSpeed ? $elem.fadeOut(options.fadeSpeed, fn) : $elem.hide();
* Selection area mousemove event handler
function areaMouseMove(event) {
var x = selX(evX(event)) - selection.x1,
y = selY(evY(event)) - selection.y1;
$box.one('mouseout', function () { adjusted = false; });
/* Clear the resize mode */
* Check if the mouse pointer is over the resize margin area and set
* the resize mode accordingly
if (y <= options.resizeMargin)
else if (y >= selection.height - options.resizeMargin)
if (x <= options.resizeMargin)
else if (x >= selection.width - options.resizeMargin)
$box.css('cursor', resize ? resize + '-resize' :
options.movable ? 'move' : '');
* Document mouseup event handler
function docMouseUp(event) {
/* Set back the default cursor */
$('body').css('cursor', '');
* If autoHide is enabled, or if the selection has zero width/height,