在之前的文章js调用陀螺仪配合CSS3 3D实现VR效果(一)中,介绍了用css3搭建了3d正方体,并控制正方体运动,模拟3d环境中视角的变化。这种方式的本质是"观测者不动,世界在动",观测者就是网页浏览器可视区,世界就是网页的内容。

结合之前的内容,我们可以用六张照片完成一个正方体空间,达到模拟“世界”的作用,让用户看到一个全景的空间。六张图片和全景图的关系,大家可以了解一下“天空盒”的概念。只要有设备能拍摄全景图,有很多软件能将全景图生成为“天空盒”模式的六张图片,以便实现VR全景网页的效果。本文介绍的方式本质还是应用dom来实现空间,当然也有通过canvas+three.js的方式实现全景,但是webgl在移动端的性能和兼容性还有待提高,不过从页面渲染的性能来看,dom的实现方式是差一些。dom天空盒实现如下:

<head>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
<style type="text/css">
  *{ padding:0; margin:0;touch-action: none;}
body,html{height: 100%;background:#ccc;}
#box{ width: 100%;
  height: 100%;
  border: 1px solid #000;
  -webkit-perspective: 200px;
  perspective: 200px;
  overflow: hidden;}
#z{ position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  -webkit-transform-style: preserve-3d;
  transform-style: preserve-3d;
  -webkit-transform: translateZ(200px);
  transform: translateZ(200px);}
#div{width:1024px; height:1024px; -webkit-transform-style: preserve-3d;transform-style:preserve-3d;-webkit-transform: translateZ(-512px) rotateY(0deg);transform: translateZ(-512px) rotateY(0deg); text-align: center;position: absolute;left:50%;top:50%; margin: -512px 0 0 -512px;}
#div span{ width:100%; height:100%; position:absolute;left:0;top:0;-webkit-backface-visibility: hidden;backface-visibility: hidden;touch-action: none;}
#div div{ touch-action: none;}
#div span:nth-of-type(1){
  background-image: url(//res.nuoyal.com/skybox/mobile_f.jpg);
  perspective: 400px;
  -webkit-transform: rotateY(0deg) translateZ(-512px);
  transform: rotateY(0deg) translateZ(-512px);
}
#div span:nth-of-type(2){
  background-image: url(//res.nuoyal.com/skybox/mobile_r.jpg);
  -webkit-transform: rotateY(-90deg) translateZ(-512px);
  transform: rotateY(-90deg) translateZ(-512px);
}
#div span:nth-of-type(3){
  background-image: url(//res.nuoyal.com/skybox/mobile_b.jpg);
  -webkit-transform: rotateY(-180deg) translateZ(-512px);
  transform: rotateY(-180deg) translateZ(-512px);
}
#div span:nth-of-type(4){
  background-image: url(//res.nuoyal.com/skybox/mobile_l.jpg);
  -webkit-transform: rotateY(-270deg) translateZ(-512px);
  transform: rotateY(-270deg) translateZ(-512px);
}
#div span:nth-of-type(5){
  background-image: url(//res.nuoyal.com/skybox/mobile_u.jpg);
  -webkit-transform: rotateX(-90deg) translateZ(-512px);
  transform: rotateX(-90deg) translateZ(-512px);
}
#div span:nth-of-type(6){
  background-image: url(//res.nuoyal.com/skybox/mobile_d.jpg);
  -webkit-transform: rotateX(90deg) translateZ(-512px);
  transform: rotateX(90deg) translateZ(-512px);
}
#div span{ background-position: 0 0; background-repeat: no-repeat;}
</style>
<script type="text/javascript">
  function setRem(psnum){
      var html = document.documentElement;
      html.style.fontSize = html.clientWidth / psnum +"px";
  }
  setRem(7.5);
  window.addEventListener('orientation' in window?"deviceorientation":"resize",function(e){
      setRem(7.5);
  });
</script>
</head>
<body id="main">
<div id="box">
  <div id="z" style="transform: translateZ(300px);">
   <div id="div" style="transform: rotateX(0deg) rotateY(0deg);">
     <span style="transform: rotateY(0deg) translateZ(-512px);"></span>
     <span style="transform: rotateY(-90deg) translateZ(-512px);"></span>
     <span style="transform: rotateY(-180deg) translateZ(-512px);"></span>
     <span style="transform: rotateY(-270deg) translateZ(-512px);"></span>
     <span style="transform: rotateX(-90deg) translateZ(-512px);"></span>
     <span style="transform: rotateX(90deg) translateZ(-512px);"></span>
   </div>
  </div>
</div>
<script type="text/javascript">
  function cssTransform(el, attr, val) {  
    if(!el.transform){
        el.transform = {}
    }
    if(typeof val == "undefined"){
        if(typeof el.transform[attr] == "undefined"){
            switch(attr) {
                case "scale":
                case "scaleX":
                case "scaleY":
                    el.transform[attr] = 100;
                    break;
                default:
                    el.transform[attr] = 0; 
            }
        }
        return el.transform[attr];
    } else {
        var transformVal = "";
        el.transform[attr] = Number(val);
        for(var s in el.transform){
            switch(s){
                case "rotate":
                case "rotateX":
                case "rotateY":
                case "rotateZ":
                case "skewX":
                case "skewY":
                    transformVal += " "+s+"("+el.transform[s]+"deg)";
                    break;
                case "translateX":
                case "translateY":
                case "translateZ":
                    transformVal += " "+s+"("+el.transform[s]+"px)";
                    break;
                case "scale":
                case "scaleX":
                case "scaleY":
                    transformVal += " "+s+"("+el.transform[s]/100+")";
                    break;
            }
        }
        el.style.WebkitTransform = el.style.transform = transformVal;
    }
}
function css(el, attr , val){
    if(attr == "rotate" || attr == "rotateX" 
    || attr == "rotateY" ||attr == "rotateZ" 
    || attr == "scale" || attr == "scaleX"
    || attr == "scaleY" || attr == "skewX"
    || attr == "skewY" || attr == "translateX"
    || attr == "translateY" || attr == "translateZ" ){
        return cssTransform(el, attr, val);
    }
    if(arguments.length == 2){
        var val = getComputedStyle(el)[attr];
        if(attr=='opacity'){
            val = Math.round(val*100);
        }
        return parseFloat(val);
    } 
    if(attr == "opacity") {
        el.style.opacity= val/100;
    } else {
        el.style[attr]= val + "px";    
    }
}  
//全局变量
var tdis = {};  //当前移动的值
var lastdis = {}; //上次移动的值
var oZ = document.querySelector('#z');
var oDiv = document.querySelector('#div');
var isTouch = false;
var isStart = false;
(function(){
    var box = document.querySelector('#box');
    var startPoint = {x:0,y:0};
    var startDeg = {x:0,y:0};
    var scale = 1024/90;
    var nowPo = {x:0,y:0};
    var last = {x:0,y:0};
    var lastDis = {x:0,y:0};
    box.addEventListener('touchstart',function(e){
        var oDiv = document.querySelector('#div');
        isTouch = true;
        isStart = false;
        tdis = {};
        lastdis = {};
        startPoint.x = e.changedTouches[0].pageX;
        startPoint.y = e.changedTouches[0].pageY;
        startDeg.x = css(oDiv,"rotateY"); 
        startDeg.y = css(oDiv,"rotateX"); 
        css(oDiv,"rotateY",startDeg.x);
        css(oDiv,"rotateX",startDeg.y);
        last.x = startDeg.x;      
        last.y = startDeg.y;      
        lastDis.x = 0;
        lastDis.y = 0;
        lastTime = new Date().getTime();  
        lastTimeDis = 0;  
    });
    box.addEventListener('touchmove',function(e){
        var oDiv = document.querySelector('#div');
        isTouch = true;
        isStart = false;
        tdis = {};
        lastdis = {};
        var dis = {};
        var nowTime = new Date().getTime();  
        dis.x = e.changedTouches[0].pageX - startPoint.x;
        dis.y = e.changedTouches[0].pageY - startPoint.y;
        var degX = startDeg.x-dis.x/scale;
        var degY = startDeg.y+dis.y/scale;
        if(degY > 60){
           degY = 60;
        }else if(degY < -60){
           degY = -60;
        }
        css(oDiv,"rotateY",degX);
        css(oDiv,"rotateX",degY);
        lastDis.x = (startDeg.x + dis.x) - last.x; 
        lastDis.y = (startDeg.y + dis.y) - last.y;
        last.x = startDeg.x + dis.x;
        last.y = startDeg.y + dis.y;
        lastTimeDis = nowTime - lastTime; 
        lastTime = nowTime;
    });
    box.addEventListener('touchend',function(e){
         var oDiv = document.querySelector('#div');
         if(window.isStart){
           return false;
         }
         isTouch = false;
         isStart = false;
         var speedX = lastDis.x/lastTimeDis;
         var speedY = lastDis.y/lastTimeDis;
         var disX = parseInt(speedX * 20);
         var disY = parseInt(speedY * 20);
         disX = isNaN(disX)?0:disX;
         disY = isNaN(disY)?0:disY;
         var targetX = css(oDiv,"rotateY")-disX;
         var targetY = css(oDiv,"rotateX")+disY; 
         if(targetY > 60){
             targetY = 60;
         }else if(targetY < -60){
             targetY = -60;
         }
    });
})();
</script>
</body>

运行测试上面代码 --移动端运行或浏览器开启移动端模拟器后手指滑动


网页运行时,通过touch方法控制正方体的transform旋转。针对手指滑动方向,可以控制正方体的旋转方向,实现VR的空间。若将控制正方体旋转的方式由滑屏升级为成陀螺仪,则最终实现VR效果。陀螺仪的实现如下:

<head>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
<style type="text/css">
*{ padding:0; margin:0;touch-action: none;}
body,html{height: 100%;background:#ccc;}
#box{ width: 100%;
  height: 100%;
  border: 1px solid #000;
  -webkit-perspective: 200px;
  perspective: 200px;
  overflow: hidden;}
#z{ position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  -webkit-transform-style: preserve-3d;
  transform-style: preserve-3d;
  -webkit-transform: translateZ(200px);
  transform: translateZ(200px);}
#div{width:1024px; height:1024px; -webkit-transform-style: preserve-3d;transform-style:preserve-3d;-webkit-transform: translateZ(-512px) rotateY(0deg);transform: translateZ(-512px) rotateY(0deg); text-align: center;position: absolute;left:50%;top:50%; margin: -512px 0 0 -512px;}
#div span{ width:100%; height:100%; position:absolute;left:0;top:0;-webkit-backface-visibility: hidden;backface-visibility: hidden;touch-action: none;}
#div div{ touch-action: none;}
#div span:nth-of-type(1){
  background-image: url(http://res.nuoyal.com/skybox/mobile_f.jpg);
  perspective: 400px;
  -webkit-transform: rotateY(0deg) translateZ(-512px);
  transform: rotateY(0deg) translateZ(-512px);
}
#div span:nth-of-type(2){
  background-image: url(http://res.nuoyal.com/skybox/mobile_r.jpg);
  -webkit-transform: rotateY(-90deg) translateZ(-512px);
  transform: rotateY(-90deg) translateZ(-512px);
}
#div span:nth-of-type(3){
  background-image: url(http://res.nuoyal.com/skybox/mobile_b.jpg);
  -webkit-transform: rotateY(-180deg) translateZ(-512px);
  transform: rotateY(-180deg) translateZ(-512px);
}
#div span:nth-of-type(4){
  background-image: url(http://res.nuoyal.com/skybox/mobile_l.jpg);
  -webkit-transform: rotateY(-270deg) translateZ(-512px);
  transform: rotateY(-270deg) translateZ(-512px);
}
#div span:nth-of-type(5){
  background-image: url(http://res.nuoyal.com/skybox/mobile_u.jpg);
  -webkit-transform: rotateX(-90deg) translateZ(-512px);
  transform: rotateX(-90deg) translateZ(-512px);
}
#div span:nth-of-type(6){
  background-image: url(http://res.nuoyal.com/skybox/mobile_d.jpg);
  -webkit-transform: rotateX(90deg) translateZ(-512px);
  transform: rotateX(90deg) translateZ(-512px);
}
#div span{ background-position: 0 0; background-repeat: no-repeat;}
</style>
<script type="text/javascript">
  function setRem(psnum){
      var html = document.documentElement;
      html.style.fontSize = html.clientWidth / psnum +"px";
  }
  setRem(7.5);
  window.addEventListener('orientation' in window?"deviceorientation":"resize",function(e){
      setRem(7.5);
  });
</script>
</head>
<body id="main">
<div id="box">
  <div id="z" style="transform: translateZ(300px);">
   <div id="div" style="transform: rotateX(0deg) rotateY(0deg);">
     <span style="transform: rotateY(0deg) translateZ(-512px);"></span>
     <span style="transform: rotateY(-90deg) translateZ(-512px);"></span>
     <span style="transform: rotateY(-180deg) translateZ(-512px);"></span>
     <span style="transform: rotateY(-270deg) translateZ(-512px);"></span>
     <span style="transform: rotateX(-90deg) translateZ(-512px);"></span>
     <span style="transform: rotateX(90deg) translateZ(-512px);"></span>
   </div>
  </div>
</div>
<script type="text/javascript" src="https://res.nuoyal.com/h53d/js/m.Tween.js"></script>
<script type="text/javascript">
//全局变量
var tdis = {};  //当前移动的值
var lastdis = {}; //上次移动的值
var oZ = document.querySelector('#z');
var oDiv = document.querySelector('#div');
var isTouch = false;
var isStart = false;
//陀螺仪
function setSensors(){
    var start = {}; //首次触发,开启陀螺仪时的值  
    var now = {};   //触发旋转后的当前值
    var startEl = {};//首次触发,开启旋转时的元素的角度值
    var lastTime = Date.now();
    var dir = window.orientation; //检测横竖屏
    var rooy = css(oDiv,"rotateY");
    var roox = css(oDiv,"rotateX");
    var timertl = null;
    isTouch = false;
    isStart = false;  //是否开始触发陀螺仪
    window.addEventListener("orientationchange", function(e) {
            var oDiv = document.querySelector('#div');
            oZ.removeChild(oDiv);
            ohtml = oDiv.innerHTML;
            var oniv=document.createElement('div');
            oniv.id = "div";
            oniv.innerHTML = ohtml;
            dir = window.orientation;
                tdis = {};  //陀螺仪当前移动的值
                lastdis = {}; //陀螺仪上次移动的值
                isTouch = false;
                isStart = false;
                start = {x:0,y:0};
                now = {x:0,y:0};
                startEl = {x:0,y:0};
                lastTime = Date.now();
                css(oniv,"rotateX",0);
                css(oniv,"rotateY",0);
                rooy = 0;
                roox = 0;
                oZ.appendChild(oniv);
    });
    window.addEventListener('deviceorientation',function(e){
        var oDiv = document.querySelector('#div');
        if(window.isTouch){
           return false;
        }
        var alpha = Math.round(e.alpha);
        switch(dir){
           case 0:
             now.x = e.beta;  //当前的旋转角度
             now.y = e.gamma+alpha;
             break;
           case 90:
             now.x = -e.gamma;  
             now.y = e.beta+alpha;
             break;
           case -90:
             now.x = e.gamma;  
             now.y = (e.beta+alpha);
             break;
           case 180:
             now.x = -e.beta;  
             now.y = -(e.gamma+alpha);
             break;
        }
        var nowTime = Date.now();
        if((nowTime-lastTime)<130){
            return false;
        }
        lastTime = nowTime;
        if(!isStart){
           if(!isTouch){
              isStart = true;
              //start
               start.x = now.x;
               start.y = now.y;
               startEl.x = css(oDiv,"rotateX");
               startEl.y = css(oDiv,"rotateY");
               rooy = css(oDiv,"rotateY");
           }
        }else{
           if(isTouch){
              return false;
           }
           isTouch = false;
           lastdis.x = tdis.x;
           lastdis.y = tdis.y;
           tdis.x = now.x - start.x;
           tdis.y = now.y - start.y;
           var tag = {};
           var lastag = {};
           tag.x = startEl.x + tdis.x;
           rooy = css(oDiv,"rotateY");
           roox = css(oDiv,"rotateX");
            var opath = tdis.y-lastdis.y;
            var opathx = tdis.x-lastdis.x;
            if(dir == 90 || dir == -90){
                if(opathx>0){
                    if(Math.abs(opathx)>140){
                       opathx = opathx-180;
                    }
                }else if(opathx<0){
                    if(Math.abs(opathx)>140){
                       opathx = 180-Math.abs(opathx);
                    }
                }else{
                    opathx = 0;
                }
                roox +=(opathx*2);
            }
            if(opath>0){
              if(Math.abs(opath)>200){
                 opath = opath-360;
              }
            }else if(opath<0){
              if(Math.abs(opath)>200){
                 opath = 360-Math.abs(opath);
              }
            }else{
              opath = 0;
            }
            rooy +=-(opath*2);
           if(dir == 90 || dir == -90){
              MTween({
                    el:oDiv,
                    target:{rotateX:roox,rotateY:rooy},
                    time:400,
                    type:"easeOut",
                    callBack:function(){
                        isTouch = false;
                    }
             });
           }else{
              MTween({
                    el:oDiv,
                    target:{rotateX:tag.x,rotateY:rooy},
                    time:400,
                    type:"easeOut",
                    callBack:function(){
                        isTouch = false;
                    }
             });
           }
        }
    });
}
setSensors();
</script>
</body>

运行测试上面代码 --移动端运行或浏览器开启移动端模拟器后手机晃动


陀螺仪的控制和手指滑动控制可以结合起来共存使用,使操作更顺滑。

点赞 (0)

欢迎转载:转载时请注明本文出处及文章链接