背景:
很久之前就在marry5.com看到这个效果,也叫图片裁剪、图片剪切,当时觉得很神奇,碍于水平有限,没做出来。 前些日子突然想做一个透镜效果,就突然想到了这个效果,于是找出当年“珍藏”的代码决定一尝所愿。
前言:
这个程序主要分三部分:层的拖放、层的缩放、图片切割(包括预览)。 其中层的拖放是很常见的效果,层的缩放有点难度,图片切割看着炫其实原理也很简单。 不过在实现的过程中也学习到很多以前不知道的东西,下面都会说明,希望大家从中也能学到东西。
效果:
<script> var $ = function (id) { return "string" == typeof id ? document.getElementById(id) : id; }; var isIE = (document.all) ? true : false; function addEventHandler(oTarget, sEventType, fnHandler) { if (oTarget.addEventListener) { oTarget.addEventListener(sEventType, fnHandler, false); } else if (oTarget.attachEvent) { oTarget.attachEvent("on" + sEventType, fnHandler); } else { oTarget["on" + sEventType] = fnHandler; } }; function removeEventHandler(oTarget, sEventType, fnHandler) { if (oTarget.removeEventListener) { oTarget.removeEventListener(sEventType, fnHandler, false); } else if (oTarget.detachEvent) { oTarget.detachEvent("on" + sEventType, fnHandler); } else { oTarget["on" + sEventType] = null; } }; var Class = { create: function() { return function() { this.initialize.apply(this, arguments); } } } Object.extend = function(destination, source) { for (var property in source) { destination[property] = source[property]; } return destination; } //拖放程序 var Drag = Class.create(); Drag.prototype = { //拖放对象,触发对象 initialize: function(obj, drag, options) { var oThis = this; this._obj = $(obj);//拖放对象 this.Drag = $(drag);//触发对象 this._x = this._y = 0;//记录鼠标相对拖放对象的位置 //事件对象(用于移除事件) this._fM = function(e){ oThis.Move(window.event || e); } this._fS = function(){ oThis.Stop(); } this.SetOptions(options); this.Limit = !!this.options.Limit; this.mxLeft = parseInt(this.options.mxLeft); this.mxRight = parseInt(this.options.mxRight); this.mxTop = parseInt(this.options.mxTop); this.mxBottom = parseInt(this.options.mxBottom); this.onMove = this.options.onMove; this._obj.style.position = "absolute"; addEventHandler(this.Drag, "mousedown", function(e){ oThis.Start(window.event || e); }); }, //设置默认属性 SetOptions: function(options) { this.options = {//默认值 Limit: false,//是否设置限制(为true时下面参数有用,可以是负数) mxLeft: 0,//左边限制 mxRight: 0,//右边限制 mxTop: 0,//上边限制 mxBottom: 0,//下边限制 onMove: function(){}//移动时执行 }; Object.extend(this.options, options || {}); }, //准备拖动 Start: function(oEvent) { //记录鼠标相对拖放对象的位置 this._x = oEvent.clientX - this._obj.offsetLeft; this._y = oEvent.clientY - this._obj.offsetTop; //mousemove时移动 mouseup时停止 addEventHandler(document, "mousemove", this._fM); addEventHandler(document, "mouseup", this._fS); //使鼠标移到窗口外也能释放 if(isIE){ addEventHandler(this.Drag, "losecapture", this._fS); this.Drag.setCapture(); }else{ addEventHandler(window, "blur", this._fS); } }, //拖动 Move: function(oEvent) { //清除选择(ie设置捕获后默认带这个) window.getSelection && window.getSelection().removeAllRanges(); //当前鼠标位置减去相对拖放对象的位置得到offset位置 var iLeft = oEvent.clientX - this._x, iTop = oEvent.clientY - this._y; //设置范围限制 if(this.Limit){ //获取超出长度 var iRight = iLeft + this._obj.offsetWidth - this.mxRight, iBottom = iTop + this._obj.offsetHeight - this.mxBottom; //这里是先设置右边下边再设置左边上边,可能会不准确 if(iRight > 0) iLeft -= iRight; if(iBottom > 0) iTop -= iBottom; if(this.mxLeft > iLeft) iLeft = this.mxLeft; if(this.mxTop > iTop) iTop = this.mxTop; } //设置位置 this._obj.style.left = iLeft + "px"; this._obj.style.top = iTop + "px"; //附加程序 this.onMove(); }, //停止拖动 Stop: function() { //移除事件 removeEventHandler(document, "mousemove", this._fM); removeEventHandler(document, "mouseup", this._fS); if(isIE){ removeEventHandler(this.Drag, "losecapture", this._fS); this.Drag.releaseCapture(); }else{ removeEventHandler(window, "blur", this._fS); } } }; //缩放程序 var Resize = Class.create(); Resize.prototype = { //缩放对象 initialize: function(obj, options) { var oThis = this; this._obj = $(obj);//缩放对象 this._right = this._down = this._left = this._up = 0;//坐标参数 this._scale = 1;//比例参数 this._touch = null;//当前触发对象 //用currentStyle(ff用getComputedStyle)取得最终样式 var _style = this._obj.currentStyle || document.defaultView.getComputedStyle(this._obj, null); this._xBorder = parseInt(_style.borderLeftWidth) + parseInt(_style.borderRightWidth); this._yBorder = parseInt(_style.borderTopWidth) + parseInt(_style.borderBottomWidth); //事件对象(用于移除事件) this._fR = function(e){ oThis.Resize(e); } this._fS = function(){ oThis.Stop(); } this.SetOptions(options); this.Limit = !!this.options.Limit; this.mxLeft = parseInt(this.options.mxLeft); this.mxRight = parseInt(this.options.mxRight); this.mxTop = parseInt(this.options.mxTop); this.mxBottom = parseInt(this.options.mxBottom); this.MinWidth = parseInt(this.options.MinWidth); this.MinHeight = parseInt(this.options.MinHeight); this.Scale = !!this.options.Scale; this.onResize = this.options.onResize; this._obj.style.position = "absolute"; }, //设置默认属性 SetOptions: function(options) { this.options = {//默认值 Limit: false,//是否设置限制(为true时下面mx参数有用) mxLeft: 0,//左边限制 mxRight: 0,//右边限制 mxTop: 0,//上边限制 mxBottom: 0,//下边限制 MinWidth: 50,//最小宽度 MinHeight: 50,//最小高度 Scale: false,//是否按比例缩放 onResize: function(){}//缩放时执行 }; Object.extend(this.options, options || {}); }, //设置触发对象 Set: function(resize, side) { var oThis = this, resize = $(resize), _fun, _cursor; if(!resize) return; //根据方向设置 _fun是缩放时执行的程序 _cursor是鼠标样式 switch (side.toLowerCase()) { case "up" : _fun = function(e){ if(oThis.Scale){ oThis.scaleUpRight(e); }else{ oThis.SetUp(e); } }; _cursor = "n-resize"; break; case "down" : _fun = function(e){ if(oThis.Scale){ oThis.scaleDownRight(e); }else{ oThis.SetDown(e); } }; _cursor = "n-resize"; break; case "left" : _fun = function(e){ if(oThis.Scale){ oThis.scaleLeftUp(e); }else{ oThis.SetLeft(e); } }; _cursor = "e-resize"; break; case "right" : _fun = function(e){ if(oThis.Scale){ oThis.scaleRightDown(e); }else{ oThis.SetRight(e); } }; _cursor = "e-resize"; break; case "left-up" : _fun = function(e){ if(oThis.Scale){ oThis.scaleLeftUp(e); }else{ oThis.SetLeft(e); oThis.SetUp(e); } }; _cursor = "nw-resize"; break; case "right-up" : _fun = function(e){ if(oThis.Scale){ oThis.scaleRightUp(e); }else{ oThis.SetRight(e); oThis.SetUp(e); } }; _cursor = "ne-resize"; break; case "left-down" : _fun = function(e){ if(oThis.Scale){ oThis.scaleLeftDown(e); }else{ oThis.SetLeft(e); oThis.SetDown(e); } }; _cursor = "ne-resize"; break; case "right-down" : default : _fun = function(e){ if(oThis.Scale){ oThis.scaleRightDown(e); }else{ oThis.SetRight(e); oThis.SetDown(e); } }; _cursor = "nw-resize"; } //插入字符解决ff下捕获效的问题 if(!isIE){ resize.innerHTML = "
"; } //设置触发对象 resize.style.cursor = _cursor; addEventHandler(resize, "mousedown", function(e){ oThis._fun = _fun; oThis._touch = resize; oThis.Start(window.event || e); }); }, //准备缩放 Start: function(oEvent, o) { //防止冒泡 if(isIE){ oEvent.cancelBubble = true; } else { oEvent.stopPropagation(); } //计算样式初始值 this.style_width = this._obj.offsetWidth; this.style_height = this._obj.offsetHeight; this.style_left = this._obj.offsetLeft; this.style_top = this._obj.offsetTop; //设置缩放比例 if(this.Scale){ this._scale = this.style_width / this.style_height; } //计算当前边的对应另一条边的坐标 例如右边缩放时需要左边界坐标 this._left = oEvent.clientX - this.style_width; this._right = oEvent.clientX + this.style_width; this._up = oEvent.clientY - this.style_height; this._down = oEvent.clientY + this.style_height; //如果有范围 先计算好范围内最大宽度和高度 if(this.Limit){ this._mxRight = this.mxRight - this._obj.offsetLeft; this._mxDown = this.mxBottom - this._obj.offsetTop; this._mxLeft = this.mxLeft + this.style_width + this._obj.offsetLeft; this._mxUp = this.mxTop + this.style_height + this._obj.offsetTop; } //mousemove时缩放 mouseup时停止 addEventHandler(document, "mousemove", this._fR); addEventHandler(document, "mouseup", this._fS); //使鼠标移到窗口外也能释放 if(isIE){ addEventHandler(this._touch, "losecapture", this._fS); this._touch.setCapture(); }else{ addEventHandler(window, "blur", this._fS); } }, //缩放 Resize: function(e) { //没有触发对象的话返回 if(!this._touch) return; //清除选择(ie设置捕获后默认带这个) window.getSelection && window.getSelection().removeAllRanges(); //执行缩放程序 this._fun(window.event || e); //设置样式 //因为计算用的offset是把边框算进去的所以要减去 this._obj.style.width = this.style_width - this._xBorder + "px"; this._obj.style.height = this.style_height - this._yBorder + "px"; this._obj.style.top = this.style_top + "px"; this._obj.style.left = this.style_left + "px"; //附加程序 this.onResize(); }, //普通缩放 //右边 SetRight: function(oEvent) { //当前坐标位置减去左边的坐标等于当前宽度 this.repairRight(oEvent.clientX - this._left); }, //下边 SetDown: function(oEvent) { this.repairDown(oEvent.clientY - this._up); }, //左边 SetLeft: function(oEvent) { //右边的坐标减去当前坐标位置等于当前宽度 this.repairLeft(this._right - oEvent.clientX); }, //上边 SetUp: function(oEvent) { this.repairUp(this._down - oEvent.clientY); }, //比例缩放 //比例缩放右下 scaleRightDown: function(oEvent) { //先计算宽度然后按比例计算高度最后根据确定的高度计算宽度(宽度优先) this.SetRight(oEvent); this.repairDown(parseInt(this.style_width / this._scale)); this.repairRight(parseInt(this.style_height * this._scale)); }, //比例缩放左下 scaleLeftDown: function(oEvent) { this.SetLeft(oEvent); this.repairDown(parseInt(this.style_width / this._scale)); this.repairLeft(parseInt(this.style_height * this._scale)); }, //比例缩放右上 scaleRightUp: function(oEvent) { this.SetRight(oEvent); this.repairUp(parseInt(this.style_width / this._scale)); this.repairRight(parseInt(this.style_height * this._scale)); }, //比例缩放左上 scaleLeftUp: function(oEvent) { this.SetLeft(oEvent); this.repairUp(parseInt(this.style_width / this._scale)); this.repairLeft(parseInt(this.style_height * this._scale)); }, //这里是高度优先用于上下两边(体验更好) //比例缩放下右 scaleDownRight: function(oEvent) { this.SetDown(oEvent); this.repairRight(parseInt(this.style_height * this._scale)); this.repairDown(parseInt(this.style_width / this._scale)); }, //比例缩放上右 scaleUpRight: function(oEvent) { this.SetUp(oEvent); this.repairRight(parseInt(this.style_height * this._scale)); this.repairUp(parseInt(this.style_width / this._scale)); }, //修正程序 //修正右边 repairRight: function(iWidth) { //右边和下边只要设置宽度和高度就行 //当少于最少宽度 if (iWidth < this.MinWidth){ iWidth = this.MinWidth; } //当超过当前设定的最大宽度 if(this.Limit && iWidth > this._mxRight){ iWidth = this._mxRight; } //修改样式 this.style_width = iWidth; }, //修正下边 repairDown: function(iHeight) { if (iHeight < this.MinHeight){ iHeight = this.MinHeight; } if(this.Limit && iHeight > this._mxDown){ iHeight = this._mxDown; } this.style_height = iHeight; }, //修正左边 repairLeft: function(iWidth) { //左边和上边比较麻烦 因为还要计算left和top //当少于最少宽度 if (iWidth < this.MinWidth){ iWidth = this.MinWidth; } //当超过当前设定的最大宽度 else if(this.Limit && iWidth > this._mxLeft){ iWidth = this._mxLeft; } //修改样式 this.style_width = iWidth; this.style_left = this._obj.offsetLeft + this._obj.offsetWidth - iWidth; }, //修正上边 repairUp: function(iHeight) { if(iHeight < this.MinHeight){ iHeight = this.MinHeight; } else if(this.Limit && iHeight > this._mxUp){ iHeight = this._mxUp; } this.style_height = iHeight; this.style_top = this._obj.offsetTop + this._obj.offsetHeight - iHeight; }, //停止缩放 Stop: function() { //移除事件 removeEventHandler(document, "mousemove", this._fR); removeEventHandler(document, "mouseup", this._fS); if(isIE){ removeEventHandler(this._touch, "losecapture", this._fS); this._touch.releaseCapture(); }else{ removeEventHandler(window, "blur", this._fS); } this._touch = null; } }; //图片切割 var ImgCropper = Class.create(); ImgCropper.prototype = { //容器对象,拖放缩放对象,图片地址,宽度,高度 initialize: function(container, drag, url, width, height, options) { var oThis = this; //容器对象 this.Container = $(container); this.Container.style.position = "relative"; this.Container.style.overflow = "hidden"; //拖放对象 this.Drag = $(drag); this.Drag.style.cursor = "move"; this.Drag.style.zIndex = 200; if(isIE){ //设置overflow解决ie6的渲染问题(缩放时填充对象高度的问题) this.Drag.style.overflow = "hidden"; //ie下用一个透明的层填充拖放对象 不填充的话onmousedown会失效(未知原因) (function(style){ style.width = style.height = "100%"; style.backgroundColor = "#fff"; style.filter = "alpha(opacity:0)"; })(this.Drag.appendChild(document.createElement("div")).style) }else{ //插入字符解决ff下捕获失效的问题 this.Drag.innerHTML += "
"; } this._pic = this.Container.appendChild(document.createElement("img"));//图片对象 this._cropper = this.Container.appendChild(document.createElement("img"));//切割对象 this._pic.style.position = this._cropper.style.position = "absolute"; this._pic.style.top = this._pic.style.left = this._cropper.style.top = this._cropper.style.left = "0";//对齐 this._cropper.style.zIndex = 100; this._cropper.onload = function(){ oThis.SetPos(); } this.Url = url;//图片地址 this.Width = parseInt(width);//宽度 this.Height = parseInt(height);//高度 this.SetOptions(options); this.Opacity = parseInt(this.options.Opacity); this.dragTop = parseInt(this.options.dragTop); this.dragLeft = parseInt(this.options.dragLeft); this.dragWidth = parseInt(this.options.dragWidth); this.dragHeight = parseInt(this.options.dragHeight); //设置预览对象 this.View = $(this.options.View) || null;//预览对象 this.viewWidth = parseInt(this.options.viewWidth); this.viewHeight = parseInt(this.options.viewHeight); this._view = null;//预览图片对象 if(this.View){ this.View.style.position = "relative"; this.View.style.overflow = "hidden"; this._view = this.View.appendChild(document.createElement("img")); this._view.style.position = "absolute"; } this.Scale = parseInt(this.options.Scale); //设置拖放 this._drag = new Drag(this.Drag, this.Drag, { Limit: true, onMove: function(){ oThis.SetPos(); } }); //设置缩放 this._resize = this.GetResize(); this.Init(); }, //设置默认属性 SetOptions: function(options) { this.options = {//默认值 Opacity: 50,//透明度(0到100) //拖放位置和宽高 dragTop: 0, dragLeft: 0, dragWidth: 100, dragHeight: 100, //缩放触发对象 Right: "", Left: "", Up: "", Down: "", RightDown: "", LeftDown: "", RightUp: "", LeftUp: "", Scale: false,//是否按比例缩放 //预览对象设置 View: "",//预览对象 viewWidth: 100,//预览宽度 viewHeight: 100//预览高度 }; Object.extend(this.options, options || {}); }, //初始化对象 Init: function() { var oThis = this; //设置容器 this.Container.style.width = this.Width + "px"; this.Container.style.height = this.Height + "px"; //设置拖放对象 this.Drag.style.top = this.dragTop + "px"; this.Drag.style.left = this.dragLeft + "px"; this.Drag.style.width = this.dragWidth + "px"; this.Drag.style.height = this.dragHeight + "px"; //设置切割对象 this._pic.src = this._cropper.src = this.Url; this._pic.style.width = this._cropper.style.width = this.Width + "px"; this._pic.style.height = this._cropper.style.height = this.Height + "px"; if(isIE){ this._pic.style.filter = "alpha(opacity:" + this.Opacity + ")"; } else { this._pic.style.opacity = this.Opacity / 100; } //设置预览对象 if(this.View){ this._view.src = this.Url; } //设置拖放 this._drag.mxRight = this.Width; this._drag.mxBottom = this.Height; //设置缩放 if(this._resize){ this._resize.mxRight = this.Width; this._resize.mxBottom = this.Height; this._resize.Scale = this.Scale; } }, //设置获取缩放对象 GetResize: function() { var op = this.options; //有触发对象时才设置 if(op.RightDown || op.LeftDown || op.RightUp || op.LeftUp || op.Right || op.Left || op.Up || op.Down ){ var oThis = this, _resize = new Resize(this.Drag, { Limit: true, onResize: function(){ oThis.SetPos(); } }); //设置缩放触发对象 if(op.RightDown){ _resize.Set(op.RightDown, "right-down"); } if(op.LeftDown){ _resize.Set(op.LeftDown, "left-down"); } if(op.RightUp){ _resize.Set(op.RightUp, "right-up"); } if(op.LeftUp){ _resize.Set(op.LeftUp, "left-up"); } if(op.Right){ _resize.Set(op.Right, "right"); } if(op.Left){ _resize.Set(op.Left, "left"); } if(op.Up){ _resize.Set(op.Up, "up"); } if(op.Down){ _resize.Set(op.Down, "down"); } return _resize; } else { return null; } }, //设置切割 SetPos: function() { var o = this.Drag; //按拖放对象的参数进行切割 this._cropper.style.clip = "rect(" + o.offsetTop + "px " + (o.offsetLeft + o.offsetWidth) + "px " + (o.offsetTop + o.offsetHeight) + "px " + o.offsetLeft + "px)"; //切割预览 if(this.View) this.PreView(); }, //切割预览 PreView: function() { //按比例设置宽度和高度 var o = this.Drag, h = this.viewWidth, w = h * o.offsetWidth / o.offsetHeight; if(w > this.viewHeight){ w = this.viewHeight; h = w * o.offsetHeight / o.offsetWidth; } //获取对应比例尺寸 var scale = h / o.offsetHeight, ph = this.Height * scale, pw = this.Width * scale, pt = o.offsetTop * scale, pl = o.offsetLeft * scale, styleView = this._view.style; //设置样式 styleView.width = pw + "px"; styleView.height = ph + "px"; styleView.top = - pt + "px "; styleView.left = - pl + "px"; //切割预览图 styleView.clip = "rect(" + pt + "px " + (pl + w) + "px " + (pt + h) + "px " + pl + "px)"; } } var ic = new ImgCropper("bgDiv", "dragDiv", "http://images.cnblogs.com/cnblogs_com/cloudgamer/143727/r_xx2.jpg", 300, 400, { dragTop: 50, dragLeft: 50, Right: "rRight", Left: "rLeft", Up: "rUp", Down: "rDown", RightDown: "rRightDown", LeftDown: "rLeftDown", RightUp: "rRightUp", LeftUp: "rLeftUp", View: "viewDiv", viewWidth: 200, viewHeight: 200}) </script> <script> function Size(w, h){ ic.Width = w; ic.Height = h; ic.Init(); } function Pic(url){ ic.Url = url; ic.Init(); } function Opacity(i){ ic.Opacity = i; ic.Init(); } function Scale(b){ ic.Scale = b; ic.Init(); } </script>
原理:
【拖放程序】
详细请参考JavaScript 拖放效果 。【缩放程序】
原理也很简单,根据鼠标的坐标来设置缩放对象样式。 除了设置width和height外,对于上边和左边的缩放还要设置left和top,详细可参考代码。
程序更重要的是结构设计,因为有八个方向又分普通和比例缩放, 要尽量抓出能重用的部分,不然程序的复杂度可想而知。 为了提高程序内函数重用度减低复杂度我做了以下设计: 1.设一个_fun程序存放缩放过程中要执行的程序(有点像委托); 2.计算四个样式初始值,缩放函数修改这些初始值,最后重新设置全部样式(为了减低复杂度不是按需修改); 3.对于普通缩放只需要四个方向的程序就够,像右下方向可以用执行右边和下边程序代替; 4.根据比例缩放程序和普通缩放程序可重用的部分抽出了四个修正程序(用了部分程序效率来换取);
下面是程序中比较有用的部分:
【边宽获取】
由于涉及到高度和宽度的修改,边框宽度的获取必不可少。 因为用offset取得的宽度或高度是包括了边框宽度的,style中的宽度和高度是不包括边框宽度的, 所以设置样式的时候必须在offset取得的宽度或高度的基础上减去边框宽度。
那怎么取得边框宽度呢? 直观的方法是通过parseInt(object.style.borderBottomWidth)来获取,但这是错误的, 因为这样只能获取style中设置的样式,而不能获取class中设置的样式。
要取得最终样式(实际的样式),在ie中可使用currentStyle取得,在ff中使用document.defaultView.getComputedStyle(object, null), 那么用下面的程序就可以获取边框宽度了:
Code
var _style = this ._obj.currentStyle || document.defaultView.getComputedStyle( this ._obj, null ); this ._xBorder = parseInt(_style.borderLeftWidth) + parseInt(_style.borderRightWidth); this ._yBorder = parseInt(_style.borderTopWidth) + parseInt(_style.borderBottomWidth);
【比例缩放】
比例缩放原理也很简单,在原有缩放的基础上,再按比例设置一次高和宽。 例如右下的比例缩放是先设置一次右边的普通缩放取得宽度, 根据比例取得高度后执行一次下边的修正程序, 由于此时高度经过修正可能已经改变了,最后需要再执行一次右边的修正程序。
Code
this .SetRight(oEvent); this .repairDown(parseInt( this .style_width / this ._scale)); this .repairRight(parseInt( this .style_height * this ._scale));
这样的缩放是以宽度优先的,对于上下两个点以高度优先会有更好的体验,
例如对于上面的点可以:
Code
this .SetUp(oEvent); this .repairRight(parseInt( this .style_height * this ._scale)); this .repairUp(parseInt( this .style_width / this ._scale));
对于上下左右四个点,更好的体验当然是以该点为中心缩放,
各位有兴趣的话可以把这个也做出来,写多四个修正程序对应这几个点就行。
而最小限制,范围限制可参照修正程序中的代码。 程序中也使用了跟拖放差不多的释放选择和鼠标捕获。
【图片切割】
关于图片切割的设计,有三个方法: 1.把图片设为背景图,通过设置背景图的位置来实现,但这样的缺点是只能按图片的正常比例实现,不够灵活; 2.把图片放到切割对象里面,通过设置图片的top和left实现,这个方法是可行,但下面有更简单的方法实现; 3.通过设置图片的clip来实现。
这个方法是从当年“珍藏”的代码中看到的,虽然以前接触过clip,但都忘了。 clip的作用是“检索或设置对象的可视区域。可视区域外的部分是透明的。” 依据上-右-下-左的顺序提供自对象左上角为(0,0)坐标计算的四个偏移数值来剪切。 例如:
div { position:absolute; width:60px; height:60px; clip:rect(
0
20
50
10
); }
注意position:absolute的设置是必须的(详细看手册)。
下面说说具体实现原理: 首先需要一个容器,拖放对象,图片地址,显示宽度和高度, 还要插入三个层: 底图层:那个半透明的图片, 显示层:拖放对象中正常显示的那个部分, 拖动层:就是拖放对象, 其中为了底图层和显示层能完美的重叠,我两个层都用了绝对定位,定在左上角。 zIndex也要设置一下,保证三个层的顺序。
下面是很简单但最重要设置切割函数SetPos(),按拖放对象的参数进行切割:
Code
var o = this .Drag; this ._cropper.style.clip = " rect( " + o.offsetTop + " px " + (o.offsetLeft + o.offsetWidth) + " px " + (o.offsetTop + o.offsetHeight) + " px " + o.offsetLeft + " px) " ;
只要把SetPos()放到Drag的onMove和Resize的onResize中就行。 onMove和onResize分别是拖放和缩放时附加执行的程序。
程序中有一个Init()函数,它的作用是初始化一些设置, 特别分出来的原因是为了在用户修改了属性后执行一次,就可以根据修改过的属性重新初始化一次。 更好的做法是根据不同的属性修改修改需要修改的设置,这里我是偷懒了。
【切割预览】
至于预览效果也不难,根据预览高度宽度和拖放对象(显示层)的参数,计算出图片和预览图的比例scale:
Code
var o = this .Drag, h = this .viewWidth, w = h * o.offsetWidth / o.offsetHeight; if (w > this .viewHeight){ w = this .viewHeight; h = w * o.offsetHeight / o.offsetWidth; } var scale = h / o.offsetHeight
通过这个比例就可以计算出预览图的width、height、top、left了:
Code
var scale = h / o.offsetHeight, ph = this .Height * scale, pw = this .Width * scale, pt = o.offsetTop * scale, pl = o.offsetLeft * scale, styleView = this ._view.style; styleView.width = pw + " px " ; styleView.height = ph + " px " ; styleView.top = - pt + " px " ; styleView.left = - pl + " px " ;
最好根据这些参数切割预览图:
styleView.clip
=
"
rect(
"
+
pt
+
"
px
"
+
(pl
+
w)
+
"
px
"
+
(pt
+
h)
+
"
px
"
+
pl
+
"
px)
"
;
预览图效果就做好了。
源码: 拖放程序:
Code
var $ = function (id) { return " string " == typeof id ? document.getElementById(id) : id; }; var isIE = (document.all) ? true : false ; function addEventHandler(oTarget, sEventType, fnHandler) { if (oTarget.addEventListener) { oTarget.addEventListener(sEventType, fnHandler, false ); } else if (oTarget.attachEvent) { oTarget.attachEvent( " on " + sEventType, fnHandler); } else { oTarget[ " on " + sEventType] = fnHandler; } }; function removeEventHandler(oTarget, sEventType, fnHandler) { if (oTarget.removeEventListener) { oTarget.removeEventListener(sEventType, fnHandler, false ); } else if (oTarget.detachEvent) { oTarget.detachEvent( " on " + sEventType, fnHandler); } else { oTarget[ " on " + sEventType] = null ; } }; var Class = { create: function () { return function () { this .initialize.apply( this , arguments); } } } Object.extend = function (destination, source) { for ( var property in source) { destination[property] = source[property]; } return destination; } // 拖放程序 var Drag = Class.create(); Drag.prototype = { // 拖放对象,触发对象 initialize: function (obj, drag, options) { var oThis = this ; this ._obj = $(obj); // 拖放对象 this .Drag = $(drag); // 触发对象 this ._x = this ._y = 0 ; // 记录鼠标相对拖放对象的位置 // 事件对象(用于移除事件) this ._fM = function (e){ oThis.Move(window.event || e); } this ._fS = function (){ oThis.Stop(); } this .SetOptions(options); this .Limit = !! this .options.Limit; this .mxLeft = parseInt( this .options.mxLeft); this .mxRight = parseInt( this .options.mxRight); this .mxTop = parseInt( this .options.mxTop); this .mxBottom = parseInt( this .options.mxBottom); this .onMove = this .options.onMove; this ._obj.style.position = " absolute " ; addEventHandler( this .Drag, " mousedown " , function (e){ oThis.Start(window.event || e); }); }, // 设置默认属性 SetOptions: function (options) { this .options = { // 默认值 Limit: false , // 是否设置限制(为true时下面参数有用,可以是负数) mxLeft: 0 , // 左边限制 mxRight: 0 , // 右边限制 mxTop: 0 , // 上边限制 mxBottom: 0 , // 下边限制 onMove: function (){} // 移动时执行 }; Object.extend( this .options, options || {}); }, // 准备拖动 Start: function (oEvent) { // 记录鼠标相对拖放对象的位置 this ._x = oEvent.clientX - this ._obj.offsetLeft; this ._y = oEvent.clientY - this ._obj.offsetTop; // mousemove时移动 mouseup时停止 addEventHandler(document, " mousemove " , this ._fM); addEventHandler(document, " mouseup " , this ._fS); // 使鼠标移到窗口外也能释放 if (isIE){ addEventHandler( this .Drag, " losecapture " , this ._fS); this .Drag.setCapture(); } else { addEventHandler(window, " blur " , this ._fS); } }, // 拖动 Move: function (oEvent) { // 清除选择(ie设置捕获后默认带这个) window.getSelection && window.getSelection().removeAllRanges(); // 当前鼠标位置减去相对拖放对象的位置得到offset位置 var iLeft = oEvent.clientX - this ._x, iTop = oEvent.clientY - this ._y; // 设置范围限制 if ( this .Limit){ // 获取超出长度 var iRight = iLeft + this ._obj.offsetWidth - this .mxRight, iBottom = iTop + this ._obj.offsetHeight - this .mxBottom; // 这里是先设置右边下边再设置左边上边,可能会不准确 if (iRight > 0 ) iLeft -= iRight; if (iBottom > 0 ) iTop -= iBottom; if ( this .mxLeft > iLeft) iLeft = this .mxLeft; if ( this .mxTop > iTop) iTop = this .mxTop; } // 设置位置 this ._obj.style.left = iLeft + " px " ; this ._obj.style.top = iTop + " px " ; // 附加程序 this .onMove(); }, // 停止拖动 Stop: function () { // 移除事件 removeEventHandler(document, " mousemove " , this ._fM); removeEventHandler(document, " mouseup " , this ._fS); if (isIE){ removeEventHandler( this .Drag, " losecapture " , this ._fS); this .Drag.releaseCapture(); } else { removeEventHandler(window, " blur " , this ._fS); } } };
缩放程序:
Code
// 缩放程序 var Resize = Class.create(); Resize.prototype = { // 缩放对象 initialize: function (obj, options) { var oThis = this ; this ._obj = $(obj); // 缩放对象 this ._right = this ._down = this ._left = this ._up = 0 ; // 坐标参数 this ._scale = 1 ; // 比例参数 this ._touch = null ; // 当前触发对象 // 用currentStyle(ff用getComputedStyle)取得最终样式 var _style = this ._obj.currentStyle || document.defaultView.getComputedStyle( this ._obj, null ); this ._xBorder = parseInt(_style.borderLeftWidth) + parseInt(_style.borderRightWidth); this ._yBorder = parseInt(_style.borderTopWidth) + parseInt(_style.borderBottomWidth); // 事件对象(用于移除事件) this ._fR = function (e){ oThis.Resize(e); } this ._fS = function (){ oThis.Stop(); } this .SetOptions(options); this .Limit = !! this .options.Limit; this .mxLeft = parseInt( this .options.mxLeft); this .mxRight = parseInt( this .options.mxRight); this .mxTop = parseInt( this .options.mxTop); this .mxBottom = parseInt( this .options.mxBottom); this .MinWidth = parseInt( this .options.MinWidth); this .MinHeight = parseInt( this .options.MinHeight); this .Scale = !! this .options.Scale; this .onResize = this .options.onResize; this ._obj.style.position = " absolute " ; }, // 设置默认属性 SetOptions: function (options) { this .options = { // 默认值 Limit: false , // 是否设置限制(为true时下面mx参数有用) mxLeft: 0 , // 左边限制 mxRight: 0 , // 右边限制 mxTop: 0 , // 上边限制 mxBottom: 0 , // 下边限制 MinWidth: 50 , // 最小宽度 MinHeight: 50 , // 最小高度 Scale: false , // 是否按比例缩放 onResize: function (){} // 缩放时执行 }; Object.extend( this .options, options || {}); }, // 设置触发对象 Set: function (resize, side) { var oThis = this , resize = $(resize), _fun, _cursor; if ( ! resize) return ; // 根据方向设置 _fun是缩放时执行的程序 _cursor是鼠标样式 switch (side.toLowerCase()) { case " up " : _fun = function (e){ if (oThis.Scale){ oThis.scaleUpRight(e); } else { oThis.SetUp(e); } }; _cursor = " n-resize " ; break ; case " down " : _fun = function (e){ if (oThis.Scale){ oThis.scaleDownRight(e); } else { oThis.SetDown(e); } }; _cursor = " n-resize " ; break ; case " left " : _fun = function (e){ if (oThis.Scale){ oThis.scaleLeftUp(e); } else { oThis.SetLeft(e); } }; _cursor = " e-resize " ; break ; case " right " : _fun = function (e){ if (oThis.Scale){ oThis.scaleRightDown(e); } else { oThis.SetRight(e); } }; _cursor = " e-resize " ; break ; case " left-up " : _fun = function (e){ if (oThis.Scale){ oThis.scaleLeftUp(e); } else { oThis.SetLeft(e); oThis.SetUp(e); } }; _cursor = " nw-resize " ; break ; case " right-up " : _fun = function (e){ if (oThis.Scale){ oThis.scaleRightUp(e); } else { oThis.SetRight(e); oThis.SetUp(e); } }; _cursor = " ne-resize " ; break ; case " left-down " : _fun = function (e){ if (oThis.Scale){ oThis.scaleLeftDown(e); } else { oThis.SetLeft(e); oThis.SetDown(e); } }; _cursor = " ne-resize " ; break ; case " right-down " : default : _fun = function (e){ if (oThis.Scale){ oThis.scaleRightDown(e); } else { oThis.SetRight(e); oThis.SetDown(e); } }; _cursor = " nw-resize " ; } // 设置触发对象 resize.style.cursor = _cursor; addEventHandler(resize, " mousedown " , function (e){ oThis._fun = _fun; oThis._touch = resize; oThis.Start(window.event || e); }); }, // 准备缩放 Start: function (oEvent, o) { // 防止冒泡 if (isIE){ oEvent.cancelBubble = true ; } else { oEvent.stopPropagation(); } // 计算样式初始值 this .style_width = this ._obj.offsetWidth; this .style_height = this ._obj.offsetHeight; this .style_left = this ._obj.offsetLeft; this .style_top = this ._obj.offsetTop; // 设置缩放比例 if ( this .Scale){ this ._scale = this .style_width / this .style_height; } // 计算当前边的对应另一条边的坐标 例如右边缩放时需要左边界坐标 this ._left = oEvent.clientX - this .style_width; this ._right = oEvent.clientX + this .style_width; this ._up = oEvent.clientY - this .style_height; this ._down = oEvent.clientY + this .style_height; // 如果有范围 先计算好范围内最大宽度和高度 if ( this .Limit){ this ._mxRight = this .mxRight - this ._obj.offsetLeft; this ._mxDown = this .mxBottom - this ._obj.offsetTop; this ._mxLeft = this .mxLeft + this .style_width + this ._obj.offsetLeft; this ._mxUp = this .mxTop + this .style_height + this ._obj.offsetTop; } // mousemove时缩放 mouseup时停止 addEventHandler(document, " mousemove " , this ._fR); addEventHandler(document, " mouseup " , this ._fS); // 使鼠标移到窗口外也能释放 if (isIE){ addEventHandler( this ._touch, " losecapture " , this ._fS); this ._touch.setCapture(); } else { addEventHandler(window, " blur " , this ._fS); } }, // 缩放 Resize: function (e) { // 没有触发对象的话返回 if ( ! this ._touch) return ; // 清除选择(ie设置捕获后默认带这个) window.getSelection && window.getSelection().removeAllRanges(); // 执行缩放程序 this ._fun(window.event || e); // 设置样式 // 因为计算用的offset是把边框算进去的所以要减去 this ._obj.style.width = this .style_width - this ._xBorder + " px " ; this ._obj.style.height = this .style_height - this ._yBorder + " px " ; this ._obj.style.top = this .style_top + " px " ; this ._obj.style.left = this .style_left + " px " ; // 附加程序 this .onResize(); }, // 普通缩放 // 右边 SetRight: function (oEvent) { // 当前坐标位置减去左边的坐标等于当前宽度 this .repairRight(oEvent.clientX - this ._left); }, // 下边 SetDown: function (oEvent) { this .repairDown(oEvent.clientY - this ._up); }, // 左边 SetLeft: function (oEvent) { // 右边的坐标减去当前坐标位置等于当前宽度 this .repairLeft( this ._right - oEvent.clientX); }, // 上边 SetUp: function (oEvent) { this .repairUp( this ._down - oEvent.clientY); }, // 比例缩放 // 比例缩放右下 scaleRightDown: function (oEvent) { // 先计算宽度然后按比例计算高度最后根据确定的高度计算宽度(宽度优先) this .SetRight(oEvent); this .repairDown(parseInt( this .style_width / this ._scale)); this .repairRight(parseInt( this .style_height * this ._scale)); }, // 比例缩放左下 scaleLeftDown: function (oEvent) { this .SetLeft(oEvent); this .repairDown(parseInt( this .style_width / this ._scale)); this .repairLeft(parseInt( this .style_height * this ._scale)); }, // 比例缩放右上 scaleRightUp: function (oEvent) { this .SetRight(oEvent); this .repairUp(parseInt( this .style_width / this ._scale)); this .repairRight(parseInt( this .style_height * this ._scale)); }, // 比例缩放左上 scaleLeftUp: function (oEvent) { this .SetLeft(oEvent); this .repairUp(parseInt( this .style_width / this ._scale)); this .repairLeft(parseInt( this .style_height * this ._scale)); }, // 这里是高度优先用于上下两边(体验更好) // 比例缩放下右 scaleDownRight: function (oEvent) { this .SetDown(oEvent); this .repairRight(parseInt( this .style_height * this ._scale)); this .repairDown(parseInt( this .style_width / this ._scale)); }, // 比例缩放上右 scaleUpRight: function (oEvent) { this .SetUp(oEvent); this .repairRight(parseInt( this .style_height * this ._scale)); this .repairUp(parseInt( this .style_width / this ._scale)); }, // 修正程序 // 修正右边 repairRight: function (iWidth) { // 右边和下边只要设置宽度和高度就行 // 当少于最少宽度 if (iWidth < this .MinWidth){ iWidth = this .MinWidth; } // 当超过当前设定的最大宽度 if ( this .Limit && iWidth > this ._mxRight){ iWidth = this ._mxRight; } // 修改样式 this .style_width = iWidth; }, // 修正下边 repairDown: function (iHeight) { if (iHeight < this .MinHeight){ iHeight = this .MinHeight; } if ( this .Limit && iHeight > this ._mxDown){ iHeight = this ._mxDown; } this .style_height = iHeight; }, // 修正左边 repairLeft: function (iWidth) { // 左边和上边比较麻烦 因为还要计算left和top // 当少于最少宽度 if (iWidth < this .MinWidth){ iWidth = this .MinWidth; } // 当超过当前设定的最大宽度 else if ( this .Limit && iWidth > this ._mxLeft){ iWidth = this ._mxLeft; } // 修改样式 this .style_width = iWidth; this .style_left = this ._obj.offsetLeft + this ._obj.offsetWidth - iWidth; }, // 修正上边 repairUp: function (iHeight) { if (iHeight < this .MinHeight){ iHeight = this .MinHeight; } else if ( this .Limit && iHeight > this ._mxUp){ iHeight = this ._mxUp; } this .style_height = iHeight; this .style_top = this ._obj.offsetTop + this ._obj.offsetHeight - iHeight; }, // 停止缩放 Stop: function () { // 移除事件 removeEventHandler(document, " mousemove " , this ._fR); removeEventHandler(document, " mouseup " , this ._fS); if (isIE){ removeEventHandler( this ._touch, " losecapture " , this ._fS); this ._touch.releaseCapture(); } else { removeEventHandler(window, " blur " , this ._fS); } this ._touch = null ; } };
图片切割程序:
Code
// 图片切割 var ImgCropper = Class.create(); ImgCropper.prototype = { // 容器对象,拖放缩放对象,图片地址,宽度,高度 initialize: function (container, drag, url, width, height, options) { var oThis = this ; // 容器对象 this .Container = $(container); this .Container.style.position = " relative " ; this .Container.style.overflow = " hidden " ; // 拖放对象 this .Drag = $(drag); this .Drag.style.cursor = " move " ; this .Drag.style.zIndex = 200 ; if (isIE){ // 设置overflow解决ie6的渲染问题(缩放时填充对象高度的问题) this .Drag.style.overflow = " hidden " ; // ie下用一个透明的层填充拖放对象 不填充的话onmousedown会失效(未知原因) ( function (style){ style.width = style.height = " 100% " ; style.backgroundColor = " #fff " ; style.filter = " alpha(opacity:0) " ; })( this .Drag.appendChild(document.createElement( " div " )).style) } this ._pic = this .Container.appendChild(document.createElement( " img " )); // 图片对象 this ._cropper = this .Container.appendChild(document.createElement( " img " )); // 切割对象 this ._pic.style.position = this ._cropper.style.position = " absolute " ; this ._pic.style.top = this ._pic.style.left = this ._cropper.style.top = this ._cropper.style.left = " 0 " ; // 对齐 this ._cropper.style.zIndex = 100 ; this ._cropper.onload = function (){ oThis.SetPos(); } this .Url = url; // 图片地址 this .Width = parseInt(width); // 宽度 this .Height = parseInt(height); // 高度 this .SetOptions(options); this .Opacity = parseInt( this .options.Opacity); this .dragTop = parseInt( this .options.dragTop); this .dragLeft = parseInt( this .options.dragLeft); this .dragWidth = parseInt( this .options.dragWidth); this .dragHeight = parseInt( this .options.dragHeight); // 设置预览对象 this .View = $( this .options.View) || null ; // 预览对象 this .viewWidth = parseInt( this .options.viewWidth); this .viewHeight = parseInt( this .options.viewHeight); this ._view = null ; // 预览图片对象 if ( this .View){ this .View.style.position = " relative " ; this .View.style.overflow = " hidden " ; this ._view = this .View.appendChild(document.createElement( " img " )); this ._view.style.position = " absolute " ; } this .Scale = parseInt( this .options.Scale); // 设置拖放 this ._drag = new Drag( this .Drag, this .Drag, { Limit: true , onMove: function (){ oThis.SetPos(); } }); // 设置缩放 this ._resize = this .GetResize(); this .Init(); }, // 设置默认属性 SetOptions: function (options) { this .options = { // 默认值 Opacity: 50 , // 透明度(0到100) // 拖放位置和宽高 dragTop: 0 , dragLeft: 0 , dragWidth: 100 , dragHeight: 100 , // 缩放触发对象 Right: "" , Left: "" , Up: "" , Down: "" , RightDown: "" , LeftDown: "" , RightUp: "" , LeftUp: "" , Scale: false , // 是否按比例缩放 // 预览对象设置 View: "" , // 预览对象 viewWidth: 100 , // 预览宽度 viewHeight: 100 // 预览高度 }; Object.extend( this .options, options || {}); }, // 初始化对象 Init: function () { var oThis = this ; // 设置容器 this .Container.style.width = this .Width + " px " ; this .Container.style.height = this .Height + " px " ; // 设置拖放对象 this .Drag.style.top = this .dragTop + " px " ; this .Drag.style.left = this .dragLeft + " px " ; this .Drag.style.width = this .dragWidth + " px " ; this .Drag.style.height = this .dragHeight + " px " ; // 设置切割对象 this ._pic.src = this ._cropper.src = this .Url; this ._pic.style.width = this ._cropper.style.width = this .Width + " px " ; this ._pic.style.height = this ._cropper.style.height = this .Height + " px " ; if (isIE){ this ._pic.style.filter = " alpha(opacity: " + this .Opacity + " ) " ; } else { this ._pic.style.opacity = this .Opacity / 100 ; } // 设置预览对象 if ( this .View){ this ._view.src = this .Url; } // 设置拖放 this ._drag.mxRight = this .Width; this ._drag.mxBottom = this .Height; // 设置缩放 if ( this ._resize){ this ._resize.mxRight = this .Width; this ._resize.mxBottom = this .Height; this ._resize.Scale = this .Scale; } }, // 设置获取缩放对象 GetResize: function () { var op = this .options; // 有触发对象时才设置 if (op.RightDown || op.LeftDown || op.RightUp || op.LeftUp || op.Right || op.Left || op.Up || op.Down ){ var oThis = this , _resize = new Resize( this .Drag, { Limit: true , onResize: function (){ oThis.SetPos(); } }); // 设置缩放触发对象 if (op.RightDown){ _resize.Set(op.RightDown, " right-down " ); } if (op.LeftDown){ _resize.Set(op.LeftDown, " left-down " ); } if (op.RightUp){ _resize.Set(op.RightUp, " right-up " ); } if (op.LeftUp){ _resize.Set(op.LeftUp, " left-up " ); } if (op.Right){ _resize.Set(op.Right, " right " ); } if (op.Left){ _resize.Set(op.Left, " left " ); } if (op.Up){ _resize.Set(op.Up, " up " ); } if (op.Down){ _resize.Set(op.Down, " down " ); } return _resize; } else { return null ; } }, // 设置切割 SetPos: function () { var o = this .Drag; // 按拖放对象的参数进行切割 this ._cropper.style.clip = " rect( " + o.offsetTop + " px " + (o.offsetLeft + o.offsetWidth) + " px " + (o.offsetTop + o.offsetHeight) + " px " + o.offsetLeft + " px) " ; // 切割预览 if ( this .View) this .PreView(); }, // 切割预览 PreView: function () { // 按比例设置宽度和高度 var o = this .Drag, h = this .viewWidth, w = h * o.offsetWidth / o.offsetHeight; if (w > this .viewHeight){ w = this .viewHeight; h = w * o.offsetHeight / o.offsetWidth; } // 获取对应比例尺寸 var scale = h / o.offsetHeight, ph = this .Height * scale, pw = this .Width * scale, pt = o.offsetTop * scale, pl = o.offsetLeft * scale, styleView = this ._view.style; // 设置样式 styleView.width = pw + " px " ; styleView.height = ph + " px " ; styleView.top = - pt + " px " ; styleView.left = - pl + " px " ; // 切割预览图 styleView.clip = " rect( " + pt + " px " + (pl + w) + " px " + (pt + h) + " px " + pl + " px) " ; } }
使用说明:
首先需要5个参数,分别是:容器对象、拖放对象、图片地址、图片宽度、图片高度。 可选设置: Opacity:透明度(0到100), dragTop:拖放对象top, dragLeft:拖放对象left, dragWidth:拖放对象宽度, dragHeight:拖放对象高度, 缩放触发对象: Right,Left,Up,Down,RightDown,LeftDown,RightUp,LeftUp, Scale:是否按比例缩放, View:预览对象, viewWidth:预览宽度, viewHeight:预览高度,
实例化对象:
Code
var ic = new ImgCropper( " bgDiv " , " dragDiv " , " 1.jpg " , 400 , 500 , { dragTop: 50 , dragLeft: 50 , Right: " rRight " , Left: " rLeft " , Up: " rUp " , Down: " rDown " , RightDown: " rRightDown " , LeftDown: " rLeftDown " , RightUp: " rRightUp " , LeftUp: " rLeftUp " , View: " viewDiv " , viewWidth: 200 , viewHeight: 200 })
可以根据需要扩展,例如:
Code
// 设置图片大小 function Size(w, h){ ic.Width = w; ic.Height = h; ic.Init(); } // 换图片 function Pic(url){ ic.Url = url; ic.Init(); } // 设置透明度 function Opacity(i){ ic.Opacity = i; ic.Init(); } // 是否使用比例 function Scale(b){ ic.Scale = b; ic.Init(); }
补充:
里面的Drag拖放程序和Resize缩放程序是可以独立出来用的, ImgCropper图片切割程序只是在内部实例化了这两个对象。
下载完整实例