cloudgamer出品ImageZoom 图片放大效果

  一般用于放大查看商品图片,在凡客,京东商城,阿里巴巴等都有类似的效果。

  好处是能在原图附近对图片进行局部放大查看,而且可以通过鼠标控制查看的部位。

  前一阵子看到sohighthesky的图片放大效果,心血来潮自己也写一个看看。

  这个程序有以下特点:

  1,支持使用原图放大或新图片设置大图;

  2,大图完成载入前使用原图放大代替,减少操作等待时间;

  3,支持鼠标滚动缩放大图;

  4,可以通过设置显示范围或显示框大小设置显示尺寸;

  5,可以设置是否自动隐藏显示框;

  6,支持插件形式的扩展来实现更多的功能(下一篇再详细介绍)。

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

  

复制代码 代码如下:

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

  注意,显示范围是通过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程序:

  var image = this._image, START = this._START;

  $$E.addEvent( image, "mouseover", START );

  $$E.addEvent( image, "mousemove", START );

  分别对应移入原图对象和在原图对象上移动的情况。

  ps:如果使用attachEvent的话还要注意重复绑定同一函数的问题,这里的addEvent就没有这个问题。

  绑定的_start程序,主要是进行一些事件的解绑和绑定:

  

复制代码 代码如下:

  $$E.removeEvent( image, "mouseover", this._START );

  $$E.removeEvent( image, "mousemove", this._START );

  $$E.addEvent( document, "mousemove", this._MOVE );

  $$E.addEvent( document, "mouseout", this._OUT );

  为了在移出窗口时能结束放大效果,给document的mouseout绑定了_OUT程序:

  

复制代码 代码如下:

  this._OUT = $$F.bindAsEventListener( function(e){

  if ( !e.relatedTarget ) this._END();

  }, this );

  当鼠标移出document会触发mouseout,如果当前relatedTarget是null的话,就延时执行_end结束程序:

  var oThis = this, END = function(){ oThis._end(); };

  this._END = function(){ oThis._timer = setTimeout( END, oThis.delay ); };

  在_end程序中,会先执行stop方法,在里面移除所有可能绑定的事件,再执行start方法继续等待触发。

  而mousemove绑定的_move移动程序,主要用来实现鼠标移动到哪里就放大哪里的功能。

  为适应更多的情况(例如扩展篇的其他模式),把它绑定到document上,但也因此不能用mouseout事件来触发移出程序。

  程序通过鼠标和原图的坐标比较,来判断鼠标是否移出原图对象范围:

  

复制代码 代码如下:

  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 {

  ...

  }

  如果鼠标移出原图对象的话,就执行_END结束放大效果。

  如果鼠标在原图对象上移动,就计算坐标,并通过_repair程序把坐标转化成定位需要的值。

  最后设置大图的left/top定位使显示框显示要放大的部位。

  ps:我尝试过用scrollLeft/scrollTop来做定位,但发现这样在ie中会像锯齿那样移动,放得越大越明显,所以放弃。

  【鼠标滚动缩放】

  如果设置mouse属性为true,就会开启鼠标滚动缩放功能。

  在执行放大效果期间,可以通过滚动鼠标滚轮对大图进行缩放处理。

  其实就是根据滚轮动参数的变化来修改放大比例。

  关于鼠标滚动事件,在slider中也提过,不过那时只分析了ie和ff的区别,这里再分析一下。

  首先ie是用mousewheel绑定事件的,使用event的wheelDelta来获取滚动参数。

  其他浏览器用以下代码测试:

  

复制代码 代码如下:

  <!DOCTYPE html>

  <html xmlns="http://www.w3.org/1999/xhtml">

  <body style="height:1000px;">

  <script>

  function test(e){ alert(e.type+":"+e.detail+"_"+e.wheelDelta) }

  document.addEventListener( "DOMMouseScroll", test, false );

  document.addEventListener( "mousewheel", test, false );

  </script>

  </body>

  </html>

  向下滚动一下,可以得到以下结果:

  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的滚动事件中:

  this.mouse && $$E.addEvent( document, $$B.firefox ? "DOMMouseScroll" : "mousewheel", this._MOUSE );

  在_mouse程序里面根据滚动参数和自定义的rate缩放比率得到新的放大比例:

  this._scale += ( e.wheelDelta ? e.wheelDelta / (-120) : (e.detail || 0) / 3 ) * this.rate;

  修改比例时,程序参数也需要重新计算。

  由于_rangeWidth/_rangeHeight会影响计算的过程,要重新恢复成自定义的默认值:

  var opt = this.options;

  this._rangeWidth = opt.rangeWidth;

  this._rangeHeight = opt.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测试时发现一个问题,测试以下代码:

  

复制代码 代码如下:

  <div id="t" style="width:50px;"></div>

  <script>

  var t=document.getElementById("t");

  t.clientWidth;

  t.style.display="none";

  alert(t.clientWidth);

  </script>

  一般来说用display隐藏后,clientWidth应该是0的,但maxthon貌似没有处理这个情况。

  这会影响到程序中clientWidth的判断,不过对一般使用没什么影响。

  我已经提交了这个问题,不知会不会处理。

  使用说明

  实例化时,必须有一个img元素作为原图对象,和一个容器作为显示框:

  var iz = new ImageZoom( "idImage", "idViewer" );

  可选参数用来设置系统的默认属性,包括:

  属性: 默认值//说明

  mode: "simple",//模式

  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:销毁程序。

  程序源码

  

复制代码 代码如下:

  var ImageZoom = function(image, viewer, options) {

  this._initialize( image, viewer, options );

  this._initMode( this.options.mode );

  this._oninit();

  this._initLoad();

  };

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

  },

  //设置默认属性

  _setOptions: function(options) {

  this.options = {//默认值

  mode: "simple",//模式

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

  },

  //根据模式初始化函数属性

  _initMode: function(mode) {

  mode = $$.extend({

  options:{},

  init: $$.emptyFunction,

  load: $$.emptyFunction,

  start: $$.emptyFunction,

  end: $$.emptyFunction,

  move: $$.emptyFunction,

  dispose:$$.emptyFunction

  }, (ImageZoom._MODE || {})[ mode.toLowerCase() ] || {} );

  this.options = $$.extend( mode.options, this.options );

  this._oninit = mode.init;

  this._onload = mode.load;

  this._onstart = mode.start;

  this._onend = mode.end;

  this._onmove = mode.move;

  this._ondispose = mode.dispose;

  },

  //初始化加载

  _initLoad: function() {

  var image = this._image, originPic = this._originPic,

  useOrigin = !this._zoomPic && this._scale,

  loadImage = $$F.bind( useOrigin ? this._loadOriginImage : this._loadImage, this );

  //设置自动隐藏

  if ( this.autoHide ) { this._viewer.style.display = "none"; }

  //先加载原图

  if ( originPic && originPic != image.src ) {//使用自定义地址

  image.onload = loadImage;

  image.src = originPic;

  } else if ( 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();

  //开始执行

  this._onload();

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

  this._onstart();

  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 zoom = this._zoom,

  pos = this._repair(

  x - rect.left - this._repairLeft,

  y - rect.top - this._repairTop

  );

  this._onmove( e, pos );

  //设置定位

  zoom.style.left = pos.left + "px";

  zoom.style.top = pos.top + "px";

  this.onMove();

  }

  },

  //修正坐标

  _repair: function(x, y) {

  var scale = this._scale, zoom = this._zoom,

  viewerWidth = this._viewerWidth,

  viewerHeight = this._viewerHeight;

  //修正坐标

  x = Math.ceil( viewerWidth / 2 - x * scale );

  y = Math.ceil( viewerHeight / 2 - y * scale );

  //范围限制

  x = Math.min( Math.max( x, viewerWidth - zoom.width ), 0 );

  y = Math.min( Math.max( y, viewerHeight - zoom.height ), 0 );

  return { left: x, top: y };

  },

  //结束

  _end: function() {

  this._onend();

  this.onEnd();

  if ( this.autoHide ) { this._viewer.style.display = "none"; }

  this.stop();

  this.start();

  },

  //鼠标缩放

  _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() {

  this._ondispose();

  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

  }

  }

  转载请注明出处:http://www.cnblogs.com/cloudgamer/

  如有任何建议或疑问,欢迎留言讨论。

  打包下载地址