[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/imgareaselect/ -> jquery.imgareaselect.js (source)

   1  /*
   2   * imgAreaSelect jQuery plugin
   3   * version 0.9.10-wp-6.2
   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      floor = Math.floor;
  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: floor(selection.x1 * sx),
 234              y1: floor(selection.y1 * sy),
 235              x2: floor(selection.x2 * sx),
 236              y2: floor(selection.y2 * sy),
 237              width: floor(selection.x2 * sx) - floor(selection.x1 * sx),
 238              height: floor(selection.y2 * sy) - floor(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: floor(x1 / sx || 0),
 261              y1: floor(y1 / sy || 0),
 262              x2: floor(x2 / sx || 0),
 263              y2: floor(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: floor($img.offset().left), top: floor($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 = floor(options.minWidth / scaleX) || 0;
 297          minHeight = floor(options.minHeight / scaleY) || 0;
 298          maxWidth = floor(min(options.maxWidth / scaleX || 1<<24, imgWidth));
 299          maxHeight = floor(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: floor($parent.offset().left) - $parent.scrollLeft(),
 315                  top: floor($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          updateSelectionRelativeToParentElement();
 434      }
 435  
 436      /**
 437       * Set the correct values of x1, y1, x2, and y2.
 438       */
 439      function updateSelectionRelativeToParentElement() {
 440          x1 = viewX(selection.x1); y1 = viewY(selection.y1);
 441          x2 = viewX(selection.x2); y2 = viewY(selection.y2);
 442      }
 443  
 444      /**
 445       * Hide or fade out an element (or multiple elements)
 446       *
 447       * @param $elem
 448       *            A jQuery object containing the element(s) to hide/fade out
 449       * @param fn
 450       *            Callback function to be called when fadeOut() completes
 451       */
 452      function hide($elem, fn) {
 453          options.fadeSpeed ? $elem.fadeOut(options.fadeSpeed, fn) : $elem.hide();
 454      }
 455  
 456      /**
 457       * Selection area mousemove event handler
 458       *
 459       * @param event
 460       *            The event object
 461       */
 462      function areaMouseMove(event) {
 463          var x = selX(evX(event)) - selection.x1,
 464              y = selY(evY(event)) - selection.y1;
 465  
 466          if (!adjusted) {
 467              adjust();
 468              adjusted = true;
 469  
 470              $box.one('mouseout', function () { adjusted = false; });
 471          }
 472  
 473          /* Clear the resize mode */
 474          resize = '';
 475  
 476          if (options.resizable) {
 477              /*
 478               * Check if the mouse pointer is over the resize margin area and set
 479               * the resize mode accordingly
 480               */
 481              if (y <= options.resizeMargin)
 482                  resize = 'n';
 483              else if (y >= selection.height - options.resizeMargin)
 484                  resize = 's';
 485              if (x <= options.resizeMargin)
 486                  resize += 'w';
 487              else if (x >= selection.width - options.resizeMargin)
 488                  resize += 'e';
 489          }
 490  
 491          $box.css('cursor', resize ? resize + '-resize' :
 492              options.movable ? 'move' : '');
 493          if ($areaOpera)
 494              $areaOpera.toggle();
 495      }
 496  
 497      /**
 498       * Document mouseup event handler
 499       *
 500       * @param event
 501       *            The event object
 502       */
 503      function docMouseUp(event) {
 504          /* Set back the default cursor */
 505          $('body').css('cursor', '');
 506          /*
 507           * If autoHide is enabled, or if the selection has zero width/height,
 508           * hide the selection and the outer area
 509           */
 510          if (options.autoHide || selection.width * selection.height == 0)
 511              hide($box.add($outer), function () { $(this).hide(); });
 512  
 513          $(document).off('mousemove touchmove', selectingMouseMove);
 514          $box.on('mousemove touchmove', areaMouseMove);
 515  
 516          options.onSelectEnd(img, getSelection());
 517      }
 518  
 519      /**
 520       * Selection area mousedown event handler
 521       *
 522       * @param event
 523       *            The event object
 524       * @return false
 525       */
 526      function areaMouseDown(event) {
 527          if (event.type == 'mousedown' && event.which != 1) return false;
 528  
 529          /*
 530           * With mobile browsers, there is no "moving the pointer over" action,
 531           * so we need to simulate one mousemove event happening prior to
 532           * mousedown/touchstart.
 533           */
 534          areaMouseMove(event);
 535  
 536          adjust();
 537  
 538          if (resize) {
 539              /* Resize mode is in effect */
 540              $('body').css('cursor', resize + '-resize');
 541  
 542              x1 = viewX(selection[/w/.test(resize) ? 'x2' : 'x1']);
 543              y1 = viewY(selection[/n/.test(resize) ? 'y2' : 'y1']);
 544  
 545              $(document).on('mousemove touchmove', selectingMouseMove)
 546                  .one('mouseup touchend', docMouseUp);
 547              $box.off('mousemove touchmove', areaMouseMove);
 548          }
 549          else if (options.movable) {
 550              startX = left + selection.x1 - evX(event);
 551              startY = top + selection.y1 - evY(event);
 552  
 553              $box.off('mousemove touchmove', areaMouseMove);
 554  
 555              $(document).on('mousemove touchmove', movingMouseMove)
 556                  .one('mouseup touchend', function () {
 557                      options.onSelectEnd(img, getSelection());
 558  
 559                      $(document).off('mousemove touchmove', movingMouseMove);
 560                      $box.on('mousemove touchmove', areaMouseMove);
 561                  });
 562          }
 563          else
 564              $img.mousedown(event);
 565  
 566          return false;
 567      }
 568  
 569      /**
 570       * Adjust the x2/y2 coordinates to maintain aspect ratio (if defined)
 571       *
 572       * @param xFirst
 573       *            If set to <code>true</code>, calculate x2 first. Otherwise,
 574       *            calculate y2 first.
 575       */
 576      function fixAspectRatio(xFirst) {
 577          if (aspectRatio)
 578              if (xFirst) {
 579                  x2 = max(left, min(left + imgWidth,
 580                      x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1)));
 581                  y2 = floor(max(top, min(top + imgHeight,
 582                      y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1))));
 583                  x2 = floor(x2);
 584              }
 585              else {
 586                  y2 = max(top, min(top + imgHeight,
 587                      y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1)));
 588                  x2 = floor(max(left, min(left + imgWidth,
 589                      x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1))));
 590                  y2 = floor(y2);
 591              }
 592      }
 593  
 594      /**
 595       * Resize the selection area respecting the minimum/maximum dimensions and
 596       * aspect ratio
 597       */
 598      function doResize() {
 599          /*
 600           * Make sure x1, x2, y1, y2 are initialized to avoid the following calculation
 601           * getting incorrect results.
 602           */
 603          if ( x1 == null || x2 == null || y1 == null || y2 == null ) {
 604              updateSelectionRelativeToParentElement();
 605          }
 606  
 607          /*
 608           * Make sure the top left corner of the selection area stays within
 609           * image boundaries (it might not if the image source was dynamically
 610           * changed).
 611           */
 612          x1 = min(x1, left + imgWidth);
 613          y1 = min(y1, top + imgHeight);
 614  
 615          if (abs(x2 - x1) < minWidth) {
 616              /* Selection width is smaller than minWidth */
 617              x2 = x1 - minWidth * (x2 < x1 || -1);
 618  
 619              if (x2 < left)
 620                  x1 = left + minWidth;
 621              else if (x2 > left + imgWidth)
 622                  x1 = left + imgWidth - minWidth;
 623          }
 624  
 625          if (abs(y2 - y1) < minHeight) {
 626              /* Selection height is smaller than minHeight */
 627              y2 = y1 - minHeight * (y2 < y1 || -1);
 628  
 629              if (y2 < top)
 630                  y1 = top + minHeight;
 631              else if (y2 > top + imgHeight)
 632                  y1 = top + imgHeight - minHeight;
 633          }
 634  
 635          x2 = max(left, min(x2, left + imgWidth));
 636          y2 = max(top, min(y2, top + imgHeight));
 637  
 638          fixAspectRatio(abs(x2 - x1) < abs(y2 - y1) * aspectRatio);
 639  
 640          if (abs(x2 - x1) > maxWidth) {
 641              /* Selection width is greater than maxWidth */
 642              x2 = x1 - maxWidth * (x2 < x1 || -1);
 643              fixAspectRatio();
 644          }
 645  
 646          if (abs(y2 - y1) > maxHeight) {
 647              /* Selection height is greater than maxHeight */
 648              y2 = y1 - maxHeight * (y2 < y1 || -1);
 649              fixAspectRatio(true);
 650          }
 651  
 652          selection = { x1: selX(min(x1, x2)), x2: selX(max(x1, x2)),
 653              y1: selY(min(y1, y2)), y2: selY(max(y1, y2)),
 654              width: abs(x2 - x1), height: abs(y2 - y1) };
 655  
 656          update();
 657  
 658          options.onSelectChange(img, getSelection());
 659      }
 660  
 661      /**
 662       * Mousemove event handler triggered when the user is selecting an area
 663       *
 664       * @param event
 665       *            The event object
 666       * @return false
 667       */
 668      function selectingMouseMove(event) {
 669          x2 = /w|e|^$/.test(resize) || aspectRatio ? evX(event) : viewX(selection.x2);
 670          y2 = /n|s|^$/.test(resize) || aspectRatio ? evY(event) : viewY(selection.y2);
 671  
 672          doResize();
 673  
 674          return false;
 675      }
 676  
 677      /**
 678       * Move the selection area
 679       *
 680       * @param newX1
 681       *            New viewport X1
 682       * @param newY1
 683       *            New viewport Y1
 684       */
 685      function doMove(newX1, newY1) {
 686          x2 = (x1 = newX1) + selection.width;
 687          y2 = (y1 = newY1) + selection.height;
 688  
 689          $.extend(selection, { x1: selX(x1), y1: selY(y1), x2: selX(x2),
 690              y2: selY(y2) });
 691  
 692          update();
 693  
 694          options.onSelectChange(img, getSelection());
 695      }
 696  
 697      /**
 698       * Mousemove event handler triggered when the selection area is being moved
 699       *
 700       * @param event
 701       *            The event object
 702       * @return false
 703       */
 704      function movingMouseMove(event) {
 705          x1 = max(left, min(startX + evX(event), left + imgWidth - selection.width));
 706          y1 = max(top, min(startY + evY(event), top + imgHeight - selection.height));
 707  
 708          doMove(x1, y1);
 709  
 710          event.preventDefault();
 711          return false;
 712      }
 713  
 714      /**
 715       * Start selection
 716       */
 717      function startSelection() {
 718          $(document).off('mousemove touchmove', startSelection);
 719          adjust();
 720  
 721          x2 = x1;
 722          y2 = y1;
 723          doResize();
 724  
 725          resize = '';
 726  
 727          if (!$outer.is(':visible'))
 728              /* Show the plugin elements */
 729              $box.add($outer).hide().fadeIn(options.fadeSpeed||0);
 730  
 731          shown = true;
 732  
 733          $(document).off('mouseup touchend', cancelSelection)
 734              .on('mousemove touchmove', selectingMouseMove)
 735              .one('mouseup touchend', docMouseUp);
 736          $box.off('mousemove touchmove', areaMouseMove);
 737  
 738          options.onSelectStart(img, getSelection());
 739      }
 740  
 741      /**
 742       * Cancel selection
 743       */
 744      function cancelSelection() {
 745          $(document).off('mousemove touchmove', startSelection)
 746              .off('mouseup touchend', cancelSelection);
 747          hide($box.add($outer));
 748  
 749          setSelection(selX(x1), selY(y1), selX(x1), selY(y1));
 750  
 751          /* If this is an API call, callback functions should not be triggered */
 752          if (!(this instanceof $.imgAreaSelect)) {
 753              options.onSelectChange(img, getSelection());
 754              options.onSelectEnd(img, getSelection());
 755          }
 756      }
 757  
 758      /**
 759       * Image mousedown event handler
 760       *
 761       * @param event
 762       *            The event object
 763       * @return false
 764       */
 765      function imgMouseDown(event) {
 766          /* Ignore the event if animation is in progress */
 767          if (event.which > 1 || $outer.is(':animated')) return false;
 768  
 769          adjust();
 770          startX = x1 = evX(event);
 771          startY = y1 = evY(event);
 772  
 773          /* Selection will start when the mouse is moved */
 774          $(document).on({ 'mousemove touchmove': startSelection,
 775              'mouseup touchend': cancelSelection });
 776  
 777          return false;
 778      }
 779  
 780      /**
 781       * Window resize event handler
 782       */
 783      function windowResize() {
 784          doUpdate(false);
 785      }
 786  
 787      /**
 788       * Image load event handler. This is the final part of the initialization
 789       * process.
 790       */
 791      function imgLoad() {
 792          imgLoaded = true;
 793  
 794          /* Set options */
 795          setOptions(options = $.extend({
 796              classPrefix: 'imgareaselect',
 797              movable: true,
 798              parent: 'body',
 799              resizable: true,
 800              resizeMargin: 10,
 801              onInit: function () {},
 802              onSelectStart: function () {},
 803              onSelectChange: function () {},
 804              onSelectEnd: function () {}
 805          }, options));
 806  
 807          $box.add($outer).css({ visibility: '' });
 808  
 809          if (options.show) {
 810              shown = true;
 811              adjust();
 812              update();
 813              $box.add($outer).hide().fadeIn(options.fadeSpeed||0);
 814          }
 815  
 816          /*
 817           * Call the onInit callback. The setTimeout() call is used to ensure
 818           * that the plugin has been fully initialized and the object instance is
 819           * available (so that it can be obtained in the callback).
 820           */
 821          setTimeout(function () { options.onInit(img, getSelection()); }, 0);
 822      }
 823  
 824      /**
 825       * Document keypress event handler
 826       *
 827       * @param event
 828       *            The event object
 829       * @return false
 830       */
 831      var docKeyPress = function(event) {
 832          var k = options.keys, d, t, key = event.keyCode;
 833  
 834          d = !isNaN(k.alt) && (event.altKey || event.originalEvent.altKey) ? k.alt :
 835              !isNaN(k.ctrl) && event.ctrlKey ? k.ctrl :
 836              !isNaN(k.shift) && event.shiftKey ? k.shift :
 837              !isNaN(k.arrows) ? k.arrows : 10;
 838  
 839          if (k.arrows == 'resize' || (k.shift == 'resize' && event.shiftKey) ||
 840              (k.ctrl == 'resize' && event.ctrlKey) ||
 841              (k.alt == 'resize' && (event.altKey || event.originalEvent.altKey)))
 842          {
 843              /* Resize selection */
 844  
 845              switch (key) {
 846              case 37:
 847                  /* Left */
 848                  d = -d;
 849              case 39:
 850                  /* Right */
 851                  t = max(x1, x2);
 852                  x1 = min(x1, x2);
 853                  x2 = max(t + d, x1);
 854                  fixAspectRatio();
 855                  break;
 856              case 38:
 857                  /* Up */
 858                  d = -d;
 859              case 40:
 860                  /* Down */
 861                  t = max(y1, y2);
 862                  y1 = min(y1, y2);
 863                  y2 = max(t + d, y1);
 864                  fixAspectRatio(true);
 865                  break;
 866              default:
 867                  return;
 868              }
 869  
 870              doResize();
 871          }
 872          else {
 873              /* Move selection */
 874  
 875              x1 = min(x1, x2);
 876              y1 = min(y1, y2);
 877  
 878              switch (key) {
 879              case 37:
 880                  /* Left */
 881                  doMove(max(x1 - d, left), y1);
 882                  break;
 883              case 38:
 884                  /* Up */
 885                  doMove(x1, max(y1 - d, top));
 886                  break;
 887              case 39:
 888                  /* Right */
 889                  doMove(x1 + min(d, imgWidth - selX(x2)), y1);
 890                  break;
 891              case 40:
 892                  /* Down */
 893                  doMove(x1, y1 + min(d, imgHeight - selY(y2)));
 894                  break;
 895              default:
 896                  return;
 897              }
 898          }
 899  
 900          return false;
 901      };
 902  
 903      /**
 904       * Apply style options to plugin element (or multiple elements)
 905       *
 906       * @param $elem
 907       *            A jQuery object representing the element(s) to style
 908       * @param props
 909       *            An object that maps option names to corresponding CSS
 910       *            properties
 911       */
 912      function styleOptions($elem, props) {
 913          for (var option in props)
 914              if (options[option] !== undefined)
 915                  $elem.css(props[option], options[option]);
 916      }
 917  
 918      /**
 919       * Set plugin options
 920       *
 921       * @param newOptions
 922       *            The new options object
 923       */
 924      function setOptions(newOptions) {
 925          if (newOptions.parent)
 926              ($parent = $(newOptions.parent)).append($box.add($outer));
 927  
 928          /* Merge the new options with the existing ones */
 929          $.extend(options, newOptions);
 930  
 931          adjust();
 932  
 933          if (newOptions.handles != null) {
 934              /* Recreate selection area handles */
 935              $handles.remove();
 936              $handles = $([]);
 937  
 938              i = newOptions.handles ? newOptions.handles == 'corners' ? 4 : 8 : 0;
 939  
 940              while (i--)
 941                  $handles = $handles.add(div());
 942  
 943              /* Add a class to handles and set the CSS properties */
 944              $handles.addClass(options.classPrefix + '-handle').css({
 945                  position: 'absolute',
 946                  /*
 947                   * The font-size property needs to be set to zero, otherwise
 948                   * Internet Explorer makes the handles too large
 949                   */
 950                  fontSize: '0',
 951                  zIndex: zIndex + 1 || 1
 952              });
 953  
 954              /*
 955               * If handle width/height has not been set with CSS rules, set the
 956               * default 5px
 957               */
 958              if (!parseInt($handles.css('width')) >= 0)
 959                  $handles.width(10).height(10);
 960  
 961              /*
 962               * If the borderWidth option is in use, add a solid border to
 963               * handles
 964               */
 965              if (o = options.borderWidth)
 966                  $handles.css({ borderWidth: o, borderStyle: 'solid' });
 967  
 968              /* Apply other style options */
 969              styleOptions($handles, { borderColor1: 'border-color',
 970                  borderColor2: 'background-color',
 971                  borderOpacity: 'opacity' });
 972          }
 973  
 974          /* Calculate scale factors */
 975          scaleX = options.imageWidth / imgWidth || 1;
 976          scaleY = options.imageHeight / imgHeight || 1;
 977  
 978          /* Set selection */
 979          if (newOptions.x1 != null) {
 980              setSelection(newOptions.x1, newOptions.y1, newOptions.x2,
 981                  newOptions.y2);
 982              newOptions.show = !newOptions.hide;
 983          }
 984  
 985          if (newOptions.keys)
 986              /* Enable keyboard support */
 987              options.keys = $.extend({ shift: 1, ctrl: 'resize' },
 988                  newOptions.keys);
 989  
 990          /* Add classes to plugin elements */
 991          $outer.addClass(options.classPrefix + '-outer');
 992          $area.addClass(options.classPrefix + '-selection');
 993          for (i = 0; i++ < 4;)
 994              $($border[i-1]).addClass(options.classPrefix + '-border' + i);
 995  
 996          /* Apply style options */
 997          styleOptions($area, { selectionColor: 'background-color',
 998              selectionOpacity: 'opacity' });
 999          styleOptions($border, { borderOpacity: 'opacity',
1000              borderWidth: 'border-width' });
1001          styleOptions($outer, { outerColor: 'background-color',
1002              outerOpacity: 'opacity' });
1003          if (o = options.borderColor1)
1004              $($border[0]).css({ borderStyle: 'solid', borderColor: o });
1005          if (o = options.borderColor2)
1006              $($border[1]).css({ borderStyle: 'dashed', borderColor: o });
1007  
1008          /* Append all the selection area elements to the container box */
1009          $box.append($area.add($border).add($areaOpera)).append($handles);
1010  
1011          if (msie) {
1012              if (o = ($outer.css('filter')||'').match(/opacity=(\d+)/))
1013                  $outer.css('opacity', o[1]/100);
1014              if (o = ($border.css('filter')||'').match(/opacity=(\d+)/))
1015                  $border.css('opacity', o[1]/100);
1016          }
1017  
1018          if (newOptions.hide)
1019              hide($box.add($outer));
1020          else if (newOptions.show && imgLoaded) {
1021              shown = true;
1022              $box.add($outer).fadeIn(options.fadeSpeed||0);
1023              doUpdate();
1024          }
1025  
1026          /* Calculate the aspect ratio factor */
1027          aspectRatio = (d = (options.aspectRatio || '').split(/:/))[0] / d[1];
1028  
1029          $img.add($outer).off('mousedown', imgMouseDown);
1030  
1031          if (options.disable || options.enable === false) {
1032              /* Disable the plugin */
1033              $box.off({ 'mousemove touchmove': areaMouseMove,
1034                  'mousedown touchstart': areaMouseDown });
1035              $(window).off('resize', windowResize);
1036          }
1037          else {
1038              if (options.enable || options.disable === false) {
1039                  /* Enable the plugin */
1040                  if (options.resizable || options.movable)
1041                      $box.on({ 'mousemove touchmove': areaMouseMove,
1042                          'mousedown touchstart': areaMouseDown });
1043  
1044                  $(window).on( 'resize', windowResize);
1045              }
1046  
1047              if (!options.persistent)
1048                  $img.add($outer).on('mousedown touchstart', imgMouseDown);
1049          }
1050  
1051          options.enable = options.disable = undefined;
1052      }
1053  
1054      /**
1055       * Remove plugin completely
1056       */
1057      this.remove = function () {
1058          /*
1059           * Call setOptions with { disable: true } to unbind the event handlers
1060           */
1061          setOptions({ disable: true });
1062          $box.add($outer).remove();
1063      };
1064  
1065      /*
1066       * Public API
1067       */
1068  
1069      /**
1070       * Get current options
1071       *
1072       * @return An object containing the set of options currently in use
1073       */
1074      this.getOptions = function () { return options; };
1075  
1076      /**
1077       * Set plugin options
1078       *
1079       * @param newOptions
1080       *            The new options object
1081       */
1082      this.setOptions = setOptions;
1083  
1084      /**
1085       * Get the current selection
1086       *
1087       * @param noScale
1088       *            If set to <code>true</code>, scaling is not applied to the
1089       *            returned selection
1090       * @return Selection object
1091       */
1092      this.getSelection = getSelection;
1093  
1094      /**
1095       * Set the current selection
1096       *
1097       * @param x1
1098       *            X coordinate of the upper left corner of the selection area
1099       * @param y1
1100       *            Y coordinate of the upper left corner of the selection area
1101       * @param x2
1102       *            X coordinate of the lower right corner of the selection area
1103       * @param y2
1104       *            Y coordinate of the lower right corner of the selection area
1105       * @param noScale
1106       *            If set to <code>true</code>, scaling is not applied to the
1107       *            new selection
1108       */
1109      this.setSelection = setSelection;
1110  
1111      /**
1112       * Cancel selection
1113       */
1114      this.cancelSelection = cancelSelection;
1115  
1116      /**
1117       * Update plugin elements
1118       *
1119       * @param resetKeyPress
1120       *            If set to <code>false</code>, this instance's keypress
1121       *            event handler is not activated
1122       */
1123      this.update = doUpdate;
1124  
1125      /* Do the dreaded browser detection */
1126      var msie = (/msie ([\w.]+)/i.exec(ua)||[])[1],
1127          opera = /opera/i.test(ua),
1128          safari = /webkit/i.test(ua) && !/chrome/i.test(ua);
1129  
1130      /*
1131       * Traverse the image's parent elements (up to <body>) and find the
1132       * highest z-index
1133       */
1134      $p = $img;
1135  
1136      while ($p.length) {
1137          zIndex = max(zIndex,
1138              !isNaN($p.css('z-index')) ? $p.css('z-index') : zIndex);
1139          /* Also check if any of the ancestor elements has fixed position */
1140          if ($p.css('position') == 'fixed')
1141              position = 'fixed';
1142  
1143          $p = $p.parent(':not(body)');
1144      }
1145  
1146      /*
1147       * If z-index is given as an option, it overrides the one found by the
1148       * above loop
1149       */
1150      zIndex = options.zIndex || zIndex;
1151  
1152      if (msie)
1153          $img.attr('unselectable', 'on');
1154  
1155      /*
1156       * In MSIE and WebKit, we need to use the keydown event instead of keypress
1157       */
1158      $.imgAreaSelect.keyPress = msie || safari ? 'keydown' : 'keypress';
1159  
1160      /*
1161       * There is a bug affecting the CSS cursor property in Opera (observed in
1162       * versions up to 10.00) that prevents the cursor from being updated unless
1163       * the mouse leaves and enters the element again. To trigger the mouseover
1164       * event, we're adding an additional div to $box and we're going to toggle
1165       * it when mouse moves inside the selection area.
1166       */
1167      if (opera)
1168          $areaOpera = div().css({ width: '100%', height: '100%',
1169              position: 'absolute', zIndex: zIndex + 2 || 2 });
1170  
1171      /*
1172       * We initially set visibility to "hidden" as a workaround for a weird
1173       * behaviour observed in Google Chrome 1.0.154.53 (on Windows XP). Normally
1174       * we would just set display to "none", but, for some reason, if we do so
1175       * then Chrome refuses to later display the element with .show() or
1176       * .fadeIn().
1177       */
1178      $box.add($outer).css({ visibility: 'hidden', position: position,
1179          overflow: 'hidden', zIndex: zIndex || '0' });
1180      $box.css({ zIndex: zIndex + 2 || 2 });
1181      $area.add($border).css({ position: 'absolute', fontSize: '0' });
1182  
1183      /*
1184       * If the image has been fully loaded, or if it is not really an image (eg.
1185       * a div), call imgLoad() immediately; otherwise, bind it to be called once
1186       * on image load event.
1187       */
1188      img.complete || img.readyState == 'complete' || !$img.is('img') ?
1189          imgLoad() : $img.one('load', imgLoad);
1190  
1191      /*
1192       * MSIE 9.0 doesn't always fire the image load event -- resetting the src
1193       * attribute seems to trigger it. The check is for version 7 and above to
1194       * accommodate for MSIE 9 running in compatibility mode.
1195       */
1196      if (!imgLoaded && msie && msie >= 7)
1197          img.src = img.src;
1198  };
1199  
1200  /**
1201   * Invoke imgAreaSelect on a jQuery object containing the image(s)
1202   *
1203   * @param options
1204   *            Options object
1205   * @return The jQuery object or a reference to imgAreaSelect instance (if the
1206   *         <code>instance</code> option was specified)
1207   */
1208  $.fn.imgAreaSelect = function (options) {
1209      options = options || {};
1210  
1211      this.each(function () {
1212          /* Is there already an imgAreaSelect instance bound to this element? */
1213          if ($(this).data('imgAreaSelect')) {
1214              /* Yes there is -- is it supposed to be removed? */
1215              if (options.remove) {
1216                  /* Remove the plugin */
1217                  $(this).data('imgAreaSelect').remove();
1218                  $(this).removeData('imgAreaSelect');
1219              }
1220              else
1221                  /* Reset options */
1222                  $(this).data('imgAreaSelect').setOptions(options);
1223          }
1224          else if (!options.remove) {
1225              /* No exising instance -- create a new one */
1226  
1227              /*
1228               * If neither the "enable" nor the "disable" option is present, add
1229               * "enable" as the default
1230               */
1231              if (options.enable === undefined && options.disable === undefined)
1232                  options.enable = true;
1233  
1234              $(this).data('imgAreaSelect', new $.imgAreaSelect(this, options));
1235          }
1236      });
1237  
1238      if (options.instance)
1239          /*
1240           * Return the imgAreaSelect instance bound to the first element in the
1241           * set
1242           */
1243          return $(this).data('imgAreaSelect');
1244  
1245      return this;
1246  };
1247  
1248  })(jQuery);


Generated : Thu May 9 08:20:02 2024 Cross-referenced by PHPXref