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.