jquery-cropper 0.3.31 → 2.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MGRkOWE3NTdjYmVhNzZiYTAxNzJkNGUyMTliNGYzM2IwYTVkNzhjOA==
5
- data.tar.gz: !binary |-
6
- ZjI5NWM4MzRlZmExMzM3NmYyODhlMTcyOWYwY2E0ZDM5MWY5M2IxYw==
2
+ SHA1:
3
+ metadata.gz: 2e6ee7401215e8d55df0e0635218ef3ec72fe2c6
4
+ data.tar.gz: 9acdf34a0136bfb7b3e44e49830745773a79ebf9
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- YmVmN2Q2ZGUwODQ4OTUzNmFmZTQwMTJhZDYzMWUyZTgzY2MxMzM1NjMzNGNl
10
- YmZiNDIzODZlYjliMjBhMDM0Mjk5NGZjZDcwY2Q1Nzg2YzcyYWNhZWVhODdk
11
- NTAxMjgyMzliNDczZTJlMGM0YTNiY2YzZmMwYTFkNjdjMWI2NGQ=
12
- data.tar.gz: !binary |-
13
- OWM1MTE2OWY2ZjdjZmQyOTkzN2Q5MjhkNDcyMTBhYjllYjYzMzIwYzQ0ZGI1
14
- ZDc3MTYzMDBmNDU3NDg1ZGNkZTM4ZjYwMTI1ZTA3NDlhNTg3OTMzYzFhMDMx
15
- Y2U2OWY2MDdjNTEzYjMzOGZlNDliMjk5OGEzMTgzYzg2YjMwOWE=
6
+ metadata.gz: 5a90c6e0225c8d5454400feb5d308c2ee2a53fe738115195a30eb5b678cd22c2425883adbec48cbdcda81006f991bce1cf9ce8146f4dc3c993c8d69e15cfdee5
7
+ data.tar.gz: 4d5fc527fb7ce4ff8e72bc847a7f6332ccc0dab7e94a07a36c9f49a6ea2c763ddbd967be439e9968ea81bbf4c5fd5e3bf17813115199f6f74ea2ee33ce9443a7
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Fengyuan Chen and other contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Jquery::Cropper
2
2
 
3
- TODO: Write a gem description
3
+ It is a assets pipeline wrapper of jquery cropper plugin https://github.com/fengyuanchen/cropper
4
4
 
5
5
  ## Installation
6
6
 
@@ -12,18 +12,14 @@ And then execute:
12
12
 
13
13
  $ bundle
14
14
 
15
- Or install it yourself as:
15
+ Add line to application.js
16
16
 
17
- $ gem install jquery-cropper
17
+ //= require cropper
18
18
 
19
- ## Usage
19
+ Add line to application.scss
20
20
 
21
- TODO: Write usage instructions here
21
+ *= require cropper
22
22
 
23
- ## Contributing
23
+ ## License
24
24
 
25
- 1. Fork it ( http://github.com/<my-github-username>/jquery-cropper/fork )
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
25
+ Released under the [MIT](http://opensource.org/licenses/mit-license.html) license.
@@ -1,770 +1,2972 @@
1
- (function(factory) {
2
- if (typeof define === "function" && define.amd) {
1
+ /*!
2
+ * Cropper v2.2.5
3
+ * https://github.com/fengyuanchen/cropper
4
+ *
5
+ * Copyright (c) 2014-2016 Fengyuan Chen and contributors
6
+ * Released under the MIT license
7
+ *
8
+ * Date: 2016-01-18T05:42:50.800Z
9
+ */
10
+
11
+ (function (factory) {
12
+ if (typeof define === 'function' && define.amd) {
3
13
  // AMD. Register as anonymous module.
4
- define(["jquery"], factory);
14
+ define(['jquery'], factory);
15
+ } else if (typeof exports === 'object') {
16
+ // Node / CommonJS
17
+ factory(require('jquery'));
5
18
  } else {
6
19
  // Browser globals.
7
20
  factory(jQuery);
8
21
  }
9
- }(function($) {
22
+ })(function ($) {
23
+
24
+ 'use strict';
25
+
26
+ // Globals
27
+ var $window = $(window);
28
+ var $document = $(document);
29
+ var location = window.location;
30
+ var ArrayBuffer = window.ArrayBuffer;
31
+ var Uint8Array = window.Uint8Array;
32
+ var DataView = window.DataView;
33
+ var btoa = window.btoa;
34
+
35
+ // Constants
36
+ var NAMESPACE = 'cropper';
37
+
38
+ // Classes
39
+ var CLASS_MODAL = 'cropper-modal';
40
+ var CLASS_HIDE = 'cropper-hide';
41
+ var CLASS_HIDDEN = 'cropper-hidden';
42
+ var CLASS_INVISIBLE = 'cropper-invisible';
43
+ var CLASS_MOVE = 'cropper-move';
44
+ var CLASS_CROP = 'cropper-crop';
45
+ var CLASS_DISABLED = 'cropper-disabled';
46
+ var CLASS_BG = 'cropper-bg';
47
+
48
+ // Events
49
+ var EVENT_MOUSE_DOWN = 'mousedown touchstart pointerdown MSPointerDown';
50
+ var EVENT_MOUSE_MOVE = 'mousemove touchmove pointermove MSPointerMove';
51
+ var EVENT_MOUSE_UP = 'mouseup touchend touchcancel pointerup pointercancel MSPointerUp MSPointerCancel';
52
+ var EVENT_WHEEL = 'wheel mousewheel DOMMouseScroll';
53
+ var EVENT_DBLCLICK = 'dblclick';
54
+ var EVENT_LOAD = 'load.' + NAMESPACE;
55
+ var EVENT_ERROR = 'error.' + NAMESPACE;
56
+ var EVENT_RESIZE = 'resize.' + NAMESPACE; // Bind to window with namespace
57
+ var EVENT_BUILD = 'build.' + NAMESPACE;
58
+ var EVENT_BUILT = 'built.' + NAMESPACE;
59
+ var EVENT_CROP_START = 'cropstart.' + NAMESPACE;
60
+ var EVENT_CROP_MOVE = 'cropmove.' + NAMESPACE;
61
+ var EVENT_CROP_END = 'cropend.' + NAMESPACE;
62
+ var EVENT_CROP = 'crop.' + NAMESPACE;
63
+ var EVENT_ZOOM = 'zoom.' + NAMESPACE;
64
+
65
+ // RegExps
66
+ var REGEXP_ACTIONS = /e|w|s|n|se|sw|ne|nw|all|crop|move|zoom/;
67
+ var REGEXP_DATA_URL = /^data\:/;
68
+ var REGEXP_DATA_URL_HEAD = /^data\:([^\;]+)\;base64,/;
69
+ var REGEXP_DATA_URL_JPEG = /^data\:image\/jpeg.*;base64,/;
70
+
71
+ // Data keys
72
+ var DATA_PREVIEW = 'preview';
73
+ var DATA_ACTION = 'action';
74
+
75
+ // Actions
76
+ var ACTION_EAST = 'e';
77
+ var ACTION_WEST = 'w';
78
+ var ACTION_SOUTH = 's';
79
+ var ACTION_NORTH = 'n';
80
+ var ACTION_SOUTH_EAST = 'se';
81
+ var ACTION_SOUTH_WEST = 'sw';
82
+ var ACTION_NORTH_EAST = 'ne';
83
+ var ACTION_NORTH_WEST = 'nw';
84
+ var ACTION_ALL = 'all';
85
+ var ACTION_CROP = 'crop';
86
+ var ACTION_MOVE = 'move';
87
+ var ACTION_ZOOM = 'zoom';
88
+ var ACTION_NONE = 'none';
89
+
90
+ // Supports
91
+ var SUPPORT_CANVAS = $.isFunction($('<canvas>')[0].getContext);
92
+
93
+ // Maths
94
+ var num = Number;
95
+ var min = Math.min;
96
+ var max = Math.max;
97
+ var abs = Math.abs;
98
+ var sin = Math.sin;
99
+ var cos = Math.cos;
100
+ var sqrt = Math.sqrt;
101
+ var round = Math.round;
102
+ var floor = Math.floor;
103
+
104
+ // Utilities
105
+ var fromCharCode = String.fromCharCode;
106
+
107
+ function isNumber(n) {
108
+ return typeof n === 'number' && !isNaN(n);
109
+ }
110
+
111
+ function isUndefined(n) {
112
+ return typeof n === 'undefined';
113
+ }
114
+
115
+ function toArray(obj, offset) {
116
+ var args = [];
117
+
118
+ // This is necessary for IE8
119
+ if (isNumber(offset)) {
120
+ args.push(offset);
121
+ }
122
+
123
+ return args.slice.apply(obj, args);
124
+ }
125
+
126
+ // Custom proxy to avoid jQuery's guid
127
+ function proxy(fn, context) {
128
+ var args = toArray(arguments, 2);
129
+
130
+ return function () {
131
+ return fn.apply(context, args.concat(toArray(arguments)));
132
+ };
133
+ }
134
+
135
+ function isCrossOriginURL(url) {
136
+ var parts = url.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i);
137
+
138
+ return parts && (
139
+ parts[1] !== location.protocol ||
140
+ parts[2] !== location.hostname ||
141
+ parts[3] !== location.port
142
+ );
143
+ }
144
+
145
+ function addTimestamp(url) {
146
+ var timestamp = 'timestamp=' + (new Date()).getTime();
147
+
148
+ return (url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp);
149
+ }
150
+
151
+ function getCrossOrigin(crossOrigin) {
152
+ return crossOrigin ? ' crossOrigin="' + crossOrigin + '"' : '';
153
+ }
154
+
155
+ function getImageSize(image, callback) {
156
+ var newImage;
157
+
158
+ // Modern browsers
159
+ if (image.naturalWidth) {
160
+ return callback(image.naturalWidth, image.naturalHeight);
161
+ }
162
+
163
+ // IE8: Don't use `new Image()` here (#319)
164
+ newImage = document.createElement('img');
165
+
166
+ newImage.onload = function () {
167
+ callback(this.width, this.height);
168
+ };
169
+
170
+ newImage.src = image.src;
171
+ }
172
+
173
+ function getTransform(options) {
174
+ var transforms = [];
175
+ var rotate = options.rotate;
176
+ var scaleX = options.scaleX;
177
+ var scaleY = options.scaleY;
178
+
179
+ if (isNumber(rotate)) {
180
+ transforms.push('rotate(' + rotate + 'deg)');
181
+ }
182
+
183
+ if (isNumber(scaleX) && isNumber(scaleY)) {
184
+ transforms.push('scale(' + scaleX + ',' + scaleY + ')');
185
+ }
186
+
187
+ return transforms.length ? transforms.join(' ') : 'none';
188
+ }
189
+
190
+ function getRotatedSizes(data, isReversed) {
191
+ var deg = abs(data.degree) % 180;
192
+ var arc = (deg > 90 ? (180 - deg) : deg) * Math.PI / 180;
193
+ var sinArc = sin(arc);
194
+ var cosArc = cos(arc);
195
+ var width = data.width;
196
+ var height = data.height;
197
+ var aspectRatio = data.aspectRatio;
198
+ var newWidth;
199
+ var newHeight;
200
+
201
+ if (!isReversed) {
202
+ newWidth = width * cosArc + height * sinArc;
203
+ newHeight = width * sinArc + height * cosArc;
204
+ } else {
205
+ newWidth = width / (cosArc + sinArc / aspectRatio);
206
+ newHeight = newWidth / aspectRatio;
207
+ }
208
+
209
+ return {
210
+ width: newWidth,
211
+ height: newHeight
212
+ };
213
+ }
214
+
215
+ function getSourceCanvas(image, data) {
216
+ var canvas = $('<canvas>')[0];
217
+ var context = canvas.getContext('2d');
218
+ var x = 0;
219
+ var y = 0;
220
+ var width = data.naturalWidth;
221
+ var height = data.naturalHeight;
222
+ var rotate = data.rotate;
223
+ var scaleX = data.scaleX;
224
+ var scaleY = data.scaleY;
225
+ var scalable = isNumber(scaleX) && isNumber(scaleY) && (scaleX !== 1 || scaleY !== 1);
226
+ var rotatable = isNumber(rotate) && rotate !== 0;
227
+ var advanced = rotatable || scalable;
228
+ var canvasWidth = width;
229
+ var canvasHeight = height;
230
+ var translateX;
231
+ var translateY;
232
+ var rotated;
233
+
234
+ if (scalable) {
235
+ translateX = width / 2;
236
+ translateY = height / 2;
237
+ }
238
+
239
+ if (rotatable) {
240
+ rotated = getRotatedSizes({
241
+ width: width,
242
+ height: height,
243
+ degree: rotate
244
+ });
245
+
246
+ canvasWidth = rotated.width;
247
+ canvasHeight = rotated.height;
248
+ translateX = rotated.width / 2;
249
+ translateY = rotated.height / 2;
250
+ }
251
+
252
+ canvas.width = canvasWidth;
253
+ canvas.height = canvasHeight;
254
+
255
+ if (advanced) {
256
+ x = -width / 2;
257
+ y = -height / 2;
258
+
259
+ context.save();
260
+ context.translate(translateX, translateY);
261
+ }
262
+
263
+ if (rotatable) {
264
+ context.rotate(rotate * Math.PI / 180);
265
+ }
10
266
 
11
- "use strict";
267
+ // Should call `scale` after rotated
268
+ if (scalable) {
269
+ context.scale(scaleX, scaleY);
270
+ }
271
+
272
+ context.drawImage(image, floor(x), floor(y), floor(width), floor(height));
273
+
274
+ if (advanced) {
275
+ context.restore();
276
+ }
277
+
278
+ return canvas;
279
+ }
280
+
281
+ function getTouchesCenter(touches) {
282
+ var length = touches.length;
283
+ var pageX = 0;
284
+ var pageY = 0;
12
285
 
13
- var $window = $(window),
14
- Cropper = function(element, options) {
15
- options = $.isPlainObject(options) ? options : {};
16
- this.$image = $(element);
17
- this.defaults = $.extend({}, Cropper.defaults, this.$image.data(), options);
18
- this.init();
286
+ if (length) {
287
+ $.each(touches, function (i, touch) {
288
+ pageX += touch.pageX;
289
+ pageY += touch.pageY;
290
+ });
291
+
292
+ pageX /= length;
293
+ pageY /= length;
294
+ }
295
+
296
+ return {
297
+ pageX: pageX,
298
+ pageY: pageY
19
299
  };
300
+ }
301
+
302
+ function getStringFromCharCode(dataView, start, length) {
303
+ var str = '';
304
+ var i;
305
+
306
+ for (i = start, length += start; i < length; i++) {
307
+ str += fromCharCode(dataView.getUint8(i));
308
+ }
309
+
310
+ return str;
311
+ }
312
+
313
+ function getOrientation(arrayBuffer) {
314
+ var dataView = new DataView(arrayBuffer);
315
+ var length = dataView.byteLength;
316
+ var orientation;
317
+ var exifIDCode;
318
+ var tiffOffset;
319
+ var firstIFDOffset;
320
+ var littleEndian;
321
+ var endianness;
322
+ var app1Start;
323
+ var ifdStart;
324
+ var offset;
325
+ var i;
326
+
327
+ // Only handle JPEG image (start by 0xFFD8)
328
+ if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
329
+ offset = 2;
330
+
331
+ while (offset < length) {
332
+ if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
333
+ app1Start = offset;
334
+ break;
335
+ }
336
+
337
+ offset++;
338
+ }
339
+ }
340
+
341
+ if (app1Start) {
342
+ exifIDCode = app1Start + 4;
343
+ tiffOffset = app1Start + 10;
344
+
345
+ if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
346
+ endianness = dataView.getUint16(tiffOffset);
347
+ littleEndian = endianness === 0x4949;
348
+
349
+ if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
350
+ if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
351
+ firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
352
+
353
+ if (firstIFDOffset >= 0x00000008) {
354
+ ifdStart = tiffOffset + firstIFDOffset;
355
+ }
356
+ }
357
+ }
358
+ }
359
+ }
360
+
361
+ if (ifdStart) {
362
+ length = dataView.getUint16(ifdStart, littleEndian);
363
+
364
+ for (i = 0; i < length; i++) {
365
+ offset = ifdStart + i * 12 + 2;
366
+
367
+ if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) {
368
+
369
+ // 8 is the offset of the current tag's value
370
+ offset += 8;
371
+
372
+ // Get the original orientation value
373
+ orientation = dataView.getUint16(offset, littleEndian);
374
+
375
+ // Override the orientation with the default value: 1
376
+ dataView.setUint16(offset, 1, littleEndian);
377
+ break;
378
+ }
379
+ }
380
+ }
381
+
382
+ return orientation;
383
+ }
384
+
385
+ function dataURLToArrayBuffer(dataURL) {
386
+ var base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, '');
387
+ var binary = atob(base64);
388
+ var length = binary.length;
389
+ var arrayBuffer = new ArrayBuffer(length);
390
+ var dataView = new Uint8Array(arrayBuffer);
391
+ var i;
392
+
393
+ for (i = 0; i < length; i++) {
394
+ dataView[i] = binary.charCodeAt(i);
395
+ }
396
+
397
+ return arrayBuffer;
398
+ }
399
+
400
+ // Only available for JPEG image
401
+ function arrayBufferToDataURL(arrayBuffer) {
402
+ var dataView = new Uint8Array(arrayBuffer);
403
+ var length = dataView.length;
404
+ var base64 = '';
405
+ var i;
406
+
407
+ for (i = 0; i < length; i++) {
408
+ base64 += fromCharCode(dataView[i]);
409
+ }
410
+
411
+ return 'data:image/jpeg;base64,' + btoa(base64);
412
+ }
413
+
414
+ function Cropper(element, options) {
415
+ this.$element = $(element);
416
+ this.options = $.extend({}, Cropper.DEFAULTS, $.isPlainObject(options) && options);
417
+ this.isLoaded = false;
418
+ this.isBuilt = false;
419
+ this.isCompleted = false;
420
+ this.isRotated = false;
421
+ this.isCropped = false;
422
+ this.isDisabled = false;
423
+ this.isReplaced = false;
424
+ this.isLimited = false;
425
+ this.wheeling = false;
426
+ this.isImg = false;
427
+ this.originalUrl = '';
428
+ this.canvas = null;
429
+ this.cropBox = null;
430
+ this.init();
431
+ }
20
432
 
21
433
  Cropper.prototype = {
22
- construstor: Cropper,
434
+ constructor: Cropper,
23
435
 
24
- init: function() {
25
- this.setAspectRatio(this.defaults.aspectRatio);
26
- this.render();
436
+ init: function () {
437
+ var $this = this.$element;
438
+ var url;
439
+
440
+ if ($this.is('img')) {
441
+ this.isImg = true;
442
+
443
+ // Should use `$.fn.attr` here. e.g.: "img/picture.jpg"
444
+ this.originalUrl = url = $this.attr('src');
445
+
446
+ // Stop when it's a blank image
447
+ if (!url) {
448
+ return;
449
+ }
450
+
451
+ // Should use `$.fn.prop` here. e.g.: "http://example.com/img/picture.jpg"
452
+ url = $this.prop('src');
453
+ } else if ($this.is('canvas') && SUPPORT_CANVAS) {
454
+ url = $this[0].toDataURL();
455
+ }
456
+
457
+ this.load(url);
458
+ },
459
+
460
+ // A shortcut for triggering custom events
461
+ trigger: function (type, data) {
462
+ var e = $.Event(type, data);
463
+
464
+ this.$element.trigger(e);
465
+
466
+ return e;
27
467
  },
28
468
 
29
- render: function(callback) {
30
- var that = this,
31
- $image = this.$image,
32
- $clone,
33
- src;
469
+ load: function (url) {
470
+ var options = this.options;
471
+ var $this = this.$element;
472
+ var read;
473
+ var xhr;
34
474
 
35
- if (this.active) {
475
+ if (!url) {
36
476
  return;
37
477
  }
38
478
 
39
- if (this.$clone) {
40
- this.$clone.remove(); // Remove the old clone
479
+ // Trigger build event first
480
+ $this.one(EVENT_BUILD, options.build);
481
+
482
+ if (this.trigger(EVENT_BUILD).isDefaultPrevented()) {
483
+ return;
41
484
  }
42
485
 
43
- src = $image.attr("src"); // Don't use "prop"
44
- $clone = $('<img src="' + src + '">');
486
+ this.url = url;
487
+ this.image = {};
45
488
 
46
- $clone.on("load", function() {
47
- var image;
489
+ if (!options.checkOrientation || !ArrayBuffer) {
490
+ return this.clone();
491
+ }
48
492
 
49
- $clone.off("load");
493
+ read = $.proxy(this.read, this);
50
494
 
51
- if (this.naturalWidth && this.naturalHeight) {
52
- image = {
53
- naturalHeight: this.naturalHeight,
54
- naturalWidth: this.naturalWidth
55
- };
56
- } else {
57
- Cropper.fn.size($clone, {
58
- height: "auto",
59
- width: "auto"
60
- });
61
-
62
- image = Cropper.fn.size($clone);
63
- image = {
64
- naturalHeight: image.height,
65
- naturalWidth: image.width
66
- };
67
- }
495
+ // XMLHttpRequest disallows to open a Data URL in some browsers like IE11 and Safari
496
+ if (REGEXP_DATA_URL.test(url)) {
497
+ return REGEXP_DATA_URL_JPEG.test(url) ?
498
+ read(dataURLToArrayBuffer(url)) :
499
+ this.clone();
500
+ }
68
501
 
69
- Cropper.fn.size($clone, {
70
- height: "100%",
71
- width: "100%"
72
- });
502
+ xhr = new XMLHttpRequest();
73
503
 
74
- image.aspectRatio = image.naturalWidth / image.naturalHeight;
75
- that.src = src;
76
- that.image = image;
77
- that.active = true;
78
- that.createCropper();
79
- });
504
+ xhr.onerror = xhr.onabort = $.proxy(function () {
505
+ this.clone();
506
+ }, this);
80
507
 
81
- if ($.isFunction(callback)) {
82
- $image.on("ready.cropper", callback);
508
+ xhr.onload = function () {
509
+ read(this.response);
510
+ };
511
+
512
+ xhr.open('get', url);
513
+ xhr.responseType = 'arraybuffer';
514
+ xhr.send();
515
+ },
516
+
517
+ read: function (arrayBuffer) {
518
+ var options = this.options;
519
+ var orientation = getOrientation(arrayBuffer);
520
+ var image = this.image;
521
+ var rotate;
522
+ var scaleX;
523
+ var scaleY;
524
+
525
+ if (orientation > 1) {
526
+ this.url = arrayBufferToDataURL(arrayBuffer);
527
+
528
+ switch (orientation) {
529
+
530
+ // flip horizontal
531
+ case 2:
532
+ scaleX = -1;
533
+ break;
534
+
535
+ // rotate left 180°
536
+ case 3:
537
+ rotate = -180;
538
+ break;
539
+
540
+ // flip vertical
541
+ case 4:
542
+ scaleY = -1;
543
+ break;
544
+
545
+ // flip vertical + rotate right 90°
546
+ case 5:
547
+ rotate = 90;
548
+ scaleY = -1;
549
+ break;
550
+
551
+ // rotate right 90°
552
+ case 6:
553
+ rotate = 90;
554
+ break;
555
+
556
+ // flip horizontal + rotate right 90°
557
+ case 7:
558
+ rotate = 90;
559
+ scaleX = -1;
560
+ break;
561
+
562
+ // rotate left 90°
563
+ case 8:
564
+ rotate = -90;
565
+ break;
566
+ }
83
567
  }
84
568
 
85
- this.$clone = $clone;
86
- $image.after($clone);
569
+ if (options.rotatable) {
570
+ image.rotate = rotate;
571
+ }
572
+
573
+ if (options.scalable) {
574
+ image.scaleX = scaleX;
575
+ image.scaleY = scaleY;
576
+ }
577
+
578
+ this.clone();
87
579
  },
88
580
 
89
- unrender: function() {
90
- if (this.active) {
91
- this.active = false;
92
- this.removeCropper();
93
- this.src = "";
94
- this.image = null;
95
- this.cropper = null;
96
- this.dragger = null;
581
+ clone: function () {
582
+ var options = this.options;
583
+ var $this = this.$element;
584
+ var url = this.url;
585
+ var crossOrigin = '';
586
+ var crossOriginUrl;
587
+ var $clone;
588
+
589
+ if (options.checkCrossOrigin && isCrossOriginURL(url)) {
590
+ crossOrigin = $this.prop('crossOrigin');
591
+
592
+ if (crossOrigin) {
593
+ crossOriginUrl = url;
594
+ } else {
595
+ crossOrigin = 'anonymous';
596
+
597
+ // Bust cache (#148) when there is not a "crossOrigin" property
598
+ crossOriginUrl = addTimestamp(url);
599
+ }
600
+ }
601
+
602
+ this.crossOrigin = crossOrigin;
603
+ this.crossOriginUrl = crossOriginUrl;
604
+ this.$clone = $clone = $('<img' + getCrossOrigin(crossOrigin) + ' src="' + (crossOriginUrl || url) + '">');
605
+
606
+ if (this.isImg) {
607
+ if ($this[0].complete) {
608
+ this.start();
609
+ } else {
610
+ $this.one(EVENT_LOAD, $.proxy(this.start, this));
611
+ }
612
+ } else {
613
+ $clone.
614
+ one(EVENT_LOAD, $.proxy(this.start, this)).
615
+ one(EVENT_ERROR, $.proxy(this.stop, this)).
616
+ addClass(CLASS_HIDE).
617
+ insertAfter($this);
97
618
  }
98
619
  },
99
620
 
100
- rerender: function() {
101
- this.unrender();
102
- this.render();
621
+ start: function () {
622
+ var $image = this.$element;
623
+ var $clone = this.$clone;
624
+
625
+ if (!this.isImg) {
626
+ $clone.off(EVENT_ERROR, this.stop);
627
+ $image = $clone;
628
+ }
629
+
630
+ getImageSize($image[0], $.proxy(function (naturalWidth, naturalHeight) {
631
+ $.extend(this.image, {
632
+ naturalWidth: naturalWidth,
633
+ naturalHeight: naturalHeight,
634
+ aspectRatio: naturalWidth / naturalHeight
635
+ });
636
+
637
+ this.isLoaded = true;
638
+ this.build();
639
+ }, this));
103
640
  },
104
641
 
105
- resize: function() {
106
- clearTimeout(this.resizing);
107
- this.resizing = setTimeout($.proxy(this.rerender, this), 200);
642
+ stop: function () {
643
+ this.$clone.remove();
644
+ this.$clone = null;
108
645
  },
109
646
 
110
- createCropper: function() {
111
- this.$cropper = $(Cropper.template);
112
- this.$dragger = this.$cropper.find(".cropper-dragger");
113
- Cropper.fn.toggle(this.$image);
114
- this.$image.after(this.$cropper);
115
- this.$cropper.prepend(this.$clone);
647
+ build: function () {
648
+ var options = this.options;
649
+ var $this = this.$element;
650
+ var $clone = this.$clone;
651
+ var $cropper;
652
+ var $cropBox;
653
+ var $face;
654
+
655
+ if (!this.isLoaded) {
656
+ return;
657
+ }
658
+
659
+ // Unbuild first when replace
660
+ if (this.isBuilt) {
661
+ this.unbuild();
662
+ }
663
+
664
+ // Create cropper elements
665
+ this.$container = $this.parent();
666
+ this.$cropper = $cropper = $(Cropper.TEMPLATE);
667
+ this.$canvas = $cropper.find('.cropper-canvas').append($clone);
668
+ this.$dragBox = $cropper.find('.cropper-drag-box');
669
+ this.$cropBox = $cropBox = $cropper.find('.cropper-crop-box');
670
+ this.$viewBox = $cropper.find('.cropper-view-box');
671
+ this.$face = $face = $cropBox.find('.cropper-face');
672
+
673
+ // Hide the original image
674
+ $this.addClass(CLASS_HIDDEN).after($cropper);
675
+
676
+ // Show the clone image if is hidden
677
+ if (!this.isImg) {
678
+ $clone.removeClass(CLASS_HIDE);
679
+ }
680
+
681
+ this.initPreview();
682
+ this.bind();
683
+
684
+ options.aspectRatio = max(0, options.aspectRatio) || NaN;
685
+ options.viewMode = max(0, min(3, round(options.viewMode))) || 0;
686
+
687
+ if (options.autoCrop) {
688
+ this.isCropped = true;
689
+
690
+ if (options.modal) {
691
+ this.$dragBox.addClass(CLASS_MODAL);
692
+ }
693
+ } else {
694
+ $cropBox.addClass(CLASS_HIDDEN);
695
+ }
696
+
697
+ if (!options.guides) {
698
+ $cropBox.find('.cropper-dashed').addClass(CLASS_HIDDEN);
699
+ }
116
700
 
117
- if (!this.defaults.modal) {
118
- Cropper.fn.toggle(this.$cropper.find(".cropper-modal"));
701
+ if (!options.center) {
702
+ $cropBox.find('.cropper-center').addClass(CLASS_HIDDEN);
119
703
  }
120
704
 
121
- this.setPreview();
122
- this.addListener();
705
+ if (options.cropBoxMovable) {
706
+ $face.addClass(CLASS_MOVE).data(DATA_ACTION, ACTION_ALL);
707
+ }
708
+
709
+ if (!options.highlight) {
710
+ $face.addClass(CLASS_INVISIBLE);
711
+ }
712
+
713
+ if (options.background) {
714
+ $cropper.addClass(CLASS_BG);
715
+ }
716
+
717
+ if (!options.cropBoxResizable) {
718
+ $cropBox.find('.cropper-line, .cropper-point').addClass(CLASS_HIDDEN);
719
+ }
720
+
721
+ this.setDragMode(options.dragMode);
722
+ this.render();
723
+ this.isBuilt = true;
724
+ this.setData(options.data);
725
+ $this.one(EVENT_BUILT, options.built);
726
+
727
+ // Trigger the built event asynchronously to keep `data('cropper')` is defined
728
+ setTimeout($.proxy(function () {
729
+ this.trigger(EVENT_BUILT);
730
+ this.isCompleted = true;
731
+ }, this), 0);
123
732
  },
124
733
 
125
- removeCropper: function() {
126
- this.removeListener();
734
+ unbuild: function () {
735
+ if (!this.isBuilt) {
736
+ return;
737
+ }
738
+
739
+ this.isBuilt = false;
740
+ this.isCompleted = false;
741
+ this.initialImage = null;
742
+
743
+ // Clear `initialCanvas` is necessary when replace
744
+ this.initialCanvas = null;
745
+ this.initialCropBox = null;
746
+ this.container = null;
747
+ this.canvas = null;
748
+
749
+ // Clear `cropBox` is necessary when replace
750
+ this.cropBox = null;
751
+ this.unbind();
752
+
753
+ this.resetPreview();
127
754
  this.$preview = null;
128
- this.$clone.remove();
129
- this.$clone = null;
130
- this.$dragger = null;
755
+
756
+ this.$viewBox = null;
757
+ this.$cropBox = null;
758
+ this.$dragBox = null;
759
+ this.$canvas = null;
760
+ this.$container = null;
761
+
131
762
  this.$cropper.remove();
132
763
  this.$cropper = null;
133
- Cropper.fn.toggle(this.$image);
134
764
  },
135
765
 
136
- addListener: function() {
137
- this.$cropper.bind("mousedown touchstart", $.proxy(this.dragstart, this));
138
- this.$cropper.bind("mousemove touchmove", $.proxy(this.dragmove, this));
139
- this.$cropper.bind("mouseup mouseleave touchend touchleave", $.proxy(this.dragend, this));
140
- $window.on("resize", $.proxy(this.resize, this));
766
+ render: function () {
767
+ this.initContainer();
768
+ this.initCanvas();
769
+ this.initCropBox();
770
+
771
+ this.renderCanvas();
772
+
773
+ if (this.isCropped) {
774
+ this.renderCropBox();
775
+ }
776
+ },
777
+
778
+ initContainer: function () {
779
+ var options = this.options;
780
+ var $this = this.$element;
781
+ var $container = this.$container;
782
+ var $cropper = this.$cropper;
783
+
784
+ $cropper.addClass(CLASS_HIDDEN);
785
+ $this.removeClass(CLASS_HIDDEN);
786
+
787
+ $cropper.css((this.container = {
788
+ width: max($container.width(), num(options.minContainerWidth) || 200),
789
+ height: max($container.height(), num(options.minContainerHeight) || 100)
790
+ }));
791
+
792
+ $this.addClass(CLASS_HIDDEN);
793
+ $cropper.removeClass(CLASS_HIDDEN);
794
+ },
795
+
796
+ // Canvas (image wrapper)
797
+ initCanvas: function () {
798
+ var viewMode = this.options.viewMode;
799
+ var container = this.container;
800
+ var containerWidth = container.width;
801
+ var containerHeight = container.height;
802
+ var image = this.image;
803
+ var imageNaturalWidth = image.naturalWidth;
804
+ var imageNaturalHeight = image.naturalHeight;
805
+ var is90Degree = abs(image.rotate) === 90;
806
+ var naturalWidth = is90Degree ? imageNaturalHeight : imageNaturalWidth;
807
+ var naturalHeight = is90Degree ? imageNaturalWidth : imageNaturalHeight;
808
+ var aspectRatio = naturalWidth / naturalHeight;
809
+ var canvasWidth = containerWidth;
810
+ var canvasHeight = containerHeight;
811
+ var canvas;
812
+
813
+ if (containerHeight * aspectRatio > containerWidth) {
814
+ if (viewMode === 3) {
815
+ canvasWidth = containerHeight * aspectRatio;
816
+ } else {
817
+ canvasHeight = containerWidth / aspectRatio;
818
+ }
819
+ } else {
820
+ if (viewMode === 3) {
821
+ canvasHeight = containerWidth / aspectRatio;
822
+ } else {
823
+ canvasWidth = containerHeight * aspectRatio;
824
+ }
825
+ }
826
+
827
+ canvas = {
828
+ naturalWidth: naturalWidth,
829
+ naturalHeight: naturalHeight,
830
+ aspectRatio: aspectRatio,
831
+ width: canvasWidth,
832
+ height: canvasHeight
833
+ };
834
+
835
+ canvas.oldLeft = canvas.left = (containerWidth - canvasWidth) / 2;
836
+ canvas.oldTop = canvas.top = (containerHeight - canvasHeight) / 2;
837
+
838
+ this.canvas = canvas;
839
+ this.isLimited = (viewMode === 1 || viewMode === 2);
840
+ this.limitCanvas(true, true);
841
+ this.initialImage = $.extend({}, image);
842
+ this.initialCanvas = $.extend({}, canvas);
843
+ },
844
+
845
+ limitCanvas: function (isSizeLimited, isPositionLimited) {
846
+ var options = this.options;
847
+ var viewMode = options.viewMode;
848
+ var container = this.container;
849
+ var containerWidth = container.width;
850
+ var containerHeight = container.height;
851
+ var canvas = this.canvas;
852
+ var aspectRatio = canvas.aspectRatio;
853
+ var cropBox = this.cropBox;
854
+ var isCropped = this.isCropped && cropBox;
855
+ var minCanvasWidth;
856
+ var minCanvasHeight;
857
+ var newCanvasLeft;
858
+ var newCanvasTop;
859
+
860
+ if (isSizeLimited) {
861
+ minCanvasWidth = num(options.minCanvasWidth) || 0;
862
+ minCanvasHeight = num(options.minCanvasHeight) || 0;
863
+
864
+ if (viewMode) {
865
+ if (viewMode > 1) {
866
+ minCanvasWidth = max(minCanvasWidth, containerWidth);
867
+ minCanvasHeight = max(minCanvasHeight, containerHeight);
868
+
869
+ if (viewMode === 3) {
870
+ if (minCanvasHeight * aspectRatio > minCanvasWidth) {
871
+ minCanvasWidth = minCanvasHeight * aspectRatio;
872
+ } else {
873
+ minCanvasHeight = minCanvasWidth / aspectRatio;
874
+ }
875
+ }
876
+ } else {
877
+ if (minCanvasWidth) {
878
+ minCanvasWidth = max(minCanvasWidth, isCropped ? cropBox.width : 0);
879
+ } else if (minCanvasHeight) {
880
+ minCanvasHeight = max(minCanvasHeight, isCropped ? cropBox.height : 0);
881
+ } else if (isCropped) {
882
+ minCanvasWidth = cropBox.width;
883
+ minCanvasHeight = cropBox.height;
884
+
885
+ if (minCanvasHeight * aspectRatio > minCanvasWidth) {
886
+ minCanvasWidth = minCanvasHeight * aspectRatio;
887
+ } else {
888
+ minCanvasHeight = minCanvasWidth / aspectRatio;
889
+ }
890
+ }
891
+ }
892
+ }
893
+
894
+ if (minCanvasWidth && minCanvasHeight) {
895
+ if (minCanvasHeight * aspectRatio > minCanvasWidth) {
896
+ minCanvasHeight = minCanvasWidth / aspectRatio;
897
+ } else {
898
+ minCanvasWidth = minCanvasHeight * aspectRatio;
899
+ }
900
+ } else if (minCanvasWidth) {
901
+ minCanvasHeight = minCanvasWidth / aspectRatio;
902
+ } else if (minCanvasHeight) {
903
+ minCanvasWidth = minCanvasHeight * aspectRatio;
904
+ }
905
+
906
+ canvas.minWidth = minCanvasWidth;
907
+ canvas.minHeight = minCanvasHeight;
908
+ canvas.maxWidth = Infinity;
909
+ canvas.maxHeight = Infinity;
910
+ }
911
+
912
+ if (isPositionLimited) {
913
+ if (viewMode) {
914
+ newCanvasLeft = containerWidth - canvas.width;
915
+ newCanvasTop = containerHeight - canvas.height;
916
+
917
+ canvas.minLeft = min(0, newCanvasLeft);
918
+ canvas.minTop = min(0, newCanvasTop);
919
+ canvas.maxLeft = max(0, newCanvasLeft);
920
+ canvas.maxTop = max(0, newCanvasTop);
921
+
922
+ if (isCropped && this.isLimited) {
923
+ canvas.minLeft = min(
924
+ cropBox.left,
925
+ cropBox.left + cropBox.width - canvas.width
926
+ );
927
+ canvas.minTop = min(
928
+ cropBox.top,
929
+ cropBox.top + cropBox.height - canvas.height
930
+ );
931
+ canvas.maxLeft = cropBox.left;
932
+ canvas.maxTop = cropBox.top;
933
+
934
+ if (viewMode === 2) {
935
+ if (canvas.width >= containerWidth) {
936
+ canvas.minLeft = min(0, newCanvasLeft);
937
+ canvas.maxLeft = max(0, newCanvasLeft);
938
+ }
939
+
940
+ if (canvas.height >= containerHeight) {
941
+ canvas.minTop = min(0, newCanvasTop);
942
+ canvas.maxTop = max(0, newCanvasTop);
943
+ }
944
+ }
945
+ }
946
+ } else {
947
+ canvas.minLeft = -canvas.width;
948
+ canvas.minTop = -canvas.height;
949
+ canvas.maxLeft = containerWidth;
950
+ canvas.maxTop = containerHeight;
951
+ }
952
+ }
953
+ },
954
+
955
+ renderCanvas: function (isChanged) {
956
+ var canvas = this.canvas;
957
+ var image = this.image;
958
+ var rotate = image.rotate;
959
+ var naturalWidth = image.naturalWidth;
960
+ var naturalHeight = image.naturalHeight;
961
+ var aspectRatio;
962
+ var rotated;
963
+
964
+ if (this.isRotated) {
965
+ this.isRotated = false;
966
+
967
+ // Computes rotated sizes with image sizes
968
+ rotated = getRotatedSizes({
969
+ width: image.width,
970
+ height: image.height,
971
+ degree: rotate
972
+ });
973
+
974
+ aspectRatio = rotated.width / rotated.height;
975
+
976
+ if (aspectRatio !== canvas.aspectRatio) {
977
+ canvas.left -= (rotated.width - canvas.width) / 2;
978
+ canvas.top -= (rotated.height - canvas.height) / 2;
979
+ canvas.width = rotated.width;
980
+ canvas.height = rotated.height;
981
+ canvas.aspectRatio = aspectRatio;
982
+ canvas.naturalWidth = naturalWidth;
983
+ canvas.naturalHeight = naturalHeight;
984
+
985
+ // Computes rotated sizes with natural image sizes
986
+ if (rotate % 180) {
987
+ rotated = getRotatedSizes({
988
+ width: naturalWidth,
989
+ height: naturalHeight,
990
+ degree: rotate
991
+ });
992
+
993
+ canvas.naturalWidth = rotated.width;
994
+ canvas.naturalHeight = rotated.height;
995
+ }
996
+
997
+ this.limitCanvas(true, false);
998
+ }
999
+ }
1000
+
1001
+ if (canvas.width > canvas.maxWidth || canvas.width < canvas.minWidth) {
1002
+ canvas.left = canvas.oldLeft;
1003
+ }
1004
+
1005
+ if (canvas.height > canvas.maxHeight || canvas.height < canvas.minHeight) {
1006
+ canvas.top = canvas.oldTop;
1007
+ }
1008
+
1009
+ canvas.width = min(max(canvas.width, canvas.minWidth), canvas.maxWidth);
1010
+ canvas.height = min(max(canvas.height, canvas.minHeight), canvas.maxHeight);
1011
+
1012
+ this.limitCanvas(false, true);
1013
+
1014
+ canvas.oldLeft = canvas.left = min(max(canvas.left, canvas.minLeft), canvas.maxLeft);
1015
+ canvas.oldTop = canvas.top = min(max(canvas.top, canvas.minTop), canvas.maxTop);
1016
+
1017
+ this.$canvas.css({
1018
+ width: canvas.width,
1019
+ height: canvas.height,
1020
+ left: canvas.left,
1021
+ top: canvas.top
1022
+ });
1023
+
1024
+ this.renderImage();
1025
+
1026
+ if (this.isCropped && this.isLimited) {
1027
+ this.limitCropBox(true, true);
1028
+ }
1029
+
1030
+ if (isChanged) {
1031
+ this.output();
1032
+ }
1033
+ },
1034
+
1035
+ renderImage: function (isChanged) {
1036
+ var canvas = this.canvas;
1037
+ var image = this.image;
1038
+ var reversed;
1039
+
1040
+ if (image.rotate) {
1041
+ reversed = getRotatedSizes({
1042
+ width: canvas.width,
1043
+ height: canvas.height,
1044
+ degree: image.rotate,
1045
+ aspectRatio: image.aspectRatio
1046
+ }, true);
1047
+ }
1048
+
1049
+ $.extend(image, reversed ? {
1050
+ width: reversed.width,
1051
+ height: reversed.height,
1052
+ left: (canvas.width - reversed.width) / 2,
1053
+ top: (canvas.height - reversed.height) / 2
1054
+ } : {
1055
+ width: canvas.width,
1056
+ height: canvas.height,
1057
+ left: 0,
1058
+ top: 0
1059
+ });
1060
+
1061
+ this.$clone.css({
1062
+ width: image.width,
1063
+ height: image.height,
1064
+ marginLeft: image.left,
1065
+ marginTop: image.top,
1066
+ transform: getTransform(image)
1067
+ });
1068
+
1069
+ if (isChanged) {
1070
+ this.output();
1071
+ }
141
1072
  },
142
1073
 
143
- removeListener: function() {
144
- this.$cropper.unbind("mousedown touchstart", this.dragstart);
145
- this.$cropper.unbind("mousemove touchmove", this.dragmove);
146
- this.$cropper.unbind("mouseup mouseleave touchend touchleave", this.dragend);
147
- $window.off("resize", this.resize);
1074
+ initCropBox: function () {
1075
+ var options = this.options;
1076
+ var canvas = this.canvas;
1077
+ var aspectRatio = options.aspectRatio;
1078
+ var autoCropArea = num(options.autoCropArea) || 0.8;
1079
+ var cropBox = {
1080
+ width: canvas.width,
1081
+ height: canvas.height
1082
+ };
1083
+
1084
+ if (aspectRatio) {
1085
+ if (canvas.height * aspectRatio > canvas.width) {
1086
+ cropBox.height = cropBox.width / aspectRatio;
1087
+ } else {
1088
+ cropBox.width = cropBox.height * aspectRatio;
1089
+ }
1090
+ }
1091
+
1092
+ this.cropBox = cropBox;
1093
+ this.limitCropBox(true, true);
1094
+
1095
+ // Initialize auto crop area
1096
+ cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth);
1097
+ cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight);
1098
+
1099
+ // The width of auto crop area must large than "minWidth", and the height too. (#164)
1100
+ cropBox.width = max(cropBox.minWidth, cropBox.width * autoCropArea);
1101
+ cropBox.height = max(cropBox.minHeight, cropBox.height * autoCropArea);
1102
+ cropBox.oldLeft = cropBox.left = canvas.left + (canvas.width - cropBox.width) / 2;
1103
+ cropBox.oldTop = cropBox.top = canvas.top + (canvas.height - cropBox.height) / 2;
1104
+
1105
+ this.initialCropBox = $.extend({}, cropBox);
148
1106
  },
149
1107
 
150
- setPreview: function() {
151
- var preview = this.defaults.preview;
1108
+ limitCropBox: function (isSizeLimited, isPositionLimited) {
1109
+ var options = this.options;
1110
+ var aspectRatio = options.aspectRatio;
1111
+ var container = this.container;
1112
+ var containerWidth = container.width;
1113
+ var containerHeight = container.height;
1114
+ var canvas = this.canvas;
1115
+ var cropBox = this.cropBox;
1116
+ var isLimited = this.isLimited;
1117
+ var minCropBoxWidth;
1118
+ var minCropBoxHeight;
1119
+ var maxCropBoxWidth;
1120
+ var maxCropBoxHeight;
1121
+
1122
+ if (isSizeLimited) {
1123
+ minCropBoxWidth = num(options.minCropBoxWidth) || 0;
1124
+ minCropBoxHeight = num(options.minCropBoxHeight) || 0;
1125
+
1126
+ // The min/maxCropBoxWidth/Height must be less than containerWidth/Height
1127
+ minCropBoxWidth = min(minCropBoxWidth, containerWidth);
1128
+ minCropBoxHeight = min(minCropBoxHeight, containerHeight);
1129
+ maxCropBoxWidth = min(containerWidth, isLimited ? canvas.width : containerWidth);
1130
+ maxCropBoxHeight = min(containerHeight, isLimited ? canvas.height : containerHeight);
1131
+
1132
+ if (aspectRatio) {
1133
+ if (minCropBoxWidth && minCropBoxHeight) {
1134
+ if (minCropBoxHeight * aspectRatio > minCropBoxWidth) {
1135
+ minCropBoxHeight = minCropBoxWidth / aspectRatio;
1136
+ } else {
1137
+ minCropBoxWidth = minCropBoxHeight * aspectRatio;
1138
+ }
1139
+ } else if (minCropBoxWidth) {
1140
+ minCropBoxHeight = minCropBoxWidth / aspectRatio;
1141
+ } else if (minCropBoxHeight) {
1142
+ minCropBoxWidth = minCropBoxHeight * aspectRatio;
1143
+ }
152
1144
 
153
- this.$preview = this.$cropper.find(".cropper-preview");
1145
+ if (maxCropBoxHeight * aspectRatio > maxCropBoxWidth) {
1146
+ maxCropBoxHeight = maxCropBoxWidth / aspectRatio;
1147
+ } else {
1148
+ maxCropBoxWidth = maxCropBoxHeight * aspectRatio;
1149
+ }
1150
+ }
154
1151
 
155
- if (typeof preview === "string" && preview.length > 0) {
156
- this.$preview = this.$preview.add(preview);
1152
+ // The minWidth/Height must be less than maxWidth/Height
1153
+ cropBox.minWidth = min(minCropBoxWidth, maxCropBoxWidth);
1154
+ cropBox.minHeight = min(minCropBoxHeight, maxCropBoxHeight);
1155
+ cropBox.maxWidth = maxCropBoxWidth;
1156
+ cropBox.maxHeight = maxCropBoxHeight;
157
1157
  }
158
1158
 
159
- this.$preview.html('<img src="' + this.src + '">');
160
- this.setCropper();
1159
+ if (isPositionLimited) {
1160
+ if (isLimited) {
1161
+ cropBox.minLeft = max(0, canvas.left);
1162
+ cropBox.minTop = max(0, canvas.top);
1163
+ cropBox.maxLeft = min(containerWidth, canvas.left + canvas.width) - cropBox.width;
1164
+ cropBox.maxTop = min(containerHeight, canvas.top + canvas.height) - cropBox.height;
1165
+ } else {
1166
+ cropBox.minLeft = 0;
1167
+ cropBox.minTop = 0;
1168
+ cropBox.maxLeft = containerWidth - cropBox.width;
1169
+ cropBox.maxTop = containerHeight - cropBox.height;
1170
+ }
1171
+ }
161
1172
  },
162
1173
 
163
- setCropper: function() {
164
- var $container = this.$image.parent(),
165
- container = Cropper.fn.size($container),
166
- image = this.image,
167
- cropper;
1174
+ renderCropBox: function () {
1175
+ var options = this.options;
1176
+ var container = this.container;
1177
+ var containerWidth = container.width;
1178
+ var containerHeight = container.height;
1179
+ var cropBox = this.cropBox;
168
1180
 
169
- if (((image.naturalWidth * container.height / image.naturalHeight) - container.width) >= 0) {
170
- cropper = {
171
- height: container.width / image.aspectRatio,
172
- width: container.width,
173
- left: 0
174
- };
1181
+ if (cropBox.width > cropBox.maxWidth || cropBox.width < cropBox.minWidth) {
1182
+ cropBox.left = cropBox.oldLeft;
1183
+ }
175
1184
 
176
- cropper.top = (container.height - cropper.height) / 2;
177
- } else {
178
- cropper = {
179
- height: container.height,
180
- width: container.height * image.aspectRatio,
181
- top: 0
182
- };
1185
+ if (cropBox.height > cropBox.maxHeight || cropBox.height < cropBox.minHeight) {
1186
+ cropBox.top = cropBox.oldTop;
1187
+ }
1188
+
1189
+ cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth);
1190
+ cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight);
1191
+
1192
+ this.limitCropBox(false, true);
1193
+
1194
+ cropBox.oldLeft = cropBox.left = min(max(cropBox.left, cropBox.minLeft), cropBox.maxLeft);
1195
+ cropBox.oldTop = cropBox.top = min(max(cropBox.top, cropBox.minTop), cropBox.maxTop);
1196
+
1197
+ if (options.movable && options.cropBoxMovable) {
1198
+
1199
+ // Turn to move the canvas when the crop box is equal to the container
1200
+ this.$face.data(DATA_ACTION, (cropBox.width === containerWidth && cropBox.height === containerHeight) ? ACTION_MOVE : ACTION_ALL);
1201
+ }
1202
+
1203
+ this.$cropBox.css({
1204
+ width: cropBox.width,
1205
+ height: cropBox.height,
1206
+ left: cropBox.left,
1207
+ top: cropBox.top
1208
+ });
1209
+
1210
+ if (this.isCropped && this.isLimited) {
1211
+ this.limitCanvas(true, true);
1212
+ }
1213
+
1214
+ if (!this.isDisabled) {
1215
+ this.output();
1216
+ }
1217
+ },
1218
+
1219
+ output: function () {
1220
+ this.preview();
1221
+
1222
+ if (this.isCompleted) {
1223
+ this.trigger(EVENT_CROP, this.getData());
1224
+ } else if (!this.isBuilt) {
183
1225
 
184
- cropper.left = (container.width - cropper.width) / 2;
1226
+ // Only trigger one crop event before complete
1227
+ this.$element.one(EVENT_BUILT, $.proxy(function () {
1228
+ this.trigger(EVENT_CROP, this.getData());
1229
+ }, this));
185
1230
  }
1231
+ },
1232
+
1233
+ initPreview: function () {
1234
+ var crossOrigin = getCrossOrigin(this.crossOrigin);
1235
+ var url = crossOrigin ? this.crossOriginUrl : this.url;
1236
+
1237
+ this.$preview = $(this.options.preview);
1238
+ this.$viewBox.html('<img' + crossOrigin + ' src="' + url + '">');
1239
+ this.$preview.each(function () {
1240
+ var $this = $(this);
1241
+
1242
+ // Save the original size for recover
1243
+ $this.data(DATA_PREVIEW, {
1244
+ width: $this.width(),
1245
+ height: $this.height(),
1246
+ html: $this.html()
1247
+ });
1248
+
1249
+ /**
1250
+ * Override img element styles
1251
+ * Add `display:block` to avoid margin top issue
1252
+ * (Occur only when margin-top <= -height)
1253
+ */
1254
+ $this.html(
1255
+ '<img' + crossOrigin + ' src="' + url + '" style="' +
1256
+ 'display:block;width:100%;height:auto;' +
1257
+ 'min-width:0!important;min-height:0!important;' +
1258
+ 'max-width:none!important;max-height:none!important;' +
1259
+ 'image-orientation:0deg!important;">'
1260
+ );
1261
+ });
1262
+ },
186
1263
 
187
- $.each(cropper, function(i, n) {
188
- cropper[i] = Math.round(n);
1264
+ resetPreview: function () {
1265
+ this.$preview.each(function () {
1266
+ var $this = $(this);
1267
+ var data = $this.data(DATA_PREVIEW);
1268
+
1269
+ $this.css({
1270
+ width: data.width,
1271
+ height: data.height
1272
+ }).html(data.html).removeData(DATA_PREVIEW);
189
1273
  });
1274
+ },
190
1275
 
191
- image.height = cropper.height;
192
- image.width = cropper.width;
193
- image.ratio = image.width / image.naturalWidth;
1276
+ preview: function () {
1277
+ var image = this.image;
1278
+ var canvas = this.canvas;
1279
+ var cropBox = this.cropBox;
1280
+ var cropBoxWidth = cropBox.width;
1281
+ var cropBoxHeight = cropBox.height;
1282
+ var width = image.width;
1283
+ var height = image.height;
1284
+ var left = cropBox.left - canvas.left - image.left;
1285
+ var top = cropBox.top - canvas.top - image.top;
1286
+
1287
+ if (!this.isCropped || this.isDisabled) {
1288
+ return;
1289
+ }
194
1290
 
195
- Cropper.fn.position($container);
196
- this.$cropper.css({
197
- height: cropper.height,
198
- left: cropper.left,
199
- top: cropper.top,
200
- width: cropper.width
1291
+ this.$viewBox.find('img').css({
1292
+ width: width,
1293
+ height: height,
1294
+ marginLeft: -left,
1295
+ marginTop: -top,
1296
+ transform: getTransform(image)
201
1297
  });
202
1298
 
203
- this.cropper = cropper;
204
- this.setDragger();
1299
+ this.$preview.each(function () {
1300
+ var $this = $(this);
1301
+ var data = $this.data(DATA_PREVIEW);
1302
+ var originalWidth = data.width;
1303
+ var originalHeight = data.height;
1304
+ var newWidth = originalWidth;
1305
+ var newHeight = originalHeight;
1306
+ var ratio = 1;
1307
+
1308
+ if (cropBoxWidth) {
1309
+ ratio = originalWidth / cropBoxWidth;
1310
+ newHeight = cropBoxHeight * ratio;
1311
+ }
1312
+
1313
+ if (cropBoxHeight && newHeight > originalHeight) {
1314
+ ratio = originalHeight / cropBoxHeight;
1315
+ newWidth = cropBoxWidth * ratio;
1316
+ newHeight = originalHeight;
1317
+ }
1318
+
1319
+ $this.css({
1320
+ width: newWidth,
1321
+ height: newHeight
1322
+ }).find('img').css({
1323
+ width: width * ratio,
1324
+ height: height * ratio,
1325
+ marginLeft: -left * ratio,
1326
+ marginTop: -top * ratio,
1327
+ transform: getTransform(image)
1328
+ });
1329
+ });
205
1330
  },
206
1331
 
207
- setDragger: function() {
208
- var cropper = this.cropper,
209
- // If not set, use the original aspect ratio of the image.
210
- aspectRatio = this.defaults.aspectRatio || this.image.aspectRatio,
211
- dragger;
1332
+ bind: function () {
1333
+ var options = this.options;
1334
+ var $this = this.$element;
1335
+ var $cropper = this.$cropper;
212
1336
 
213
- if (((cropper.height * aspectRatio) - cropper.width) >= 0) {
214
- dragger = {
215
- height: cropper.width / aspectRatio,
216
- width: cropper.width,
217
- left: 0,
218
- top: (cropper.height - (cropper.width / aspectRatio)) / 2,
219
- maxWidth: cropper.width,
220
- maxHeight: cropper.width / aspectRatio
221
- };
1337
+ if ($.isFunction(options.cropstart)) {
1338
+ $this.on(EVENT_CROP_START, options.cropstart);
1339
+ }
1340
+
1341
+ if ($.isFunction(options.cropmove)) {
1342
+ $this.on(EVENT_CROP_MOVE, options.cropmove);
1343
+ }
1344
+
1345
+ if ($.isFunction(options.cropend)) {
1346
+ $this.on(EVENT_CROP_END, options.cropend);
1347
+ }
1348
+
1349
+ if ($.isFunction(options.crop)) {
1350
+ $this.on(EVENT_CROP, options.crop);
1351
+ }
1352
+
1353
+ if ($.isFunction(options.zoom)) {
1354
+ $this.on(EVENT_ZOOM, options.zoom);
1355
+ }
1356
+
1357
+ $cropper.on(EVENT_MOUSE_DOWN, $.proxy(this.cropStart, this));
1358
+
1359
+ if (options.zoomable && options.zoomOnWheel) {
1360
+ $cropper.on(EVENT_WHEEL, $.proxy(this.wheel, this));
1361
+ }
1362
+
1363
+ if (options.toggleDragModeOnDblclick) {
1364
+ $cropper.on(EVENT_DBLCLICK, $.proxy(this.dblclick, this));
1365
+ }
1366
+
1367
+ $document.
1368
+ on(EVENT_MOUSE_MOVE, (this._cropMove = proxy(this.cropMove, this))).
1369
+ on(EVENT_MOUSE_UP, (this._cropEnd = proxy(this.cropEnd, this)));
1370
+
1371
+ if (options.responsive) {
1372
+ $window.on(EVENT_RESIZE, (this._resize = proxy(this.resize, this)));
1373
+ }
1374
+ },
1375
+
1376
+ unbind: function () {
1377
+ var options = this.options;
1378
+ var $this = this.$element;
1379
+ var $cropper = this.$cropper;
1380
+
1381
+ if ($.isFunction(options.cropstart)) {
1382
+ $this.off(EVENT_CROP_START, options.cropstart);
1383
+ }
1384
+
1385
+ if ($.isFunction(options.cropmove)) {
1386
+ $this.off(EVENT_CROP_MOVE, options.cropmove);
1387
+ }
1388
+
1389
+ if ($.isFunction(options.cropend)) {
1390
+ $this.off(EVENT_CROP_END, options.cropend);
1391
+ }
1392
+
1393
+ if ($.isFunction(options.crop)) {
1394
+ $this.off(EVENT_CROP, options.crop);
1395
+ }
1396
+
1397
+ if ($.isFunction(options.zoom)) {
1398
+ $this.off(EVENT_ZOOM, options.zoom);
1399
+ }
1400
+
1401
+ $cropper.off(EVENT_MOUSE_DOWN, this.cropStart);
1402
+
1403
+ if (options.zoomable && options.zoomOnWheel) {
1404
+ $cropper.off(EVENT_WHEEL, this.wheel);
1405
+ }
1406
+
1407
+ if (options.toggleDragModeOnDblclick) {
1408
+ $cropper.off(EVENT_DBLCLICK, this.dblclick);
1409
+ }
1410
+
1411
+ $document.
1412
+ off(EVENT_MOUSE_MOVE, this._cropMove).
1413
+ off(EVENT_MOUSE_UP, this._cropEnd);
1414
+
1415
+ if (options.responsive) {
1416
+ $window.off(EVENT_RESIZE, this._resize);
1417
+ }
1418
+ },
1419
+
1420
+ resize: function () {
1421
+ var restore = this.options.restore;
1422
+ var $container = this.$container;
1423
+ var container = this.container;
1424
+ var canvasData;
1425
+ var cropBoxData;
1426
+ var ratio;
1427
+
1428
+ // Check `container` is necessary for IE8
1429
+ if (this.isDisabled || !container) {
1430
+ return;
1431
+ }
1432
+
1433
+ ratio = $container.width() / container.width;
1434
+
1435
+ // Resize when width changed or height changed
1436
+ if (ratio !== 1 || $container.height() !== container.height) {
1437
+ if (restore) {
1438
+ canvasData = this.getCanvasData();
1439
+ cropBoxData = this.getCropBoxData();
1440
+ }
1441
+
1442
+ this.render();
1443
+
1444
+ if (restore) {
1445
+ this.setCanvasData($.each(canvasData, function (i, n) {
1446
+ canvasData[i] = n * ratio;
1447
+ }));
1448
+ this.setCropBoxData($.each(cropBoxData, function (i, n) {
1449
+ cropBoxData[i] = n * ratio;
1450
+ }));
1451
+ }
1452
+ }
1453
+ },
1454
+
1455
+ dblclick: function () {
1456
+ if (this.isDisabled) {
1457
+ return;
1458
+ }
1459
+
1460
+ if (this.$dragBox.hasClass(CLASS_CROP)) {
1461
+ this.setDragMode(ACTION_MOVE);
222
1462
  } else {
223
- dragger = {
224
- height: cropper.height,
225
- width: cropper.height * aspectRatio,
226
- left: (cropper.width - (cropper.height * aspectRatio)) / 2,
227
- top: 0,
228
- maxHeight: cropper.height,
229
- maxWidth: cropper.height * aspectRatio
230
- };
1463
+ this.setDragMode(ACTION_CROP);
1464
+ }
1465
+ },
1466
+
1467
+ wheel: function (event) {
1468
+ var e = event.originalEvent || event;
1469
+ var ratio = num(this.options.wheelZoomRatio) || 0.1;
1470
+ var delta = 1;
1471
+
1472
+ if (this.isDisabled) {
1473
+ return;
1474
+ }
1475
+
1476
+ event.preventDefault();
1477
+
1478
+ // Limit wheel speed to prevent zoom too fast
1479
+ if (this.wheeling) {
1480
+ return;
1481
+ }
1482
+
1483
+ this.wheeling = true;
1484
+
1485
+ setTimeout($.proxy(function () {
1486
+ this.wheeling = false;
1487
+ }, this), 50);
1488
+
1489
+ if (e.deltaY) {
1490
+ delta = e.deltaY > 0 ? 1 : -1;
1491
+ } else if (e.wheelDelta) {
1492
+ delta = -e.wheelDelta / 120;
1493
+ } else if (e.detail) {
1494
+ delta = e.detail > 0 ? 1 : -1;
1495
+ }
1496
+
1497
+ this.zoom(-delta * ratio, event);
1498
+ },
1499
+
1500
+ cropStart: function (event) {
1501
+ var options = this.options;
1502
+ var originalEvent = event.originalEvent;
1503
+ var touches = originalEvent && originalEvent.touches;
1504
+ var e = event;
1505
+ var touchesLength;
1506
+ var action;
1507
+
1508
+ if (this.isDisabled) {
1509
+ return;
1510
+ }
1511
+
1512
+ if (touches) {
1513
+ touchesLength = touches.length;
1514
+
1515
+ if (touchesLength > 1) {
1516
+ if (options.zoomable && options.zoomOnTouch && touchesLength === 2) {
1517
+ e = touches[1];
1518
+ this.startX2 = e.pageX;
1519
+ this.startY2 = e.pageY;
1520
+ action = ACTION_ZOOM;
1521
+ } else {
1522
+ return;
1523
+ }
1524
+ }
1525
+
1526
+ e = touches[0];
1527
+ }
1528
+
1529
+ action = action || $(e.target).data(DATA_ACTION);
1530
+
1531
+ if (REGEXP_ACTIONS.test(action)) {
1532
+ if (this.trigger(EVENT_CROP_START, {
1533
+ originalEvent: originalEvent,
1534
+ action: action
1535
+ }).isDefaultPrevented()) {
1536
+ return;
1537
+ }
1538
+
1539
+ event.preventDefault();
1540
+
1541
+ this.action = action;
1542
+ this.cropping = false;
1543
+
1544
+ // IE8 has `event.pageX/Y`, but not `event.originalEvent.pageX/Y`
1545
+ // IE10 has `event.originalEvent.pageX/Y`, but not `event.pageX/Y`
1546
+ this.startX = e.pageX || originalEvent && originalEvent.pageX;
1547
+ this.startY = e.pageY || originalEvent && originalEvent.pageY;
1548
+
1549
+ if (action === ACTION_CROP) {
1550
+ this.cropping = true;
1551
+ this.$dragBox.addClass(CLASS_MODAL);
1552
+ }
1553
+ }
1554
+ },
1555
+
1556
+ cropMove: function (event) {
1557
+ var options = this.options;
1558
+ var originalEvent = event.originalEvent;
1559
+ var touches = originalEvent && originalEvent.touches;
1560
+ var e = event;
1561
+ var action = this.action;
1562
+ var touchesLength;
1563
+
1564
+ if (this.isDisabled) {
1565
+ return;
1566
+ }
1567
+
1568
+ if (touches) {
1569
+ touchesLength = touches.length;
1570
+
1571
+ if (touchesLength > 1) {
1572
+ if (options.zoomable && options.zoomOnTouch && touchesLength === 2) {
1573
+ e = touches[1];
1574
+ this.endX2 = e.pageX;
1575
+ this.endY2 = e.pageY;
1576
+ } else {
1577
+ return;
1578
+ }
1579
+ }
1580
+
1581
+ e = touches[0];
231
1582
  }
232
1583
 
233
- dragger.height *= 0.8;
234
- dragger.width *= 0.8;
1584
+ if (action) {
1585
+ if (this.trigger(EVENT_CROP_MOVE, {
1586
+ originalEvent: originalEvent,
1587
+ action: action
1588
+ }).isDefaultPrevented()) {
1589
+ return;
1590
+ }
235
1591
 
236
- dragger.left = (cropper.width - dragger.width) / 2;
237
- dragger.top = (cropper.height - dragger.height) / 2;
1592
+ event.preventDefault();
238
1593
 
239
- this.dragger = Cropper.fn.round(dragger);
240
- this.setData(this.defaults.data);
241
- this.$image.trigger("ready.cropper").off("ready.cropper");
242
- },
1594
+ this.endX = e.pageX || originalEvent && originalEvent.pageX;
1595
+ this.endY = e.pageY || originalEvent && originalEvent.pageY;
243
1596
 
244
- resetDragger: function() {
245
- var dragger = this.dragger,
246
- cropper = this.cropper;
1597
+ this.change(e.shiftKey, action === ACTION_ZOOM ? event : null);
1598
+ }
1599
+ },
247
1600
 
248
- dragger.width = dragger.width > dragger.maxWidth ? dragger.maxWidth : Math.abs(dragger.width);
249
- dragger.height = dragger.height > dragger.maxHeight ? dragger.maxHeight : Math.abs(dragger.height);
1601
+ cropEnd: function (event) {
1602
+ var originalEvent = event.originalEvent;
1603
+ var action = this.action;
250
1604
 
251
- dragger.maxLeft = cropper.width - dragger.width;
252
- dragger.maxTop = cropper.height - dragger.height;
1605
+ if (this.isDisabled) {
1606
+ return;
1607
+ }
253
1608
 
254
- dragger.left = dragger.left < 0 ? 0 : dragger.left > dragger.maxLeft ? dragger.maxLeft : dragger.left;
255
- dragger.top = dragger.top < 0 ? 0 : dragger.top > dragger.maxTop ? dragger.maxTop : dragger.top;
1609
+ if (action) {
1610
+ event.preventDefault();
256
1611
 
257
- dragger = Cropper.fn.round(dragger);
1612
+ if (this.cropping) {
1613
+ this.cropping = false;
1614
+ this.$dragBox.toggleClass(CLASS_MODAL, this.isCropped && this.options.modal);
1615
+ }
258
1616
 
259
- this.$dragger.css({
260
- height: dragger.height,
261
- left: dragger.left,
262
- top: dragger.top,
263
- width: dragger.width
264
- });
1617
+ this.action = '';
265
1618
 
266
- this.dragger = dragger;
267
- this.preview();
268
- this.output();
1619
+ this.trigger(EVENT_CROP_END, {
1620
+ originalEvent: originalEvent,
1621
+ action: action
1622
+ });
1623
+ }
269
1624
  },
270
1625
 
271
- dragging: function() {
272
- var direction = this.direction,
273
- dragger = this.dragger,
274
- aspectRatio = this.defaults.aspectRatio,
275
- range = {
276
- x: this.endX - this.startX,
277
- y: this.endY - this.startY
278
- };
1626
+ change: function (shiftKey, event) {
1627
+ var options = this.options;
1628
+ var aspectRatio = options.aspectRatio;
1629
+ var action = this.action;
1630
+ var container = this.container;
1631
+ var canvas = this.canvas;
1632
+ var cropBox = this.cropBox;
1633
+ var width = cropBox.width;
1634
+ var height = cropBox.height;
1635
+ var left = cropBox.left;
1636
+ var top = cropBox.top;
1637
+ var right = left + width;
1638
+ var bottom = top + height;
1639
+ var minLeft = 0;
1640
+ var minTop = 0;
1641
+ var maxWidth = container.width;
1642
+ var maxHeight = container.height;
1643
+ var renderable = true;
1644
+ var offset;
1645
+ var range;
1646
+
1647
+ // Locking aspect ratio in "free mode" by holding shift key (#259)
1648
+ if (!aspectRatio && shiftKey) {
1649
+ aspectRatio = width && height ? width / height : 1;
1650
+ }
1651
+
1652
+ if (this.limited) {
1653
+ minLeft = cropBox.minLeft;
1654
+ minTop = cropBox.minTop;
1655
+ maxWidth = minLeft + min(container.width, canvas.width);
1656
+ maxHeight = minTop + min(container.height, canvas.height);
1657
+ }
1658
+
1659
+ range = {
1660
+ x: this.endX - this.startX,
1661
+ y: this.endY - this.startY
1662
+ };
279
1663
 
280
1664
  if (aspectRatio) {
281
1665
  range.X = range.y * aspectRatio;
282
1666
  range.Y = range.x / aspectRatio;
283
1667
  }
284
1668
 
285
- switch (direction) {
1669
+ switch (action) {
1670
+ // Move crop box
1671
+ case ACTION_ALL:
1672
+ left += range.x;
1673
+ top += range.y;
1674
+ break;
1675
+
1676
+ // Resize crop box
1677
+ case ACTION_EAST:
1678
+ if (range.x >= 0 && (right >= maxWidth || aspectRatio &&
1679
+ (top <= minTop || bottom >= maxHeight))) {
286
1680
 
287
- // dragging
288
- case "e":
289
- dragger.width += range.x;
1681
+ renderable = false;
1682
+ break;
1683
+ }
1684
+
1685
+ width += range.x;
290
1686
 
291
1687
  if (aspectRatio) {
292
- dragger.height = dragger.width / aspectRatio;
293
- dragger.top -= range.Y / 2;
1688
+ height = width / aspectRatio;
1689
+ top -= range.Y / 2;
294
1690
  }
295
1691
 
296
- if (dragger.width < 0) {
297
- this.direction = "w";
298
- dragger.width = 0;
1692
+ if (width < 0) {
1693
+ action = ACTION_WEST;
1694
+ width = 0;
299
1695
  }
300
1696
 
301
1697
  break;
302
1698
 
303
- case "n":
304
- dragger.height -= range.y;
305
- dragger.top += range.y;
1699
+ case ACTION_NORTH:
1700
+ if (range.y <= 0 && (top <= minTop || aspectRatio &&
1701
+ (left <= minLeft || right >= maxWidth))) {
1702
+
1703
+ renderable = false;
1704
+ break;
1705
+ }
1706
+
1707
+ height -= range.y;
1708
+ top += range.y;
306
1709
 
307
1710
  if (aspectRatio) {
308
- dragger.width = dragger.height * aspectRatio;
309
- dragger.left += range.X / 2;
1711
+ width = height * aspectRatio;
1712
+ left += range.X / 2;
310
1713
  }
311
1714
 
312
- if (dragger.height < 0) {
313
- this.direction = "s";
314
- dragger.height = 0;
1715
+ if (height < 0) {
1716
+ action = ACTION_SOUTH;
1717
+ height = 0;
315
1718
  }
316
1719
 
317
1720
  break;
318
1721
 
319
- case "w":
320
- dragger.width -= range.x;
321
- dragger.left += range.x;
1722
+ case ACTION_WEST:
1723
+ if (range.x <= 0 && (left <= minLeft || aspectRatio &&
1724
+ (top <= minTop || bottom >= maxHeight))) {
1725
+
1726
+ renderable = false;
1727
+ break;
1728
+ }
1729
+
1730
+ width -= range.x;
1731
+ left += range.x;
322
1732
 
323
1733
  if (aspectRatio) {
324
- dragger.height = dragger.width / aspectRatio;
325
- dragger.top += range.Y / 2;
1734
+ height = width / aspectRatio;
1735
+ top += range.Y / 2;
326
1736
  }
327
1737
 
328
- if (dragger.width < 0) {
329
- this.direction = "e";
330
- dragger.width = 0;
1738
+ if (width < 0) {
1739
+ action = ACTION_EAST;
1740
+ width = 0;
331
1741
  }
332
1742
 
333
1743
  break;
334
1744
 
335
- case "s":
336
- dragger.height += range.y;
1745
+ case ACTION_SOUTH:
1746
+ if (range.y >= 0 && (bottom >= maxHeight || aspectRatio &&
1747
+ (left <= minLeft || right >= maxWidth))) {
1748
+
1749
+ renderable = false;
1750
+ break;
1751
+ }
1752
+
1753
+ height += range.y;
337
1754
 
338
1755
  if (aspectRatio) {
339
- dragger.width = dragger.height * aspectRatio;
340
- dragger.left -= range.X / 2;
1756
+ width = height * aspectRatio;
1757
+ left -= range.X / 2;
341
1758
  }
342
1759
 
343
- if (dragger.height < 0) {
344
- this.direction = "n";
345
- dragger.height = 0;
1760
+ if (height < 0) {
1761
+ action = ACTION_NORTH;
1762
+ height = 0;
346
1763
  }
347
1764
 
348
1765
  break;
349
1766
 
350
- case "ne":
351
- dragger.height -= range.y;
352
- dragger.top += range.y;
353
-
1767
+ case ACTION_NORTH_EAST:
354
1768
  if (aspectRatio) {
355
- dragger.width = dragger.height * aspectRatio;
1769
+ if (range.y <= 0 && (top <= minTop || right >= maxWidth)) {
1770
+ renderable = false;
1771
+ break;
1772
+ }
1773
+
1774
+ height -= range.y;
1775
+ top += range.y;
1776
+ width = height * aspectRatio;
356
1777
  } else {
357
- dragger.width += range.x;
1778
+ if (range.x >= 0) {
1779
+ if (right < maxWidth) {
1780
+ width += range.x;
1781
+ } else if (range.y <= 0 && top <= minTop) {
1782
+ renderable = false;
1783
+ }
1784
+ } else {
1785
+ width += range.x;
1786
+ }
1787
+
1788
+ if (range.y <= 0) {
1789
+ if (top > minTop) {
1790
+ height -= range.y;
1791
+ top += range.y;
1792
+ }
1793
+ } else {
1794
+ height -= range.y;
1795
+ top += range.y;
1796
+ }
358
1797
  }
359
1798
 
360
- if (dragger.height < 0) {
361
- this.direction = "sw";
362
- dragger.height = 0;
363
- dragger.width = 0;
1799
+ if (width < 0 && height < 0) {
1800
+ action = ACTION_SOUTH_WEST;
1801
+ height = 0;
1802
+ width = 0;
1803
+ } else if (width < 0) {
1804
+ action = ACTION_NORTH_WEST;
1805
+ width = 0;
1806
+ } else if (height < 0) {
1807
+ action = ACTION_SOUTH_EAST;
1808
+ height = 0;
364
1809
  }
365
1810
 
366
1811
  break;
367
1812
 
368
- case "nw":
369
- dragger.height -= range.y;
370
- dragger.top += range.y;
371
-
1813
+ case ACTION_NORTH_WEST:
372
1814
  if (aspectRatio) {
373
- dragger.width = dragger.height * aspectRatio;
374
- dragger.left += range.X;
1815
+ if (range.y <= 0 && (top <= minTop || left <= minLeft)) {
1816
+ renderable = false;
1817
+ break;
1818
+ }
1819
+
1820
+ height -= range.y;
1821
+ top += range.y;
1822
+ width = height * aspectRatio;
1823
+ left += range.X;
375
1824
  } else {
376
- dragger.width -= range.x;
377
- dragger.left += range.x;
1825
+ if (range.x <= 0) {
1826
+ if (left > minLeft) {
1827
+ width -= range.x;
1828
+ left += range.x;
1829
+ } else if (range.y <= 0 && top <= minTop) {
1830
+ renderable = false;
1831
+ }
1832
+ } else {
1833
+ width -= range.x;
1834
+ left += range.x;
1835
+ }
1836
+
1837
+ if (range.y <= 0) {
1838
+ if (top > minTop) {
1839
+ height -= range.y;
1840
+ top += range.y;
1841
+ }
1842
+ } else {
1843
+ height -= range.y;
1844
+ top += range.y;
1845
+ }
378
1846
  }
379
1847
 
380
- if (dragger.height < 0) {
381
- this.direction = "se";
382
- dragger.height = 0;
383
- dragger.width = 0;
1848
+ if (width < 0 && height < 0) {
1849
+ action = ACTION_SOUTH_EAST;
1850
+ height = 0;
1851
+ width = 0;
1852
+ } else if (width < 0) {
1853
+ action = ACTION_NORTH_EAST;
1854
+ width = 0;
1855
+ } else if (height < 0) {
1856
+ action = ACTION_SOUTH_WEST;
1857
+ height = 0;
384
1858
  }
385
1859
 
386
1860
  break;
387
1861
 
388
- case "sw":
389
- dragger.width -= range.x;
390
- dragger.left += range.x;
391
-
1862
+ case ACTION_SOUTH_WEST:
392
1863
  if (aspectRatio) {
393
- dragger.height = dragger.width / aspectRatio;
1864
+ if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) {
1865
+ renderable = false;
1866
+ break;
1867
+ }
1868
+
1869
+ width -= range.x;
1870
+ left += range.x;
1871
+ height = width / aspectRatio;
394
1872
  } else {
395
- dragger.height += range.y;
1873
+ if (range.x <= 0) {
1874
+ if (left > minLeft) {
1875
+ width -= range.x;
1876
+ left += range.x;
1877
+ } else if (range.y >= 0 && bottom >= maxHeight) {
1878
+ renderable = false;
1879
+ }
1880
+ } else {
1881
+ width -= range.x;
1882
+ left += range.x;
1883
+ }
1884
+
1885
+ if (range.y >= 0) {
1886
+ if (bottom < maxHeight) {
1887
+ height += range.y;
1888
+ }
1889
+ } else {
1890
+ height += range.y;
1891
+ }
396
1892
  }
397
1893
 
398
- if (dragger.width < 0) {
399
- this.direction = "ne";
400
- dragger.height = 0;
401
- dragger.width = 0;
1894
+ if (width < 0 && height < 0) {
1895
+ action = ACTION_NORTH_EAST;
1896
+ height = 0;
1897
+ width = 0;
1898
+ } else if (width < 0) {
1899
+ action = ACTION_SOUTH_EAST;
1900
+ width = 0;
1901
+ } else if (height < 0) {
1902
+ action = ACTION_NORTH_WEST;
1903
+ height = 0;
402
1904
  }
403
1905
 
404
1906
  break;
405
1907
 
406
- case "se":
407
- dragger.width += range.x;
408
-
1908
+ case ACTION_SOUTH_EAST:
409
1909
  if (aspectRatio) {
410
- dragger.height = dragger.width / aspectRatio;
1910
+ if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) {
1911
+ renderable = false;
1912
+ break;
1913
+ }
1914
+
1915
+ width += range.x;
1916
+ height = width / aspectRatio;
411
1917
  } else {
412
- dragger.height += range.y;
1918
+ if (range.x >= 0) {
1919
+ if (right < maxWidth) {
1920
+ width += range.x;
1921
+ } else if (range.y >= 0 && bottom >= maxHeight) {
1922
+ renderable = false;
1923
+ }
1924
+ } else {
1925
+ width += range.x;
1926
+ }
1927
+
1928
+ if (range.y >= 0) {
1929
+ if (bottom < maxHeight) {
1930
+ height += range.y;
1931
+ }
1932
+ } else {
1933
+ height += range.y;
1934
+ }
1935
+ }
1936
+
1937
+ if (width < 0 && height < 0) {
1938
+ action = ACTION_NORTH_WEST;
1939
+ height = 0;
1940
+ width = 0;
1941
+ } else if (width < 0) {
1942
+ action = ACTION_SOUTH_WEST;
1943
+ width = 0;
1944
+ } else if (height < 0) {
1945
+ action = ACTION_NORTH_EAST;
1946
+ height = 0;
1947
+ }
1948
+
1949
+ break;
1950
+
1951
+ // Move canvas
1952
+ case ACTION_MOVE:
1953
+ this.move(range.x, range.y);
1954
+ renderable = false;
1955
+ break;
1956
+
1957
+ // Zoom canvas
1958
+ case ACTION_ZOOM:
1959
+ this.zoom((function (x1, y1, x2, y2) {
1960
+ var z1 = sqrt(x1 * x1 + y1 * y1);
1961
+ var z2 = sqrt(x2 * x2 + y2 * y2);
1962
+
1963
+ return (z2 - z1) / z1;
1964
+ })(
1965
+ abs(this.startX - this.startX2),
1966
+ abs(this.startY - this.startY2),
1967
+ abs(this.endX - this.endX2),
1968
+ abs(this.endY - this.endY2)
1969
+ ), event);
1970
+ this.startX2 = this.endX2;
1971
+ this.startY2 = this.endY2;
1972
+ renderable = false;
1973
+ break;
1974
+
1975
+ // Create crop box
1976
+ case ACTION_CROP:
1977
+ if (!range.x || !range.y) {
1978
+ renderable = false;
1979
+ break;
1980
+ }
1981
+
1982
+ offset = this.$cropper.offset();
1983
+ left = this.startX - offset.left;
1984
+ top = this.startY - offset.top;
1985
+ width = cropBox.minWidth;
1986
+ height = cropBox.minHeight;
1987
+
1988
+ if (range.x > 0) {
1989
+ action = range.y > 0 ? ACTION_SOUTH_EAST : ACTION_NORTH_EAST;
1990
+ } else if (range.x < 0) {
1991
+ left -= width;
1992
+ action = range.y > 0 ? ACTION_SOUTH_WEST : ACTION_NORTH_WEST;
413
1993
  }
414
1994
 
415
- if (dragger.width < 0) {
416
- this.direction = "nw";
417
- dragger.height = 0;
418
- dragger.width = 0;
1995
+ if (range.y < 0) {
1996
+ top -= height;
1997
+ }
1998
+
1999
+ // Show the crop box if is hidden
2000
+ if (!this.isCropped) {
2001
+ this.$cropBox.removeClass(CLASS_HIDDEN);
2002
+ this.isCropped = true;
2003
+
2004
+ if (this.limited) {
2005
+ this.limitCropBox(true, true);
2006
+ }
419
2007
  }
420
2008
 
421
2009
  break;
422
2010
 
423
- // moving
424
- default:
425
- dragger.left += range.x;
426
- dragger.top += range.y;
2011
+ // No default
2012
+ }
2013
+
2014
+ if (renderable) {
2015
+ cropBox.width = width;
2016
+ cropBox.height = height;
2017
+ cropBox.left = left;
2018
+ cropBox.top = top;
2019
+ this.action = action;
2020
+
2021
+ this.renderCropBox();
427
2022
  }
428
2023
 
429
- this.resetDragger();
2024
+ // Override
430
2025
  this.startX = this.endX;
431
2026
  this.startY = this.endY;
432
2027
  },
433
2028
 
434
- output: function() {
435
- this.defaults.done(this.getData());
2029
+ // Show the crop box manually
2030
+ crop: function () {
2031
+ if (!this.isBuilt || this.isDisabled) {
2032
+ return;
2033
+ }
2034
+
2035
+ if (!this.isCropped) {
2036
+ this.isCropped = true;
2037
+ this.limitCropBox(true, true);
2038
+
2039
+ if (this.options.modal) {
2040
+ this.$dragBox.addClass(CLASS_MODAL);
2041
+ }
2042
+
2043
+ this.$cropBox.removeClass(CLASS_HIDDEN);
2044
+ }
2045
+
2046
+ this.setCropBoxData(this.initialCropBox);
436
2047
  },
437
2048
 
438
- preview: function() {
439
- var that = this,
440
- cropper = that.cropper,
441
- dragger = that.dragger;
2049
+ // Reset the image and crop box to their initial states
2050
+ reset: function () {
2051
+ if (!this.isBuilt || this.isDisabled) {
2052
+ return;
2053
+ }
2054
+
2055
+ this.image = $.extend({}, this.initialImage);
2056
+ this.canvas = $.extend({}, this.initialCanvas);
2057
+ this.cropBox = $.extend({}, this.initialCropBox);
442
2058
 
443
- this.$preview.each(function() {
444
- var $this = $(this),
445
- ratio = $this.width() / dragger.width,
446
- styles = {
447
- height: cropper.height,
448
- marginLeft: - dragger.left,
449
- marginTop: - dragger.top,
450
- width: cropper.width
451
- };
2059
+ this.renderCanvas();
2060
+
2061
+ if (this.isCropped) {
2062
+ this.renderCropBox();
2063
+ }
2064
+ },
2065
+
2066
+ // Clear the crop box
2067
+ clear: function () {
2068
+ if (!this.isCropped || this.isDisabled) {
2069
+ return;
2070
+ }
452
2071
 
453
- $this.css({overflow: "hidden"});
454
- $this.find("img").css(Cropper.fn.round(styles, function(n) {
455
- return n * ratio;
456
- }));
2072
+ $.extend(this.cropBox, {
2073
+ left: 0,
2074
+ top: 0,
2075
+ width: 0,
2076
+ height: 0
457
2077
  });
2078
+
2079
+ this.isCropped = false;
2080
+ this.renderCropBox();
2081
+
2082
+ this.limitCanvas(true, true);
2083
+
2084
+ // Render canvas after crop box rendered
2085
+ this.renderCanvas();
2086
+
2087
+ this.$dragBox.removeClass(CLASS_MODAL);
2088
+ this.$cropBox.addClass(CLASS_HIDDEN);
458
2089
  },
459
2090
 
460
- // Public methods
2091
+ /**
2092
+ * Replace the image's src and rebuild the cropper
2093
+ *
2094
+ * @param {String} url
2095
+ */
2096
+ replace: function (url) {
2097
+ if (!this.isDisabled && url) {
2098
+ if (this.isImg) {
2099
+ this.isReplaced = true;
2100
+ this.$element.attr('src', url);
2101
+ }
2102
+
2103
+ // Clear previous data
2104
+ this.options.data = null;
2105
+ this.load(url);
2106
+ }
2107
+ },
461
2108
 
462
- enable: function(callback) {
463
- this.render(callback);
2109
+ // Enable (unfreeze) the cropper
2110
+ enable: function () {
2111
+ if (this.isBuilt) {
2112
+ this.isDisabled = false;
2113
+ this.$cropper.removeClass(CLASS_DISABLED);
2114
+ }
464
2115
  },
465
2116
 
466
- disable: function() {
467
- this.unrender();
2117
+ // Disable (freeze) the cropper
2118
+ disable: function () {
2119
+ if (this.isBuilt) {
2120
+ this.isDisabled = true;
2121
+ this.$cropper.addClass(CLASS_DISABLED);
2122
+ }
468
2123
  },
469
2124
 
470
- setAspectRatio: function(aspectRatio) {
471
- if (aspectRatio === "auto" || ($.isNumeric(aspectRatio) && aspectRatio > 0)) {
472
- this.defaults.aspectRatio = aspectRatio === "auto" ? NaN : aspectRatio;
2125
+ // Destroy the cropper and remove the instance from the image
2126
+ destroy: function () {
2127
+ var $this = this.$element;
2128
+
2129
+ if (this.isLoaded) {
2130
+ if (this.isImg && this.isReplaced) {
2131
+ $this.attr('src', this.originalUrl);
2132
+ }
473
2133
 
474
- if (this.active) {
475
- this.setDragger();
2134
+ this.unbuild();
2135
+ $this.removeClass(CLASS_HIDDEN);
2136
+ } else {
2137
+ if (this.isImg) {
2138
+ $this.off(EVENT_LOAD, this.start);
2139
+ } else if (this.$clone) {
2140
+ this.$clone.remove();
476
2141
  }
477
2142
  }
2143
+
2144
+ $this.removeData(NAMESPACE);
478
2145
  },
479
2146
 
480
- setData: function(data) {
481
- var cropper = this.cropper,
482
- dragger = this.dragger,
483
- aspectRatio = this.defaults.aspectRatio,
484
- isNumber = function(n) {
485
- return typeof n === "number";
486
- };
2147
+ /**
2148
+ * Move the canvas with relative offsets
2149
+ *
2150
+ * @param {Number} offsetX
2151
+ * @param {Number} offsetY (optional)
2152
+ */
2153
+ move: function (offsetX, offsetY) {
2154
+ var canvas = this.canvas;
2155
+
2156
+ this.moveTo(
2157
+ isUndefined(offsetX) ? offsetX : canvas.left + num(offsetX),
2158
+ isUndefined(offsetY) ? offsetY : canvas.top + num(offsetY)
2159
+ );
2160
+ },
487
2161
 
488
- if (!this.active) {
489
- return;
2162
+ /**
2163
+ * Move the canvas to an absolute point
2164
+ *
2165
+ * @param {Number} x
2166
+ * @param {Number} y (optional)
2167
+ */
2168
+ moveTo: function (x, y) {
2169
+ var canvas = this.canvas;
2170
+ var isChanged = false;
2171
+
2172
+ // If "y" is not present, its default value is "x"
2173
+ if (isUndefined(y)) {
2174
+ y = x;
490
2175
  }
491
2176
 
492
- if ($.isPlainObject(data) && !$.isEmptyObject(data)) {
493
- data = Cropper.fn.transformData(data, this.image.ratio);
2177
+ x = num(x);
2178
+ y = num(y);
494
2179
 
495
- if (isNumber(data.x1) && data.x1 <= cropper.width) {
496
- dragger.left = data.x1;
2180
+ if (this.isBuilt && !this.isDisabled && this.options.movable) {
2181
+ if (isNumber(x)) {
2182
+ canvas.left = x;
2183
+ isChanged = true;
497
2184
  }
498
2185
 
499
- if (isNumber(data.y1) && data.y1 <= cropper.height) {
500
- dragger.top = data.y1;
2186
+ if (isNumber(y)) {
2187
+ canvas.top = y;
2188
+ isChanged = true;
501
2189
  }
502
2190
 
503
- if (aspectRatio){
504
- if (isNumber(data.width) && data.width <= cropper.width) {
505
- dragger.width = data.width;
506
- dragger.height = dragger.width / aspectRatio;
507
- } else if (isNumber(data.height) && data.height <= cropper.height) {
508
- dragger.height = data.height;
509
- dragger.width = dragger.height * aspectRatio;
510
- } else if (isNumber(data.x2) && data.x2 <= cropper.width) {
511
- dragger.width = data.x2 - dragger.left;
512
- dragger.height = dragger.width / aspectRatio;
513
- } else if (isNumber(data.y2) && data.y2 <= cropper.height) {
514
- dragger.height = data.y2 - dragger.top;
515
- dragger.width = dragger.height * aspectRatio;
516
- }
517
- } else {
518
- if (isNumber(data.width) && data.width <= cropper.width) {
519
- dragger.width = data.width;
520
- } else if (isNumber(data.x2) && data.x2 <= cropper.width) {
521
- dragger.width = data.x2 - dragger.left;
522
- }
523
-
524
- if (isNumber(data.height) && data.height <= cropper.height) {
525
- dragger.height = data.height;
526
- } else if (isNumber(data.y2) && data.height <= cropper.height) {
527
- dragger.height = data.y2 - dragger.top;
528
- }
2191
+ if (isChanged) {
2192
+ this.renderCanvas(true);
529
2193
  }
530
2194
  }
531
-
532
- this.dragger = dragger;
533
- this.resetDragger();
534
2195
  },
535
2196
 
536
- getData: function() {
537
- var dragger = this.dragger,
538
- data = {};
2197
+ /**
2198
+ * Zoom the canvas with a relative ratio
2199
+ *
2200
+ * @param {Number} ratio
2201
+ * @param {jQuery Event} _event (private)
2202
+ */
2203
+ zoom: function (ratio, _event) {
2204
+ var canvas = this.canvas;
539
2205
 
540
- if (this.active) {
541
- data = {
542
- x1: dragger.left,
543
- y1: dragger.top,
544
- width: dragger.width,
545
- height: dragger.height,
546
- x2: dragger.left + dragger.width,
547
- y2: dragger.top + dragger.height
548
- };
2206
+ ratio = num(ratio);
549
2207
 
550
- data = Cropper.fn.transformData(data, (1 / this.image.ratio));
2208
+ if (ratio < 0) {
2209
+ ratio = 1 / (1 - ratio);
2210
+ } else {
2211
+ ratio = 1 + ratio;
551
2212
  }
552
2213
 
553
- return data;
2214
+ this.zoomTo(canvas.width * ratio / canvas.naturalWidth, _event);
554
2215
  },
555
2216
 
556
- setImgSrc: function(src) {
557
- if (typeof src === "string" && src.length > 0 && src !== this.src) {
558
- this.$image.attr("src", src);
559
- this.rerender();
2217
+ /**
2218
+ * Zoom the canvas to an absolute ratio
2219
+ *
2220
+ * @param {Number} ratio
2221
+ * @param {jQuery Event} _event (private)
2222
+ */
2223
+ zoomTo: function (ratio, _event) {
2224
+ var options = this.options;
2225
+ var canvas = this.canvas;
2226
+ var width = canvas.width;
2227
+ var height = canvas.height;
2228
+ var naturalWidth = canvas.naturalWidth;
2229
+ var naturalHeight = canvas.naturalHeight;
2230
+ var originalEvent;
2231
+ var newWidth;
2232
+ var newHeight;
2233
+ var offset;
2234
+ var center;
2235
+
2236
+ ratio = num(ratio);
2237
+
2238
+ if (ratio >= 0 && this.isBuilt && !this.isDisabled && options.zoomable) {
2239
+ newWidth = naturalWidth * ratio;
2240
+ newHeight = naturalHeight * ratio;
2241
+
2242
+ if (_event) {
2243
+ originalEvent = _event.originalEvent;
2244
+ }
2245
+
2246
+ if (this.trigger(EVENT_ZOOM, {
2247
+ originalEvent: originalEvent,
2248
+ oldRatio: width / naturalWidth,
2249
+ ratio: newWidth / naturalWidth
2250
+ }).isDefaultPrevented()) {
2251
+ return;
2252
+ }
2253
+
2254
+ if (originalEvent) {
2255
+ offset = this.$cropper.offset();
2256
+ center = originalEvent.touches ? getTouchesCenter(originalEvent.touches) : {
2257
+ pageX: _event.pageX || originalEvent.pageX || 0,
2258
+ pageY: _event.pageY || originalEvent.pageY || 0
2259
+ };
2260
+
2261
+ // Zoom from the triggering point of the event
2262
+ canvas.left -= (newWidth - width) * (
2263
+ ((center.pageX - offset.left) - canvas.left) / width
2264
+ );
2265
+ canvas.top -= (newHeight - height) * (
2266
+ ((center.pageY - offset.top) - canvas.top) / height
2267
+ );
2268
+ } else {
2269
+
2270
+ // Zoom from the center of the canvas
2271
+ canvas.left -= (newWidth - width) / 2;
2272
+ canvas.top -= (newHeight - height) / 2;
2273
+ }
2274
+
2275
+ canvas.width = newWidth;
2276
+ canvas.height = newHeight;
2277
+ this.renderCanvas(true);
560
2278
  }
561
2279
  },
562
2280
 
563
- getImgInfo: function() {
564
- return this.image || {};
2281
+ /**
2282
+ * Rotate the canvas with a relative degree
2283
+ *
2284
+ * @param {Number} degree
2285
+ */
2286
+ rotate: function (degree) {
2287
+ this.rotateTo((this.image.rotate || 0) + num(degree));
565
2288
  },
566
2289
 
567
- // Public events
568
-
569
- dragstart: function(event) {
570
- var touches = Cropper.fn.getOriginalEvent(event).touches,
571
- e = event,
572
- touching,
573
- direction;
2290
+ /**
2291
+ * Rotate the canvas to an absolute degree
2292
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#rotate()
2293
+ *
2294
+ * @param {Number} degree
2295
+ */
2296
+ rotateTo: function (degree) {
2297
+ degree = num(degree);
2298
+
2299
+ if (isNumber(degree) && this.isBuilt && !this.isDisabled && this.options.rotatable) {
2300
+ this.image.rotate = degree % 360;
2301
+ this.isRotated = true;
2302
+ this.renderCanvas(true);
2303
+ }
2304
+ },
574
2305
 
575
- if (touches && touches.length === 1) {
576
- e = touches[0];
577
- this.touchId = e.identifier;
578
- touching = true;
2306
+ /**
2307
+ * Scale the image
2308
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#scale()
2309
+ *
2310
+ * @param {Number} scaleX
2311
+ * @param {Number} scaleY (optional)
2312
+ */
2313
+ scale: function (scaleX, scaleY) {
2314
+ var image = this.image;
2315
+ var isChanged = false;
2316
+
2317
+ // If "scaleY" is not present, its default value is "scaleX"
2318
+ if (isUndefined(scaleY)) {
2319
+ scaleY = scaleX;
579
2320
  }
580
2321
 
581
- direction = $(e.target).data().direction;
2322
+ scaleX = num(scaleX);
2323
+ scaleY = num(scaleY);
2324
+
2325
+ if (this.isBuilt && !this.isDisabled && this.options.scalable) {
2326
+ if (isNumber(scaleX)) {
2327
+ image.scaleX = scaleX;
2328
+ isChanged = true;
2329
+ }
2330
+
2331
+ if (isNumber(scaleY)) {
2332
+ image.scaleY = scaleY;
2333
+ isChanged = true;
2334
+ }
582
2335
 
583
- if (Cropper.fn.isDirection(direction)) {
584
- this.startX = e.pageX;
585
- this.startY = e.pageY;
586
- this.direction = direction;
587
- this.$image.trigger("dragstart");
588
- touching && event.preventDefault();
2336
+ if (isChanged) {
2337
+ this.renderImage(true);
2338
+ }
589
2339
  }
590
2340
  },
591
2341
 
592
- dragmove: function(event) {
593
- var touches = Cropper.fn.getOriginalEvent(event).changedTouches,
594
- e = event,
595
- touching;
2342
+ /**
2343
+ * Scale the abscissa of the image
2344
+ *
2345
+ * @param {Number} scaleX
2346
+ */
2347
+ scaleX: function (scaleX) {
2348
+ var scaleY = this.image.scaleY;
596
2349
 
597
- if (touches && touches.length === 1) {
598
- e = touches[0];
599
- touching = true;
2350
+ this.scale(scaleX, isNumber(scaleY) ? scaleY : 1);
2351
+ },
600
2352
 
601
- if (e.identifier !== this.touchId) {
602
- return;
603
- }
2353
+ /**
2354
+ * Scale the ordinate of the image
2355
+ *
2356
+ * @param {Number} scaleY
2357
+ */
2358
+ scaleY: function (scaleY) {
2359
+ var scaleX = this.image.scaleX;
2360
+
2361
+ this.scale(isNumber(scaleX) ? scaleX : 1, scaleY);
2362
+ },
2363
+
2364
+ /**
2365
+ * Get the cropped area position and size data (base on the original image)
2366
+ *
2367
+ * @param {Boolean} isRounded (optional)
2368
+ * @return {Object} data
2369
+ */
2370
+ getData: function (isRounded) {
2371
+ var options = this.options;
2372
+ var image = this.image;
2373
+ var canvas = this.canvas;
2374
+ var cropBox = this.cropBox;
2375
+ var ratio;
2376
+ var data;
2377
+
2378
+ if (this.isBuilt && this.isCropped) {
2379
+ data = {
2380
+ x: cropBox.left - canvas.left,
2381
+ y: cropBox.top - canvas.top,
2382
+ width: cropBox.width,
2383
+ height: cropBox.height
2384
+ };
2385
+
2386
+ ratio = image.width / image.naturalWidth;
2387
+
2388
+ $.each(data, function (i, n) {
2389
+ n = n / ratio;
2390
+ data[i] = isRounded ? round(n) : n;
2391
+ });
2392
+
2393
+ } else {
2394
+ data = {
2395
+ x: 0,
2396
+ y: 0,
2397
+ width: 0,
2398
+ height: 0
2399
+ };
2400
+ }
2401
+
2402
+ if (options.rotatable) {
2403
+ data.rotate = image.rotate || 0;
604
2404
  }
605
2405
 
606
- if (this.direction) {
607
- this.$image.trigger("dragmove");
608
- touching && event.preventDefault();
609
- this.endX = e.pageX;
610
- this.endY = e.pageY;
611
- this.dragging();
2406
+ if (options.scalable) {
2407
+ data.scaleX = image.scaleX || 1;
2408
+ data.scaleY = image.scaleY || 1;
612
2409
  }
2410
+
2411
+ return data;
613
2412
  },
614
2413
 
615
- dragend: function(event) {
616
- var touches = Cropper.fn.getOriginalEvent(event).changedTouches,
617
- e = event,
618
- touching;
2414
+ /**
2415
+ * Set the cropped area position and size with new data
2416
+ *
2417
+ * @param {Object} data
2418
+ */
2419
+ setData: function (data) {
2420
+ var options = this.options;
2421
+ var image = this.image;
2422
+ var canvas = this.canvas;
2423
+ var cropBoxData = {};
2424
+ var isRotated;
2425
+ var isScaled;
2426
+ var ratio;
2427
+
2428
+ if ($.isFunction(data)) {
2429
+ data = data.call(this.element);
2430
+ }
619
2431
 
620
- if (touches && touches.length === 1) {
621
- e = touches[0];
622
- touching = true;
2432
+ if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) {
2433
+ if (options.rotatable) {
2434
+ if (isNumber(data.rotate) && data.rotate !== image.rotate) {
2435
+ image.rotate = data.rotate;
2436
+ this.isRotated = isRotated = true;
2437
+ }
2438
+ }
623
2439
 
624
- if (e.identifier !== this.touchId) {
625
- return;
2440
+ if (options.scalable) {
2441
+ if (isNumber(data.scaleX) && data.scaleX !== image.scaleX) {
2442
+ image.scaleX = data.scaleX;
2443
+ isScaled = true;
2444
+ }
2445
+
2446
+ if (isNumber(data.scaleY) && data.scaleY !== image.scaleY) {
2447
+ image.scaleY = data.scaleY;
2448
+ isScaled = true;
2449
+ }
2450
+ }
2451
+
2452
+ if (isRotated) {
2453
+ this.renderCanvas();
2454
+ } else if (isScaled) {
2455
+ this.renderImage();
2456
+ }
2457
+
2458
+ ratio = image.width / image.naturalWidth;
2459
+
2460
+ if (isNumber(data.x)) {
2461
+ cropBoxData.left = data.x * ratio + canvas.left;
626
2462
  }
2463
+
2464
+ if (isNumber(data.y)) {
2465
+ cropBoxData.top = data.y * ratio + canvas.top;
2466
+ }
2467
+
2468
+ if (isNumber(data.width)) {
2469
+ cropBoxData.width = data.width * ratio;
2470
+ }
2471
+
2472
+ if (isNumber(data.height)) {
2473
+ cropBoxData.height = data.height * ratio;
2474
+ }
2475
+
2476
+ this.setCropBoxData(cropBoxData);
627
2477
  }
2478
+ },
628
2479
 
629
- if (this.direction) {
630
- this.direction = "";
631
- this.$image.trigger("dragend");
632
- touching && event.preventDefault();
2480
+ /**
2481
+ * Get the container size data
2482
+ *
2483
+ * @return {Object} data
2484
+ */
2485
+ getContainerData: function () {
2486
+ return this.isBuilt ? this.container : {};
2487
+ },
2488
+
2489
+ /**
2490
+ * Get the image position and size data
2491
+ *
2492
+ * @return {Object} data
2493
+ */
2494
+ getImageData: function () {
2495
+ return this.isLoaded ? this.image : {};
2496
+ },
2497
+
2498
+ /**
2499
+ * Get the canvas position and size data
2500
+ *
2501
+ * @return {Object} data
2502
+ */
2503
+ getCanvasData: function () {
2504
+ var canvas = this.canvas;
2505
+ var data = {};
2506
+
2507
+ if (this.isBuilt) {
2508
+ $.each([
2509
+ 'left',
2510
+ 'top',
2511
+ 'width',
2512
+ 'height',
2513
+ 'naturalWidth',
2514
+ 'naturalHeight'
2515
+ ], function (i, n) {
2516
+ data[n] = canvas[n];
2517
+ });
633
2518
  }
634
- }
635
- };
636
2519
 
637
- // Common methods
638
- Cropper.fn = {
639
- toggle: function($e) {
640
- $e.toggleClass("cropper-hidden");
2520
+ return data;
641
2521
  },
642
2522
 
643
- position: function($e, option) {
644
- var position = $e.css("position");
2523
+ /**
2524
+ * Set the canvas position and size with new data
2525
+ *
2526
+ * @param {Object} data
2527
+ */
2528
+ setCanvasData: function (data) {
2529
+ var canvas = this.canvas;
2530
+ var aspectRatio = canvas.aspectRatio;
2531
+
2532
+ if ($.isFunction(data)) {
2533
+ data = data.call(this.$element);
2534
+ }
2535
+
2536
+ if (this.isBuilt && !this.isDisabled && $.isPlainObject(data)) {
2537
+ if (isNumber(data.left)) {
2538
+ canvas.left = data.left;
2539
+ }
2540
+
2541
+ if (isNumber(data.top)) {
2542
+ canvas.top = data.top;
2543
+ }
2544
+
2545
+ if (isNumber(data.width)) {
2546
+ canvas.width = data.width;
2547
+ canvas.height = data.width / aspectRatio;
2548
+ } else if (isNumber(data.height)) {
2549
+ canvas.height = data.height;
2550
+ canvas.width = data.height * aspectRatio;
2551
+ }
645
2552
 
646
- if (position === "static") {
647
- $e.css("position", option || "relative");
2553
+ this.renderCanvas(true);
648
2554
  }
649
2555
  },
650
2556
 
651
- size: function($e, options) {
652
- if ($.isPlainObject(options)) {
653
- $e.css(options);
654
- } else {
655
- return {
656
- height: $e.height(),
657
- width: $e.width()
2557
+ /**
2558
+ * Get the crop box position and size data
2559
+ *
2560
+ * @return {Object} data
2561
+ */
2562
+ getCropBoxData: function () {
2563
+ var cropBox = this.cropBox;
2564
+ var data;
2565
+
2566
+ if (this.isBuilt && this.isCropped) {
2567
+ data = {
2568
+ left: cropBox.left,
2569
+ top: cropBox.top,
2570
+ width: cropBox.width,
2571
+ height: cropBox.height
658
2572
  };
659
2573
  }
2574
+
2575
+ return data || {};
660
2576
  },
661
2577
 
662
- round: function(data, fn) {
663
- var value,
664
- i;
2578
+ /**
2579
+ * Set the crop box position and size with new data
2580
+ *
2581
+ * @param {Object} data
2582
+ */
2583
+ setCropBoxData: function (data) {
2584
+ var cropBox = this.cropBox;
2585
+ var aspectRatio = this.options.aspectRatio;
2586
+ var isWidthChanged;
2587
+ var isHeightChanged;
2588
+
2589
+ if ($.isFunction(data)) {
2590
+ data = data.call(this.$element);
2591
+ }
665
2592
 
666
- for (i in data) {
667
- value = data[i];
2593
+ if (this.isBuilt && this.isCropped && !this.isDisabled && $.isPlainObject(data)) {
668
2594
 
669
- if (data.hasOwnProperty(i) && typeof value === "number") {
670
- data[i] = Math.round($.isFunction(fn) ? fn(value) : value);
2595
+ if (isNumber(data.left)) {
2596
+ cropBox.left = data.left;
671
2597
  }
672
- }
673
2598
 
674
- return data;
675
- },
2599
+ if (isNumber(data.top)) {
2600
+ cropBox.top = data.top;
2601
+ }
676
2602
 
677
- transformData: function(data, ratio) {
678
- var that = this,
679
- result = {};
2603
+ if (isNumber(data.width)) {
2604
+ isWidthChanged = true;
2605
+ cropBox.width = data.width;
2606
+ }
680
2607
 
681
- $.each(data, function(i, n) {
682
- if (that.isDataOption(i) && $.isNumeric(n) && n >= 0) {
683
- result[i] = Math.round(n * ratio);
2608
+ if (isNumber(data.height)) {
2609
+ isHeightChanged = true;
2610
+ cropBox.height = data.height;
2611
+ }
2612
+
2613
+ if (aspectRatio) {
2614
+ if (isWidthChanged) {
2615
+ cropBox.height = cropBox.width / aspectRatio;
2616
+ } else if (isHeightChanged) {
2617
+ cropBox.width = cropBox.height * aspectRatio;
2618
+ }
684
2619
  }
685
- });
686
2620
 
687
- return result;
2621
+ this.renderCropBox();
2622
+ }
688
2623
  },
689
2624
 
690
- getOriginalEvent: function(event) {
691
- if (event && typeof event.originalEvent !== "undefined") {
692
- event = event.originalEvent;
2625
+ /**
2626
+ * Get a canvas drawn the cropped image
2627
+ *
2628
+ * @param {Object} options (optional)
2629
+ * @return {HTMLCanvasElement} canvas
2630
+ */
2631
+ getCroppedCanvas: function (options) {
2632
+ var originalWidth;
2633
+ var originalHeight;
2634
+ var canvasWidth;
2635
+ var canvasHeight;
2636
+ var scaledWidth;
2637
+ var scaledHeight;
2638
+ var scaledRatio;
2639
+ var aspectRatio;
2640
+ var canvas;
2641
+ var context;
2642
+ var data;
2643
+
2644
+ if (!this.isBuilt || !this.isCropped || !SUPPORT_CANVAS) {
2645
+ return;
2646
+ }
2647
+
2648
+ if (!$.isPlainObject(options)) {
2649
+ options = {};
2650
+ }
2651
+
2652
+ data = this.getData();
2653
+ originalWidth = data.width;
2654
+ originalHeight = data.height;
2655
+ aspectRatio = originalWidth / originalHeight;
2656
+
2657
+ if ($.isPlainObject(options)) {
2658
+ scaledWidth = options.width;
2659
+ scaledHeight = options.height;
2660
+
2661
+ if (scaledWidth) {
2662
+ scaledHeight = scaledWidth / aspectRatio;
2663
+ scaledRatio = scaledWidth / originalWidth;
2664
+ } else if (scaledHeight) {
2665
+ scaledWidth = scaledHeight * aspectRatio;
2666
+ scaledRatio = scaledHeight / originalHeight;
2667
+ }
2668
+ }
2669
+
2670
+ // The canvas element will use `Math.floor` on a float number, so floor first
2671
+ canvasWidth = floor(scaledWidth || originalWidth);
2672
+ canvasHeight = floor(scaledHeight || originalHeight);
2673
+
2674
+ canvas = $('<canvas>')[0];
2675
+ canvas.width = canvasWidth;
2676
+ canvas.height = canvasHeight;
2677
+ context = canvas.getContext('2d');
2678
+
2679
+ if (options.fillColor) {
2680
+ context.fillStyle = options.fillColor;
2681
+ context.fillRect(0, 0, canvasWidth, canvasHeight);
693
2682
  }
694
2683
 
695
- return event;
2684
+ // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage
2685
+ context.drawImage.apply(context, (function () {
2686
+ var source = getSourceCanvas(this.$clone[0], this.image);
2687
+ var sourceWidth = source.width;
2688
+ var sourceHeight = source.height;
2689
+ var args = [source];
2690
+
2691
+ // Source canvas
2692
+ var srcX = data.x;
2693
+ var srcY = data.y;
2694
+ var srcWidth;
2695
+ var srcHeight;
2696
+
2697
+ // Destination canvas
2698
+ var dstX;
2699
+ var dstY;
2700
+ var dstWidth;
2701
+ var dstHeight;
2702
+
2703
+ if (srcX <= -originalWidth || srcX > sourceWidth) {
2704
+ srcX = srcWidth = dstX = dstWidth = 0;
2705
+ } else if (srcX <= 0) {
2706
+ dstX = -srcX;
2707
+ srcX = 0;
2708
+ srcWidth = dstWidth = min(sourceWidth, originalWidth + srcX);
2709
+ } else if (srcX <= sourceWidth) {
2710
+ dstX = 0;
2711
+ srcWidth = dstWidth = min(originalWidth, sourceWidth - srcX);
2712
+ }
2713
+
2714
+ if (srcWidth <= 0 || srcY <= -originalHeight || srcY > sourceHeight) {
2715
+ srcY = srcHeight = dstY = dstHeight = 0;
2716
+ } else if (srcY <= 0) {
2717
+ dstY = -srcY;
2718
+ srcY = 0;
2719
+ srcHeight = dstHeight = min(sourceHeight, originalHeight + srcY);
2720
+ } else if (srcY <= sourceHeight) {
2721
+ dstY = 0;
2722
+ srcHeight = dstHeight = min(originalHeight, sourceHeight - srcY);
2723
+ }
2724
+
2725
+ // All the numerical parameters should be integer for `drawImage` (#476)
2726
+ args.push(floor(srcX), floor(srcY), floor(srcWidth), floor(srcHeight));
2727
+
2728
+ // Scale destination sizes
2729
+ if (scaledRatio) {
2730
+ dstX *= scaledRatio;
2731
+ dstY *= scaledRatio;
2732
+ dstWidth *= scaledRatio;
2733
+ dstHeight *= scaledRatio;
2734
+ }
2735
+
2736
+ // Avoid "IndexSizeError" in IE and Firefox
2737
+ if (dstWidth > 0 && dstHeight > 0) {
2738
+ args.push(floor(dstX), floor(dstY), floor(dstWidth), floor(dstHeight));
2739
+ }
2740
+
2741
+ return args;
2742
+ }).call(this));
2743
+
2744
+ return canvas;
696
2745
  },
697
2746
 
698
- isDataOption: function(s) {
699
- return /^(x1|y1|x2|y2|width|height)$/i.test(s);
2747
+ /**
2748
+ * Change the aspect ratio of the crop box
2749
+ *
2750
+ * @param {Number} aspectRatio
2751
+ */
2752
+ setAspectRatio: function (aspectRatio) {
2753
+ var options = this.options;
2754
+
2755
+ if (!this.isDisabled && !isUndefined(aspectRatio)) {
2756
+
2757
+ // 0 -> NaN
2758
+ options.aspectRatio = max(0, aspectRatio) || NaN;
2759
+
2760
+ if (this.isBuilt) {
2761
+ this.initCropBox();
2762
+
2763
+ if (this.isCropped) {
2764
+ this.renderCropBox();
2765
+ }
2766
+ }
2767
+ }
700
2768
  },
701
2769
 
702
- isDirection: function(s) {
703
- return /^(\*|e|n|w|s|ne|nw|sw|se)$/i.test(s);
2770
+ /**
2771
+ * Change the drag mode
2772
+ *
2773
+ * @param {String} mode (optional)
2774
+ */
2775
+ setDragMode: function (mode) {
2776
+ var options = this.options;
2777
+ var croppable;
2778
+ var movable;
2779
+
2780
+ if (this.isLoaded && !this.isDisabled) {
2781
+ croppable = mode === ACTION_CROP;
2782
+ movable = options.movable && mode === ACTION_MOVE;
2783
+ mode = (croppable || movable) ? mode : ACTION_NONE;
2784
+
2785
+ this.$dragBox.
2786
+ data(DATA_ACTION, mode).
2787
+ toggleClass(CLASS_CROP, croppable).
2788
+ toggleClass(CLASS_MOVE, movable);
2789
+
2790
+ if (!options.cropBoxMovable) {
2791
+
2792
+ // Sync drag mode to crop box when it is not movable(#300)
2793
+ this.$face.
2794
+ data(DATA_ACTION, mode).
2795
+ toggleClass(CLASS_CROP, croppable).
2796
+ toggleClass(CLASS_MOVE, movable);
2797
+ }
2798
+ }
704
2799
  }
705
2800
  };
706
2801
 
707
- Cropper.template = [
708
- '<div class="cropper-container">',
709
- '<div class="cropper-modal"></div>',
710
- '<div class="cropper-dragger">',
711
- '<span class="cropper-preview"></span>',
712
- '<span class="cropper-dashed dashed-h"></span>',
713
- '<span class="cropper-dashed dashed-v"></span>',
714
- '<span class="cropper-face" data-direction="*"></span>',
715
- '<span class="cropper-line line-e" data-direction="e"></span>',
716
- '<span class="cropper-line line-n" data-direction="n"></span>',
717
- '<span class="cropper-line line-w" data-direction="w"></span>',
718
- '<span class="cropper-line line-s" data-direction="s"></span>',
719
- '<span class="cropper-point point-e" data-direction="e"></span>',
720
- '<span class="cropper-point point-n" data-direction="n"></span>',
721
- '<span class="cropper-point point-w" data-direction="w"></span>',
722
- '<span class="cropper-point point-s" data-direction="s"></span>',
723
- '<span class="cropper-point point-ne" data-direction="ne"></span>',
724
- '<span class="cropper-point point-nw" data-direction="nw"></span>',
725
- '<span class="cropper-point point-sw" data-direction="sw"></span>',
726
- '<span class="cropper-point point-se" data-direction="se"></span>',
727
- '</div>',
728
- '</div>'
729
- ].join("");
730
-
731
- Cropper.defaults = {
732
- aspectRatio: "auto",
733
- data: {},
734
- done: function(/* data */) {},
2802
+ Cropper.DEFAULTS = {
2803
+
2804
+ // Define the view mode of the cropper
2805
+ viewMode: 0, // 0, 1, 2, 3
2806
+
2807
+ // Define the dragging mode of the cropper
2808
+ dragMode: 'crop', // 'crop', 'move' or 'none'
2809
+
2810
+ // Define the aspect ratio of the crop box
2811
+ aspectRatio: NaN,
2812
+
2813
+ // An object with the previous cropping result data
2814
+ data: null,
2815
+
2816
+ // A jQuery selector for adding extra containers to preview
2817
+ preview: '',
2818
+
2819
+ // Re-render the cropper when resize the window
2820
+ responsive: true,
2821
+
2822
+ // Restore the cropped area after resize the window
2823
+ restore: true,
2824
+
2825
+ // Check if the current image is a cross-origin image
2826
+ checkCrossOrigin: true,
2827
+
2828
+ // Check the current image's Exif Orientation information
2829
+ checkOrientation: true,
2830
+
2831
+ // Show the black modal
735
2832
  modal: true,
736
- preview: ""
2833
+
2834
+ // Show the dashed lines for guiding
2835
+ guides: true,
2836
+
2837
+ // Show the center indicator for guiding
2838
+ center: true,
2839
+
2840
+ // Show the white modal to highlight the crop box
2841
+ highlight: true,
2842
+
2843
+ // Show the grid background
2844
+ background: true,
2845
+
2846
+ // Enable to crop the image automatically when initialize
2847
+ autoCrop: true,
2848
+
2849
+ // Define the percentage of automatic cropping area when initializes
2850
+ autoCropArea: 0.8,
2851
+
2852
+ // Enable to move the image
2853
+ movable: true,
2854
+
2855
+ // Enable to rotate the image
2856
+ rotatable: true,
2857
+
2858
+ // Enable to scale the image
2859
+ scalable: true,
2860
+
2861
+ // Enable to zoom the image
2862
+ zoomable: true,
2863
+
2864
+ // Enable to zoom the image by dragging touch
2865
+ zoomOnTouch: true,
2866
+
2867
+ // Enable to zoom the image by wheeling mouse
2868
+ zoomOnWheel: true,
2869
+
2870
+ // Define zoom ratio when zoom the image by wheeling mouse
2871
+ wheelZoomRatio: 0.1,
2872
+
2873
+ // Enable to move the crop box
2874
+ cropBoxMovable: true,
2875
+
2876
+ // Enable to resize the crop box
2877
+ cropBoxResizable: true,
2878
+
2879
+ // Toggle drag mode between "crop" and "move" when click twice on the cropper
2880
+ toggleDragModeOnDblclick: true,
2881
+
2882
+ // Size limitation
2883
+ minCanvasWidth: 0,
2884
+ minCanvasHeight: 0,
2885
+ minCropBoxWidth: 0,
2886
+ minCropBoxHeight: 0,
2887
+ minContainerWidth: 200,
2888
+ minContainerHeight: 100,
2889
+
2890
+ // Shortcuts of events
2891
+ build: null,
2892
+ built: null,
2893
+ cropstart: null,
2894
+ cropmove: null,
2895
+ cropend: null,
2896
+ crop: null,
2897
+ zoom: null
737
2898
  };
738
2899
 
739
- Cropper.setDefaults = function(options) {
740
- $.extend(Cropper.defaults, options);
2900
+ Cropper.setDefaults = function (options) {
2901
+ $.extend(Cropper.DEFAULTS, options);
741
2902
  };
742
2903
 
2904
+ Cropper.TEMPLATE = (
2905
+ '<div class="cropper-container">' +
2906
+ '<div class="cropper-wrap-box">' +
2907
+ '<div class="cropper-canvas"></div>' +
2908
+ '</div>' +
2909
+ '<div class="cropper-drag-box"></div>' +
2910
+ '<div class="cropper-crop-box">' +
2911
+ '<span class="cropper-view-box"></span>' +
2912
+ '<span class="cropper-dashed dashed-h"></span>' +
2913
+ '<span class="cropper-dashed dashed-v"></span>' +
2914
+ '<span class="cropper-center"></span>' +
2915
+ '<span class="cropper-face"></span>' +
2916
+ '<span class="cropper-line line-e" data-action="e"></span>' +
2917
+ '<span class="cropper-line line-n" data-action="n"></span>' +
2918
+ '<span class="cropper-line line-w" data-action="w"></span>' +
2919
+ '<span class="cropper-line line-s" data-action="s"></span>' +
2920
+ '<span class="cropper-point point-e" data-action="e"></span>' +
2921
+ '<span class="cropper-point point-n" data-action="n"></span>' +
2922
+ '<span class="cropper-point point-w" data-action="w"></span>' +
2923
+ '<span class="cropper-point point-s" data-action="s"></span>' +
2924
+ '<span class="cropper-point point-ne" data-action="ne"></span>' +
2925
+ '<span class="cropper-point point-nw" data-action="nw"></span>' +
2926
+ '<span class="cropper-point point-sw" data-action="sw"></span>' +
2927
+ '<span class="cropper-point point-se" data-action="se"></span>' +
2928
+ '</div>' +
2929
+ '</div>'
2930
+ );
2931
+
2932
+ // Save the other cropper
2933
+ Cropper.other = $.fn.cropper;
2934
+
743
2935
  // Register as jQuery plugin
744
- $.fn.cropper = function(options, settings) {
745
- var result = this;
2936
+ $.fn.cropper = function (option) {
2937
+ var args = toArray(arguments, 1);
2938
+ var result;
746
2939
 
747
- this.each(function() {
748
- var $this = $(this),
749
- data = $this.data("cropper");
2940
+ this.each(function () {
2941
+ var $this = $(this);
2942
+ var data = $this.data(NAMESPACE);
2943
+ var options;
2944
+ var fn;
750
2945
 
751
2946
  if (!data) {
752
- data = new Cropper(this, options);
753
- $this.data("cropper", data);
2947
+ if (/destroy/.test(option)) {
2948
+ return;
2949
+ }
2950
+
2951
+ options = $.extend({}, $this.data(), $.isPlainObject(option) && option);
2952
+ $this.data(NAMESPACE, (data = new Cropper(this, options)));
754
2953
  }
755
2954
 
756
- if (typeof options === "string" && $.isFunction(data[options])) {
757
- result = data[options](settings);
2955
+ if (typeof option === 'string' && $.isFunction(fn = data[option])) {
2956
+ result = fn.apply(data, args);
758
2957
  }
759
2958
  });
760
2959
 
761
- return (typeof result !== "undefined" ? result : this);
2960
+ return isUndefined(result) ? this : result;
762
2961
  };
763
2962
 
764
2963
  $.fn.cropper.Constructor = Cropper;
765
2964
  $.fn.cropper.setDefaults = Cropper.setDefaults;
766
2965
 
767
- $(function() {
768
- $("img[cropper]").cropper();
769
- });
770
- }));
2966
+ // No conflict
2967
+ $.fn.cropper.noConflict = function () {
2968
+ $.fn.cropper = Cropper.other;
2969
+ return this;
2970
+ };
2971
+
2972
+ });