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