1/* 2 * imgAreaSelect jQuery plugin 3 * version 0.9.11-rc.1 4 * 5 * Copyright (c) 2008-2013 Michal Wojciechowski (odyniec.net) 6 * 7 * Dual licensed under the MIT (MIT-LICENSE.txt) 8 * and GPL (GPL-LICENSE.txt) licenses. 9 * 10 * http://odyniec.net/projects/imgareaselect/ 11 * 12 */ 13 14(function($) { 15 16 /* 17 * Math functions will be used extensively, so it's convenient to make a few 18 * shortcuts 19 */ 20 var abs = Math.abs, 21 max = Math.max, 22 min = Math.min, 23 round = Math.round; 24 25 /** 26 * Create a new HTML div element 27 * 28 * @return A jQuery object representing the new element 29 */ 30 function div() { 31 return $('<div/>'); 32 } 33 34 /** 35 * imgAreaSelect initialization 36 * 37 * @param img 38 * A HTML image element to attach the plugin to 39 * @param options 40 * An options object 41 */ 42 $.imgAreaSelect = function (img, options) { 43 var 44 /* jQuery object representing the image */ 45 $img = $(img), 46 47 /* Has the image finished loading? */ 48 imgLoaded, 49 50 /* Plugin elements */ 51 52 /* Container box */ 53 $box = div(), 54 /* Selection area */ 55 $area = div(), 56 /* Border (four divs) */ 57 $border = div().add(div()).add(div()).add(div()), 58 /* Outer area (four divs) */ 59 $outer = div().add(div()).add(div()).add(div()), 60 /* Handles (empty by default, initialized in setOptions()) */ 61 $handles = $([]), 62 63 /* 64 * Additional element to work around a cursor problem in Opera 65 * (explained later) 66 */ 67 $areaOpera, 68 69 /* Image position (relative to viewport) */ 70 left, top, 71 72 /* Image offset (as returned by .offset()) */ 73 imgOfs = { left: 0, top: 0 }, 74 75 /* Image dimensions (as returned by .width() and .height()) */ 76 imgWidth, imgHeight, 77 78 /* 79 * jQuery object representing the parent element that the plugin 80 * elements are appended to 81 */ 82 $parent, 83 84 /* Parent element offset (as returned by .offset()) */ 85 parOfs = { left: 0, top: 0 }, 86 87 /* Base z-index for plugin elements */ 88 zIndex = 0, 89 90 /* Plugin elements position */ 91 position = 'absolute', 92 93 /* X/Y coordinates of the starting point for move/resize operations */ 94 startX, startY, 95 96 /* Horizontal and vertical scaling factors */ 97 scaleX, scaleY, 98 99 /* Current resize mode ("nw", "se", etc.) */ 100 resize, 101 102 /* Selection area constraints */ 103 minWidth, minHeight, maxWidth, maxHeight, 104 105 /* Aspect ratio to maintain (floating point number) */ 106 aspectRatio, 107 108 /* Are the plugin elements currently displayed? */ 109 shown, 110 111 /* Current selection (relative to parent element) */ 112 x1, y1, x2, y2, 113 114 /* Current selection (relative to scaled image) */ 115 selection = { x1: 0, y1: 0, x2: 0, y2: 0, width: 0, height: 0 }, 116 117 /* Document element */ 118 docElem = document.documentElement, 119 120 /* User agent */ 121 ua = navigator.userAgent, 122 123 /* Various helper variables used throughout the code */ 124 $p, d, i, o, w, h, adjusted; 125 126 /* 127 * Translate selection coordinates (relative to scaled image) to viewport 128 * coordinates (relative to parent element) 129 */ 130 131 /** 132 * Translate selection X to viewport X 133 * 134 * @param x 135 * Selection X 136 * @return Viewport X 137 */ 138 function viewX(x) { 139 return x + imgOfs.left - parOfs.left; 140 } 141 142 /** 143 * Translate selection Y to viewport Y 144 * 145 * @param y 146 * Selection Y 147 * @return Viewport Y 148 */ 149 function viewY(y) { 150 return y + imgOfs.top - parOfs.top; 151 } 152 153 /* 154 * Translate viewport coordinates to selection coordinates 155 */ 156 157 /** 158 * Translate viewport X to selection X 159 * 160 * @param x 161 * Viewport X 162 * @return Selection X 163 */ 164 function selX(x) { 165 return x - imgOfs.left + parOfs.left; 166 } 167 168 /** 169 * Translate viewport Y to selection Y 170 * 171 * @param y 172 * Viewport Y 173 * @return Selection Y 174 */ 175 function selY(y) { 176 return y - imgOfs.top + parOfs.top; 177 } 178 179 /* 180 * Translate event coordinates (relative to document) to viewport 181 * coordinates 182 */ 183 184 /** 185 * Get event X and translate it to viewport X 186 * 187 * @param event 188 * The event object 189 * @return Viewport X 190 */ 191 function evX(event) { 192 return event.pageX - parOfs.left; 193 } 194 195 /** 196 * Get event Y and translate it to viewport Y 197 * 198 * @param event 199 * The event object 200 * @return Viewport Y 201 */ 202 function evY(event) { 203 return event.pageY - parOfs.top; 204 } 205 206 /** 207 * Get the current selection 208 * 209 * @param noScale 210 * If set to <code>true</code>, scaling is not applied to the 211 * returned selection 212 * @return Selection object 213 */ 214 function getSelection(noScale) { 215 var sx = noScale || scaleX, sy = noScale || scaleY; 216 217 return { x1: round(selection.x1 * sx), 218 y1: round(selection.y1 * sy), 219 x2: round(selection.x2 * sx), 220 y2: round(selection.y2 * sy), 221 width: round(selection.x2 * sx) - round(selection.x1 * sx), 222 height: round(selection.y2 * sy) - round(selection.y1 * sy) }; 223 } 224 225 /** 226 * Set the current selection 227 * 228 * @param x1 229 * X coordinate of the upper left corner of the selection area 230 * @param y1 231 * Y coordinate of the upper left corner of the selection area 232 * @param x2 233 * X coordinate of the lower right corner of the selection area 234 * @param y2 235 * Y coordinate of the lower right corner of the selection area 236 * @param noScale 237 * If set to <code>true</code>, scaling is not applied to the 238 * new selection 239 */ 240 function setSelection(x1, y1, x2, y2, noScale) { 241 var sx = noScale || scaleX, sy = noScale || scaleY; 242 243 selection = { 244 x1: round(x1 / sx || 0),
245 y1: round(y1 / sy || 0), 246 x2: round(x2 / sx || 0), 247 y2: round(y2 / sy || 0) 248 }; 249 250 selection.width = selection.x2 - selection.x1; 251 selection.height = selection.y2 - selection.y1; 252 } 253 254 /** Recalculate image and parent offsets */ 255 function adjust() { 256 /* 257 * Do not adjust if image has not yet loaded or if width is not a 258 * positive number. The latter might happen when imgAreaSelect is put 259 * on a parent element which is then hidden. 260 */ 261 if (!imgLoaded || !$img.width()) 262 return; 263 264 /* 265 * Get image offset. The .offset() method returns float values, so they 266 * need to be rounded. 267 */ 268 imgOfs = { left: round($img.offset().left), top: round($img.offset().top) }; 269 270 /* Get image dimensions */ 271 imgWidth = $img.innerWidth(); 272 imgHeight = $img.innerHeight(); 273 274 imgOfs.top += ($img.outerHeight() - imgHeight) >> 1; 275 imgOfs.left += ($img.outerWidth() - imgWidth) >> 1; 276 277 /* Set minimum and maximum selection area dimensions */ 278 minWidth = round(options.minWidth / scaleX) || 0; 279 minHeight = round(options.minHeight / scaleY) || 0; 280 maxWidth = round(min(options.maxWidth / scaleX || 1<<24, imgWidth)); 281 maxHeight = round(min(options.maxHeight / scaleY || 1<<24, imgHeight)); 282 283 /* 284 * Workaround for jQuery 1.3.2 incorrect offset calculation, originally 285 * observed in Safari 3. Firefox 2 is also affected. 286 */ 287 if ($().jquery == '1.3.2' && position == 'fixed' && 288 !docElem['getBoundingClientRect']) 289 { 290 imgOfs.top += max(document.body.scrollTop, docElem.scrollTop); 291 imgOfs.left += max(document.body.scrollLeft, docElem.scrollLeft); 292 } 293 294 /* Determine parent element offset */ 295 parOfs = /absolute|relative/.test($parent.css('position')) ? 296 { left: round($parent.offset().left) - $parent.scrollLeft(), 297 top: round($parent.offset().top) - $parent.scrollTop() } : 298 position == 'fixed' ? 299 { left: $(document).scrollLeft(), top: $(document).scrollTop() } : 300 { left: 0, top: 0 }; 301 302 left = viewX(0); 303 top = viewY(0); 304 305 /* 306 * Check if selection area is within image boundaries, adjust if 307 * necessary 308 */ 309 if (selection.x2 > imgWidth || selection.y2 > imgHeight) 310 doResize(); 311 } 312 313 /** 314 * Update plugin elements 315 * 316 * @param resetKeyPress 317 * If set to <code>false</code>, this instance's keypress 318 * event handler is not activated 319 */ 320 function update(resetKeyPress) { 321 /* If plugin elements are hidden, do nothing */ 322 if (!shown) return; 323 324 /* 325 * Set the position and size of the container box and the selection area 326 * inside it 327 */ 328 $box.css({ left: viewX(selection.x1), top: viewY(selection.y1) }) 329 .add($area).width(w = selection.width).height(h = selection.height); 330 331 /* 332 * Reset the position of selection area, borders, and handles (IE6/IE7 333 * position them incorrectly if we don't do this) 334 */ 335 $area.add($border).add($handles).css({ left: 0, top: 0 }); 336 337 /* Set border dimensions */ 338 $border 339 .width(max(w - $border.outerWidth() + $border.innerWidth(), 0)) 340 .height(max(h - $border.outerHeight() + $border.innerHeight(), 0)); 341 342 /* Arrange the outer area elements */ 343 $($outer[0]).css({ left: left, top: top, 344 width: selection.x1, height: imgHeight }); 345 $($outer[1]).css({ left: left + selection.x1, top: top, 346 width: w, height: selection.y1 }); 347 $($outer[2]).css({ left: left + selection.x2, top: top, 348 width: imgWidth - selection.x2, height: imgHeight }); 349 $($outer[3]).css({ left: left + selection.x1, top: top + selection.y2, 350 width: w, height: imgHeight - selection.y2 }); 351 352 w -= $handles.outerWidth(); 353 h -= $handles.outerHeight(); 354 355 /* Arrange handles */ 356 switch ($handles.length) { 357 case 8: 358 $($handles[4]).css({ left: w >> 1 }); 359 $($handles[5]).css({ left: w, top: h >> 1 }); 360 $($handles[6]).css({ left: w >> 1, top: h }); 361 $($handles[7]).css({ top: h >> 1 }); 362 case 4: 363 $handles.slice(1,3).css({ left: w }); 364 $handles.slice(2,4).css({ top: h }); 365 } 366 367 if (resetKeyPress !== false) { 368 /* 369 * Need to reset the document keypress event handler -- unbind the 370 * current handler 371 */ 372 if ($.imgAreaSelect.onKeyPress != docKeyPress) 373 $(document).unbind($.imgAreaSelect.keyPress, 374 $.imgAreaSelect.onKeyPress); 375 376 if (options.keys) 377 /* 378 * Set the document keypress event handler to this instance's 379 * docKeyPress() function 380 */ 381 $(document)[$.imgAreaSelect.keyPress]( 382 $.imgAreaSelect.onKeyPress = docKeyPress); 383 } 384 385 /* 386 * Internet Explorer displays 1px-wide dashed borders incorrectly by 387 * filling the spaces between dashes with white. Toggling the margin 388 * property between 0 and "auto" fixes this in IE6 and IE7 (IE8 is still 389 * broken). This workaround is not perfect, as it requires setTimeout() 390 * and thus causes the border to flicker a bit, but I haven't found a 391 * better solution. 392 * 393 * Note: This only happens with CSS borders, set with the borderWidth, 394 * borderOpacity, borderColor1, and borderColor2 options (which are now 395 * deprecated). Borders created with GIF background images are fine. 396 */ 397 if (msie && $border.outerWidth() - $border.innerWidth() == 2) { 398 $border.css('margin', 0); 399 setTimeout(function () { $border.css('margin', 'auto'); }, 0); 400 } 401 } 402 403 /** 404 * Do the complete update sequence: recalculate offsets, update the 405 * elements, and set the correct values of x1, y1, x2, and y2. 406 * 407 * @param resetKeyPress 408 * If set to <code>false</code>, this instance's keypress 409 * event handler is not activated 410 */ 411 function doUpdate(resetKeyPress) {
412 adjust(); 413 update(resetKeyPress); 414 x1 = viewX(selection.x1); y1 = viewY(selection.y1); 415 x2 = viewX(selection.x2); y2 = viewY(selection.y2); 416 } 417 418 /** 419 * Hide or fade out an element (or multiple elements) 420 * 421 * @param $elem 422 * A jQuery object containing the element(s) to hide/fade out 423 * @param fn 424 * Callback function to be called when fadeOut() completes 425 */ 426 function hide($elem, fn) { 427 options.fadeSpeed ? $elem.fadeOut(options.fadeSpeed, fn) : $elem.hide(); 428 } 429 430 /** 431 * Selection area mousemove event handler 432 * 433 * @param event 434 * The event object 435 */ 436 function areaMouseMove(event) { 437 var x = selX(evX(event)) - selection.x1, 438 y = selY(evY(event)) - selection.y1; 439 440 if (!adjusted) { 441 adjust(); 442 adjusted = true; 443 444 $box.one('mouseout', function () { adjusted = false; }); 445 } 446 447 /* Clear the resize mode */ 448 resize = ''; 449 450 if (options.resizable) { 451 /* 452 * Check if the mouse pointer is over the resize margin area and set 453 * the resize mode accordingly 454 */ 455 if (y <= options.resizeMargin) 456 resize = 'n'; 457 else if (y >= selection.height - options.resizeMargin) 458 resize = 's'; 459 if (x <= options.resizeMargin) 460 resize += 'w'; 461 else if (x >= selection.width - options.resizeMargin) 462 resize += 'e'; 463 } 464 465 $box.css('cursor', resize ? resize + '-resize' : 466 options.movable ? 'move' : ''); 467 if ($areaOpera) 468 $areaOpera.toggle(); 469 } 470 471 /** 472 * Document mouseup event handler 473 * 474 * @param event 475 * The event object 476 */ 477 function docMouseUp(event) { 478 /* Set back the default cursor */ 479 $('body').css('cursor', ''); 480 /* 481 * If autoHide is enabled, or if the selection has zero width/height, 482 * hide the selection and the outer area 483 */ 484 if (options.autoHide || selection.width * selection.height == 0) 485 hide($box.add($outer), function () { $(this).hide(); }); 486 487 $(document).unbind('mousemove', selectingMouseMove); 488 $box.mousemove(areaMouseMove); 489 490 options.onSelectEnd(img, getSelection()); 491 } 492 493 /** 494 * Selection area mousedown event handler 495 * 496 * @param event 497 * The event object 498 * @return false 499 */ 500 function areaMouseDown(event) { 501 if (event.which != 1) return false; 502 503 adjust(); 504 505 if (resize) { 506 /* Resize mode is in effect */ 507 $('body').css('cursor', resize + '-resize'); 508 509 x1 = viewX(selection[/w/.test(resize) ? 'x2' : 'x1']); 510 y1 = viewY(selection[/n/.test(resize) ? 'y2' : 'y1']); 511 512 $(document).mousemove(selectingMouseMove) 513 .one('mouseup', docMouseUp); 514 $box.unbind('mousemove', areaMouseMove); 515 } 516 else if (options.movable) { 517 startX = left + selection.x1 - evX(event); 518 startY = top + selection.y1 - evY(event); 519 520 $box.unbind('mousemove', areaMouseMove); 521 522 $(document).mousemove(movingMouseMove) 523 .one('mouseup', function () { 524 options.onSelectEnd(img, getSelection()); 525 526 $(document).unbind('mousemove', movingMouseMove); 527 $box.mousemove(areaMouseMove); 528 }); 529 } 530 else 531 $img.mousedown(event); 532 533 return false; 534 } 535 536 /** 537 * Adjust the x2/y2 coordinates to maintain aspect ratio (if defined) 538 * 539 * @param xFirst 540 * If set to <code>true</code>, calculate x2 first. Otherwise, 541 * calculate y2 first. 542 */ 543 function fixAspectRatio(xFirst) { 544 if (aspectRatio) 545 if (xFirst) { 546 x2 = max(left, min(left + imgWidth, 547 x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1))); 548 y2 = round(max(top, min(top + imgHeight, 549 y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1)))); 550 x2 = round(x2); 551 } 552 else { 553 y2 = max(top, min(top + imgHeight, 554 y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1))); 555 x2 = round(max(left, min(left + imgWidth, 556 x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1)))); 557 y2 = round(y2); 558 } 559 } 560 561 /** 562 * Resize the selection area respecting the minimum/maximum dimensions and 563 * aspect ratio 564 */ 565 function doResize() { 566 /* 567 * Make sure the top left corner of the selection area stays within 568 * image boundaries (it might not if the image source was dynamically 569 * changed). 570 */ 571 572 x1 = min(x1, left + imgWidth); 573 y1 = min(y1, top + imgHeight); 574
575 if (abs(x2 - x1) < minWidth) { 576 /* Selection width is smaller than minWidth */ 577 x2 = x1 - minWidth * (x2 < x1 || -1); 578 579 if (x2 < left) 580 x1 = left + minWidth; 581 else if (x2 > left + imgWidth) 582 x1 = left + imgWidth - minWidth; 583 } 584 585 if (abs(y2 - y1) < minHeight) { 586 /* Selection height is smaller than minHeight */ 587 y2 = y1 - minHeight * (y2 < y1 || -1); 588 589 if (y2 < top) 590 y1 = top + minHeight; 591 else if (y2 > top + imgHeight) 592 y1 = top + imgHeight - minHeight; 593 } 594 595 if (sMode == MODE_CROP) { 596 x2 = max(left, min(x2, left + imgWidth)); 597 y2 = max(top, min(y2, top + imgHeight)); 598 } else if (maxWidth == imgWidth || maxHeight == maxHeight) { 599 maxWidth = maxWidth * 100; 600 maxHeight = maxHeight * 100 601 } 602 603 fixAspectRatio(abs(x2 - x1) < abs(y2 - y1) * aspectRatio); 604 605 if (abs(x2 - x1) > maxWidth) { 606 /* Selection width is greater than maxWidth */ 607 x2 = x1 - maxWidth * (x2 < x1 || -1); 608 fixAspectRatio(); 609 } 610 611 if (abs(y2 - y1) > maxHeight) { 612 /* Selection height is greater than maxHeight */ 613 y2 = y1 - maxHeight * (y2 < y1 || -1); 614 fixAspectRatio(true); 615 } 616 617 selection = { x1: selX(min(x1, x2)), x2: selX(max(x1, x2)), 618 y1: selY(min(y1, y2)), y2: selY(max(y1, y2)), 619 width: abs(x2 - x1), height: abs(y2 - y1) }; 620 621 if (sMode == MODE_RESIZE) { 622 resizeImage(); 623 } 624 update(); 625 626 options.onSelectChange(img, getSelection()); 627 } 628 629 function resizeImage () { 630 var boxSize = getImageBoxSize(); 631 img.style.width = (selection.x2 - selection.x1 - boxSize.left - boxSize.right) + 'px'; 632 img.style.height = (selection.y2 - selection.y1 - boxSize.top - boxSize.bottom) + 'px'; 633 } 634 635 function getImageBoxSize() { 636 if (typeof getComputedStyle == 'function') { 637 var style = getComputedStyle(img); 638 return { 639 left: parseInt(style.borderLeftWidth) + 640 parseInt(style.paddingLeft) + 641 parseInt(style.marginLeft), 642 643 right: parseInt(style.borderRightWidth) + 644 parseInt(style.paddingRight) + 645 parseInt(style.marginRight), 646 647 top: parseInt(style.borderTopWidth) + 648 parseInt(style.paddingTop) + 649 parseInt(style.marginTop), 650 651 bottom: parseInt(style.borderBottomWidth) + 652 parseInt(style.paddingBottom) + 653 parseInt(style.marginBottom) 654 } 655 } else if (img.currentStyle) { 656 return { 657 lef: parseInt(img.currentStyle.borderLeftWidth) + 658 parseInt(img.currentStyle.paddingLeft) + 659 parseInt(img.currentStyle.marginLeft), 660 661 right: parseInt(img.currentStyle.borderRightWidth) + 662 parseInt(img.currentStyle.paddingRight) + 663 parseInt(img.currentStyle.marginRight), 664 665 top: parseInt(img.currentStyle.borderTopWidth) + 666 parseInt(img.currentStyle.paddingTop) + 667 parseInt(img.currentStyle.marginTop), 668 669 bottom: parseInt(img.currentStyle.borderBottomWidth) + 670 parseInt(img.currentStyle.paddingBottom) + 671 parseInt(img.currentStyle.marginBottom) 672 } 673 } else { 674 return {left: 0, right: 0, top: 0, bottom: 0} 675 } 676 } 677 678 /** 679 * Mousemove event handler triggered when the user is selecting an area 680 * 681 * @param event 682 * The event object 683 * @return false 684 */ 685 function selectingMouseMove(event) { 686 x2 = /w|e|^$/.test(resize) || aspectRatio ? evX(event) : viewX(selection.x2); 687 y2 = /n|s|^$/.test(resize) || aspectRatio ? evY(event) : viewY(selection.y2); 688 689 doResize(); 690 691 return false; 692 } 693 694 /** 695 * Move the selection area 696 * 697 * @param newX1 698 * New viewport X1 699 * @param newY1 700 * New viewport Y1 701 */ 702 function doMove(newX1, newY1) { 703 x2 = (x1 = newX1) + selection.width; 704 y2 = (y1 = newY1) + selection.height; 705 706 $.extend(selection, { x1: selX(x1), y1: selY(y1), x2: selX(x2), 707 y2: selY(y2) }); 708 709 update(); 710 711 options.onSelectChange(img, getSelection()); 712 } 713 714 /** 715 * Mousemove event handler triggered when the selection area is being moved 716 * 717 * @param event 718 * The event object 719 * @return false 720 */ 721 function movingMouseMove(event) { 722 x1 = max(left, min(startX + evX(event), left + imgWidth - selection.width)); 723 y1 = max(top, min(startY + evY(event), top + imgHeight - selection.height)); 724 725 doMove(x1, y1); 726 727 event.preventDefault(); 728 return false;
729 } 730 731 /** Start selection */ 732 function startSelection() { 733 $(document).unbind('mousemove', startSelection); 734 adjust(); 735 736 x2 = x1; 737 y2 = y1; 738 doResize(); 739 740 resize = ''; 741 742 if (!$outer.is(':visible')) 743 /* Show the plugin elements */ 744 $box.add($outer).hide().fadeIn(options.fadeSpeed||0); 745 746 shown = true; 747 748 $(document).unbind('mouseup', cancelSelection) 749 .mousemove(selectingMouseMove).one('mouseup', docMouseUp); 750 $box.unbind('mousemove', areaMouseMove); 751 752 options.onSelectStart(img, getSelection()); 753 } 754 755 /** Cancel selection */ 756 function cancelSelection() { 757 $(document).unbind('mousemove', startSelection) 758 .unbind('mouseup', cancelSelection); 759 hide($box.add($outer)); 760 761 setSelection(selX(x1), selY(y1), selX(x1), selY(y1)); 762 763 /* If this is an API call, callback functions should not be triggered */ 764 if (!(this instanceof $.imgAreaSelect)) { 765 options.onSelectChange(img, getSelection()); 766 options.onSelectEnd(img, getSelection()); 767 } 768 } 769 770 /** 771 * Image mousedown event handler 772 * 773 * @param event 774 * The event object 775 * @return false 776 */ 777 function imgMouseDown(event) { 778 /* Ignore the event if animation is in progress */ 779 if (event.which != 1 || $outer.is(':animated')) return false; 780 781 adjust(); 782 startX = x1 = evX(event); 783 startY = y1 = evY(event); 784 785 /* Selection will start when the mouse is moved */ 786 $(document).mousemove(startSelection).mouseup(cancelSelection); 787 788 return false; 789 } 790 791 /** Window resize event handler */ 792 function windowResize() { 793 doUpdate(false); 794 } 795 796 /** 797 * Image load event handler. This is the final part of the initialization 798 * process. 799 */ 800 function imgLoad() { 801 imgLoaded = true; 802 803 /* Set options */ 804 setOptions(options = $.extend({ 805 classPrefix: 'imgareaselect', 806 movable: true, 807 parent: 'body', 808 resizable: true, 809 resizeMargin: 10, 810 onInit: function () {}, 811 onSelectStart: function () {}, 812 onSelectChange: function () {}, 813 onSelectEnd: function () {} 814 }, options)); 815 816 $box.add($outer).css({ visibility: '' }); 817 818 if (options.show) { 819 shown = true; 820 adjust(); 821 update(); 822 $box.add($outer).hide().fadeIn(options.fadeSpeed||0); 823 } 824 825 /* 826 * Call the onInit callback. The setTimeout() call is used to ensure 827 * that the plugin has been fully initialized and the object instance is 828 * available (so that it can be obtained in the callback). 829 */ 830 setTimeout(function () { options.onInit(img, getSelection()); }, 0); 831 } 832 833 /** 834 * Document keypress event handler 835 * 836 * @param event 837 * The event object 838 * @return false 839 */ 840 var docKeyPress = function(event) { 841 var k = options.keys, d, t, key = event.keyCode; 842 843 d = !isNaN(k.alt) && (event.altKey || event.originalEvent.altKey) ? k.alt : 844 !isNaN(k.ctrl) && event.ctrlKey ? k.ctrl : 845 !isNaN(k.shift) && event.shiftKey ? k.shift : 846 !isNaN(k.arrows) ? k.arrows : 10; 847 848 if (k.arrows == 'resize' || (k.shift == 'resize' && event.shiftKey) || 849 (k.ctrl == 'resize' && event.ctrlKey) || 850 (k.alt == 'resize' && (event.altKey || event.originalEvent.altKey))) 851 { 852 /* Resize selection */ 853 854 switch (key) { 855 case 37: 856 /* Left */ 857 d = -d; 858 case 39: 859 /* Right */ 860 t = max(x1, x2); 861 x1 = min(x1, x2); 862 x2 = max(t + d, x1); 863 fixAspectRatio(); 864 break; 865 case 38: 866 /* Up */ 867 d = -d; 868 case 40: 869 /* Down */ 870 t = max(y1, y2); 871 y1 = min(y1, y2); 872 y2 = max(t + d, y1); 873 fixAspectRatio(true); 874 break; 875 default: 876 return; 877 } 878 879 doResize(); 880 } 881 else { 882 /* Move selection */ 883 884 x1 = min(x1, x2); 885 y1 = min(y1, y2); 886 887 switch (key) { 888 case 37: 889 /* Left */ 890 doMove(max(x1 - d, left), y1); 891 break; 892 case 38: 893 /* Up */ 894 doMove(x1, max(y1 - d, top)); 895 break; 896 case 39: 897 /* Right */ 898 doMove(x1 + min(d, imgWidth - selX(x2)), y1); 899 break; 900 case 40: 901 /* Down */ 902 doMove(x1, y1 + min(d, imgHeight - selY(y2))); 903 break; 904 default: 905 return; 906 } 907 } 908 909 return false;
910 }; 911 912 /** 913 * Apply style options to plugin element (or multiple elements) 914 * 915 * @param $elem 916 * A jQuery object representing the element(s) to style 917 * @param props 918 * An object that maps option names to corresponding CSS 919 * properties 920 */ 921 function styleOptions($elem, props) { 922 for (var option in props) 923 if (options[option] !== undefined) 924 $elem.css(props[option], options[option]); 925 } 926 927 /** 928 * Set plugin options 929 * 930 * @param newOptions 931 * The new options object 932 */ 933 function setOptions(newOptions) { 934 if (newOptions.parent) 935 ($parent = $(newOptions.parent)).append($box).append($outer); 936 937 /* Merge the new options with the existing ones */ 938 $.extend(options, newOptions); 939 940 adjust(); 941 942 if (newOptions.handles != null) { 943 /* Recreate selection area handles */ 944 $handles.remove(); 945 $handles = $([]); 946 947 i = newOptions.handles ? newOptions.handles == 'corners' ? 4 : 8 : 0; 948 949 while (i--) 950 $handles = $handles.add(div()); 951 952 /* Add a class to handles and set the CSS properties */ 953 $handles.addClass(options.classPrefix + '-handle').css({ 954 position: 'absolute', 955 /* 956 * The font-size property needs to be set to zero, otherwise 957 * Internet Explorer makes the handles too large 958 */ 959 fontSize: 0, 960 zIndex: zIndex + 1 || 1 961 }); 962 963 /* 964 * If handle width/height has not been set with CSS rules, set the 965 * default 5px 966 */ 967 if (!parseInt($handles.css('width')) >= 0) 968 $handles.width(5).height(5); 969 970 /* 971 * If the borderWidth option is in use, add a solid border to 972 * handles 973 */ 974 if (o = options.borderWidth) 975 $handles.css({ borderWidth: o, borderStyle: 'solid' }); 976 977 /* Apply other style options */ 978 styleOptions($handles, { borderColor1: 'border-color', 979 borderColor2: 'background-color', 980 borderOpacity: 'opacity' }); 981 } 982 983 /* Calculate scale factors */ 984 scaleX = options.imageWidth / imgWidth || 1; 985 scaleY = options.imageHeight / imgHeight || 1; 986 987 /* Set selection */ 988 if (newOptions.x1 != null) { 989 setSelection(newOptions.x1, newOptions.y1, newOptions.x2, 990 newOptions.y2); 991 newOptions.show = !newOptions.hide; 992 } 993 994 if (newOptions.keys) 995 /* Enable keyboard support */ 996 options.keys = $.extend({ shift: 1, ctrl: 'resize' }, 997 newOptions.keys); 998 999 /* Add classes to plugin elements */ 1000 $outer.addClass(options.classPrefix + '-outer'); 1001 $area.addClass(options.classPrefix + '-selection'); 1002 for (i = 0; i++ < 4;) 1003 $($border[i-1]).addClass(options.classPrefix + '-border' + i); 1004 1005 /* Apply style options */ 1006 styleOptions($area, { selectionColor: 'background-color', 1007 selectionOpacity: 'opacity' }); 1008 styleOptions($border, { borderOpacity: 'opacity', 1009 borderWidth: 'border-width' }); 1010 styleOptions($outer, { outerColor: 'background-color', 1011 outerOpacity: 'opacity' }); 1012 if (o = options.borderColor1) 1013 $($border[0]).css({ borderStyle: 'solid', borderColor: o }); 1014 if (o = options.borderColor2) 1015 $($border[1]).css({ borderStyle: 'dashed', borderColor: o }); 1016 1017 /* Append all the selection area elements to the container box */ 1018 $box.append($area.add($border).add($areaOpera)).append($handles); 1019 1020 if (msie) { 1021 if (o = ($outer.css('filter')||'').match(/opacity=(\d+)/)) 1022 $outer.css('opacity', o[1]/100); 1023 if (o = ($border.css('filter')||'').match(/opacity=(\d+)/)) 1024 $border.css('opacity', o[1]/100); 1025 } 1026 1027 if (newOptions.hide) 1028 hide($box.add($outer)); 1029 else if (newOptions.show && imgLoaded) { 1030 shown = true; 1031 $box.add($outer).fadeIn(options.fadeSpeed||0); 1032 doUpdate(); 1033 } 1034 1035 /* Calculate the aspect ratio factor */ 1036 aspectRatio = (d = (options.aspectRatio || '').split(/:/))[0] / d[1]; 1037 1038 $img.add($outer).unbind('mousedown', imgMouseDown); 1039 1040 if (options.disable || options.enable === false) { 1041 /* Disable the plugin */ 1042 $box.unbind('mousemove', areaMouseMove).unbind('mousedown', areaMouseDown); 1043 $(window).unbind('resize', windowResize); 1044 } 1045 else { 1046 if (options.enable || options.disable === false) { 1047 /* Enable the plugin */ 1048 if (options.resizable || options.movable) 1049 $box.mousemove(areaMouseMove).mousedown(areaMouseDown); 1050 1051 $(window).resize(windowResize); 1052 } 1053 1054 if (!options.persistent) 1055 $img.add($outer).mousedown(imgMouseDown); 1056 } 1057 1058 options.enable = options.disable = undefined; 1059 } 1060 1061 this.show = function () { 1062 setOptions({show: true}); 1063 doUpdate(); 1064 }; 1065 1066 this.hide = function () { 1067 setOptions({show: false}); 1068 doUpdate(); 1069 }; 1070 1071 var MODE_CROP = 'crop', 1072 MODE_RESIZE = 'resize'; 1073 1074 var sMode = MODE_CROP; 1075 1076 this.setMode = function (mode) { 1077 if (!sMode) sMode = MODE_RESIZE;
1078 sMode = mode; 1079 }; 1080 1081 /** Remove plugin completely */ 1082 this.remove = function () { 1083 /* 1084 * Call setOptions with { disable: true } to unbind the event handlers 1085 */ 1086 setOptions({ disable: true }); 1087 $box.add($outer).remove(); 1088 }; 1089 1090 /* 1091 * Public API 1092 */ 1093 1094 /** 1095 * Get current options 1096 * 1097 * @return An object containing the set of options currently in use 1098 */ 1099 this.getOptions = function () { return options; }; 1100 1101 /** 1102 * Set plugin options 1103 * 1104 * @param newOptions 1105 * The new options object 1106 */ 1107 this.setOptions = setOptions; 1108 1109 /** 1110 * Get the current selection 1111 * 1112 * @param noScale 1113 * If set to <code>true</code>, scaling is not applied to the 1114 * returned selection 1115 * @return Selection object 1116 */ 1117 this.getSelection = getSelection; 1118 1119 /** 1120 * Set the current selection 1121 * 1122 * @param x1 1123 * X coordinate of the upper left corner of the selection area 1124 * @param y1 1125 * Y coordinate of the upper left corner of the selection area 1126 * @param x2 1127 * X coordinate of the lower right corner of the selection area 1128 * @param y2 1129 * Y coordinate of the lower right corner of the selection area 1130 * @param noScale 1131 * If set to <code>true</code>, scaling is not applied to the 1132 * new selection 1133 */ 1134 this.setSelection = setSelection; 1135 1136 /** Cancel selection */ 1137 this.cancelSelection = cancelSelection; 1138 1139 /** 1140 * Update plugin elements 1141 * 1142 * @param resetKeyPress 1143 * If set to <code>false</code>, this instance's keypress 1144 * event handler is not activated 1145 */ 1146 this.update = doUpdate; 1147 1148 /* Do the dreaded browser detection */ 1149 var msie = (/msie ([\w.]+)/i.exec(ua)||[])[1], 1150 opera = /opera/i.test(ua), 1151 safari = /webkit/i.test(ua) && !/chrome/i.test(ua); 1152 1153 /* 1154 * Traverse the image's parent elements (up to <body>) and find the 1155 * highest z-index 1156 */ 1157 $p = $img; 1158 1159 while ($p.length) { 1160 zIndex = max(zIndex, 1161 !isNaN($p.css('z-index')) ? $p.css('z-index') : zIndex); 1162 /* Also check if any of the ancestor elements has fixed position */ 1163 if ($p.css('position') == 'fixed') 1164 position = 'fixed'; 1165 1166 $p = $p.parent(':not(body)'); 1167 } 1168 1169 /* 1170 * If z-index is given as an option, it overrides the one found by the 1171 * above loop 1172 */ 1173 zIndex = options.zIndex || zIndex; 1174 1175 if (msie) 1176 $img.attr('unselectable', 'on'); 1177 1178 /* 1179 * In MSIE and WebKit, we need to use the keydown event instead of keypress 1180 */ 1181 $.imgAreaSelect.keyPress = msie || safari ? 'keydown' : 'keypress'; 1182 1183 /* 1184 * There is a bug affecting the CSS cursor property in Opera (observed in 1185 * versions up to 10.00) that prevents the cursor from being updated unless 1186 * the mouse leaves and enters the element again. To trigger the mouseover 1187 * event, we're adding an additional div to $box and we're going to toggle 1188 * it when mouse moves inside the selection area. 1189 */ 1190 if (opera) 1191 $areaOpera = div().css({ width: '100%', height: '100%', 1192 position: 'absolute', zIndex: zIndex + 2 || 2 }); 1193 1194 /* 1195 * We initially set visibility to "hidden" as a workaround for a weird 1196 * behaviour observed in Google Chrome 1.0.154.53 (on Windows XP). Normally 1197 * we would just set display to "none", but, for some reason, if we do so 1198 * then Chrome refuses to later display the element with .show() or 1199 * .fadeIn(). 1200 */ 1201 $box.add($outer).css({ visibility: 'hidden', position: position, 1202 overflow: 'hidden', zIndex: zIndex || '0' }); 1203 $box.css({ zIndex: zIndex + 2 || 2 }); 1204 $area.add($border).css({ position: 'absolute', fontSize: 0 }); 1205 1206 /* 1207 * If the image has been fully loaded, or if it is not really an image (eg. 1208 * a div), call imgLoad() immediately; otherwise, bind it to be called once 1209 * on image load event. 1210 */ 1211 img.complete || img.readyState == 'complete' || !$img.is('img') ? 1212 imgLoad() : $img.one('load', imgLoad); 1213 1214 /* 1215 * MSIE 9.0 doesn't always fire the image load event -- resetting the src 1216 * attribute seems to trigger it. The check is for version 7 and above to 1217 * accommodate for MSIE 9 running in compatibility mode. 1218 */ 1219 if (!imgLoaded && msie && msie >= 7) 1220 img.src = img.src; 1221 }; 1222 1223 /**
1224 * Invoke imgAreaSelect on a jQuery object containing the image(s) 1225 * 1226 * @param options 1227 * Options object 1228 * @return The jQuery object or a reference to imgAreaSelect instance (if the 1229 * <code>instance</code> option was specified) 1230 */ 1231 $.fn.imgAreaSelect = function (options) { 1232 options = options || {}; 1233 1234 this.each(function () { 1235 /* Is there already an imgAreaSelect instance bound to this element? */ 1236 if ($(this).data('imgAreaSelect')) { 1237 /* Yes there is -- is it supposed to be removed? */ 1238 if (options.remove) { 1239 /* Remove the plugin */ 1240 $(this).data('imgAreaSelect').remove(); 1241 $(this).removeData('imgAreaSelect'); 1242 } 1243 else 1244 /* Reset options */ 1245 $(this).data('imgAreaSelect').setOptions(options); 1246 } 1247 else if (!options.remove) { 1248 /* No exising instance -- create a new one */ 1249 1250 /* 1251 * If neither the "enable" nor the "disable" option is present, add 1252 * "enable" as the default 1253 */ 1254 if (options.enable === undefined && options.disable === undefined) 1255 options.enable = true; 1256 1257 $(this).data('imgAreaSelect', new $.imgAreaSelect(this, options)); 1258 } 1259 }); 1260 1261 if (options.instance) 1262 /* 1263 * Return the imgAreaSelect instance bound to the first element in the 1264 * set 1265 */ 1266 return $(this).data('imgAreaSelect'); 1267 1268 return this; 1269 }; 1270 1271})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.