ImageZoom 圖片放大鏡效果(多功能擴充篇)_javascript技巧

來源:互聯網
上載者:User
主要擴充了原圖和顯示框的展示模式,有以下幾種模式:
"follow" 跟隨模式:顯示框能跟隨滑鼠移動的效果;
"handle" 拖柄模式:原圖上有一個拖柄來標記顯示範圍;
"cropper" 切割模式:原圖用不透明的來標記顯示範圍,其他部分用半透明顯示;
"handle-cropper" 拖柄切割模式:拖柄模式和切割模式的混合版,同時用透明度和拖柄來標記顯示範圍。
當然更多的擴充等待你的想象力來發掘。
相容:ie6/7/8, firefox 3.6.2, opera 10.51, safari 4.0.4, chrome 4.1

程式說明

【擴充模式】

上次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拖柄對象:
複製代碼 代碼如下:

var handle = $$( this.options.handle );
if ( !handle ) {
var body = document.body;
handle = body.insertBefore(this._viewer.cloneNode(false), body.childNodes[0]);
handle.id = "";
handle["_createbyhandle"] = true;
}
$$D.setStyle( handle, { padding: 0, margin: 0, display: "none" } );

如果沒有自訂拖柄對象,會複製顯示框作為拖柄對象。
對於自動產生的拖柄對象,會添加"_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時,隱藏拖柄對象。


【切割模式】

“切割”就是選擇的部分全透明,其他部分半透明的效果。
主要通過clip來實現,具體原理可以看圖片切割效果。

為了實現切割效果,需要在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, {
position: "absolute",
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"模式時,以上兩個參數都可以使用。
程式源碼
複製代碼 代碼如下:

ImageZoom._MODE = {
//跟隨
"follow": {
methods: {
init: function() {
this._stylesFollow = null;//備份樣式
this._repairFollowLeft = 0;//修正座標left
this._repairFollowTop = 0;//修正座標top
},
load: function() {
var viewer = this._viewer, style = viewer.style, styles;
this._stylesFollow = {
left: style.left, top: style.top, position: style.position
};
viewer.style.position = "absolute";
//擷取修正參數
if ( !viewer.offsetWidth ) {//隱藏
styles = { display: style.display, visibility: style.visibility };
$$D.setStyle( viewer, { display: "block", visibility: "hidden" });
}
//修正中心位置
this._repairFollowLeft = viewer.offsetWidth / 2;
this._repairFollowTop = viewer.offsetHeight / 2;
//修正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;
}
if ( styles ) { $$D.setStyle( viewer, styles ); }
},
repair: function(e, pos) {
var zoom = this._zoom,
viewerWidth = this._viewerWidth,
viewerHeight = this._viewerHeight;
pos.left = ( viewerWidth / 2 - pos.left ) * ( viewerWidth / zoom.width - 1 );
pos.top = ( viewerHeight / 2 - pos.top ) * ( viewerHeight / zoom.height - 1 );
},
move: function(e) {
var style = this._viewer.style;
style.left = e.pageX - this._repairFollowLeft + "px";
style.top = e.pageY - this._repairFollowTop + "px";
},
dispose: function() {
$$D.setStyle( this._viewer, this._stylesFollow );
}
}
},
//拖柄
"handle": {
options: {//預設值
handle: ""//拖柄對象
},
methods: {
init: function() {
var handle = $$( this.options.handle );
if ( !handle ) {//沒有定義的話用複製顯示框代替
var body = document.body;
handle = body.insertBefore(this._viewer.cloneNode(false), body.childNodes[0]);
handle.id = "";
handle["_createbyhandle"] = true;//產生標識用於移除
}
$$D.setStyle( handle, { padding: 0, margin: 0, display: "none" } );

this._handle = handle;
this._repairHandleLeft = 0;//修正座標left
this._repairHandleTop = 0;//修正座標top
},
load: function() {
var handle = this._handle, rect = this._rect;
$$D.setStyle( handle, {
position: "absolute",
width: this._rangeWidth + "px",
height: this._rangeHeight + "px",
display: "block",
visibility: "hidden"
});
//擷取修正參數
this._repairHandleLeft = rect.left + this._repairLeft - handle.clientLeft;
this._repairHandleTop = rect.top + this._repairTop - handle.clientTop;
//修正offsetParent位置
if ( !/BODY|HTML/.test( handle.offsetParent.nodeName ) ) {
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: function() {
this._handle.style.display = "block";
},
move: function(e, x, y) {
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: function() {
this._handle.style.display = "none";
},
dispose: function() {
if( "_createbyhandle" in this._handle ){ document.body.removeChild( this._handle ); }
this._handle = null;
}
}
},
//切割
"cropper": {
options: {//預設值
opacity: .5//透明度
},
methods: {
init: function() {
var body = document.body,
cropper = body.insertBefore(document.createElement("img"), body.childNodes[0]);
cropper.style.display = "none";

this._cropper = cropper;
this.opacity = this.options.opacity;
},
load: function() {
var cropper = this._cropper, image = this._image, rect = this._rect;
cropper.src = image.src;
cropper.width = image.width;
cropper.height = image.height;
$$D.setStyle( cropper, {
position: "absolute",
left: rect.left + this._repairLeft + "px",
top: rect.top + this._repairTop + "px"
});
},
start: function() {
this._cropper.style.display = "block";
$$D.setStyle( this._image, "opacity", this.opacity );
},
move: function(e, x, y) {
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: function() {
$$D.setStyle( this._image, "opacity", 1 );
this._cropper.style.display = "none";
},
dispose: function() {
document.body.removeChild( this._cropper );
this._cropper = null;
}
}
}
}

ImageZoom.prototype._initialize = (function(){
var init = ImageZoom.prototype._initialize,
mode = ImageZoom._MODE,
modes = {
"follow": [ mode.follow ],
"handle": [ mode.handle ],
"cropper": [ mode.cropper ],
"handle-cropper": [ mode.handle, mode.cropper ]
};
return function(){
var options = arguments[2];
if ( options && options.mode && modes[ options.mode ] ) {
$$A.forEach( modes[ options.mode ], function( mode ){
//擴充options
$$.extend( options, mode.options, false );
//擴充鉤子
$$A.forEach( mode.methods, function( method, name ){
$$CE.addEvent( this, name, method );
}, this );
}, this );
}
init.apply( this, arguments );
}
})();

線上示範地址http://demo.jb51.net/js/ImageZoom_ext/ImageZoom_ext.htm
打包下載地址http://www.jb51.net/jiaoben/25809.html
出處:http://www.cnblogs.com/cloudgamer/

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.