ImageZoom 图片放大效果
这个效果也叫放大镜效果,最早好像在ppg出现的,之后就有了很多仿制品出来了。 一般用于放大查看商品图片,在凡客,京东商城,阿里巴巴等都有类似的效果。 好处是能在原图附近对图片进行局部放大查看,而且可以通过鼠标控制查看的部位。 前一阵子看到sohighthesky的图片放大效果,心血来潮自己也写一个看看。 这个程序有以下特点: 1,支持使用原图放大或新图片设置大图; 2,大图完成载入前使用原图放大代替,减少操作等待时间; 3,支持鼠标滚动缩放大图; 4,可以通过设置显示范围或显示框大小设置显示尺寸; 5,可以设置是否自动隐藏显示框; 下一篇扩展篇再介绍更多的功能。 兼容:ie6/7/8, firefox 3.6.2, opera 10.51, safari 4.0.4, chrome 4.1
效果预览
![](https://images.cnblogs.com/cnblogs_com/cloudgamer/143727/o_vivian.jpg)
原图: 大图:
程序说明
【基本原理】
首先要有一个img元素作为原图对象,还要有一个容器作为显示框。 显示框里面放另一个img元素作为大图对象,并根据比例设置好尺寸。 当鼠标在原图上移动时,通过对大图进行绝对定位来显示对应的部位,实现类似放大镜的效果。
【图片加载】
程序初始化时会先执行_initLoad程序,主要用来载入图片。 由于img在载入之前获取尺寸是不准确的,所以相关参数的计算都要等到图片加载之后。
有两种放大的方式:使用原图放大、用已经放大的新图片。 原图放大的好处是只需加载一个图片,而使用新图片就可以得到更清晰的效果。 根据不同的方式,会选择对应的原图加载程序:
当没有设置大图但有放大比例时,会自动使用原图放大加载程序。
先看看使用原图放大加载的过程: 1,加载原图:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
_originPic属性记录原图地址,如果设置了原图并且跟元素当前加载的图片不同,就设置onload并加载原图。 否则,如果元素当前有加载图片的话,先通过complete判断是否加载完成,没完成就设置onload,已经完成的话就直接执行加载程序。 最后,没有原图图片的话就退出程序。 2,执行_loadOriginImage加载程序:
由于ie6/7的gif图片载入bug,会先重置onload。 然后执行_initLoaded初始化加载设置程序。
使用新图片就复杂一点: 1,加载原图,同上。 2,预载大图:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
_preload是预载对象,使用的是_loadPreload预载加载程序。 预载对象主要用来获取大图的原始尺寸,也是后面原图替换技巧的基础。 如果没有设置大图,说明当前没有设置大图也又没有放大比例,这时就用原图作为大图来用。 加载的方法跟原图类似。 3,当原图加载完成时,执行_loadImage原图加载程序:
如果_loaded为true,说明大图已经加载,直接执行_initLoaded程序。 否则设置_loaded为true来标记原图已经加载,如果这时有自定义比例的话,先用原图替换大图。 因为一般大图加载会比较慢,先用原图替换就能立刻操作了,同时设置_substitute属性为true标记使用了替换。 4,当大图预载完成时,执行_loadPreload大图预载程序:
如果_loaded是true,说明原图已经加载完,同时_substitute为false即没有使用原图替换的话,就执行_initLoaded程序。 如果原图没有加载完,那么设置_loaded为true标记大图已经加载。
关于图片加载还要注意一个问题,测试以下代码:
在chrome/safari只会弹出一次"load",而其他都是正常的两次,可能是做了优化之类的吧。
当加载完成后,就可以设置相关的对象和参数,这些都在_initLoaded程序中进行。
【加载设置】
在_initLoaded初始化加载设置程序,主要是做触发放大效果前的准备工作。
第一步,执行_initSize程序初始化显示图尺寸。 首先修正放大比例:
如果没有设置比例,就从预载对象获取的默认尺寸作为大图尺寸。 在图片加载时已经做好“安全措施”,确保这里能获得放大比例。 还可以通过自定义max和min属性来限制比例大小。 然后就可以按比例设置大图尺寸:
第二步,执行_initViewer初始化显示框程序,设置显示框。 先设置好样式:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
再把显示图插入显示框:
第三步,执行_initData初始化数据程序,主要是设置放大时用到的一些参数。 包括用于位置判断的原图坐标:
用于left/top修正的修正参数:
还有范围参数和显示尺寸。 范围参数就是要显示的范围在原图的尺寸,显示尺寸是显示框的显示尺寸。 如果通过rangeWidth和rangeHeight自定义了范围参数,就可以结合放大比例计算出显示尺寸:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
如果没有设置的话,就使用显示框的默认显示尺寸:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
rangeWidth = Math.ceil( this._viewerWidth / scale ); rangeHeight = Math.ceil( this._viewerHeight / scale );
注意,显示范围是通过clientWidth/clientHeight来获取的。 如果显示框是display为none的隐藏状态,就不能直接获取clientWidth/clientHeight。 这种情况下,程序用以下方法获取: 1,记录display/position/visibility的原始值; 2,分别设为"block"/"absolute"/"hidden",这是既能隐藏也能占位的状态; 3,获取参数; 4,重新设回原始值,恢复原来的状态。 得到显示范围后,再配合比例就能得到范围参数了。 ps:这是通用的获取不占位元素尺寸参数的方法,jquery的css也是用这个方法获取width/height的。 比例计算后可能会得到小数,而尺寸大小只能是整数,程序一律使用Math.ceil来取整。
【放大效果】
所有东西都设置好后,就可以执行start设置触发程序了。
程序会自动执行start方法,里面主要是给原图对象的mouseover/mousemove绑定_start程序:
分别对应移入原图对象和在原图对象上移动的情况。 ps:如果使用attachEvent的话还要注意重复绑定同一函数的问题,这里的addEvent就没有这个问题。
绑定的_start程序,主要是进行一些事件的解绑和绑定:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
为了在移出窗口时能结束放大效果,给document的mouseout绑定了_OUT程序:
当鼠标移出document会触发mouseout,如果当前relatedTarget是null的话,就延时执行_end结束程序:
在_end程序中,会先执行stop方法,在里面移除所有可能绑定的事件,再执行start方法继续等待触发。
而mousemove绑定的_move移动程序,主要用来实现鼠标移动到哪里就放大哪里的功能。 为适应更多的情况(例如扩展篇的其他模式),把它绑定到document上,但也因此不能用mouseout事件来触发移出程序。 程序通过鼠标和原图的坐标比较,来判断鼠标是否移出原图对象范围:
如果鼠标移出原图对象的话,就执行_END结束放大效果。
如果鼠标在原图对象上移动,就计算坐标进行定位。 先修正坐标,把鼠标坐标转化成大图的定位坐标:
再设置范围限制:
最后设置定位,使显示框显示要放大的部位。 ps:我尝试过用scrollLeft/scrollTop来做定位,但发现这样在ie中会像锯齿那样移动,放得越大越明显,所以放弃。
【鼠标滚动缩放】
如果设置mouse属性为true,就会开启鼠标滚动缩放功能。 在执行放大效果期间,可以通过滚动鼠标滚轮对大图进行缩放处理。 其实就是根据滚轮动参数的变化来修改放大比例。
关于鼠标滚动事件,在slider中也提过,不过那时只分析了ie和ff的区别,这里再分析一下。 首先ie是用mousewheel绑定事件的,使用event的wheelDelta来获取滚动参数。 其他浏览器用以下代码测试:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
向下滚动一下,可以得到以下结果: ff:DOMMouseScroll:3_undefined opera:mousewheel:3_-120 chrome/safari:mousewheel:0_-120 可以看到事件的绑定,ff只支持DOMMouseScroll,其他就只支持mousewheel。 而滚动参数的获取,ff只支持detail,opera两种都支持,chrome/safari就支持wheelDelta。 ps:不明白chrome/safari的detail为什么是0,有其他用途? 而DOMMouseScroll跟mousewheel还有一个不同是前者不能直接绑定元素,后者可以。 即可以elem.onmousewheel,但不能elem.onDOMMouseScroll。
根据以上分析,在_start程序里是这样把_mouse程序绑定到document的滚动事件中:
在_mouse程序里面根据滚动参数和自定义的rate缩放比率得到新的放大比例:
修改比例时,程序参数也需要重新计算。 由于_rangeWidth/_rangeHeight会影响计算的过程,要重新恢复成自定义的默认值:
然后执行_initSize和_initData重新设置尺寸和参数,再执行_move重新定位。 最后记得用preventDefault防止触发页面滚动。
使用技巧
【图片设置】
程序支持大图使用原图放大或用新大图。 如果用新大图而且图片比较大的话,强烈建议设置放大比例,这样程序会自动在大图载入前先使用原图放大,这样就不用等待大图加载完成。 还要注意新大图本身的宽高比例要跟原图一致,否则就对不准坐标了,使用原图放大就没有这个问题。
【显示框设置】
有两个方法可以设置显示框的尺寸: 要固定显示范围的话,先设置rangeWidth/rangeHeight,程序会根据显示范围和放大比例计算显示框的尺寸; 要用显示框当前的尺寸来显示的话,只要不设置rangeWidth/rangeHeight或设为0就可以了。
【reset】
由于各个属性和对象之间有很多的关联,很多属性不能直接修改。 程序设置了一个reset方法专门用来修改这类属性的。 如果程序加载完成后又修改了影响程序计算的样式,例如原图大小,显示框大小等,也要执行一次reset来重新设置参数和属性。
【浮动定位】
程序没有设置显示框浮动定位的功能,需要的话可以自行添加。 简单的定位可以参考实例的方法,还要小心覆盖select的问题。 如果要更复杂的浮动定位,可以参考“浮动定位提示效果”。
【opera的bug】
测试中发现opera 10.10有两个bug。 分别是img元素设置透明时会看到背景图,用js修改鼠标样式会有问题。 不过这两个问题在10.50都已经修复了,还没升级的赶快升啦。
【maxthon的bug】
用maxthon 2.5.1测试时发现一个问题,测试以下代码:
一般来说用display隐藏后,clientWidth应该是0的,但maxthon貌似没有处理这个情况。 这会影响到程序中clientWidth的判断,不过对一般使用没什么影响。 我已经提交了这个问题,不知会不会处理。
使用说明
实例化时,必须有一个img元素作为原图对象,和一个容器作为显示框:
可选参数用来设置系统的默认属性,包括: 属性: 默认值//说明 scale: 0,//比例(大图/原图) max: 10,//最大比例 min: 1.5,//最小比例 originPic: "",//原图地址 zoomPic: "",//大图地址 rangeWidth: 0,//显示范围宽度 rangeHeight:0,//显示范围高度 delay: 20,//延迟结束时间 autoHide: true,//是否自动隐藏 mouse: false,//鼠标缩放 rate: .2,//鼠标缩放比率 onLoad: $$.emptyFunction,//加载完成时执行 onStart: $$.emptyFunction,//开始放大时执行 onMove: $$.emptyFunction,//放大移动时执行 onEnd: $$.emptyFunction//放大结束时执行 其中模式的使用在下一篇扩展篇再说明。 初始化后,scale、max、min、originPic、zoomPic、rangeWidth、rangeHeight这些属性需要用reset方法来修改。
还提供了以下方法: start:开始放大程序(程序会自动执行); stop:停止放大程序; reset:修改设置; dispose:销毁程序。
程序源码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
ImageZoom.prototype = { //初始化程序 _initialize: function(image, viewer, options) { this._image = $$(image);//原图 this._zoom = document.createElement("img");//显示图 this._viewer = $$(viewer);//显示框 this._viewerWidth =0;//显示框宽 this._viewerHeight =0;//显示框高 this._preload =new Image();//预载对象 this._rect =null;//原图坐标 this._repairLeft =0;//显示图x坐标修正 this._repairTop =0;//显示图y坐标修正 this._rangeWidth =0;//显示范围宽度 this._rangeHeight =0;//显示范围高度 this._timer =null;//计时器 this._loaded =false;//是否加载 this._substitute =false;//是否替换 var opt =this._setOptions(options); this._scale = opt.scale; this._max = opt.max; this._min = opt.min; this._originPic = opt.originPic; this._zoomPic = opt.zoomPic; this._rangeWidth = opt.rangeWidth; this._rangeHeight = opt.rangeHeight; this.delay = opt.delay; this.autoHide = opt.autoHide; this.mouse = opt.mouse; this.rate = opt.rate; this.onLoad = opt.onLoad; this.onStart = opt.onStart; this.onMove = opt.onMove; this.onEnd = opt.onEnd; var oThis =this, END =function(){ oThis._end(); }; this._END =function(){ oThis._timer = setTimeout( END, oThis.delay ); }; this._START = $$F.bindAsEventListener( this._start, this ); this._MOVE = $$F.bindAsEventListener( this._move, this ); this._MOUSE = $$F.bindAsEventListener( this._mouse, this ); this._OUT = $$F.bindAsEventListener( function(e){ if ( !e.relatedTarget ) this._END(); }, this ); $$CE.fireEvent( this, "init" ); }, //设置默认属性 _setOptions: function(options) { this.options = {//默认值 scale: 0,//比例(大图/原图) max: 10,//最大比例 min: 1.5,//最小比例 originPic: "",//原图地址 zoomPic: "",//大图地址 rangeWidth: 0,//显示范围宽度 rangeHeight:0,//显示范围高度 delay: 20,//延迟结束时间 autoHide: true,//是否自动隐藏 mouse: false,//鼠标缩放 rate: .2,//鼠标缩放比率 onLoad: $$.emptyFunction,//加载完成时执行 onStart: $$.emptyFunction,//开始放大时执行 onMove: $$.emptyFunction,//放大移动时执行 onEnd: $$.emptyFunction//放大结束时执行 }; return $$.extend(this.options, options || {}); }, //初始化加载 _initLoad: function() { var image =this._image, originPic =this._originPic, useOrigin =!this._zoomPic &&this._scale, loadImage = $$F.bind( useOrigin ?this._loadOriginImage : this._loadImage, this ); //设置自动隐藏 this.autoHide &&this._hide(); //先加载原图 if ( originPic && originPic != image.src ) {//使用自定义地址 image.onload = loadImage; image.src = originPic; } elseif ( image.src ) {//使用元素地址 if ( !image.complete ) {//未载入完 image.onload = loadImage; } else {//已经载入 loadImage(); } } else { return;//没有原图地址 } //加载大图 if ( !useOrigin ) { var preload =this._preload, zoomPic =this._zoomPic || image.src, loadPreload = $$F.bind( this._loadPreload, this ); if ( zoomPic != preload.src ) {//新地址重新加载 preload.onload = loadPreload; preload.src = zoomPic; } else {//正在加载 if ( !preload.complete ) {//未载入完 preload.onload = loadPreload; } else {//已经载入 this._loadPreload(); } } } }, //原图放大加载程序 _loadOriginImage: function() { this._image.onload =null; this._zoom.src =this._image.src; this._initLoaded(); }, //原图加载程序 _loadImage: function() { this._image.onload =null; if ( this._loaded ) {//大图已经加载 this._initLoaded(); } else { this._loaded =true; if ( this._scale ) {//有自定义比例才用原图放大替换大图 this._substitute =true; this._zoom.src =this._image.src; this._initLoaded(); } } }, //大图预载程序 _loadPreload: function() { this._preload.onload =null; this._zoom.src =this._preload.src; if ( this._loaded ) {//原图已经加载 //没有使用替换 if ( !this._substitute ) { this._initLoaded(); } } else { this._loaded =true; } }, //初始化加载设置 _initLoaded: function(src) { //初始化显示图 this._initSize(); //初始化显示框 this._initViewer(); //初始化数据 this._initData(); //开始执行 $$CE.fireEvent( this, "load" ); this.onLoad(); this.start(); }, //初始化显示图尺寸 _initSize: function() { var zoom =this._zoom, image =this._image, scale =this._scale; if ( !scale ) { scale =this._preload.width / image.width; } this._scale = scale = Math.min( Math.max( this._min, scale ), this._max ); //按比例设置显示图大小 zoom.width = Math.ceil( image.width * scale ); zoom.height = Math.ceil( image.height * scale ); }, //初始化显示框 _initViewer: function() { var zoom =this._zoom, viewer =this._viewer; //设置样式 var styles = { padding: 0, overflow: "hidden" }, p = $$D.getStyle( viewer, "position" ); if ( p !="relative"&& p !="absolute" ){ styles.position ="relative"; }; $$D.setStyle( viewer, styles ); zoom.style.position ="absolute"; //插入显示图 if ( !$$D.contains( viewer, zoom ) ){ viewer.appendChild( zoom ); } }, //初始化数据 _initData: function() { var zoom =this._zoom, image =this._image, viewer =this._viewer, scale =this._scale, rangeWidth =this._rangeWidth, rangeHeight =this._rangeHeight; //原图坐标 this._rect = $$D.rect( image ); //修正参数 this._repairLeft = image.clientLeft + parseInt($$D.getStyle( image, "padding-left" )); this._repairTop = image.clientTop + parseInt($$D.getStyle( image, "padding-top" )); //设置范围参数和显示框大小 if ( rangeWidth >0&& rangeHeight >0 ) { rangeWidth = Math.ceil( rangeWidth ); rangeHeight = Math.ceil( rangeHeight ); this._viewerWidth = Math.ceil( rangeWidth * scale ); this._viewerHeight = Math.ceil( rangeHeight * scale ); $$D.setStyle( viewer, { width: this._viewerWidth +"px", height: this._viewerHeight +"px" }); } else { var styles; if ( !viewer.clientWidth ) {//隐藏 var style = viewer.style; styles = { display: style.display, position: style.position, visibility: style.visibility }; $$D.setStyle( viewer, { display: "block", position: "absolute", visibility: "hidden" }); } this._viewerWidth = viewer.clientWidth; this._viewerHeight = viewer.clientHeight; if ( styles ) { $$D.setStyle( viewer, styles ); } rangeWidth = Math.ceil( this._viewerWidth / scale ); rangeHeight = Math.ceil( this._viewerHeight / scale ); } this._rangeWidth = rangeWidth; this._rangeHeight = rangeHeight; }, //开始 _start: function() { clearTimeout( this._timer ); var viewer =this._viewer, image =this._image, scale =this._scale; viewer.style.display ="block"; $$CE.fireEvent( this, "start" ); this.onStart(); $$E.removeEvent( image, "mouseover", this._START ); $$E.removeEvent( image, "mousemove", this._START ); $$E.addEvent( document, "mousemove", this._MOVE ); $$E.addEvent( document, "mouseout", this._OUT ); this.mouse && $$E.addEvent( document, $$B.firefox ?"DOMMouseScroll" : "mousewheel", this._MOUSE ); }, //移动 _move: function(e) { clearTimeout( this._timer ); var x = e.pageX, y = e.pageY, rect =this._rect; if ( x < rect.left || x > rect.right || y < rect.top || y > rect.bottom ) { this._END();//移出原图范围 } else { var pos = {}, scale =this._scale, zoom =this._zoom, viewerWidth =this._viewerWidth, viewerHeight =this._viewerHeight; //修正坐标 pos.left = viewerWidth /2- ( x - rect.left -this._repairLeft ) * scale; pos.top = viewerHeight /2- ( y - rect.top -this._repairTop ) * scale; $$CE.fireEvent( this, "repair", e, pos ); //范围限制 x = Math.ceil(Math.min(Math.max( pos.left, viewerWidth - zoom.width ), 0)); y = Math.ceil(Math.min(Math.max( pos.top, viewerHeight - zoom.height ), 0)); //设置定位 zoom.style.left = x +"px"; zoom.style.top = y +"px"; $$CE.fireEvent( this, "move", e, x, y ); this.onMove(); } }, //结束 _end: function() { $$CE.fireEvent( this, "end" ); this.onEnd(); this.autoHide &&this._hide(); this.stop(); this.start(); }, //隐藏 _hide: function() { this._viewer.style.display ="none"; }, //鼠标缩放 _mouse: function(e) { this._scale += ( e.wheelDelta ? e.wheelDelta / (-120) : (e.detail ||0) /3 ) *this.rate; var opt =this.options; this._rangeWidth = opt.rangeWidth; this._rangeHeight = opt.rangeHeight; this._initSize(); this._initData(); this._move(e); e.preventDefault(); }, //开始 start: function() { if ( this._viewerWidth &&this._viewerHeight ) { var image =this._image, START =this._START; $$E.addEvent( image, "mouseover", START ); $$E.addEvent( image, "mousemove", START ); } }, //停止 stop: function() { clearTimeout( this._timer ); $$E.removeEvent( this._image, "mouseover", this._START ); $$E.removeEvent( this._image, "mousemove", this._START ); $$E.removeEvent( document, "mousemove", this._MOVE ); $$E.removeEvent( document, "mouseout", this._OUT ); $$E.removeEvent( document, $$B.firefox ?"DOMMouseScroll" : "mousewheel", this._MOUSE ); }, //修改设置 reset: function(options) { this.stop(); var viewer =this._viewer, zoom =this._zoom; if ( $$D.contains( viewer, zoom ) ) { viewer.removeChild( zoom ); } var opt = $$.extend( this.options, options || {} ); this._scale = opt.scale; this._max = opt.max; this._min = opt.min; this._originPic = opt.originPic; this._zoomPic = opt.zoomPic; this._rangeWidth = opt.rangeWidth; this._rangeHeight = opt.rangeHeight; //重置属性 this._loaded =this._substitute =false; this._rect =null; this._repairLeft =this._repairTop = this._viewerWidth =this._viewerHeight =0; this._initLoad(); }, //销毁程序 dispose: function() { $$CE.fireEvent( this, "dispose" ); this.stop(); if ( $$D.contains( this._viewer, this._zoom ) ) { this._viewer.removeChild( this._zoom ); } this._image.onload =this._preload.onload = this._image =this._preload =this._zoom =this._viewer = this.onLoad =this.onStart =this.onMove =this.onEnd = this._START =this._MOVE =this._END =this._OUT =null } }