[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

   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);


Generated: Thu Nov 21 01:00:03 2024 Cross-referenced by PHPXref 0.7.1