首先要說,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。