上一篇imagezoom 已經對圖檔放大效果做了詳細的分析,這次在imagezoom的基礎上進行擴充,實作更多的效果。
主要擴充了原圖和顯示框的展示模式,有以下幾種模式:
"follow" 跟随模式:顯示框能跟随滑鼠移動的效果;
"handle" 拖柄模式:原圖上有一個拖柄來标記顯示範圍;
"cropper" 切割模式:原圖用不透明的來标記顯示範圍,其他部分用半透明顯示;
"handle-cropper" 拖柄切割模式:拖柄模式和切割模式的混合版,同時用透明度和拖柄來标記顯示範圍。
當然更多的擴充等待你的想象力來發掘。
相容:ie6/7/8, firefox 3.6.2, opera 10.51, safari 4.0.4, chrome 4.1
效果預覽
模式選擇:
一般模式 跟随模式 搖桿模式 切割模式 搖桿切割模式

仿凡客誠品(vancl)商品圖檔放大效果
程式說明
【擴充模式】
上次imageslazyload 使用了繼承做擴充,這次用插件的形式來做擴充。
先看看基礎模式,這些模式是儲存在imagezoom._mode中的,類似這樣的結構:
imagezoom._mode = {
模式名: {
options: {
...
},
methods: {
init: function() {
...
},
}
},
...
}
其中模式名就是基礎模式的名字,options是可選參數擴充,methods是程式結構的擴充。
基礎模式包含"follow", "handle"和"cropper"模式,後面再詳細介紹。
methods包含要擴充的鈎子程式,是擴充的主要部分。
ps:這裡說的模式不是“設計模式”裡面的模式。
擴充需要在程式初始化時進行,要放在_initialize程式之前執行。
為了不影響原程式的結構,這裡用織入法在_initialize之前插入一段程式:
imagezoom.prototype._initialize = (function(){
var init = imagezoom.prototype._initialize,
...;
return function(){
...
init.apply( this, arguments );
}
})();
原理就是先儲存原來的函數,插入一段程式組成新函數,然後重新替換原來的函數。
考慮到組合基礎模式的情況,使用了一個對象儲存真正使用的模式:
mode = imagezoom._mode,
modes = {
"follow": [ mode.follow ],
"handle": [ mode.handle ],
"cropper": [ mode.cropper ],
"handle-cropper": [ mode.handle, mode.cropper ]
};
可以看到"handle-cropper"模式其實就是"handle"和"cropper"的組合模式。
插入的程式的主要任務是根據設定好的基礎模式,進行擴充:
var options = arguments[2];
if ( options && options.mode && modes[ options.mode ] ) {
$$a.foreach( modes[ options.mode ], function( mode ){
$$.extend( options, mode.options, false );
$$a.foreach( mode.methods, function( method, name ){
$$ce.addevent( this, name, method );
}, this );
}, this );
}
首先擴充options可選參數對象,由于可選參數是第三個參數,是以用arguments[2]擷取。
extend的第三個參數設為false,說明不重寫相同屬性,即保留自定義的屬性值。
然後把methods裡面的方法作為鈎子函數逐個添加到程式中。
methods可以包含init, load, start, repair, move, end, dispose這幾個方法,分别對應imagezoom中初始化、加載、開始、修正、移動、結束和銷毀事件。
在擴充時,不同的事件執行不同的任務:
init初始化函數:用來設定擴充需要的屬性,注意這些屬性不要跟imagezoom本身的屬性沖突了,即重名。
load加載函數:圖檔加載完成,相關參數也設定完成,主要做執行放大效果前的準備工作。
start開始函數:觸發放大效果時執行的。
repair修正函數:用于修正大圖定位的坐标值。
move移動函數:觸發放大效果後,滑鼠移動時執行的。
end結束函數就:結束放大效果時執行的。
dispose銷毀函數:在移除程式時清理程式的。
ps:具體位置參考imagezoom中使用$$ce.fireevent的部分。
可以看到這裡用了織入法(weave)和鈎子法(hook)對程式做擴充。
織入法是一種aop,可以在不改變原程式的基礎上進行擴充,但隻能在函數前面或後面加入程式。
而鈎子法必須在原程式中設定好對應鈎子才能配合使用,但位置相對靈活。
【跟随模式】
在"follow"跟随模式中,進行放大時,顯示框會跟随滑鼠移動,就像拿着放大鏡看的效果。
首先顯示框要絕對定位,要實作顯示框跟随滑鼠移動,隻要在move中設定對應的left/top就行了:
var style = this._viewer.style;
style.left = e.pagex - this._repairfollowleft + "px";
style.top = e.pagey - this._repairfollowtop + "px";
其中pagex/pagey是滑鼠目前的坐标,_repairfollowleft/_repairfollowtop是坐标的修正參數。
修正參數是在load中設定的,如果顯示框隐藏的話,用上一篇擷取顯示範圍的方法擷取參數:
if ( !viewer.offsetwidth ) {
styles = { display: style.display, visibility: style.visibility };
$$d.setstyle( viewer, { display: "block", visibility: "hidden" });
...
if ( styles ) { $$d.setstyle( viewer, styles ); }
為了跟随時,讓滑鼠固定在顯示框中心位置,先根據顯示框的offsetwidth/offsetheight修正參數:
this._repairfollowleft = viewer.offsetwidth / 2;
this._repairfollowtop = viewer.offsetheight / 2;
如果顯示框的offsetparent不是body,還需要根據offsetparent修正坐标:
if ( !/body|html/.test( viewer.offsetparent.nodename ) ) {
var parent = viewer.offsetparent, rect = $$d.rect( parent );
this._repairfollowleft += rect.left + parent.clientleft;
this._repairfollowtop += rect.top + parent.clienttop;
}
ps:在maxthon測試時發現body子元素的offsetparent不是body而是html。
為了在移除程式後,能恢複顯示框的樣式,在load程式中做了樣式的備份:
var viewer = this._viewer, style = viewer.style, styles;
this._stylesfollow = {
left: style.left, top: style.top, position: style.position
并在dispose中恢複:
$$d.setstyle( this._viewer, this._stylesfollow );
現在已經達到了基本的效果,但由于大圖移動範圍的限制,當滑鼠移動到接近邊界時,大圖就卡住不會動了。
為了實作在滑鼠移動時,大圖會持續變化的效果,在repair中修正了移動坐标:
pos.left = ( viewerwidth / 2 - pos.left ) * ( viewerwidth / zoom.width - 1 );
pos.top = ( viewerheight / 2 - pos.top ) * ( viewerheight / zoom.height - 1 );
原理稍有些複雜,以水準坐标為例,先看下圖:
大方框代表大圖對象,小方框代表顯示框。
目前位置是根據滑鼠坐标得到的實際顯示的位置,目标位置想要實作效果的位置。
有一些實體或幾何知識應該明白這個等式:x / y = m / n
可以推出:y = x * n / m = x * ( zoom.width - viewerwidth ) / zoom.height
x目前坐标可以通過pos.left來得到:x = viewerwidth / 2 - pos.left
最後得到:left = -y = ( viewerwidth / 2 - pos.left ) * ( viewerwidth / zoom.width - 1 )
垂直坐标也差不多。
【拖柄模式】
拖柄是一個層,在原圖上面,用來表示顯示範圍在原圖的位置和範圍。
顯示範圍可以根據_rangewidth/_rangeheight擷取。
至于位置的指定可以根據滑鼠坐标或大圖定位坐标來設定。
如果滑鼠坐标的話還必須做其他處理,例如範圍控制,而根據大圖定位坐标相對就友善準确得多,程式也是用後面一個方法。
首先在init定義一個_handle拖柄對象:
if ( !handle ) {
var body = document.body;
handle = body.insertbefore(this._viewer.clonenode(false), body.childnodes[0]);
handle.id = "";
handle["_createbyhandle"] = true;
} else {
var style = handle.style;
this._styleshandle = {
left: style.left, top: style.top, position: style.position,
display: style.display, visibility: style.visibility,
padding: style.padding, margin: style.margin,
width: style.width, height: style.height
};
如果有自定義拖柄對象,就像跟随模式那樣備份拖柄的樣式。
否則就複制顯示框作為拖柄對象,并添加"_createbyhandle"屬性作标記,友善在dispose中移除。
在load時,設定拖柄樣式:
$$d.setstyle( handle, {
position: "absolute",
width: this._rangewidth + "px",
height: this._rangeheight + "px",
display: "block",
visibility: "hidden"
});
絕對定位是必須的,并根據_rangewidth/_rangeheight設定尺寸。
設定display和visibility是為了下面擷取參數。
先根據原圖坐标擷取修正參數:
this._repairhandleleft = rect.left + this._repairleft - handle.clientleft;
this._repairhandletop = rect.top + this._repairtop - handle.clienttop;
和跟随模式類似,也要做offsetparent定位的修正:
if ( handle.offsetparent.nodename.touppercase() != "body" ) {
var parent = handle.offsetparent, rect = $$d.rect( parent );
this._repairhandleleft -= rect.left + parent.clientleft;
this._repairhandletop -= rect.top + parent.clienttop;
然後重新隐藏:
$$d.setstyle( handle, { display: "none", visibility: "visible" });
在start時,顯示拖柄對象。 在move時,根據大圖定位坐标設定拖柄定位:
var style = this._handle.style, scale = this._scale;
style.left = math.ceil( this._repairhandleleft - x / scale ) + "px";
style.top = math.ceil( this._repairhandletop - y / scale ) + "px";
在end時,隐藏拖柄對象。
【切割模式】
“切割”就是選擇的部分全透明,其他部分半透明的效果。
為了實作切割效果,需要在init中建立一個_cropper切割層:
var body = document.body,
cropper = body.insertbefore(document.createelement("img"), body.childnodes[0]);
cropper.style.display = "none";
并在load中設定這個切割層:
cropper.src = image.src;
cropper.width = image.width;
cropper.height = image.height;
$$d.setstyle( cropper, {
left: rect.left + this._repairleft + "px",
top: rect.top + this._repairtop + "px"
差不多是複制一個原圖對象,并且絕對定位到原圖對象上面。
在start時,顯示切割層,并根據透明度設定原圖為半透明狀态。
在move時,根據大圖移動的坐标設定切割層要clip的範圍:
var w = this._rangewidth, h = this._rangeheight, scale = this._scale;
x = math.ceil( -x / scale ); y = math.ceil( -y / scale );
this._cropper.style.clip = "rect(" + y + "px " + (x + w) + "px " + (y + h) + "px " + x + "px)";
在end時,隐藏切割層,并重新設定原圖為不透明,來恢複原來的狀态。
還要記得在dispose中移除切割層。
使用技巧
需要擴充的效果時才需要添加這個擴充程式。
可自行對imagezoom._mode進行擴充,擴充後記得在modes添加對應模式。
可以組合多個基礎模式同時使用,具體參考"handle-cropper"模式。
使用說明
使用方法跟imagezoom差不多,隻是多了一個可選參考mode設定顯示模式。
使用"handle"模式時,可選參數的"handle"屬性可以設定拖柄對象。
使用"cropper"模式時,可選參數的"opacity"屬性可以設定透明度。
使用"handle-cropper"模式時,以上兩個參數都可以使用。