ImageZoom 图片放大镜效果(多功能扩展篇)


Posted in Javascript onApril 14, 2010

主要扩展了原图和显示框的展示模式,有以下几种模式:
"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 );

原理稍有些复杂,以水平坐标为例,先看下图:
ImageZoom 图片放大镜效果(多功能扩展篇)
大方框代表大图对象,小方框代表显示框。
当前位置是根据鼠标坐标得到的实际显示的位置,目标位置想要实现效果的位置。
有一些物理或几何知识应该明白这个等式: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.3water.com/js/ImageZoom_ext/ImageZoom_ext.htm
打包下载地址https://3water.com/jiaoben/25809.html
出处:http://www.cnblogs.com/cloudgamer/
Javascript 相关文章推荐
js 设置选中行的样式的实现代码
May 24 Javascript
JS获取select-option-text_value的方法
Dec 26 Javascript
页面加载完毕后滚动条自动滚动一定位置
Feb 20 Javascript
在浏览器中实现图片粘贴的jQuery插件-- pasteimg使用指南
Dec 29 Javascript
原生javascript实现图片弹窗交互效果
Jan 12 Javascript
JavaScript实现在页面间传值的方法
Apr 07 Javascript
实用又漂亮的BootstrapValidator表单验证插件
May 30 Javascript
微信小程序中的onLoad详解及简单实例
Apr 05 Javascript
node.js 抓取代理ip实例代码
Apr 30 Javascript
JQuery通过后台获取数据遍历到前台的方法
Aug 13 jQuery
js实现ATM机存取款功能
Oct 27 Javascript
JavaScript canvas实现雪花随机动态飘落
Feb 08 Javascript
JavaScript中的prototype使用说明
Apr 13 #Javascript
Js 刷新框架页的代码
Apr 13 #Javascript
百度Popup.js弹出框进化版 拖拽小框架发布 兼容IE6/7/8,Firefox,Chrome
Apr 13 #Javascript
JavaScript在IE和Firefox(火狐)的不兼容问题解决方法小结
Apr 13 #Javascript
JavaScript 数组运用实现代码
Apr 13 #Javascript
关于Jqzoom的使用心得 jquery放大镜效果插件
Apr 12 #Javascript
Javascript 判断Flash是否加载完成的代码
Apr 12 #Javascript
You might like
BBS(php & mysql)完整版(七)
2006/10/09 PHP
通过具体程序来理解PHP里面的抽象类
2010/01/28 PHP
支持数组的ADDSLASHES的php函数
2010/02/16 PHP
php入门学习知识点四 PHP正则表达式基本应用
2011/07/14 PHP
php rsa加密解密使用详解
2015/01/14 PHP
php准确计算复活节日期的方法
2015/04/18 PHP
启用Csrf后POST数据时出现的400错误
2015/07/05 PHP
PHP实现生成唯一会员卡号
2015/08/24 PHP
jQuery 创建Dom元素
2010/05/07 Javascript
express的中间件basicAuth详解
2014/12/04 Javascript
基于canvas实现的绚丽圆圈效果完整实例
2016/01/26 Javascript
jquery实现简单的banner轮播效果【实例】
2016/03/30 Javascript
JQuery控制图片由中心点逐渐放大效果
2016/06/26 Javascript
针对JavaScript中this指向的简单理解
2016/08/26 Javascript
使用canvas及js简单生成验证码方法
2017/04/02 Javascript
Vue官网todoMVC示例代码
2018/01/29 Javascript
jQuery NProgress.js加载进度插件的简单使用方法
2018/01/31 jQuery
jQuery AJAX 方法success()后台传来的4种数据详解
2018/08/08 jQuery
策略模式实现 Vue 动态表单验证的方法
2019/09/16 Javascript
基于layui框架响应式布局的一些使用详解
2019/09/16 Javascript
vue 实现单选框设置默认选中值
2019/11/07 Javascript
VSCode 添加自定义注释的方法(附带红色警戒经典注释风格)
2020/08/27 Javascript
element-ui点击查看大图的方法示例
2020/12/14 Javascript
Python完成毫秒级抢淘宝大单功能
2019/06/06 Python
详解Django 时间与时区设置问题
2019/07/23 Python
Ralph Lauren法国官网:美国高品味时装品牌
2017/12/08 全球购物
DJI美国:消费类无人机领域的领导者
2018/04/27 全球购物
党员教师个人对照检查材料范文
2014/09/25 职场文书
2014年财务部工作总结
2014/11/11 职场文书
店铺转让协议书
2014/12/02 职场文书
铁路安全反思材料
2014/12/24 职场文书
先进个人评语大全
2015/01/04 职场文书
关于颐和园的导游词
2015/01/30 职场文书
老乡聚会通知
2015/04/23 职场文书
适合毕业生创业的项目怎么找?
2019/08/08 职场文书
PostgreSQL通过oracle_fdw访问Oracle数据的实现步骤
2021/05/21 PostgreSQL