You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
779 lines
24 KiB
779 lines
24 KiB
/*!
|
|
* Cropper v0.3.2
|
|
* https://github.com/fengyuanchen/cropper
|
|
*
|
|
* Copyright 2014 Fengyuan Chen
|
|
* Released under the MIT license
|
|
*/
|
|
|
|
(function(factory) {
|
|
if (typeof define === "function" && define.amd) {
|
|
// AMD. Register as anonymous module.
|
|
define(["jquery"], factory);
|
|
} else {
|
|
// Browser globals.
|
|
factory(jQuery);
|
|
}
|
|
}(function($) {
|
|
|
|
"use strict";
|
|
|
|
var $window = $(window),
|
|
Cropper = function(element, options) {
|
|
options = $.isPlainObject(options) ? options : {};
|
|
this.$image = $(element);
|
|
this.defaults = $.extend({}, Cropper.defaults, this.$image.data(), options);
|
|
this.init();
|
|
};
|
|
|
|
Cropper.prototype = {
|
|
construstor: Cropper,
|
|
|
|
init: function() {
|
|
this.setAspectRatio(this.defaults.aspectRatio);
|
|
this.render();
|
|
},
|
|
|
|
render: function(callback) {
|
|
var that = this,
|
|
$image = this.$image,
|
|
$clone,
|
|
src;
|
|
|
|
if (this.active) {
|
|
return;
|
|
}
|
|
|
|
if (this.$clone) {
|
|
this.$clone.remove(); // Remove the old clone
|
|
}
|
|
|
|
src = $image.attr("src"); // Don't use "prop"
|
|
$clone = $('<img src="' + src + '">');
|
|
|
|
$clone.on("load", function() {
|
|
var image;
|
|
|
|
$clone.off("load");
|
|
|
|
if (this.naturalWidth && this.naturalHeight) {
|
|
image = {
|
|
naturalHeight: this.naturalHeight,
|
|
naturalWidth: this.naturalWidth
|
|
};
|
|
} else {
|
|
Cropper.fn.size($clone, {
|
|
height: "auto",
|
|
width: "auto"
|
|
});
|
|
|
|
image = Cropper.fn.size($clone);
|
|
image = {
|
|
naturalHeight: image.height,
|
|
naturalWidth: image.width
|
|
};
|
|
}
|
|
|
|
Cropper.fn.size($clone, {
|
|
height: "100%",
|
|
width: "100%"
|
|
});
|
|
|
|
image.aspectRatio = image.naturalWidth / image.naturalHeight;
|
|
that.src = src;
|
|
that.image = image;
|
|
that.active = true;
|
|
that.createCropper();
|
|
});
|
|
|
|
if ($.isFunction(callback)) {
|
|
$image.on("ready.cropper", callback);
|
|
}
|
|
|
|
this.$clone = $clone;
|
|
$image.after($clone);
|
|
},
|
|
|
|
unrender: function() {
|
|
if (this.active) {
|
|
this.active = false;
|
|
this.removeCropper();
|
|
this.src = "";
|
|
this.image = null;
|
|
this.cropper = null;
|
|
this.dragger = null;
|
|
}
|
|
},
|
|
|
|
rerender: function() {
|
|
this.unrender();
|
|
this.render();
|
|
},
|
|
|
|
resize: function() {
|
|
clearTimeout(this.resizing);
|
|
this.resizing = setTimeout($.proxy(this.rerender, this), 200);
|
|
},
|
|
|
|
createCropper: function() {
|
|
this.$cropper = $(Cropper.template);
|
|
this.$dragger = this.$cropper.find(".cropper-dragger");
|
|
Cropper.fn.toggle(this.$image);
|
|
this.$image.after(this.$cropper);
|
|
this.$cropper.prepend(this.$clone);
|
|
|
|
if (!this.defaults.modal) {
|
|
Cropper.fn.toggle(this.$cropper.find(".cropper-modal"));
|
|
}
|
|
|
|
this.setPreview();
|
|
this.addListener();
|
|
},
|
|
|
|
removeCropper: function() {
|
|
this.removeListener();
|
|
this.$preview = null;
|
|
this.$clone.remove();
|
|
this.$clone = null;
|
|
this.$dragger = null;
|
|
this.$cropper.remove();
|
|
this.$cropper = null;
|
|
Cropper.fn.toggle(this.$image);
|
|
},
|
|
|
|
addListener: function() {
|
|
this.$cropper.bind("mousedown touchstart", $.proxy(this.dragstart, this));
|
|
this.$cropper.bind("mousemove touchmove", $.proxy(this.dragmove, this));
|
|
this.$cropper.bind("mouseup mouseleave touchend touchleave", $.proxy(this.dragend, this));
|
|
$window.on("resize", $.proxy(this.resize, this));
|
|
},
|
|
|
|
removeListener: function() {
|
|
this.$cropper.unbind("mousedown touchstart", this.dragstart);
|
|
this.$cropper.unbind("mousemove touchmove", this.dragmove);
|
|
this.$cropper.unbind("mouseup mouseleave touchend touchleave", this.dragend);
|
|
$window.off("resize", this.resize);
|
|
},
|
|
|
|
setPreview: function() {
|
|
var preview = this.defaults.preview;
|
|
|
|
this.$preview = this.$cropper.find(".cropper-preview");
|
|
|
|
if (typeof preview === "string" && preview.length > 0) {
|
|
this.$preview = this.$preview.add(preview);
|
|
}
|
|
|
|
this.$preview.html('<img src="' + this.src + '">');
|
|
this.setCropper();
|
|
},
|
|
|
|
setCropper: function() {
|
|
var $container = this.$image.parent(),
|
|
container = Cropper.fn.size($container),
|
|
image = this.image,
|
|
cropper;
|
|
|
|
if (((image.naturalWidth * container.height / image.naturalHeight) - container.width) >= 0) {
|
|
cropper = {
|
|
height: container.width / image.aspectRatio,
|
|
width: container.width,
|
|
left: 0
|
|
};
|
|
|
|
cropper.top = (container.height - cropper.height) / 2;
|
|
} else {
|
|
cropper = {
|
|
height: container.height,
|
|
width: container.height * image.aspectRatio,
|
|
top: 0
|
|
};
|
|
|
|
cropper.left = (container.width - cropper.width) / 2;
|
|
}
|
|
|
|
$.each(cropper, function(i, n) {
|
|
cropper[i] = Math.round(n);
|
|
});
|
|
|
|
image.height = cropper.height;
|
|
image.width = cropper.width;
|
|
image.ratio = image.width / image.naturalWidth;
|
|
|
|
Cropper.fn.position($container);
|
|
this.$cropper.css({
|
|
height: cropper.height,
|
|
left: cropper.left,
|
|
top: cropper.top,
|
|
width: cropper.width
|
|
});
|
|
|
|
this.cropper = cropper;
|
|
this.setDragger();
|
|
},
|
|
|
|
setDragger: function() {
|
|
var cropper = this.cropper,
|
|
// If not set, use the original aspect ratio of the image.
|
|
aspectRatio = this.defaults.aspectRatio || this.image.aspectRatio,
|
|
dragger;
|
|
|
|
if (((cropper.height * aspectRatio) - cropper.width) >= 0) {
|
|
dragger = {
|
|
height: cropper.width / aspectRatio,
|
|
width: cropper.width,
|
|
left: 0,
|
|
top: (cropper.height - (cropper.width / aspectRatio)) / 2,
|
|
maxWidth: cropper.width,
|
|
maxHeight: cropper.width / aspectRatio
|
|
};
|
|
} else {
|
|
dragger = {
|
|
height: cropper.height,
|
|
width: cropper.height * aspectRatio,
|
|
left: (cropper.width - (cropper.height * aspectRatio)) / 2,
|
|
top: 0,
|
|
maxHeight: cropper.height,
|
|
maxWidth: cropper.height * aspectRatio
|
|
};
|
|
}
|
|
|
|
dragger.height *= 0.8;
|
|
dragger.width *= 0.8;
|
|
|
|
dragger.left = (cropper.width - dragger.width) / 2;
|
|
dragger.top = (cropper.height - dragger.height) / 2;
|
|
|
|
this.dragger = Cropper.fn.round(dragger);
|
|
this.setData(this.defaults.data);
|
|
this.$image.trigger("ready.cropper").off("ready.cropper");
|
|
},
|
|
|
|
resetDragger: function() {
|
|
var dragger = this.dragger,
|
|
cropper = this.cropper;
|
|
|
|
dragger.width = dragger.width > dragger.maxWidth ? dragger.maxWidth : Math.abs(dragger.width);
|
|
dragger.height = dragger.height > dragger.maxHeight ? dragger.maxHeight : Math.abs(dragger.height);
|
|
|
|
dragger.maxLeft = cropper.width - dragger.width;
|
|
dragger.maxTop = cropper.height - dragger.height;
|
|
|
|
dragger.left = dragger.left < 0 ? 0 : dragger.left > dragger.maxLeft ? dragger.maxLeft : dragger.left;
|
|
dragger.top = dragger.top < 0 ? 0 : dragger.top > dragger.maxTop ? dragger.maxTop : dragger.top;
|
|
|
|
dragger = Cropper.fn.round(dragger);
|
|
|
|
this.$dragger.css({
|
|
height: dragger.height,
|
|
left: dragger.left,
|
|
top: dragger.top,
|
|
width: dragger.width
|
|
});
|
|
|
|
this.dragger = dragger;
|
|
this.preview();
|
|
this.output();
|
|
},
|
|
|
|
dragging: function() {
|
|
var direction = this.direction,
|
|
dragger = this.dragger,
|
|
aspectRatio = this.defaults.aspectRatio,
|
|
range = {
|
|
x: this.endX - this.startX,
|
|
y: this.endY - this.startY
|
|
};
|
|
|
|
if (aspectRatio) {
|
|
range.X = range.y * aspectRatio;
|
|
range.Y = range.x / aspectRatio;
|
|
}
|
|
|
|
switch (direction) {
|
|
|
|
// dragging
|
|
case "e":
|
|
dragger.width += range.x;
|
|
|
|
if (aspectRatio) {
|
|
dragger.height = dragger.width / aspectRatio;
|
|
dragger.top -= range.Y / 2;
|
|
}
|
|
|
|
if (dragger.width < 0) {
|
|
this.direction = "w";
|
|
dragger.width = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case "n":
|
|
dragger.height -= range.y;
|
|
dragger.top += range.y;
|
|
|
|
if (aspectRatio) {
|
|
dragger.width = dragger.height * aspectRatio;
|
|
dragger.left += range.X / 2;
|
|
}
|
|
|
|
if (dragger.height < 0) {
|
|
this.direction = "s";
|
|
dragger.height = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case "w":
|
|
dragger.width -= range.x;
|
|
dragger.left += range.x;
|
|
|
|
if (aspectRatio) {
|
|
dragger.height = dragger.width / aspectRatio;
|
|
dragger.top += range.Y / 2;
|
|
}
|
|
|
|
if (dragger.width < 0) {
|
|
this.direction = "e";
|
|
dragger.width = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case "s":
|
|
dragger.height += range.y;
|
|
|
|
if (aspectRatio) {
|
|
dragger.width = dragger.height * aspectRatio;
|
|
dragger.left -= range.X / 2;
|
|
}
|
|
|
|
if (dragger.height < 0) {
|
|
this.direction = "n";
|
|
dragger.height = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case "ne":
|
|
dragger.height -= range.y;
|
|
dragger.top += range.y;
|
|
|
|
if (aspectRatio) {
|
|
dragger.width = dragger.height * aspectRatio;
|
|
} else {
|
|
dragger.width += range.x;
|
|
}
|
|
|
|
if (dragger.height < 0) {
|
|
this.direction = "sw";
|
|
dragger.height = 0;
|
|
dragger.width = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case "nw":
|
|
dragger.height -= range.y;
|
|
dragger.top += range.y;
|
|
|
|
if (aspectRatio) {
|
|
dragger.width = dragger.height * aspectRatio;
|
|
dragger.left += range.X;
|
|
} else {
|
|
dragger.width -= range.x;
|
|
dragger.left += range.x;
|
|
}
|
|
|
|
if (dragger.height < 0) {
|
|
this.direction = "se";
|
|
dragger.height = 0;
|
|
dragger.width = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case "sw":
|
|
dragger.width -= range.x;
|
|
dragger.left += range.x;
|
|
|
|
if (aspectRatio) {
|
|
dragger.height = dragger.width / aspectRatio;
|
|
} else {
|
|
dragger.height += range.y;
|
|
}
|
|
|
|
if (dragger.width < 0) {
|
|
this.direction = "ne";
|
|
dragger.height = 0;
|
|
dragger.width = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
case "se":
|
|
dragger.width += range.x;
|
|
|
|
if (aspectRatio) {
|
|
dragger.height = dragger.width / aspectRatio;
|
|
} else {
|
|
dragger.height += range.y;
|
|
}
|
|
|
|
if (dragger.width < 0) {
|
|
this.direction = "nw";
|
|
dragger.height = 0;
|
|
dragger.width = 0;
|
|
}
|
|
|
|
break;
|
|
|
|
// moving
|
|
default:
|
|
dragger.left += range.x;
|
|
dragger.top += range.y;
|
|
}
|
|
|
|
this.resetDragger();
|
|
this.startX = this.endX;
|
|
this.startY = this.endY;
|
|
},
|
|
|
|
output: function() {
|
|
this.defaults.done(this.getData());
|
|
},
|
|
|
|
preview: function() {
|
|
var that = this,
|
|
cropper = that.cropper,
|
|
dragger = that.dragger;
|
|
|
|
this.$preview.each(function() {
|
|
var $this = $(this),
|
|
ratio = $this.outerWidth() / dragger.width,
|
|
styles = {
|
|
height: cropper.height,
|
|
marginLeft: - dragger.left,
|
|
marginTop: - dragger.top,
|
|
width: cropper.width
|
|
};
|
|
|
|
$this.css({overflow: "hidden"});
|
|
$this.find("img").css(Cropper.fn.round(styles, function(n) {
|
|
return n * ratio;
|
|
}));
|
|
});
|
|
},
|
|
|
|
// Public methods
|
|
|
|
enable: function(callback) {
|
|
this.render(callback);
|
|
},
|
|
|
|
disable: function() {
|
|
this.unrender();
|
|
},
|
|
|
|
setAspectRatio: function(aspectRatio) {
|
|
if (aspectRatio === "auto" || ($.isNumeric(aspectRatio) && aspectRatio > 0)) {
|
|
this.defaults.aspectRatio = aspectRatio === "auto" ? NaN : aspectRatio;
|
|
|
|
if (this.active) {
|
|
this.setDragger();
|
|
}
|
|
}
|
|
},
|
|
|
|
setData: function(data) {
|
|
var cropper = this.cropper,
|
|
dragger = this.dragger,
|
|
aspectRatio = this.defaults.aspectRatio,
|
|
isNumber = function(n) {
|
|
return typeof n === "number";
|
|
};
|
|
|
|
if (!this.active) {
|
|
return;
|
|
}
|
|
|
|
if ($.isPlainObject(data) && !$.isEmptyObject(data)) {
|
|
data = Cropper.fn.transformData(data, this.image.ratio);
|
|
|
|
if (isNumber(data.x1) && data.x1 <= cropper.width) {
|
|
dragger.left = data.x1;
|
|
}
|
|
|
|
if (isNumber(data.y1) && data.y1 <= cropper.height) {
|
|
dragger.top = data.y1;
|
|
}
|
|
|
|
if (aspectRatio){
|
|
if (isNumber(data.width) && data.width <= cropper.width) {
|
|
dragger.width = data.width;
|
|
dragger.height = dragger.width / aspectRatio;
|
|
} else if (isNumber(data.height) && data.height <= cropper.height) {
|
|
dragger.height = data.height;
|
|
dragger.width = dragger.height * aspectRatio;
|
|
} else if (isNumber(data.x2) && data.x2 <= cropper.width) {
|
|
dragger.width = data.x2 - dragger.left;
|
|
dragger.height = dragger.width / aspectRatio;
|
|
} else if (isNumber(data.y2) && data.y2 <= cropper.height) {
|
|
dragger.height = data.y2 - dragger.top;
|
|
dragger.width = dragger.height * aspectRatio;
|
|
}
|
|
} else {
|
|
if (isNumber(data.width) && data.width <= cropper.width) {
|
|
dragger.width = data.width;
|
|
} else if (isNumber(data.x2) && data.x2 <= cropper.width) {
|
|
dragger.width = data.x2 - dragger.left;
|
|
}
|
|
|
|
if (isNumber(data.height) && data.height <= cropper.height) {
|
|
dragger.height = data.height;
|
|
} else if (isNumber(data.y2) && data.height <= cropper.height) {
|
|
dragger.height = data.y2 - dragger.top;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.dragger = dragger;
|
|
this.resetDragger();
|
|
},
|
|
|
|
getData: function() {
|
|
var dragger = this.dragger,
|
|
data = {};
|
|
|
|
if (this.active) {
|
|
data = {
|
|
x1: dragger.left,
|
|
y1: dragger.top,
|
|
width: dragger.width,
|
|
height: dragger.height,
|
|
x2: dragger.left + dragger.width,
|
|
y2: dragger.top + dragger.height
|
|
};
|
|
|
|
data = Cropper.fn.transformData(data, (1 / this.image.ratio));
|
|
}
|
|
|
|
return data;
|
|
},
|
|
|
|
setImgSrc: function(src) {
|
|
if (typeof src === "string" && src.length > 0 && src !== this.src) {
|
|
this.$image.attr("src", src);
|
|
this.rerender();
|
|
}
|
|
},
|
|
|
|
getImgInfo: function() {
|
|
return this.image || {};
|
|
},
|
|
|
|
// Public events
|
|
|
|
dragstart: function(event) {
|
|
var touches = Cropper.fn.getOriginalEvent(event).touches,
|
|
e = event,
|
|
touching,
|
|
direction;
|
|
|
|
if (touches && touches.length === 1) {
|
|
e = touches[0];
|
|
this.touchId = e.identifier;
|
|
touching = true;
|
|
}
|
|
|
|
direction = $(e.target).data().direction;
|
|
|
|
if (Cropper.fn.isDirection(direction)) {
|
|
this.startX = e.pageX;
|
|
this.startY = e.pageY;
|
|
this.direction = direction;
|
|
this.$image.trigger("dragstart");
|
|
touching && event.preventDefault();
|
|
}
|
|
},
|
|
|
|
dragmove: function(event) {
|
|
var touches = Cropper.fn.getOriginalEvent(event).changedTouches,
|
|
e = event,
|
|
touching;
|
|
|
|
if (touches && touches.length === 1) {
|
|
e = touches[0];
|
|
touching = true;
|
|
|
|
if (e.identifier !== this.touchId) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (this.direction) {
|
|
this.$image.trigger("dragmove");
|
|
touching && event.preventDefault();
|
|
this.endX = e.pageX;
|
|
this.endY = e.pageY;
|
|
this.dragging();
|
|
}
|
|
},
|
|
|
|
dragend: function(event) {
|
|
var touches = Cropper.fn.getOriginalEvent(event).changedTouches,
|
|
e = event,
|
|
touching;
|
|
|
|
if (touches && touches.length === 1) {
|
|
e = touches[0];
|
|
touching = true;
|
|
|
|
if (e.identifier !== this.touchId) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (this.direction) {
|
|
this.direction = "";
|
|
this.$image.trigger("dragend");
|
|
touching && event.preventDefault();
|
|
}
|
|
}
|
|
};
|
|
|
|
// Common methods
|
|
Cropper.fn = {
|
|
toggle: function($e) {
|
|
$e.toggleClass("cropper-hidden");
|
|
},
|
|
|
|
position: function($e, option) {
|
|
var position = $e.css("position");
|
|
|
|
if (position === "static") {
|
|
$e.css("position", option || "relative");
|
|
}
|
|
},
|
|
|
|
size: function($e, options) {
|
|
if ($.isPlainObject(options)) {
|
|
$e.css(options);
|
|
} else {
|
|
return {
|
|
height: $e.height(),
|
|
width: $e.width()
|
|
};
|
|
}
|
|
},
|
|
|
|
round: function(data, fn) {
|
|
var value,
|
|
i;
|
|
|
|
for (i in data) {
|
|
value = data[i];
|
|
|
|
if (data.hasOwnProperty(i) && typeof value === "number") {
|
|
data[i] = Math.round($.isFunction(fn) ? fn(value) : value);
|
|
}
|
|
}
|
|
|
|
return data;
|
|
},
|
|
|
|
transformData: function(data, ratio) {
|
|
var that = this,
|
|
result = {};
|
|
|
|
$.each(data, function(i, n) {
|
|
if (that.isDataOption(i) && $.isNumeric(n) && n >= 0) {
|
|
result[i] = Math.round(n * ratio);
|
|
}
|
|
});
|
|
|
|
return result;
|
|
},
|
|
|
|
getOriginalEvent: function(event) {
|
|
if (event && typeof event.originalEvent !== "undefined") {
|
|
event = event.originalEvent;
|
|
}
|
|
|
|
return event;
|
|
},
|
|
|
|
isDataOption: function(s) {
|
|
return /^(x1|y1|x2|y2|width|height)$/i.test(s);
|
|
},
|
|
|
|
isDirection: function(s) {
|
|
return /^(\*|e|n|w|s|ne|nw|sw|se)$/i.test(s);
|
|
}
|
|
};
|
|
|
|
Cropper.template = [
|
|
'<div class="cropper-container">',
|
|
'<div class="cropper-modal"></div>',
|
|
'<div class="cropper-dragger">',
|
|
'<span class="cropper-preview"></span>',
|
|
'<span class="cropper-dashed dashed-h"></span>',
|
|
'<span class="cropper-dashed dashed-v"></span>',
|
|
'<span class="cropper-face" data-direction="*"></span>',
|
|
'<span class="cropper-line line-e" data-direction="e"></span>',
|
|
'<span class="cropper-line line-n" data-direction="n"></span>',
|
|
'<span class="cropper-line line-w" data-direction="w"></span>',
|
|
'<span class="cropper-line line-s" data-direction="s"></span>',
|
|
'<span class="cropper-point point-e" data-direction="e"></span>',
|
|
'<span class="cropper-point point-n" data-direction="n"></span>',
|
|
'<span class="cropper-point point-w" data-direction="w"></span>',
|
|
'<span class="cropper-point point-s" data-direction="s"></span>',
|
|
'<span class="cropper-point point-ne" data-direction="ne"></span>',
|
|
'<span class="cropper-point point-nw" data-direction="nw"></span>',
|
|
'<span class="cropper-point point-sw" data-direction="sw"></span>',
|
|
'<span class="cropper-point point-se" data-direction="se"></span>',
|
|
'</div>',
|
|
'</div>'
|
|
].join("");
|
|
|
|
Cropper.defaults = {
|
|
aspectRatio: "auto",
|
|
data: {},
|
|
done: function(/* data */) {},
|
|
modal: true,
|
|
preview: ""
|
|
};
|
|
|
|
Cropper.setDefaults = function(options) {
|
|
$.extend(Cropper.defaults, options);
|
|
};
|
|
|
|
// Register as jQuery plugin
|
|
$.fn.cropper = function(options, settings) {
|
|
var result = this;
|
|
|
|
this.each(function() {
|
|
var $this = $(this),
|
|
data = $this.data("cropper");
|
|
|
|
if (!data) {
|
|
data = new Cropper(this, options);
|
|
$this.data("cropper", data);
|
|
}
|
|
|
|
if (typeof options === "string" && $.isFunction(data[options])) {
|
|
result = data[options](settings);
|
|
}
|
|
});
|
|
|
|
return (typeof result !== "undefined" ? result : this);
|
|
};
|
|
|
|
$.fn.cropper.Constructor = Cropper;
|
|
$.fn.cropper.setDefaults = Cropper.setDefaults;
|
|
|
|
$(function() {
|
|
$("img[cropper]").cropper();
|
|
});
|
|
}));
|