下面是我自己写的图片裁剪的功能介绍:
可以利用鼠标拖拉,产生裁剪框
可以改变裁剪框大小
点击确定,返回裁剪数据
原理
完成裁剪的方法有两种:
1、利用HTML5新增拖拽事件drag drop等
2、传统方法,利用鼠标事件,mousedown、mousemove等
在这里,我们采用方法2。
裁剪区域的形成
要进行裁剪首先要形成裁剪区域,这个裁剪区域的形成我们可以与鼠标移动的距离相关联。鼠标移动多远,裁剪区域就有多大。如下图:
如上图所示鼠标的横向移动距离与纵向移动距离共同组成了裁剪区域的宽和高。
而这横向与纵向移动的距离如何计算呢?当我们点下鼠标时,就能够通过event
事件对象获取鼠标点击位置,而移动鼠标时,也能够通过event
获取鼠标的位置,通过两次鼠标位置的改变,就能够获得鼠标的移动距离。
获取鼠标位置的属性是clientX以及clientY
阴影区域的形成
接下来就是绘制阴影区域。被裁剪图片中除裁剪区域以外的部分,都属于阴影部分,也可以不绘制该区域,绘制该区域是为了让用户更清晰的看清裁剪区域。
我将该区域分成了上下左右四个部分,见下图分布:
那么该区域如果计算呢?这时就要用到Dom元素的偏移值了,利用裁剪区域的左偏移值减去图片本身的左偏移值就是左阴影的宽,利用裁剪区域的上偏移值减去图片的上偏移值,等于上阴影的高度值。如下图:
获取到左阴影、上阴影的值后,就能够通过这两个将其他阴影的属性计算出来。
图片的偏移值有两种取法
1.利用offsetLeft 与 offsetTop 值 弊端 如果dom元素有border margin等值会将这些值计算在内
2.获取dom的css属性 弊端 预定义的css有关 如果没定义left top就无法获取
这两种方法都有各自的弊端,视不同情况来使用
裁剪越界的阻止
裁剪区域的计算是通过鼠标的移动距离来计算的,因此会出现裁剪区域越界的情况,而这情况又分成两种:
1.裁剪过程中越界
2.移动裁剪区域时越界
那么下面就来说说如何防止越界。
裁剪越界
什么是裁剪时越界?就是鼠标拖动区域超出了图片的返回,形成了越界,如下图:
对于这种越界需要判断裁剪区域的右侧相对于浏览器左侧的位置 不能够超过 图片右侧的位置相当于浏览器左侧的位置;同时裁剪区域底部相对于浏览器顶部位置 不能够超过 图片底部相对应浏览器顶部的位置。还是画图来说明:
当TX >= PX
时就让TX的值强制为一固定值。
TX与PX的计算方法,假设裁剪区域为oTailor
,图片区域oPicture
:
TX = oTailor.offsetWidth + oTailor.offsetLeft; PX = oPicture.offsetWidth + oPicture.offsetLeft;
同理,可以按照上述方法对左侧越界,上侧越界,下侧越界进行限制,就不多赘述。
移动越界
移动越界指的是已经形成了裁剪区域了,但通过鼠标移动裁剪区域时产生了越界。这个理解比较简单,就不画图介绍了。这种越界与dom拖拽越界限制一致,通过判断鼠标移动距离是否超过了图片区域来判断。
原理与问题解决了,现在开始来完成实际功能。
准备工作
在做之前,先做一些准备工作,磨刀不误砍柴功。
网页布局准备
网页布局部分关键代码如下:
其中img_box
表示的是裁剪区域,outer
表示阴影区域,而img_box
中的div是裁剪区域的边框
样式控制如下:
* { padding:0; margin:0; } body { background: #454545; } .main { width: 500px; margin:50px auto; } .main img { width: 500px; position: absolute; left: 450px; top: 50px; } .img_box { overflow: hidden; position: absolute; top:0px; left: 0px; z-index: 2; } .outer { overflow: hidden; background: #000; opacity: 0.4; position: absolute; top:0px; left: 0px; z-index: 0; } .box_border1 , .box_border2 , .box_border3 , .box_border4 { opacity: 0.5; } .box_border1 { background: url(./images/border-anim-v.gif) repeat-y left top; } .box_border2 { background: url(./images/border-anim-h.gif) repeat-x left top; } .box_border3 { background: url(./images/border-anim-v.gif) repeat-y right top; } .box_border4 { background: url(./images/border-anim-h.gif) repeat-x right bottom; } .box_handle { background: #fff; border: 1px solid #000; opacity: 0.5; } .confrim { width: 80px; height: 35px; }
布局效果如下:
通用函数
完成图片裁剪,通过上述原理,可以知道需要大量获取标签对象以及标签的css属性等,所以可以编写通用函数,更好的获取这些值。如下:
Dom获取函数
/* 仿JqueryDom获取 */ function $(dom) { function getDom(dom) { var str = dom.charAt(0); switch( str ) { case '.' : this.ele = document.getElementsByClassName(dom.substring(1))||null; break; case '#' : this.ele = document.getElementById(dom.substring(1)) || null; break; default : if(document.getElementsByTagName(dom).length) { this.ele = document.getElementsByTagName(dom); } else if(document.getElementsByName(dom).length) { this.ele = document.getElementsByName(dom); } else { this.ele = null; } } return this; }; getDom.prototype.get = function(num) { return this.ele[num]||this.ele; } getDom.prototype.insert = function(value , num) { this.ele[num].innerHTML = value; } return new getDom(dom); }
Css属性获取函数
Css属性的获取分成两种,一种是IE的,使用currentStyle
;另一种是其他主流浏览器,使用getComputedStyle
,以下是兼容版本:
/* Css获取 */ function getCss(o , key){ return o.currentStyle? o.currentStyle[key] : document.defaultView.getComputedStyle(o,false)[key]; };
赋值函数
编写时经常遇到对Dom的样式进行赋值,为方便,我专门编写了一个函数用于赋值:
/** - 赋值函数 - @param : obj 被赋值对象 - @param : option 进行的操作 - @parma : value 赋值内容 */ function setAssign(obj , option , value) { switch(option) { case 'width': obj.style.width = value; break; case 'height': obj.style.height = value; break; case 'top': obj.style.top = value; break; case 'left': obj.style.left = value; break; case 'position': obj.style.position = value; break; case 'cursor': obj.style.cursor = value; } }
好了准备工作基本完成,现在就正式开始编写。
通过点击与移动事件完成裁剪区域绘制
对图片设置mousedown
以及mousemove
事件监视,如下:
// 鼠标点击图片触发 oPicture.onmousedown = function(ev) { // 事件对象 var oEvent = ev || window.event; // 初始鼠标位置 var tempX = oEvent.clientX; var tempY = oEvent.clientY; // 调整裁剪区域位置 oTailor.style.left = oEvent.clientX + 'px'; oTailor.style.top = oEvent.clientY + 'px'; // 鼠标在图片上移动 绘制裁剪区域 阴影区域 document.onmousemove = function(ev) { // 鼠标移动事件对象 var oEvent = ev || window.event; // 当前鼠标位置减去鼠标之前的鼠标位置 等于 鼠标移动距离 var sLeft = oEvent.clientX - tempX; var sTop = oEvent.clientY - tempY; // 裁剪越界限制 只需限制右侧 与 下侧 if((oTailor.offsetLeft+oTailor.offsetWidth) >= (oPicture.offsetLeft+oPicture.offsetWidth)) { sLeft = oPicture.offsetLeft+oPicture.offsetWidth - oTailor.offsetLeft; } if((oTailor.offsetTop+oTailor.offsetHeight) >= (oPicture.offsetTop+oPicture.offsetHeight)) { sTop = oPicture.offsetTop+oPicture.offsetHeight - oTailor.offsetTop; } // 裁剪区域绘制 oTailor.style.width = sLeft + 'px'; oTailor.style.height = sTop + 'px'; // 裁剪区域显示 oTailor.style.display = 'block'; // 阴影区域显示 for (var i = 0; i < oShadow.length; i++) { oShadow[i].style.display = 'block'; } // 阴影区域绘制 shadow(oPicture , oTailor , oShadow); // 添加裁剪边框 tailorBorder(oDiv , oHandle , oTailor); // 阻止默认事件 oEvent.preventDefault(); }; // 鼠标松开 将移动事件取消 document.onmouseup = function(ev) { var oEvent = ev || window.event; // 移动事件取消 document.onmousemove = null; // 阻止默认事件 oEvent.preventDefault(); }; // 阻止默认事件 oEvent.preventDefault(); }
阴影区域绘制
/** * @param:oPicture 图片dom对象 * @param:oTailor 裁剪区域dom对象 * @param:oShadow 阴影区域dom对象 */ function shadow(oPicture , oTailor , oShadow) { // 左侧阴影区 setAssign(oShadow[0] , 'width' , (parseInt(getCss(oTailor , 'left')) - parseInt(getCss(oPicture , 'left'))) + 'px'); setAssign(oShadow[0] , 'height' , parseInt(getCss(oPicture , 'height')) + 'px'); setAssign(oShadow[0] , 'left' , parseInt(getCss(oPicture , 'left')) + 'px') setAssign(oShadow[0] , 'top' , parseInt(getCss(oPicture , 'top')) + 'px') //右侧阴影区 setAssign(oShadow[2] , 'width' , (parseInt(getCss(oPicture , 'width')) - parseInt(getCss(oTailor ,'width')) - parseInt(getCss(oShadow[0] , 'width'))) + 'px'); setAssign(oShadow[2] , 'height' , parseInt(getCss(oPicture , 'height')) + 'px'); setAssign(oShadow[2] , 'left' , (parseInt(getCss(oTailor , 'left')) + parseInt(getCss(oTailor , 'width'))) + 'px'); setAssign(oShadow[2] , 'top' , parseInt(getCss(oPicture , 'top')) + 'px'); // 上侧阴影区 setAssign(oShadow[1] , 'width' , parseInt(getCss(oTailor , 'width')) + 'px'); setAssign(oShadow[1] , 'height' , (parseInt(getCss(oTailor , 'top')) - parseInt(getCss(oPicture , 'top'))) + 'px'); setAssign(oShadow[1] , 'left' , (parseInt(getCss(oPicture , 'left')) + parseInt(getCss(oShadow[0] , 'width'))) + 'px'); setAssign(oShadow[1] , 'top' , parseInt(getCss(oPicture , 'top')) + 'px'); // 下侧阴影区 setAssign(oShadow[3] , 'width' , parseInt(getCss(oTailor , 'width')) + 'px'); setAssign(oShadow[3] , 'height' , (parseInt(getCss(oPicture , 'height')) - parseInt(getCss(oTailor , 'height')) - parseInt(getCss(oShadow[1] , 'height'))) + 'px'); setAssign(oShadow[3] , 'left' , (parseInt(getCss(oPicture , 'left' )) + parseInt(getCss(oShadow[0] , 'width'))) + 'px'); setAssign(oShadow[3] , 'top' , (parseInt(getCss(oTailor , 'top' )) + parseInt(getCss(oTailor , 'height'))) + 'px'); }
注意在网页实际运用中,如果布局中图片css中没有left或top属性,那么上面代码会产生错误。应该使用offsetLeft与offsetTop代替之。
添加裁剪边框
在放出的布局图中,可以看见裁剪的边沿,四角及四边各有一个小正方形的形状,添加不仅是为了区分裁剪区与非裁剪区,还为下一步添加拉伸裁剪区域提供方便。下面开始编写代码:
/** * 裁剪边框绘制 * @param : oDIv 所有边框对象 * @param : oHandle 点状边沿 * @param : oTailor 裁剪对象 */ function tailorBorder(oDiv , oHandle , oTailor) { // 对边框进行初始化 for (var i = 0; i < oDiv.length; i++) { setAssign(oDiv[i] , 'position' , 'absolute'); setAssign(oDiv[i] , 'top' , '0px'); setAssign(oDiv[i] , 'left' , '0px'); setAssign(oDiv[i] , 'width' , parseInt(getCss(oTailor , 'width')) + 'px'); setAssign(oDiv[i] , 'height' , parseInt(getCss(oTailor , 'height')) + 'px'); } /* 点状边沿绘制 */ // 四角点状边沿绘制 for (var i = 0; i < 4; i++) { // 点状绘制 setAssign(oHandle[i] , 'position' , 'absolute'); setAssign(oHandle[i] , 'width' , '5px'); setAssign(oHandle[i] , 'height' , '5px'); // 0 2 表示左侧点状 if(i % 2 == 0) { setAssign(oHandle[i] , 'left' , '0px'); setAssign(oHandle[i] , 'top' , (i == 0?'0px' : (parseInt(getCss(oTailor , 'height')) - 8) + 'px')); } else { // 右侧点状 setAssign(oHandle[i] , 'left' , ( parseInt(getCss(oTailor , 'width')) - 6 ) + 'px'); setAssign(oHandle[i] , 'top' , (i == 1?'0px' : parseInt(getCss(oTailor , 'height')) - 8 ) + 'px'); } } // 四边点状边框 for (var i = 4; i < oHandle.length; i++) { setAssign(oHandle[i] , 'position' , 'absolute'); setAssign(oHandle[i] , 'width' , '5px'); setAssign(oHandle[i] , 'height' , '5px'); // 4 6 表示上 下 点状边框 if(i % 2 == 0) { setAssign(oHandle[i] , 'left' , parseInt(getCss(oTailor , 'width')) / 2 + 'px'); setAssign(oHandle[i] , 'top' , (i == 4 ? '0px' : (parseInt(getCss(oTailor , 'height')) - 8) + 'px')); } else { // 左右点状 setAssign(oHandle[i] , 'top' , parseInt(getCss(oTailor , 'height')) / 2 + 'px'); setAssign(oHandle[i] , 'left' ,(i == 5 ? '0px' : parseInt(getCss(oTailor , 'width')) - 8 ) + 'px'); } } }
布局中,裁剪区域类名为box_handle
的div前四个代表四角的点状,后四个表示边沿中间的点状,都按照顺时针分布。完成后效果如下:
监视阴影区域
裁剪区域与阴影区域绘制完成,现在添加一个小功能,当鼠标点击到非裁剪区时(即阴影区),取消裁剪区域。
// 对阴影区域设置时间 点击到阴影区时 裁剪区域消失 阴影区消失 for (var i = 0; i < oShadow.length; i++) { oShadow[i].index = i; oShadow[i].onmousedown = function() { oTailor.style.display = 'none'; oTailor.style.width = '0px'; oTailor.style.hegiht = '0px'; for (var i = 0; i < oShadow.length; i++) { oShadow[i].style.display = 'none'; oShadow[i].style.left = '0px'; oShadow[i].style.top = '0px'; } } }
监视鼠标移动位置
接下来添加裁剪区域拉伸的功能,当鼠标移动到边沿的点状边框时呈现不同的效果
添加鼠标显示效果
// 点状边框监视 设置相应操作 oTailor.onmousemove = function(ev) { var oTarget = oEvent.target; switch(oTarget.id) { case 'box_1': // 左上 setAssign(oTailor , 'cursor' , 'nw-resize'); break; case 'box_2': // 右上 setAssign(oTailor , 'cursor' , 'ne-resize'); break; case 'box_3': // 左下 setAssign(oTailor , 'cursor' , 'sw-resize'); break; case 'box_4': // 右下 setAssign(oTailor , 'cursor' , 'se-resize'); break; case 'box_5': // 上 setAssign(oTailor , 'cursor' , 'n-resize'); break; case 'box_6': // 左 setAssign(oTailor , 'cursor' , 'w-resize'); break; case 'box_7': // 下 setAssign(oTailor , 'cursor' , 's-resize'); break; case 'box_8': // 右 setAssign(oTailor , 'cursor' , 'e-resize'); break; default : // 裁剪区域 显示可移动提示 setAssign(oTailor , 'cursor' , 'move'); break; } }
由于监视的div较多,因此采用事件委托的方式添加,效果不方便演示,有兴趣的同学可以自己测试,
添加拉伸效果
代码
// 裁剪区域的移动事件 oTailor.onmousedown = function(ev) { // event事件对象 var oEvent = ev || window.event; // 获取cursor状态 var oCur = getCss(oTailor , 'cursor'); // 鼠标初始位置 var sTmpX = oEvent.clientX; var sTmpY = oEvent.clientY; // 获取裁剪区域的属性 用一个对象保存起来方便调用 oAttrs.left = getCss(oTailor , 'left'); oAttrs.top = getCss(oTailor , 'top'); oAttrs.width = getCss(oTailor , 'width'); oAttrs.height = getCss(oTailor , 'height'); document.onmousemove = function(ev) { // 移动事件对象 var oEvent = ev || window.event; // 当前鼠标位置减去初始鼠标位置 等于 鼠标移动距离 var sLeftT = oEvent.clientX - sTmpX; var sTopT = oEvent.clientY - sTmpY ; // 表示鼠标移动的距离 var oTmpHeight = ''; var oTmpTop = ''; var oTmpWidth = ''; var oTmpLeft = ''; switch(oCur) { case 'nw-resize' : // 左上 oTmpWidth = parseInt(oAttrs.width) - sLeftT ; oTmpHeight = parseInt(oAttrs.height) - sTopT ; oTmpLeft = parseInt(oAttrs.left) + sLeftT ; oTmpTop = parseInt(oAttrs.top) + sTopT ; break; case 'ne-resize' : // 右上 // 此时width不能减去鼠标移动距离 因为此时移动距离为正值 oTmpWidth = parseInt(oAttrs.width) + sLeftT ; oTmpHeight = parseInt(oAttrs.height) - sTopT ; // 右上角移动不需要left值 因为默认响右移动 oTmpTop = parseInt(oAttrs.top) + sTopT ; break; case 'sw-resize' : // 左下 // 同右上 height 必须是加上鼠标移动距离 oTmpWidth = parseInt(oAttrs.width) - sLeftT ; oTmpHeight = parseInt(oAttrs.height) + sTopT ; oTmpLeft = parseInt(oAttrs.left) + sLeftT ; break; case 'se-resize' : // 右下 // 左下与右上的结合 同时去除left与top oTmpWidth = parseInt(oAttrs.width) + sLeftT ; oTmpHeight = parseInt(oAttrs.height) + sTopT ; break; case 'n-resize' : // 上 oTmpHeight = parseInt(oAttrs.height) - sTopT; oTmpTop = parseInt(oAttrs.top) + sTopT; break; case 'w-resize' : // 左 oTmpWidth = parseInt(oAttrs.width) - sLeftT ; oTmpLeft = parseInt(oAttrs.left) + sLeftT; break; case 's-resize' : // 下 oTmpHeight = parseInt(oAttrs.height) + sTopT; break; case 'e-resize' : // 右 var oTmpWidth = parseInt(oAttrs.width) + sLeftT; break; default : // 否则是移动裁剪区域 tailorMove(oEvent , oTailor , oPicture , oShadow); break; } // 向上拉到边界 if(parseInt(getCss(oTailor , 'top')) <= oPicture.offsetTop) { oTmpHeight = parseInt(getCss(oPicture,'height')) - (oPicture.offsetTop+parseInt(getCss(oPicture,'height'))-parseInt(getCss(oTailor,'top'))-parseInt(getCss(oTailor,'height'))); oTmpTop = oPicture.offsetTop; }else if(oPicture.offsetTop+parseInt(getCss(oPicture,'height')) <= (parseInt(getCss(oTailor,'top'))+parseInt(getCss(oTailor,'height')))){ // 向下拉到边界 oTmpHeight = oPicture.offsetTop+parseInt(getCss(oPicture,'height')) - parseInt(getCss(oTailor,'top')); } // 向左拉到边界 if((parseInt(getCss(oTailor , 'left'))) <= oPicture.offsetLeft) { oTmpWidth = parseInt(getCss(oPicture,'width')) - (oPicture.offsetLeft+parseInt(getCss(oPicture),'width')-parseInt(getCss(oTailor,'left'))-parseInt(getCss(oTailor,'width'))) oTmpLeft = oPicture.offsetLeft; } else if(parseInt(getCss(oTailor , 'width')) + parseInt(getCss(oTailor,'left')) >= (oPicture.offsetLeft+oPicture.offsetWidth)) { // 向右拉到边界 oTmpWidth = oPicture.offsetLeft+oPicture.offsetWidth - parseInt(getCss(oTailor,'left')); } // 赋值 if(oTmpWidth){ setAssign(oTailor , 'width' , oTmpWidth + 'px'); } if(oTmpHeight) { setAssign(oTailor , 'height' , oTmpHeight + 'px'); } if (oTmpLeft) { setAssign(oTailor , 'left' , oTmpLeft + 'px'); } if (oTmpTop) { setAssign(oTailor , 'top' , oTmpTop + 'px'); } // 阴影区域绘制 shadow(oPicture , oTailor , oShadow); // 添加裁剪边框 tailorBorder(oDiv , oHandle , oTailor); }; // 当松开鼠标时注意取消移动事件 document.onmouseup = function(ev) { // event事件对象 var oEvent = ev || window.event; document.onmousemove = null; oEvent.preventDefault(); } oEvent.preventDefault(); };
拉伸时注意移动距离的计算,特别是向上及向左移动时,要注意同时改变裁剪区域的left、top值,否则它只会向下、向右增大。来具体说一下如何计算:
原理
以鼠标向左上角拉伸为例,鼠标的移动距离与上面所讲的一致,但此时注意计算出的值是一个负数,所以在计算裁剪区域的增加值时,要用原裁剪区的宽度或高度减去该值,同时,增加多少宽度,裁剪区的左偏移值就要减去多少,否则显示的效果是裁剪区域向右增大,如下图:
上图中,绿色区域是拉伸时增加宽、高后的裁剪区域,如果没进行偏移调整后的效果既是这样,黄色区域是进行偏移跳转后的裁剪区域,两个的叠加区就是原来的裁剪区了。
这是左上角拉伸,左下角拉伸即其他与之类似,可依照向上套。
而另一关键,拉伸越界在上面已经说过,就不再叙述了。
裁剪区域的移动
现在来说最后一个功能,裁剪区域的移动。当鼠标移动到裁剪区域内部时,就会触发移动事件,此时可以移动裁剪区域,代码如下:
/* 裁剪区域的移动 */ function tailorMove(ev ,oTailor , oPicture ,oShadow) { var oEvent = ev || window.event; var oTmpx = oEvent.clientX - oTailor.offsetLeft; var oTmpy = oEvent.clientY - oTailor.offsetTop; document.onmousemove = function(ev) { var oEvent = ev || window.event; oLeft = oEvent.clientX - oTmpx; oTop = oEvent.clientY - oTmpy; if(oLeft < oPicture.offsetLeft ) { oLeft = oPicture.offsetLeft ; } else if(oLeft > (oPicture.offsetLeft + oPicture.offsetWidth - oTailor.offsetWidth)) { oLeft = oPicture.offsetLeft + oPicture.offsetWidth - oTailor.offsetWidth; } if(oTop < oPicture.offsetTop) { oTop = oPicture.offsetTop; } else if (oTop > (oPicture.offsetTop + oPicture.offsetHeight - oTailor.offsetHeight)) { oTop = oPicture.offsetTop + oPicture.offsetHeight - oTailor.offsetHeight; } oTailor.style.left = ( oLeft)+ 'px'; oTailor.style.top = (oTop) + 'px'; shadow(oPicture , oTailor , oShadow); } }
获取裁剪的位置
裁剪效果的功能基本完成,那么就要获取裁剪的位置,首先要知道需要获取那些属性。根据PHP
的GD
库操作,进行图片裁剪需要知道,裁剪的起点坐标以及裁剪的宽高。我用一个函数来获取这些数据,并将其封装后返回:
function getEle() { var oPicture = $('img').get(0); var oTailor = $('.img_box').get(0); oAttrs.LeftX = (parseInt(getCss(oTailor,'left')) - oPicture.offsetLeft); oAttrs.LeftY = (parseInt(getCss(oTailor,'top')) - oPicture.offsetTop); oAttrs.Twidth = (parseInt(getCss(oTailor,'width'))); oAttrs.Theight = (parseInt(getCss(oTailor,'height'))); return oAttrs; }
还有一个问题,如果网页上的图片是使用css压缩后的图片,那么在此获得的位置与裁剪大小会与你想像的有区别,可能裁剪后的图片范围会变大(原图较大),也有可能会变小(原图较小)。
如果能够获得原图的大小,可以根据压缩图与原图的比例来进行裁剪,这样可以获得正确的裁剪图。
好了,一个简单的图片裁剪功能就完成了,可以利用ajax传递到后台进行处理了。
本文内容到此就结束了,有问题的话欢迎大家留言讨论,希望本文对大家学习javascript有所帮助。