* hide the selection and the outer area
if (options.autoHide || selection.width * selection.height == 0)
hide($box.add($outer), function () { $(this).hide(); });
$(document).off('mousemove touchmove', selectingMouseMove);
$box.on('mousemove touchmove', areaMouseMove);
options.onSelectEnd(img, getSelection());
* Selection area mousedown event handler
function areaMouseDown(event) {
if (event.type == 'mousedown' && event.which != 1) return false;
* With mobile browsers, there is no "moving the pointer over" action,
* so we need to simulate one mousemove event happening prior to
/* Resize mode is in effect */
$('body').css('cursor', resize + '-resize');
x1 = viewX(selection[/w/.test(resize) ? 'x2' : 'x1']);
y1 = viewY(selection[/n/.test(resize) ? 'y2' : 'y1']);
$(document).on('mousemove touchmove', selectingMouseMove)
.one('mouseup touchend', docMouseUp);
$box.off('mousemove touchmove', areaMouseMove);
else if (options.movable) {
startX = left + selection.x1 - evX(event);
startY = top + selection.y1 - evY(event);
$box.off('mousemove touchmove', areaMouseMove);
$(document).on('mousemove touchmove', movingMouseMove)
.one('mouseup touchend', function () {
options.onSelectEnd(img, getSelection());
$(document).off('mousemove touchmove', movingMouseMove);
$box.on('mousemove touchmove', areaMouseMove);
* Adjust the x2/y2 coordinates to maintain aspect ratio (if defined)
* If set to <code>true</code>, calculate x2 first. Otherwise,
function fixAspectRatio(xFirst) {
x2 = max(left, min(left + imgWidth,
x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1)));
y2 = round(max(top, min(top + imgHeight,
y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1))));
y2 = max(top, min(top + imgHeight,
y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1)));
x2 = round(max(left, min(left + imgWidth,
x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1))));
* Resize the selection area respecting the minimum/maximum dimensions and
* Make sure the top left corner of the selection area stays within
* image boundaries (it might not if the image source was dynamically
x1 = min(x1, left + imgWidth);
y1 = min(y1, top + imgHeight);
if (abs(x2 - x1) < minWidth) {
/* Selection width is smaller than minWidth */
x2 = x1 - minWidth * (x2 < x1 || -1);
else if (x2 > left + imgWidth)
x1 = left + imgWidth - minWidth;
if (abs(y2 - y1) < minHeight) {
/* Selection height is smaller than minHeight */
y2 = y1 - minHeight * (y2 < y1 || -1);
else if (y2 > top + imgHeight)
y1 = top + imgHeight - minHeight;
x2 = max(left, min(x2, left + imgWidth));
y2 = max(top, min(y2, top + imgHeight));
fixAspectRatio(abs(x2 - x1) < abs(y2 - y1) * aspectRatio);
if (abs(x2 - x1) > maxWidth) {
/* Selection width is greater than maxWidth */
x2 = x1 - maxWidth * (x2 < x1 || -1);
if (abs(y2 - y1) > maxHeight) {
/* Selection height is greater than maxHeight */
y2 = y1 - maxHeight * (y2 < y1 || -1);
selection = { x1: selX(min(x1, x2)), x2: selX(max(x1, x2)),
y1: selY(min(y1, y2)), y2: selY(max(y1, y2)),
width: abs(x2 - x1), height: abs(y2 - y1) };
options.onSelectChange(img, getSelection());
* Mousemove event handler triggered when the user is selecting an area
function selectingMouseMove(event) {
x2 = /w|e|^$/.test(resize) || aspectRatio ? evX(event) : viewX(selection.x2);
y2 = /n|s|^$/.test(resize) || aspectRatio ? evY(event) : viewY(selection.y2);
* Move the selection area
function doMove(newX1, newY1) {
x2 = (x1 = newX1) + selection.width;
y2 = (y1 = newY1) + selection.height;
$.extend(selection, { x1: selX(x1), y1: selY(y1), x2: selX(x2),
options.onSelectChange(img, getSelection());
* Mousemove event handler triggered when the selection area is being moved
function movingMouseMove(event) {
x1 = max(left, min(startX + evX(event), left + imgWidth - selection.width));
y1 = max(top, min(startY + evY(event), top + imgHeight - selection.height));
function startSelection() {
$(document).off('mousemove touchmove', startSelection);
if (!$outer.is(':visible'))
/* Show the plugin elements */
$box.add($outer).hide().fadeIn(options.fadeSpeed||0);
$(document).off('mouseup touchend', cancelSelection)
.on('mousemove touchmove', selectingMouseMove)
.one('mouseup touchend', docMouseUp);
$box.off('mousemove touchmove', areaMouseMove);
options.onSelectStart(img, getSelection());
function cancelSelection() {
$(document).off('mousemove touchmove', startSelection)
.off('mouseup touchend', cancelSelection);
setSelection(selX(x1), selY(y1), selX(x1), selY(y1));
/* If this is an API call, callback functions should not be triggered */
if (!(this instanceof $.imgAreaSelect)) {
options.onSelectChange(img, getSelection());
options.onSelectEnd(img, getSelection());
* Image mousedown event handler
function imgMouseDown(event) {
/* Ignore the event if animation is in progress */
if (event.which > 1 || $outer.is(':animated')) return false;
startX = x1 = evX(event);
startY = y1 = evY(event);
/* Selection will start when the mouse is moved */
$(document).on({ 'mousemove touchmove': startSelection,
'mouseup touchend': cancelSelection });
* Window resize event handler
function windowResize() {
* Image load event handler. This is the final part of the initialization
setOptions(options = $.extend({
classPrefix: 'imgareaselect',
onSelectStart: function () {},
onSelectChange: function () {},
onSelectEnd: function () {}
$box.add($outer).css({ visibility: '' });
$box.add($outer).hide().fadeIn(options.fadeSpeed||0);
* Call the onInit callback. The setTimeout() call is used to ensure
* that the plugin has been fully initialized and the object instance is
* available (so that it can be obtained in the callback).
setTimeout(function () { options.onInit(img, getSelection()); }, 0);
* Document keypress event handler
var docKeyPress = function(event) {
var k = options.keys, d, t, key = event.keyCode;
d = !isNaN(k.alt) && (event.altKey || event.originalEvent.altKey) ? k.alt :
!isNaN(k.ctrl) && event.ctrlKey ? k.ctrl :
!isNaN(k.shift) && event.shiftKey ? k.shift :
!isNaN(k.arrows) ? k.arrows : 10;
if (k.arrows == 'resize' || (k.shift == 'resize' && event.shiftKey) ||
(k.ctrl == 'resize' && event.ctrlKey) ||
(k.alt == 'resize' && (event.altKey || event.originalEvent.altKey)))
doMove(max(x1 - d, left), y1);
doMove(x1, max(y1 - d, top));
doMove(x1 + min(d, imgWidth - selX(x2)), y1);
doMove(x1, y1 + min(d, imgHeight - selY(y2)));
* Apply style options to plugin element (or multiple elements)
* A jQuery object representing the element(s) to style
* An object that maps option names to corresponding CSS
function styleOptions($elem, props) {
for (var option in props)
if (options[option] !== undefined)
$elem.css(props[option], options[option]);
function setOptions(newOptions) {
($parent = $(newOptions.parent)).append($box.add($outer));
/* Merge the new options with the existing ones */
$.extend(options, newOptions);
if (newOptions.handles != null) {
/* Recreate selection area handles */
i = newOptions.handles ? newOptions.handles == 'corners' ? 4 : 8 : 0;
$handles = $handles.add(div());
/* Add a class to handles and set the CSS properties */
$handles.addClass(options.classPrefix + '-handle').css({
* The font-size property needs to be set to zero, otherwise
* Internet Explorer makes the handles too large
* If handle width/height has not been set with CSS rules, set the
if (!parseInt($handles.css('width')) >= 0)
$handles.width(5).height(5);
* If the borderWidth option is in use, add a solid border to
if (o = options.borderWidth)
$handles.css({ borderWidth: o, borderStyle: 'solid' });
/* Apply other style options */
styleOptions($handles, { borderColor1: 'border-color',
borderColor2: 'background-color',
borderOpacity: 'opacity' });
/* Calculate scale factors */
scaleX = options.imageWidth / imgWidth || 1;
scaleY = options.imageHeight / imgHeight || 1;
if (newOptions.x1 != null) {
setSelection(newOptions.x1, newOptions.y1, newOptions.x2,
newOptions.show = !newOptions.hide;
/* Enable keyboard support */
options.keys = $.extend({ shift: 1, ctrl: 'resize' },
/* Add classes to plugin elements */
$outer.addClass(options.classPrefix + '-outer');
$area.addClass(options.classPrefix + '-selection');
$($border[i-1]).addClass(options.classPrefix + '-border' + i);
/* Apply style options */
styleOptions($area, { selectionColor: 'background-color',
selectionOpacity: 'opacity' });
styleOptions($border, { borderOpacity: 'opacity',
borderWidth: 'border-width' });
styleOptions($outer, { outerColor: 'background-color',
outerOpacity: 'opacity' });
if (o = options.borderColor1)
$($border[0]).css({ borderStyle: 'solid', borderColor: o });
if (o = options.borderColor2)
$($border[1]).css({ borderStyle: 'dashed', borderColor: o });
/* Append all the selection area elements to the container box */
$box.append($area.add($border).add($areaOpera)).append($handles);
if (o = ($outer.css('filter')||'').match(/opacity=(\d+)/))
$outer.css('opacity', o[1]/100);
if (o = ($border.css('filter')||'').match(/opacity=(\d+)/))
$border.css('opacity', o[1]/100);