天天看點

多屏互動——H5中級進階 - 修複動态圖檔版本

前言

随着智能硬體的普及,手機,平闆,PC甚至路邊的電子廣告牌,現代浏覽器已經無處不在。在浏覽器裡編織出我們自己的一片天地已經輕車熟路,但是這還不夠,H5賦予了浏覽器太多的新特性,等待我們去使用。這篇文章介紹利用手機浏覽器的羅盤API,在PC的浏覽器實時地繪制一個3D盒模型。

這種炫酷的玩法叫做“多屏互動”,就像是把手機當做遊戲搖桿,PC顯示器當做電視機,不過這些都是在浏覽器裡實作的。

先上效果圖

多屏互動——H5中級進階 - 修複動态圖檔版本

(測試機是刷了小米系統的裂了螢幕的HTC霹靂2+Chrome浏覽器)

源碼請戳這裡:https://coding.net/u/OverTree/p/webSocketDemo/git

本地測試過程:

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

2. 建立一個“房間”并自動進入“房間”。

3. 用手機掃描“房間”内任意位置的二維碼。

4. 確定手機和PC可以互相PING通

ADsafe是個很好用的去廣告軟體,但是會阻止本機IP通路,可能造成項目首頁打不開,是以請先暫時關閉

本程式會自動擷取本機IP,如果有虛拟網卡,IP位址可能擷取不正确

用戶端(浏覽器)

1. 手機浏覽器端

一個物體在空間内的旋轉體位,都可以用一個方向向量(x,y,z)和旋轉角度(angle)來表示。也就是CSS3

transform

rotate3d(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。
           

那麼手機的空間坐标如下圖:

多屏互動——H5中級進階 - 修複動态圖檔版本

箭頭指向都是坐标正方向。

當手機開始傾斜,X,Y,Z軸的加速度分量都有值,且絕對值都小于9.8。根據分量的數值,是可以算出手機在三維空間的傾斜狀态,隻不過這個計算過程複雜,而且在手機運動時,重力加速度的值并不準确表達目前傾斜。一般不用這個資料去計算手機在三維空間的傾斜。

當手機水準放置,撥動手機,使其慢慢旋轉,重力加速度的資料并沒有變化。是以,重力感應的這個API,隻能擷取裝置目前的傾斜狀态,而無法擷取裝置的旋轉方向。而一些簡單的功能,比如搖一搖,晃一晃,就可以用這個接口去實作。

利用重力感應的API,可以輕松利用高中數學的反三角函數,實作XY二維平面的旋轉,效果如下:

多屏互動——H5中級進階 - 修複動态圖檔版本

代碼如下:

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

這個 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

能夠很好的表現物體在空間中的狀态,旋轉方向,傾斜角度,無論是靜止還是運動或者加速運動。

這裡要和

devicemotion

evt.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/deviceorientation/spec-source-orientation.html

在這個頁面内搜尋 getQuaternion。

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

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

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

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

  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 =  * Math.acos(_w)/degtorad ;

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

  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[],qu[],qu[],qu[]);
  // rotate3d的參數已經有了,随你處理咯。我是把他送給伺服器,交給PC,在PC上顯示旋轉
}
           
1.3 校準

這裡有個3D裡的概念,錄影機位置。我們的PC顯示器就是一個錄影機。隻能被動的從某一個角度展示拍攝的景象。正常情況下,手機所在平面應該和顯示器所在平面平行,且垂直于地平面的角度。就好比是,錄影機正對着手機正面拍攝。

如果校準的時候手機并沒有垂直于地平面,錄影機的位置就不一定是正前方了。這時候展示的畫面并不是水準同步的了。

如下圖所示,校準時,手機螢幕朝上。這時候錄影機位置就在天花闆上了,你看到的成像就是俯視圖。

多屏互動——H5中級進階 - 修複動态圖檔版本

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

總結起來就是:校準時,手機螢幕朝着哪裡,錄影機就在那裡拍攝着螢幕,一動不動。

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>= && uri[] == "room"){
    roomNumber = uri[];
    document.title = "虛拟房間 "+ roomNumber + "号"
    return ;
  }else{
    window.location.href = "/index";
    return ;
  }
}

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": ,
        "color": "#3a3",
        "text": window.location.origin + "/room/" + roomNumber + "/" + (index+)
    });
    //這裡用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: ; /*不加會歪*/
    }
    .container {
      width: px;
      height: px;
      position: relative;
      perspective: px;  /*錄影機距離,設定小的的話,立方體顯示會變形*/
    }
    #box figure {
      display: block;
      position: absolute;
      border: px solid black;
      line-height: px;
      font-size: px;
      text-align: center;
      font-weight: bold;
      color: white;
      box-sizing: border-box; /*因為有2px寬的border,如果不設定為此值,那麼每個面的寬高都要少設定4個像素,才能對齊*/
    }    
    #box {
      width: %;
      height: %;
      position: absolute;
      transform-style: preserve-d;/*這個很重要,預設是平面變形flat*/
    }

    #box .front,
    #box .back {
      width: px;   
      height: px;
    }

    #box .right,
    #box .left {
      width: px;
      height: px;
      left:px;     /*調整*/
    }

    #box .top,
    #box .bottom {
      width: px;
      height: px;
      top:px;       /*調整*/
      line-height:px;
    }

     /*給每個面上半透明的顔色*/
     #box .front  { background: hsla( , %, %,  ); }
     #box .back   { background: hsla( , %, %,  ); }
     #box .right  { background: hsla( , %, %,  ); }
     #box .left   { background: hsla( , %, %,  ); }
     #box .top    { background: hsla( , %, %,  ); }
     #box .bottom { background: hsla( , %, %,  ); }


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

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

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

如果用sass寫法,那麼隻需要寫一次#box和多層嵌套就可以了。

效果如下:

多屏互動——H5中級進階 - 修複動态圖檔版本

如果我們使用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-d;
}
           
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:,        //最多座位數
         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<=){
        pathName += "/"; //通路根目錄 
      }else if(params[]=="api"){   //通路以api開頭
        parseAPI(params,req,res);  //功能函數
        return ;
      }else{
        pathName = params[]+".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[]]){
    funMap[URIarray[]](connection);
  }else{
    connection.send("{err:Function Not Found!!}");
  }
}
           
3.1 消息,廣播,保活

每當有ws連接配接進來,都有類似檔案描述符的id來區分每個不同的連接配接。

connection._ultron.id

用它可以區分自己與别人的連接配接,很有用。

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

//以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 == ){
      connection.close();
      connection.emit("close");
      clearInterval(keeplifeHandler);
    }
    try{
      lastkeeplife = ;
      connection.send("{}");
    }catch(e){
      console.log("keep live error! "+ e +"\n\n");
      connection.close();
      connection.emit("close");
      clearInterval(keeplifeHandler);
    }
  },)

  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,);
                  return false;
              }
          })
      }else{
          room.player.forEach(function(item,index){
              if(item.socket === connection){
                  room.player.splice(index,);
                  return false;
              }
          })
      }
      //發送掉線消息
      boradCast( room, msgPack(platform,place,"lost") , connection );
  });
           

iOS裝置如果鎖屏,會發送斷開資訊給伺服器,而安卓不會。想要斷開連結,必須等到預設120秒逾時後關閉。

ws初始化時并沒有提供初始化timeout的配置。通過修改

ws._server.timeout = 1000;//1秒逾時

并不會生效。問題來了,怎麼修改才能設定逾時時間呢?

目前隻能用上述比較捉急的方法來及時斷開掉線裝置。

最後

多屏互動已經不是新鮮的東西了,我做這個Demo還是受chrome實驗室一個叫做【光劍出鞘】的項目的啟發。因為體驗時需要手機端和PC同時翻-牆,導緻體驗差,然後自己才想做一個。做出來的時候感覺好酷炫,好神奇,好興奮。

後續還是有很多可以拓展和改進的,希望最終可以變為一個成熟的産品,而不是僅僅止步于Demo。

相關閱讀

  • 無需Flash實作圖檔裁剪——HTML5中級進階
  • 5個提高Node.js應用性能的技巧
  • 浏覽器存儲及使用

作者資訊

作者來自力譜宿雲 LeapCloud 團隊_UX成員:王詩詩 【原創】

首發位址:https://blog.maxleap.cn/archives/985

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

歡迎關注微信訂閱号:從移動到雲端