序一
很久之前就在一個網站的截取相片的功能中看到這個效果,也叫圖片裁剪、圖片剪切(設置一下也可以做出放大鏡等類似的效果)。
當時覺得很神奇,礙于水平有限,沒做出來。
前些日子突然想做一個透鏡效果,就突然想到了這個效果,于是找出當年“珍藏”的代碼決定一嘗所愿。
序二
自上一個版本的圖片切割效果出來后,雖然也經常看到“框架開發這個如何如何容易”之類的評論,但也受到很多人的肯定,小弟在此感謝大家的支持。
上一個版本由于是初次接觸這類效果,而且是三個大功能一起開發,能力所限,所以僅僅是實現了效果就完成了。
后來我知道這個效果叫ImageCropper,找了些這類效果參考,完善了切割的部分。
近來我把其中的拖放效果和縮放效果單獨出來研究,經過整理和完善,再套進切割效果,個人感覺效果已經不錯了。
要說明的是這個只是一個效果,并不是真正的切割圖片,要獲取真正的切割圖片請參考圖片切割系統。
瀏覽效果請參考:http://www.cnblogs.com/cloudgamer/archive/2008/07/21/ImgCropper.html
(瀏覽完效果,你一定想知道其中的奧秘吧,那就聽我細細道來吧~)
程序說明
這個效果主要分三個部分:層的拖放、層的縮放、圖片切割(包括預覽)。
其中層的拖放和層的縮放我已經在其他兩篇文章中有詳細說明,這里就說說圖片切割這部分吧。
【圖片切割】
關于圖片切割的設計,有三個方法:
1.定位四個半透明層,遮住要蓋住的部分,沒試過,感覺比較麻煩;
2.把圖片設為背景圖,通過設置背景圖的位置來實現,但這樣的缺點是只能按圖片的正常比例實現,不夠靈活;
3.把圖片放到切割對象里面,通過設置圖片的top和left實現,這個方法是可行,但下面有更簡單的方法實現;
4.通過設置圖片的clip來實現。
這里介紹方法4的實現方法,這個方法是從當年“珍藏”的代碼中看到的,先說說clip:
clip的作用是“檢索或設置對象的可視區域。可視區域外的部分是透明的。”
依據上-右-下-左的順序提供自對象左上角為(0,0)坐標計算的四個偏移數值來剪切。
例如:
div { position:absolute; width:60px; height:60px; clip:rect(0 20 50 10); }
注意position:absolute的設置是必須的(詳細看手冊)。
下面說說具體實現原理:
首先需要一個容器(_Container),容器里面會插入三個層:
底圖層(_layBase):那個半透明的圖片;
切割層(_layCropper):正常顯示的那個部分;
控制層(_layHandle):就是控制顯示的那個部分。
其中為了底圖層和切割層是程序自動創建的圖片,控制層是自己定義的層(程序中是一個div)。
底圖層和切割層必須完全重合,程序中把這兩個層都絕對定位到了左上角:
this._layBase.style.top = this._layBase.style.left = this._layCropper.style.top = this._layCropper.style.left = 0;
層疊順序也要設置一下保證各層順序。
下面說說各部分的作用:
容器:除了容器本身的作用,通過設置其背景色來設置透明的漸變色,由于圖片本身沒有背景色所以要通過容器來設置;
底圖層:在容器最底部,作用是顯示非選擇區域的圖片,透明效果就是在這層設置;
切割層:最關鍵的一個層,在底圖層和控制層之間,在這個層通過clip設置其可視區域來實現切割圖片的效果;
控制層:位于頂部,拖放(_drag)和縮放(_resize)效果就是在這個層實現,根據其拖放和縮放的結果控制切割層的切割效果。
這里要注意的是控制層的_drag拖放效果的Transparent要設為true(詳細看拖放效果的透明背景bug部分)。
要使用縮放需要把Resize設為true,并設置各個拖拉對象,程序通過_resize設置縮放的比例和最少范圍(詳細看拖拉縮放效果)。
下面說說控制層如何控制切割效果:
控制層的拖放和縮放過程中加入了SetPos設置切割樣式程序,在SetPos程序中根據控制層的樣式設置切割層的可視區域范圍:
var p = this.GetPos();
this._layCropper.style.clip = "rect(" + p.Top + "px " + (p.Left + p.Width) + "px " + (p.Top + p.Height) + "px " + p.Left + "px)";
其中GetPos程序,它可以把當前控制層的樣式參數作為一個對象返回:
with(this._layHandle){
return { Top: offsetTop, Left: offsetLeft, Width: offsetWidth, Height: offsetHeight }
}
如果理解了的話就會覺得其實原理挺簡單的,不過要想出來還是要一定創意才行,為想出這個方法的人致敬!
【切割預覽】
預覽效果需要設置Preview屬性為預覽容器對象,程序會自動給容器插入一個預覽對象(圖片)。
預覽效果的關鍵在于如何根據控制層的數據來給預覽對象定位,這個主要在SetPreview預覽效果程序中處理。
首先根據控制層的高寬比置預覽對象顯示的寬和高(不是圖片本身的寬高哦),這里可以用GetSize程序獲取:
var p = this.GetPos(), s = this.GetSize(p.Width, p.Height, this.viewWidth, this.viewHeight), scale = s.Height / p.Height;
其中GetSize獲取尺寸程序可以根據圖片實際大小按比例縮放到要設置的大小:

Code
var iWidth = nowWidth, iHeight = nowHeight, scale = iWidth / iHeight;
//按比例設置
if(fixHeight){ iWidth = (iHeight = fixHeight) * scale; }
if(fixWidth && (!fixHeight || iWidth > fixWidth)){ iHeight = (iWidth = fixWidth) / scale; }
//返回尺寸對象
return { Width: iWidth, Height: iHeight }
可以看出如果后兩個參數(viewWidth和this.viewHeight)都不設置就會按原來大小顯示了,
然后再按預覽圖跟控制層的比例設置預覽圖的樣式參數:
var pHeight = this._layBase.height * scale, pWidth = this._layBase.width * scale, pTop = o.Top * scale, pLeft = o.Left * scale;
最后根據參數對預覽對象進行樣式設置和切割:

Code
with(this._view.style){
//設置樣式
width = pWidth + "px"; height = pHeight + "px"; top = - pTop + "px "; left = - pLeft + "px";
//切割預覽圖
clip = "rect(" + pTop + "px " + (pLeft + s.Width) + "px " + (pTop + s.Height) + "px " + pLeft + "px)";
}
這里有點煩亂,但應該不難理解就不詳細說明了。
【圖片大小】
容器的大小一般是固定的,但圖片的大小就不是固定的,這里又可以使用SetSize程序用來設置圖片大小:

Code
var s = this.GetSize(this._tempImg.width, this._tempImg.height, this.Width, this.Height);
//設置底圖和切割圖
this._layBase.style.width = this._layCropper.style.width = s.Width + "px";
this._layBase.style.height = this._layCropper.style.height = s.Height + "px";
有了圖片大小就可以用來設置拖放和縮放的范圍限制了:

Code
this._drag.mxRight = s.Width; this._drag.mxBottom = s.Height;
if(this.Resize){ this._resize.mxRight = s.Width; this._resize.mxBottom = s.Height; }
ps:程序中的Init程序是用來把個性設置呈現出來,所以一般設置過屬性之后(例如圖片地址、透明度等)就執行一次Init程序就可以顯示效果了。
【ie6渲染bug】
在拖放效果中說到插入一個div解決ie的透明背景bug,這里就需要修復這個bug。
跟縮放效果配合使用時,不得不說ie6的一個渲染bug,用下面的代碼測試(ie6):

Code
DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<body>
<div id="aa" style="width:300px;height:100px;border:1px solid;"><div style="background:#00f;height:100%;"></div></div>
<script>setTimeout("aa.style.height=200",0)</script>
</body>
</html>
可以對比ie7的效果,可以看出里面的div雖然高度設置了100%,但在外面的div修改高度之后,卻不知什么原因沒有填充了(外面的div渲染后,沒有觸發里面的div再渲染)。
解決這個bug的關鍵是使里面的div能再渲染,這里有幾個方法可以解決:
1,設置控制層的overflow為hidden,好處是只需要設置一次,但這個樣式會影響其他效果,當然在不影響效果的情況下這個是最好的方法;
2,在縮放的時候,通過修改控制層的某些樣式,使里面的div再渲染,這樣的樣式包括zoom,display(應該還有其他吧);
3,在縮放的時候,通過修改插入div的某些樣式,使其再渲染,這樣的樣式包括display(zoom也不行);
程序中是通過修改_layHandle的zoom解決的:
if(isIE6){ with(this._layHandle.style){ zoom = .9; zoom = 1; }; };
ps:這個bug的原因貌似跟ie的layout有關,但我查了一下還是沒弄清楚,誰有詳細的介紹或解析務必跟我分享一下啊。
使用說明
實例化時有三個必要參數:容器對象、控制層、圖片地址:
var ic = new ImgCropper("bgDiv", "dragDiv", "1.jpg");
有以下這些可選參數和屬性:
屬性:默認值//說明
Opacity: 50,//透明度(0到100)
Color: "",//背景色
Width: 0,//圖片高度
Height: 0,//圖片高度
//縮放觸發對象
Resize: false,//是否設置縮放
Right: "",//右邊縮放對象
Left: "",//左邊縮放對象
Up: "",//上邊縮放對象
Down: "",//下邊縮放對象
RightDown: "",//右下縮放對象
LeftDown: "",//左下縮放對象
RightUp: "",//右上縮放對象
LeftUp: "",//左上縮放對象
Min: false,//是否最小寬高限制(為true時下面min參數有用)
minWidth: 50,//最小寬度
minHeight: 50,//最小高度
Scale: false,//是否按比例縮放
Ratio: 0,//縮放比例(寬/高)
//預覽對象設置
Preview: "",//預覽對象
viewWidth: 0,//預覽寬度
viewHeight: 0//預覽高度
程序代碼

Code
//圖片切割
var ImgCropper = Class.create();
ImgCropper.prototype = {
//容器對象,控制層,圖片地址
initialize: function(container, handle, url, options) {
this._Container = $(container);//容器對象
this._layHandle = $(handle);//控制層
this.Url = url;//圖片地址
this._layBase = this._Container.appendChild(document.createElement("img"));//底層
this._layCropper = this._Container.appendChild(document.createElement("img"));//切割層
this._layCropper.onload = Bind(this, this.SetPos);
//用來設置大小
this._tempImg = document.createElement("img");
this._tempImg.onload = Bind(this, this.SetSize);
this.SetOptions(options);
this.Opacity = Math.round(this.options.Opacity);
this.Color = this.options.Color;
this.Scale = !!this.options.Scale;
this.Ratio = Math.max(this.options.Ratio, 0);
this.Width = Math.round(this.options.Width);
this.Height = Math.round(this.options.Height);
//設置預覽對象
var oPreview = $(this.options.Preview);//預覽對象
if(oPreview){
oPreview.style.position = "relative";
oPreview.style.overflow = "hidden";
this.viewWidth = Math.round(this.options.viewWidth);
this.viewHeight = Math.round(this.options.viewHeight);
//預覽圖片對象
this._view = oPreview.appendChild(document.createElement("img"));
this._view.style.position = "absolute";
this._view.onload = Bind(this, this.SetPreview);
}
//設置拖放
this._drag = new Drag(this._layHandle, { Limit: true, onMove: Bind(this, this.SetPos), Transparent: true });
//設置縮放
this.Resize = !!this.options.Resize;
if(this.Resize){
var op = this.options, _resize = new Resize(this._layHandle, { Max: true, onResize: Bind(this, this.SetPos) });
//設置縮放觸發對象
op.RightDown && (_resize.Set(op.RightDown, "right-down"));
op.LeftDown && (_resize.Set(op.LeftDown, "left-down"));
op.RightUp && (_resize.Set(op.RightUp, "right-up"));
op.LeftUp && (_resize.Set(op.LeftUp, "left-up"));
op.Right && (_resize.Set(op.Right, "right"));
op.Left && (_resize.Set(op.Left, "left"));
op.Down && (_resize.Set(op.Down, "down"));
op.Up && (_resize.Set(op.Up, "up"));
//最小范圍限制
this.Min = !!this.options.Min;
this.minWidth = Math.round(this.options.minWidth);
this.minHeight = Math.round(this.options.minHeight);
//設置縮放對象
this._resize = _resize;
}
//設置樣式
this._Container.style.position = "relative";
this._Container.style.overflow = "hidden";
this._layHandle.style.zIndex = 200;
this._layCropper.style.zIndex = 100;
this._layBase.style.position = this._layCropper.style.position = "absolute";
this._layBase.style.top = this._layBase.style.left = this._layCropper.style.top = this._layCropper.style.left = 0;//對齊
//初始化設置
this.Init();
},
//設置默認屬性
SetOptions: function(options) {
this.options = {//默認值
Opacity: 50,//透明度(0到100)
Color: "",//背景色
Width: 0,//圖片高度
Height: 0,//圖片高度
//縮放觸發對象
Resize: false,//是否設置縮放
Right: "",//右邊縮放對象
Left: "",//左邊縮放對象
Up: "",//上邊縮放對象
Down: "",//下邊縮放對象
RightDown: "",//右下縮放對象
LeftDown: "",//左下縮放對象
RightUp: "",//右上縮放對象
LeftUp: "",//左上縮放對象
Min: false,//是否最小寬高限制(為true時下面min參數有用)
minWidth: 50,//最小寬度
minHeight: 50,//最小高度
Scale: false,//是否按比例縮放
Ratio: 0,//縮放比例(寬/高)
//預覽對象設置
Preview: "",//預覽對象
viewWidth: 0,//預覽寬度
viewHeight: 0//預覽高度
};
Extend(this.options, options || {});
},
//初始化對象
Init: function() {
//設置背景色
this.Color && (this._Container.style.backgroundColor = this.Color);
//設置圖片
this._tempImg.src = this._layBase.src = this._layCropper.src = this.Url;
//設置透明
if(isIE){
this._layBase.style.filter = "alpha(opacity:" + this.Opacity + ")";
} else {
this._layBase.style.opacity = this.Opacity / 100;
}
//設置預覽對象
this._view && (this._view.src = this.Url);
//設置縮放
if(this.Resize){
with(this._resize){
Scale = this.Scale; Ratio = this.Ratio; Min = this.Min; minWidth = this.minWidth; minHeight = this.minHeight;
}
}
},
//設置切割樣式
SetPos: function() {
//ie6渲染bug
if(isIE6){ with(this._layHandle.style){ zoom = .9; zoom = 1; }; };
//獲取位置參數
var p = this.GetPos();
//按拖放對象的參數進行切割
this._layCropper.style.clip = "rect(" + p.Top + "px " + (p.Left + p.Width) + "px " + (p.Top + p.Height) + "px " + p.Left + "px)";
//設置預覽
this.SetPreview();
},
//設置預覽效果
SetPreview: function() {
if(this._view){
//預覽顯示的寬和高
var p = this.GetPos(), s = this.GetSize(p.Width, p.Height, this.viewWidth, this.viewHeight), scale = s.Height / p.Height;
//按比例設置參數
var pHeight = this._layBase.height * scale, pWidth = this._layBase.width * scale, pTop = p.Top * scale, pLeft = p.Left * scale;
//設置預覽對象
with(this._view.style){
//設置樣式
width = pWidth + "px"; height = pHeight + "px"; top = - pTop + "px "; left = - pLeft + "px";
//切割預覽圖
clip = "rect(" + pTop + "px " + (pLeft + s.Width) + "px " + (pTop + s.Height) + "px " + pLeft + "px)";
}
}
},
//設置圖片大小
SetSize: function() {
var s = this.GetSize(this._tempImg.width, this._tempImg.height, this.Width, this.Height);
//設置底圖和切割圖
this._layBase.style.width = this._layCropper.style.width = s.Width + "px";
this._layBase.style.height = this._layCropper.style.height = s.Height + "px";
//設置拖放范圍
this._drag.mxRight = s.Width; this._drag.mxBottom = s.Height;
//設置縮放范圍
if(this.Resize){ this._resize.mxRight = s.Width; this._resize.mxBottom = s.Height; }
},
//獲取當前樣式
GetPos: function() {
with(this._layHandle){
return { Top: offsetTop, Left: offsetLeft, Width: offsetWidth, Height: offsetHeight }
}
},
//獲取尺寸
GetSize: function(nowWidth, nowHeight, fixWidth, fixHeight) {
var iWidth = nowWidth, iHeight = nowHeight, scale = iWidth / iHeight;
//按比例設置
if(fixHeight){ iWidth = (iHeight = fixHeight) * scale; }
if(fixWidth && (!fixHeight || iWidth > fixWidth)){ iHeight = (iWidth = fixWidth) / scale; }
//返回尺寸對象
return { Width: iWidth, Height: iHeight }
}
}
完整實例下載