一.说明

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

简单的说一下图片怎么放,比例的问题,分三种情况

第一种,图片宽度大于高度,那么就让图片宽度平铺在页面,然后高度自适应,裁剪区为正方形(这是我们的业务需求),宽高为图片显示的高度

javascript 上传image到某个目录 js上传图片到服务器_上传

第二种,图片宽度小于高度,且,当图片宽度平铺时,高度小于屏幕高度,那么就让图片宽度平铺在页面,然后高度自适应,裁剪区为正方形(这是我们的业务需求),宽高为图片显示的宽度,也就是手机屏幕的宽度

javascript 上传image到某个目录 js上传图片到服务器_js_02

第三种,图片宽度大于高度,且宽度平铺后高度大于一屏的高度,那么就让图片高度平铺在页面,然后宽度自适应,裁剪区为正方形(这是我们的业务需求),宽高为图片显示的宽度

javascript 上传image到某个目录 js上传图片到服务器_宽高_03

由于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对象删掉就可以了;