刚到的这创业公司还真是忙啊,web前端就我一个,最近时间忙一个微信上的saas系统,都快一个月时间没空写博客了。项目快忙完了,也顺便总结下自己遇到的几个问题,这里说得是canvas实现切图

首先想到的是js如何操作图片数据呢,毕竟js没有读写函数,但查了下资料,html5有file api,可见还是要多去了解html5新技术的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<html>
<head>
</head>
<body>
<input type="file" value="" id="petImg-input" accept="image/*" style="display:none;"/>
<div id="image-cropper" style="display:none; z-index:10; position:absolute; top:0; bottom:0; left:0; right:0; background-color:black;">
<div id="image-area" style=" position:absolute; top:10px; left: 50%; -webkit-transform:translateX(-50%); ">
<img id="corp-img" style="max-width:650px; max-height:700px; display:block;">
<div id="corp-div" style="visibility:hidden; border:1px solid white; width:200px; height:200px; position:absolute; top:0; left:0; background-color: rgba(0, 0, 0, 0.2);">
<i id="resize-point" style="display:block; width:30px; height:30px; border:1px solid white; border-radius:15px; position:absolute; right:-15px; bottom:-15px"></i>
</div>
</div>
<canvas id="crop-canvas" width="200" height="200" style="position:absolute; display:none;"></canvas>
</div>
<div id="cropper-btns" style="position:absolute; z-index:12; bottom:30px; width:100%; height:80px; display:none;">
<span id="cropper-sure" style="display: block;width: 150px;height: 80px;line-height: 80px;text-align: center; font-size: 32px;color: white;float: left;">确定</span>
<span id="cropper-cancel" style="display: block;width: 150px;height: 80px;line-height: 80px;text-align: center; font-size: 32px;color: white;float: right;">取消</span>
</div>
<script type="text/javascript">
var imgInput = document.querySelector('#petImg-input');
var cropImg = document.querySelector('#corp-img');
imgInput.addEventListener('change', function(file){
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(){
//展示用图片
cropImg.src = reader.result;
//裁剪逻辑......
}
}, false);
</script>
</body>
</html>

FileReader对象允许访问Blob中的字符或字节,可以把它视为是BlobBuilder对应的一个对象。FileReader共有readAsText(), readAsArrayBuffer(), readAsDataURL(),readAsBinaryString()几个读取方法

但input type=file这种方式选取和读取本地文件在移动端却兼容性很差,很多手机上调不起本地图库。所以代替用微信jssdk解决图像选取这部分的问题。裁剪仍然用之前的逻辑。
微信图像接口主要有
wx.chooseImage({
count: 1, // 默认9
sizeType: [‘original’, ‘compressed’], // 可以指定是原图还是压缩图,默认二者都有
sourceType: [‘album’, ‘camera’], // 可以指定来源是相册还是相机,默认二者都有
success: function (res) {
var localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
}
});
返回的localIds只是一段普通的字符串,并不能借此获取图片源信息,也就不能裁剪,所以还得用
wx.uploadImage({
localId: ‘’, // 需要上传的图片的本地ID,由chooseImage接口获得
isShowProgressTips: 1, // 默认为1,显示进度提示
success: function (res) {
var serverId = res.serverId; // 返回图片的服务器端ID
}
});
将图片上传到微信服务器。但上传的图片有效期是三天,所以还需要用自己的服务器将图片存储下来
http请求方式: GET
http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID
调用微信多媒体下载接口就可以了。

整个流程出来了。先使用chooseImage之后在回调中调用uploadImage。之后请求后台自己的接口拉取微信图片服务器中的图片,并且返回base64字符串就行
注:这里有个坑!!!起初为了减少数据量传输,本来只打算返回cdn上的图片url就行,想通过canvas的getImageData获取图片base64信息,后来调这个方法总是报错,查了下,是有跨域的限制,原来canvas也有跨域的限制,学习了,果然浏览器对于安全的限制无处不在
有了base64信息,接下来就是裁剪了。裁剪方法也很简单:
1.将base64字符串赋值给corpimg.src作为展示;
2.使用oCropDiv作为裁剪框,加上拖动和拉伸改变大小的效果;
3.点击确定裁剪的时候,根据oCropDiv的大小和位置坐标,将图片的一部分drawImage到canvas上,再通过getImageData获取裁剪的图片的base64字符串,并且调用后台接口上传
完整代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
var maxWidth = 650, maxHeight=700, transformRatio=1;
var cropImg = $('#corp-img');
var cropCanvas = $('#crop-canvas');
var cropDiv = $('#corp-div');
var resizePoint = $('#resize-point');
var imageCropper = $('#image-cropper');
var imageArea = $('#image-area');
$('.petInfoInput-page .pet-Img').on('click', function(ev){
wx.chooseImage({
count: 1, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function (res) {
var localIds = res.localIds; //返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
//显示裁剪浮层
imageCropper.css('display','block');
//显示loading
util.loading();
wx.uploadImage({
localId: localIds[0], // 需要上传的图片的本地ID,由chooseImage接口获得
isShowProgressTips: 0, // 默认为1,显示进度提示
success: function (res) {
var serverId = res.serverId; // 返回图片的服务器端ID
util.ajaxData('/json/SAAS_H5_GetImageToWx', {type:2, mediaId:serverId}).done(function(data){ //1是个人,2是宠物
var corpimg = document.getElementById('corp-img');
corpimg.src = 'data:image/jpg;base64,' + data.body["img"].trim();
alert(corpimg.src);
corpimg.onload = function(){
util.loading(true);
var imgNw = cropImg[0].naturalWidth;
var imgNh = cropImg[0].naturalHeight;
if(imgNh>maxHeight || imgNw>maxWidth){ //图片被压缩了
if(imgNw>imgNh){
transformRatio = imgNw / maxWidth;
cropDiv.css({'width':imgNh/transformRatio, 'height':imgNh/transformRatio});
} else { //img压缩高度宽度不会被改变,需要自己调整
transformRatio = imgNh / maxHeight;
cropImg.css('width', imgNw/transformRatio);
cropDiv.css({'width':imgNw/transformRatio, 'height':imgNw/transformRatio});
}
} else { //需要拉伸图片
if(imgNw>imgNh){
transformRatio = imgNw/maxWidth;
cropImg.css({
'width': maxWidth,
'height': imgNh/transformRatio
});
cropDiv.css({'width':maxHeight, 'height':maxHeight});
} else {
transformRatio = imgNh/maxHeight;
cropImg.css({
'width': maxWidth/transformRatio,
'height': maxHeight
});
cropDiv.css({'width':maxHeight, 'height':maxHeight});
}
}
};
}).fail(function(){
util.toster('图片上传失败');
});
}
});
}
});
ev.stopImmediatePropagation();
ev.preventDefault();
});
var oImageArea = document.getElementById('image-area');
oImageArea.addEventListener('click', function(e){
var deltax = e.pageX - (360-imageArea.width()/2) - cropDiv.width()/2;
var deltay = e.pageY - cropDiv.height()/2;
cropDiv.css({
'left': deltax,
'top': deltay
});
e.stopImmediatePropagation();
e.preventDefault();
});
var sx,sy,nowleft,nowtop, oCropDiv=document.getElementById('corp-div');
oCropDiv.addEventListener('touchstart', function(e){
sx = e.changedTouches[0].pageX;
sy = e.changedTouches[0].pageY;
nowleft = parseFloat($(this).css('left').slice(0,-2));
nowtop = parseFloat($(this).css('top').slice(0,-2));
});
oCropDiv.addEventListener('touchmove', function(e){
var deltax = e.changedTouches[0].pageX - sx;
var deltay = e.changedTouches[0].pageY - sy;
// console.log(deltax + ' ' + deltay);
$(this).css({
'left': nowleft + deltax,
'top': nowtop + deltay
});
});
oCropDiv.addEventListener('touchend', function(e){
sx = e.changedTouches[0].pageX;
sy = e.changedTouches[0].pageY;
//检测边界问题
self.checkBound(cropDiv, imageArea, 1);
});
var px,py,nowwidth,nowheight, oResizePoint=document.getElementById('resize-point');
oResizePoint.addEventListener('touchstart', function(e){
px = e.changedTouches[0].pageX;
py = e.changedTouches[0].pageY;
nowwidth = $(cropDiv).width();
nowheight = $(cropDiv).height();
e.stopImmediatePropagation();
e.preventDefault();
});
oResizePoint.addEventListener('touchmove', function(e){
var deltax = e.changedTouches[0].pageX - px;
var deltay = e.changedTouches[0].pageY - py;
$(cropDiv).css({
'width': nowwidth + deltax,
'height': nowheight + deltay
});
e.stopImmediatePropagation();
e.preventDefault();
});
oResizePoint.addEventListener('touchend', function(e){
px = e.changedTouches[0].pageX;
py = e.changedTouches[0].pageY;
//检测边界问题
self.checkBound(cropDiv, imageArea, 2);
e.stopImmediatePropagation();
e.preventDefault();
});
// var canvas = document.getElementById('crop-canvas');
// var ctx = canvas.getContext('2d');
$('#cropper-sure').on('click', function(e){
util.loading(false);
//需要乘上devicePixelRatio,canvas坐标使用的是物理像素 * devicePixelRatio
var x = transformRatio * parseFloat($(cropDiv).css('left').slice(0,-2));
var y = transformRatio * parseFloat($(cropDiv).css('top').slice(0,-2));
var wid = transformRatio * $(cropDiv).width();
var hei = transformRatio * $(cropDiv).height();
// cropCanvas.attr({"width":wid, "height":hei});
var cropCan = document.getElementById("crop-canvas");
var cropImage = document.getElementById("corp-img");
cropImage.crossOrigin = "Anonymous";
cropCan.width = wid;
cropCan.height = hei;
var ctx = cropCan.getContext('2d');
ctx.drawImage(cropImage, x, y, wid, hei, 0, 0, wid, hei);
var data = cropCan.toDataURL();
alert("imgdata" + data);
$.ajax({
type: 'post',
url: '/imageupload',
data:{type:'petimg', base64:data},
dataType: 'json',
success: function(res){
if(res && res.code == 0){
util.toster('保存成功');
$('.petImg-show').attr('src', data); //设置显示
$('#image-cropper').css('display', 'none');
$('.petImg-show').attr('data-id', res.imageId);
self.checkChange(1);
} else {
util.toster('保存失败');
}
},
error: function(){
util.toster('保存失败');
},
complete: function(){
util.loading(true);
}
})
});
$('#cropper-cancel').on('click', function(){
setTimeout(function(){
$('#image-cropper').css('display', 'none');
}, 500);
});

其中还考虑了两个问题:
1.图像压缩的问题,限定最大宽maxWidth和高maxHeight,图片等比例压缩,计算出缩小或者拉伸的比例transformRatio。在canvas drawImage的时候要将cropdiv的坐标和高度乘上相应比例
2.边界超出处理,如果拉伸大小或者拖动超出边界,都直接回正