整体思路 :
在移动端压缩图片并且上传主要用到filereader、canvas 以及 formdata 这三个h5的api。逻辑并不难。整个过程就是:
(1)用户使用input file上传图片的时候,用filereader读取用户上传的图片数据(base64格式)
(2)把图片数据传入img对象,然后将img绘制到canvas上,再调用canvas.toDataURL对图片进行压缩
(3)利用copper.js组件进行图片的裁剪旋转处理
(4)获取到压缩后的base64格式图片数据,转成二进制塞入formdata,再通过XmlHttpRequest提交formdata。、
说起来好像挺简单,其实还是有些坑的。接下来就直接用代码进行分析:
1)先是获取图片数据,也就是监听input file的change事件,然后获取到上传的文件对象files,将类数组的files转成数组,然后进行forEach遍历。
先了解一个方法 fileReader
aaarticlea/png;base64,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" alt="" />
aaarticlea/png;base64,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" alt="" />
2)接着判断文件类型,如果不是图片则不作处理。如果是图片就实例化一个filereader,以base64格式读取上传的文件数据,判断数据长度,如果大于200KB的图片就调用compress方法进行压缩,否则调用upload方法进行上传。
//在我做的项目中并没有判断图片大小进行分别处理
【2】压缩图片
上面做完图片数据的获取后,就可以做compress压缩图片的方法了。而压缩图片也并不是直接把图片绘制到canvas再调用一下toDataURL就行的。
在IOS中,canvas绘制图片是有两个限制的:
首先是图片的大小,如果图片的大小超过两百万像素,图片也是无法绘制到canvas上的,调用drawImage的时候不会报错,但是你用toDataURL获取图片数据的时候获取到的是空的图片数据。
再者就是canvas的大小有限制,如果canvas的大小大于大概五百万像素(即宽高乘积)的时候,不仅图片画不出来,其他什么东西也都是画不出来的。
应对第一种限制,处理办法就是瓦片绘制了。瓦片绘制,也就是将图片分割成多块绘制到canvas上,我代码里的做法是把图片分割成100万像素一块的大小,再绘制到canvas上。
而应对第二种限制,我的处理办法是对图片的宽高进行适当压缩,我代码里为了保险起见,设的上限是四百万像素,如果图片大于四百万像素就压缩到小于四百万像素。四百万像素的图片应该够了,算起来宽高都有2000X2000了。
如此一来就解决了IOS上的两种限制了。
除了上面所述的限制,还有两个坑,一个就是canvas的toDataURL是只能压缩jpg的,当用户上传的图片是png的话,就需要转成 jpg,也就是统一用canvas.toDataURL('image/jpeg', 0.1) , 类型统一设成jpeg,而压缩比就自己控制了。
另一个就是如果是png转jpg,绘制到canvas上的时候,canvas存在透明区域的话,当转成jpg的时候透明区域会变成黑色,因为 canvas的透明像素默认为rgba(0,0,0,0),所以转成jpg就变成rgba(0,0,0,1)了,也就是透明背景会变成了黑色。解决办法就 是绘制之前在canvas上铺一层白色的底色。
function compress(img) {
var initSize = img.src.length;
var width = img.width;
var height = img.height; //如果图片大于四百万像素,计算压缩比并将大小压至400万以下
var ratio;
if ((ratio = width * height / 4000000)>1) {
ratio = Math.sqrt(ratio);
width /= ratio;
height /= ratio;
}else {
ratio = 1;
} canvas.width = width;
canvas.height = height; // 铺底色
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, canvas.width, canvas.height); //如果图片像素大于100万则使用瓦片绘制
var count;
if ((count = width * height / 1000000) > 1) {
count = ~~(Math.sqrt(count)+1); //计算要分成多少块瓦片 // 计算每块瓦片的宽和高
var nw = ~~(width / count);
var nh = ~~(height / count); tCanvas.width = nw;
tCanvas.height = nh; for (var i = 0; i < count; i++) {
for (var j = 0; j < count; j++) {
tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh); ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);
}
}
} else {
ctx.drawImage(img, 0, 0, width, height);
} //进行最小压缩
var ndata = canvas.toDataURL('image/jpeg', 0.1); console.log('压缩前:' + initSize);
console.log('压缩后:' + ndata.length);
console.log('压缩率:' + ~~(100 * (initSize - ndata.length) / initSize) + "%"); tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0; return ndata;
} //这里是在别人的博客上面找到的方法 有效的解决了 IOS微信图片压缩后数据量翻倍的问题 下面是我的项目代码
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<!--定义 title keywords description信息 start-->
<title>图书管理</title>
<meta name="keywords" content=""/>
<meta name="description" content=""/>
<!--定义 title keywords description信息 end-->
<!-- 定义文档类型 视口信息 start-->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" href="css/reset.css"/>
<link rel="stylesheet" type="text/css" href="css/common.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=2.0, user-scalable=yes" />
</head>
<body>
<div class="main-wrap">
<header class="headerTop">
<h3>我要分享</h3>
</header>
<div class="bookBg">
<img src="img/bookBg.png" width="640" height="248">
</div>
<div class="cont">
<form action="" method="" class="form">
<div class="form-sub"><label>姓名 :</label><input type="text" id="name" class="text"></div>
<div class="form-sub mbn">
<label>分类 :</label>
<select id="type" class="select">
<option value="1">开发语言</option>
<option value="2">数据库</option>
<option value="3">操作系统</option>
<option value="4">前端</option>
<option value="5" selected>综合</option>
</select>
</div>
<div class="form-txt">
<textarea id="word" class="text2">简介</textarea>
</div>
<ul id="preview">
<li><img src="img/get1.png"></li>
<li><img src="img/get.png"><input type="hidden"></li>
<li><img src="img/get.png"><input type="hidden"></li>
<li><img src="img/get.png"><input type="hidden"></li>
<li><img src="img/get.png"><input type="hidden"></li>
<li><img src="img/get.png"><input type="hidden"></li>
</ul>
<button type="button" id="submit" class="button">确认分享</button>
</form>
</div> <div class="tan tan-co">
<div class="file-box">
<div class="fileTxt">
<label>选取图片:</label>
<div>
<button>选择文件</button>
<input type="file" id="getImg">
</div>
</div>
<span id="close"></span>
</div>
<div class="img-wp"></div>
<div class="img-btn">
<p>
<button class="rotate rotate-L" id="rotate-L"></button>
<button class="sure" id="sure"></button>
<button class="rotate rotate-R" id="rotate-R"></button>
</p>
</div>
</div> <footer class="footer">
<a href=""><em></em>图书名单</a>
<a href=""><em></em>已借图书</a>
<a href="" class="cur"><em></em>我要分享</a>
</footer>
</div>
<script type="text/javascript" src="js/jquery-1.11.0.min.js"></script>
<script type="text/javascript" src="js/jy.js"></script>
<script type="text/javascript" src="js/cropper.js"></script>
<script type="text/javascript">
$(function(){
var box=$(".img-wp");
var img=null;
var i=null;
//var imgURL;
function init(){
var w=$(window).width();
var h=$(window).height();
$('.tan-co').width(w).height(h);
$('.img-wp').width(w-20).height(h-120);
};
init(); $('#preview li:gt(0)').click(function(){
i=$(this).index();
$('.tan').show(); $('#getImg').change(function(){
var files,file,url;
files=$(this).prop('files');
if(files.length>0){
file = files[0]; //上传按钮上传的一个对象
if(isImageFile(file)){ //判断是否是图片
url = createObjectURLfun(file);
}else{
$('.img-wp').html('您选择的不是图片文件!请重新选择!!');
};
};
startCropper(url,i);
});
}); function startCropper(url,i){ //准备裁剪
img=$('<img src="' + url + '">');
box.empty().html(img); //先清空在放置图片
img.cropper({aspectRatio:2/3});
//图片的压缩速率
} $('#sure').click(function(){
if(img){
var c=img.cropper("getCroppedCanvas");
//获得图片base64的数据
var imgsrc=c.toDataURL("image/jpeg",0.5); //base64位的数据
$('#preview li:eq('+i+') img').attr({'src':imgsrc});
$('#preview li:eq('+i+') input').val(imgsrc);
$('#getImg').val('');
$('.img-wp').empty();
$('.tan').hide();
img=null;
c=null;
}
}); $('#rotate-L').click(function(){
if(img){
img.cropper("rotate", -90);
}
});
$('#rotate-R').click(function(){
if(img){
img.cropper("rotate", 90);
}
}); function isImageFile(file){ //判断是否是图片
if (file.type) {
return /^image\/\w+$/.test(file.type);
} else {
return /\.(jpg|jpeg|png|gif)$/.test(file);
}
} $('#close').click(function(){
$('#getImg').val('');
$('.img-wp').empty();
$('.tan').hide();
}); $('#submit').click(function(){
var name=$('#name').val();
var book=$('#book').val();
var type=$('#type option:selected').val();
var word=$('#word').val(); var pic1=$('#preview li:eq(1) input').val();
var pic2=$('#preview li:eq(2) input').val();
var pic3=$('#preview li:eq(3) input').val();
var pic4=$('#preview li:eq(4) input').val();
var pic5=$('#preview li:eq(5) input').val();
}); createObjectURLfun = function(file) {
if (window.navigator.userAgent.indexOf("Chrome") >= 1 || window.navigator.userAgent.indexOf("Safari") >= 1) {
return window.webkitURL.createObjectURL(file);
} else {
return window.URL.createObjectURL(file);
};
};
}); </script>
</body>
</html>
/*!
* Cropper v0.9.2
* https://github.com/fengyuanchen/cropper
*
* Copyright (c) 2014-2015 Fengyuan Chen and contributors
* Released under the MIT license
*
* Date: 2015-04-18T04:35:01.500Z
*/ (function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node / CommonJS
factory(require('jquery'));
} else {
// Browser globals.
factory(jQuery);
}
})(function ($) {
'use strict';
var $window = $(window),
$document = $(document),
location = window.location,
// Constants
CROPPER_NAMESPACE = '.cropper',
CROPPER_PREVIEW = 'preview' + CROPPER_NAMESPACE, // RegExps
REGEXP_DRAG_TYPES = /^(e|n|w|s|ne|nw|sw|se|all|crop|move|zoom)$/, // Classes
CLASS_MODAL = 'cropper-modal',
CLASS_HIDE = 'cropper-hide',
CLASS_HIDDEN = 'cropper-hidden',
CLASS_INVISIBLE = 'cropper-invisible',
CLASS_MOVE = 'cropper-move',
CLASS_CROP = 'cropper-crop',
CLASS_DISABLED = 'cropper-disabled',
CLASS_BG = 'cropper-bg', // Events
EVENT_MOUSE_DOWN = 'mousedown touchstart',
EVENT_MOUSE_MOVE = 'mousemove touchmove',
EVENT_MOUSE_UP = 'mouseup mouseleave touchend touchleave touchcancel',
EVENT_WHEEL = 'wheel mousewheel DOMMouseScroll',
EVENT_DBLCLICK = 'dblclick',
EVENT_RESIZE = 'resize' + CROPPER_NAMESPACE, // Bind to window with namespace
EVENT_BUILD = 'build' + CROPPER_NAMESPACE,
EVENT_BUILT = 'built' + CROPPER_NAMESPACE,
EVENT_DRAG_START = 'dragstart' + CROPPER_NAMESPACE,
EVENT_DRAG_MOVE = 'dragmove' + CROPPER_NAMESPACE,
EVENT_DRAG_END = 'dragend' + CROPPER_NAMESPACE,
EVENT_ZOOM_IN = 'zoomin' + CROPPER_NAMESPACE,
EVENT_ZOOM_OUT = 'zoomout' + CROPPER_NAMESPACE, // Supports
SUPPORT_CANVAS = $.isFunction($('<canvas>')[0].getContext), // Others
sqrt = Math.sqrt,
min = Math.min,
max = Math.max,
abs = Math.abs,
sin = Math.sin,
cos = Math.cos,
num = parseFloat, // Prototype
prototype = {}; function isNumber(n) {
return typeof n === 'number';
} function isUndefined(n) {
return typeof n === 'undefined';
} function toArray(obj, offset) {
var args = []; if (isNumber(offset)) { // It's necessary for IE8
args.push(offset);
} return args.slice.apply(obj, args);
} // Custom proxy to avoid jQuery's guid
function proxy(fn, context) {
var args = toArray(arguments, 2); return function () {
return fn.apply(context, args.concat(toArray(arguments)));
};
} function isCrossOriginURL(url) {
var parts = url.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i);
return parts && (parts[1] !== location.protocol || parts[2] !== location.hostname || parts[3] !== location.port);
} function addTimestamp(url) { //加上时间搓
var timestamp = 'timestamp=' + (new Date()).getTime();
return (url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp);
} function inRange(source, target) { //是否在范围内
return target.left < 0 && source.width < (target.left + target.width) && target.top < 0 && source.height < (target.top + target.height);
} function getRotateValue(degree) { //获取旋转的角度
return degree ? 'rotate(' + degree + 'deg)' : 'none';
} function getRotatedSizes(data, reverse) {
var deg = abs(data.degree) % 180,
arc = (deg > 90 ? (180 - deg) : deg) * Math.PI / 180,
sinArc = sin(arc),
cosArc = cos(arc),
width = data.width,
height = data.height,
aspectRatio = data.aspectRatio,
newWidth,
newHeight;
if (!reverse) { //如果反向的话
newWidth = width * cosArc + height * sinArc;
newHeight = width * sinArc + height * cosArc;
} else {
newWidth = width / (cosArc + sinArc / aspectRatio);
newHeight = newWidth / aspectRatio;
}
return {
width: newWidth,
height: newHeight
};
} function getSourceCanvas(image, data) {
var canvas = $('<canvas>')[0],
context = canvas.getContext('2d'),
width = data.naturalWidth,
height = data.naturalHeight,
rotate = data.rotate,
rotated = getRotatedSizes({
width: width,
height: height,
degree: rotate
}); if (rotate) {
canvas.width = rotated.width;
canvas.height = rotated.height;
context.save();
context.translate(rotated.width / 2, rotated.height / 2);
context.rotate(rotate * Math.PI / 180);
context.drawImage(image, -width / 2, -height / 2, width, height);
context.restore();
} else {
canvas.width = width;
canvas.height = height;
context.drawImage(image, 0, 0, width, height);
} return canvas;
} function Cropper(element, options) {
this.$element = $(element);
this.options = $.extend({}, Cropper.DEFAULTS, $.isPlainObject(options) && options); this.ready = false;
this.built = false;
this.rotated = false;
this.cropped = false;
this.disabled = false;
this.canvas = null;
this.cropBox = null; this.load();
} prototype.load = function (url) {
var options = this.options,
$this = this.$element,
crossOrigin,
bustCacheUrl,
buildEvent,
$clone; if (!url) {
if ($this.is('img')) {
if (!$this.attr('src')) { //没有src属性 就返回
return;
}
url = $this.prop('src');
//什么时候使用attr(),什么时候使用prop()?
//1.添加属性名称该属性就会生效应该使用prop();
//2.是有true,false两个属性使用prop();
//3.其他则使用attr();
} else if ($this.is('canvas') && SUPPORT_CANVAS) { //是画布并且支持画布
url =compress($this[0]);
}
}
if (!url) { //如果找不到地址 就返回
return;
} buildEvent = $.Event(EVENT_BUILD);
$this.one(EVENT_BUILD, options.build).trigger(buildEvent); // Only trigger once if (buildEvent.isDefaultPrevented()) {
return;
} if (options.checkImageOrigin && isCrossOriginURL(url)) {
crossOrigin = 'anonymous'; if (!$this.prop('crossOrigin')) { // Only when there was not a "crossOrigin" property 只有当没有“crossOrigin”属性
bustCacheUrl = addTimestamp(url); // Bust cache (#148)
}
} this.$clone = $clone = $('<img>'); $clone.one('load', $.proxy(function () {
var naturalWidth = $clone.prop('naturalWidth') || $clone.width(),
naturalHeight = $clone.prop('naturalHeight') || $clone.height(); this.image = {
naturalWidth: naturalWidth,
naturalHeight: naturalHeight,
aspectRatio: naturalWidth / naturalHeight,
rotate: 0
}; this.url = url;
this.ready = true;
this.build();
}, this)).one('error', function () {
$clone.remove();
}).attr({
src: bustCacheUrl || url,
crossOrigin: crossOrigin
}); // Hide and insert into the document
$clone.addClass(CLASS_HIDE).insertAfter($this);
}; prototype.build = function () {
var $this = this.$element,
$clone = this.$clone,
options = this.options,
$cropper,
$cropBox; if (!this.ready) {
return;
} if (this.built) {
this.unbuild();
} // Create cropper elements
this.$cropper = $cropper = $(Cropper.TEMPLATE); // Hide the original image
$this.addClass(CLASS_HIDDEN); // Show the clone iamge
$clone.removeClass(CLASS_HIDE); this.$container = $this.parent().append($cropper);
this.$canvas = $cropper.find('.cropper-canvas').append($clone);
this.$dragBox = $cropper.find('.cropper-drag-box');
this.$cropBox = $cropBox = $cropper.find('.cropper-crop-box');
this.$viewBox = $cropper.find('.cropper-view-box'); this.addListeners();
this.initPreview(); // Format aspect ratio
options.aspectRatio = num(options.aspectRatio) || NaN; // 0 -> NaN if (options.autoCrop) {
this.cropped = true; if (options.modal) {
this.$dragBox.addClass(CLASS_MODAL);
}
} else {
$cropBox.addClass(CLASS_HIDDEN);
} if (options.background) {
$cropper.addClass(CLASS_BG);
} if (!options.highlight) {
$cropBox.find('.cropper-face').addClass(CLASS_INVISIBLE);
} if (!options.guides) {
$cropBox.find('.cropper-dashed').addClass(CLASS_HIDDEN);
} if (!options.movable) {
$cropBox.find('.cropper-face').data('drag', 'move');
} if (!options.resizable) {
$cropBox.find('.cropper-line, .cropper-point').addClass(CLASS_HIDDEN);
} this.setDragMode(options.dragCrop ? 'crop' : 'move'); this.built = true;
this.render();
$this.one(EVENT_BUILT, options.built).trigger(EVENT_BUILT); // Only trigger once
}; prototype.unbuild = function () {
if (!this.built) {
return;
} this.built = false;
this.container = null;
this.canvas = null;
this.cropBox = null; // This is necessary when replace
this.removeListeners(); this.resetPreview();
this.$preview = null; this.$viewBox = null;
this.$cropBox = null;
this.$dragBox = null;
this.$canvas = null;
this.$container = null; this.$cropper.remove();
this.$cropper = null;
}; $.extend(prototype, {
render: function () {
this.initContainer();
this.initCanvas();
this.initCropBox(); this.renderCanvas(); if (this.cropped) {
this.renderCropBox();
}
}, initContainer: function () {
var $this = this.$element,
$container = this.$container,
$cropper = this.$cropper,
options = this.options; $cropper.addClass(CLASS_HIDDEN);
$this.removeClass(CLASS_HIDDEN); $cropper.css((this.container = {
width: max($container.width(), num(options.minContainerWidth) || 200),
height: max($container.height(), num(options.minContainerHeight) || 100)
})); $this.addClass(CLASS_HIDDEN);
$cropper.removeClass(CLASS_HIDDEN);
}, // image box (wrapper)
initCanvas: function () {
var container = this.container,
containerWidth = container.width,
containerHeight = container.height,
image = this.image,
aspectRatio = image.aspectRatio,
canvas = {
aspectRatio: aspectRatio,
width: containerWidth,
height: containerHeight
}; if (containerHeight * aspectRatio > containerWidth) {
canvas.height = containerWidth / aspectRatio;
} else {
canvas.width = containerHeight * aspectRatio;
} canvas.oldLeft = canvas.left = (containerWidth - canvas.width) / 2;
canvas.oldTop = canvas.top = (containerHeight - canvas.height) / 2; this.canvas = canvas;
this.limitCanvas(true, true);
this.initialImage = $.extend({}, image);
this.initialCanvas = $.extend({}, canvas);
}, limitCanvas: function (size, position) {
var options = this.options,
strict = options.strict,
container = this.container,
containerWidth = container.width,
containerHeight = container.height,
canvas = this.canvas,
aspectRatio = canvas.aspectRatio,
cropBox = this.cropBox,
cropped = this.cropped && cropBox,
minCanvasWidth,
minCanvasHeight; if (size) {
minCanvasWidth = num(options.minCanvasWidth) || 0;
minCanvasHeight = num(options.minCanvasHeight) || 0; if (minCanvasWidth) {
if (strict) {
minCanvasWidth = max(cropped ? cropBox.width : containerWidth, minCanvasWidth);
} minCanvasHeight = minCanvasWidth / aspectRatio;
} else if (minCanvasHeight) { if (strict) {
minCanvasHeight = max(cropped ? cropBox.height : containerHeight, minCanvasHeight);
} minCanvasWidth = minCanvasHeight * aspectRatio;
} else if (strict) {
if (cropped) {
minCanvasWidth = cropBox.width;
minCanvasHeight = cropBox.height; if (minCanvasHeight * aspectRatio > minCanvasWidth) {
minCanvasWidth = minCanvasHeight * aspectRatio;
} else {
minCanvasHeight = minCanvasWidth / aspectRatio;
}
} else {
minCanvasWidth = containerWidth;
minCanvasHeight = containerHeight; if (minCanvasHeight * aspectRatio > minCanvasWidth) {
minCanvasHeight = minCanvasWidth / aspectRatio;
} else {
minCanvasWidth = minCanvasHeight * aspectRatio;
}
}
} $.extend(canvas, {
minWidth: minCanvasWidth,
minHeight: minCanvasHeight,
maxWidth: Infinity,
maxHeight: Infinity
});
} if (position) {
if (strict) {
if (cropped) {
canvas.minLeft = min(cropBox.left, (cropBox.left + cropBox.width) - canvas.width);
canvas.minTop = min(cropBox.top, (cropBox.top + cropBox.height) - canvas.height);
canvas.maxLeft = cropBox.left;
canvas.maxTop = cropBox.top;
} else {
canvas.minLeft = min(0, containerWidth - canvas.width);
canvas.minTop = min(0, containerHeight - canvas.height);
canvas.maxLeft = max(0, containerWidth - canvas.width);
canvas.maxTop = max(0, containerHeight - canvas.height);
}
} else {
canvas.minLeft = -canvas.width;
canvas.minTop = -canvas.height;
canvas.maxLeft = containerWidth;
canvas.maxTop = containerHeight;
}
}
}, renderCanvas: function (changed) {
var options = this.options,
canvas = this.canvas,
image = this.image,
aspectRatio,
rotated; if (this.rotated) {
this.rotated = false; // Computes rotatation sizes with image sizes
rotated = getRotatedSizes({
width: image.width,
height: image.height,
degree: image.rotate
}); aspectRatio = rotated.width / rotated.height; if (aspectRatio !== canvas.aspectRatio) {
canvas.left -= (rotated.width - canvas.width) / 2;
canvas.top -= (rotated.height - canvas.height) / 2;
canvas.width = rotated.width;
canvas.height = rotated.height;
canvas.aspectRatio = aspectRatio;
this.limitCanvas(true, false);
}
} if (canvas.width > canvas.maxWidth || canvas.width < canvas.minWidth) {
canvas.left = canvas.oldLeft;
} if (canvas.height > canvas.maxHeight || canvas.height < canvas.minHeight) {
canvas.top = canvas.oldTop;
} canvas.width = min(max(canvas.width, canvas.minWidth), canvas.maxWidth);
canvas.height = min(max(canvas.height, canvas.minHeight), canvas.maxHeight); this.limitCanvas(false, true); canvas.oldLeft = canvas.left = min(max(canvas.left, canvas.minLeft), canvas.maxLeft);
canvas.oldTop = canvas.top = min(max(canvas.top, canvas.minTop), canvas.maxTop); this.$canvas.css({
width: canvas.width,
height: canvas.height,
left: canvas.left,
top: canvas.top
}); this.renderImage(); if (this.cropped && options.strict && !inRange(this.container, canvas)) {
this.limitCropBox(true, true);
} if (changed) {
this.output();
}
}, renderImage: function () {
var canvas = this.canvas,
image = this.image,
reversed; if (image.rotate) {
reversed = getRotatedSizes({
width: canvas.width,
height: canvas.height,
degree: image.rotate,
aspectRatio: image.aspectRatio
}, true);
} $.extend(image, reversed ? {
width: reversed.width,
height: reversed.height,
left: (canvas.width - reversed.width) / 2,
top: (canvas.height - reversed.height) / 2
} : {
width: canvas.width,
height: canvas.height,
left: 0,
top: 0
}); this.$clone.css({
width: image.width,
height: image.height,
marginLeft: image.left,
marginTop: image.top,
transform: getRotateValue(image.rotate)
});
}, initCropBox: function () {
var options = this.options,
canvas = this.canvas,
aspectRatio = options.aspectRatio,
autoCropArea = num(options.autoCropArea) || 0.8,
cropBox = {
width: canvas.width,
height: canvas.height
}; if (aspectRatio) {
if (canvas.height * aspectRatio > canvas.width) {
cropBox.height = cropBox.width / aspectRatio;
} else {
cropBox.width = cropBox.height * aspectRatio;
}
} this.cropBox = cropBox;
this.limitCropBox(true, true); // Initialize auto crop area
cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth);
cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight); // The width of auto crop area must large than "minWidth", and the height too. (#164)
cropBox.width = max(cropBox.minWidth, cropBox.width * autoCropArea);
cropBox.height = max(cropBox.minHeight, cropBox.height * autoCropArea);
cropBox.oldLeft = cropBox.left = canvas.left + (canvas.width - cropBox.width) / 2;
cropBox.oldTop = cropBox.top = canvas.top + (canvas.height - cropBox.height) / 2; this.initialCropBox = $.extend({}, cropBox);
}, limitCropBox: function (size, position) {
var options = this.options,
strict = options.strict,
container = this.container,
containerWidth = container.width,
containerHeight = container.height,
canvas = this.canvas,
cropBox = this.cropBox,
aspectRatio = options.aspectRatio,
minCropBoxWidth,
minCropBoxHeight; if (size) {
minCropBoxWidth = num(options.minCropBoxWidth) || 0;
minCropBoxHeight = num(options.minCropBoxHeight) || 0; // min/maxCropBoxWidth/Height must less than conatiner width/height
cropBox.minWidth = min(containerWidth, minCropBoxWidth);
cropBox.minHeight = min(containerHeight, minCropBoxHeight);
cropBox.maxWidth = min(containerWidth, strict ? canvas.width : containerWidth);
cropBox.maxHeight = min(containerHeight, strict ? canvas.height : containerHeight); if (aspectRatio) {
// compare crop box size with container first
if (cropBox.maxHeight * aspectRatio > cropBox.maxWidth) {
cropBox.minHeight = cropBox.minWidth / aspectRatio;
cropBox.maxHeight = cropBox.maxWidth / aspectRatio;
} else {
cropBox.minWidth = cropBox.minHeight * aspectRatio;
cropBox.maxWidth = cropBox.maxHeight * aspectRatio;
}
} // The "minWidth" must be less than "maxWidth", and the "minHeight" too.
cropBox.minWidth = min(cropBox.maxWidth, cropBox.minWidth);
cropBox.minHeight = min(cropBox.maxHeight, cropBox.minHeight);
} if (position) {
if (strict) {
cropBox.minLeft = max(0, canvas.left);
cropBox.minTop = max(0, canvas.top);
cropBox.maxLeft = min(containerWidth, canvas.left + canvas.width) - cropBox.width;
cropBox.maxTop = min(containerHeight, canvas.top + canvas.height) - cropBox.height;
} else {
cropBox.minLeft = 0;
cropBox.minTop = 0;
cropBox.maxLeft = containerWidth - cropBox.width;
cropBox.maxTop = containerHeight - cropBox.height;
}
}
}, renderCropBox: function () {
var options = this.options,
container = this.container,
containerWidth = container.width,
containerHeight = container.height,
$cropBox = this.$cropBox,
cropBox = this.cropBox; if (cropBox.width > cropBox.maxWidth || cropBox.width < cropBox.minWidth) {
cropBox.left = cropBox.oldLeft;
} if (cropBox.height > cropBox.maxHeight || cropBox.height < cropBox.minHeight) {
cropBox.top = cropBox.oldTop;
} cropBox.width = min(max(cropBox.width, cropBox.minWidth), cropBox.maxWidth);
cropBox.height = min(max(cropBox.height, cropBox.minHeight), cropBox.maxHeight); this.limitCropBox(false, true); cropBox.oldLeft = cropBox.left = min(max(cropBox.left, cropBox.minLeft), cropBox.maxLeft);
cropBox.oldTop = cropBox.top = min(max(cropBox.top, cropBox.minTop), cropBox.maxTop); if (options.movable) {
$cropBox.find('.cropper-face').data('drag', (cropBox.width === containerWidth && cropBox.height === containerHeight) ? 'move' : 'all');
} $cropBox.css({
width: cropBox.width,
height: cropBox.height,
left: cropBox.left,
top: cropBox.top
}); if (this.cropped && options.strict && !inRange(container, this.canvas)) {
this.limitCanvas(true, true);
} if (!this.disabled) {
this.output();
}
}, output: function () {
var options = this.options; this.preview(); if (options.crop) {
options.crop.call(this.$element, this.getData());
}
}
}); prototype.initPreview = function () {
var url = this.url; this.$preview = $(this.options.preview);
this.$viewBox.html('<img src="' + url + '">'); // Override img element styles
// Add `display:block` to avoid margin top issue (Occur only when margin-top <= -height)
this.$preview.each(function () {
var $this = $(this); $this.data(CROPPER_PREVIEW, {
width: $this.width(),
height: $this.height(),
original: $this.html()
}).html('<img src="' + url + '" style="display:block;width:100%;min-width:0!important;min-height:0!important;max-width:none!important;max-height:none!important;image-orientation: 0deg!important">');
});
}; prototype.resetPreview = function () {
this.$preview.each(function () {
var $this = $(this); $this.html($this.data(CROPPER_PREVIEW).original).removeData(CROPPER_PREVIEW);
});
}; prototype.preview = function () {
var image = this.image,
canvas = this.canvas,
cropBox = this.cropBox,
width = image.width,
height = image.height,
left = cropBox.left - canvas.left - image.left,
top = cropBox.top - canvas.top - image.top,
rotate = image.rotate; if (!this.cropped || this.disabled) {
return;
} this.$viewBox.find('img').css({
width: width,
height: height,
marginLeft: -left,
marginTop: -top,
transform: getRotateValue(rotate)
}); this.$preview.each(function () {
var $this = $(this),
data = $this.data(CROPPER_PREVIEW),
ratio = data.width / cropBox.width,
newWidth = data.width,
newHeight = cropBox.height * ratio; if (newHeight > data.height) {
ratio = data.height / cropBox.height;
newWidth = cropBox.width * ratio;
newHeight = data.height;
} $this.width(newWidth).height(newHeight).find('img').css({
width: width * ratio,
height: height * ratio,
marginLeft: -left * ratio,
marginTop: -top * ratio,
transform: getRotateValue(rotate)
});
});
}; prototype.addListeners = function () {
var options = this.options; this.$element.on(EVENT_DRAG_START, options.dragstart).on(EVENT_DRAG_MOVE, options.dragmove).on(EVENT_DRAG_END, options.dragend).on(EVENT_ZOOM_IN, options.zoomin).on(EVENT_ZOOM_OUT, options.zoomout);
this.$cropper.on(EVENT_MOUSE_DOWN, $.proxy(this.dragstart, this)).on(EVENT_DBLCLICK, $.proxy(this.dblclick, this)); if (options.zoomable && options.mouseWheelZoom) {
this.$cropper.on(EVENT_WHEEL, $.proxy(this.wheel, this));
} $document.on(EVENT_MOUSE_MOVE, (this._dragmove = proxy(this.dragmove, this))).on(EVENT_MOUSE_UP, (this._dragend = proxy(this.dragend, this))); if (options.responsive) {
$window.on(EVENT_RESIZE, (this._resize = proxy(this.resize, this)));
}
}; prototype.removeListeners = function () {
var options = this.options; this.$element.off(EVENT_DRAG_START, options.dragstart).off(EVENT_DRAG_MOVE, options.dragmove).off(EVENT_DRAG_END, options.dragend).off(EVENT_ZOOM_IN, options.zoomin).off(EVENT_ZOOM_OUT, options.zoomout);
this.$cropper.off(EVENT_MOUSE_DOWN, this.dragstart).off(EVENT_DBLCLICK, this.dblclick); if (options.zoomable && options.mouseWheelZoom) {
this.$cropper.off(EVENT_WHEEL, this.wheel);
} $document.off(EVENT_MOUSE_MOVE, this._dragmove).off(EVENT_MOUSE_UP, this._dragend); if (options.responsive) {
$window.off(EVENT_RESIZE, this._resize);
}
}; $.extend(prototype, {
resize: function () {
var $container = this.$container,
container = this.container,
canvasData,
cropBoxData,
ratio; if (this.disabled) {
return;
} ratio = $container.width() / container.width; if (ratio !== 1 || $container.height() !== container.height) {
canvasData = this.getCanvasData();
cropBoxData = this.getCropBoxData(); this.render();
this.setCanvasData($.each(canvasData, function (i, n) {
canvasData[i] = n * ratio;
}));
this.setCropBoxData($.each(cropBoxData, function (i, n) {
cropBoxData[i] = n * ratio;
}));
}
}, dblclick: function () {
if (this.disabled) {
return;
} if (this.$dragBox.hasClass(CLASS_CROP)) {
this.setDragMode('move');
} else {
this.setDragMode('crop');
}
}, wheel: function (event) {
var e = event.originalEvent,
delta = 1; if (this.disabled) {
return;
} event.preventDefault(); if (e.deltaY) {
delta = e.deltaY > 0 ? 1 : -1;
} else if (e.wheelDelta) {
delta = -e.wheelDelta / 120;
} else if (e.detail) {
delta = e.detail > 0 ? 1 : -1;
} this.zoom(-delta * 0.1);
}, dragstart: function (event) {
var options = this.options,
originalEvent = event.originalEvent,
touches = originalEvent && originalEvent.touches,
e = event,
dragType,
dragStartEvent,
touchesLength; if (this.disabled) {
return;
} if (touches) {
touchesLength = touches.length; if (touchesLength > 1) {
if (options.zoomable && options.touchDragZoom && touchesLength === 2) {
e = touches[1];
this.startX2 = e.pageX;
this.startY2 = e.pageY;
dragType = 'zoom';
} else {
return;
}
} e = touches[0];
} dragType = dragType || $(e.target).data('drag'); if (REGEXP_DRAG_TYPES.test(dragType)) {
event.preventDefault(); dragStartEvent = $.Event(EVENT_DRAG_START, {
originalEvent: originalEvent,
dragType: dragType
}); this.$element.trigger(dragStartEvent); if (dragStartEvent.isDefaultPrevented()) {
return;
} this.dragType = dragType;
this.cropping = false;
this.startX = e.pageX;
this.startY = e.pageY; if (dragType === 'crop') {
this.cropping = true;
this.$dragBox.addClass(CLASS_MODAL);
}
}
}, dragmove: function (event) {
var options = this.options,
originalEvent = event.originalEvent,
touches = originalEvent && originalEvent.touches,
e = event,
dragType = this.dragType,
dragMoveEvent,
touchesLength; if (this.disabled) {
return;
} if (touches) {
touchesLength = touches.length; if (touchesLength > 1) {
if (options.zoomable && options.touchDragZoom && touchesLength === 2) {
e = touches[1];
this.endX2 = e.pageX;
this.endY2 = e.pageY;
} else {
return;
}
} e = touches[0];
} if (dragType) {
event.preventDefault(); dragMoveEvent = $.Event(EVENT_DRAG_MOVE, {
originalEvent: originalEvent,
dragType: dragType
}); this.$element.trigger(dragMoveEvent); if (dragMoveEvent.isDefaultPrevented()) {
return;
} this.endX = e.pageX;
this.endY = e.pageY; this.change();
}
}, dragend: function (event) {
var dragType = this.dragType,
dragEndEvent; if (this.disabled) {
return;
} if (dragType) {
event.preventDefault(); dragEndEvent = $.Event(EVENT_DRAG_END, {
originalEvent: event.originalEvent,
dragType: dragType
}); this.$element.trigger(dragEndEvent); if (dragEndEvent.isDefaultPrevented()) {
return;
} if (this.cropping) {
this.cropping = false;
this.$dragBox.toggleClass(CLASS_MODAL, this.cropped && this.options.modal);
} this.dragType = '';
}
}
}); $.extend(prototype, {
reset: function () {
if (!this.built || this.disabled) {
return;
} this.image = $.extend({}, this.initialImage);
this.canvas = $.extend({}, this.initialCanvas);
this.renderCanvas(); if (this.cropped) {
this.cropBox = $.extend({}, this.initialCropBox);
this.renderCropBox();
}
}, clear: function () {
if (!this.cropped || this.disabled) {
return;
} $.extend(this.cropBox, {
left: 0,
top: 0,
width: 0,
height: 0
}); this.cropped = false;
this.renderCropBox(); this.limitCanvas();
this.renderCanvas(); // Render canvas after render crop box this.$dragBox.removeClass(CLASS_MODAL);
this.$cropBox.addClass(CLASS_HIDDEN);
}, destroy: function () {
var $this = this.$element; if (this.ready) {
this.unbuild();
$this.removeClass(CLASS_HIDDEN);
} else {
this.$clone.off('load').remove();
} $this.removeData('cropper');
}, replace: function (url) {
if (!this.disabled && url) {
this.load(url);
}
}, enable: function () {
if (this.built) {
this.disabled = false;
this.$cropper.removeClass(CLASS_DISABLED);
}
}, disable: function () {
if (this.built) {
this.disabled = true;
this.$cropper.addClass(CLASS_DISABLED);
}
}, move: function (offsetX, offsetY) {
var canvas = this.canvas; if (this.built && !this.disabled && isNumber(offsetX) && isNumber(offsetY)) {
canvas.left += offsetX;
canvas.top += offsetY;
this.renderCanvas(true);
}
}, zoom: function (delta) {
var canvas = this.canvas,
zoomEvent,
width,
height; delta = num(delta); if (delta && this.built && !this.disabled && this.options.zoomable) {
zoomEvent = delta > 0 ? $.Event(EVENT_ZOOM_IN) : $.Event(EVENT_ZOOM_OUT);
this.$element.trigger(zoomEvent); if (zoomEvent.isDefaultPrevented()) {
return;
} delta = delta <= -1 ? 1 / (1 - delta) : delta <= 1 ? (1 + delta) : delta;
width = canvas.width * delta;
height = canvas.height * delta;
canvas.left -= (width - canvas.width) / 2;
canvas.top -= (height - canvas.height) / 2;
canvas.width = width;
canvas.height = height;
this.renderCanvas(true);
this.setDragMode('move');
}
}, rotate: function (degree) {
var image = this.image; degree = num(degree); if (degree && this.built && !this.disabled && this.options.rotatable) {
image.rotate = (image.rotate + degree) % 360;
this.rotated = true;
this.renderCanvas(true);
}
}, getData: function () {
var cropBox = this.cropBox,
canvas = this.canvas,
image = this.image,
ratio,
data; if (this.built && this.cropped) {
data = {
x: cropBox.left - canvas.left,
y: cropBox.top - canvas.top,
width: cropBox.width,
height: cropBox.height
}; ratio = image.width / image.naturalWidth; $.each(data, function (i, n) {
n = n / ratio;
data[i] = n;
}); } else {
data = {
x: 0,
y: 0,
width: 0,
height: 0
};
} data.rotate = image.rotate; return data;
}, getContainerData: function () {
return this.built ? this.container : {};
}, getImageData: function () {
return this.ready ? this.image : {};
}, getCanvasData: function () {
var canvas = this.canvas,
data; if (this.built) {
data = {
left: canvas.left,
top: canvas.top,
width: canvas.width,
height: canvas.height
};
} return data || {};
}, setCanvasData: function (data) {
var canvas = this.canvas,
aspectRatio = canvas.aspectRatio; if (this.built && !this.disabled && $.isPlainObject(data)) {
if (isNumber(data.left)) {
canvas.left = data.left;
} if (isNumber(data.top)) {
canvas.top = data.top;
} if (isNumber(data.width)) {
canvas.width = data.width;
canvas.height = data.width / aspectRatio;
} else if (isNumber(data.height)) {
canvas.height = data.height;
canvas.width = data.height * aspectRatio;
} this.renderCanvas(true);
}
}, getCropBoxData: function () {
var cropBox = this.cropBox,
data; if (this.built && this.cropped) {
data = {
left: cropBox.left,
top: cropBox.top,
width: cropBox.width,
height: cropBox.height
};
} return data || {};
}, setCropBoxData: function (data) {
var cropBox = this.cropBox,
aspectRatio = this.options.aspectRatio; if (this.built && this.cropped && !this.disabled && $.isPlainObject(data)) { if (isNumber(data.left)) {
cropBox.left = data.left;
} if (isNumber(data.top)) {
cropBox.top = data.top;
} if (aspectRatio) {
if (isNumber(data.width)) {
cropBox.width = data.width;
cropBox.height = cropBox.width / aspectRatio;
} else if (isNumber(data.height)) {
cropBox.height = data.height;
cropBox.width = cropBox.height * aspectRatio;
}
} else {
if (isNumber(data.width)) {
cropBox.width = data.width;
} if (isNumber(data.height)) {
cropBox.height = data.height;
}
} this.renderCropBox();
}
}, getCroppedCanvas: function (options) { //获取画布
var originalWidth,
originalHeight,
canvasWidth,
canvasHeight,
scaledWidth,
scaledHeight,
scaledRatio,
aspectRatio,
canvas,
context,
data; if (!this.built || !this.cropped || !SUPPORT_CANVAS) {
return;
} if (!$.isPlainObject(options)) {
options = {};
} data = this.getData();
originalWidth = data.width;
originalHeight = data.height;
aspectRatio = originalWidth / originalHeight; if ($.isPlainObject(options)) {
scaledWidth = options.width;
scaledHeight = options.height; if (scaledWidth) {
scaledHeight = scaledWidth / aspectRatio;
scaledRatio = scaledWidth / originalWidth;
} else if (scaledHeight) {
scaledWidth = scaledHeight * aspectRatio;
scaledRatio = scaledHeight / originalHeight;
}
} canvasWidth = scaledWidth || originalWidth;
canvasHeight = scaledHeight || originalHeight; canvas = $('<canvas>')[0];
canvas.width = canvasWidth;
canvas.height = canvasHeight;
context = canvas.getContext('2d'); if (options.fillColor) {
context.fillStyle = options.fillColor;
context.fillRect(0, 0, canvasWidth, canvasHeight);
} // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage
context.drawImage.apply(context, (function () {
var source = getSourceCanvas(this.$clone[0], this.image),
sourceWidth = source.width,
sourceHeight = source.height,
args = [source],
srcX = data.x, // source canvas
srcY = data.y,
srcWidth,
srcHeight,
dstX, // destination canvas
dstY,
dstWidth,
dstHeight; if (srcX <= -originalWidth || srcX > sourceWidth) {
srcX = srcWidth = dstX = dstWidth = 0;
} else if (srcX <= 0) {
dstX = -srcX;
srcX = 0;
srcWidth = dstWidth = min(sourceWidth, originalWidth + srcX);
} else if (srcX <= sourceWidth) {
dstX = 0;
srcWidth = dstWidth = min(originalWidth, sourceWidth - srcX);
} if (srcWidth <= 0 || srcY <= -originalHeight || srcY > sourceHeight) {
srcY = srcHeight = dstY = dstHeight = 0;
} else if (srcY <= 0) {
dstY = -srcY;
srcY = 0;
srcHeight = dstHeight = min(sourceHeight, originalHeight + srcY);
} else if (srcY <= sourceHeight) {
dstY = 0;
srcHeight = dstHeight = min(originalHeight, sourceHeight - srcY);
} args.push(srcX, srcY, srcWidth, srcHeight); // Scale destination sizes
if (scaledRatio) {
dstX *= scaledRatio;
dstY *= scaledRatio;
dstWidth *= scaledRatio;
dstHeight *= scaledRatio;
} // Avoid "IndexSizeError" in IE and Firefox
if (dstWidth > 0 && dstHeight > 0) {
args.push(dstX, dstY, dstWidth, dstHeight);
} return args;
}).call(this)); return canvas;
}, setAspectRatio: function (aspectRatio) {
var options = this.options; if (!this.disabled && !isUndefined(aspectRatio)) {
options.aspectRatio = num(aspectRatio) || NaN; // 0 -> NaN if (this.built) {
this.initCropBox(); if (this.cropped) {
this.renderCropBox();
}
}
}
}, setDragMode: function (mode) {
var $dragBox = this.$dragBox,
cropable = false,
movable = false; if (!this.ready || this.disabled) {
return;
} switch (mode) {
case 'crop':
if (this.options.dragCrop) {
cropable = true;
$dragBox.data('drag', mode);
} else {
movable = true;
} break; case 'move':
movable = true;
$dragBox.data('drag', mode); break; default:
$dragBox.removeData('drag');
} $dragBox.toggleClass(CLASS_CROP, cropable).toggleClass(CLASS_MOVE, movable);
}
}); prototype.change = function () {
var dragType = this.dragType,
options = this.options,
canvas = this.canvas,
container = this.container,
cropBox = this.cropBox,
width = cropBox.width,
height = cropBox.height,
left = cropBox.left,
top = cropBox.top,
right = left + width,
bottom = top + height,
minLeft = 0,
minTop = 0,
maxWidth = container.width,
maxHeight = container.height,
renderable = true,
aspectRatio = options.aspectRatio,
range = {
x: this.endX - this.startX,
y: this.endY - this.startY
},
offset; if (options.strict) {
minLeft = cropBox.minLeft;
minTop = cropBox.minTop;
maxWidth = minLeft + min(container.width, canvas.width);
maxHeight = minTop + min(container.height, canvas.height);
} if (aspectRatio) {
range.X = range.y * aspectRatio;
range.Y = range.x / aspectRatio;
} switch (dragType) {
// Move cropBox
case 'all':
left += range.x;
top += range.y;
break; // Resize cropBox
case 'e':
if (range.x >= 0 && (right >= maxWidth || aspectRatio && (top <= minTop || bottom >= maxHeight))) {
renderable = false;
break;
} width += range.x; if (aspectRatio) {
height = width / aspectRatio;
top -= range.Y / 2;
} if (width < 0) {
dragType = 'w';
width = 0;
} break; case 'n':
if (range.y <= 0 && (top <= minTop || aspectRatio && (left <= minLeft || right >= maxWidth))) {
renderable = false;
break;
} height -= range.y;
top += range.y; if (aspectRatio) {
width = height * aspectRatio;
left += range.X / 2;
} if (height < 0) {
dragType = 's';
height = 0;
} break; case 'w':
if (range.x <= 0 && (left <= minLeft || aspectRatio && (top <= minTop || bottom >= maxHeight))) {
renderable = false;
break;
} width -= range.x;
left += range.x; if (aspectRatio) {
height = width / aspectRatio;
top += range.Y / 2;
} if (width < 0) {
dragType = 'e';
width = 0;
} break; case 's':
if (range.y >= 0 && (bottom >= maxHeight || aspectRatio && (left <= minLeft || right >= maxWidth))) {
renderable = false;
break;
} height += range.y; if (aspectRatio) {
width = height * aspectRatio;
left -= range.X / 2;
} if (height < 0) {
dragType = 'n';
height = 0;
} break; case 'ne':
if (aspectRatio) {
if (range.y <= 0 && (top <= minTop || right >= maxWidth)) {
renderable = false;
break;
} height -= range.y;
top += range.y;
width = height * aspectRatio;
} else {
if (range.x >= 0) {
if (right < maxWidth) {
width += range.x;
} else if (range.y <= 0 && top <= minTop) {
renderable = false;
}
} else {
width += range.x;
} if (range.y <= 0) {
if (top > 0) {
height -= range.y;
top += range.y;
}
} else {
height -= range.y;
top += range.y;
}
} if (width < 0 && height < 0) {
dragType = 'sw';
height = 0;
width = 0;
} else if (width < 0) {
dragType = 'nw';
width = 0;
} else if (height < 0) {
dragType = 'se';
height = 0;
} break; case 'nw':
if (aspectRatio) {
if (range.y <= 0 && (top <= minTop || left <= minLeft)) {
renderable = false;
break;
} height -= range.y;
top += range.y;
width = height * aspectRatio;
left += range.X;
} else {
if (range.x <= 0) {
if (left > 0) {
width -= range.x;
left += range.x;
} else if (range.y <= 0 && top <= minTop) {
renderable = false;
}
} else {
width -= range.x;
left += range.x;
} if (range.y <= 0) {
if (top > 0) {
height -= range.y;
top += range.y;
}
} else {
height -= range.y;
top += range.y;
}
} if (width < 0 && height < 0) {
dragType = 'se';
height = 0;
width = 0;
} else if (width < 0) {
dragType = 'ne';
width = 0;
} else if (height < 0) {
dragType = 'sw';
height = 0;
} break; case 'sw':
if (aspectRatio) {
if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) {
renderable = false;
break;
} width -= range.x;
left += range.x;
height = width / aspectRatio;
} else {
if (range.x <= 0) {
if (left > 0) {
width -= range.x;
left += range.x;
} else if (range.y >= 0 && bottom >= maxHeight) {
renderable = false;
}
} else {
width -= range.x;
left += range.x;
} if (range.y >= 0) {
if (bottom < maxHeight) {
height += range.y;
}
} else {
height += range.y;
}
} if (width < 0 && height < 0) {
dragType = 'ne';
height = 0;
width = 0;
} else if (width < 0) {
dragType = 'se';
width = 0;
} else if (height < 0) {
dragType = 'nw';
height = 0;
} break; case 'se':
if (aspectRatio) {
if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) {
renderable = false;
break;
} width += range.x;
height = width / aspectRatio;
} else {
if (range.x >= 0) {
if (right < maxWidth) {
width += range.x;
} else if (range.y >= 0 && bottom >= maxHeight) {
renderable = false;
}
} else {
width += range.x;
} if (range.y >= 0) {
if (bottom < maxHeight) {
height += range.y;
}
} else {
height += range.y;
}
} if (width < 0 && height < 0) {
dragType = 'nw';
height = 0;
width = 0;
} else if (width < 0) {
dragType = 'sw';
width = 0;
} else if (height < 0) {
dragType = 'ne';
height = 0;
} break; // Move image
case 'move':
canvas.left += range.x;
canvas.top += range.y;
this.renderCanvas(true);
renderable = false;
break; // Scale image
case 'zoom':
this.zoom(function (x1, y1, x2, y2) {
var z1 = sqrt(x1 * x1 + y1 * y1),
z2 = sqrt(x2 * x2 + y2 * y2); return (z2 - z1) / z1;
}(
abs(this.startX - this.startX2),
abs(this.startY - this.startY2),
abs(this.endX - this.endX2),
abs(this.endY - this.endY2)
)); this.startX2 = this.endX2;
this.startY2 = this.endY2;
renderable = false;
break; // Crop image
case 'crop':
if (range.x && range.y) {
offset = this.$cropper.offset();
left = this.startX - offset.left;
top = this.startY - offset.top;
width = cropBox.minWidth;
height = cropBox.minHeight; if (range.x > 0) {
if (range.y > 0) {
dragType = 'se';
} else {
dragType = 'ne';
top -= height;
}
} else {
if (range.y > 0) {
dragType = 'sw';
left -= width;
} else {
dragType = 'nw';
left -= width;
top -= height;
}
} // Show the cropBox if is hidden
if (!this.cropped) {
this.cropped = true;
this.$cropBox.removeClass(CLASS_HIDDEN);
}
} break; // No default
} if (renderable) {
cropBox.width = width;
cropBox.height = height;
cropBox.left = left;
cropBox.top = top;
this.dragType = dragType; this.renderCropBox();
} // Override
this.startX = this.endX;
this.startY = this.endY;
}; $.extend(Cropper.prototype, prototype); Cropper.DEFAULTS = {
// Defines the aspect ratio of the crop box
// Type: Number
aspectRatio: NaN, // Defines the percentage of automatic cropping area when initializes
// Type: Number (Must large than 0 and less than 1)
autoCropArea: 0.8, // 80% // Outputs the cropping results.
// Type: Function
crop: null, // Add extra containers for previewing
// Type: String (jQuery selector)
preview: '', // Toggles
strict: true, // strict mode, the image cannot zoom out less than the container
responsive: true, // Rebuild when resize the window
checkImageOrigin: true, // Check if the target image is cross origin modal: true, // Show the black modal
guides: true, // Show the dashed lines for guiding
highlight: true, // Show the white modal to highlight the crop box
background: true, // Show the grid background autoCrop: true, // Enable to crop the image automatically when initialize
dragCrop: true, // Enable to create new crop box by dragging over the image
movable: true, // Enable to move the crop box
resizable: true, // Enable to resize the crop box
rotatable: true, // Enable to rotate the image
zoomable: true, // Enable to zoom the image
touchDragZoom: true, // Enable to zoom the image by wheeling mouse
mouseWheelZoom: true, // Enable to zoom the image by dragging touch // Dimensions
minCanvasWidth: 0,
minCanvasHeight: 0,
minCropBoxWidth: 0,
minCropBoxHeight: 0,
minContainerWidth: 200,
minContainerHeight: 100, // Events
build: null, // Function
built: null, // Function
dragstart: null, // Function
dragmove: null, // Function
dragend: null, // Function
zoomin: null, // Function
zoomout: null // Function
}; Cropper.setDefaults = function (options) {
$.extend(Cropper.DEFAULTS, options);
}; // Use the string compressor: Strmin (https://github.com/fengyuanchen/strmin)
Cropper.TEMPLATE = (function (source, words) {
words = words.split(',');
return source.replace(/\d+/g, function (i) {
return words[i];
});
})('<0 6="5-container"><0 6="5-canvas"></0><0 6="5-2-9" 3-2="move"></0><0 6="5-crop-9"><1 6="5-view-9"></1><1 6="5-8 8-h"></1><1 6="5-8 8-v"></1><1 6="5-face" 3-2="all"></1><1 6="5-7 7-e" 3-2="e"></1><1 6="5-7 7-n" 3-2="n"></1><1 6="5-7 7-w" 3-2="w"></1><1 6="5-7 7-s" 3-2="s"></1><1 6="5-4 4-e" 3-2="e"></1><1 6="5-4 4-n" 3-2="n"></1><1 6="5-4 4-w" 3-2="w"></1><1 6="5-4 4-s" 3-2="s"></1><1 6="5-4 4-ne" 3-2="ne"></1><1 6="5-4 4-nw" 3-2="nw"></1><1 6="5-4 4-sw" 3-2="sw"></1><1 6="5-4 4-se" 3-2="se"></1></0></0>', 'div,span,drag,data,point,cropper,class,line,dashed,box'); /* Template source:
<div class="cropper-container">
<div class="cropper-canvas"></div>
<div class="cropper-drag-box" data-drag="move"></div>
<div class="cropper-crop-box">
<span class="cropper-view-box"></span>
<span class="cropper-dashed dashed-h"></span>
<span class="cropper-dashed dashed-v"></span>
<span class="cropper-face" data-drag="all"></span>
<span class="cropper-line line-e" data-drag="e"></span>
<span class="cropper-line line-n" data-drag="n"></span>
<span class="cropper-line line-w" data-drag="w"></span>
<span class="cropper-line line-s" data-drag="s"></span>
<span class="cropper-point point-e" data-drag="e"></span>
<span class="cropper-point point-n" data-drag="n"></span>
<span class="cropper-point point-w" data-drag="w"></span>
<span class="cropper-point point-s" data-drag="s"></span>
<span class="cropper-point point-ne" data-drag="ne"></span>
<span class="cropper-point point-nw" data-drag="nw"></span>
<span class="cropper-point point-sw" data-drag="sw"></span>
<span class="cropper-point point-se" data-drag="se"></span>
</div>
</div>
*/ // Save the other cropper
Cropper.other = $.fn.cropper; // Register as jQuery plugin
$.fn.cropper = function (options) {
var args = toArray(arguments, 1),
result; this.each(function () {
var $this = $(this),
data = $this.data('cropper'),
fn; if (!data) {
$this.data('cropper', (data = new Cropper(this, options)));
} if (typeof options === 'string' && $.isFunction((fn = data[options]))) {
result = fn.apply(data, args);
}
}); return isUndefined(result) ? this : result;
}; $.fn.cropper.Constructor = Cropper;
$.fn.cropper.setDefaults = Cropper.setDefaults; // No conflict
$.fn.cropper.noConflict = function () {
$.fn.cropper = Cropper.other;
return this;
}; function compress(img) { //图片压缩的方法
var initSize = img.src.length;
var width = img.width;
var height = img.height;
//如果图片大于四百万像素,计算压缩比并将大小压至400万以下
var ratio;
if ((ratio = width * height / 4000000)>1) {
ratio = Math.sqrt(ratio);
width /= ratio;
height /= ratio;
}else {
ratio = 1;
} canvas.width = width;
canvas.height = height;
// 铺底色
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, canvas.width, canvas.height); //如果图片像素大于100万则使用瓦片绘制
var count;
if ((count = width * height / 1000000) > 1) {
count = ~~(Math.sqrt(count)+1); //计算要分成多少块瓦片
// 计算每块瓦片的宽和高
var nw = ~~(width / count);
var nh = ~~(height / count);
tCanvas.width = nw;
tCanvas.height = nh;
for (var i = 0; i < count; i++) {
for (var j = 0; j < count; j++) {
tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh); ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh);
}
}
} else {
ctx.drawImage(img, 0, 0, width, height);
}
//进行最小压缩
var ndata = canvas.toDataURL('image/jpeg', 0.1);
console.log('压缩前:' + initSize);
console.log('压缩后:' + ndata.length);
console.log('压缩率:' + ~~(100 * (initSize - ndata.length) / initSize) + "%");
tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0;
return ndata;
} });
这里的copper.js插件原来用的是 toDataUrl()方法 由于前面提到的问题我将它改成了compress方法
在做这个项目时我发现微信和QQ浏览器base64位数据前面要截取的文档头长度不一致