javascript实现3D房间
来源:广州中睿信息技术有限公司官网
发布时间:2012/10/21 23:25:16 编辑:admin 阅读 612
功能说明:1.鼠标点击房间内家具则视觉焦点移动到该家具。2.鼠标点击空白处则视觉移动相应距离。3.鼠标滚轮控制缩放比例大小。兼容IE5678910firefoxchrome效果预览:静态效果图:实现

功能说明:

1.鼠标点击房间内家具则视觉焦点移动到该家具。

2.鼠标点击空白处则视觉移动相应距离。

3.鼠标滚轮控制缩放比例大小。

兼容IE 5 6 7 8 9 10 firefox chrome

 

 

效果预览:

静态效果图:

 

实现原理:

  如果把一个景象用矩形表示,那么视觉上由近到远的景象就可以表示为多个原点相同,尺寸逐渐变小的矩形组,如下图:

  因此如果我们把处于相同视觉距离的一组物体绘制在同一个矩形区域内,最后把不同层级的矩形按其远近对矩形内物体坐标进行对应的缩放,就可以实现不同物体远近的视觉差。

 

代码分析:

  

      init: function(id, options) {//初始化                      options = options || {};                      this.container = $(id || 'container'); //容器                      this.arr = []; //保存所有元素                      this.centerX = this.container.clientWidth / 2; //原点坐标的left                      this.centerY = this.container.clientHeight / 2; //原点坐标的top                      this.maxScale = options.maxScale || 1.5; //最近图像最大缩放比                      this.minScale = options.minScale || 0.7; //最近图像最小缩放比                      this.selectedDuration = options.selectedDuration || 1000; //选中图片时,视觉移向被选中图片所需要的时间                      this.moveDuration = options.moveDuration || 1000; //点击视觉移动需要的时间                      this.tweenType = options.tweenType || 'easeOut';                      this.currentScale = 1; //最近图像当前缩放比                      containerPos = getContainerPos(this.container); //获取容器位置                      bindScrollHandler.call(this); //绑定鼠标滚动处理程序                      bindClickHandler.call(this); //绑定鼠标移动处理程序

  首先看需要初始化的参数:

  container:一个容器作为房子,包含所有家具元素。

  arr:保存所有家具元素的数组,为后续对所有家具元素的批处理操作作准备。

  centerX,centerY:为了后续计算元素位置和缩放比的方便,我们把容器中点作为坐标原点,这两个参数分别为原点的left和top值。

  maxScale,minScale:我们需要鼠标滚轮的滚动来缩放元素大小(改变视觉距离大小),因此我们需要定下视觉上最近的图像的最大和最小缩放比。

  selectedDuration:当鼠标点击一张图片时,会把视觉聚焦到该图片上,该参数为视觉聚焦到该图片上所花的时间。

  moveDuration:当鼠标点击空白位置,视觉上也会进行一定的位移的移动,该参数为位移所经历的时间。

  tweenType:缓动的类型,上面提到过得聚焦和移动都是通过缓动方式进行,该参数为缓动类型名,默认支持linear,easeIn,easeOut三种缓动模式。

  currentScale:视觉上最近的图像当前的缩放比,一开始为1,经过鼠标滚轮缩放会发生变化,最小为minScale,最大为maxScale。

 

  addTweenType: function(type, func) {//添加缓动方式                      (typeof func == 'function' || typeof func == 'object') && (Tween[type] = func);                    }

  

  前面提到过可以为聚焦和移动选择缓动类型,这里也提供接口添加缓动类型,可以自己创建缓动类型名,传入function或object。

 

   addElem: (function() {//为房子添加元素 参数:x坐标 y坐标 缩放比 图片地址 元素id                         var setImgHover = function(img) {//鼠标在元素上显示白框                          img.onmouseover = function(eve) {                              eve = eve || window.event;                              var target = eve.target || eve.srcElement;                              target.style.border = '1px white solid';                          }                          }                      var setImgOut = function(img) {//鼠标离开元素白框消失                          img.onmouseout = function(eve) {                              eve = eve || window.event;                              var target = eve.target || eve.srcElement;                              target.style.border = 'none';                          }                          }                      return function(x, y, scale, src, id) {                          var elem = new Image();                          var arr = this.arr;                          var container = this.container;                          var self = this;                            elem.onload = function() {    //元素加载处理程序                              container.appendChild(elem);                              elem.id = id;                              elem.style.position = 'absolute';                              elem.style.zIndex = Math.floor(scale * 1000);                              elem.initWidth = elem.clientWidth;                              elem.initHeight = elem.clientHeight;                              elem.initScale = scale;                              elem.initX = x;                              elem.initY = y;                                updatePos.call(self, elem, x, y, scale); //更新元素位置                              arr.push(elem);                              bindHandlerForImage.call(self, elem);                              setImgHover(elem);                              setImgOut(elem);                              }                          elem.src = src;                      }                  })()              }

  该方法用于为房子添加家具元素,注意传入的x,y坐标只需要是正常情况下(非缩放后)家具相对于容器的坐标,按比例缩放的事交给程序去做而不需要自己计算。另外这里有两个似有的方法:setImgHover和setImgOut,它们分别完成的小任务是为元素在鼠标移上去时添加白框,移出后白框消失的功能。接着就是生成元素,设置其初始x,y值,初始缩放比,之后调用updatePos根据x y设置元素的left,top实现在容器内的定位。

  

   var updatePos = function(elem, x, y, scale) {//根据元素的x y坐标 更新元素位置,缩放比和大小                  elem._x = x;                  elem._y = y;                    elem.scale = scale;                  if (scale > 1.05) {        //缩放比大于1.05则消失                      elem.style.display = 'none';                  }                  else {                      elem.style.display = 'block';                  }                  elem.style.left = this.centerX + scale * x + 'px';                  elem.style.top = this.centerY - scale * y + 'px';                  elem.style.width = elem.initWidth * scale + 'px';                  elem.style.height = elem.initHeight * scale + 'px';                };

 

  update的时候为元素设置_x,_y值,为元素当前的x y值,之后如果当前scale大于1.05,则隐藏图片,否则会挡住后面缩放比较小的图片。最后把x,y,scale映射到left,top,width,height。

 

   var bindHandlerForImage = function(img) {//为图片点击绑定事件处理程序                  var self = this;                  img.onclick = function(eve) {                      eve = eve || window.event;                      var enhance; //是否放大(否则缩小)                      var target = eve.target || eve.srcElement;                      //如果该图片的缩放比已经为1,或点击的是容器,或timeId不为undefined(之前的选择动画还没结束)则返回                      if (target.initScale == 1 || target == self.container || typeof timeId != 'undefined') {                          return;                      }                      removeClickHandler(); //选中图片期间,取消鼠标移动处理程序                      removeScrollHandler();  //选中图片期间,取消鼠标滚动处理程序                          var scaleMargin = 1 - target.scale; //所选中图片的缩放比距离1的差距                          var newX = target.initWidth / 2 * (-1); //被选中的图片的新X坐标,处于容器正中央                      var newY = target.initHeight / 2; //被选中的图片的新Y坐标,处于容器正中央                      var xMargin = newX - img._x; //到新位置X方向所要移动的位移                      var yMargin = newY - img._y; //到新位置Y方向所要移动的位移                        var arr = self.arr;                      for (var i = 0; i < arr.length; i++) {//用现在的xy坐标和scale重新设置initX,initY和initScale                          arr[i].initX = arr[i]._x;                          arr[i].initY = arr[i]._y;                          arr[i].initScale = arr[i].scale;                      }                      times = 0;                      target.scale >= 1 ? enhance = false : enhance = true; //如果所选中scale大于1,则需要缩小,小于1,则要放大                          timeId = window.setTimeout(function() {                            for (var i = 0; i < arr.length; i++) {                              var ele = arr[i];                              if (typeof arr[i].startTime == 'undefined') {                                  ele.startTime = new Date().getTime();                                }                                setImgPositionInTween.call(self, ele, xMargin, yMargin, scaleMargin, target); //缓动方式改变元素位置                            }                            if ((enhance && target.scale >= 1) || (!enhance && target.scale < 1)) {                                for (var i = 0; i < arr.length; i++) {                                  var ele = arr[i];                                  ele.startTime = undefined;                                  ele.initScale = ele.scale;                                  ele.initX = ele._x;                                  ele.initY = ele._y;                                  }                              window.clearTimeout(timeId);                              timeId = undefined;                              bindClickHandler.call(self);                              bindScrollHandler.call(self);                          }                          else {                              window.setTimeout(arguments.callee, 20);                          }                      }, 20);                    }                }

  

  之后是为图片绑定点击选中的处理程序,如果该图片的缩放比已经为1,或点击的是容器,或timeId不为undefined(之前的选择动画还没结束)则返回。之后是根据点击图片当前的缩放比,x,y的值计算图片移动到正中央时需要的缩放比差值,x差值,y差值,之后传入缓动函数使图片以特定的缓动模式进行移动和缩放。缓动函数需要传入的四个参数分别为:t(当前所用时间) b(其实距离) c(要走的总距离) d(经历的总时间)。

 

              var ScrollHandler = function(self) {                    return function(eve) {//绑定鼠标滚动时处理程序,动态改变房子内所有元素缩放比                        eve = eve || window.event;                      var arr = self.arr;                      if (navigator.userAgent.indexOf("Firefox") > 0) {                          //如果已到达最大值,只能向负方向滚动缩小比例,如果已到最小值,则只能向正方向滚动扩大比例,如果处于中间值,则正负方向都可以滚动                          if (hasToMax(self.currentScale, self.maxScale, self.minScale, eve.detail)) {                              times += eve.detail / 3;                              scrollable = true;                          }                          else {                              scrollable = false;                            }                        }                      else {                          if (hasToMax(self.currentScale, self.maxScale, self.minScale, eve.wheelDelta)) {                              times += eve.wheelDelta / 120;                              scrollable = true;                          }                          else {                              scrollable = false;                            }                      }                        if (scrollable) {//如果可以滚动缩放                          self.currentScale = 0;                          for (var i = 0; i < arr.length; i++) {                              var newScale = arr[i].initScale + times * 0.02; //更新缩放比scale                              (newScale > self.currentScale) && (self.currentScale = newScale); //获取最近图像(最大图像)当前缩放比                                updatePos.call(self, arr[i], arr[i]._x, arr[i]._y, newScale);                            }                      }                      eve.preventDefault ? eve.preventDefault() : eve.returnValue = false;                  }                };

  再看看鼠标滚动的处理程序,首先只有三种情况允许滚动:

  1.缩放比到达最大值,鼠标滚动缩小。

  2.缩放比到达最小值,鼠标滚动放大。

  3.缩放比在中间值。

  以上三种情况的判断交给hasToMax方法。计算好缩放比后,再次调用updatePos调节元素位置和大小。另外需要禁止鼠标滚轮的默认行为,防止滚动鼠标是滚动条跟着滚动。

 

              var clickHandler = function() {                  var self = this;                  return function(eve) {                      removeClickHandler();                      removeHandlerForAllImg(self.arr);                      eve = eve || window.event;                      var containerLeft = containerPos[0];                      var containerTop = containerPos[1];                      var pageX = eve.pageX || eve.clientX + document.documentElement.scrollLeft - document.documentElement.clientLeft;                      var pageY = eve.pageY || eve.clientY + document.documentElement.scrollTop - document.documentElement.clientTop;                      var marginX = (pageX - containerLeft - self.centerX) * (-1); //鼠标在容器的X坐标                      var marginY = (pageY - containerTop - self.centerY); //鼠标在容器的Y坐标                        var startTime = new Date().getTime();                        changeInTween.call(self, startTime, marginX, marginY, self.moveDuration)();                      }                }

  

  最后看看点击容器移动指定位移的处理程序:首先通过pageX或clientX+scrollLeft-clientTop(IE8以及以下版本)计算鼠标指针相对于容器的位置,然后根据点击位置,计算出离原点的距离,并使所有元素位移相应长度。每次点击更新startTime,duration则为用户所设置。

 

  var r=new room();    /**/  r.addElem(350,-60,1,'grass.gif','grass1');  r.addElem(-600,-60,1,'grass.gif','grass2');  r.addElem(350,-60,0.7,'grass.gif','grass3');  r.addElem(-600,-60,0.7,'grass.gif','grass4');    /**/  r.addElem(-225,200,0.65,'door.gif','door');    /**/  r.addElem(-400,600,0.63,'x-light.jpg','xlight');  r.addElem(-99,600,0.63,'light.gif','light');        /* 方桌 */  r.addElem(-480,-73,0.63,'table1.gif','table1');      /* 圆桌 */  r.addElem(600,0,0.6,'chair3.gif','chair2');  r.addElem(100,42,0.59,'chair1.gif','chair1');  r.addElem(180,-74,0.59,'table2.gif','table2');  r.addElem(360,24,0.57,'chair2.gif','chair2');    /* 钢琴 */  r.addElem(-850,246,0.56,'piano.gif','piano');    /* 书桌 */  r.addElem(600,240,0.5,'booktable.gif','booktable');    /* 电视 */  r.addElem(-206,79,0.4,'tv.gif','tv');

 

  最后是调用方法,传入参数:x坐标 y坐标 缩放比 图片地址 id。(注意该x y坐标是缩放前的坐标而不是缩放后的坐标,缩放后的坐标在程序内部根据scale值计算)。

 

所有源代码:

 

View Code
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  <html xmlns="http://www.w3.org/1999/xhtml">  <head>  <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />  <title>无标题文档</title>  <style>  body{text-align:center;}  #container{position:relative; background-color:black; width:1200px; height:600px; overflow:hidden; cursor:pointer; margin:0 auto;}    </style>  </head>    <body>  <div id="s"></div>  <div id="container">  </div>  </body>  <script>      var room = (function() {            var _room = function(id, options) {              this.init(id, options);            }          _room.prototype = (function() {              var containerPos; //容器位置              var times = 0; //缩放倍数              var scrollable = true; //是否可滚动缩放              var timeId2;              var Tween = {            //缓动对象                  linear: function(t, b, c, d) {                      return c * t / d + b;                  },                  easeIn: function(t, b, c, d) {                      return c * (t /= d) * t + b;                  },                  easeOut: function(t, b, c, d) {                      return c * ((t = t / d - 1) * t * t + 1) + b;                  }                };              var $ = function(id) { return document.getElementById(id); }              var emptyFunction=function(){};                var getContainerPos = function(container) {//获取容器位置                  var left = 0;                  var top = 0;                  while (container.offsetParent) {                      left += container.offsetLeft;                      top += container.offsetTop;                      container = container.offsetParent;                    }                  return [left, top];                }                var updatePos = function(elem, x, y, scale) {//根据元素的x y坐标 更新元素位置,缩放比和大小                  elem._x = x;                  elem._y = y;                    elem.scale = scale;                  if (scale > 1.05) {        //缩放比大于1.05则消失                      elem.style.display = 'none';                  }                  else {                      elem.style.display = 'block';                  }                  elem.style.left = this.centerX + scale * x + 'px';                  elem.style.top = this.centerY - scale * y + 'px';                  elem.style.width = elem.initWidth * scale + 'px';                  elem.style.height = elem.initHeight * scale + 'px';                };                          var changeInTween = function(startTime, marginX, marginY, duration) {                    var self = this;                  var arr=self.arr;                  window.clearTimeout(timeId2);                  return function() {                      var time = new Date().getTime() - startTime;                      var distantX = Tween[self.tweenType](time, 0, marginX, duration);                      var distantY = Tween[self.tweenType](time, 0, marginY, duration);                          if (time <= duration) {                          for (var i = 0; i < arr.length; i++) {                              var img = arr[i];                              updatePos.call(self, img, img.initX + distantX, img.initY + distantY, img.scale); //更新所有元素位置                            }                          timeId2 = window.setTimeout(arguments.callee, 50);                      }                      else {                          for (var i = 0; i < arr.length; i++) {                              var img = arr[i];                              img.initX = img._x;                              img.initY = img._y;                            }                          window.clearTimeout(timeId2);                          bindClickHandler.call(self);                          bindHandlerForAllImg.call(self);                        }                  }                }                var clickHandler = function() {                  var self = this;                  return function(eve) {                      removeClickHandler();                      removeHandlerForAllImg(self.arr);                      eve = eve || window.event;                      var containerLeft = containerPos[0];                      var containerTop = containerPos[1];                      var pageX = eve.pageX || eve.clientX + document.documentElement.scrollLeft - document.documentElement.clientLeft;                      var pageY = eve.pageY || eve.clientY + document.documentElement.scrollTop - document.documentElement.clientTop;                      var marginX = (pageX - containerLeft - self.centerX) * (-1); //鼠标在容器的X坐标                      var marginY = (pageY - containerTop - self.centerY); //鼠标在容器的Y坐标                        var startTime = new Date().getTime();                        changeInTween.call(self, startTime, marginX, marginY, self.moveDuration)();                      }                }                    var bindClickHandler = function() {//绑定鼠标事件处理程序                  $('container').onclick = clickHandler.call(this);              }              var removeClickHandler = function() {//删除鼠标事件处理程序                  $('container').onclick = emptyFunction;              }              //是否达到最大值              var hasToMax = function(currentScale, maxScale, minScale, detail) {                  return (currentScale >= maxScale && detail < 0) || (currentScale <= minScale && detail > 0) || (currentScale > minScale && currentScale < maxScale)              }        
联系我们CONTACT 扫一扫
愿景:成为最专业的软件研发服务领航者
中睿信息技术有限公司 广州•深圳 Tel:020-38931912 务实 Pragmatic
广州:广州市天河区翰景路1号金星大厦18层中睿信息 Fax:020-38931912 专业 Professional
深圳:深圳市福田区车公庙有色金属大厦509~510 Tel:0755-25855012 诚信 Integrity
所有权声明:PMI, PMP, Project Management Professional, PMI-ACP, PMI-PBA和PMBOK是项目管理协会(Project Management Institute, Inc.)的注册标志。
版权所有:广州中睿信息技术有限公司 粤ICP备13082838号-2