PageSourceSearch

https://samtripoli.com/wp-content/plugins/ultimate-member/assets/libs/cropper/cropper.js?ver=1.6.1

js samtripoli.com collected 2026-10-01 14:01:23 UTC 113,993 bytes, 3,274 lines download raw bytes

vendor: 4,674 bytes, lines 1-122
1/*!
2 * Cropper.js v1.6.1
3 * https://fengyuanchen.github.io/cropperjs
4 *
5 * Copyright 2015-present Chen Fengyuan
6 * Released under the MIT license
7 *
8 * Date: 2023-09-17T03:44:19.860Z
9 */
10
11(function (global, factory) {
12  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
13  typeof define === 'function' && define.amd ? define(factory) :
14  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Cropper = factory());
15})(this, (function () { 'use strict';
16
17  function ownKeys(e, r) {
18    var t = Object.keys(e);
19    if (Object.getOwnPropertySymbols) {
20      var o = Object.getOwnPropertySymbols(e);
21      r && (o = o.filter(function (r) {
22        return Object.getOwnPropertyDescriptor(e, r).enumerable;
23      })), t.push.apply(t, o);
24    }
25    return t;
26  }
27  function _objectSpread2(e) {
28    for (var r = 1; r < arguments.length; r++) {
29      var t = null != arguments[r] ? arguments[r] : {};
30      r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
31        _defineProperty(e, r, t[r]);
32      }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
33        Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
34      });
35    }
36    return e;
37  }
38  function _typeof(o) {
39    "@babel/helpers - typeof";
40
41    return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
42      return typeof o;
43    } : function (o) {
44      return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
45    }, _typeof(o);
46  }
47  function _classCallCheck(instance, Constructor) {
48    if (!(instance instanceof Constructor)) {
49      throw new TypeError("Cannot call a class as a function");
50    }
51  }
52  function _defineProperties(target, props) {
53    for (var i = 0; i < props.length; i++) {
54      var descriptor = props[i];
55      descriptor.enumerable = descriptor.enumerable || false;
56      descriptor.configurable = true;
57      if ("value" in descriptor) descriptor.writable = true;
58      Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
59    }
60  }
61  function _createClass(Constructor, protoProps, staticProps) {
62    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
63    if (staticProps) _defineProperties(Constructor, staticProps);
64    Object.defineProperty(Constructor, "prototype", {
65      writable: false
66    });
67    return Constructor;
68  }
69  function _defineProperty(obj, key, value) {
70    key = _toPropertyKey(key);
71    if (key in obj) {
72      Object.defineProperty(obj, key, {
73        value: value,
74        enumerable: true,
75        configurable: true,
76        writable: true
77      });
78    } else {
79      obj[key] = value;
80    }
81    return obj;
82  }
83  function _toConsumableArray(arr) {
84    return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
85  }
86  function _arrayWithoutHoles(arr) {
87    if (Array.isArray(arr)) return _arrayLikeToArray(arr);
88  }
89  function _iterableToArray(iter) {
90    if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
91  }
92  function _unsupportedIterableToArray(o, minLen) {
93    if (!o) return;
94    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
95    var n = Object.prototype.toString.call(o).slice(8, -1);
96    if (n === "Object" && o.constructor) n = o.constructor.name;
97    if (n === "Map" || n === "Set") return Array.from(o);
98    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
99  }
100  function _arrayLikeToArray(arr, len) {
101    if (len == null || len > arr.length) len = arr.length;
102    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
103    return arr2;
104  }
105  function _nonIterableSpread() {
106    throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
107  }
108  function _toPrimitive(input, hint) {
109    if (typeof input !== "object" || input === null) return input;
110    var prim = input[Symbol.toPrimitive];
111    if (prim !== undefined) {
112      var res = prim.call(input, hint || "default");
113      if (typeof res !== "object") return res;
114      throw new TypeError("@@toPrimitive must return a primitive value.");
115    }
116    return (hint === "string" ? String : Number)(input);
117  }
118  function _toPropertyKey(arg) {
119    var key = _toPrimitive(arg, "string");
120    return typeof key === "symbol" ? key : String(key);
121  }
122
vendor: 109,319 bytes, lines 123-3274
123  var IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';
124  var WINDOW = IS_BROWSER ? window : {};
125  var IS_TOUCH_DEVICE = IS_BROWSER && WINDOW.document.documentElement ? 'ontouchstart' in WINDOW.document.documentElement : false;
126  var HAS_POINTER_EVENT = IS_BROWSER ? 'PointerEvent' in WINDOW : false;
127  var NAMESPACE = 'cropper';
128
129  // Actions
130  var ACTION_ALL = 'all';
131  var ACTION_CROP = 'crop';
132  var ACTION_MOVE = 'move';
133  var ACTION_ZOOM = 'zoom';
134  var ACTION_EAST = 'e';
135  var ACTION_WEST = 'w';
136  var ACTION_SOUTH = 's';
137  var ACTION_NORTH = 'n';
138  var ACTION_NORTH_EAST = 'ne';
139  var ACTION_NORTH_WEST = 'nw';
140  var ACTION_SOUTH_EAST = 'se';
141  var ACTION_SOUTH_WEST = 'sw';
142
143  // Classes
144  var CLASS_CROP = "".concat(NAMESPACE, "-crop");
145  var CLASS_DISABLED = "".concat(NAMESPACE, "-disabled");
146  var CLASS_HIDDEN = "".concat(NAMESPACE, "-hidden");
147  var CLASS_HIDE = "".concat(NAMESPACE, "-hide");
148  var CLASS_INVISIBLE = "".concat(NAMESPACE, "-invisible");
149  var CLASS_MODAL = "".concat(NAMESPACE, "-modal");
150  var CLASS_MOVE = "".concat(NAMESPACE, "-move");
151
152  // Data keys
153  var DATA_ACTION = "".concat(NAMESPACE, "Action");
154  var DATA_PREVIEW = "".concat(NAMESPACE, "Preview");
155
156  // Drag modes
157  var DRAG_MODE_CROP = 'crop';
158  var DRAG_MODE_MOVE = 'move';
159  var DRAG_MODE_NONE = 'none';
160
161  // Events
162  var EVENT_CROP = 'crop';
163  var EVENT_CROP_END = 'cropend';
164  var EVENT_CROP_MOVE = 'cropmove';
165  var EVENT_CROP_START = 'cropstart';
166  var EVENT_DBLCLICK = 'dblclick';
167  var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';
168  var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';
169  var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup';
170  var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START;
171  var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE;
172  var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END;
173  var EVENT_READY = 'ready';
174  var EVENT_RESIZE = 'resize';
175  var EVENT_WHEEL = 'wheel';
176  var EVENT_ZOOM = 'zoom';
177
178  // Mime types
179  var MIME_TYPE_JPEG = 'image/jpeg';
180
181  // RegExps
182  var REGEXP_ACTIONS = /^e|w|s|n|se|sw|ne|nw|all|crop|move|zoom$/;
183  var REGEXP_DATA_URL = /^data:/;
184  var REGEXP_DATA_URL_JPEG = /^data:image\/jpeg;base64,/;
185  var REGEXP_TAG_NAME = /^img|canvas$/i;
186
187  // Misc
188  // Inspired by the default width and height of a canvas element.
189  var MIN_CONTAINER_WIDTH = 200;
190  var MIN_CONTAINER_HEIGHT = 100;
191
192  var DEFAULTS = {
193    // Define the view mode of the cropper
194    viewMode: 0,
195    // 0, 1, 2, 3
196
197    // Define the dragging mode of the cropper
198    dragMode: DRAG_MODE_CROP,
199    // 'crop', 'move' or 'none'
200
201    // Define the initial aspect ratio of the crop box
202    initialAspectRatio: NaN,
203    // Define the aspect ratio of the crop box
204    aspectRatio: NaN,
205    // An object with the previous cropping result data
206    data: null,
207    // A selector for adding extra containers to preview
208    preview: '',
209    // Re-render the cropper when resize the window
210    responsive: true,
211    // Restore the cropped area after resize the window
212    restore: true,
213    // Check if the current image is a cross-origin image
214    checkCrossOrigin: true,
215    // Check the current image's Exif Orientation information
216    checkOrientation: true,
217    // Show the black modal
218    modal: true,
219    // Show the dashed lines for guiding
220    guides: true,
221    // Show the center indicator for guiding
222    center: true,
223    // Show the white modal to highlight the crop box
224    highlight: true,
225    // Show the grid background
226    background: true,
227    // Enable to crop the image automatically when initialize
228    autoCrop: true,
229    // Define the percentage of automatic cropping area when initializes
230    autoCropArea: 0.8,
231    // Enable to move the image
232    movable: true,
233    // Enable to rotate the image
234    rotatable: true,
235    // Enable to scale the image
236    scalable: true,
237    // Enable to zoom the image
238    zoomable: true,
239    // Enable to zoom the image by dragging touch
240    zoomOnTouch: true,
241    // Enable to zoom the image by wheeling mouse
242    zoomOnWheel: true,
243    // Define zoom ratio when zoom the image by wheeling mouse
244    wheelZoomRatio: 0.1,
245    // Enable to move the crop box
246    cropBoxMovable: true,
247    // Enable to resize the crop box
248    cropBoxResizable: true,
249    // Toggle drag mode between "crop" and "move" when click twice on the cropper
250    toggleDragModeOnDblclick: true,
251    // Size limitation
252    minCanvasWidth: 0,
253    minCanvasHeight: 0,
254    minCropBoxWidth: 0,
255    minCropBoxHeight: 0,
256    minContainerWidth: MIN_CONTAINER_WIDTH,
257    minContainerHeight: MIN_CONTAINER_HEIGHT,
258    // Shortcuts of events
259    ready: null,
260    cropstart: null,
261    cropmove: null,
262    cropend: null,
263    crop: null,
264    zoom: null
265  };
266
267  var TEMPLATE = '<div class="cropper-container" touch-action="none">' + '<div class="cropper-wrap-box">' + '<div class="cropper-canvas"></div>' + '</div>' + '<div class="cropper-drag-box"></div>' + '<div class="cropper-crop-box">' + '<span class="cropper-view-box"></span>' + '<span class="cropper-dashed dashed-h"></span>' + '<span class="cropper-dashed dashed-v"></span>' + '<span class="cropper-center"></span>' + '<span class="cropper-face"></span>' + '<span class="cropper-line line-e" data-cropper-action="e"></span>' + '<span class="cropper-line line-n" data-cropper-action="n"></span>' + '<span class="cropper-line line-w" data-cropper-action="w"></span>' + '<span class="cropper-line line-s" data-cropper-action="s"></span>' + '<span class="cropper-point point-e" data-cropper-action="e"></span>' + '<span class="cropper-point point-n" data-cropper-action="n"></span>' + '<span class="cropper-point point-w" data-cropper-action="w"></span>' + '<span class="cropper-point point-s" data-cropper-action="s"></span>' + '<span class="cropper-point point-ne" data-cropper-action="ne"></span>' + '<span class="cropper-point point-nw" data-cropper-action="nw"></span>' + '<span class="cropper-point point-sw" data-cropper-action="sw"></span>' + '<span class="cropper-point point-se" data-cropper-action="se"></span>' + '</div>' + '</div>';
268
269  /**
270   * Check if the given value is not a number.
271   */
272  var isNaN = Number.isNaN || WINDOW.isNaN;
273
274  /**
275   * Check if the given value is a number.
276   * @param {*} value - The value to check.
277   * @returns {boolean} Returns `true` if the given value is a number, else `false`.
278   */
279  function isNumber(value) {
280    return typeof value === 'number' && !isNaN(value);
281  }
282
283  /**
284   * Check if the given value is a positive number.
285   * @param {*} value - The value to check.
286   * @returns {boolean} Returns `true` if the given value is a positive number, else `false`.
287   */
288  var isPositiveNumber = function isPositiveNumber(value) {
289    return value > 0 && value < Infinity;
290  };
291
292  /**
293   * Check if the given value is undefined.
294   * @param {*} value - The value to check.
295   * @returns {boolean} Returns `true` if the given value is undefined, else `false`.
296   */
297  function isUndefined(value) {
298    return typeof value === 'undefined';
299  }
300
301  /**
302   * Check if the given value is an object.
303   * @param {*} value - The value to check.
304   * @returns {boolean} Returns `true` if the given value is an object, else `false`.
305   */
306  function isObject(value) {
307    return _typeof(value) === 'object' && value !== null;
308  }
309  var hasOwnProperty = Object.prototype.hasOwnProperty;
310
311  /**
312   * Check if the given value is a plain object.
313   * @param {*} value - The value to check.
314   * @returns {boolean} Returns `true` if the given value is a plain object, else `false`.
315   */
316  function isPlainObject(value) {
317    if (!isObject(value)) {
318      return false;
319    }
320    try {
321      var _constructor = value.constructor;
322      var prototype = _constructor.prototype;
323      return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');
324    } catch (error) {
325      return false;
326    }
327  }
328
329  /**
330   * Check if the given value is a function.
331   * @param {*} value - The value to check.
332   * @returns {boolean} Returns `true` if the given value is a function, else `false`.
333   */
334  function isFunction(value) {
335    return typeof value === 'function';
336  }
337  var slice = Array.prototype.slice;
338
339  /**
340   * Convert array-like or iterable object to an array.
341   * @param {*} value - The value to convert.
342   * @returns {Array} Returns a new array.
343   */
344  function toArray(value) {
345    return Array.from ? Array.from(value) : slice.call(value);
346  }
347
348  /**
349   * Iterate the given data.
350   * @param {*} data - The data to iterate.
351   * @param {Function} callback - The process function for each element.
352   * @returns {*} The original data.
353   */
354  function forEach(data, callback) {
355    if (data && isFunction(callback)) {
356      if (Array.isArray(data) || isNumber(data.length) /* array-like */) {
357        toArray(data).forEach(function (value, key) {
358          callback.call(data, value, key, data);
359        });
360      } else if (isObject(data)) {
361        Object.keys(data).forEach(function (key) {
362          callback.call(data, data[key], key, data);
363        });
364      }
365    }
366    return data;
367  }
368
369  /**
370   * Extend the given object.
371   * @param {*} target - The target object to extend.
372   * @param {*} args - The rest objects for merging to the target object.
373   * @returns {Object} The extended object.
374   */
375  var assign = Object.assign || function assign(target) {
376    for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
377      args[_key - 1] = arguments[_key];
378    }
379    if (isObject(target) && args.length > 0) {
380      args.forEach(function (arg) {
381        if (isObject(arg)) {
382          Object.keys(arg).forEach(function (key) {
383            target[key] = arg[key];
384          });
385        }
386      });
387    }
388    return target;
389  };
390  var REGEXP_DECIMALS = /\.\d*(?:0|9){12}\d*$/;
391
392  /**
393   * Normalize decimal number.
394   * Check out {@link https://0.30000000000000004.com/}
395   * @param {number} value - The value to normalize.
396   * @param {number} [times=100000000000] - The times for normalizing.
397   * @returns {number} Returns the normalized number.
398   */
399  function normalizeDecimalNumber(value) {
400    var times = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100000000000;
401    return REGEXP_DECIMALS.test(value) ? Math.round(value * times) / times : value;
402  }
403  var REGEXP_SUFFIX = /^width|height|left|top|marginLeft|marginTop$/;
404
405  /**
406   * Apply styles to the given element.
407   * @param {Element} element - The target element.
408   * @param {Object} styles - The styles for applying.
409   */
410  function setStyle(element, styles) {
411    var style = element.style;
412    forEach(styles, function (value, property) {
413      if (REGEXP_SUFFIX.test(property) && isNumber(value)) {
414        value = "".concat(value, "px");
415      }
416      style[property] = value;
417    });
418  }
419
420  /**
421   * Check if the given element has a special class.
422   * @param {Element} element - The element to check.
423   * @param {string} value - The class to search.
424   * @returns {boolean} Returns `true` if the special class was found.
425   */
426  function hasClass(element, value) {
427    return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1;
428  }
429
430  /**
431   * Add classes to the given element.
432   * @param {Element} element - The target element.
433   * @param {string} value - The classes to be added.
434   */
435  function addClass(element, value) {
436    if (!value) {
437      return;
438    }
439    if (isNumber(element.length)) {
440      forEach(element, function (elem) {
441        addClass(elem, value);
442      });
443      return;
444    }
445    if (element.classList) {
446      element.classList.add(value);
447      return;
448    }
449    var className = element.className.trim();
450    if (!className) {
451      element.className = value;
452    } else if (className.indexOf(value) < 0) {
453      element.className = "".concat(className, " ").concat(value);
454    }
455  }
456
457  /**
458   * Remove classes from the given element.
459   * @param {Element} element - The target element.
460   * @param {string} value - The classes to be removed.
461   */
462  function removeClass(element, value) {
463    if (!value) {
464      return;
465    }
466    if (isNumber(element.length)) {
467      forEach(element, function (elem) {
468        removeClass(elem, value);
469      });
470      return;
471    }
472    if (element.classList) {
473      element.classList.remove(value);
474      return;
475    }
476    if (element.className.indexOf(value) >= 0) {
477      element.className = element.className.replace(value, '');
478    }
479  }
480
481  /**
482   * Add or remove classes from the given element.
483   * @param {Element} element - The target element.
484   * @param {string} value - The classes to be toggled.
485   * @param {boolean} added - Add only.
486   */
487  function toggleClass(element, value, added) {
488    if (!value) {
489      return;
490    }
491    if (isNumber(element.length)) {
492      forEach(element, function (elem) {
493        toggleClass(elem, value, added);
494      });
495      return;
496    }
497
498    // IE10-11 doesn't support the second parameter of `classList.toggle`
499    if (added) {
500      addClass(element, value);
501    } else {
502      removeClass(element, value);
503    }
504  }
505  var REGEXP_CAMEL_CASE = /([a-z\d])([A-Z])/g;
506
507  /**
508   * Transform the given string from camelCase to kebab-case
509   * @param {string} value - The value to transform.
510   * @returns {string} The transformed value.
511   */
512  function toParamCase(value) {
513    return value.replace(REGEXP_CAMEL_CASE, '$1-$2').toLowerCase();
514  }
515
516  /**
517   * Get data from the given element.
518   * @param {Element} element - The target element.
519   * @param {string} name - The data key to get.
520   * @returns {string} The data value.
521   */
522  function getData(element, name) {
523    if (isObject(element[name])) {
524      return element[name];
525    }
526    if (element.dataset) {
527      return element.dataset[name];
528    }
529    return element.getAttribute("data-".concat(toParamCase(name)));
530  }
531
532  /**
533   * Set data to the given element.
534   * @param {Element} element - The target element.
535   * @param {string} name - The data key to set.
536   * @param {string} data - The data value.
537   */
538  function setData(element, name, data) {
539    if (isObject(data)) {
540      element[name] = data;
541    } else if (element.dataset) {
542      element.dataset[name] = data;
543    } else {
544      element.setAttribute("data-".concat(toParamCase(name)), data);
545    }
546  }
547
548  /**
549   * Remove data from the given element.
550   * @param {Element} element - The target element.
551   * @param {string} name - The data key to remove.
552   */
553  function removeData(element, name) {
554    if (isObject(element[name])) {
555      try {
556        delete element[name];
557      } catch (error) {
558        element[name] = undefined;
559      }
560    } else if (element.dataset) {
561      // #128 Safari not allows to delete dataset property
562      try {
563        delete element.dataset[name];
564      } catch (error) {
565        element.dataset[name] = undefined;
566      }
567    } else {
568      element.removeAttribute("data-".concat(toParamCase(name)));
569    }
570  }
571  var REGEXP_SPACES = /\s\s*/;
572  var onceSupported = function () {
573    var supported = false;
574    if (IS_BROWSER) {
575      var once = false;
576      var listener = function listener() {};
577      var options = Object.defineProperty({}, 'once', {
578        get: function get() {
579          supported = true;
580          return once;
581        },
582        /**
583         * This setter can fix a `TypeError` in strict mode
584         * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only}
585         * @param {boolean} value - The value to set
586         */
587        set: function set(value) {
588          once = value;
589        }
590      });
591      WINDOW.addEventListener('test', listener, options);
592      WINDOW.removeEventListener('test', listener, options);
593    }
594    return supported;
595  }();
596
597  /**
598   * Remove event listener from the target element.
599   * @param {Element} element - The event target.
600   * @param {string} type - The event type(s).
601   * @param {Function} listener - The event listener.
602   * @param {Object} options - The event options.
603   */
604  function removeListener(element, type, listener) {
605    var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
606    var handler = listener;
607    type.trim().split(REGEXP_SPACES).forEach(function (event) {
608      if (!onceSupported) {
609        var listeners = element.listeners;
610        if (listeners && listeners[event] && listeners[event][listener]) {
611          handler = listeners[event][listener];
612          delete listeners[event][listener];
613          if (Object.keys(listeners[event]).length === 0) {
614            delete listeners[event];
615          }
616          if (Object.keys(listeners).length === 0) {
617            delete element.listeners;
618          }
619        }
620      }
621      element.removeEventListener(event, handler, options);
622    });
623  }
624
625  /**
626   * Add event listener to the target element.
627   * @param {Element} element - The event target.
628   * @param {string} type - The event type(s).
629   * @param {Function} listener - The event listener.
630   * @param {Object} options - The event options.
631   */
632  function addListener(element, type, listener) {
633    var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
634    var _handler = listener;
635    type.trim().split(REGEXP_SPACES).forEach(function (event) {
636      if (options.once && !onceSupported) {
637        var _element$listeners = element.listeners,
638          listeners = _element$listeners === void 0 ? {} : _element$listeners;
639        _handler = function handler() {
640          delete listeners[event][listener];
641          element.removeEventListener(event, _handler, options);
642          for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
643            args[_key2] = arguments[_key2];
644          }
645          listener.apply(element, args);
646        };
647        if (!listeners[event]) {
648          listeners[event] = {};
649        }
650        if (listeners[event][listener]) {
651          element.removeEventListener(event, listeners[event][listener], options);
652        }
653        listeners[event][listener] = _handler;
654        element.listeners = listeners;
655      }
656      element.addEventListener(event, _handler, options);
657    });
658  }
659
660  /**
661   * Dispatch event on the target element.
662   * @param {Element} element - The event target.
663   * @param {string} type - The event type(s).
664   * @param {Object} data - The additional event data.
665   * @returns {boolean} Indicate if the event is default prevented or not.
666   */
667  function dispatchEvent(element, type, data) {
668    var event;
669
670    // Event and CustomEvent on IE9-11 are global objects, not constructors
671    if (isFunction(Event) && isFunction(CustomEvent)) {
672      event = new CustomEvent(type, {
673        detail: data,
674        bubbles: true,
675        cancelable: true
676      });
677    } else {
678      event = document.createEvent('CustomEvent');
679      event.initCustomEvent(type, true, true, data);
680    }
681    return element.dispatchEvent(event);
682  }
683
684  /**
685   * Get the offset base on the document.
686   * @param {Element} element - The target element.
687   * @returns {Object} The offset data.
688   */
689  function getOffset(element) {
690    var box = element.getBoundingClientRect();
691    return {
692      left: box.left + (window.pageXOffset - document.documentElement.clientLeft),
693      top: box.top + (window.pageYOffset - document.documentElement.clientTop)
694    };
695  }
696  var location = WINDOW.location;
697  var REGEXP_ORIGINS = /^(\w+:)\/\/([^:/?#]*):?(\d*)/i;
698
699  /**
700   * Check if the given URL is a cross origin URL.
701   * @param {string} url - The target URL.
702   * @returns {boolean} Returns `true` if the given URL is a cross origin URL, else `false`.
703   */
704  function isCrossOriginURL(url) {
705    var parts = url.match(REGEXP_ORIGINS);
706    return parts !== null && (parts[1] !== location.protocol || parts[2] !== location.hostname || parts[3] !== location.port);
707  }
708
709  /**
710   * Add timestamp to the given URL.
711   * @param {string} url - The target URL.
712   * @returns {string} The result URL.
713   */
714  function addTimestamp(url) {
715    var timestamp = "timestamp=".concat(new Date().getTime());
716    return url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp;
717  }
718
719  /**
720   * Get transforms base on the given object.
721   * @param {Object} obj - The target object.
722   * @returns {string} A string contains transform values.
723   */
724  function getTransforms(_ref) {
725    var rotate = _ref.rotate,
726      scaleX = _ref.scaleX,
727      scaleY = _ref.scaleY,
728      translateX = _ref.translateX,
729      translateY = _ref.translateY;
730    var values = [];
731    if (isNumber(translateX) && translateX !== 0) {
732      values.push("translateX(".concat(translateX, "px)"));
733    }
734    if (isNumber(translateY) && translateY !== 0) {
735      values.push("translateY(".concat(translateY, "px)"));
736    }
737
738    // Rotate should come first before scale to match orientation transform
739    if (isNumber(rotate) && rotate !== 0) {
740      values.push("rotate(".concat(rotate, "deg)"));
741    }
742    if (isNumber(scaleX) && scaleX !== 1) {
743      values.push("scaleX(".concat(scaleX, ")"));
744    }
745    if (isNumber(scaleY) && scaleY !== 1) {
746      values.push("scaleY(".concat(scaleY, ")"));
747    }
748    var transform = values.length ? values.join(' ') : 'none';
749    return {
750      WebkitTransform: transform,
751      msTransform: transform,
752      transform: transform
753    };
754  }
755
756  /**
757   * Get the max ratio of a group of pointers.
758   * @param {string} pointers - The target pointers.
759   * @returns {number} The result ratio.
760   */
761  function getMaxZoomRatio(pointers) {
762    var pointers2 = _objectSpread2({}, pointers);
763    var maxRatio = 0;
764    forEach(pointers, function (pointer, pointerId) {
765      delete pointers2[pointerId];
766      forEach(pointers2, function (pointer2) {
767        var x1 = Math.abs(pointer.startX - pointer2.startX);
768        var y1 = Math.abs(pointer.startY - pointer2.startY);
769        var x2 = Math.abs(pointer.endX - pointer2.endX);
770        var y2 = Math.abs(pointer.endY - pointer2.endY);
771        var z1 = Math.sqrt(x1 * x1 + y1 * y1);
772        var z2 = Math.sqrt(x2 * x2 + y2 * y2);
773        var ratio = (z2 - z1) / z1;
774        if (Math.abs(ratio) > Math.abs(maxRatio)) {
775          maxRatio = ratio;
776        }
777      });
778    });
779    return maxRatio;
780  }
781
782  /**
783   * Get a pointer from an event object.
784   * @param {Object} event - The target event object.
785   * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not.
786   * @returns {Object} The result pointer contains start and/or end point coordinates.
787   */
788  function getPointer(_ref2, endOnly) {
789    var pageX = _ref2.pageX,
790      pageY = _ref2.pageY;
791    var end = {
792      endX: pageX,
793      endY: pageY
794    };
795    return endOnly ? end : _objectSpread2({
796      startX: pageX,
797      startY: pageY
798    }, end);
799  }
800
801  /**
802   * Get the center point coordinate of a group of pointers.
803   * @param {Object} pointers - The target pointers.
804   * @returns {Object} The center point coordinate.
805   */
806  function getPointersCenter(pointers) {
807    var pageX = 0;
808    var pageY = 0;
809    var count = 0;
810    forEach(pointers, function (_ref3) {
811      var startX = _ref3.startX,
812        startY = _ref3.startY;
813      pageX += startX;
814      pageY += startY;
815      count += 1;
816    });
817    pageX /= count;
818    pageY /= count;
819    return {
820      pageX: pageX,
821      pageY: pageY
822    };
823  }
824
825  /**
826   * Get the max sizes in a rectangle under the given aspect ratio.
827   * @param {Object} data - The original sizes.
828   * @param {string} [type='contain'] - The adjust type.
829   * @returns {Object} The result sizes.
830   */
831  function getAdjustedSizes(_ref4) {
832    var aspectRatio = _ref4.aspectRatio,
833      height = _ref4.height,
834      width = _ref4.width;
835    var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'contain';
836    var isValidWidth = isPositiveNumber(width);
837    var isValidHeight = isPositiveNumber(height);
838    if (isValidWidth && isValidHeight) {
839      var adjustedWidth = height * aspectRatio;
840      if (type === 'contain' && adjustedWidth > width || type === 'cover' && adjustedWidth < width) {
841        height = width / aspectRatio;
842      } else {
843        width = height * aspectRatio;
844      }
845    } else if (isValidWidth) {
846      height = width / aspectRatio;
847    } else if (isValidHeight) {
848      width = height * aspectRatio;
849    }
850    return {
851      width: width,
852      height: height
853    };
854  }
855
856  /**
857   * Get the new sizes of a rectangle after rotated.
858   * @param {Object} data - The original sizes.
859   * @returns {Object} The result sizes.
860   */
861  function getRotatedSizes(_ref5) {
862    var width = _ref5.width,
863      height = _ref5.height,
864      degree = _ref5.degree;
865    degree = Math.abs(degree) % 180;
866    if (degree === 90) {
867      return {
868        width: height,
869        height: width
870      };
871    }
872    var arc = degree % 90 * Math.PI / 180;
873    var sinArc = Math.sin(arc);
874    var cosArc = Math.cos(arc);
875    var newWidth = width * cosArc + height * sinArc;
876    var newHeight = width * sinArc + height * cosArc;
877    return degree > 90 ? {
878      width: newHeight,
879      height: newWidth
880    } : {
881      width: newWidth,
882      height: newHeight
883    };
884  }
885
886  /**
887   * Get a canvas which drew the given image.
888   * @param {HTMLImageElement} image - The image for drawing.
889   * @param {Object} imageData - The image data.
890   * @param {Object} canvasData - The canvas data.
891   * @param {Object} options - The options.
892   * @returns {HTMLCanvasElement} The result canvas.
893   */
894  function getSourceCanvas(image, _ref6, _ref7, _ref8) {
895    var imageAspectRatio = _ref6.aspectRatio,
896      imageNaturalWidth = _ref6.naturalWidth,
897      imageNaturalHeight = _ref6.naturalHeight,
898      _ref6$rotate = _ref6.rotate,
899      rotate = _ref6$rotate === void 0 ? 0 : _ref6$rotate,
900      _ref6$scaleX = _ref6.scaleX,
901      scaleX = _ref6$scaleX === void 0 ? 1 : _ref6$scaleX,
902      _ref6$scaleY = _ref6.scaleY,
903      scaleY = _ref6$scaleY === void 0 ? 1 : _ref6$scaleY;
904    var aspectRatio = _ref7.aspectRatio,
905      naturalWidth = _ref7.naturalWidth,
906      naturalHeight = _ref7.naturalHeight;
907    var _ref8$fillColor = _ref8.fillColor,
908      fillColor = _ref8$fillColor === void 0 ? 'transparent' : _ref8$fillColor,
909      _ref8$imageSmoothingE = _ref8.imageSmoothingEnabled,
910      imageSmoothingEnabled = _ref8$imageSmoothingE === void 0 ? true : _ref8$imageSmoothingE,
911      _ref8$imageSmoothingQ = _ref8.imageSmoothingQuality,
912      imageSmoothingQuality = _ref8$imageSmoothingQ === void 0 ? 'low' : _ref8$imageSmoothingQ,
913      _ref8$maxWidth = _ref8.maxWidth,
914      maxWidth = _ref8$maxWidth === void 0 ? Infinity : _ref8$maxWidth,
915      _ref8$maxHeight = _ref8.maxHeight,
916      maxHeight = _ref8$maxHeight === void 0 ? Infinity : _ref8$maxHeight,
917      _ref8$minWidth = _ref8.minWidth,
918      minWidth = _ref8$minWidth === void 0 ? 0 : _ref8$minWidth,
919      _ref8$minHeight = _ref8.minHeight,
920      minHeight = _ref8$minHeight === void 0 ? 0 : _ref8$minHeight;
921    var canvas = document.createElement('canvas');
922    var context = canvas.getContext('2d');
923    var maxSizes = getAdjustedSizes({
924      aspectRatio: aspectRatio,
925      width: maxWidth,
926      height: maxHeight
927    });
928    var minSizes = getAdjustedSizes({
929      aspectRatio: aspectRatio,
930      width: minWidth,
931      height: minHeight
932    }, 'cover');
933    var width = Math.min(maxSizes.width, Math.max(minSizes.width, naturalWidth));
934    var height = Math.min(maxSizes.height, Math.max(minSizes.height, naturalHeight));
935
936    // Note: should always use image's natural sizes for drawing as
937    // imageData.naturalWidth === canvasData.naturalHeight when rotate % 180 === 90
938    var destMaxSizes = getAdjustedSizes({
939      aspectRatio: imageAspectRatio,
940      width: maxWidth,
941      height: maxHeight
942    });
943    var destMinSizes = getAdjustedSizes({
944      aspectRatio: imageAspectRatio,
945      width: minWidth,
946      height: minHeight
947    }, 'cover');
948    var destWidth = Math.min(destMaxSizes.width, Math.max(destMinSizes.width, imageNaturalWidth));
949    var destHeight = Math.min(destMaxSizes.height, Math.max(destMinSizes.height, imageNaturalHeight));
950    var params = [-destWidth / 2, -destHeight / 2, destWidth, destHeight];
951    canvas.width = normalizeDecimalNumber(width);
952    canvas.height = normalizeDecimalNumber(height);
953    context.fillStyle = fillColor;
954    context.fillRect(0, 0, width, height);
955    context.save();
956    context.translate(width / 2, height / 2);
957    context.rotate(rotate * Math.PI / 180);
958    context.scale(scaleX, scaleY);
959    context.imageSmoothingEnabled = imageSmoothingEnabled;
960    context.imageSmoothingQuality = imageSmoothingQuality;
961    context.drawImage.apply(context, [image].concat(_toConsumableArray(params.map(function (param) {
962      return Math.floor(normalizeDecimalNumber(param));
963    }))));
964    context.restore();
965    return canvas;
966  }
967  var fromCharCode = String.fromCharCode;
968
969  /**
970   * Get string from char code in data view.
971   * @param {DataView} dataView - The data view for read.
972   * @param {number} start - The start index.
973   * @param {number} length - The read length.
974   * @returns {string} The read result.
975   */
976  function getStringFromCharCode(dataView, start, length) {
977    var str = '';
978    length += start;
979    for (var i = start; i < length; i += 1) {
980      str += fromCharCode(dataView.getUint8(i));
981    }
982    return str;
983  }
984  var REGEXP_DATA_URL_HEAD = /^data:.*,/;
985
986  /**
987   * Transform Data URL to array buffer.
988   * @param {string} dataURL - The Data URL to transform.
989   * @returns {ArrayBuffer} The result array buffer.
990   */
991  function dataURLToArrayBuffer(dataURL) {
992    var base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, '');
993    var binary = atob(base64);
994    var arrayBuffer = new ArrayBuffer(binary.length);
995    var uint8 = new Uint8Array(arrayBuffer);
996    forEach(uint8, function (value, i) {
997      uint8[i] = binary.charCodeAt(i);
998    });
999    return arrayBuffer;
1000  }
1001
1002  /**
1003   * Transform array buffer to Data URL.
1004   * @param {ArrayBuffer} arrayBuffer - The array buffer to transform.
1005   * @param {string} mimeType - The mime type of the Data URL.
1006   * @returns {string} The result Data URL.
1007   */
1008  function arrayBufferToDataURL(arrayBuffer, mimeType) {
1009    var chunks = [];
1010
1011    // Chunk Typed Array for better performance (#435)
1012    var chunkSize = 8192;
1013    var uint8 = new Uint8Array(arrayBuffer);
1014    while (uint8.length > 0) {
1015      // XXX: Babel's `toConsumableArray` helper will throw error in IE or Safari 9
1016      // eslint-disable-next-line prefer-spread
1017      chunks.push(fromCharCode.apply(null, toArray(uint8.subarray(0, chunkSize))));
1018      uint8 = uint8.subarray(chunkSize);
1019    }
1020    return "data:".concat(mimeType, ";base64,").concat(btoa(chunks.join('')));
1021  }
1022
1023  /**
1024   * Get orientation value from given array buffer.
1025   * @param {ArrayBuffer} arrayBuffer - The array buffer to read.
1026   * @returns {number} The read orientation value.
1027   */
1028  function resetAndGetOrientation(arrayBuffer) {
1029    var dataView = new DataView(arrayBuffer);
1030    var orientation;
1031
1032    // Ignores range error when the image does not have correct Exif information
1033    try {
1034      var littleEndian;
1035      var app1Start;
1036      var ifdStart;
1037
1038      // Only handle JPEG image (start by 0xFFD8)
1039      if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
1040        var length = dataView.byteLength;
1041        var offset = 2;
1042        while (offset + 1 < length) {
1043          if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
1044            app1Start = offset;
1045            break;
1046          }
1047          offset += 1;
1048        }
1049      }
1050      if (app1Start) {
1051        var exifIDCode = app1Start + 4;
1052        var tiffOffset = app1Start + 10;
1053        if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
1054          var endianness = dataView.getUint16(tiffOffset);
1055          littleEndian = endianness === 0x4949;
1056          if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
1057            if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
1058              var firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
1059              if (firstIFDOffset >= 0x00000008) {
1060                ifdStart = tiffOffset + firstIFDOffset;
1061              }
1062            }
1063          }
1064        }
1065      }
1066      if (ifdStart) {
1067        var _length = dataView.getUint16(ifdStart, littleEndian);
1068        var _offset;
1069        var i;
1070        for (i = 0; i < _length; i += 1) {
1071          _offset = ifdStart + i * 12 + 2;
1072          if (dataView.getUint16(_offset, littleEndian) === 0x0112 /* Orientation */) {
1073            // 8 is the offset of the current tag's value
1074            _offset += 8;
1075
1076            // Get the original orientation value
1077            orientation = dataView.getUint16(_offset, littleEndian);
1078
1079            // Override the orientation with its default value
1080            dataView.setUint16(_offset, 1, littleEndian);
1081            break;
1082          }
1083        }
1084      }
1085    } catch (error) {
1086      orientation = 1;
1087    }
1088    return orientation;
1089  }
1090
1091  /**
1092   * Parse Exif Orientation value.
1093   * @param {number} orientation - The orientation to parse.
1094   * @returns {Object} The parsed result.
1095   */
1096  function parseOrientation(orientation) {
1097    var rotate = 0;
1098    var scaleX = 1;
1099    var scaleY = 1;
1100    switch (orientation) {
1101      // Flip horizontal
1102      case 2:
1103        scaleX = -1;
1104        break;
1105
1106      // Rotate left 180°
1107      case 3:
1108        rotate = -180;
1109        break;
1110
1111      // Flip vertical
1112      case 4:
1113        scaleY = -1;
1114        break;
1115
1116      // Flip vertical and rotate right 90°
1117      case 5:
1118        rotate = 90;
1119        scaleY = -1;
1120        break;
1121
1122      // Rotate right 90°
1123      case 6:
1124        rotate = 90;
1125        break;
1126
1127      // Flip horizontal and rotate right 90°
1128      case 7:
1129        rotate = 90;
1130        scaleX = -1;
1131        break;
1132
1133      // Rotate left 90°
1134      case 8:
1135        rotate = -90;
1136        break;
1137    }
1138    return {
1139      rotate: rotate,
1140      scaleX: scaleX,
1141      scaleY: scaleY
1142    };
1143  }
1144
1145  var render = {
1146    render: function render() {
1147      this.initContainer();
1148      this.initCanvas();
1149      this.initCropBox();
1150      this.renderCanvas();
1151      if (this.cropped) {
1152        this.renderCropBox();
1153      }
1154    },
1155    initContainer: function initContainer() {
1156      var element = this.element,
1157        options = this.options,
1158        container = this.container,
1159        cropper = this.cropper;
1160      var minWidth = Number(options.minContainerWidth);
1161      var minHeight = Number(options.minContainerHeight);
1162      addClass(cropper, CLASS_HIDDEN);
1163      removeClass(element, CLASS_HIDDEN);
1164      var containerData = {
1165        width: Math.max(container.offsetWidth, minWidth >= 0 ? minWidth : MIN_CONTAINER_WIDTH),
1166        height: Math.max(container.offsetHeight, minHeight >= 0 ? minHeight : MIN_CONTAINER_HEIGHT)
1167      };
1168      this.containerData = containerData;
1169      setStyle(cropper, {
1170        width: containerData.width,
1171        height: containerData.height
1172      });
1173      addClass(element, CLASS_HIDDEN);
1174      removeClass(cropper, CLASS_HIDDEN);
1175    },
1176    // Canvas (image wrapper)
1177    initCanvas: function initCanvas() {
1178      var containerData = this.containerData,
1179        imageData = this.imageData;
1180      var viewMode = this.options.viewMode;
1181      var rotated = Math.abs(imageData.rotate) % 180 === 90;
1182      var naturalWidth = rotated ? imageData.naturalHeight : imageData.naturalWidth;
1183      var naturalHeight = rotated ? imageData.naturalWidth : imageData.naturalHeight;
1184      var aspectRatio = naturalWidth / naturalHeight;
1185      var canvasWidth = containerData.width;
1186      var canvasHeight = containerData.height;
1187      if (containerData.height * aspectRatio > containerData.width) {
1188        if (viewMode === 3) {
1189          canvasWidth = containerData.height * aspectRatio;
1190        } else {
1191          canvasHeight = containerData.width / aspectRatio;
1192        }
1193      } else if (viewMode === 3) {
1194        canvasHeight = containerData.width / aspectRatio;
1195      } else {
1196        canvasWidth = containerData.height * aspectRatio;
1197      }
1198      var canvasData = {
1199        aspectRatio: aspectRatio,
1200        naturalWidth: naturalWidth,
1201        naturalHeight: naturalHeight,
1202        width: canvasWidth,
1203        height: canvasHeight
1204      };
1205      this.canvasData = canvasData;
1206      this.limited = viewMode === 1 || viewMode === 2;
1207      this.limitCanvas(true, true);
1208      canvasData.width = Math.min(Math.max(canvasData.width, canvasData.minWidth), canvasData.maxWidth);
1209      canvasData.height = Math.min(Math.max(canvasData.height, canvasData.minHeight), canvasData.maxHeight);
1210      canvasData.left = (containerData.width - canvasData.width) / 2;
1211      canvasData.top = (containerData.height - canvasData.height) / 2;
1212      canvasData.oldLeft = canvasData.left;
1213      canvasData.oldTop = canvasData.top;
1214      this.initialCanvasData = assign({}, canvasData);
1215    },
1216    limitCanvas: function limitCanvas(sizeLimited, positionLimited) {
1217      var options = this.options,
1218        containerData = this.containerData,
1219        canvasData = this.canvasData,
1220        cropBoxData = this.cropBoxData;
1221      var viewMode = options.viewMode;
1222      var aspectRatio = canvasData.aspectRatio;
1223      var cropped = this.cropped && cropBoxData;
1224      if (sizeLimited) {
1225        var minCanvasWidth = Number(options.minCanvasWidth) || 0;
1226        var minCanvasHeight = Number(options.minCanvasHeight) || 0;
1227        if (viewMode > 1) {
1228          minCanvasWidth = Math.max(minCanvasWidth, containerData.width);
1229          minCanvasHeight = Math.max(minCanvasHeight, containerData.height);
1230          if (viewMode === 3) {
1231            if (minCanvasHeight * aspectRatio > minCanvasWidth) {
1232              minCanvasWidth = minCanvasHeight * aspectRatio;
1233            } else {
1234              minCanvasHeight = minCanvasWidth / aspectRatio;
1235            }
1236          }
1237        } else if (viewMode > 0) {
1238          if (minCanvasWidth) {
1239            minCanvasWidth = Math.max(minCanvasWidth, cropped ? cropBoxData.width : 0);
1240          } else if (minCanvasHeight) {
1241            minCanvasHeight = Math.max(minCanvasHeight, cropped ? cropBoxData.height : 0);
1242          } else if (cropped) {
1243            minCanvasWidth = cropBoxData.width;
1244            minCanvasHeight = cropBoxData.height;
1245            if (minCanvasHeight * aspectRatio > minCanvasWidth) {
1246              minCanvasWidth = minCanvasHeight * aspectRatio;
1247            } else {
1248              minCanvasHeight = minCanvasWidth / aspectRatio;
1249            }
1250          }
1251        }
1252        var _getAdjustedSizes = getAdjustedSizes({
1253          aspectRatio: aspectRatio,
1254          width: minCanvasWidth,
1255          height: minCanvasHeight
1256        });
1257        minCanvasWidth = _getAdjustedSizes.width;
1258        minCanvasHeight = _getAdjustedSizes.height;
1259        canvasData.minWidth = minCanvasWidth;
1260        canvasData.minHeight = minCanvasHeight;
1261        canvasData.maxWidth = Infinity;
1262        canvasData.maxHeight = Infinity;
1263      }
1264      if (positionLimited) {
1265        if (viewMode > (cropped ? 0 : 1)) {
1266          var newCanvasLeft = containerData.width - canvasData.width;
1267          var newCanvasTop = containerData.height - canvasData.height;
1268          canvasData.minLeft = Math.min(0, newCanvasLeft);
1269          canvasData.minTop = Math.min(0, newCanvasTop);
1270          canvasData.maxLeft = Math.max(0, newCanvasLeft);
1271          canvasData.maxTop = Math.max(0, newCanvasTop);
1272          if (cropped && this.limited) {
1273            canvasData.minLeft = Math.min(cropBoxData.left, cropBoxData.left + (cropBoxData.width - canvasData.width));
1274            canvasData.minTop = Math.min(cropBoxData.top, cropBoxData.top + (cropBoxData.height - canvasData.height));
1275            canvasData.maxLeft = cropBoxData.left;
1276            canvasData.maxTop = cropBoxData.top;
1277            if (viewMode === 2) {
1278              if (canvasData.width >= containerData.width) {
1279                canvasData.minLeft = Math.min(0, newCanvasLeft);
1280                canvasData.maxLeft = Math.max(0, newCanvasLeft);
1281              }
1282              if (canvasData.height >= containerData.height) {
1283                canvasData.minTop = Math.min(0, newCanvasTop);
1284                canvasData.maxTop = Math.max(0, newCanvasTop);
1285              }
1286            }
1287          }
1288        } else {
1289          canvasData.minLeft = -canvasData.width;
1290          canvasData.minTop = -canvasData.height;
1291          canvasData.maxLeft = containerData.width;
1292          canvasData.maxTop = containerData.height;
1293        }
1294      }
1295    },
1296    renderCanvas: function renderCanvas(changed, transformed) {
1297      var canvasData = this.canvasData,
1298        imageData = this.imageData;
1299      if (transformed) {
1300        var _getRotatedSizes = getRotatedSizes({
1301            width: imageData.naturalWidth * Math.abs(imageData.scaleX || 1),
1302            height: imageData.naturalHeight * Math.abs(imageData.scaleY || 1),
1303            degree: imageData.rotate || 0
1304          }),
1305          naturalWidth = _getRotatedSizes.width,
1306          naturalHeight = _getRotatedSizes.height;
1307        var width = canvasData.width * (naturalWidth / canvasData.naturalWidth);
1308        var height = canvasData.height * (naturalHeight / canvasData.naturalHeight);
1309        canvasData.left -= (width - canvasData.width) / 2;
1310        canvasData.top -= (height - canvasData.height) / 2;
1311        canvasData.width = width;
1312        canvasData.height = height;
1313        canvasData.aspectRatio = naturalWidth / naturalHeight;
1314        canvasData.naturalWidth = naturalWidth;
1315        canvasData.naturalHeight = naturalHeight;
1316        this.limitCanvas(true, false);
1317      }
1318      if (canvasData.width > canvasData.maxWidth || canvasData.width < canvasData.minWidth) {
1319        canvasData.left = canvasData.oldLeft;
1320      }
1321      if (canvasData.height > canvasData.maxHeight || canvasData.height < canvasData.minHeight) {
1322        canvasData.top = canvasData.oldTop;
1323      }
1324      canvasData.width = Math.min(Math.max(canvasData.width, canvasData.minWidth), canvasData.maxWidth);
1325      canvasData.height = Math.min(Math.max(canvasData.height, canvasData.minHeight), canvasData.maxHeight);
1326      this.limitCanvas(false, true);
1327      canvasData.left = Math.min(Math.max(canvasData.left, canvasData.minLeft), canvasData.maxLeft);
1328      canvasData.top = Math.min(Math.max(canvasData.top, canvasData.minTop), canvasData.maxTop);
1329      canvasData.oldLeft = canvasData.left;
1330      canvasData.oldTop = canvasData.top;
1331      setStyle(this.canvas, assign({
1332        width: canvasData.width,
1333        height: canvasData.height
1334      }, getTransforms({
1335        translateX: canvasData.left,
1336        translateY: canvasData.top
1337      })));
1338      this.renderImage(changed);
1339      if (this.cropped && this.limited) {
1340        this.limitCropBox(true, true);
1341      }
1342    },
1343    renderImage: function renderImage(changed) {
1344      var canvasData = this.canvasData,
1345        imageData = this.imageData;
1346      var width = imageData.naturalWidth * (canvasData.width / canvasData.naturalWidth);
1347      var height = imageData.naturalHeight * (canvasData.height / canvasData.naturalHeight);
1348      assign(imageData, {
1349        width: width,
1350        height: height,
1351        left: (canvasData.width - width) / 2,
1352        top: (canvasData.height - height) / 2
1353      });
1354      setStyle(this.image, assign({
1355        width: imageData.width,
1356        height: imageData.height
1357      }, getTransforms(assign({
1358        translateX: imageData.left,
1359        translateY: imageData.top
1360      }, imageData))));
1361      if (changed) {
1362        this.output();
1363      }
1364    },
1365    initCropBox: function initCropBox() {
1366      var options = this.options,
1367        canvasData = this.canvasData;
1368      var aspectRatio = options.aspectRatio || options.initialAspectRatio;
1369      var autoCropArea = Number(options.autoCropArea) || 0.8;
1370      var cropBoxData = {
1371        width: canvasData.width,
1372        height: canvasData.height
1373      };
1374      if (aspectRatio) {
1375        if (canvasData.height * aspectRatio > canvasData.width) {
1376          cropBoxData.height = cropBoxData.width / aspectRatio;
1377        } else {
1378          cropBoxData.width = cropBoxData.height * aspectRatio;
1379        }
1380      }
1381      this.cropBoxData = cropBoxData;
1382      this.limitCropBox(true, true);
1383
1384      // Initialize auto crop area
1385      cropBoxData.width = Math.min(Math.max(cropBoxData.width, cropBoxData.minWidth), cropBoxData.maxWidth);
1386      cropBoxData.height = Math.min(Math.max(cropBoxData.height, cropBoxData.minHeight), cropBoxData.maxHeight);
1387
1388      // The width/height of auto crop area must large than "minWidth/Height"
1389      cropBoxData.width = Math.max(cropBoxData.minWidth, cropBoxData.width * autoCropArea);
1390      cropBoxData.height = Math.max(cropBoxData.minHeight, cropBoxData.height * autoCropArea);
1391      cropBoxData.left = canvasData.left + (canvasData.width - cropBoxData.width) / 2;
1392      cropBoxData.top = canvasData.top + (canvasData.height - cropBoxData.height) / 2;
1393      cropBoxData.oldLeft = cropBoxData.left;
1394      cropBoxData.oldTop = cropBoxData.top;
1395      this.initialCropBoxData = assign({}, cropBoxData);
1396    },
1397    limitCropBox: function limitCropBox(sizeLimited, positionLimited) {
1398      var options = this.options,
1399        containerData = this.containerData,
1400        canvasData = this.canvasData,
1401        cropBoxData = this.cropBoxData,
1402        limited = this.limited;
1403      var aspectRatio = options.aspectRatio;
1404      if (sizeLimited) {
1405        var minCropBoxWidth = Number(options.minCropBoxWidth) || 0;
1406        var minCropBoxHeight = Number(options.minCropBoxHeight) || 0;
1407        var maxCropBoxWidth = limited ? Math.min(containerData.width, canvasData.width, canvasData.width + canvasData.left, containerData.width - canvasData.left) : containerData.width;
1408        var maxCropBoxHeight = limited ? Math.min(containerData.height, canvasData.height, canvasData.height + canvasData.top, containerData.height - canvasData.top) : containerData.height;
1409
1410        // The min/maxCropBoxWidth/Height must be less than container's width/height
1411        minCropBoxWidth = Math.min(minCropBoxWidth, containerData.width);
1412        minCropBoxHeight = Math.min(minCropBoxHeight, containerData.height);
1413        if (aspectRatio) {
1414          if (minCropBoxWidth && minCropBoxHeight) {
1415            if (minCropBoxHeight * aspectRatio > minCropBoxWidth) {
1416              minCropBoxHeight = minCropBoxWidth / aspectRatio;
1417            } else {
1418              minCropBoxWidth = minCropBoxHeight * aspectRatio;
1419            }
1420          } else if (minCropBoxWidth) {
1421            minCropBoxHeight = minCropBoxWidth / aspectRatio;
1422          } else if (minCropBoxHeight) {
1423            minCropBoxWidth = minCropBoxHeight * aspectRatio;
1424          }
1425          if (maxCropBoxHeight * aspectRatio > maxCropBoxWidth) {
1426            maxCropBoxHeight = maxCropBoxWidth / aspectRatio;
1427          } else {
1428            maxCropBoxWidth = maxCropBoxHeight * aspectRatio;
1429          }
1430        }
1431
1432        // The minWidth/Height must be less than maxWidth/Height
1433        cropBoxData.minWidth = Math.min(minCropBoxWidth, maxCropBoxWidth);
1434        cropBoxData.minHeight = Math.min(minCropBoxHeight, maxCropBoxHeight);
1435        cropBoxData.maxWidth = maxCropBoxWidth;
1436        cropBoxData.maxHeight = maxCropBoxHeight;
1437      }
1438      if (positionLimited) {
1439        if (limited) {
1440          cropBoxData.minLeft = Math.max(0, canvasData.left);
1441          cropBoxData.minTop = Math.max(0, canvasData.top);
1442          cropBoxData.maxLeft = Math.min(containerData.width, canvasData.left + canvasData.width) - cropBoxData.width;
1443          cropBoxData.maxTop = Math.min(containerData.height, canvasData.top + canvasData.height) - cropBoxData.height;
1444        } else {
1445          cropBoxData.minLeft = 0;
1446          cropBoxData.minTop = 0;
1447          cropBoxData.maxLeft = containerData.width - cropBoxData.width;
1448          cropBoxData.maxTop = containerData.height - cropBoxData.height;
1449        }
1450      }
1451    },
1452    renderCropBox: function renderCropBox() {
1453      var options = this.options,
1454        containerData = this.containerData,
1455        cropBoxData = this.cropBoxData;
1456      if (cropBoxData.width > cropBoxData.maxWidth || cropBoxData.width < cropBoxData.minWidth) {
1457        cropBoxData.left = cropBoxData.oldLeft;
1458      }
1459      if (cropBoxData.height > cropBoxData.maxHeight || cropBoxData.height < cropBoxData.minHeight) {
1460        cropBoxData.top = cropBoxData.oldTop;
1461      }
1462      cropBoxData.width = Math.min(Math.max(cropBoxData.width, cropBoxData.minWidth), cropBoxData.maxWidth);
1463      cropBoxData.height = Math.min(Math.max(cropBoxData.height, cropBoxData.minHeight), cropBoxData.maxHeight);
1464      this.limitCropBox(false, true);
1465      cropBoxData.left = Math.min(Math.max(cropBoxData.left, cropBoxData.minLeft), cropBoxData.maxLeft);
1466      cropBoxData.top = Math.min(Math.max(cropBoxData.top, cropBoxData.minTop), cropBoxData.maxTop);
1467      cropBoxData.oldLeft = cropBoxData.left;
1468      cropBoxData.oldTop = cropBoxData.top;
1469      if (options.movable && options.cropBoxMovable) {
1470        // Turn to move the canvas when the crop box is equal to the container
1471        setData(this.face, DATA_ACTION, cropBoxData.width >= containerData.width && cropBoxData.height >= containerData.height ? ACTION_MOVE : ACTION_ALL);
1472      }
1473      setStyle(this.cropBox, assign({
1474        width: cropBoxData.width,
1475        height: cropBoxData.height
1476      }, getTransforms({
1477        translateX: cropBoxData.left,
1478        translateY: cropBoxData.top
1479      })));
1480      if (this.cropped && this.limited) {
1481        this.limitCanvas(true, true);
1482      }
1483      if (!this.disabled) {
1484        this.output();
1485      }
1486    },
1487    output: function output() {
1488      this.preview();
1489      dispatchEvent(this.element, EVENT_CROP, this.getData());
1490    }
1491  };
1492
1493  var preview = {
1494    initPreview: function initPreview() {
1495      var element = this.element,
1496        crossOrigin = this.crossOrigin;
1497      var preview = this.options.preview;
1498      var url = crossOrigin ? this.crossOriginUrl : this.url;
1499      var alt = element.alt || 'The image to preview';
1500      var image = document.createElement('img');
1501      if (crossOrigin) {
1502        image.crossOrigin = crossOrigin;
1503      }
1504      image.src = url;
1505      image.alt = alt;
1506      this.viewBox.appendChild(image);
1507      this.viewBoxImage = image;
1508      if (!preview) {
1509        return;
1510      }
1511      var previews = preview;
1512      if (typeof preview === 'string') {
1513        previews = element.ownerDocument.querySelectorAll(preview);
1514      } else if (preview.querySelector) {
1515        previews = [preview];
1516      }
1517      this.previews = previews;
1518      forEach(previews, function (el) {
1519        var img = document.createElement('img');
1520
1521        // Save the original size for recover
1522        setData(el, DATA_PREVIEW, {
1523          width: el.offsetWidth,
1524          height: el.offsetHeight,
1525          html: el.innerHTML
1526        });
1527        if (crossOrigin) {
1528          img.crossOrigin = crossOrigin;
1529        }
1530        img.src = url;
1531        img.alt = alt;
1532
1533        /**
1534         * Override img element styles
1535         * Add `display:block` to avoid margin top issue
1536         * Add `height:auto` to override `height` attribute on IE8
1537         * (Occur only when margin-top <= -height)
1538         */
1539        img.style.cssText = 'display:block;' + 'width:100%;' + 'height:auto;' + 'min-width:0!important;' + 'min-height:0!important;' + 'max-width:none!important;' + 'max-height:none!important;' + 'image-orientation:0deg!important;"';
1540        el.innerHTML = '';
1541        el.appendChild(img);
1542      });
1543    },
1544    resetPreview: function resetPreview() {
1545      forEach(this.previews, function (element) {
1546        var data = getData(element, DATA_PREVIEW);
1547        setStyle(element, {
1548          width: data.width,
1549          height: data.height
1550        });
1551        element.innerHTML = data.html;
1552        removeData(element, DATA_PREVIEW);
1553      });
1554    },
1555    preview: function preview() {
1556      var imageData = this.imageData,
1557        canvasData = this.canvasData,
1558        cropBoxData = this.cropBoxData;
1559      var cropBoxWidth = cropBoxData.width,
1560        cropBoxHeight = cropBoxData.height;
1561      var width = imageData.width,
1562        height = imageData.height;
1563      var left = cropBoxData.left - canvasData.left - imageData.left;
1564      var top = cropBoxData.top - canvasData.top - imageData.top;
1565      if (!this.cropped || this.disabled) {
1566        return;
1567      }
1568      setStyle(this.viewBoxImage, assign({
1569        width: width,
1570        height: height
1571      }, getTransforms(assign({
1572        translateX: -left,
1573        translateY: -top
1574      }, imageData))));
1575      forEach(this.previews, function (element) {
1576        var data = getData(element, DATA_PREVIEW);
1577        var originalWidth = data.width;
1578        var originalHeight = data.height;
1579        var newWidth = originalWidth;
1580        var newHeight = originalHeight;
1581        var ratio = 1;
1582        if (cropBoxWidth) {
1583          ratio = originalWidth / cropBoxWidth;
1584          newHeight = cropBoxHeight * ratio;
1585        }
1586        if (cropBoxHeight && newHeight > originalHeight) {
1587          ratio = originalHeight / cropBoxHeight;
1588          newWidth = cropBoxWidth * ratio;
1589          newHeight = originalHeight;
1590        }
1591        setStyle(element, {
1592          width: newWidth,
1593          height: newHeight
1594        });
1595        setStyle(element.getElementsByTagName('img')[0], assign({
1596          width: width * ratio,
1597          height: height * ratio
1598        }, getTransforms(assign({
1599          translateX: -left * ratio,
1600          translateY: -top * ratio
1601        }, imageData))));
1602      });
1603    }
1604  };
1605
1606  var events = {
1607    bind: function bind() {
1608      var element = this.element,
1609        options = this.options,
1610        cropper = this.cropper;
1611      if (isFunction(options.cropstart)) {
1612        addListener(element, EVENT_CROP_START, options.cropstart);
1613      }
1614      if (isFunction(options.cropmove)) {
1615        addListener(element, EVENT_CROP_MOVE, options.cropmove);
1616      }
1617      if (isFunction(options.cropend)) {
1618        addListener(element, EVENT_CROP_END, options.cropend);
1619      }
1620      if (isFunction(options.crop)) {
1621        addListener(element, EVENT_CROP, options.crop);
1622      }
1623      if (isFunction(options.zoom)) {
1624        addListener(element, EVENT_ZOOM, options.zoom);
1625      }
1626      addListener(cropper, EVENT_POINTER_DOWN, this.onCropStart = this.cropStart.bind(this));
1627      if (options.zoomable && options.zoomOnWheel) {
1628        addListener(cropper, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), {
1629          passive: false,
1630          capture: true
1631        });
1632      }
1633      if (options.toggleDragModeOnDblclick) {
1634        addListener(cropper, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this));
1635      }
1636      addListener(element.ownerDocument, EVENT_POINTER_MOVE, this.onCropMove = this.cropMove.bind(this));
1637      addListener(element.ownerDocument, EVENT_POINTER_UP, this.onCropEnd = this.cropEnd.bind(this));
1638      if (options.responsive) {
1639        addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this));
1640      }
1641    },
1642    unbind: function unbind() {
1643      var element = this.element,
1644        options = this.options,
1645        cropper = this.cropper;
1646      if (isFunction(options.cropstart)) {
1647        removeListener(element, EVENT_CROP_START, options.cropstart);
1648      }
1649      if (isFunction(options.cropmove)) {
1650        removeListener(element, EVENT_CROP_MOVE, options.cropmove);
1651      }
1652      if (isFunction(options.cropend)) {
1653        removeListener(element, EVENT_CROP_END, options.cropend);
1654      }
1655      if (isFunction(options.crop)) {
1656        removeListener(element, EVENT_CROP, options.crop);
1657      }
1658      if (isFunction(options.zoom)) {
1659        removeListener(element, EVENT_ZOOM, options.zoom);
1660      }
1661      removeListener(cropper, EVENT_POINTER_DOWN, this.onCropStart);
1662      if (options.zoomable && options.zoomOnWheel) {
1663        removeListener(cropper, EVENT_WHEEL, this.onWheel, {
1664          passive: false,
1665          capture: true
1666        });
1667      }
1668      if (options.toggleDragModeOnDblclick) {
1669        removeListener(cropper, EVENT_DBLCLICK, this.onDblclick);
1670      }
1671      removeListener(element.ownerDocument, EVENT_POINTER_MOVE, this.onCropMove);
1672      removeListener(element.ownerDocument, EVENT_POINTER_UP, this.onCropEnd);
1673      if (options.responsive) {
1674        removeListener(window, EVENT_RESIZE, this.onResize);
1675      }
1676    }
1677  };
1678
1679  var handlers = {
1680    resize: function resize() {
1681      if (this.disabled) {
1682        return;
1683      }
1684      var options = this.options,
1685        container = this.container,
1686        containerData = this.containerData;
1687      var ratioX = container.offsetWidth / containerData.width;
1688      var ratioY = container.offsetHeight / containerData.height;
1689      var ratio = Math.abs(ratioX - 1) > Math.abs(ratioY - 1) ? ratioX : ratioY;
1690
1691      // Resize when width changed or height changed
1692      if (ratio !== 1) {
1693        var canvasData;
1694        var cropBoxData;
1695        if (options.restore) {
1696          canvasData = this.getCanvasData();
1697          cropBoxData = this.getCropBoxData();
1698        }
1699        this.render();
1700        if (options.restore) {
1701          this.setCanvasData(forEach(canvasData, function (n, i) {
1702            canvasData[i] = n * ratio;
1703          }));
1704          this.setCropBoxData(forEach(cropBoxData, function (n, i) {
1705            cropBoxData[i] = n * ratio;
1706          }));
1707        }
1708      }
1709    },
1710    dblclick: function dblclick() {
1711      if (this.disabled || this.options.dragMode === DRAG_MODE_NONE) {
1712        return;
1713      }
1714      this.setDragMode(hasClass(this.dragBox, CLASS_CROP) ? DRAG_MODE_MOVE : DRAG_MODE_CROP);
1715    },
1716    wheel: function wheel(event) {
1717      var _this = this;
1718      var ratio = Number(this.options.wheelZoomRatio) || 0.1;
1719      var delta = 1;
1720      if (this.disabled) {
1721        return;
1722      }
1723      event.preventDefault();
1724
1725      // Limit wheel speed to prevent zoom too fast (#21)
1726      if (this.wheeling) {
1727        return;
1728      }
1729      this.wheeling = true;
1730      setTimeout(function () {
1731        _this.wheeling = false;
1732      }, 50);
1733      if (event.deltaY) {
1734        delta = event.deltaY > 0 ? 1 : -1;
1735      } else if (event.wheelDelta) {
1736        delta = -event.wheelDelta / 120;
1737      } else if (event.detail) {
1738        delta = event.detail > 0 ? 1 : -1;
1739      }
1740      this.zoom(-delta * ratio, event);
1741    },
1742    cropStart: function cropStart(event) {
1743      var buttons = event.buttons,
1744        button = event.button;
1745      if (this.disabled
1746
1747      // Handle mouse event and pointer event and ignore touch event
1748      || (event.type === 'mousedown' || event.type === 'pointerdown' && event.pointerType === 'mouse') && (
1749      // No primary button (Usually the left button)
1750      isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0
1751
1752      // Open context menu
1753      || event.ctrlKey)) {
1754        return;
1755      }
1756      var options = this.options,
1757        pointers = this.pointers;
1758      var action;
1759      if (event.changedTouches) {
1760        // Handle touch event
1761        forEach(event.changedTouches, function (touch) {
1762          pointers[touch.identifier] = getPointer(touch);
1763        });
1764      } else {
1765        // Handle mouse event and pointer event
1766        pointers[event.pointerId || 0] = getPointer(event);
1767      }
1768      if (Object.keys(pointers).length > 1 && options.zoomable && options.zoomOnTouch) {
1769        action = ACTION_ZOOM;
1770      } else {
1771        action = getData(event.target, DATA_ACTION);
1772      }
1773      if (!REGEXP_ACTIONS.test(action)) {
1774        return;
1775      }
1776      if (dispatchEvent(this.element, EVENT_CROP_START, {
1777        originalEvent: event,
1778        action: action
1779      }) === false) {
1780        return;
1781      }
1782
1783      // This line is required for preventing page zooming in iOS browsers
1784      event.preventDefault();
1785      this.action = action;
1786      this.cropping = false;
1787      if (action === ACTION_CROP) {
1788        this.cropping = true;
1789        addClass(this.dragBox, CLASS_MODAL);
1790      }
1791    },
1792    cropMove: function cropMove(event) {
1793      var action = this.action;
1794      if (this.disabled || !action) {
1795        return;
1796      }
1797      var pointers = this.pointers;
1798      event.preventDefault();
1799      if (dispatchEvent(this.element, EVENT_CROP_MOVE, {
1800        originalEvent: event,
1801        action: action
1802      }) === false) {
1803        return;
1804      }
1805      if (event.changedTouches) {
1806        forEach(event.changedTouches, function (touch) {
1807          // The first parameter should not be undefined (#432)
1808          assign(pointers[touch.identifier] || {}, getPointer(touch, true));
1809        });
1810      } else {
1811        assign(pointers[event.pointerId || 0] || {}, getPointer(event, true));
1812      }
1813      this.change(event);
1814    },
1815    cropEnd: function cropEnd(event) {
1816      if (this.disabled) {
1817        return;
1818      }
1819      var action = this.action,
1820        pointers = this.pointers;
1821      if (event.changedTouches) {
1822        forEach(event.changedTouches, function (touch) {
1823          delete pointers[touch.identifier];
1824        });
1825      } else {
1826        delete pointers[event.pointerId || 0];
1827      }
1828      if (!action) {
1829        return;
1830      }
1831      event.preventDefault();
1832      if (!Object.keys(pointers).length) {
1833        this.action = '';
1834      }
1835      if (this.cropping) {
1836        this.cropping = false;
1837        toggleClass(this.dragBox, CLASS_MODAL, this.cropped && this.options.modal);
1838      }
1839      dispatchEvent(this.element, EVENT_CROP_END, {
1840        originalEvent: event,
1841        action: action
1842      });
1843    }
1844  };
1845
1846  var change = {
1847    change: function change(event) {
1848      var options = this.options,
1849        canvasData = this.canvasData,
1850        containerData = this.containerData,
1851        cropBoxData = this.cropBoxData,
1852        pointers = this.pointers;
1853      var action = this.action;
1854      var aspectRatio = options.aspectRatio;
1855      var left = cropBoxData.left,
1856        top = cropBoxData.top,
1857        width = cropBoxData.width,
1858        height = cropBoxData.height;
1859      var right = left + width;
1860      var bottom = top + height;
1861      var minLeft = 0;
1862      var minTop = 0;
1863      var maxWidth = containerData.width;
1864      var maxHeight = containerData.height;
1865      var renderable = true;
1866      var offset;
1867
1868      // Locking aspect ratio in "free mode" by holding shift key
1869      if (!aspectRatio && event.shiftKey) {
1870        aspectRatio = width && height ? width / height : 1;
1871      }
1872      if (this.limited) {
1873        minLeft = cropBoxData.minLeft;
1874        minTop = cropBoxData.minTop;
1875        maxWidth = minLeft + Math.min(containerData.width, canvasData.width, canvasData.left + canvasData.width);
1876        maxHeight = minTop + Math.min(containerData.height, canvasData.height, canvasData.top + canvasData.height);
1877      }
1878      var pointer = pointers[Object.keys(pointers)[0]];
1879      var range = {
1880        x: pointer.endX - pointer.startX,
1881        y: pointer.endY - pointer.startY
1882      };
1883      var check = function check(side) {
1884        switch (side) {
1885          case ACTION_EAST:
1886            if (right + range.x > maxWidth) {
1887              range.x = maxWidth - right;
1888            }
1889            break;
1890          case ACTION_WEST:
1891            if (left + range.x < minLeft) {
1892              range.x = minLeft - left;
1893            }
1894            break;
1895          case ACTION_NORTH:
1896            if (top + range.y < minTop) {
1897              range.y = minTop - top;
1898            }
1899            break;
1900          case ACTION_SOUTH:
1901            if (bottom + range.y > maxHeight) {
1902              range.y = maxHeight - bottom;
1903            }
1904            break;
1905        }
1906      };
1907      switch (action) {
1908        // Move crop box
1909        case ACTION_ALL:
1910          left += range.x;
1911          top += range.y;
1912          break;
1913
1914        // Resize crop box
1915        case ACTION_EAST:
1916          if (range.x >= 0 && (right >= maxWidth || aspectRatio && (top <= minTop || bottom >= maxHeight))) {
1917            renderable = false;
1918            break;
1919          }
1920          check(ACTION_EAST);
1921          width += range.x;
1922          if (width < 0) {
1923            action = ACTION_WEST;
1924            width = -width;
1925            left -= width;
1926          }
1927          if (aspectRatio) {
1928            height = width / aspectRatio;
1929            top += (cropBoxData.height - height) / 2;
1930          }
1931          break;
1932        case ACTION_NORTH:
1933          if (range.y <= 0 && (top <= minTop || aspectRatio && (left <= minLeft || right >= maxWidth))) {
1934            renderable = false;
1935            break;
1936          }
1937          check(ACTION_NORTH);
1938          height -= range.y;
1939          top += range.y;
1940          if (height < 0) {
1941            action = ACTION_SOUTH;
1942            height = -height;
1943            top -= height;
1944          }
1945          if (aspectRatio) {
1946            width = height * aspectRatio;
1947            left += (cropBoxData.width - width) / 2;
1948          }
1949          break;
1950        case ACTION_WEST:
1951          if (range.x <= 0 && (left <= minLeft || aspectRatio && (top <= minTop || bottom >= maxHeight))) {
1952            renderable = false;
1953            break;
1954          }
1955          check(ACTION_WEST);
1956          width -= range.x;
1957          left += range.x;
1958          if (width < 0) {
1959            action = ACTION_EAST;
1960            width = -width;
1961            left -= width;
1962          }
1963          if (aspectRatio) {
1964            height = width / aspectRatio;
1965            top += (cropBoxData.height - height) / 2;
1966          }
1967          break;
1968        case ACTION_SOUTH:
1969          if (range.y >= 0 && (bottom >= maxHeight || aspectRatio && (left <= minLeft || right >= maxWidth))) {
1970            renderable = false;
1971            break;
1972          }
1973          check(ACTION_SOUTH);
1974          height += range.y;
1975          if (height < 0) {
1976            action = ACTION_NORTH;
1977            height = -height;
1978            top -= height;
1979          }
1980          if (aspectRatio) {
1981            width = height * aspectRatio;
1982            left += (cropBoxData.width - width) / 2;
1983          }
1984          break;
1985        case ACTION_NORTH_EAST:
1986          if (aspectRatio) {
1987            if (range.y <= 0 && (top <= minTop || right >= maxWidth)) {
1988              renderable = false;
1989              break;
1990            }
1991            check(ACTION_NORTH);
1992            height -= range.y;
1993            top += range.y;
1994            width = height * aspectRatio;
1995          } else {
1996            check(ACTION_NORTH);
1997            check(ACTION_EAST);
1998            if (range.x >= 0) {
1999              if (right < maxWidth) {
2000                width += range.x;
2001              } else if (range.y <= 0 && top <= minTop) {
2002                renderable = false;
2003              }
2004            } else {
2005              width += range.x;
2006            }
2007            if (range.y <= 0) {
2008              if (top > minTop) {
2009                height -= range.y;
2010                top += range.y;
2011              }
2012            } else {
2013              height -= range.y;
2014              top += range.y;
2015            }
2016          }
2017          if (width < 0 && height < 0) {
2018            action = ACTION_SOUTH_WEST;
2019            height = -height;
2020            width = -width;
2021            top -= height;
2022            left -= width;
2023          } else if (width < 0) {
2024            action = ACTION_NORTH_WEST;
2025            width = -width;
2026            left -= width;
2027          } else if (height < 0) {
2028            action = ACTION_SOUTH_EAST;
2029            height = -height;
2030            top -= height;
2031          }
2032          break;
2033        case ACTION_NORTH_WEST:
2034          if (aspectRatio) {
2035            if (range.y <= 0 && (top <= minTop || left <= minLeft)) {
2036              renderable = false;
2037              break;
2038            }
2039            check(ACTION_NORTH);
2040            height -= range.y;
2041            top += range.y;
2042            width = height * aspectRatio;
2043            left += cropBoxData.width - width;
2044          } else {
2045            check(ACTION_NORTH);
2046            check(ACTION_WEST);
2047            if (range.x <= 0) {
2048              if (left > minLeft) {
2049                width -= range.x;
2050                left += range.x;
2051              } else if (range.y <= 0 && top <= minTop) {
2052                renderable = false;
2053              }
2054            } else {
2055              width -= range.x;
2056              left += range.x;
2057            }
2058            if (range.y <= 0) {
2059              if (top > minTop) {
2060                height -= range.y;
2061                top += range.y;
2062              }
2063            } else {
2064              height -= range.y;
2065              top += range.y;
2066            }
2067          }
2068          if (width < 0 && height < 0) {
2069            action = ACTION_SOUTH_EAST;
2070            height = -height;
2071            width = -width;
2072            top -= height;
2073            left -= width;
2074          } else if (width < 0) {
2075            action = ACTION_NORTH_EAST;
2076            width = -width;
2077            left -= width;
2078          } else if (height < 0) {
2079            action = ACTION_SOUTH_WEST;
2080            height = -height;
2081            top -= height;
2082          }
2083          break;
2084        case ACTION_SOUTH_WEST:
2085          if (aspectRatio) {
2086            if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) {
2087              renderable = false;
2088              break;
2089            }
2090            check(ACTION_WEST);
2091            width -= range.x;
2092            left += range.x;
2093            height = width / aspectRatio;
2094          } else {
2095            check(ACTION_SOUTH);
2096            check(ACTION_WEST);
2097            if (range.x <= 0) {
2098              if (left > minLeft) {
2099                width -= range.x;
2100                left += range.x;
2101              } else if (range.y >= 0 && bottom >= maxHeight) {
2102                renderable = false;
2103              }
2104            } else {
2105              width -= range.x;
2106              left += range.x;
2107            }
2108            if (range.y >= 0) {
2109              if (bottom < maxHeight) {
2110                height += range.y;
2111              }
2112            } else {
2113              height += range.y;
2114            }
2115          }
2116          if (width < 0 && height < 0) {
2117            action = ACTION_NORTH_EAST;
2118            height = -height;
2119            width = -width;
2120            top -= height;
2121            left -= width;
2122          } else if (width < 0) {
2123            action = ACTION_SOUTH_EAST;
2124            width = -width;
2125            left -= width;
2126          } else if (height < 0) {
2127            action = ACTION_NORTH_WEST;
2128            height = -height;
2129            top -= height;
2130          }
2131          break;
2132        case ACTION_SOUTH_EAST:
2133          if (aspectRatio) {
2134            if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) {
2135              renderable = false;
2136              break;
2137            }
2138            check(ACTION_EAST);
2139            width += range.x;
2140            height = width / aspectRatio;
2141          } else {
2142            check(ACTION_SOUTH);
2143            check(ACTION_EAST);
2144            if (range.x >= 0) {
2145              if (right < maxWidth) {
2146                width += range.x;
2147              } else if (range.y >= 0 && bottom >= maxHeight) {
2148                renderable = false;
2149              }
2150            } else {
2151              width += range.x;
2152            }
2153            if (range.y >= 0) {
2154              if (bottom < maxHeight) {
2155                height += range.y;
2156              }
2157            } else {
2158              height += range.y;
2159            }
2160          }
2161          if (width < 0 && height < 0) {
2162            action = ACTION_NORTH_WEST;
2163            height = -height;
2164            width = -width;
2165            top -= height;
2166            left -= width;
2167          } else if (width < 0) {
2168            action = ACTION_SOUTH_WEST;
2169            width = -width;
2170            left -= width;
2171          } else if (height < 0) {
2172            action = ACTION_NORTH_EAST;
2173            height = -height;
2174            top -= height;
2175          }
2176          break;
2177
2178        // Move canvas
2179        case ACTION_MOVE:
2180          this.move(range.x, range.y);
2181          renderable = false;
2182          break;
2183
2184        // Zoom canvas
2185        case ACTION_ZOOM:
2186          this.zoom(getMaxZoomRatio(pointers), event);
2187          renderable = false;
2188          break;
2189
2190        // Create crop box
2191        case ACTION_CROP:
2192          if (!range.x || !range.y) {
2193            renderable = false;
2194            break;
2195          }
2196          offset = getOffset(this.cropper);
2197          left = pointer.startX - offset.left;
2198          top = pointer.startY - offset.top;
2199          width = cropBoxData.minWidth;
2200          height = cropBoxData.minHeight;
2201          if (range.x > 0) {
2202            action = range.y > 0 ? ACTION_SOUTH_EAST : ACTION_NORTH_EAST;
2203          } else if (range.x < 0) {
2204            left -= width;
2205            action = range.y > 0 ? ACTION_SOUTH_WEST : ACTION_NORTH_WEST;
2206          }
2207          if (range.y < 0) {
2208            top -= height;
2209          }
2210
2211          // Show the crop box if is hidden
2212          if (!this.cropped) {
2213            removeClass(this.cropBox, CLASS_HIDDEN);
2214            this.cropped = true;
2215            if (this.limited) {
2216              this.limitCropBox(true, true);
2217            }
2218          }
2219          break;
2220      }
2221      if (renderable) {
2222        cropBoxData.width = width;
2223        cropBoxData.height = height;
2224        cropBoxData.left = left;
2225        cropBoxData.top = top;
2226        this.action = action;
2227        this.renderCropBox();
2228      }
2229
2230      // Override
2231      forEach(pointers, function (p) {
2232        p.startX = p.endX;
2233        p.startY = p.endY;
2234      });
2235    }
2236  };
2237
2238  var methods = {
2239    // Show the crop box manually
2240    crop: function crop() {
2241      if (this.ready && !this.cropped && !this.disabled) {
2242        this.cropped = true;
2243        this.limitCropBox(true, true);
2244        if (this.options.modal) {
2245          addClass(this.dragBox, CLASS_MODAL);
2246        }
2247        removeClass(this.cropBox, CLASS_HIDDEN);
2248        this.setCropBoxData(this.initialCropBoxData);
2249      }
2250      return this;
2251    },
2252    // Reset the image and crop box to their initial states
2253    reset: function reset() {
2254      if (this.ready && !this.disabled) {
2255        this.imageData = assign({}, this.initialImageData);
2256        this.canvasData = assign({}, this.initialCanvasData);
2257        this.cropBoxData = assign({}, this.initialCropBoxData);
2258        this.renderCanvas();
2259        if (this.cropped) {
2260          this.renderCropBox();
2261        }
2262      }
2263      return this;
2264    },
2265    // Clear the crop box
2266    clear: function clear() {
2267      if (this.cropped && !this.disabled) {
2268        assign(this.cropBoxData, {
2269          left: 0,
2270          top: 0,
2271          width: 0,
2272          height: 0
2273        });
2274        this.cropped = false;
2275        this.renderCropBox();
2276        this.limitCanvas(true, true);
2277
2278        // Render canvas after crop box rendered
2279        this.renderCanvas();
2280        removeClass(this.dragBox, CLASS_MODAL);
2281        addClass(this.cropBox, CLASS_HIDDEN);
2282      }
2283      return this;
2284    },
2285    /**
2286     * Replace the image's src and rebuild the cropper
2287     * @param {string} url - The new URL.
2288     * @param {boolean} [hasSameSize] - Indicate if the new image has the same size as the old one.
2289     * @returns {Cropper} this
2290     */
2291    replace: function replace(url) {
2292      var hasSameSize = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
2293      if (!this.disabled && url) {
2294        if (this.isImg) {
2295          this.element.src = url;
2296        }
2297        if (hasSameSize) {
2298          this.url = url;
2299          this.image.src = url;
2300          if (this.ready) {
2301            this.viewBoxImage.src = url;
2302            forEach(this.previews, function (element) {
2303              element.getElementsByTagName('img')[0].src = url;
2304            });
2305          }
2306        } else {
2307          if (this.isImg) {
2308            this.replaced = true;
2309          }
2310          this.options.data = null;
2311          this.uncreate();
2312          this.load(url);
2313        }
2314      }
2315      return this;
2316    },
2317    // Enable (unfreeze) the cropper
2318    enable: function enable() {
2319      if (this.ready && this.disabled) {
2320        this.disabled = false;
2321        removeClass(this.cropper, CLASS_DISABLED);
2322      }
2323      return this;
2324    },
2325    // Disable (freeze) the cropper
2326    disable: function disable() {
2327      if (this.ready && !this.disabled) {
2328        this.disabled = true;
2329        addClass(this.cropper, CLASS_DISABLED);
2330      }
2331      return this;
2332    },
2333    /**
2334     * Destroy the cropper and remove the instance from the image
2335     * @returns {Cropper} this
2336     */
2337    destroy: function destroy() {
2338      var element = this.element;
2339      if (!element[NAMESPACE]) {
2340        return this;
2341      }
2342      element[NAMESPACE] = undefined;
2343      if (this.isImg && this.replaced) {
2344        element.src = this.originalUrl;
2345      }
2346      this.uncreate();
2347      return this;
2348    },
2349    /**
2350     * Move the canvas with relative offsets
2351     * @param {number} offsetX - The relative offset distance on the x-axis.
2352     * @param {number} [offsetY=offsetX] - The relative offset distance on the y-axis.
2353     * @returns {Cropper} this
2354     */
2355    move: function move(offsetX) {
2356      var offsetY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : offsetX;
2357      var _this$canvasData = this.canvasData,
2358        left = _this$canvasData.left,
2359        top = _this$canvasData.top;
2360      return this.moveTo(isUndefined(offsetX) ? offsetX : left + Number(offsetX), isUndefined(offsetY) ? offsetY : top + Number(offsetY));
2361    },
2362    /**
2363     * Move the canvas to an absolute point
2364     * @param {number} x - The x-axis coordinate.
2365     * @param {number} [y=x] - The y-axis coordinate.
2366     * @returns {Cropper} this
2367     */
2368    moveTo: function moveTo(x) {
2369      var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;
2370      var canvasData = this.canvasData;
2371      var changed = false;
2372      x = Number(x);
2373      y = Number(y);
2374      if (this.ready && !this.disabled && this.options.movable) {
2375        if (isNumber(x)) {
2376          canvasData.left = x;
2377          changed = true;
2378        }
2379        if (isNumber(y)) {
2380          canvasData.top = y;
2381          changed = true;
2382        }
2383        if (changed) {
2384          this.renderCanvas(true);
2385        }
2386      }
2387      return this;
2388    },
2389    /**
2390     * Zoom the canvas with a relative ratio
2391     * @param {number} ratio - The target ratio.
2392     * @param {Event} _originalEvent - The original event if any.
2393     * @returns {Cropper} this
2394     */
2395    zoom: function zoom(ratio, _originalEvent) {
2396      var canvasData = this.canvasData;
2397      ratio = Number(ratio);
2398      if (ratio < 0) {
2399        ratio = 1 / (1 - ratio);
2400      } else {
2401        ratio = 1 + ratio;
2402      }
2403      return this.zoomTo(canvasData.width * ratio / canvasData.naturalWidth, null, _originalEvent);
2404    },
2405    /**
2406     * Zoom the canvas to an absolute ratio
2407     * @param {number} ratio - The target ratio.
2408     * @param {Object} pivot - The zoom pivot point coordinate.
2409     * @param {Event} _originalEvent - The original event if any.
2410     * @returns {Cropper} this
2411     */
2412    zoomTo: function zoomTo(ratio, pivot, _originalEvent) {
2413      var options = this.options,
2414        canvasData = this.canvasData;
2415      var width = canvasData.width,
2416        height = canvasData.height,
2417        naturalWidth = canvasData.naturalWidth,
2418        naturalHeight = canvasData.naturalHeight;
2419      ratio = Number(ratio);
2420      if (ratio >= 0 && this.ready && !this.disabled && options.zoomable) {
2421        var newWidth = naturalWidth * ratio;
2422        var newHeight = naturalHeight * ratio;
2423        if (dispatchEvent(this.element, EVENT_ZOOM, {
2424          ratio: ratio,
2425          oldRatio: width / naturalWidth,
2426          originalEvent: _originalEvent
2427        }) === false) {
2428          return this;
2429        }
2430        if (_originalEvent) {
2431          var pointers = this.pointers;
2432          var offset = getOffset(this.cropper);
2433          var center = pointers && Object.keys(pointers).length ? getPointersCenter(pointers) : {
2434            pageX: _originalEvent.pageX,
2435            pageY: _originalEvent.pageY
2436          };
2437
2438          // Zoom from the triggering point of the event
2439          canvasData.left -= (newWidth - width) * ((center.pageX - offset.left - canvasData.left) / width);
2440          canvasData.top -= (newHeight - height) * ((center.pageY - offset.top - canvasData.top) / height);
2441        } else if (isPlainObject(pivot) && isNumber(pivot.x) && isNumber(pivot.y)) {
2442          canvasData.left -= (newWidth - width) * ((pivot.x - canvasData.left) / width);
2443          canvasData.top -= (newHeight - height) * ((pivot.y - canvasData.top) / height);
2444        } else {
2445          // Zoom from the center of the canvas
2446          canvasData.left -= (newWidth - width) / 2;
2447          canvasData.top -= (newHeight - height) / 2;
2448        }
2449        canvasData.width = newWidth;
2450        canvasData.height = newHeight;
2451        this.renderCanvas(true);
2452      }
2453      return this;
2454    },
2455    /**
2456     * Rotate the canvas with a relative degree
2457     * @param {number} degree - The rotate degree.
2458     * @returns {Cropper} this
2459     */
2460    rotate: function rotate(degree) {
2461      return this.rotateTo((this.imageData.rotate || 0) + Number(degree));
2462    },
2463    /**
2464     * Rotate the canvas to an absolute degree
2465     * @param {number} degree - The rotate degree.
2466     * @returns {Cropper} this
2467     */
2468    rotateTo: function rotateTo(degree) {
2469      degree = Number(degree);
2470      if (isNumber(degree) && this.ready && !this.disabled && this.options.rotatable) {
2471        this.imageData.rotate = degree % 360;
2472        this.renderCanvas(true, true);
2473      }
2474      return this;
2475    },
2476    /**
2477     * Scale the image on the x-axis.
2478     * @param {number} scaleX - The scale ratio on the x-axis.
2479     * @returns {Cropper} this
2480     */
2481    scaleX: function scaleX(_scaleX) {
2482      var scaleY = this.imageData.scaleY;
2483      return this.scale(_scaleX, isNumber(scaleY) ? scaleY : 1);
2484    },
2485    /**
2486     * Scale the image on the y-axis.
2487     * @param {number} scaleY - The scale ratio on the y-axis.
2488     * @returns {Cropper} this
2489     */
2490    scaleY: function scaleY(_scaleY) {
2491      var scaleX = this.imageData.scaleX;
2492      return this.scale(isNumber(scaleX) ? scaleX : 1, _scaleY);
2493    },
2494    /**
2495     * Scale the image
2496     * @param {number} scaleX - The scale ratio on the x-axis.
2497     * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis.
2498     * @returns {Cropper} this
2499     */
2500    scale: function scale(scaleX) {
2501      var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX;
2502      var imageData = this.imageData;
2503      var transformed = false;
2504      scaleX = Number(scaleX);
2505      scaleY = Number(scaleY);
2506      if (this.ready && !this.disabled && this.options.scalable) {
2507        if (isNumber(scaleX)) {
2508          imageData.scaleX = scaleX;
2509          transformed = true;
2510        }
2511        if (isNumber(scaleY)) {
2512          imageData.scaleY = scaleY;
2513          transformed = true;
2514        }
2515        if (transformed) {
2516          this.renderCanvas(true, true);
2517        }
2518      }
2519      return this;
2520    },
2521    /**
2522     * Get the cropped area position and size data (base on the original image)
2523     * @param {boolean} [rounded=false] - Indicate if round the data values or not.
2524     * @returns {Object} The result cropped data.
2525     */
2526    getData: function getData() {
2527      var rounded = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
2528      var options = this.options,
2529        imageData = this.imageData,
2530        canvasData = this.canvasData,
2531        cropBoxData = this.cropBoxData;
2532      var data;
2533      if (this.ready && this.cropped) {
2534        data = {
2535          x: cropBoxData.left - canvasData.left,
2536          y: cropBoxData.top - canvasData.top,
2537          width: cropBoxData.width,
2538          height: cropBoxData.height
2539        };
2540        var ratio = imageData.width / imageData.naturalWidth;
2541        forEach(data, function (n, i) {
2542          data[i] = n / ratio;
2543        });
2544        if (rounded) {
2545          // In case rounding off leads to extra 1px in right or bottom border
2546          // we should round the top-left corner and the dimension (#343).
2547          var bottom = Math.round(data.y + data.height);
2548          var right = Math.round(data.x + data.width);
2549          data.x = Math.round(data.x);
2550          data.y = Math.round(data.y);
2551          data.width = right - data.x;
2552          data.height = bottom - data.y;
2553        }
2554      } else {
2555        data = {
2556          x: 0,
2557          y: 0,
2558          width: 0,
2559          height: 0
2560        };
2561      }
2562      if (options.rotatable) {
2563        data.rotate = imageData.rotate || 0;
2564      }
2565      if (options.scalable) {
2566        data.scaleX = imageData.scaleX || 1;
2567        data.scaleY = imageData.scaleY || 1;
2568      }
2569      return data;
2570    },
2571    /**
2572     * Set the cropped area position and size with new data
2573     * @param {Object} data - The new data.
2574     * @returns {Cropper} this
2575     */
2576    setData: function setData(data) {
2577      var options = this.options,
2578        imageData = this.imageData,
2579        canvasData = this.canvasData;
2580      var cropBoxData = {};
2581      if (this.ready && !this.disabled && isPlainObject(data)) {
2582        var transformed = false;
2583        if (options.rotatable) {
2584          if (isNumber(data.rotate) && data.rotate !== imageData.rotate) {
2585            imageData.rotate = data.rotate;
2586            transformed = true;
2587          }
2588        }
2589        if (options.scalable) {
2590          if (isNumber(data.scaleX) && data.scaleX !== imageData.scaleX) {
2591            imageData.scaleX = data.scaleX;
2592            transformed = true;
2593          }
2594          if (isNumber(data.scaleY) && data.scaleY !== imageData.scaleY) {
2595            imageData.scaleY = data.scaleY;
2596            transformed = true;
2597          }
2598        }
2599        if (transformed) {
2600          this.renderCanvas(true, true);
2601        }
2602        var ratio = imageData.width / imageData.naturalWidth;
2603        if (isNumber(data.x)) {
2604          cropBoxData.left = data.x * ratio + canvasData.left;
2605        }
2606        if (isNumber(data.y)) {
2607          cropBoxData.top = data.y * ratio + canvasData.top;
2608        }
2609        if (isNumber(data.width)) {
2610          cropBoxData.width = data.width * ratio;
2611        }
2612        if (isNumber(data.height)) {
2613          cropBoxData.height = data.height * ratio;
2614        }
2615        this.setCropBoxData(cropBoxData);
2616      }
2617      return this;
2618    },
2619    /**
2620     * Get the container size data.
2621     * @returns {Object} The result container data.
2622     */
2623    getContainerData: function getContainerData() {
2624      return this.ready ? assign({}, this.containerData) : {};
2625    },
2626    /**
2627     * Get the image position and size data.
2628     * @returns {Object} The result image data.
2629     */
2630    getImageData: function getImageData() {
2631      return this.sized ? assign({}, this.imageData) : {};
2632    },
2633    /**
2634     * Get the canvas position and size data.
2635     * @returns {Object} The result canvas data.
2636     */
2637    getCanvasData: function getCanvasData() {
2638      var canvasData = this.canvasData;
2639      var data = {};
2640      if (this.ready) {
2641        forEach(['left', 'top', 'width', 'height', 'naturalWidth', 'naturalHeight'], function (n) {
2642          data[n] = canvasData[n];
2643        });
2644      }
2645      return data;
2646    },
2647    /**
2648     * Set the canvas position and size with new data.
2649     * @param {Object} data - The new canvas data.
2650     * @returns {Cropper} this
2651     */
2652    setCanvasData: function setCanvasData(data) {
2653      var canvasData = this.canvasData;
2654      var aspectRatio = canvasData.aspectRatio;
2655      if (this.ready && !this.disabled && isPlainObject(data)) {
2656        if (isNumber(data.left)) {
2657          canvasData.left = data.left;
2658        }
2659        if (isNumber(data.top)) {
2660          canvasData.top = data.top;
2661        }
2662        if (isNumber(data.width)) {
2663          canvasData.width = data.width;
2664          canvasData.height = data.width / aspectRatio;
2665        } else if (isNumber(data.height)) {
2666          canvasData.height = data.height;
2667          canvasData.width = data.height * aspectRatio;
2668        }
2669        this.renderCanvas(true);
2670      }
2671      return this;
2672    },
2673    /**
2674     * Get the crop box position and size data.
2675     * @returns {Object} The result crop box data.
2676     */
2677    getCropBoxData: function getCropBoxData() {
2678      var cropBoxData = this.cropBoxData;
2679      var data;
2680      if (this.ready && this.cropped) {
2681        data = {
2682          left: cropBoxData.left,
2683          top: cropBoxData.top,
2684          width: cropBoxData.width,
2685          height: cropBoxData.height
2686        };
2687      }
2688      return data || {};
2689    },
2690    /**
2691     * Set the crop box position and size with new data.
2692     * @param {Object} data - The new crop box data.
2693     * @returns {Cropper} this
2694     */
2695    setCropBoxData: function setCropBoxData(data) {
2696      var cropBoxData = this.cropBoxData;
2697      var aspectRatio = this.options.aspectRatio;
2698      var widthChanged;
2699      var heightChanged;
2700      if (this.ready && this.cropped && !this.disabled && isPlainObject(data)) {
2701        if (isNumber(data.left)) {
2702          cropBoxData.left = data.left;
2703        }
2704        if (isNumber(data.top)) {
2705          cropBoxData.top = data.top;
2706        }
2707        if (isNumber(data.width) && data.width !== cropBoxData.width) {
2708          widthChanged = true;
2709          cropBoxData.width = data.width;
2710        }
2711        if (isNumber(data.height) && data.height !== cropBoxData.height) {
2712          heightChanged = true;
2713          cropBoxData.height = data.height;
2714        }
2715        if (aspectRatio) {
2716          if (widthChanged) {
2717            cropBoxData.height = cropBoxData.width / aspectRatio;
2718          } else if (heightChanged) {
2719            cropBoxData.width = cropBoxData.height * aspectRatio;
2720          }
2721        }
2722        this.renderCropBox();
2723      }
2724      return this;
2725    },
2726    /**
2727     * Get a canvas drawn the cropped image.
2728     * @param {Object} [options={}] - The config options.
2729     * @returns {HTMLCanvasElement} - The result canvas.
2730     */
2731    getCroppedCanvas: function getCroppedCanvas() {
2732      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
2733      if (!this.ready || !window.HTMLCanvasElement) {
2734        return null;
2735      }
2736      var canvasData = this.canvasData;
2737      var source = getSourceCanvas(this.image, this.imageData, canvasData, options);
2738
2739      // Returns the source canvas if it is not cropped.
2740      if (!this.cropped) {
2741        return source;
2742      }
2743      var _this$getData = this.getData(options.rounded),
2744        initialX = _this$getData.x,
2745        initialY = _this$getData.y,
2746        initialWidth = _this$getData.width,
2747        initialHeight = _this$getData.height;
2748      var ratio = source.width / Math.floor(canvasData.naturalWidth);
2749      if (ratio !== 1) {
2750        initialX *= ratio;
2751        initialY *= ratio;
2752        initialWidth *= ratio;
2753        initialHeight *= ratio;
2754      }
2755      var aspectRatio = initialWidth / initialHeight;
2756      var maxSizes = getAdjustedSizes({
2757        aspectRatio: aspectRatio,
2758        width: options.maxWidth || Infinity,
2759        height: options.maxHeight || Infinity
2760      });
2761      var minSizes = getAdjustedSizes({
2762        aspectRatio: aspectRatio,
2763        width: options.minWidth || 0,
2764        height: options.minHeight || 0
2765      }, 'cover');
2766      var _getAdjustedSizes = getAdjustedSizes({
2767          aspectRatio: aspectRatio,
2768          width: options.width || (ratio !== 1 ? source.width : initialWidth),
2769          height: options.height || (ratio !== 1 ? source.height : initialHeight)
2770        }),
2771        width = _getAdjustedSizes.width,
2772        height = _getAdjustedSizes.height;
2773      width = Math.min(maxSizes.width, Math.max(minSizes.width, width));
2774      height = Math.min(maxSizes.height, Math.max(minSizes.height, height));
2775      var canvas = document.createElement('canvas');
2776      var context = canvas.getContext('2d');
2777      canvas.width = normalizeDecimalNumber(width);
2778      canvas.height = normalizeDecimalNumber(height);
2779      context.fillStyle = options.fillColor || 'transparent';
2780      context.fillRect(0, 0, width, height);
2781      var _options$imageSmoothi = options.imageSmoothingEnabled,
2782        imageSmoothingEnabled = _options$imageSmoothi === void 0 ? true : _options$imageSmoothi,
2783        imageSmoothingQuality = options.imageSmoothingQuality;
2784      context.imageSmoothingEnabled = imageSmoothingEnabled;
2785      if (imageSmoothingQuality) {
2786        context.imageSmoothingQuality = imageSmoothingQuality;
2787      }
2788
2789      // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage
2790      var sourceWidth = source.width;
2791      var sourceHeight = source.height;
2792
2793      // Source canvas parameters
2794      var srcX = initialX;
2795      var srcY = initialY;
2796      var srcWidth;
2797      var srcHeight;
2798
2799      // Destination canvas parameters
2800      var dstX;
2801      var dstY;
2802      var dstWidth;
2803      var dstHeight;
2804      if (srcX <= -initialWidth || srcX > sourceWidth) {
2805        srcX = 0;
2806        srcWidth = 0;
2807        dstX = 0;
2808        dstWidth = 0;
2809      } else if (srcX <= 0) {
2810        dstX = -srcX;
2811        srcX = 0;
2812        srcWidth = Math.min(sourceWidth, initialWidth + srcX);
2813        dstWidth = srcWidth;
2814      } else if (srcX <= sourceWidth) {
2815        dstX = 0;
2816        srcWidth = Math.min(initialWidth, sourceWidth - srcX);
2817        dstWidth = srcWidth;
2818      }
2819      if (srcWidth <= 0 || srcY <= -initialHeight || srcY > sourceHeight) {
2820        srcY = 0;
2821        srcHeight = 0;
2822        dstY = 0;
2823        dstHeight = 0;
2824      } else if (srcY <= 0) {
2825        dstY = -srcY;
2826        srcY = 0;
2827        srcHeight = Math.min(sourceHeight, initialHeight + srcY);
2828        dstHeight = srcHeight;
2829      } else if (srcY <= sourceHeight) {
2830        dstY = 0;
2831        srcHeight = Math.min(initialHeight, sourceHeight - srcY);
2832        dstHeight = srcHeight;
2833      }
2834      var params = [srcX, srcY, srcWidth, srcHeight];
2835
2836      // Avoid "IndexSizeError"
2837      if (dstWidth > 0 && dstHeight > 0) {
2838        var scale = width / initialWidth;
2839        params.push(dstX * scale, dstY * scale, dstWidth * scale, dstHeight * scale);
2840      }
2841
2842      // All the numerical parameters should be integer for `drawImage`
2843      // https://github.com/fengyuanchen/cropper/issues/476
2844      context.drawImage.apply(context, [source].concat(_toConsumableArray(params.map(function (param) {
2845        return Math.floor(normalizeDecimalNumber(param));
2846      }))));
2847      return canvas;
2848    },
2849    /**
2850     * Change the aspect ratio of the crop box.
2851     * @param {number} aspectRatio - The new aspect ratio.
2852     * @returns {Cropper} this
2853     */
2854    setAspectRatio: function setAspectRatio(aspectRatio) {
2855      var options = this.options;
2856      if (!this.disabled && !isUndefined(aspectRatio)) {
2857        // 0 -> NaN
2858        options.aspectRatio = Math.max(0, aspectRatio) || NaN;
2859        if (this.ready) {
2860          this.initCropBox();
2861          if (this.cropped) {
2862            this.renderCropBox();
2863          }
2864        }
2865      }
2866      return this;
2867    },
2868    /**
2869     * Change the drag mode.
2870     * @param {string} mode - The new drag mode.
2871     * @returns {Cropper} this
2872     */
2873    setDragMode: function setDragMode(mode) {
2874      var options = this.options,
2875        dragBox = this.dragBox,
2876        face = this.face;
2877      if (this.ready && !this.disabled) {
2878        var croppable = mode === DRAG_MODE_CROP;
2879        var movable = options.movable && mode === DRAG_MODE_MOVE;
2880        mode = croppable || movable ? mode : DRAG_MODE_NONE;
2881        options.dragMode = mode;
2882        setData(dragBox, DATA_ACTION, mode);
2883        toggleClass(dragBox, CLASS_CROP, croppable);
2884        toggleClass(dragBox, CLASS_MOVE, movable);
2885        if (!options.cropBoxMovable) {
2886          // Sync drag mode to crop box when it is not movable
2887          setData(face, DATA_ACTION, mode);
2888          toggleClass(face, CLASS_CROP, croppable);
2889          toggleClass(face, CLASS_MOVE, movable);
2890        }
2891      }
2892      return this;
2893    }
2894  };
2895
2896  var AnotherCropper = WINDOW.Cropper;
2897  var Cropper = /*#__PURE__*/function () {
2898    /**
2899     * Create a new Cropper.
2900     * @param {Element} element - The target element for cropping.
2901     * @param {Object} [options={}] - The configuration options.
2902     */
2903    function Cropper(element) {
2904      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
2905      _classCallCheck(this, Cropper);
2906      if (!element || !REGEXP_TAG_NAME.test(element.tagName)) {
2907        throw new Error('The first argument is required and must be an <img> or <canvas> element.');
2908      }
2909      this.element = element;
2910      this.options = assign({}, DEFAULTS, isPlainObject(options) && options);
2911      this.cropped = false;
2912      this.disabled = false;
2913      this.pointers = {};
2914      this.ready = false;
2915      this.reloading = false;
2916      this.replaced = false;
2917      this.sized = false;
2918      this.sizing = false;
2919      this.init();
2920    }
2921    _createClass(Cropper, [{
2922      key: "init",
2923      value: function init() {
2924        var element = this.element;
2925        var tagName = element.tagName.toLowerCase();
2926        var url;
2927        if (element[NAMESPACE]) {
2928          return;
2929        }
2930        element[NAMESPACE] = this;
2931        if (tagName === 'img') {
2932          this.isImg = true;
2933
2934          // e.g.: "img/picture.jpg"
2935          url = element.getAttribute('src') || '';
2936          this.originalUrl = url;
2937
2938          // Stop when it's a blank image
2939          if (!url) {
2940            return;
2941          }
2942
2943          // e.g.: "https://example.com/img/picture.jpg"
2944          url = element.src;
2945        } else if (tagName === 'canvas' && window.HTMLCanvasElement) {
2946          url = element.toDataURL();
2947        }
2948        this.load(url);
2949      }
2950    }, {
2951      key: "load",
2952      value: function load(url) {
2953        var _this = this;
2954        if (!url) {
2955          return;
2956        }
2957        this.url = url;
2958        this.imageData = {};
2959        var element = this.element,
2960          options = this.options;
2961        if (!options.rotatable && !options.scalable) {
2962          options.checkOrientation = false;
2963        }
2964
2965        // Only IE10+ supports Typed Arrays
2966        if (!options.checkOrientation || !window.ArrayBuffer) {
2967          this.clone();
2968          return;
2969        }
2970
2971        // Detect the mime type of the image directly if it is a Data URL
2972        if (REGEXP_DATA_URL.test(url)) {
2973          // Read ArrayBuffer from Data URL of JPEG images directly for better performance
2974          if (REGEXP_DATA_URL_JPEG.test(url)) {
2975            this.read(dataURLToArrayBuffer(url));
2976          } else {
2977            // Only a JPEG image may contains Exif Orientation information,
2978            // the rest types of Data URLs are not necessary to check orientation at all.
2979            this.clone();
2980          }
2981          return;
2982        }
2983
2984        // 1. Detect the mime type of the image by a XMLHttpRequest.
2985        // 2. Load the image as ArrayBuffer for reading orientation if its a JPEG image.
2986        var xhr = new XMLHttpRequest();
2987        var clone = this.clone.bind(this);
2988        this.reloading = true;
2989        this.xhr = xhr;
2990
2991        // 1. Cross origin requests are only supported for protocol schemes:
2992        // http, https, data, chrome, chrome-extension.
2993        // 2. Access to XMLHttpRequest from a Data URL will be blocked by CORS policy
2994        // in some browsers as IE11 and Safari.
2995        xhr.onabort = clone;
2996        xhr.onerror = clone;
2997        xhr.ontimeout = clone;
2998        xhr.onprogress = function () {
2999          // Abort the request directly if it not a JPEG image for better performance
3000          if (xhr.getResponseHeader('content-type') !== MIME_TYPE_JPEG) {
3001            xhr.abort();
3002          }
3003        };
3004        xhr.onload = function () {
3005          _this.read(xhr.response);
3006        };
3007        xhr.onloadend = function () {
3008          _this.reloading = false;
3009          _this.xhr = null;
3010        };
3011
3012        // Bust cache when there is a "crossOrigin" property to avoid browser cache error
3013        if (options.checkCrossOrigin && isCrossOriginURL(url) && element.crossOrigin) {
3014          url = addTimestamp(url);
3015        }
3016
3017        // The third parameter is required for avoiding side-effect (#682)
3018        xhr.open('GET', url, true);
3019        xhr.responseType = 'arraybuffer';
3020        xhr.withCredentials = element.crossOrigin === 'use-credentials';
3021        xhr.send();
3022      }
3023    }, {
3024      key: "read",
3025      value: function read(arrayBuffer) {
3026        var options = this.options,
3027          imageData = this.imageData;
3028
3029        // Reset the orientation value to its default value 1
3030        // as some iOS browsers will render image with its orientation
3031        var orientation = resetAndGetOrientation(arrayBuffer);
3032        var rotate = 0;
3033        var scaleX = 1;
3034        var scaleY = 1;
3035        if (orientation > 1) {
3036          // Generate a new URL which has the default orientation value
3037          this.url = arrayBufferToDataURL(arrayBuffer, MIME_TYPE_JPEG);
3038          var _parseOrientation = parseOrientation(orientation);
3039          rotate = _parseOrientation.rotate;
3040          scaleX = _parseOrientation.scaleX;
3041          scaleY = _parseOrientation.scaleY;
3042        }
3043        if (options.rotatable) {
3044          imageData.rotate = rotate;
3045        }
3046        if (options.scalable) {
3047          imageData.scaleX = scaleX;
3048          imageData.scaleY = scaleY;
3049        }
3050        this.clone();
3051      }
3052    }, {
3053      key: "clone",
3054      value: function clone() {
3055        var element = this.element,
3056          url = this.url;
3057        var crossOrigin = element.crossOrigin;
3058        var crossOriginUrl = url;
3059        if (this.options.checkCrossOrigin && isCrossOriginURL(url)) {
3060          if (!crossOrigin) {
3061            crossOrigin = 'anonymous';
3062          }
3063
3064          // Bust cache when there is not a "crossOrigin" property (#519)
3065          crossOriginUrl = addTimestamp(url);
3066        }
3067        this.crossOrigin = crossOrigin;
3068        this.crossOriginUrl = crossOriginUrl;
3069        var image = document.createElement('img');
3070        if (crossOrigin) {
3071          image.crossOrigin = crossOrigin;
3072        }
3073        image.src = crossOriginUrl || url;
3074        image.alt = element.alt || 'The image to crop';
3075        this.image = image;
3076        image.onload = this.start.bind(this);
3077        image.onerror = this.stop.bind(this);
3078        addClass(image, CLASS_HIDE);
3079        element.parentNode.insertBefore(image, element.nextSibling);
3080      }
3081    }, {
3082      key: "start",
3083      value: function start() {
3084        var _this2 = this;
3085        var image = this.image;
3086        image.onload = null;
3087        image.onerror = null;
3088        this.sizing = true;
3089
3090        // Match all browsers that use WebKit as the layout engine in iOS devices,
3091        // such as Safari for iOS, Chrome for iOS, and in-app browsers.
3092        var isIOSWebKit = WINDOW.navigator && /(?:iPad|iPhone|iPod).*?AppleWebKit/i.test(WINDOW.navigator.userAgent);
3093        var done = function done(naturalWidth, naturalHeight) {
3094          assign(_this2.imageData, {
3095            naturalWidth: naturalWidth,
3096            naturalHeight: naturalHeight,
3097            aspectRatio: naturalWidth / naturalHeight
3098          });
3099          _this2.initialImageData = assign({}, _this2.imageData);
3100          _this2.sizing = false;
3101          _this2.sized = true;
3102          _this2.build();
3103        };
3104
3105        // Most modern browsers (excepts iOS WebKit)
3106        if (image.naturalWidth && !isIOSWebKit) {
3107          done(image.naturalWidth, image.naturalHeight);
3108          return;
3109        }
3110        var sizingImage = document.createElement('img');
3111        var body = document.body || document.documentElement;
3112        this.sizingImage = sizingImage;
3113        sizingImage.onload = function () {
3114          done(sizingImage.width, sizingImage.height);
3115          if (!isIOSWebKit) {
3116            body.removeChild(sizingImage);
3117          }
3118        };
3119        sizingImage.src = image.src;
3120
3121        // iOS WebKit will convert the image automatically
3122        // with its orientation once append it into DOM (#279)
3123        if (!isIOSWebKit) {
3124          sizingImage.style.cssText = 'left:0;' + 'max-height:none!important;' + 'max-width:none!important;' + 'min-height:0!important;' + 'min-width:0!important;' + 'opacity:0;' + 'position:absolute;' + 'top:0;' + 'z-index:-1;';
3125          body.appendChild(sizingImage);
3126        }
3127      }
3128    }, {
3129      key: "stop",
3130      value: function stop() {
3131        var image = this.image;
3132        image.onload = null;
3133        image.onerror = null;
3134        image.parentNode.removeChild(image);
3135        this.image = null;
3136      }
3137    }, {
3138      key: "build",
3139      value: function build() {
3140        if (!this.sized || this.ready) {
3141          return;
3142        }
3143        var element = this.element,
3144          options = this.options,
3145          image = this.image;
3146
3147        // Create cropper elements
3148        var container = element.parentNode;
3149        var template = document.createElement('div');
3150        template.innerHTML = TEMPLATE;
3151        var cropper = template.querySelector(".".concat(NAMESPACE, "-container"));
3152        var canvas = cropper.querySelector(".".concat(NAMESPACE, "-canvas"));
3153        var dragBox = cropper.querySelector(".".concat(NAMESPACE, "-drag-box"));
3154        var cropBox = cropper.querySelector(".".concat(NAMESPACE, "-crop-box"));
3155        var face = cropBox.querySelector(".".concat(NAMESPACE, "-face"));
3156        this.container = container;
3157        this.cropper = cropper;
3158        this.canvas = canvas;
3159        this.dragBox = dragBox;
3160        this.cropBox = cropBox;
3161        this.viewBox = cropper.querySelector(".".concat(NAMESPACE, "-view-box"));
3162        this.face = face;
3163        canvas.appendChild(image);
3164
3165        // Hide the original image
3166        addClass(element, CLASS_HIDDEN);
3167
3168        // Inserts the cropper after to the current image
3169        container.insertBefore(cropper, element.nextSibling);
3170
3171        // Show the hidden image
3172        removeClass(image, CLASS_HIDE);
3173        this.initPreview();
3174        this.bind();
3175        options.initialAspectRatio = Math.max(0, options.initialAspectRatio) || NaN;
3176        options.aspectRatio = Math.max(0, options.aspectRatio) || NaN;
3177        options.viewMode = Math.max(0, Math.min(3, Math.round(options.viewMode))) || 0;
3178        addClass(cropBox, CLASS_HIDDEN);
3179        if (!options.guides) {
3180          addClass(cropBox.getElementsByClassName("".concat(NAMESPACE, "-dashed")), CLASS_HIDDEN);
3181        }
3182        if (!options.center) {
3183          addClass(cropBox.getElementsByClassName("".concat(NAMESPACE, "-center")), CLASS_HIDDEN);
3184        }
3185        if (options.background) {
3186          addClass(cropper, "".concat(NAMESPACE, "-bg"));
3187        }
3188        if (!options.highlight) {
3189          addClass(face, CLASS_INVISIBLE);
3190        }
3191        if (options.cropBoxMovable) {
3192          addClass(face, CLASS_MOVE);
3193          setData(face, DATA_ACTION, ACTION_ALL);
3194        }
3195        if (!options.cropBoxResizable) {
3196          addClass(cropBox.getElementsByClassName("".concat(NAMESPACE, "-line")), CLASS_HIDDEN);
3197          addClass(cropBox.getElementsByClassName("".concat(NAMESPACE, "-point")), CLASS_HIDDEN);
3198        }
3199        this.render();
3200        this.ready = true;
3201        this.setDragMode(options.dragMode);
3202        if (options.autoCrop) {
3203          this.crop();
3204        }
3205        this.setData(options.data);
3206        if (isFunction(options.ready)) {
3207          addListener(element, EVENT_READY, options.ready, {
3208            once: true
3209          });
3210        }
3211        dispatchEvent(element, EVENT_READY);
3212      }
3213    }, {
3214      key: "unbuild",
3215      value: function unbuild() {
3216        if (!this.ready) {
3217          return;
3218        }
3219        this.ready = false;
3220        this.unbind();
3221        this.resetPreview();
3222        var parentNode = this.cropper.parentNode;
3223        if (parentNode) {
3224          parentNode.removeChild(this.cropper);
3225        }
3226        removeClass(this.element, CLASS_HIDDEN);
3227      }
3228    }, {
3229      key: "uncreate",
3230      value: function uncreate() {
3231        if (this.ready) {
3232          this.unbuild();
3233          this.ready = false;
3234          this.cropped = false;
3235        } else if (this.sizing) {
3236          this.sizingImage.onload = null;
3237          this.sizing = false;
3238          this.sized = false;
3239        } else if (this.reloading) {
3240          this.xhr.onabort = null;
3241          this.xhr.abort();
3242        } else if (this.image) {
3243          this.stop();
3244        }
3245      }
3246
3247      /**
3248       * Get the no conflict cropper class.
3249       * @returns {Cropper} The cropper class.
3250       */
3251    }], [{
3252      key: "noConflict",
3253      value: function noConflict() {
3254        window.Cropper = AnotherCropper;
3255        return Cropper;
3256      }
3257
3258      /**
3259       * Change the default options.
3260       * @param {Object} options - The new default options.
3261       */
3262    }, {
3263      key: "setDefaults",
3264      value: function setDefaults(options) {
3265        assign(DEFAULTS, isPlainObject(options) && options);
3266      }
3267    }]);
3268    return Cropper;
3269  }();
3270  assign(Cropper.prototype, render, preview, events, handlers, change, methods);
3271
3272  return Cropper;
3273
3274}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.