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