1(function (factory) { 2 if (typeof define === "function" && define.amd) { 3 // AMD. Register as anonymous module. 4 define(["jquery"], factory); 5 } else if (typeof exports === "object") { 6 // Node / CommonJS 7 factory(require("jquery")); 8 } else { 9 // Browser globals. 10 factory(jQuery); 11 } 12})(function ($) { 13 14 "use strict"; 15 16 var $window = $(window), 17 $document = $(document), 18 location = window.location, 19 20 // Constants 21 TRUE = true, 22 FALSE = false, 23 NULL = null, 24 NAN = NaN, 25 INFINITY = Infinity, 26 STRING_UNDEFINED = "undefined", 27 STRING_DIRECTIVE = "directive", 28 CROPPER_NAMESPACE = ".cropper", 29 30 // RegExps 31 REGEXP_DIRECTIVES = /^(e|n|w|s|ne|nw|sw|se|all|crop|move|zoom)$/, 32 REGEXP_OPTIONS = /^(x|y|width|height)$/, 33 REGEXP_PROPERTIES = /^(naturalWidth|naturalHeight|width|height|aspectRatio|ratio|rotate)$/, 34 35 // Classes 36 CLASS_MODAL = "cropper-modal", 37 CLASS_HIDDEN = "cropper-hidden", 38 CLASS_INVISIBLE = "cropper-invisible", 39 CLASS_MOVE = "cropper-move", 40 CLASS_CROP = "cropper-crop", 41 CLASS_DISABLED = "cropper-disabled", 42 43 // Events 44 EVENT_MOUSE_DOWN = "mousedown touchstart", 45 EVENT_MOUSE_MOVE = "mousemove touchmove", 46 EVENT_MOUSE_UP = "mouseup mouseleave touchend touchleave touchcancel", 47 EVENT_WHEEL = "wheel mousewheel DOMMouseScroll", 48 EVENT_RESIZE = "resize" + CROPPER_NAMESPACE, // Bind to window with namespace 49 EVENT_DBLCLICK = "dblclick", 50 EVENT_BUILD = "build" + CROPPER_NAMESPACE, 51 EVENT_BUILT = "built" + CROPPER_NAMESPACE, 52 EVENT_DRAG_START = "dragstart" + CROPPER_NAMESPACE, 53 EVENT_DRAG_MOVE = "dragmove" + CROPPER_NAMESPACE, 54 EVENT_DRAG_END = "dragend" + CROPPER_NAMESPACE, 55 56 // Functions 57 isNumber = function (n) {
58 return typeof n === "number"; 59 }, 60 61 toArray = function (obj, offset) { 62 var args = []; 63 64 if (typeof offset === "number") { // It's necessary for IE8 65 args.push(offset); 66 } 67 68 return args.slice.apply(obj, args); 69 }, 70 71 // Custom proxy to avoid jQuery's guid 72 proxy = function (fn, context) { 73 var args = toArray(arguments, 2); 74 75 return function () { 76 return fn.apply(context, args.concat(toArray(arguments))); 77 }; 78 }, 79 80 // Constructor 81 Cropper = function (element, options) { 82 this.element = element; 83 this.$element = $(element); 84 this.defaults = $.extend({}, Cropper.DEFAULTS, $.isPlainObject(options) ? options : {}); 85 this.$original = NULL; 86 this.ready = FALSE; 87 this.built = FALSE; 88 this.cropped = FALSE; 89 this.rotated = FALSE; 90 this.disabled = FALSE; 91 this.replaced = FALSE; 92 this.init(); 93 }, 94 95 // Others 96 sqrt = Math.sqrt, 97 min = Math.min, 98 max = Math.max, 99 abs = Math.abs, 100 sin = Math.sin, 101 cos = Math.cos, 102 num = parseFloat; 103 104 Cropper.prototype = { 105 constructor: Cropper, 106 107 support: { 108 canvas: $.isFunction($("<canvas>")[0].getContext) 109 }, 110 111 init: function () { 112 var defaults = this.defaults; 113 114 $.each(defaults, function (i, n) { 115 switch (i) { 116 case "aspectRatio": 117 defaults[i] = abs(num(n)) || NAN; // 0 -> NaN 118 break; 119 120 case "autoCropArea": 121 defaults[i] = abs(num(n)) || 0.8; // 0 | NaN -> 0.8 122 break; 123 124 case "minWidth": 125 case "minHeight": 126 defaults[i] = abs(num(n)) || 0; // NaN -> 0 127 break; 128 129 case "maxWidth": 130 case "maxHeight": 131 defaults[i] = abs(num(n)) || INFINITY; // 0 | NaN -> Infinity 132 break; 133 134 // No default 135 } 136 }); 137 138 // Set default image data 139 this.image = { 140 rotate: 0 141 }; 142 143 this.load(); 144 }, 145 146 load: function () { 147 var _this = this, 148 $this = this.$element, 149 element = this.element, 150 image = this.image, 151 crossOrigin = "", 152 $clone, 153 url; 154 155 if ($this.is("img")) { 156 url = $this.prop("src"); 157 } else if ($this.is("canvas") && this.support.canvas) { 158 url = element.toDataURL(); 159 } 160 161 if (!url) { 162 return; 163 } 164 165 // Reset image rotate degree 166 if (this.replaced) { 167 image.rotate = 0; 168 } 169 170 if (this.defaults.checkImageOrigin) { 171 if ($this.prop("crossOrigin") || this.isCrossOriginURL(url)) { 172 crossOrigin = " crossOrigin"; 173 } 174 } 175 176 this.$clone = ($clone = $("<img" + crossOrigin + ' src="' + url + '">')); 177 178 $clone.one("load", function () { 179 image.naturalWidth = this.naturalWidth || $clone.width(); 180 image.naturalHeight = this.naturalHeight || $clone.height(); 181 image.aspectRatio = image.naturalWidth / image.naturalHeight; 182 183 _this.url = url; 184 _this.ready = TRUE; 185 _this.build(); 186 }); 187 188 // Hide and prepend the clone iamge to the document body (Don't append to). 189 $clone.addClass(CLASS_INVISIBLE).prependTo("body"); 190 }, 191 192 isCrossOriginURL: function (url) { 193 var parts = url.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i); 194 195 if ((parts && (parts[1] !== location.protocol || parts[2] !== location.hostname || parts[3] !== location.port))) { 196 return TRUE; 197 } 198 199 return FALSE; 200 }, 201 202 build: function () { 203 var $this = this.$element, 204 defaults = this.defaults, 205 buildEvent, 206 $cropper; 207 208 if (!this.ready) { 209 return; 210 } 211 212 if (this.built) { 213 this.unbuild(); 214 } 215 216 $this.one(EVENT_BUILD, defaults.build); // Only trigger once 217 buildEvent = $.Event(EVENT_BUILD); 218 $this.trigger(buildEvent); 219 220 if (buildEvent.isDefaultPrevented()) { 221 return; 222 } 223 224 // Create cropper elements 225 this.$cropper = ($cropper = $(Cropper.TEMPLATE)); 226 227 // Hide the original image 228 $this.addClass(CLASS_HIDDEN); 229 230 // Show and prepend the clone iamge to the cropper 231 this.$clone.removeClass(CLASS_INVISIBLE).prependTo($cropper); 232 233 // Save original image for rotation 234 if (!this.rotated) { 235 this.$original = this.$clone.clone(); 236 237 // Append the image to document to avoid "NS_ERROR_NOT_AVAILABLE" error on Firefox when call the "drawImage" method. 238 this.$original.addClass(CLASS_HIDDEN).prependTo(this.$cropper); 239 240 this.originalImage = $.extend({}, this.image); 241 } 242 243 this.$container = $this.parent(); 244 this.$container.append($cropper); 245 246 this.$canvas = $cropper.find(".cropper-canvas"); 247 this.$dragger = $cropper.find(".cropper-dragger"); 248 this.$viewer = $cropper.find(".cropper-viewer"); 249 250 defaults.autoCrop ? (this.cropped = TRUE) : this.$dragger.addClass(CLASS_HIDDEN); 251 defaults.dragCrop && this.setDragMode("crop"); 252 defaults.modal && this.$canvas.addClass(CLASS_MODAL); 253 !defaults.dashed && this.$dragger.find(".cropper-dashed").addClass(CLASS_HIDDEN); 254 !defaults.movable && this.$dragger.find(".cropper-face").data(STRING_DIRECTIVE, "move"); 255 !defaults.resizable && this.$dragger.find(".cropper-line, .cropper-point").addClass(CLASS_HIDDEN); 256 257 this.addListeners(); 258 this.initPreview(); 259 260 this.built = TRUE; // Set `true` before update 261 this.update(); 262 this.replaced = FALSE; // Reset to `false` after update 263 264 $this.one(EVENT_BUILT, defaults.built); // Only trigger once 265 $this.trigger(EVENT_BUILT); 266 }, 267 268 unbuild: function () { 269 if (!this.built) { 270 return; 271 } 272 273 this.built = FALSE; 274 this.removeListeners(); 275 276 this.$preview.empty(); 277 this.$preview = NULL; 278 279 this.$dragger = NULL; 280 this.$canvas = NULL; 281 this.$container = NULL; 282 283 this.$cropper.remove(); 284 this.$cropper = NULL; 285 }, 286 287 update: function (data) { 288 this.initContainer(); 289 this.initCropper(); 290 this.initImage(); 291 this.initDragger(); 292 293 if (data) { 294 this.setData(data, TRUE); 295 this.setDragMode("crop"); 296 } else { 297 this.setData(this.defaults.data); 298 } 299 }, 300 301 resize: function () { 302 clearTimeout(this.resizing); 303 this.resizing = setTimeout($.proxy(this.update, this, this.getData()), 200); 304 }, 305 306 preview: function () { 307 var image = this.image, 308 dragger = this.dragger, 309 width = image.width, 310 height = image.height, 311 left = dragger.left - image.left, 312 top = dragger.top - image.top; 313 314 this.$viewer.find("img").css({ 315 width: width, 316 height: height, 317 marginLeft: -left, 318 marginTop: -top 319 }); 320 321 this.$preview.each(function () { 322 var $this = $(this), 323 ratio = $this.width() / dragger.width; 324 325 $this.find("img").css({ 326 width: width * ratio, 327 height: height * ratio, 328 marginLeft: -left * ratio, 329 marginTop: -top * ratio 330 }); 331 }); 332 }, 333 334 addListeners: function () { 335 var defaults = this.defaults; 336 337 this.$element.on(EVENT_DRAG_START, defaults.dragstart).on(EVENT_DRAG_MOVE, defaults.dragmove).on(EVENT_DRAG_END, defaults.dragend); 338 this.$cropper.on(EVENT_MOUSE_DOWN, $.proxy(this.dragstart, this)).on(EVENT_DBLCLICK, $.proxy(this.dblclick, this)); 339 340 if (defaults.zoomable) { 341 this.$cropper.on(EVENT_WHEEL, $.proxy(this.wheel, this)); 342 } 343 344 if (defaults.multiple) { 345 this.$cropper.on(EVENT_MOUSE_MOVE, $.proxy(this.dragmove, this)).on(EVENT_MOUSE_UP, $.proxy(this.dragend, this)); 346 } else { 347 $document.on(EVENT_MOUSE_MOVE, (this._dragmove = proxy(this.dragmove, this))).on(EVENT_MOUSE_UP, (this._dragend = proxy(this.dragend, this))); 348 } 349 350 $window.on(EVENT_RESIZE, (this._resize = proxy(this.resize, this))); 351 }, 352 353 removeListeners: function () { 354 var defaults = this.defaults; 355 356 this.$element.off(EVENT_DRAG_START, defaults.dragstart).off(EVENT_DRAG_MOVE, defaults.dragmove).off(EVENT_DRAG_END, defaults.dragend); 357 this.$cropper.off(EVENT_MOUSE_DOWN, this.dragstart).off(EVENT_DBLCLICK, this.dblclick); 358 359 if (defaults.zoomable) { 360 this.$cropper.off(EVENT_WHEEL, this.wheel); 361 } 362 363 if (defaults.multiple) { 364 this.$cropper.off(EVENT_MOUSE_MOVE, this.dragmove).off(EVENT_MOUSE_UP, this.dragend); 365 } else { 366 $document.off(EVENT_MOUSE_MOVE, this._dragmove).off(EVENT_MOUSE_UP, this._dragend); 367 } 368 369 $window.off(EVENT_RESIZE, this._resize); 370 }, 371 372 initPreview: function () { 373 var img = '<img src="' + this.url + '">'; 374 375 this.$preview = $(this.defaults.preview); 376 this.$viewer.html(img); 377 this.$preview.html(img).find("img").css("cssText", "min-width:0!important;min-height:0!important;max-width:none!important;max-height:none!important;"); 378 }, 379
380 initContainer: function () { 381 var $container = this.$container; 382 383 this.container = { 384 width: max($container.width(), 300), 385 height: max($container.height(), 150) 386 }; 387 }, 388 389 initCropper: function () { 390 var container = this.container, 391 image = this.image, 392 cropper; 393 394 if (((image.naturalWidth * container.height / image.naturalHeight) - container.width) >= 0) { 395 cropper = { 396 width: container.width, 397 height: container.width / image.aspectRatio, 398 left: 0 399 }; 400 401 cropper.top = (container.height - cropper.height) / 2; 402 } else { 403 cropper = { 404 width: container.height * image.aspectRatio, 405 height: container.height, 406 top: 0 407 }; 408 409 cropper.left = (container.width - cropper.width) / 2; 410 } 411 412 this.$cropper.css({ 413 width: cropper.width, 414 height: cropper.height, 415 left: cropper.left, 416 top: cropper.top 417 }); 418 419 this.cropper = cropper; 420 }, 421 422 initImage: function () { 423 var image = this.image, 424 cropper = this.cropper, 425 defaultImage = { 426 _width: cropper.width, 427 _height: cropper.height, 428 width: cropper.width, 429 height: cropper.height, 430 left: 0, 431 top: 0, 432 ratio: cropper.width / image.naturalWidth 433 }; 434 435 this.defaultImage = $.extend({}, image, defaultImage); 436 437 if (image._width !== cropper.width || image._height !== cropper.height) { 438 $.extend(image, defaultImage); 439 } else { 440 image = $.extend({}, defaultImage, image); 441 442 // Reset image ratio 443 if (this.replaced) { 444 image.ratio = defaultImage.ratio; 445 } 446 } 447 448 this.image = image; 449 this.renderImage(); 450 }, 451 452 renderImage: function (mode) { 453 var image = this.image; 454 455 if (mode === "zoom") { 456 image.left -= (image.width - image.oldWidth) / 2; 457 image.top -= (image.height - image.oldHeight) / 2; 458 } 459 460 image.left = min(max(image.left, image._width - image.width), 0); 461 image.top = min(max(image.top, image._height - image.height), 0); 462 463 this.$clone.css({ 464 width: image.width, 465 height: image.height, 466 marginLeft: image.left, 467 marginTop: image.top 468 }); 469 470 if (mode) { 471 this.defaults.done(this.getData()); 472 this.preview(); 473 } 474 }, 475 476 initDragger: function () { 477 var defaults = this.defaults, 478 cropper = this.cropper, 479 // If not set, use the original aspect ratio of the image. 480 aspectRatio = defaults.aspectRatio || this.image.aspectRatio, 481 ratio = this.image.ratio, 482 autoCropDragger = {}, 483 dragger; 484 485 if (((cropper.height * aspectRatio) - cropper.width) >= 0) { 486 dragger = { 487 height: cropper.width / aspectRatio, 488 width: cropper.width, 489 left: 0, 490 top: (cropper.height - (cropper.width / aspectRatio)) / 2, 491 maxWidth: cropper.width, 492 maxHeight: cropper.width / aspectRatio 493 }; 494 } else { 495 dragger = { 496 height: cropper.height, 497 width: cropper.height * aspectRatio, 498 left: (cropper.width - (cropper.height * aspectRatio)) / 2, 499 top: 0, 500 maxWidth: cropper.height * aspectRatio, 501 maxHeight: cropper.height 502 }; 503 } 504 505 dragger.minWidth = 0; 506 dragger.minHeight = 0; 507 508 if (defaults.aspectRatio) { 509 if (isFinite(defaults.maxWidth)) { 510 dragger.maxWidth = min(dragger.maxWidth, defaults.maxWidth * ratio); 511 dragger.maxHeight = dragger.maxWidth / aspectRatio; 512 } else if (isFinite(defaults.maxHeight)) { 513 dragger.maxHeight = min(dragger.maxHeight, defaults.maxHeight * ratio); 514 dragger.maxWidth = dragger.maxHeight * aspectRatio; 515 } 516 517 if (defaults.minWidth > 0) { 518 dragger.minWidth = max(0, defaults.minWidth * ratio); 519 dragger.minHeight = dragger.minWidth / aspectRatio; 520 } else if (defaults.minHeight > 0) { 521 dragger.minHeight = max(0, defaults.minHeight * ratio); 522 dragger.minWidth = dragger.minHeight * aspectRatio; 523 } 524 } else { 525 dragger.maxWidth = min(dragger.maxWidth, defaults.maxWidth * ratio); 526 dragger.maxHeight = min(dragger.maxHeight, defaults.maxHeight * ratio); 527 dragger.minWidth = max(0, defaults.minWidth * ratio); 528 dragger.minHeight = max(0, defaults.minHeight * ratio); 529 } 530 531 // minWidth can't be greater than maxWidth, and minHeight too. 532 dragger.minWidth = min(dragger.maxWidth, dragger.minWidth); 533 dragger.minHeight = min(dragger.maxHeight, dragger.minHeight); 534 535 // Center the dragger by default 536 autoCropDragger.height = dragger.height * defaults.autoCropArea; 537 autoCropDragger.width = dragger.width * defaults.autoCropArea; 538 autoCropDragger.left = (cropper.width - autoCropDragger.width) / 2; 539 autoCropDragger.top = (cropper.height - autoCropDragger.height) / 2; 540 541 autoCropDragger.oldLeft = dragger.oldLeft = dragger.left; 542 autoCropDragger.oldTop = dragger.oldTop = dragger.top; 543 544 this.autoCropDragger = autoCropDragger; 545 this.defaultDragger = $.extend({}, dragger); 546 this.dragger = dragger; 547 }, 548 549 renderDragger: function () { 550 var dragger = this.dragger, 551 cropper = this.cropper; 552 553 if (dragger.width > dragger.maxWidth) { 554 dragger.width = dragger.maxWidth; 555 dragger.left = dragger.oldLeft; 556 } else if (dragger.width < dragger.minWidth) { 557 dragger.width = dragger.minWidth; 558 dragger.left = dragger.oldLeft; 559 } 560 561 if (dragger.height > dragger.maxHeight) { 562 dragger.height = dragger.maxHeight; 563 dragger.top = dragger.oldTop; 564 } else if (dragger.height < dragger.minHeight) { 565 dragger.height = dragger.minHeight; 566 dragger.top = dragger.oldTop; 567 } 568 569 dragger.left = min(max(dragger.left, 0), cropper.width - dragger.width); 570 dragger.top = min(max(dragger.top, 0), cropper.height - dragger.height); 571 dragger.oldLeft = dragger.left; 572 dragger.oldTop = dragger.top; 573 574 // Re-render the dragger 575 this.dragger = dragger; 576 577 if (!this.disabled) { 578 this.defaults.done(this.getData()); 579 } 580 581 this.$dragger.css({ 582 width: dragger.width, 583 height: dragger.height, 584 left: dragger.left, 585 top: dragger.top 586 }); 587 588 this.preview(); 589 }, 590 591 reset: function (deep) { 592 if (!this.cropped) { 593 return; 594 } 595 596 if (deep) { 597 this.defaults.data = {}; 598 } 599 600 this.image = $.extend({}, this.defaultImage); 601 this.renderImage(); 602 this.dragger = $.extend({}, this.defaultDragger); 603 this.setData(this.defaults.data); 604 }, 605 606 clear: function () { 607 if (!this.cropped) { 608 return; 609 } 610 611 this.cropped = FALSE; 612 613 this.setData({ 614 x: 0, 615 y: 0, 616 width: 0, 617 height: 0 618 }); 619 620 this.$canvas.removeClass(CLASS_MODAL); 621 this.$dragger.addClass(CLASS_HIDDEN); 622 }, 623 624 destroy: function () { 625 var $this = this.$element; 626 627 if (!this.ready) { 628 return; 629 } 630 631 this.unbuild(); 632 $this.removeClass(CLASS_HIDDEN).removeData("cropper"); 633 634 if (this.rotated) { 635 $this.attr("src", this.$original.attr("src")); 636 } 637 }, 638 639 replace: function (url, /*INTERNAL*/ rotated) { 640 var _this = this, 641 $this = this.$element, 642 element = this.element, 643 context; 644 645 if (url && url !== this.url && url !== $this.attr("src")) { 646 if (!rotated) { 647 this.rotated = FALSE; 648 this.replaced = TRUE; 649 } 650 651 if ($this.is("img")) { 652 $this.attr("src", url); 653 this.load(); 654 } else if ($this.is("canvas") && this.support.canvas) { 655 context = element.getContext("2d"); 656 657 $('<img src="' + url + '">').one("load", function () { 658 element.width = this.width; 659 element.height = this.height; 660 context.clearRect(0, 0, element.width, element.height); 661 context.drawImage(this, 0, 0); 662 _this.load(); 663 }); 664 } 665 } 666 }, 667 668 setData: function (data, /*INTERNAL*/ once) { 669 var cropper = this.cropper, 670 dragger = this.dragger, 671 image = this.image, 672 aspectRatio = this.defaults.aspectRatio; 673 674 if (!this.built || typeof data === STRING_UNDEFINED) { 675 return; 676 } 677 678 if (data === NULL || $.isEmptyObject(data)) { 679 dragger = $.extend({}, this.autoCropDragger); 680 } 681 682 if ($.isPlainObject(data) && !$.isEmptyObject(data)) { 683 684 if (!once) { 685 this.defaults.data = data; 686 } 687 688 data = this.transformData(data); 689 690 if (isNumber(data.x) && data.x <= cropper.width - image.left) { 691 dragger.left = data.x + image.left; 692 } 693 694 if (isNumber(data.y) && data.y <= cropper.height - image.top) { 695 dragger.top = data.y + image.top; 696 } 697 698 if (aspectRatio) { 699 if (isNumber(data.width) && data.width <= dragger.maxWidth && data.width >= dragger.minWidth) { 700 dragger.width = data.width; 701 dragger.height = dragger.width / aspectRatio; 702 } else if (isNumber(data.height) && data.height <= dragger.maxHeight && data.height >= dragger.minHeight) { 703 dragger.height = data.height; 704 dragger.width = dragger.height * aspectRatio; 705 } 706 } else { 707 if (isNumber(data.width) && data.width <= dragger.maxWidth && data.width >= dragger.minWidth) { 708 dragger.width = data.width; 709 } 710 711 if (isNumber(data.height) && data.height <= dragger.maxHeight && data.height >= dragger.minHeight) { 712 dragger.height = data.height; 713 } 714 } 715 } 716 717 this.dragger = dragger; 718 this.renderDragger(); 719 }, 720 721 getData: function (rounded) { 722 var dragger = this.dragger, 723 image = this.image, 724 data = {}; 725 726 if (this.built) { 727 data = { 728 x: dragger.left - image.left, 729 y: dragger.top - image.top, 730 width: dragger.width, 731 height: dragger.height 732 }; 733
734 data = this.transformData(data, TRUE, rounded); 735 } 736 737 return data; 738 }, 739 740 transformData: function (data, reversed, rounded) { 741 var ratio = this.image.ratio, 742 result = {}; 743 744 $.each(data, function (i, n) { 745 n = num(n); 746 747 if (REGEXP_OPTIONS.test(i) && !isNaN(n)) { 748 result[i] = reversed ? (rounded ? Math.round(n / ratio) : n / ratio) : n * ratio; 749 } 750 }); 751 752 return result; 753 }, 754 755 setAspectRatio: function (aspectRatio) { 756 var freeRatio = aspectRatio === "auto"; 757 758 aspectRatio = num(aspectRatio); 759 760 if (freeRatio || (!isNaN(aspectRatio) && aspectRatio > 0)) { 761 this.defaults.aspectRatio = freeRatio ? NAN : aspectRatio; 762 763 if (this.built) { 764 this.initDragger(); 765 this.renderDragger(); 766 } 767 } 768 }, 769 770 getImageData: function () { 771 var data = {}; 772 773 if (this.ready) { 774 $.each(this.image, function (name, value) { 775 if (REGEXP_PROPERTIES.test(name)) { 776 data[name] = value; 777 } 778 }); 779 } 780 781 return data; 782 }, 783 784 getDataURL: function (options, type, quality) { 785 var canvas = $("<canvas>")[0], 786 data = this.getData(), 787 dataURL = "", 788 context; 789 790 if (!$.isPlainObject(options)) { 791 quality = type; 792 type = options; 793 options = {}; 794 } 795 796 options = $.extend({ 797 width: data.width, 798 height: data.height 799 }, options); 800 801 if (this.cropped && this.support.canvas) { 802 canvas.width = options.width; 803 canvas.height = options.height; 804 context = canvas.getContext("2d"); 805 806 const steps = (canvas.width / canvas.width) >> 1; 807 if (steps !== 0) { // This was causing [NS_ERROR_FAILURE] so if its at 0 then move one else set it 808 context.filter = `blur(${steps}px)`; 809 } 810 811 812 if (type === "image/jpeg") { 813 context.fillStyle = "#fff"; 814 context.fillRect(0, 0, options.width, options.height); 815 } 816 817 context.imageSmoothingQuality = 'high'; 818 context.drawImage(this.$clone[0], data.x, data.y, data.width, data.height, 0, 0, options.width, options.height); 819 dataURL = canvas.toDataURL(type, quality); 820 } 821 822 return dataURL; 823 }, 824 825 setDragMode: function (mode) { 826 var $canvas = this.$canvas, 827 defaults = this.defaults, 828 cropable = FALSE, 829 movable = FALSE; 830 831 if (!this.built || this.disabled) { 832 return; 833 } 834 835 switch (mode) { 836 case "crop": 837 if (defaults.dragCrop) { 838 cropable = TRUE; 839 $canvas.data(STRING_DIRECTIVE, mode); 840 } 841 842 break; 843 844 case "move": 845 movable = TRUE; 846 $canvas.data(STRING_DIRECTIVE, mode); 847 848 break; 849 850 default: 851 $canvas.removeData(STRING_DIRECTIVE); 852 } 853 854 $canvas.toggleClass(CLASS_CROP, cropable).toggleClass(CLASS_MOVE, movable); 855 }, 856 857 enable: function () { 858 if (this.built) { 859 this.disabled = FALSE; 860 this.$cropper.removeClass(CLASS_DISABLED); 861 } 862 }, 863 864 disable: function () { 865 if (this.built) { 866 this.disabled = TRUE; 867 this.$cropper.addClass(CLASS_DISABLED); 868 } 869 }, 870 871 rotate: function (degree) { 872 var image = this.image; 873 874 degree = num(degree) || 0; 875 876 if (!this.built || degree === 0 || this.disabled || !this.defaults.rotatable || !this.support.canvas) { 877 return; 878 } 879 880 this.rotated = TRUE; 881 degree = (image.rotate = (image.rotate + degree) % 360); 882 883 // replace with "true" to prevent to override the original image 884 this.replace(this.getRotatedDataURL(degree), true); 885 }, 886 887 getRotatedDataURL: function (degree) { 888 var canvas = $("<canvas>")[0], 889 context = canvas.getContext("2d"), 890 arc = degree * Math.PI / 180, 891 deg = abs(degree) % 180, 892 acuteAngle = deg > 90 ? (180 - deg) : deg, 893 acuteAngleArc = acuteAngle * Math.PI / 180, 894 originalImage = this.originalImage,
895 naturalWidth = originalImage.naturalWidth, 896 naturalHeight = originalImage.naturalHeight, 897 width = abs(naturalWidth * cos(acuteAngleArc) + naturalHeight * sin(acuteAngleArc)), 898 height = abs(naturalWidth * sin(acuteAngleArc) + naturalHeight * cos(acuteAngleArc)); 899 900 canvas.width = width; 901 canvas.height = height; 902 context.save(); 903 context.translate(width / 2, height / 2); 904 context.rotate(arc); 905 context.drawImage(this.$original[0], -naturalWidth / 2, -naturalHeight / 2, naturalWidth, naturalHeight); 906 context.restore(); 907 908 return canvas.toDataURL(); 909 }, 910 911 zoom: function (delta) { 912 var image = this.image, 913 width, 914 height, 915 range; 916 917 delta = num(delta); 918 919 if (!this.built || !delta || this.disabled || !this.defaults.zoomable) { 920 return; 921 } 922 923 width = image.width * (1 + delta); 924 height = image.height * (1 + delta); 925 range = width / image._width; 926 927 if (range > 10) { 928 return; 929 } 930 931 if (range < 1) { 932 width = image._width; 933 height = image._height; 934 } 935 936 if (range <= 1) { 937 this.setDragMode("crop"); 938 } else { 939 this.setDragMode("move"); 940 } 941 942 image.oldWidth = image.width; 943 image.oldHeight = image.height; 944 945 image.width = width; 946 image.height = height; 947 image.ratio = image.width / image.naturalWidth; 948 949 this.renderImage("zoom"); 950 }, 951 952 dblclick: function () { 953 if (this.disabled) { 954 return; 955 } 956 957 if (this.$canvas.hasClass(CLASS_CROP)) { 958 this.setDragMode("move"); 959 } else { 960 this.setDragMode("crop"); 961 } 962 }, 963 964 wheel: function (event) { 965 var e = event.originalEvent, 966 msDeltaY = 117.25, // IE 967 mozDelatY = 5, // Firefox 968 webkitDelatY = 166.66665649414062, // Chrome, Opera 969 zoomDelta = 0.1, // 10% 970 delta; 971 972 if (this.disabled) { 973 return; 974 } 975 976 event.preventDefault(); 977 978 if (e.deltaY) { 979 delta = e.deltaY; 980 delta = delta % mozDelatY === 0 ? delta / mozDelatY : delta % msDeltaY === 0 ? delta / msDeltaY : delta / webkitDelatY; 981 } else { 982 delta = e.wheelDelta ? -e.wheelDelta / 120 : (e.detail ? e.detail / 3 : 0); 983 } 984 985 this.zoom(delta * zoomDelta); 986 }, 987 988 dragstart: function (event) { 989 var touches = event.originalEvent.touches, 990 e = event, 991 directive, 992 dragStartEvent, 993 touchesLength; 994 995 if (this.disabled) { 996 return; 997 } 998 999 if (touches) { 1000 touchesLength = touches.length; 1001 1002 if (touchesLength > 1) { 1003 if (this.defaults.zoomable && touchesLength === 2) { 1004 e = touches[1]; 1005 this.startX2 = e.pageX; 1006 this.startY2 = e.pageY; 1007 directive = "zoom"; 1008 } else { 1009 return; 1010 } 1011 } 1012 1013 e = touches[0]; 1014 } 1015 1016 directive = directive || $(e.target).data(STRING_DIRECTIVE); 1017 1018 if (REGEXP_DIRECTIVES.test(directive)) { 1019 event.preventDefault(); 1020 1021 dragStartEvent = $.Event(EVENT_DRAG_START); 1022 this.$element.trigger(dragStartEvent); 1023 1024 if (dragStartEvent.isDefaultPrevented()) { 1025 return; 1026 } 1027 1028 this.directive = directive; 1029 this.cropping = FALSE; 1030 this.startX = e.pageX; 1031 this.startY = e.pageY; 1032 1033 if (directive === "crop") { 1034 this.cropping = TRUE; 1035 this.$canvas.addClass(CLASS_MODAL); 1036 } 1037 } 1038 }, 1039 1040 dragmove: function (event) { 1041 var touches = event.originalEvent.touches, 1042 e = event, 1043 dragMoveEvent, 1044 touchesLength; 1045 1046 if (this.disabled) { 1047 return; 1048 } 1049 1050 if (touches) { 1051 touchesLength = touches.length; 1052 1053 if (touchesLength > 1) { 1054 if (this.defaults.zoomable && touchesLength === 2) { 1055 e = touches[1]; 1056 this.endX2 = e.pageX; 1057 this.endY2 = e.pageY; 1058 } else { 1059 return; 1060 } 1061 } 1062 1063 e = touches[0]; 1064 } 1065 1066 if (this.directive) { 1067 event.preventDefault(); 1068 1069 dragMoveEvent = $.Event(EVENT_DRAG_MOVE); 1070 this.$element.trigger(dragMoveEvent); 1071 1072 if (dragMoveEvent.isDefaultPrevented()) { 1073 return; 1074 } 1075 1076 this.endX = e.pageX; 1077 this.endY = e.pageY; 1078 1079 this.dragging(); 1080 } 1081 }, 1082 1083 dragend: function (event) { 1084 var dragEndEvent; 1085 1086 if (this.disabled) { 1087 return; 1088 } 1089 1090 if (this.directive) { 1091 event.preventDefault(); 1092 1093 dragEndEvent = $.Event(EVENT_DRAG_END); 1094 this.$element.trigger(dragEndEvent); 1095 1096 if (dragEndEvent.isDefaultPrevented()) { 1097 return; 1098 } 1099 1100 if (this.cropping) { 1101 this.cropping = FALSE; 1102 this.$canvas.toggleClass(CLASS_MODAL, this.cropped && this.defaults.modal); 1103 } 1104 1105 this.directive = ""; 1106 } 1107 }, 1108 1109 dragging: function () { 1110 var directive = this.directive, 1111 image = this.image, 1112 cropper = this.cropper, 1113 maxWidth = cropper.width, 1114 maxHeight = cropper.height, 1115 dragger = this.dragger, 1116 width = dragger.width, 1117 height = dragger.height, 1118 left = dragger.left, 1119 top = dragger.top, 1120 right = left + width, 1121 bottom = top + height, 1122 renderable = TRUE, 1123 defaults = this.defaults, 1124 aspectRatio = defaults.aspectRatio, 1125 range = { 1126 x: this.endX - this.startX, 1127 y: this.endY - this.startY 1128 }, 1129 offset; 1130 1131 if (aspectRatio) { 1132 range.X = range.y * aspectRatio; 1133 range.Y = range.x / aspectRatio; 1134 } 1135 1136 switch (directive) { 1137 // Move dragger 1138 case "all": 1139 left += range.x; 1140 top += range.y; 1141 1142 break; 1143 1144 // Resize dragger 1145 case "e": 1146 if (range.x >= 0 && (right >= maxWidth || aspectRatio && (top <= 0 || bottom >= maxHeight))) { 1147 renderable = FALSE; 1148 break; 1149 } 1150 1151 width += range.x; 1152 1153 if (aspectRatio) { 1154 height = width / aspectRatio; 1155 top -= range.Y / 2; 1156 } 1157 1158 if (width < 0) { 1159 directive = "w"; 1160 width = 0; 1161 } 1162 1163 break; 1164 1165 case "n": 1166 if (range.y <= 0 && (top <= 0 || aspectRatio && (left <= 0 || right >= maxWidth))) { 1167 renderable = FALSE; 1168 break; 1169 } 1170 1171 height -= range.y; 1172 top += range.y; 1173 1174 if (aspectRatio) { 1175 width = height * aspectRatio; 1176 left += range.X / 2; 1177 } 1178 1179 if (height < 0) { 1180 directive = "s"; 1181 height = 0; 1182 } 1183 1184 break; 1185 1186 case "w": 1187 if (range.x <= 0 && (left <= 0 || aspectRatio && (top <= 0 || bottom >= maxHeight))) { 1188 renderable = FALSE; 1189 break; 1190 } 1191 1192 width -= range.x; 1193 left += range.x; 1194 1195 if (aspectRatio) { 1196 height = width / aspectRatio; 1197 top += range.Y / 2; 1198 } 1199 1200 if (width < 0) { 1201 directive = "e"; 1202 width = 0; 1203 } 1204 1205 break; 1206 1207 case "s": 1208 if (range.y >= 0 && (bottom >= maxHeight || aspectRatio && (left <= 0 || right >= maxWidth))) { 1209 renderable = FALSE; 1210 break; 1211 } 1212 1213 height += range.y; 1214 1215 if (aspectRatio) { 1216 width = height * aspectRatio; 1217 left -= range.X / 2; 1218 } 1219 1220 if (height < 0) { 1221 directive = "n"; 1222 height = 0; 1223 } 1224 1225 break; 1226 1227 case "ne": 1228 if (aspectRatio) { 1229 if (range.y <= 0 && (top <= 0 || right >= maxWidth)) { 1230 renderable = FALSE; 1231 break; 1232 } 1233 1234 height -= range.y; 1235 top += range.y; 1236 width = height * aspectRatio; 1237 } else { 1238 if (range.x >= 0) { 1239 if (right < maxWidth) { 1240 width += range.x; 1241 } else if (range.y <= 0 && top <= 0) { 1242 renderable = FALSE; 1243 } 1244 } else { 1245 width += range.x; 1246 } 1247 1248 if (range.y <= 0) { 1249 if (top > 0) { 1250 height -= range.y; 1251 top += range.y; 1252 } 1253 } else { 1254 height -= range.y; 1255 top += range.y; 1256 } 1257 } 1258 1259 if (height < 0) { 1260 directive = "sw"; 1261 height = 0; 1262 width = 0; 1263 } 1264 1265 break; 1266 1267 case "nw": 1268 if (aspectRatio) { 1269 if (range.y <= 0 && (top <= 0 || left <= 0)) { 1270 renderable = FALSE; 1271 break; 1272 } 1273 1274 height -= range.y; 1275 top += range.y; 1276 width = height * aspectRatio; 1277 left += range.X; 1278 } else { 1279 if (range.x <= 0) { 1280 if (left > 0) { 1281 width -= range.x; 1282 left += range.x; 1283 } else if (range.y <= 0 && top <= 0) { 1284 renderable = FALSE; 1285 } 1286 } else { 1287 width -= range.x; 1288 left += range.x; 1289 } 1290 1291 if (range.y <= 0) { 1292 if (top > 0) { 1293 height -= range.y; 1294 top += range.y; 1295 } 1296 } else { 1297 height -= range.y; 1298 top += range.y; 1299 } 1300 } 1301 1302 if (height < 0) { 1303 directive = "se"; 1304 height = 0; 1305 width = 0; 1306 } 1307 1308 break; 1309 1310 case "sw": 1311 if (aspectRatio) { 1312 if (range.x <= 0 && (left <= 0 || bottom >= maxHeight)) { 1313 renderable = FALSE; 1314 break; 1315 } 1316 1317 width -= range.x; 1318 left += range.x; 1319 height = width / aspectRatio; 1320 } else { 1321 if (range.x <= 0) { 1322 if (left > 0) { 1323 width -= range.x; 1324 left += range.x; 1325 } else if (range.y >= 0 && bottom >= maxHeight) { 1326 renderable = FALSE; 1327 } 1328 } else { 1329 width -= range.x; 1330 left += range.x; 1331 } 1332 1333 if (range.y >= 0) { 1334 if (bottom < maxHeight) { 1335 height += range.y; 1336 } 1337 } else { 1338 height += range.y; 1339 } 1340 } 1341 1342 if (width < 0) { 1343 directive = "ne"; 1344 height = 0; 1345 width = 0; 1346 } 1347 1348 break; 1349 1350 case "se": 1351 if (aspectRatio) { 1352 if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) { 1353 renderable = FALSE; 1354 break; 1355 } 1356 1357 width += range.x; 1358 height = width / aspectRatio; 1359 } else { 1360 if (range.x >= 0) { 1361 if (right < maxWidth) { 1362 width += range.x; 1363 } else if (range.y >= 0 && bottom >= maxHeight) { 1364 renderable = FALSE; 1365 } 1366 } else { 1367 width += range.x; 1368 } 1369 1370 if (range.y >= 0) { 1371 if (bottom < maxHeight) { 1372 height += range.y; 1373 } 1374 } else { 1375 height += range.y; 1376 } 1377 } 1378 1379 if (width < 0) { 1380 directive = "nw"; 1381 height = 0; 1382 width = 0; 1383 } 1384 1385 break; 1386 1387 // Move image 1388 case "move": 1389 image.left += range.x; 1390 image.top += range.y; 1391 this.renderImage("move"); 1392 renderable = FALSE; 1393 1394 break; 1395 1396 // Scale image 1397 case "zoom": 1398 if (defaults.zoomable) { 1399 this.zoom(function (x, y, x1, y1, x2, y2) { 1400 return (sqrt(x2 * x2 + y2 * y2) - sqrt(x1 * x1 + y1 * y1)) / sqrt(x * x + y * y); 1401 }( 1402 image.width, 1403 image.height, 1404 abs(this.startX - this.startX2), 1405 abs(this.startY - this.startY2), 1406 abs(this.endX - this.endX2),
1407 abs(this.endY - this.endY2) 1408 )); 1409 1410 this.endX2 = this.startX2; 1411 this.endY2 = this.startY2; 1412 } 1413 1414 break; 1415 1416 // Crop image 1417 case "crop": 1418 if (range.x && range.y) { 1419 offset = this.$cropper.offset(); 1420 left = this.startX - offset.left; 1421 top = this.startY - offset.top; 1422 width = dragger.minWidth; 1423 height = dragger.minHeight; 1424 1425 if (range.x > 0) { 1426 if (range.y > 0) { 1427 directive = "se"; 1428 } else { 1429 directive = "ne"; 1430 top -= height; 1431 } 1432 } else { 1433 if (range.y > 0) { 1434 directive = "sw"; 1435 left -= width; 1436 } else { 1437 directive = "nw"; 1438 left -= width; 1439 top -= height; 1440 } 1441 } 1442 1443 // Show the dragger if is hidden 1444 if (!this.cropped) { 1445 this.cropped = TRUE; 1446 this.$dragger.removeClass(CLASS_HIDDEN); 1447 } 1448 } 1449 1450 break; 1451 1452 // No default 1453 } 1454 1455 if (renderable) { 1456 dragger.width = width; 1457 dragger.height = height; 1458 dragger.left = left; 1459 dragger.top = top; 1460 this.directive = directive; 1461 1462 this.renderDragger(); 1463 } 1464 1465 // Override 1466 this.startX = this.endX; 1467 this.startY = this.endY; 1468 } 1469 }; 1470 1471 // Use the string compressor: Strmin (https://github.com/fengyuanchen/strmin) 1472 Cropper.TEMPLATE = (function (source, words) { 1473 words = words.split(","); 1474 return source.replace(/\d+/g, function (i) { 1475 return words[i]; 1476 }); 1477 })('<0 6="5-container"><0 6="5-canvas"></0><0 6="5-dragger"><1 6="5-viewer"></1><1 6="5-8 8-h"></1><1 6="5-8 8-v"></1><1 6="5-face" 3-2="all"></1><1 6="5-7 7-e" 3-2="e"></1><1 6="5-7 7-n" 3-2="n"></1><1 6="5-7 7-w" 3-2="w"></1><1 6="5-7 7-s" 3-2="s"></1><1 6="5-4 4-e" 3-2="e"></1><1 6="5-4 4-n" 3-2="n"></1><1 6="5-4 4-w" 3-2="w"></1><1 6="5-4 4-s" 3-2="s"></1><1 6="5-4 4-ne" 3-2="ne"></1><1 6="5-4 4-nw" 3-2="nw"></1><1 6="5-4 4-sw" 3-2="sw"></1><1 6="5-4 4-se" 3-2="se"></1></0></0>', "div,span,directive,data,point,cropper,class,line,dashed"); 1478 1479 /* Template source: 1480 <div class="cropper-container"> 1481 <div class="cropper-canvas"></div> 1482 <div class="cropper-dragger"> 1483 <span class="cropper-viewer"></span> 1484 <span class="cropper-dashed dashed-h"></span> 1485 <span class="cropper-dashed dashed-v"></span> 1486 <span class="cropper-face" data-directive="all"></span> 1487 <span class="cropper-line line-e" data-directive="e"></span> 1488 <span class="cropper-line line-n" data-directive="n"></span> 1489 <span class="cropper-line line-w" data-directive="w"></span> 1490 <span class="cropper-line line-s" data-directive="s"></span> 1491 <span class="cropper-point point-e" data-directive="e"></span> 1492 <span class="cropper-point point-n" data-directive="n"></span> 1493 <span class="cropper-point point-w" data-directive="w"></span> 1494 <span class="cropper-point point-s" data-directive="s"></span> 1495 <span class="cropper-point point-ne" data-directive="ne"></span> 1496 <span class="cropper-point point-nw" data-directive="nw"></span> 1497 <span class="cropper-point point-sw" data-directive="sw"></span> 1498 <span class="cropper-point point-se" data-directive="se"></span> 1499 </div> 1500 </div> 1501 */ 1502 1503 Cropper.DEFAULTS = { 1504 // Basic 1505 aspectRatio: "auto", 1506 autoCropArea: 0.8, // 80% 1507 data: { 1508 // x: 0, 1509 // y: 0, 1510 // width: 300, 1511 // height: 150 1512 }, 1513 done: $.noop, 1514 preview: "", 1515 1516 // Toggles 1517 multiple: FALSE, 1518 autoCrop: TRUE, 1519 dragCrop: TRUE, 1520 dashed: TRUE, 1521 modal: TRUE, 1522 movable: TRUE, 1523 resizable: TRUE, 1524 zoomable: TRUE, 1525 rotatable: TRUE, 1526 checkImageOrigin: TRUE, 1527 1528 // Dimensions 1529 minWidth: 0, 1530 minHeight: 0, 1531 maxWidth: INFINITY, 1532 maxHeight: INFINITY, 1533 1534 // Events 1535 build: NULL, 1536 built: NULL, 1537 dragstart: NULL, 1538 dragmove: NULL, 1539 dragend: NULL 1540 }; 1541 1542 Cropper.setDefaults = function (options) { 1543 $.extend(Cropper.DEFAULTS, options); 1544 }; 1545 1546 // Save the other cropper 1547 Cropper.other = $.fn.cropper; 1548 1549 // Register as jQuery plugin 1550 $.fn.cropper = function (options) { 1551 var args = toArray(arguments, 1), 1552 result; 1553 1554 this.each(function () { 1555 var $this = $(this), 1556 data = $this.data("cropper"), 1557 fn; 1558 1559 if (!data) { 1560 $this.data("cropper", (data = new Cropper(this, options))); 1561 } 1562 1563 if (typeof options === "string" && $.isFunction((fn = data[options]))) { 1564 result = fn.apply(data, args); 1565 } 1566 }); 1567 1568 return (typeof result !== STRING_UNDEFINED ? result : this); 1569 }; 1570 1571 $.fn.cropper.Constructor = Cropper; 1572 $.fn.cropper.setDefaults = Cropper.setDefaults; 1573 1574 // No conflict 1575 $.fn.cropper.noConflict = function () { 1576 $.fn.cropper = Cropper.other; 1577 return this; 1578 }; 1579});
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.