Camera Snapshot(getUserMedia)

html5有提供一個可以取得視訊的api,使用navigator.getUserMedia,可以取得鏡頭的影像及聲音,example如下。

html:

<!DOCTYPE HTML>    
<html lang="en">   
<head>             
    <meta charset="UTF-8">
    <title></title>
</head>            
<body>             
    <video autoplay></video>
    <img src="">   
    <canvas style="display:none;"></canvas>     
    <script type="text/javascript" src="cameraSnapshot.js"></script>
</body>            
</html>         

cameraSnapshot.js:

(function(){     
    navigator.getUserMedia = navigator.getUserMedia || 
                             navigator.webkitGetUserMedia || 
                             navigator.mozGetUserMedia || 
                             navigator.msGetUserMedia;

    var video = document.querySelector('video');
    var canvas = document.querySelector('canvas');
    var ctx = canvas.getContext('2d');
    var localMediaStream = null;
    var img = document.querySelector('img');

    function snapshot() {
      if (localMediaStream) {
        canvas.width = img.width = video.clientWidth;
        canvas.height = img.height = video.clientHeight;
        ctx.drawImage(video, 0, 0); 
        img.src = canvas.toDataURL('image/webp');
      }             
    }               

    video.addEventListener('click', snapshot, false);

    if( navigator.getUserMedia ){
        navigator.getUserMedia({video: true}, function(stream) {
        video.src = window.URL.createObjectURL(stream);
        localMediaStream = stream;

        },function( error ){
            alert("User denied access to their camera!");  
        });           
    }else{         
        alert("The browser is not support");
    }               

})();           

這個範例主要是使用getUserMedia取得user的授權,將會回傳user所選的鏡頭串流,接著觸發video的click event,把目前video的畫面,繪製在canvas,在轉換成image。支援的瀏覽器

HTML5 Cache Manifest

當網路無法使用的時候,html5提供一個可以預先儲存到本機端,讓沒有網路的情況也可以使用。當需要更新時,只要去修改manifest的版本,browser就會自動做update動作。

如果是使用nginx需要在mime.types,加入以下設定:

text/cache-manifest      mf manifest;

以上動作只是針對server對browser需要吐回header,讓browser做解析,例如是php、java、node.js,這類型的語言,也可以直接在header自行加入。

html example:

<!DOCTYPE HTML>        
<html lang="en" manifest="test.manifest">       
<head>                 
    <meta charset="UTF-8">
    <title></title> 
</head>                
<body>                 
    <script type="text/javascript" src="test.js"></script>
    <script type="text/javascript">
        test.hello();
    </script>
    hello
</body>                
</html>    

test.manifest:

CACHE MANIFEST

# VERSION 0.1 

#需要cache path
CACHE:
test.js

#需要連線至網路的path
NETWORK:

#找不到path,將去替換path
FALLBACK: 

以上這種作法,會有一個問題,如果當page不是靜態的,而是要做成動態方式,page會預先被cache住,即時在manifest裡面的network設定,也還是有一樣的情況。網路上查到可以使用iframe去內嵌入一個cache.html,但是這幫助並不大,實際頁面上讀取的static檔案,還是會重新去讀取,會與iframe裡面的cache沒有相關,反而會覆蓋iframe cache裡面的檔案(路徑相同,ex:test.js)。

如果只是要cache js的話,那目前有個作法,可以解決這個問題,但不是一個好的解決方式,如下。

index html:

<!DOCTYPE HTML>        
<html lang="en">       
<head>                 
    <meta charset="UTF-8">
    <title></title> 
</head>                
<body>                 

    <script type="text/javascript">
        function onload(){
            document.querySelectorAll("iframe")[0].contentWindow.test.hello();        
        }
    </script>
    <iframe src="cache.html" style="display:none;" ></iframe>
    <script>
          document.querySelectorAll("iframe")[0].onload = onload;
    </script>
</body>                
</html>     

cache html:

<!DOCTYPE HTML>                                    
<html lang="en" manifest="test.manifest">          
<head>                                             
    <meta charset="UTF-8">                         
    <title></title>                                
</head>                                            
<body>                                             
    <script type="text/javascript" src="test.js"></script>

</body>                                  
</html>   

test.js:

test = { 
    hello:function(){
        console.log("hello");
    }   
};

test.manifest:

CACHE MANIFEST

# VERSION 0.1 

#
CACHE:
test.js

#
NETWORK:

#
FALLBACK:

以上作法,主要是利用iframe讀取到的cache script,在從主要頁面對iframe抓取相關javascript object。

requirejs clear cache

當每次需要把code佈署至production,又希望user不要因為瀏覽器的機制讀取到舊的code,那麼最簡單方式,就是改變靜態檔案的連結,只要連結不同,瀏覽器就會視為不同檔案。

第一種方法就是讓每次佈署到production時,先替換靜態檔案的連結,如果使用ruby on rails,它能直接將靜態檔案去做merge,和將連結轉成亂碼,可以參考icook頁面上的source code。

另一種方式就是直接將js、css這些靜態檔案,直接經由頁面上吐出來,這種方式request的數量會大幅減低,讀取速度也會更快,例如yahoogoogle

那如果是使用requirejs,又不想使用以上方法或者自行在每個script加參數,那麼可以使用以下方式:

var require = {
    urlArgs: "bust=v1"
};

官方有建議使用var的方式宣告require,而不要使用window.require方式,因為在老舊的IE瀏覽器,會有可能會出問題。

(相信之前有測過IE6和常常使用共用同一個namespace,並且有寫得很嚴謹的人,應該都有碰過這奇怪bug。)

Modernizr

modernizr是一個判斷瀏覽器支援性的JavaScript library ,比起以往使用userAgent判斷不同瀏覽器的支援性,使用modernizr相對更直覺方便。

支援性的example:

//是否支援css3動畫的效果
if( Modernizr.csstransitions ){

}

//是否支援touch event
if( Modernizr.touch ){

}

在css3如transform屬性,在不同瀏覽器上,都會有不同屬性名稱,可以用prefixed來判斷:

//如果是chrome則會顯示WebkitTransform
console.log( Modernizr.prefixed('transform') );

如果要判斷css的media query,目前是否符合那段判斷,可以使用Modernizr.mq

Modernizr.mq('(min-width: 0px)');

判斷event可以採用hasEvent:

//是否有縮放event(iphone、ipad)
Modernizr.hasEvent('gesturestart', elem) ;

如果要自己撰寫測試的判斷,可以使用addTest

//官方已實作
Modernizr.addTest('canvas', function() {
  var elem = document.createElement('canvas');
  return !!(elem.getContext && elem.getContext('2d'));
});

判斷有沒有此css屬性:

Modernizr.testProp('pointerEvents')

其他method的使用方式和詳細文件,可以參考官方的doc

Backbone history

backbone的router,主要用途是可以依照不同url,做不同的動作,很適合拿來做動態頁面切換,像是拿來製作mobile web,就滿適合的,使用到的技術有兩種,一個是利用錨點(#)的特性,另一個則是使用html5的pushState,達到不換頁的效果。

以下是官方的範例:

var Workspace = Backbone.Router.extend({

  routes: {
    "help":                 "help",    // #help
    "search/:query":        "search",  // #search/kiwis
    "search/:query/p:page": "search"   // #search/kiwis/p7
  },

  help: function() {
    doSomething("help");
  },

  search: function(query, page) {
    doSomething( query, page );
  }

});

定義好router要做的事情後,接著把router實體化:

new Workspace()

最後執行start,router就能開始運作了:

/**
* 開始使用router
* @param {String} root 指的是起始url
* @param {object} opts.pustState 就是html的pushState,如果為true就不會採用錨點方式,而是直接更動url,讓web看到在不同頁面的url
*/
Backbone.history.start({root:"/"});

/**
* 更動web上的location url
* @param {String} url
* @param {Object} opts.trigger 為true,則會觸發router event,如果為false,只會更動location url
* @param {Object} opts.replace 為true,會取代目前這筆的history,pushState則是新增一筆
*/
Backbone.history.navigate("/search",{trigger:true});

用錨點的優勢,支援舊有的瀏覽器(backbone都處理好了,即時不支援pushState,也會採用錨點方式),也可以直接利用a這個element,直接觸發event,而不需在另外寫script去bind event。如下:

<a href="#search">search</a>

利用pushState想達到跟錨點一樣的效果,可以如下:

$(document.body).on( "click", "a.history-go" ,function( event ){
    Backbone.history.navigate( $(this).attr("href").replace("#","") ,{trigger:true} );
    event.preventDefault();
}).on( "click", "a.history-back" );

接著只要在a這個Element,加入class,即可達到一樣效果:

<a class="history-go" href="#search">search</a><br>
<a class="history-back">back</a>

mobile web debug tool(jsconsole)

如果要在androidbrowser debug,最早期的方式,是使用adb logcat然後過濾掉其他訊息,只顯示browser的log,而ios上的Safari,則是可在設定,直接在mobile上觀看log(在mac上,可以使用類似chrome同樣的除錯介面)。但是這種除錯方式不易,只能單方面看到browser訊息,並沒辦法直接從pc上對瀏覽器下指令,而jsconsole剛好可以解決這個問題。

jsconsole提供了一個console page(jsconsole.com),會產出一個listen key,同時test page(開發者寫得測試頁面)也會include一隻script,在透過server讓test pageconsole page溝通。

step 1 在console page輸入以下code:

:listen

server會回傳

Creating connection...
Connected to "2A049E81-520B-4074-BB06-45BE8ADBE9E1"
<script src="http://jsconsole.com/remote.js?2A049E81-520B-4074-BB06-45BE8ADBE9E1"></script>

step 2 將回傳的script貼到test page:

<html>
  <head>
     <script src="http://jsconsole.com/remote.js?2A049E81-520B-4074-BB06-45BE8ADBE9E1"></script>
  </head>
<body>

</body>
</html>

這樣就配置完成,可以直接在console page,輸入javascript,也能同時接收test page裡的console.log。

step 3 在console page輸入:

alert("test");

就會看到在mobile上,跑出訊息視窗。技術猜測應該是使用long pollingserver push之類技術。

 

mobile chrome debug:

那如果使用chrome browser,可以直接在mobile選項,點選設定→開發人員工具→啟用usb網頁偵錯。

接著只要在終端機鍵入以下指令:

adb forward tcp:9222 localabstract:chrome_devtools_remote

然後在到pc上,打開chrome,在網址上輸入localhost:9222,就可以直接使用pc上的除錯工具了,比起jsconsle,對於開發者相對於友善多了。可以參考remote debugging

translate and left top

常常在chrome發現一些奇怪的現象,工作上會接觸到一些地圖相關的東西,在windows7chrome拖拉map時,會發現某些marker會漂移,而且在每次會漂移的marker,都不一定是同一個,有點像是random。並且漂移時,是沒辦法click(csspointer也指標消失了),ipad上的safari也有相同問題,但是同樣的在linux上的chrome卻不會有此現象,於是就試了一下google map,發現在windows7上是正常的,於是就把lefttop,改用translate取代,發現就可以正常顯示了。

以下html結構

<div class="container" style="position:relative;overflow:hidden;">
    <div id="dragContainer" style="left:101px;top:25px;position:absolute;">
        <div class="map-image" style="position:absolute;">
            <img src="..." style="position:absolute;top:0px;left:0px;"/>
            <img src="..." style="position:absolute;top:256px;left:0px;"/>
            <img src="..." style="position:absolute;top:512px;left:0px;"/>
            ....
        <div>
        <div class="overlay" style="position::absolute;">
            <div style="width:25px;height:25px;position:absolute;left:50px;top:50px;cursor:pointer;">
                <img src="..." style="width:25px;height:25px;" />
            </div>
            ...
        </div>
    </div>
</div>

實際上在移動的是dragContainermap-image是顯示地圖圖片,overlay則是顯示相關marker

requirejs shim

使用同步載入,以往都是使用order plugin,在requirejs 2.0.1版,已經使用shim作為替代,可以直接定義各套件的相依性。

require.config({
    shim:{
        "sparrowApi":["module1","module2"]
    }
});

定義完後,直接require "sparrowApi" 就會自動載入module1module2了。

require.config({
    shim:{
        sparrowApi:{
            deps:["module1","module2"],
            exports:"sparrowApi"
        }
    }
});

如果使用exports這個attribute,可直接將"sparrowApi"回傳回來的值,直接定義成global變數,當然exports也可以使用function作為傳入值,然後在判斷要回傳什麼,作為global變數。

requirejs example

requirejsjavascript的套件管理library ,使用於管理套件的相依性,其中載入方式有非同步同步兩種方式。

以下是一個簡單的範例:

<!--file:index.html-->
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <script data-main="js/main" src="http://requirejs.org/docs/release/1.0.7/minified/require.js"></script> 
</body>
</html>

requirejs被載入的時候,會去讀取data-main的值,並且載入這隻script(js/main.js)。

//file:js/main.js
(function(){

    require.config({
        baseUrl:"js",
        paths:{
            order:"libs/order"
        }   
    }); 

    //require( dependencies ,callback )
    //dependencies是載入的相關套件,參數為string array
    //當載入完畢會呼叫此function,callback為function
    //先載入完js/config.js,才會載入js/module2.js
    require(["order!config","order!module2"],function( config, module2 ){
        console.log( "loaded " + module2.name );
        console.log( "version : " + config.version );
    }); 

}).call( this );
**require.config**可以配置一些設定,**baseUrl**指的是**include script**的起始路徑,而**paths**可以設定名稱,會直接對照路徑,就不需打一長串了。
其中**require**的第一個參數,是載入相關的套件,加**order!**指的會依序往下執行,第二個參數則是,當載入完成後,會呼叫的**function**。
//file:js/config.js
define([],function(){
    return {
        version:"production"
    };  
});

define function的第一個參數與require function相同,都是用來載入相關package用,而第二個參數當載入完後,return的直將會傳給requirecallback function

//file:js/module1.js
define([],function(){
    return {
        name:"module1"
    };  
});
//file:js/module2.js
define(["module1"],function( module1 ){

    console.log("loaded " + module1.name);

    return {
        name:"module2"
    };  
});

js/lib/order.js是當載入需要同步時,需要使用到的requirejs plugin,其他plugin可參考官方。

當要將js壓縮和合併成同一隻時,可以執行以下指令,他會參考build.js,這個配置檔(需先裝node.js和requirejs):

./build.sh

github example

canvas event

在canvas上監聽event,最簡單的方式,就是將event綁在canvas,但是當canvas畫上各種不同的shape時,這時就必須另外實作監聽各個shape的event。另一種方式,則是在canvas上,在疊加一塊image或html5svg(利用現成html的tag),用來監聽不規則的形狀。

用image的方式,則是搭配map和area tag,這種方式再大部份的瀏覽器都支援。

以下是個map tag搭配canvas的example:
<canvas id="canvas" width="200" height="200"></canvas>
<img src="" width="200" height="200" usemap="#map" style="border:0px;position:absolute;z-index:1;opacity:0;" id="mapImage" />

<map name="map" id="map">
  <area id="areaCircle" shape="circle" coords="75,75,10"  href="#" />
</map>

image上的usemap屬性,是用來參照map tag的名稱,而area則是設定監聽的範圍,和綁定event的tag,範例都以圓形為例。圓的coords(x,y,r),r為半徑

var $canvas = $("#canvas");

$("#mapImage").offset({
    top:$canvas.offset().top,
    left:$canvas.offset().left
});
$("#areaCircle").on("click",function(){
    alert("click");
});

//get a reference to the canvas
var ctx = $canvas[0].getContext("2d");

//draw a circle
ctx.beginPath();
ctx.arc(75, 75, 10, 0, Math.PI*2, true); 
ctx.closePath();
ctx.fill();
上面程式做的事情,先將image疊在canvas上,綁定好event,接著用canvas畫一個圓。[map tag可參考w3c](http://www.w3schools.com/tags/tag_area.asp)
 
##### svg搭配canvas的example:
<canvas id="canvas" width="200" height="200"></canvas>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg" style="position:absolute;z-index:1;opacity:0;">
   <circle id="circle" cx="75" cy="75" r="10" stroke="black" stroke-width="0" style="cursor:pointer;opacity:0;"/>
</svg>

svgmap tag不同的是,map tag必須指定image作為參照,而svg可以自己產生不同的shape,這裡的範例是用來搭配canvas使用,所以會設定為透明。

$("circle").on("click",function(){
alert("click");
});

var $canvas = $("#canvas");

$("#svg").offset({
top:$canvas.offset().top,
left:$canvas.offset().left
});

//get a reference to the canvas
var ctx = $canvas[0].getContext("2d");

//draw a circle
ctx.beginPath();
ctx.arc(75, 75, 10, 0, Math.PI2, true);
ctx.closePath();
ctx.fill();

*svg的應用方式還有多種,可以參考此頁面