多屏互動——H5 中級進階

前言

隨着智能硬件的普及,手機,平板,PC甚至路邊的電子廣告牌,現代瀏覽器已經無處不在。在瀏覽器裏編織出咱們本身的一片天地已經輕車熟路,可是這還不夠,H5賦予了瀏覽器太多的新特性,等待咱們去使用。這篇文章介紹利用手機瀏覽器的羅盤API,在PC的瀏覽器實時地繪製一個3D盒模型。javascript

這種炫酷的玩法叫作「多屏互動」,就像是把手機當作遊戲手柄,PC顯示器當作電視機,不過這些都是在瀏覽器裏實現的。css

先上效果圖
這裏寫圖片描述html

(測試機是刷了小米系統的裂了屏幕的HTC霹靂2+Chrome瀏覽器)前端

源碼請戳這裏https://coding.net/u/OverTree...html5

本地測試過程:java

  1. 在PC上,使用命令 node index.js,自動打開項目主頁。(請關閉ADsafe,若有虛擬機,請停用虛擬網卡)node

  2. 建立一個「房間」並自動進入「房間」。jquery

  3. 用手機掃描「房間」內任意位置的二維碼。git

  4. 確保手機和PC能夠相互PING通github

ADsafe是個很好用的去廣告軟件,可是會阻止本機IP訪問,可能形成項目首頁打不開,因此請先暫時關閉
本程序會自動獲取本機IP,若是有虛擬網卡,IP地址可能獲取不正確


客戶端(瀏覽器)

1. 手機瀏覽器端

一個物體在空間內的旋轉體位,均可以用一個方向向量(x,y,z)和旋轉角度(angle)來表示。也就是CSS3transformrotate3d(x,y,z,angle)這個函數的4個參數。

想要在瀏覽器裏方便的繪製一個立體模型的的旋轉,重點就是利用手機瀏覽器的H5新特性去獲取手機旋轉狀態的數據,而後轉化成這4個參數。

1.1 重力感應API

devicemotion 顧名思義設備運動
其實不只僅有重力感應的數據,還有移動加速度,擺動角度。
不過這個接口傾向於運動時瞬間的數據展現,靜止時,除了重力加速度,其餘數據(移動加速度,擺動角度)基本爲0。

window.addEventListener('devicemotion', deviceMotionHandler, true);
function deviceMotionHandler(evt){
   if(evt.accelerationIncludingGravity){
       document.body.innerHTML = 
          "x軸加速度: " + evt.accelerationIncludingGravity.x + "<br>"
        + "y軸加速度: " + evt.accelerationIncludingGravity.y + "<br>"
        + "z軸加速度: " + evt.accelerationIncludingGravity.z + "<br>"
    }
    if(evt.rotationRate ){
       document.body.innerHTML +=  
         "x軸扭轉: " + evt.rotationRate.beta + "<br>" 
       + "y軸扭轉: " + evt.rotationRate.gamma + "<br>"
       + "z軸扭轉: " + evt.rotationRate.alpha + "<br>"
    }
}

(魅族老機型,安卓4.4.4的自帶瀏覽器對此API支持不徹底,請另外安裝QQ瀏覽器)
在手機瀏覽器裏運行以上代碼,並稍微晃動,會看到打印數據狂跳。
拿到了數據,接下來開始觀察規律。
手機屏幕朝上,水平靜止放置,Z軸重力加速度爲9.8,Y,X爲0。
手機屏幕朝下,水平靜止放置,Z軸重力加速度爲-9.8,Y,X爲0。
手機話筒朝下,豎直靜止放置,Y重力加速度爲9.8, X,Z爲0。
手機話筒朝上,豎直靜止放置,Y重力加速度爲-9.8, X,Z爲0。
手機右側朝上,豎直靜止放置,X重力加速度爲9.8, Y,Z爲0。
手機左側朝下,豎直靜止放置,X重力加速度爲-9.8, Y,Z爲0。

那麼手機的空間座標以下圖:
這裏寫圖片描述
箭頭指向都是座標正方向。

當手機開始傾斜,X,Y,Z軸的加速度份量都有值,且絕對值都小於9.8。根據份量的數值,是能夠算出手機在三維空間的傾斜狀態,只不過這個計算過程複雜,並且在手機運動時,重力加速度的值並不許確表達當前傾斜。通常不用這個數據去計算手機在三維空間的傾斜。

當手機水平放置,撥動手機,使其慢慢旋轉,重力加速度的數據並無變化。
因此,重力感應的這個API,只能獲取設備當前的傾斜狀態,而沒法獲取設備的旋轉方向。而一些簡單的功能,好比搖一搖,晃一晃,就能夠用這個接口去實現。

利用重力感應的API,能夠輕鬆利用高中數學的反三角函數,實現XY二維平面的旋轉,效果以下:

這裏寫圖片描述

代碼以下:

function deviceMotionHandler(evt){
    var angle = 
    Math.atan2(
            0 - evt.accelerationIncludingGravity.x ,        
            evt.accelerationIncludingGravity.y
        ).toFixed(2) / Math.PI * 180 ; 
}

這個 angle 就能夠直接應用在DOM的CSS屬性transform:rotate(angle deg)上。

1.2 羅盤API

window.addEventListener('deviceorientation', deviceOrientationHandler, true);
function deviceMotionHandler(evt){
     document.body.innerHTML = 
          "z軸旋轉(羅盤方向) alpha:  " + event.alpha + "<br>"
        + "y軸旋轉 gamma:  " + event.gamma + "<br>"
        + "x軸旋轉 beta:  " + event.beta

}

重點來了,deviceorientation可以很好的表現物體在空間中的狀態,旋轉方向,傾斜角度,不管是靜止仍是運動或者加速運動。

這裏要和devicemotionevt.rotationRate區分一下,雖然都有alpha,gamma,beta 可是 devicemotion 描述的是旋轉變化了的角度值,物體角度變化纔會有數據,靜止了以後就變爲0,而 deviceorientation 的是描述是靜止時的角度值。

這三個數值的單位都是deg,如何轉化爲CSS3 transform:rotate3d(x,y,z,angle) 的4個參數,對於沒有任何3D知識的前端狗來講是個挺麻煩的問題。

如今要引入一個概念:四元數

四元數是個高階複數 q = [w,x,y,z]。
四元數的基本數學方程爲 :
q = cos (a/2) + i(x sin(a/2)) + j(y sin(a/2)) + k(z * sin(a/2)) 其中a表示旋轉角度,(x,y,z)表示旋轉軸。
四元數表示一個完整的旋轉。
四元數能夠由各軸旋轉角(alpha,beta,gamma)求得。
四元數能夠轉換旋轉軸(x,y,z)和旋轉角度(angle)。

做爲初試,本篇並不深刻討論四元數的具體定義,難點是獲取四元數[w,x,y,z]。
好在官方提供了旋轉角(alpha,beta,gamma)轉換成四元數的方法
https://w3c.github.io/deviceo...
在這個頁面內搜索 getQuaternion

另外我根據數學公式反求,寫了一個四元數轉(x,y,z,angle) 的函數 getAcQuaternion
代碼以下:

var degtorad = Math.PI / 180;
function getQuaternion( alpha, beta, gamma ) {  //官方求四元數方法

  var _x = beta  ? beta  * degtorad : 0; // beta value
  var _y = gamma ? gamma * degtorad : 0; // gamma value
  var _z = alpha ? alpha * degtorad : 0; // alpha value

  var cX = Math.cos( _x/2 );
  var cY = Math.cos( _y/2 );
  var cZ = Math.cos( _z/2 );
  var sX = Math.sin( _x/2 );
  var sY = Math.sin( _y/2 );
  var sZ = Math.sin( _z/2 );

  var w = cX * cY * cZ - sX * sY * sZ;
  var x = sX * cY * cZ - cX * sY * sZ;
  var y = cX * sY * cZ + sX * cY * sZ;
  var z = cX * cY * sZ + sX * sY * cZ;

  return [ w, x, y, z ];

}

function getAcQuaternion( _w, _x, _y, _z ) {  //個人四元數轉旋轉軸和旋轉角度方法

  var rotate = 2 * Math.acos(_w)/degtorad ;

  var x = _x / Math.sin(degtorad * rotate/2) || 0;
  var y = _y / Math.sin(degtorad * rotate/2) || 0;
  var z = _z / Math.sin(degtorad * rotate/2) || 0;

  return {x:x,y:y,z:z,rotate:rotate};

}

function deviceMotionHandler(evt){  // deviceorientation 事件處理函數
  var qu = getQuaternion(evt.alpha,evt.beta,evt.gamma);
  var rotate3d = getAcQuaternion(qu[0],qu[1],qu[2],qu[3]);
  // rotate3d的參數已經有了,隨你處理咯。我是把他送給服務器,交給PC,在PC上顯示旋轉
}

1.3 校準

這裏有個3D裏的概念,攝像機位置。咱們的PC顯示器就是一個攝像機。只能被動的從某一個角度展現拍攝的景象。正常狀況下,手機所在平面應該和顯示器所在平面平行,且垂直於地平面的角度。就比如是,攝像機正對着手機正面拍攝。
若是校準的時候手機並無垂直於地平面,攝像機的位置就不必定是正前方了。這時候展現的畫面並非水平同步的了。
以下圖所示,校準時,手機屏幕朝上。這時候攝像機位置就在天花板上了,你看到的成像就是俯視圖。

這裏寫圖片描述

同理,校準時,手機屏幕朝下,這時候攝像機的位置就是在地上,往上拍攝,你看到的成像就是仰視圖。

總結起來就是:校準時,手機屏幕朝着哪裏,攝像機就在那裏拍攝着屏幕,一動不動。

1.4 兼容性

demo的兼容性測試並不理想
在iOS平臺上測試良好,且流暢。

在安卓平臺上,除了chrome瀏覽器以外的瀏覽器,會出現各類問題,主要表如今羅盤數據不許確。
而chrome瀏覽器並無掃一掃功能,由於在國外並不流行這個玩意。因此在安卓平臺上就很蛋疼,還要多裝一個我查查,才能完總體驗。
(若是出現旋轉不許確的問題,能夠嘗試校準羅盤,大概就是拿着手機畫8。百度一下方法有不少)

代碼若是有兼容寫法,或者有其餘兼容問題請賜教,能夠在coding上私信我(OverTree ),不勝感激。

2. PC瀏覽器端

PC瀏覽器的做用就是可以顯示房間信息,建立房間。

顯示房間,建立時間,參與人數,點擊進入。
建立一個房間,成功後自動進入房間。

在房間內,接受服務器轉發的手機端的消息,並做出相應動做,包括上線,校準,旋轉,下線。

上線時,安排就坐(隱藏二維碼,顯示模型)
校準時,從新設置模型的顯示角度。
旋轉時,就旋轉咯。
下線時,從新顯示二維碼(顯示二維碼,隱藏模型)

2.1初始化, 創建ws鏈接

重點是房間裏的事情。因此這裏就只介紹進入房間發生的事吧。
首先房間參數要正確,至少有房間編號。

房間路由:
/room/[roomNumber]
roomNumber是一串16位隨機字符串。
座位路由:
/room/[roomNumber]/[seatNumber]

var uri = win.location.pathname.split('/'),roomNumber;

function initUrlData(){
  if(uri.length>=3 && uri[1] == "room"){
    roomNumber = uri[2];
    document.title = "虛擬房間 "+ roomNumber + "號"
    return 1;
  }else{
    window.location.href = "/index";
    return 0;
  }
}

function initWebSocket(){
   var wsUri = "ws://"+ window.location.hostname +":<%= config.wsport %>"+"/ws/room"; //這裏用了一個ejs的佔位符,已便在服務器更改websocket端口時能夠及時使用正確端口。
   
   var websocket = new WebSocket(wsUri); 
   websocket.onopen = function(evt) { 
       websocket.send(JSON.stringify({room:roomNumber})); 
   }; //連接創建後,發送一個消息,代表在哪一個房間

   websocket.onclose = function(evt) { 

   }; 

   websocket.onmessage = function(evt) { 
       parseMessage(evt.data) //解析數據
   }; 
   websocket.onerror = function(evt) { 

   }; 
   //綁定了這些處理函數以後,websocket開始創建連接,而不是 New 的時候開始創建
}


$(".room-place .qrcode").each(function(index,item){
    $(item).qrcode({
        "size": 200,
        "color": "#3a3",
        "text": window.location.origin + "/room/" + roomNumber + "/" + (index+1)
    });
    //這裏用jQuery的插件,jquery-qrcode 按照座位路由初始化二維碼
})

2.2 純CSS3立體模型

作爲一名普通的前端人員,想要畫一個3D的模型,按照最熟悉的方法就是用CSS3了。
(若是是用Three.js的大神請跳過本節)
不過要很快畫出一個六面體出來,仍是須要想想的,畢竟這個技能不多用。

畫一個長方體

<section class="container">
    <div id="box" >
      <figure class="front"><span>前</span></figure>
      <figure class="back"><span>後</span></figure>
      <figure class="right"><span>右</span></figure>
      <figure class="left"><span>左</span></figure>
      <figure class="top"><span>頂</span></figure>
      <figure class="bottom"><span>底</span></figure>
    </div>
</section>
<style>
    *{
        margin: 0; /*不加會歪*/
    }
    .container {
      width: 300px;
      height: 200px;
      position: relative;
      perspective: 1200px;  /*攝像機距離,設置小的的話,立方體顯示會變形*/
    }
    #box figure {
      display: block;
      position: absolute;
      border: 2px solid black;
      line-height: 200px;
      font-size: 40px;
      text-align: center;
      font-weight: bold;
      color: white;
      box-sizing: border-box; /*由於有2px寬的border,若是不設置爲此值,那麼每一個面的寬高都要少設置4個像素,才能對齊*/
    }    
    #box {
      width: 100%;
      height: 100%;
      position: absolute;
      transform-style: preserve-3d;/*這個很重要,默認是平面變形flat*/
    }

    #box .front,
    #box .back {
      width: 300px;   
      height: 200px;
    }
    
    #box .right,
    #box .left {
      width: 100px;
      height: 200px;
      left:100px;     /*調整*/
    }
    
    #box .top,
    #box .bottom {
      width: 300px;
      height: 100px;
      top:50px;       /*調整*/
      line-height:100px;
    }

     /*給每一個面上半透明的顏色*/
     #box .front  { background: hsla( 000, 100%, 50%, 0.7 ); }
     #box .back   { background: hsla( 160, 100%, 50%, 0.7 ); }
     #box .right  { background: hsla( 120, 100%, 50%, 0.7 ); }
     #box .left   { background: hsla( 180, 100%, 50%, 0.7 ); }
     #box .top    { background: hsla( 240, 100%, 50%, 0.7 ); }
     #box .bottom { background: hsla( 300, 100%, 50%, 0.7 ); }


     #box .front  { /*這個距離乘以2爲先後面的距離*/
         transform: translateZ( 50px );
     }
     #box .back   { /*front面沿着x軸旋轉180度,作後面*/
         transform: rotateX( -180deg ) translateZ(  50px );
     }
     #box .right {                 /*這個距離乘以2爲左右面的距離*/
         transform: rotateY(   90deg ) translateZ( 150px );
     }
     #box .left {  /*front面沿着y軸旋轉90度,作側面*/
         transform: rotateY(  -90deg ) translateZ( 150px );
     }
     #box .top {                   /*這個距離乘以2爲長方體高*/
         transform: rotateX(   90deg ) translateZ( 100px );
     }
     #box .bottom { /*front面沿着x軸旋轉90度,作底面*/
         transform: rotateX(  -90deg ) translateZ( 100px );
     }
</style>

對這樣的css有什麼要吐槽的麼?

這樣的stylesheet簡直是刀耕火種時期的

若是用sass寫法,那麼只須要寫一次#box和多層嵌套就能夠了。

效果以下:
這裏寫圖片描述

若是咱們使用webGL去繪製的話,導入一些現成的3D模型,不管物體仍是人物,均可以360度無死角的玩弄於手掌了。
(若是有蒼老師的模型,想一想還有點小激動呢,VR的感受說來就來啊 - -)

接下來就是等待來自手機端的旋轉信息,x,y,z,angle,使#box進行transform旋轉就是了。

$seat.find("#box").
css("transform","rotate3d("
+ (-parseFloat(content.x))+","  //取反
+ (+parseFloat(content.y))+","
+ (-parseFloat(content.z))+","  //取反
+ content.rotate +"deg)");

不取反的話,旋轉是錯誤的。我曾屢次嘗試給不一樣的座標取反,最終得出這個取反方法,是惟一顯示正常的組合。

沒法理解這兩個取反,猜想是由於css的x,y,z的座標和物理設備x,y,z的座標方向有差別吧。畢竟顯示器是平面的,他的x,y,z的定義不能和手機傳感器一致。

2.3 校準

PC端的校準就簡單多了,在#box外套一層div.adjust。
當接受來自手機端的校準信息 x,y,z,angle,設置外套的 div.adjust 的旋轉爲 x,y,z,-angle 就行了。

$seat.find(".adjust").
css("transform","rotate3d("
+ (-parseFloat(content.x))+","  
+ (+parseFloat(content.y))+","
+ (-parseFloat(content.z))+","  
+ (-parseFloat(content.rotate)) +"deg)");  //取反

固然,這個adjust的樣式至少包含如下樣式

.adjust{
  position: absolute;
  transform-style:preserve-3d;
}

2.4 兼容性

PC端的兼容性就好多了,只要是現代H5瀏覽器基本上沒有兼容性問題。


服務端

1.數據結構

這個服務只作臨時數據的保存和消息轉發。
臨時數據:好比,各端的webSocket鏈接句柄,房間信息等,我把它們放在global全局對象下,就比如是共享內存,訪問方便,速度快。

global.ShareMem = {
  rooms:{
       "12345678":{          //房間號作爲key,方便查找
         player:[{socket:connection,place:place}],          //手機端數組:鏈接句柄,座位號
         projector:[],       //PC端數組
         id:"12345678",
         startTime:Date.now(),
         maxplayer:2,        //最多座位數
         type:"ddd"          //房間類型
       }
  }
};

2.webServer

若是您是nodejs的大神,或者在用koajs、express等nodejs框架,請跳過本大節。由於我用原生的nodejs寫了一遍webServer,雖然重複造輪子很差,可是複習複習webServer的基本知識,仍是不錯的,本節適合新手入門。
包含知識點:header解析,靜態文件查找,gzip,文件hash計算,狀態碼。

2.1 目錄結構

/API
    /funMap.js            /*http功能函數集合*/
    /xxx.js
/socketAPI
    /funMap.js          /*webSocket功能函數集合*/
    /xxx.js
/Util                    /*工具目錄,獲取本地IP,打開默認瀏覽器*/
/webRoot
    /common             /*公共資源目錄*/
        /js
            /lib
        /css
    /m                  /*移動端html,js,css等*/
    /p                  /*PC端html,js,css等*/
/index.js                  /*入口文件*/
/config.js                /*配置文件,端口號,ws最大數據包大小等*/
/socketServer.js        /*webSocket處理函數*/
/webServer.js

2.2 webServer

基本規則是這樣的,搭建靜態服務器,靜態資源正常讀取返回,html文件用ejs渲染後返回。

因爲ejs的緣由,html文件並無被修改,可是渲染後的內容被修改,好比,更改了ws的端口,可是html文件沒有修改。因此不能使用Last-Modified來判斷是文件是否最新,而是要根據返回內容有沒有被改變來判斷,因此要用Etag

Etag須要根據內容算出hash值,通常用md5計算。

返回內容以前,須要進行gzip壓縮,用來節省帶寬。90KB的jquery.min.js能夠被gzip到30KB,壓縮纔是王道。

由於手機端和PC端執行的是徹底不一樣的代碼,因此要判斷從客戶端傳過來的user-agent是否包含Mobile字符串,以來區分客戶端是PC仍是手機,以便返回正確的資源。

經過簡單的約定,來區分靜態文件和REST請求

if (libPath.extname(pathName) == "") {
      //若是路徑沒有擴展名 
      if(params.length<=2){
        pathName += "/"; //訪問根目錄 
      }else if(params[1]=="api"){   //訪問以api開頭
        parseAPI(params,req,res);  //功能函數
        return ;
      }else{
        pathName = params[1]+".html";
      }
    }

我在這裏作了一個簡單的框架,在API目錄或者socketAPI目錄下新增js文件,一個js文件對應一個處理函數,而後在funMap.js中聚合爲一個Map,方便查找函數,也容易隔離和修改函數名。

var funMap = {
  "room":require("./room"),
  "changeName":require("./xxx"),
  "changeName2":require("./xxxyyy")
};
module.exports = funMap;

客戶端訪問時就能夠經過 /api/[functionName] 來訪問想要的服務了。

3 webSocketServer

nodejs自己並無提供webSockerServer的模塊,因此須要另外安裝一個。

在npm install的時候會安裝一個ws模塊,require("ws") 就能夠用了。用法與http模塊類似,都用 createServer({options},MainHandlerFunction) 建立服務,只是ws多了幾個參數。

主要是port,注意不要和webserver端口重複。
還有一個 maxPayload 就是單個ws數據包最大大小,單位是bytes,本身估計項目傳輸數據時候數據包大小。默認值是65535 即 64KB。通常webSocket用於小包傳輸,不用太大,我設置了1024 , 1KB。

主處理函數MainHandlerFunction,在有客戶端鏈接進來時會傳入一個參數connection,這個對象內容很是豐富,不看手冊,能夠打印出來也慢慢研究。
成功創建鏈接的方法就是要connection綁定message方法。

因爲wsSocket訪問是能夠帶着url的,因此咱們能夠用url隔離不一樣的功能函數,而不是去解析message主體。

var connectHandler = function(connection){
  // :4002/api/Function1 
  var URIarray = connection.upgradeReq.url.split("/")
  if(funMap[URIarray[2]]){
    funMap[URIarray[2]](connection);
  }else{
    connection.send("{err:Function Not Found!!}");
  }
}

3.1 消息,廣播,保活

每當有ws鏈接進來,都有相似文件描述符的id來區分每一個不一樣的鏈接。
connection._ultron.id 用它能夠區分本身與別人的鏈接,頗有用。

//消息格式
function msgPack(){
  return JSON.stringify({
    "who":arguments[0],      // Mobile , PC
    "place":arguments[1],    // 座位
    "dowhat":arguments[2],   // "connect","ready","message","lost"
    "content":arguments[3]||"" // 內容
  })
} 

//以room爲單位廣播,廣播房間內全部角色
function boradCast(room,msg,ignore){
  room.projector.forEach(function(item,index){
    if(ignore&&ignore._ultron.id===item.socket._ultron.id){
      // console.log("ignore!!!")
      // 忽略本身不發送給本身
    }
    else{
      try{
        item.socket.send(msg);
      }catch(e){
        console.log(e);
      }
    }
  });
  room.player.forEach(function(item,index){
    if(ignore&&ignore._ultron.id===item.socket._ultron.id){
      // console.log("ignore!!!")
      // 忽略本身不發送給本身
    }
    else{
      try{
        item.socket.send(msg);
      }catch(e){
        console.log(e);
      }
    }
  });
}

爲了檢查客戶端是否掉線,在創建鏈接時手動加入保活機制,方法很簡單:
給客戶端發送空消息時lastkeeplife爲1,只要客戶端返回任意消息,那麼更新lastkeeplife爲0,若是5秒以內,沒有任何回覆斷定爲掉線。
若是客戶端掉線,那麼關閉鏈接,從鏈接池中移除。並廣播掉線消息給房間內其餘角色。

var keeplifeHandler = setInterval(function(){
    if(lastkeeplife == 0){
      connection.close();
      connection.emit("close");
      clearInterval(keeplifeHandler);
    }
    try{
      lastkeeplife = 0;
      connection.send("{}");
    }catch(e){
      console.log("keep live error! "+ e +"\n\n");
      connection.close();
      connection.emit("close");
      clearInterval(keeplifeHandler);
    }
  },5000)

  connection.on('close',function(msg){
      if(keeplifeHandler){  //關閉保活循環
        clearInterval(keeplifeHandler);
      }
      console.log("close!",roomid,place);
      var room = global.ShareMem.rooms[roomid];
      if(!room)
        return;
      
      //從鏈接池移除鏈接句柄
      if(platform === PC){
          room.projector.forEach(function(item,index){
              if(item.socket === connection){
                  room.projector.splice(index,1);
                  return false;
              }
          })
      }else{
          room.player.forEach(function(item,index){
              if(item.socket === connection){
                  room.player.splice(index,1);
                  return false;
              }
          })
      }
      //發送掉線消息
      boradCast( room, msgPack(platform,place,"lost") , connection );
  });

iOS設備若是鎖屏,會發送斷開信息給服務器,而安卓不會。想要斷開連接,必須等到默認120秒超時後關閉。
ws初始化時並無提供初始化timeout的配置。經過修改
ws._server.timeout = 1000;//1秒超時
並不會生效。問題來了,怎麼修改才能設置超時時間呢?

目前只能用上述比較捉急的方法來及時斷開掉線設備。

最後

多屏互動已經不是新鮮的東西了,我作這個Demo仍是受chrome實驗室一個叫作【光劍出鞘】的項目的啓發。由於體驗時須要手機端和PC同時翻-牆,致使體驗差,而後本身才想作一個。作出來的時候感受好酷炫,好神奇,好興奮。
後續仍是有不少能夠拓展和改進的,但願最終能夠變爲一個成熟的產品,而不是僅僅止步於Demo。

相關閱讀


做者信息
做者來自力譜宿雲 LeapCloud 團隊_UX成員:王詩詩 【原創】
首發地址:https://blog.maxleap.cn/archi...

王詩詩,前端新人,專職前端工做兩年。曾供職於AMI作底層軟件開發。喜歡分析H5代碼,追崇用簡單的CSS,構建精美動效,作前端以前,這些是業餘愛好。現任職於MaxLeap UX 組,負責MaxWon 的開發和維護。現熱衷於Real-time WebApp。

歡迎關注微信訂閱號:MaxLeap_yidongyanfa

相關文章
相關標籤/搜索