基於Jcrop做客戶端無上傳圖片剪裁

首先要說,jcrop做剪裁,是很強大的,也有很多實現了。不過,對於這些實現,都有個缺點,無法做到不經上傳的客戶端實時預覽與剪裁效果預覽(我是指兼容瀏覽器)。主要原因就是ie太蛋疼了。爲了更好地挖掘客戶端的性能,讓服務器壓力更小一些,我只好研究一下了。


慣例,先說原理:

1、jcrop做圖片選區。

2、ie基於濾鏡做預覽,其他瀏覽器基本是設置src,但獲取圖片源的方式略有不同。

3、利用overflow:hidden和margin負值做剪裁效果預覽。

4、實際上客戶端未做文件剪裁,但能得到響應剪裁信息,在服務端處理時剪裁。

5、ie基於濾鏡做預覽時,使用透明圖片覆蓋在div上方做jcrop的基礎(jcrop要基於img)。

6、由於涉及覆蓋,jcrop生成的背景div帶色,需要設置該顏色爲透明色。


上效果……

chrome下的效果:


ie下的效果:



ok,我還是懶人一個,看代碼:


照例先上js再上css,額,這次有個透明image,比較蛋疼。

先看引用吧:

<link rel="stylesheet" type="text/css" href="css/image.crop.css"/>
<link rel="stylesheet" type="text/css" href="css/jquery.Jcrop.min.css"/>
<script type="text/javascript" src="js/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="js/jquery.Jcrop.min.js"></script>
<script type="text/javascript" src="js/jquery.imagecrop.croper.js"></script>
<script type="text/javascript" src="js/jquery.imagecrop.previewer.js"></script>
第一個css是實現方案,的引用樣式。

第二個css是jcrop的引用樣式。

jquery不必說了……

jcrop的引用庫

後面兩個js就是本次實現的關鍵,croper.js是在jcrop的基礎上封裝一層。

previewer.js就是實現預覽的關鍵。


上js了……

先是croper.js:

if(typeof(jquery) == "undefined"){
	jquery = {};
}
if(typeof(jquery.imagecrop) == "undefined"){
	jquery.imagecrop = {};
}
/**
 * 截圖工具
 * @param opts 配置
 */
jquery.imagecrop.croper = function(opts){
	
	var configs = opts;
	
	var me = this;
	
	var jcrop;
	var select;
	var bounds;
	
	this.getCrop = function(){
		return jcrop;
	}
	
	this.tellSelect = function(){
		return select;
	}
	
	this.getBounds = function(){
		return bounds;
	}
	
	/**
	 * 執行選區預覽
	 * @param select
	 * @param bounds
	 */
	this.doPreview = function(select,bounds){
		var realWidth = bounds[0];
		var realHeight =  bounds[1];
		var previewContainerWidth = configs.previewContainer.width();
		var previewImgWidth = (configs.previewContainer.width() * realWidth) / select.w;

		configs.previewImg.attr("src", configs.orgImg.attr("src"));
		configs.previewImg.width(previewImgWidth);
		
		var marginLeft = (-1) * select.x * configs.previewImg.width() / realWidth;
		var marginTop = (-1) * select.y *  configs.previewImg.height() / realHeight;
		
		configs.previewImg.css("marginLeft", marginLeft);
		configs.previewImg.css("marginTop", marginTop);
		configs.previewImg.show();
	};
	/**
	 * 執行選取
	 * @param settings jcrop配置參數
	 */
	this.crop = function(settings){
		var cropSettings = {
			aspectRatio : configs.previewContainer.width() / configs.previewContainer.height(),
			onChange : function(){
				select = this.tellSelect();
				bounds = this.getBounds();
				me.doPreview(select,bounds);
			}
		};
		if(settings){
			$.extend(cropSettings,settings);
		}
		//jcrop = configs.orgImg.Jcrop(cropSettings);
		jcrop = $.Jcrop("#" + configs.orgImg.attr("id"), cropSettings);
	}
	/**
	 * 銷燬
	 */
	this.desdroy = function(){
		if(jcrop && jcrop.destroy){
			jcrop.destroy();
		}
	}
}
這個比較簡單,主要是定義了默認的預覽事件,兼容ie外的瀏覽器。

然後是previewer.js:

if(typeof(jquery) == "undefined"){
	jquery = {};
}
if(typeof(jquery.imagecrop) == "undefined"){
	jquery.imagecrop = {};
}
/**
 * 預覽器
 * @param file 用於測試是否支持html5
 */
jquery.imagecrop.previewer = function(file){
	var browserVersion = window.navigator.userAgent.toUpperCase();
	var jcroper;
	var me = this;
	var supportHTML5 = false;
	
	var html =  '<span class="image-crop-container">' +
					'<fieldset class="image-crop-fieldset">' +
						'<legend>原圖</legend>' +
						'<div class="image-crop-org-container" id="image_crop_org_container">' +
							'<div id="image_crop_org_div" class="image-crop-org-div"></div>' +
							'<img id="image_crop_org_img" class="image-crop-org-img" src="images/touming.gif"/>' +
						'</div>' +
					'</fieldset>' +
					'<div class="image-crop-container">' +
						'<fieldset class="image-crop-fieldset simple-div">' +
							'<legend>縮略圖</legend>' +
							'<div class="image-crop-preview-container" id="image_crop_preview_container">' +
								'<div id="image_crop_preview_div"></div>' +
								'<img id="image_crop_preview_img" src="images/touming.gif"/>' +
							'</div>' +
						'</fieldset>' +
					'</div>' +
				'</span>';
	/**
	 * 判斷是否數組
	 * @param obj 參數對象
	 * @returns boolean
	 */
	var isArray = function (obj) {
		return Object.prototype.toString.call(obj) === '[object Array]';
	}
	/**
	 * 繼承
	 * @param a 基礎對象
	 * @param b 爲object時,繼承其所有屬性,爲array時,繼承其所有成員的所有屬性
	 * @return 組成對象
	 */
	var extend = function(a, b){
		if(a){
			if(isArray(b)){
				for(var i in b){
					var o = b[i];
					for(var p in o){
						a[p] = o[p];
					}
				}
			}else if(typeof(b) == "object"){
				for(var p in b){
					a[p] = b[p];
				}
			}
			return a;
		}
	}
	/**
	 * 默認配置
	 */
	var defaultConfig = {
		jcropBaseClass : "jcrop",
		fileChooser : file
	};
	
	/**
	 * 獲取jcrop對象
	 * @returns jcrop
	 */
	this.getCrop = function(){
		if(!jcroper){
			return undefined;
		}else{
			return jcroper.getCrop();
		}
	}
	
	/**
	 * 獲取選框的值(實際尺寸)
	 * @returns select{x,y,x2,y2,w,h}
	 */
	this.tellSelect = function(){
		if(!jcroper){
			return undefined;
		}else{
			return jcroper.tellSelect();
		}
	}
	
	/**
	 * 獲取選框的值(界面尺寸)
	 * @returns select{x,y,x2,y2,w,h}
	 */
	this.tellScaled = function(){
		if(!jcroper){
			return undefined;
		}else{
			return jcroper.getCrop().tellScaled();
		}
	}
	
	/**
	 * 獲取圖片實際尺寸
	 * @returns [w, h]
	 */
	this.getBounds = function(){
		if(!jcroper){
			return undefined;
		}else{
			return jcroper.getBounds();
		}
	}
	
	/**
	 * 獲取圖片顯示尺寸
	 * @returns [w, h]
	 */
	this.getWidgetSize = function(){
		if(!jcroper){
			return undefined;
		}else{
			return jcroper.getCrop().getWidgetSize();
		}
	}
	
	/**
	 * 獲取圖片縮放的比例
	 * @returns [w, h]
	 */
	this.getScaleFactor = function(){
		if(!jcroper){
			return undefined;
		}else{
			return jcroper.getCrop().getScaleFactor();
		}
	}
	
	
	/**
	 * 初始化
	 * @param container
	 * @param emptyImage
	 * @param file
	 */
	this.init = function(container, emptyImage){
		$(container).html(html);
		var c = {
			enterBtn : $("#image_crop_enter_btn"),
			cancelBtn : $("#image_crop_cancel_btn"),
			orgContainer : $("#image_crop_org_container"),
			orgDiv : $("#image_crop_org_div"),
			orgDivClass : "image-crop-org-div",
			orgImg : $("#image_crop_org_img"),
			orgImgClass : "image-crop-org-img",
			previewContainer : $("#image_crop_preview_container"),
			previewDiv : $("#image_crop_preview_div"),
			previewImg : $("#image_crop_preview_img"),
			emtpyImage : emptyImage
		};
		/**
		 * 繼承配置
		 */
		this.configs = $.extend(defaultConfig , c);
		jcroper = new jquery.imagecrop.croper(this.configs);
	}
	
	/**
	 * 銷燬,在預覽前調用,清除上一次預覽結果
	 */
	this.desdroy = function(){
		if(typeof(this.configs) == "undefined"){
			return;
		}
		var parent = this.configs.orgDiv.parent();
		//----------reset org div----------
		this.configs.orgDiv.remove();
		var divId = "image_crop_org_div_" + new Date().getTime();
		var divDom = document.createElement("div");
		divDom.id = divId;
		parent.append($(divDom));
		this.configs.orgDiv = $("#" + divId);
		this.configs.orgDiv.addClass(this.configs.orgDivClass);
		//----------reset org div----------
		//----------reset org img----------
		this.configs.orgImg.remove();
		var imgId = "image_crop_org_img_" + new Date().getTime();
		var imgDom = document.createElement("img");
		imgDom.id = imgId;
		parent.append($(imgDom));
		this.configs.orgImg = $("#" + imgId);
		this.configs.orgImg.addClass(this.configs.orgImgClass);
		//----------reset org img----------
		//----------reset preview div----------
		//通過濾鏡實現縮略圖預覽
		this.configs.previewDiv.css(
			"filter",
			"progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod='scale',src='" + this.configs.emptyImage + "')"
		);
		//----------reset preview div----------
		//----------reset preview img----------
		this.configs.previewImg.attr("src", this.configs.emtpyImage);
		//----------reset preview img----------
	}
	/**
	 * 預覽圖片
	 * @param file 選擇文件的input對象
	 */
	this.preview = function(file){
		//設置預覽
		this.configs.orgImg.height("auto");
		this.configs.orgImg.attr("src", file.value);
		//級聯變動
		this.configs.orgDiv.hide();
		this.configs.previewDiv.hide();
		this.configs.orgImg.show();
		this.configs.previewImg.show();
		//初始化截圖工具
		this.doCrop(file.value);
	}
	
	/**
	 * 對預覽圖片進行截圖初始化
	 * @param src 圖片源
	 * @param settings 可選參數,用於指定jcrop參數,但需在指定自定義doPreview時才其作用
	 * @param doPreview 可選參數,自定義doPreview,用於jcrop回調,方法入參:select,bounds。參見jcrop。
	 */
	this.doCrop = function(src, settings, doPreview){
		//如果當前已存在jcroper,則先銷燬重建
		if(jcroper){
			jcroper.desdroy();
		}
		$("." + this.configs.jcropBaseClass + "-holder").remove();
		jcroper = new jquery.imagecrop.croper(this.configs);
		//DIV預覽實現
		if(doPreview){
			jcroper.doPreview = doPreview;
			jcroper.crop(settings);
		//IMG預覽實現
		}else{
			jcroper.crop();
		}
	}
	if(file.files){
		supportHTML5 = true;
	}else{
		supportHTML5 = false;
	}
	//進行瀏覽器判斷,選擇對應的實現方式
	if(supportHTML5){
		return extend(this, new jquery.imagecrop.previewer.impls("html5Preview"));
	} else if (browserVersion.indexOf("MSIE") > -1 && browserVersion.indexOf("MSIE 6") > -1) {
		return extend(this, new jquery.imagecrop.previewer.impls("ie6Preview"));
	} else if (browserVersion.indexOf("MSIE") > -1 && browserVersion.indexOf("MSIE 6") <= -1) {
		return extend(this, new jquery.imagecrop.previewer.impls("ie7to9Preview"));
	} else if (browserVersion.indexOf("FIREFOX") > -1) {
		return extend(this, new jquery.imagecrop.previewer.impls("firefoxPreview"));
	} else{
		return this;
	}
}
/**
 * 不同瀏覽器的實現方式
 * @param name 實現方式名稱
 */
jquery.imagecrop.previewer.impls = function(name){
	var impls = {
		/**
		 * ie6實現
		 */
		ie6Preview : function(){
			var me = this;
			this.preview = function(file){
				//設置預覽
				this.configs.orgImg.height("auto");
				this.configs.orgImg.attr("src", file.value);
				//級聯變動
				this.configs.orgDiv.hide();
				this.configs.previewDiv.hide();
				this.configs.orgImg.show();
				this.configs.previewImg.show();
				//初始化截圖工具
				this.doCrop(file.value);
			}
			return this;
		},
		/**
		 * ie7+實現
		 */
		ie7to9Preview : function(){
			var me = this;
			/**
			 * 獲取圖片的實際大小
			 * @param tmpSrc 圖片源
			 * @return {w,h} 寬和高
			 */
			this.getImageBounds = function(tmpSrc){
				var imgObj = new Image();
				imgObj.src = tmpSrc;
				var width = imgObj.width;
				var height = imgObj.height;
				if((typeof width=="undefined" || width==0) && (typeof height=="undefined" || height==0)){
					var picpreview=document.getElementById("image_crop_org_container");
				    var tempDiv=document.createElement("div");
				    picpreview.appendChild(tempDiv);
				    tempDiv.style.width="10px";
				    tempDiv.style.height="10px";
				    tempDiv.style.diplay="none";
				    tempDiv.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod='image',src='" + tmpSrc + "');";
				    tempDiv.ID="previewTemp" + new Date().getTime();
				    width=tempDiv.offsetWidth;
				    height=tempDiv.offsetHeight;
				    picpreview.removeChild(tempDiv);
				}
				var w = me.configs.orgContainer.width();
				var h = height * w / width;
				return {w:w,h:h};
			};
			/**
			 * ie7+爲div預覽,需要實現自定義doPreview
			 * @param select 選區
			 * @param bounds 實際內容
			 */
			this.doPreview = function(select,bounds){
				var realWidth = bounds[0];
				var realHeight =  bounds[1];
				//計算預覽大小
				var previewContainerWidth = me.configs.previewContainer.width();
				var previewDivWidth = (me.configs.previewContainer.width() * realWidth) / select.w;
				var previewDivHeight = (me.configs.previewContainer.height() * realHeight) / select.h;
				//通過濾鏡實現縮略圖預覽
				me.configs.previewDiv.css(
					"filter",
					"progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod='scale',src='" + me.configs.orgImg.attr("realSrc") + "')"
				);
				//設定預覽大小
				me.configs.previewDiv.height(previewDivHeight);
				me.configs.previewDiv.width(previewDivWidth);
				//計算偏移
				var marginLeft = (-1) * select.x * me.configs.previewDiv.width() / realWidth;
				var marginTop = (-1) * select.y *  me.configs.previewDiv.height() / realHeight;
				//設置偏移實現剪切
				me.configs.previewDiv.css("marginLeft", marginLeft);
				me.configs.previewDiv.css("marginTop", marginTop);
				me.configs.previewDiv.show();
				me.configs.orgImg.show();
			}
			
			this.preview = function(file){
				me = this;
				file.select();
				var browserVersion = window.navigator.userAgent.toUpperCase();
				//如果是ie9需要觸發blur事件,避免被安全規則阻攔
				if (browserVersion.indexOf("MSIE 9") > -1){
					file.blur();// 不加上document.selection.createRange().text在ie9會拒絕訪問
				}
				var tmpSrc = document.selection.createRange().text;
				//設置原圖預覽濾鏡
				this.configs.orgDiv.css(
					"filter",
					"progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod='scale',src='" + tmpSrc + "')"
				);
				//獲取圖片實際大小(由於使用縮放濾鏡scale必須指定div寬高,無法做到自適應,因此先通過image濾鏡獲取實際大小)
				var bounds = this.getImageBounds(tmpSrc);
				//級聯變動
				this.configs.orgDiv.width(bounds.w);
				this.configs.orgDiv.height(bounds.h);
				this.configs.orgDiv.show();
				//需要對透明的img使用絕對定位並設置offset爲預覽div的offset,避免jcrop無法覆蓋在預覽div上面
				this.configs.orgImg.css("position","absolute");
				this.configs.orgImg.css("display","inline-block");
				this.configs.orgImg.offset(this.configs.orgDiv.offset());
				this.configs.orgImg.width(bounds.w);
				this.configs.orgImg.height(bounds.h);
				this.configs.orgImg.attr("src", this.configs.emtpyImage);
				this.configs.orgImg.attr("realSrc", tmpSrc);
				this.configs.orgImg.show();
				this.configs.previewDiv.show();
				this.configs.previewImg.hide();
				//初始化截圖工具
				this.doCrop(
					this.configs.emtpyImage,{
						bgColor : "#00000000" //DIV預覽需要遮蓋物透明
					},this.doPreview
				);
			}
			return this;
		},
		/**
		 * 火狐瀏覽器實現
		 */
		firefoxPreview : function(){
			this.preview = function(file){
				var browserVersion = window.navigator.userAgent.toUpperCase();
				var firefoxVersion = parseFloat(browserVersion.toLowerCase().match(/firefox\/([\d.]+)/)[1]);
				var src;
				// firefox7以下版本
				if (firefoxVersion < 7) {
					src = file.files[0].getAsDataURL();
				// firefox7.0+
				} else {
					src = window.URL.createObjectURL(file.files[0]);
				}
				//設置預覽
				this.configs.orgImg.height("auto");
				this.configs.orgImg.attr("src", src);
				//級聯變動
				this.configs.orgDiv.hide();
				this.configs.previewDiv.hide();
				this.configs.orgImg.show();
				this.configs.previewImg.show();
				//初始化截圖工具
				this.doCrop(src);
			}
			return this;
		},
		/**
		 * html5實現
		 */
		html5Preview : function(){
			this.preview = function(file){
				var me = this;
				var browserVersion = window.navigator.userAgent.toUpperCase();
				if (window.FileReader) {
					var reader = new FileReader();
					reader.onload = function(e) {
						me.configs.orgImg.height("auto");
						me.configs.orgImg.width(me.configs.orgContainer.width());
						me.configs.orgImg.attr("src", e.target.result);
						me.configs.orgImg.show();
						//設置預覽
						//級聯變動
						me.configs.orgDiv.hide();
						me.configs.previewDiv.hide();
						me.configs.orgImg.show();
						me.configs.previewImg.show();
						//初始化截圖工具
						me.doCrop(e.target.result);
					}
					reader.readAsDataURL(file.files[0]);
				} else if (browserVersion.indexOf("SAFARI") > -1) {
					alert("不支持Safari6.0以下瀏覽器的圖片預覽!");
				}
			}
			return this;
		},
		/**
		 * 默認實現
		 */
		defaultPreview : function(){
			this.preview = function(file){
				//設置預覽
				this.configs.orgImg.height("auto");
				this.configs.orgImg.attr("src", file.value);
				//級聯變動
				this.configs.orgDiv.hide();
				this.configs.previewDiv.hide();
				this.configs.orgImg.show();
				this.configs.previewImg.show();
				//初始化截圖工具
				this.doCrop(file.value);
			}
			return this;
		}
	}
	return impls[name]();
}
這個就比較蛋疼了,涉及了多個瀏覽器的具體預覽實現方案。具體……看註釋吧。

調用代碼:

$(document).ready(function(){
	var file = document.getElementById("image_crop_file_chooser");
	var previewer = new jquery.imagecrop.previewer(file);
	previewer.init("#previewer", "images/touming.gif");		
	$(file).change(function(){
		previewer.desdroy();
		previewer.preview(file);
	});
	$("#image_crop_enter_btn").click(function(){
		console.log(previewer.getCrop());
	});
});



最後,上css……

/*頁面主體*/
body {
	text-align: center;
	margin: 0px;
}
/*列布局容器*/
.image-crop-container {
	display: inline-block;
}
/*區域*/
.image-crop-fieldset{
	border: 1px solid #dedede;
	float: left;
	display: inline-block;
}
/*區域標題*/
.image-crop-fieldset legend {
	font-size: 13px;
}
/*原圖區域容器*/
.image-crop-org-container{
	width: 600px;
	overflow: hidden;
	margin: 5px;
}
/*原圖預覽DIV*/
.image-crop-org-div{
	width: 100%;
	float: left;
	display: inline-block;
}
/*原圖預覽IMG*/
.image-crop-org-img{
	width: 600px;
	float: left;
	display: inline-block;
}
/*截圖預覽區域容器*/
.image-crop-preview-container{
	float: left;
	display: inline-block;
	overflow: hidden;
	margin: 5px;
	width: 300px;/*由此決定寬高比*/
	height: 200px;/*由此決定寬高比*/
}
/*截圖預覽DIV*/
#image_crop_preview_div{
	width: 100%;
	float: left;
	display: none;/*開始不顯示*/
}
/*截圖預覽IMG*/
#image_crop_preview_img{
	width: 100%;
	float: left;
	display: none;/*開始不顯示*/
}


/*普通DIV*/
.simple-div {
	display: block;
	float: none;
}
/*文件選擇*/
#image_crop_file_chooser{
	margin: 5px;
}
/*按鈕容器*/
.buttonS {
	margin: 5px;
}
/*按鈕*/
.buttonS input {
	width: 80px;
	padding: 5px;
	border: 1px solid #efefef;
	background-color: #efefef;
	cursor: pointer;
}
/*按鈕聚焦*/
.buttonS input:HOVER {
	border: 1px solid #bdbdbd;
	background-color: #bdbdbd;
}
這個不是一定的,是可以調整的,各頁面可更改,可自定義設置,但名稱是約定的。不過inline-block、float:left和hidden可別改了……


額,其實最關鍵的是處理ie那段,欺騙jcrop,給張假圖然它選,實際預覽處理改取真圖,囧,然後就是很蛋疼的濾鏡和必須指定寬高……


over。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章