一.说明
1. Demo功能说明
前端基于js的选取图片,裁剪,预览,上传,可用于头像上传,或者其他地方的上传图片;
(1)高亮显示区为裁剪去,可拖动
(2)IOS竖向拍摄图片宽高数据相反的问题修复
(3)确认裁剪后预览,取消后需重新上传
(4)预览小图可删除,上有按钮
(5)点击上传可上传多张图片,返回图片链接
2.Demo地址
阿里云Demo地址:前端签名直传
我的Demo图片裁剪上传github地址:图片剪切上传至oss服务器
二.代码分析
1.上传模块
因为是基于阿里oss的demo,所以是引入的相应的js,然后初始化实例
var uploader = new plupload.Uploader({
runtimes: 'html5,flash,silverlight,html4',
browse_button: 'selectfiles',
//runtimes : 'flash',
container: document.getElementById('container'),
flash_swf_url: 'lib/plupload-2.1.2/js/Moxie.swf',
silverlight_xap_url: 'lib/plupload-2.1.2/js/Moxie.xap',
url: host,
// multi_selection:false,//是否可以多文件上传
multipart_params: {
// 'Filename': '${filename}',
'key': 'qph_test/activity_image/' + path,
'policy': policyBase64,
'OSSAccessKeyId': accessid,
'success_action_status': '200', //让服务端返回200,不然,默认会返回204
'signature': signature,
},
unique_names: true,
init: {
PostInit: function(e) {
document.getElementById('postfiles').onclick = function() {
set_upload_param(uploader, '');
// uploader.start();
return false;
};
},
BeforeUpload: function(up, file) {
set_upload_param(up, file.name)
console.log(file)
},
//添加文件后
FilesAdded: function(up, files) {
plupload.each(files, function(file) {
path = new Date().getTime() + random_string(10) + get_suffix(file.name);
})
},
//上传文件过程
UploadProgress: function(up, file) {
/*file.process为上传进度*/
},
//文件传输结束
FileUploaded: function(up, file, info) {
if(info.status >= 200 || info.status < 200) {
}
},
Error: function(up, err) {
}
}
});
//初始化
uploader.init();
其他的可以不管,我们如果要操作图片,那么就在FilesAdded这个函数里面,它返回了两个参数,我们用到的是那个
files,files是一个数组格式的数
据,
因为我们要操作图片,所以每次只允许操作一张,所以在这个函数刚进入就开始判断
files.length,大于1就返回false,重新选择。我们可以通过这个
files用
reader获取图片
var reader = new FileReader();
reader.readAsDataURL(files[0].getNative());
reader.onload = (function(e) {
//console.log(e.target.exif)
var image = new Image();
image.src = e.target.result;
image.id = "big";
//image.setAttribute("data-id",files[0].id); // 设置
image.onload = function() {//已经获取到了图片
}
})
2.裁剪模块
(1)裁剪页面设置
<div id="page2">
<div id="img_box">
<div class="mask"></div>
<div id="show"></div>
<div class="mask"></div>
</div>
<p class="btn_box">
<button id="sure" class="btn">确定</button>
<button id="quit" class="btn">取消</button>
</p>
</div>
page2是整个页面,图片加载成功之后就可以把page1隐藏,显示
page2,img_box用来放图片,高亮裁剪区show,遮罩区mask
简单的说一下图片怎么放,比例的问题,分三种情况
第一种,图片宽度大于高度,那么就让图片宽度平铺在页面,然后高度自适应,裁剪区为正方形(这是我们的业务需求),宽高为图片显示的高度
第二种,图片宽度小于高度,且,当图片宽度平铺时,高度小于屏幕高度,那么就让图片宽度平铺在页面,然后高度自适应,裁剪区为正方形(这是我们的业务需求),宽高为图片显示的宽度,也就是手机屏幕的宽度
第三种,图片宽度大于高度,且宽度平铺后高度大于一屏的高度,那么就让图片高度平铺在页面,然后宽度自适应,裁剪区为正方形(这是我们的业务需求),宽高为图片显示的宽度
由于IOS拍摄的图片,有方向Orientation属性,所以引入exif用来读取图片的原始数据。如果被翻转了90度,就用一个画布重绘图片,从而得到数据正常的图片,具体参考代码。
image.onload = function() {
imgw = this.width;
imgh = this.height;
bili = this.width / w;
img_box.appendChild(image)
img = document.getElementById("big");
EXIF.getData(image, function() {
Orientation = EXIF.getTag(this, 'Orientation');
});
if(Orientation!=1){
if(Orientation==6){//旋转90度
var x=imgh;
imgh=imgw;
imgw=x;
var step=1;//旋转几个90度
var ctx_x=0;//画图的原点位置
var ctx_y=-imgw;
}else if(Orientation==3){
alert("3")
var step=2;
var ctx_x=-imgw;
var ctx_y=-imgh;
}else if(Orientation==8){
alert("8")
var x=imgh;
imgh=imgw;
imgw=x;
var step=3;
var ctx_x=-imgh;
var ctx_y=0;
}
ctx.restore();//恢复状态
canvas.width = imgw;
canvas.height = imgh;
var degree = step * 90 * Math.PI / 180;
ctx.rotate(degree); //画布坐标旋转
ctx.drawImage(img, ctx_x, ctx_y);//画图
//替换img
img.parentNode.removeChild(img);
img_box.appendChild(convertCanvasToImage(canvas));
//宽高设置相反
img=img_box.getElementsByTagName("img")[0]
img.id="big"
}
if(imgw > imgh) {
img_flag = false;
img_type = 1;
bili = imgw / w;
} else {
if((imgh * w / imgw) < h) {
img_flag = true;
img_type = 2;
bili = imgw / w;
} else {
img_flag = true;
img_type = 3;
bili = imgh / h;
}
}
console.log(img_type)
page1.style.display = "none";
page2.style.display = "block";
set_page2();//设置显示区
set_mask();//设置遮罩层
var obj_div=document.createElement("div")
var obj_span=document.createElement("span")
obj_span.id=files[0].id;
obj_span.setAttribute("class","delete")
obj_div.appendChild(obj_span)
ossfile.appendChild(obj_div)
img.setAttribute("data-id",files[0].id); // 设置
};
遮罩层的设置
还有设置遮罩层的大小和位置,只要是横向平铺的图,遮罩层就是在高亮可视区的左右两边,左边定位left为0,右边定位right为0,左边的宽度为可视区的 offsetleft,右边的宽度为图片宽度也就是品目宽度w减去可视区宽度再减去可视区的offsetleft,竖向同理
function set_mask(){
if(img_flag) { //竖向
masks[0].style.width = "100%";
masks[1].style.width = "100%";
masks[0].style.height = show.offsetTop + "px";
masks[1].style.height = ((imgh / bili) - show.offsetTop - show.offsetWidth) + "px";
masks[1].style.right = 'auto';
masks[1].style.top = 'auto';
masks[1].style.bottom = 0;
masks[1].style.left = 0;
//masks[1].style.top=(show.offsetWidth)+"px";;
} else {
masks[0].style.height = "100%";
masks[1].style.height = "100%";
masks[0].style.width = show.offsetLeft + "px";
masks[1].style.width = (w - show.offsetLeft - (imgh / bili)) + "px";
masks[1].style.top = 0;
masks[1].style.right = 0;
masks[1].style.left = 'auto';
masks[1].style.bottom = 'auto';
}
}
页面其他设置
function set_page2() {
if(img_type == 3) { //竖图
img_box.style.height = "100%";
img_box.style.width = (imgw / bili) + "px";
img.style.height = "100%";
show.style.width = (imgw / bili) + "px";
show.style.height = (imgw / bili) + "px";
show.style.left = 0;
show.style.top = 0;
show_l = imgw / bili;
position = {
"x": 0,
'y': 0,
'w': (imgw)
}
} else if(img_type == 1) { //横图
img.style.width = "100%";
img_box.style.width = "100%";
img_box.style.height = (imgh / bili) + "px";
show.style.height = (imgh / bili) + "px";
show.style.width = (imgh / bili) + "px";
show.style.left = 0;
show.style.top = 0;
show_l = imgh / bili;
position = {
"x": 0,
'y': 0,
'w': (imgh)
}
} else if(img_type == 2) { //竖图
img.style.width = "100%";
img_box.style.width = "100%";
img_box.style.height = (imgh / bili) + "px";
show.style.height = w + "px";
show.style.width = w + "px";
show.style.left = 0;
show.style.top = 0;
show_l = w;
position = {
"x": 0,
'y': 0,
'w': (imgw)
}
}
}
2.拖拽获取坐标
window.onload = function() {
var startx, starty;
show.addEventListener("touchstart", function(e) {
startx = e.touches[0].pageX - show.offsetLeft;
starty = e.touches[0].pageY - show.offsetTop;
show.addEventListener("touchmove", function(e) {
old_x = e.targetTouches[0].pageX;
old_y = e.targetTouches[0].pageY;
l = old_x - startx;
t = old_y - starty;
if(img_flag) { //竖
l = 0;
if(t > ((imgh / bili) - show_l)) {
t = ((imgh / bili) - show_l);
}
if(t < 0) {
t = 0;
}
show.style.top = t + "px"
} else {
t = 0;
if(l > (w - show_l)) {
l = (w - show_l)
}
if(l < 0) {
l = 0
}
show.style.left = l + "px"
}
set_mask()
})
})
show.addEventListener("touchend", function(e) {
if(img_flag) {
position = {
"x": (show.offsetLeft * bili),
'y': (show.offsetTop * bili),
'w': (imgw)
}
} else {
position = {
"x": (show.offsetLeft * bili),
'y': (show.offsetTop * bili),
'w': (imgh)
}
}
})
btn_sure.onclick = function() {
page1.style.display = "block";
page2.style.display = "none";
canvas.width = imgw;
canvas.height = imgw;
//绘制图片
ctx.drawImage(img, position.x, position.y, position.w, position.w, 0, 0, imgw, imgw)
//预览
//obj_div.appendChild(convertCanvasToImage(canvas))
//obj_div.appendChild(convertCanvasToImage(canvas))
document.getElementById(img.getAttribute("data-id")).parentNode.appendChild(convertCanvasToImage(canvas));
var img_num = document.getElementById('ossfile').getElementsByTagName("img").length;
if(img_num == 3) {
console.log("3个")
selectfiles.parentNode.style.display = "none"
}
};
通过touch事件获取触摸区到屏幕的距离,获得移动的距离,控制可视区的移动,离开屏幕时记录坐标和缩放比例,同时可以用画布重绘图片来进行预览
剪切图像,并在画布上定位被剪切的部分:
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
参数值
参数 | 描述 |
img | 规定要使用的图像、画布或视频。 |
sx | 可选。开始剪切的 x 坐标位置。 |
sy | 可选。开始剪切的 y 坐标位置。 |
swidth | 可选。被剪切图像的宽度。 |
sheight | 可选。被剪切图像的高度。 |
x | 在画布上放置图像的 x 坐标位置。 |
y | 在画布上放置图像的 y 坐标位置。 |
width | 可选。要使用的图像的宽度。(伸展或缩小图像) |
height | 可选。要使用的图像的高度。(伸展或缩小图像) |
3.截取页面点击取消,预览图点击删除
这部分就是,首先要把uploader.files这个数组,也就是放置文件的数组,把对应的数据删掉,可通过将file.id绑定在DOM对象的自定义
属性上来达到操作的目的,然后把对应的dom对象删掉就可以了;