HTML5 的出现让我们可以更方便的实现这一需求。虽然这里所说的技术都貌似有点过时了(前端界的“过时”,你懂的),但还是有些许参考价值。在这里我只说一下要点,具体实现同学们慢慢研究。
下面奉上我自己写的一个demo,在输入框中选好自己服务器 url, 生成好图片后点击 Submit 上传,然后自己去服务器里看看效果吧~~
浏览器要求支持以下 Feature:
File API
Blob
atob
canvas
代码直接从现有项目移植过来,没有经过“太多的”测试,写的很乱,也没注释,大家就慢慢看吧。重点就在 js 脚本的 28 行,clipImage 函数中,同学们可以直接跳过去看。
js代码:
var tmp=$('<div class="resizer">'+ '<div class="inner">'+ '<img>'+ '<div class="frames"></div>'+ '</div>'+ //'<button>✗</button>'+ '<button class="ok">✓</button>'+ '</div>'); $.imageResizer=function(){ if(Uint8Array&&HTMLCanvasElement&&atob&&Blob){ }else{ return false; } var resizer=tmp.clone(); resizer.image=resizer.find('img')[0]; resizer.frames=resizer.find('.frames'); resizer.okButton=resizer.find('button.ok'); resizer.frames.offset={ top:0, left:0 }; resizer.okButton.click(function(){ resizer.clipImage(); }); resizer.clipImage=function(){ var nh=this.image.naturalHeight, nw=this.image.naturalWidth, size=nw>nh?nh:nw; size=size>1000?1000:size; var canvas=$('<canvas width="'+size+'" height="'+size+'"></canvas>')[0], ctx=canvas.getContext('2d'), scale=nw/this.offset.width, x=this.frames.offset.left*scale, y=this.frames.offset.top*scale, w=this.frames.offset.size*scale, h=this.frames.offset.size*scale; ctx.drawImage(this.image,x,y,w,h,0,0,size,size); var src=canvas.toDataURL(); this.canvas=canvas; this.append(canvas); this.addClass('uploading'); this.removeClass('have-img'); src=src.split(',')[1]; if(!src)return this.doneCallback(null); src=window.atob(src); var ia = new Uint8Array(src.length); for (var i = 0; i < src.length; i++) { ia[i] = src.charCodeAt(i); }; this.doneCallback(new Blob([ia], {type:"image/png"})); }; resizer.resize=function(file,done){ this.reset(); this.doneCallback=done; this.setFrameSize(0); this.frames.css({ top:0, left:0 }); var reader=new FileReader(); reader.onload=function(){ resizer.image.src=reader.result; reader=null; resizer.addClass('have-img'); resizer.setFrames(); }; reader.readAsDataURL(file); }; resizer.reset=function(){ this.image.src=''; this.removeClass('have-img'); this.removeClass('uploading'); this.find('canvas').detach(); }; resizer.setFrameSize=function(size){ this.frames.offset.size=size; return this.frames.css({ width:size+'px', height:size+'px' }); }; resizer.getDefaultSize=function(){ var width=this.find(".inner").width(), height=this.find(".inner").height(); this.offset={ width:width, height:height }; console.log(this.offset) return width>height?height:width; }; resizer.moveFrames=function(offset){ var x=offset.x, y=offset.y, top=this.frames.offset.top, left=this.frames.offset.left, size=this.frames.offset.size, width=this.offset.width, height=this.offset.height; if(x+size+left>width){ x=width-size; }else{ x=x+left; }; if(y+size+top>height){ y=height-size; }else{ y=y+top; }; x=x<0?0:x; y=y<0?0:y; this.frames.css({ top:y+'px', left:x+'px' }); this.frames.offset.top=y; this.frames.offset.left=x; }; (function(){ var time; function setFrames(){ var size=resizer.getDefaultSize(); resizer.setFrameSize(size); }; window.onresize=function(){ clearTimeout(time) time=setTimeout(function(){ setFrames(); },1000); }; resizer.setFrames=setFrames; })(); (function(){ var lastPoint=null; function getOffset(event){ event=event.originalEvent; var x,y; if(event.touches){ var touch=event.touches[0]; x=touch.clientX; y=touch.clientY; }else{ x=event.clientX; y=event.clientY; } if(!lastPoint){ lastPoint={ x:x, y:y }; }; var offset={ x:x-lastPoint.x, y:y-lastPoint.y } lastPoint={ x:x, y:y }; return offset; }; resizer.frames.on('touchstart mousedown',function(event){ getOffset(event); }); resizer.frames.on('touchmove mousemove',function(event){ if(!lastPoint)return; var offset=getOffset(event); resizer.moveFrames(offset); }); resizer.frames.on('touchend mouseup',function(event){ lastPoint=null; }); })(); return resizer; }; var resizer=$.imageResizer(), resizedImage; if(!resizer){ resizer=$("<p>Your browser doesn't support these feature:</p><ul><li>canvas</li><li>Blob</li><li>Uint8Array</li><li>FormData</li><li>atob</li></ul>") }; $('.container').append(resizer); $('input').change(function(event){ var file=this.files[0]; resizer.resize(file,function(file){ resizedImage=file; }); }); $('button.submit').click(function(){ var url=$('input.url').val(); if(!url||!resizedFile)return; var fd=new FormData(); fd.append('file',resizedFile); $.ajax({ type:'POST', url:url, data:fd }); });html代码:
<input type="file" accept="images/*"> <input class="url" type="url" placeholder="url"> <div class="container"></div> <button class="submit">Submit</button>css代码:
.container{ width: 300px; } .resizer{ overflow: hidden; } .resizer.have-img button.ok{ display: inline-block; } .resizer.have-img .inner { display: block; } .inner{ width: 100%; position: relative; font-size: 0; overflow: hidden; display: none; } img{ width: 100%; } .frames{ position: absolute; top: 0; left: 0; border: 1px solid black; cursor: move; outline: rgba(0, 0, 0, 0.6) solid 10000px; } button.ok{ float:right; margin-left: 5px; display: none; } canvas{ max-width: 100%; margin:auto; display: block; }
第一步:获取文件
HTML5 支持从 input[type=file] 元素中直接获取文件信息,也可以读取文件内容。我们用下面代码就可以实现:
$('input[type=file]').change(function(){ var file=this.files[0]; // continue ... });
第二步:读取文件,并生成 Image 元素
这一步就需要用到 FileReader 了,这个类是专门用来读取本地文件的。纯文本或者二进制都可以读取,但是本地文件必须是经过用户允许才能读取,也就是说用户要在input[type=file]中选择了这个文件,你才能读取到它。
通过 FileReader 我们可以将图片文件转化成 DataURL,就是以 data:image/png;base64, 开头的一种URL,然后可以直接放在 image.src 里,这样本地图片就显示出来了。
$('input[type=file]').change(function(){ var file=this.files[0]; var reader=new FileReader(); reader.onload=function(){ // 通过 reader.result 来访问生成的 DataURL var url=reader.result; setImageURL(url); }; reader.readAsDataURL(file); }); var image=new Image(); function setImageURL(url){ image.src=url; }Image 就是在 html 里的 <img> 标签,所以可以直接插入到文档流里。
第三步:获取裁剪坐标
这一步没啥好说的,实现的方法也很多,需要获得下面四个裁剪框的坐标:
Y坐标
X坐标
高度
宽度
如下图所示:
第四步:裁剪图片
这是时候我们就需要用到 canvas 了,canvas 和图片一样,所以新建 canvas 时就要确定其高宽。这里我们还运用到 image.naturalHeight 和 image.naturalWidth 这两个属性来获取图片原始尺寸。
将图片放置入 canvas 时需要调用 drawImage ,这个接口参数比较多,在 MDN 上有详细的说明。
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
因为我们用 canvas 只是用于裁剪图片的,所以需要新建一个 canvas 让它的尺寸和裁剪之后图片的尺寸相等,此时 canvas 就相当与我们的裁剪框。运用这个函数还可以将大图缩放成小图,同学们自己研究吧。
// 以下四个参数由第三步获得 var x, y, width, height; var canvas=$('<canvas width="'+width+'" height="'+height+'"></canvas>')[0], ctx=canvas.getContext('2d'); ctx.drawImage(image,x,y,width,height,0,0,width,height); $(document.body).append(canvas);将 canvas 加入文档流之后,就可以看到裁剪后的效果了。不过我们还需要将图片上传至服务器里。
第五步:读取裁剪后的图片并上传
这时我们要获取 canvas 中图片的信息,用 toDataURL 就可以转换成上面用到的 DataURL 。 然后取出其中 base64 信息,再用 window.atob 转换成由二进制字符串。但 window.atob 转换后的结果仍然是字符串,直接给 Blob 还是会出错。所以又要用 Uint8Array 转换一下。总之这里挺麻烦的。
var data=canvas.toDataURL(); // dataURL 的格式为 “data:image/png;base64,****”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了 data=data.split(',')[1]; data=window.atob(data); var ia = new Uint8Array(data.length); for (var i = 0; i < data.length; i++) { ia[i] = data.charCodeAt(i); }; // canvas.toDataURL 返回的默认格式就是 image/png var blob=new Blob([ia], {type:"image/png"});这时候裁剪后的文件就储存在 blob 里了,我们可以把它当作是普通文件一样,加入到 FormData 里,并上传至服务器了。
FormData 顾名思义,就是用来创建表单数据的,用 append 以键值的形式将数据加入进去即可。但他最大的特点就是可以手工添加文件或者 Blob 类型的数据,Blob 数据也会被当作文件来处理。原生 js 可以直接传递给 xhr.send(fd), jquery 可以放入 data 里请求。
var fd=new FormData(); fd.append('file',blob); $.ajax({ url:"your.server.com", type:"POST", data:fd, success:function(){} });然后你服务器里应该就可以收到这个文件了。这里,推荐大家一个html5图片裁切插件:Cropper.js