天天看點

ImageZoom 圖檔放大效果(擴充篇)

主要擴充了原圖和顯示框的展示模式,有以下幾種模式:

"follow" 跟随模式:顯示框能跟随滑鼠移動的效果;

"handle" 拖柄模式:原圖上有一個拖柄來标記顯示範圍;

"cropper" 切割模式:原圖用不透明的來标記顯示範圍,其他部分用半透明顯示;

"handle-cropper" 拖柄切割模式:拖柄模式和切割模式的混合版,同時用透明度和拖柄來标記顯示範圍。

當然更多的擴充等待你的想象力來發掘。

相容:ie6/7/8, firefox 3.6.2, opera 10.51, safari 4.0.4, chrome 4.1

效果預覽

模式選擇:

 一般模式  跟随模式  搖桿模式  切割模式  搖桿切割模式 

仿凡客誠品(vancl)商品圖檔放大效果

ImageZoom 圖檔放大效果(擴充篇)

程式說明

【擴充模式】

先看看基礎模式,這些模式是儲存在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"模式時,以上兩個參數都可以使用。

程式源碼  

    //跟随

    "follow": {

                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:        ""//拖柄對象

                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;//生成辨別用于移除

                } 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

                    };

                $$D.setStyle( handle, { padding: 0, margin: 0, display: "none" } );

                this._handle = handle;

                this._repairHandleLeft = 0;//修正坐标left

                this._repairHandleTop = 0;//修正坐标top

                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;

                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";

                if( "_createbyhandle" in this._handle ){

                    document.body.removeChild( this._handle );

                    $$D.setStyle( this._handle, this._stylesHandle );

                this._handle = null;

    //切割

    "cropper": {

            opacity:    .5//透明度

                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;

                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, {

                    left: rect.left + this._repairLeft + "px",

                    top: rect.top + this._repairTop + "px"

                this._cropper.style.display = "block";

                $$D.setStyle( this._image, "opacity", this.opacity );

                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)";

                $$D.setStyle( this._image, "opacity", 1 );

                this._cropper.style.display = "none";

                document.body.removeChild( this._cropper );

                this._cropper = null;

        mode = ImageZoom._MODE,

        modes = {

            "follow": [ mode.follow ],

            "handle": [ mode.handle ],

            "cropper": [ mode.cropper ],

            "handle-cropper": [ mode.handle, mode.cropper ]

        };

        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 );

<a href="http://files.cnblogs.com/cloudgamer/ImageZoom_ext.rar" target="_blank">完整執行個體下載下傳</a>

繼續閱讀