十五、HTML5 腳本編程

??HTML5 規(guī)范了很多新 HTML 標記。為了配合這些標記的變化,HTML5 規(guī)范也用顯著篇幅定義了很多 JavaScript API。

??定義這些 API 的用意就是簡化此前實現起來困難重重的任務,最終簡化創(chuàng)建動態(tài) Web 界面的工作。

1、跨文檔消息傳送

??跨文檔消息傳送(cross-document messaging),有時候簡稱為 XDM,指的是在來自不同域的頁面間傳遞消息。例如,www.wrox.com 域中的頁面與位于一個內嵌框架中的 p2p.wrox.com 域中的頁面通信。
??在 XDM 機制出現之前,要穩(wěn)妥地實現這種通信需要花很多功夫。XDM 把這種機制規(guī)范化,讓我們能既穩(wěn)妥又簡單地實現跨文檔通信。

??XDM 的核心是 postMessage() 方法。在HTML5 規(guī)范中,除了 XDM 部分之外的其他部分也會提到這個方法名,但都是為了同一目的:向另一個地方傳遞數據。
??對于 XDM 而言,“另一個地方”指的是包含在當前頁面中的 <iframe> 元素,或者由當前頁面彈出的窗口。
??postMessage() 方法接收兩個參數:一條消息和一個表示消息接收方來自哪個域的字符串。第二個參數對保障安全通信非常重要,可以放置瀏覽器把消息發(fā)送到不安全的地方。示例:

// 注意:所有支持 XMD 的瀏覽器也支持 iframe 的contentWindow 屬性
var iframeWindow = document.getElementById('myframe').contentWindow;
iframeWindow.postMessage("A secret", "http://www.wrox.com");

??最后一行代碼嘗試向內嵌框架中發(fā)送一條消息,并指定框架中的文檔必須來源于"http://www.wrox.com"域。如果來源匹配,消息就會傳遞到內嵌框架中;否則,postMessage() 什么也不做。
??這一限制可以避免窗口中的位置在你不知情的情況下發(fā)生改變。如果傳給 postMessage() 的第二個參數是"*",則表示可以把消息發(fā)送給來自任何域的文檔,但我們不推薦這樣做。

??接收到 XDM 消息時,會觸發(fā) window 對象的 message 事件。這個事件是以異步形式觸發(fā)的,因此從發(fā)送消息到接收消息(觸發(fā)接收窗口的 message 事件)可能要經過一段時間的延遲。觸發(fā) message 事件后,傳遞給 onmessage 處理程序的事件對象包含以下三方面的重要信息。

  • data:作為 postMessage() 第一個參數傳入的字符串數據。
  • origin:發(fā)送消息的文檔所在的域,例如"http://www.wrox.com"。
  • source:發(fā)送消息的文檔的 window 對象的代理。這個代理對象主要用于在發(fā)送上一條消息的窗口中調用 postMessage() 方法。如果發(fā)送消息的窗口來自同一個域,那這個對象就是 window。

??接收到消息后驗證發(fā)送窗口的來源是至關重要的。就像給 postMessage() 方法指定第二個參數,以確保瀏覽器不會把消息發(fā)送給未知頁面一樣,在 onmessage 處理程序中檢測消息來源可以確保傳入的消息來自已知的頁面?;镜臋z測模式如下。

EventUtil.addHandler(window, "message", function(event){

    // 確保發(fā)送消息的域是已知的域
    if (event.origin == "http://www.wrox.com"){

        // 處理接收到的數據
        processMessage(event.data);

        // 可選:向來源窗口發(fā)送回執(zhí)
        event.source.postMessage("Received!", "http://p2p.wrox.com");
    }
});

??還是要提醒大家,event.source 大多數情況下只是 window 對象的代理,并非實際的 window 對象。換句話說,不能通過這個代理對象訪問 window 對象的其他任何信息。
??記住,只通過這個代理調用 postMessage() 就好,這個方法永遠存在,永遠可以調用。

??XDM 還有一些怪異之處。首先,postMessage() 的第一個參數最早是作為“永遠都是字符串”來實現的。但后來這個參數的定義改了,改成允許傳入任何數據結構。可是,并非所有瀏覽器都實現了這一變化。
??為保險起見,使用 postMessage() 時,最好還是只傳字符串。如果你想傳入結構化的數據,最佳選擇是先在要傳入的數據上調用 JSON.stringify(),通過 postMessage() 傳入得到的字符串,然后再在 onmessage 事件處理程序中調用 JSON.parse()。

??在通過內嵌框架加載其他域的內容時,使用 XDM 是非常方便的。因此,在混搭(mashup)和社交網絡應用中,這種傳遞消息的方法極為常用。有了 XDM,包含<iframe>的頁面可以確保自身不受惡意內容的侵擾,因為它只通過 XDM 與嵌入的框架通信。而 XDM 也可以在來自相同域的頁面間使用。
??支持 XDM 的瀏覽器有 IE8+、Firefox 3.5+、Safari 4+、Opera、Chrome、iOS 版 Safari 及 Android 版 WebKit。XDM 已經作為一個規(guī)范獨立出來,現在它的名字叫 Web Messaging,官方頁面是 http://dev.w3.org/html5/postmsg/。

2、 原生拖放

??最早在網頁中引入 JavaScript 拖放功能的是 IE4。當時,網頁中只有兩種對象可以拖放:圖像和某些文本。
??拖動圖像時,把鼠標放在圖像上,按住鼠標不放就可以拖動它。拖動文本時,要先選中文本,然后可以像拖動圖像一樣拖動被選中的文本。
??在 IE 4 中,唯一有效的放置目標是文本框。到了 IE5,拖
放功能得到擴展,添加了新的事件,而且?guī)缀蹙W頁中的任何元素都可以作為放置目標。IE5.5 更進一步,讓網頁中的任何元素都可以拖放。(IE6 同樣也支持這些功能。)
??HTML5 以 IE 的實例為基礎制定了拖放規(guī)范。Firefox 3.5、Safari 3+和 Chrome 也根據 HTML5 規(guī)范實現了原生拖放功能。
??說到拖放,最有意思的恐怕就是能夠在框架間、窗口間,甚至在應用間拖放網頁元素了。瀏覽器對拖放的支持為實現這些功能提供了便利。

2.1、 拖放事件

??通過拖放事件,可以控制拖放相關的各個方面。其中最關鍵的地方在于確定哪里發(fā)生了拖放事件,有些事件是在被拖動的元素上觸發(fā)的,而有些事件是在放置目標上觸發(fā)的。拖動某元素時,將依次觸發(fā)下列事件:
????(1) dragstart
????(2) drag
????(3) dragend

??按下鼠標鍵并開始移動鼠標時,會在被拖放的元素上觸發(fā) dragstart 事件。此時光標變成“不能放”符號(圓環(huán)中有一條反斜線),表示不能把元素放到自己上面。拖動開始時,可以通過 ondragstart 事件處理程序來運行 JavaScript 代碼。
??觸發(fā) dragstart 事件后,隨即會觸發(fā) drag 事件,而且在元素被拖動期間會持續(xù)觸發(fā)該事件。這個事件與 mousemove 事件相似,在鼠標移動過程中,mousemove 事件也會持續(xù)發(fā)生。
??當拖動停止時(無論是把元素放到了有效的放置目標,還是放到了無效的放置目標上),會觸發(fā) dragend 事件。
??上述三個事件的目標都是被拖動的元素。默認情況下,瀏覽器不會在拖動期間改變被拖動元素的外觀,但你可以自己修改。不過,大多數瀏覽器會為正被拖動的元素創(chuàng)建一個半透明的副本,這個副本始終跟隨著光標移動。
??當某個元素被拖動到一個有效的放置目標上時,下列事件會依次發(fā)生:
????(1) dragenter
????(2) dragover
????(3) dragleave 或 drop

??只要有元素被拖動到放置目標上,就會觸發(fā) dragenter 事件(類似于 mouseover 事件)。
??緊隨其后的是 dragover 事件,而且在被拖動的元素還在放置目標的范圍內移動時,就會持續(xù)觸發(fā)該事件。
??如果元素被拖出了放置目標,dragover 事件不再發(fā)生,但會觸發(fā) dragleave 事件(類似于 mouseout 事件)。
??如果元素被放到了放置目標中,則會觸發(fā) drop 事件而不是 dragleave 事件。上述三個事件的目標都是作為放置目標的元素。

2.2、 自定義放置目標

??在拖動元素經過某些無效放置目標時,可以看到一種特殊的光標(圓環(huán)中有一條反斜線),表示不能放置。雖然所有元素都支持放置目標事件,但這些元素默認是不允許放置的。
??如果拖動元素經過不允許放置的元素,無論用戶如何操作,都不會發(fā)生 drop 事件。不過,你可以把任何元素變成有效的放置目標,方法是重寫 dragenter 和 dragover 事件的默認行為。
??例如,假設有一個 ID 為"droptarget" 的<div>元素,可以用如下代碼將它變成一個放置目標。

var droptarget = document.getElementById("droptarget");

EventUtil.addHandler(droptarget, "dragover", function(event){
    EventUtil.preventDefault(event);
});
EventUtil.addHandler(droptarget, "dragenter", function(event){
    EventUtil.preventDefault(event);
}); 

??以上代碼執(zhí)行后,你就會發(fā)現當拖動著元素移動到放置目標上時,光標變成了允許放置的符號。當然,釋放鼠標也會觸發(fā) drop 事件。
??在 Firefox 3.5+中,放置事件的默認行為是打開被放到放置目標上的 URL。換句話說,如果是把圖像拖放到放置目標上,頁面就會轉向圖像文件;而如果是把文本拖放到放置目標上,則會導致無效 URL
錯誤。因此,為了讓 Firefox 支持正常的拖放,還要取消 drop 事件的默認行為,阻止它打開 URL:

EventUtil.addHandler(droptarget, "drop", function(event){
    EventUtil.preventDefault(event);
});

2.3、 dataTransfer 對象

??只有簡單的拖放而沒有數據變化是沒有什么用的。為了在拖放操作時實現數據交換,IE5 引入了 dataTransfer 對象,它是事件對象的一個屬性,用于從被拖動元素向放置目標傳遞字符串格式的數據。
??因為它是事件對象的屬性,所以只能在拖放事件的事件處理程序中訪問 dataTransfer 對象。在事件處理程序中,可以使用這個對象的屬性和方法來完善拖放功能。目前,HTML5 規(guī)范草案也收入了 dataTransfer 對象。

??dataTransfer 對象有兩個主要方法:getData() 和 setData()。getData() 可以取得由 setData() 保存的值。
??setData() 方法的第一個參數,也是 getData() 方法唯一的一個參數,是一個字符串,表示保存的數據類型,取值為"text"或"URL",如下所示:

// 設置和接收文本數據
event.dataTransfer.setData("text", "some text");
var text = event.dataTransfer.getData("text");

// 設置和接收 URL
event.dataTransfer.setData("URL", "http://www.wrox.com/");
var url = event.dataTransfer.getData("URL");

??IE只定義了"text"和"URL"兩種有效的數據類型,而 HTML5 則對此加以擴展,允許指定各種 MIME 類型。考慮到向后兼容,HTML5 也支持"text"和"URL",但這兩種類型會被映射為"text/plain"和"text/uri-list"。
??實際上,dataTransfer 對象可以為每種 MIME 類型都保存一個值。換句話說,同時在這個對象中保存一段文本和一個 URL 不會有任何問題。
??不過,保存在 dataTransfer 對象中的數據只能在 drop 事件處理程序中讀取。如果在 ondrop 處理程序中沒有讀到數據,那就是 dataTransfer 對象已經被銷毀,數據也丟失了。
??在拖動文本框中的文本時,瀏覽器會調用 setData() 方法,將拖動的文本以"text"格式保存在 dataTransfer 對象中。
??類似地,在拖放鏈接或圖像時,會調用 setData() 方法并保存 URL。
??然后,在這些元素被拖放到放置目標時,就可以通過 getData() 讀到這些數據。當然,作為開發(fā)人員,你也可以在 dragstart 事件處理程序中調用 setData(),手工保存自己要傳輸的數據,以便將來使用。
??將數據保存為文本和保存為 URL 是有區(qū)別的。如果將數據保存為文本格式,那么數據不會得到任何特殊處理。
??而如果將數據保存為 URL,瀏覽器會將其當成網頁中的鏈接。換句話說,如果你把它放置到另一個瀏覽器窗口中,瀏覽器就會打開該 URL。
??Firefox 在其第 5 個版本之前不能正確地將 "url" 和 "text" 映射為 "text/uri-list" 和 "text/plain"。但是卻能把"Text"(T 大寫)映射為"text/plain"。為了更好地在跨瀏覽器的情況下從 dataTransfer 對象取得數據,最好在取得 URL 數據時檢測兩個值,而在取得文本數據時使用"Text"。

var dataTransfer = event.dataTransfer;

// 讀取 URL
var url = dataTransfer.getData("url") || dataTransfer.getData("text/uri-list");

// 讀取文本
var text = dataTransfer.getData("Text");

??注意,一定要把短數據類型放在前面,因為 IE 10 及之前的版本仍然不支持擴展的 MIME 類型名,而它們在遇到無法識別的數據類型時,會拋出錯誤。

2.4、 dropEffect 與 effectAllowed

??利用 dataTransfer 對象,可不光是能夠傳輸數據,還能通過它來確定被拖動的元素以及作為放置目標的元素能夠接收什么操作。為此,需要訪問 dataTransfer 對象的兩個屬性:dropEffect 和 effectAllowed。
??其中,通過 dropEffect 屬性可以知道被拖動的元素能夠執(zhí)行哪種放置行為。這個屬性有下列 4 個可能的值。

  • "none":不能把拖動的元素放在這里。這是除文本框之外所有元素的默認值。
  • "move":應該把拖動的元素移動到放置目標。
  • "copy":應該把拖動的元素復制到放置目標。
  • "link":表示放置目標會打開拖動的元素(但拖動的元素必須是一個鏈接,有 URL)。

??在把元素拖動到放置目標上時,以上每一個值都會導致光標顯示為不同的符號。然而,要怎樣實現光標所指示的動作完全取決于你。換句話說,如果你不介入,沒有什么會自動地移動、復制,也不會打
開鏈接??傊?,瀏覽器只能幫你改變光標的樣式,而其他的都要靠你自己來實現。
??要使用 dropEffect 屬性,必須在 ondragenter 事件處理程序中針對放置目標來設置它。

??dropEffect 屬性只有搭配 effectAllowed 屬性才有用。effectAllowed 屬性表示允許拖動元素的哪種 dropEffect,effectAllowed 屬性可能的值如下。

  • "uninitialized":沒有給被拖動的元素設置任何放置行為。
  • "none":被拖動的元素不能有任何行為。
  • "copy":只允許值為"copy"的 dropEffect。
  • "link":只允許值為"link"的 dropEffect。
  • "move":只允許值為"move"的 dropEffect。
  • "copyLink":允許值為"copy"和"link"的 dropEffect。
  • "copyMove":允許值為"copy"和"move"的 dropEffect。
  • "linkMove":允許值為"link"和"move"的 dropEffect。
  • "all":允許任意 dropEffect。

??必須在 ondragstart 事件處理程序中設置 effectAllowed 屬性。

??假設你想允許用戶把文本框中的文本拖放到一個<div>元素中。首先,必須將 dropEffect 和 effectAllowed 設置為"move"。但是,由于<div>元素的放置事件的默認行為是什么也不做,所以文本不可能自動移動。重寫這個默認行為,就能從文本框中移走文本。然后你就可以自己編寫代碼將文本插入到<div>中,這樣整個拖放操作就完成了。
??如果你將 dropEffect 和 effectAllowed 的值設置為"copy",那就不會自動移走文本框中的文本。
??Firefox 5 及之前的版本在處理 effectAllowed 屬性時有一個問題,即如果你在代碼中設置了這個屬性的值,那不一定會觸發(fā) drop 事件。

2.5、 可拖動

??默認情況下,圖像、鏈接和文本是可以拖動的,也就是說,不用額外編寫代碼,用戶就可以拖動它們。
??文本只有在被選中的情況下才能拖動,而圖像和鏈接在任何時候都可以拖動。
??讓其他元素可以拖動也是可能的。HTML5 為所有 HTML 元素規(guī)定了一個 draggable 屬性,表示元素是否可以拖動。圖像和鏈接的 draggable 屬性自動被設置成了 true,而其他元素這個屬性的默認值都是 false。要想讓其他元素可拖動,或者讓圖像或鏈接不能拖動,都可以設置這個屬性。示例:

<!-- 讓這個圖像不可以拖動 -->
<img  src="smile.gif" draggable="false" alt="Smiley face">

<!-- 讓這個元素可以拖動 -->
<div draggable="true">...</div>

??支持 draggable 屬性的瀏覽器有 IE 10+、Firefox 4+、Safari 5+ 和 Chrome。Opera 11.5 及之前的版本都不支持 HTML5 的拖放功能。
??另外,為了讓 Firefox 支持可拖動屬性,還必須添加一個 ondragstart 事件處理程序,并在 dataTransfer 對象中保存一些信息。
??在 IE9 及更早版本中,通過 mousedown 事件處理程序調用 dragDrop() 能夠讓任何元素可拖動。而在 Safari 4 及之前版本中,必須額外給相應元素設置 CSS 樣式 –khtml-user-drag: element。

2.6、 其他成員

??HTML5 規(guī)范規(guī)定 dataTransfer 對象還應該包含下列方法和屬性。

  • addElement(element):為拖動操作添加一個元素。添加這個元素只影響數據(即增加作為拖動源而響應回調的對象),不會影響拖動操作時頁面元素的外觀。在寫作本書時,只有 Firefox 3.5+ 實現了這個方法。
  • clearData(format):清除以特定格式保存的數據。實現這個方法的瀏覽器有 IE、Fireforx 3.5+、Chrome 和 Safari 4+。
  • setDragImage(element, x, y):指定一幅圖像,當拖動發(fā)生時,顯示在光標下方。這個方法接收的三個參數分別是要顯示的 HTML 元素和光標在圖像中的 x、y 坐標。其中,HTML 元素可以是一幅圖像,也可以是其他元素。是圖像則顯示圖像,是其他元素則顯示渲染后的元素。實現這個方法的瀏覽器有 Firefox 3.5+、Safari 4+和 Chrome。
  • types:當前保存的數據類型。這是一個類似數組的集合,以"text"這樣的字符串形式保存著數據類型。實現這個屬性的瀏覽器有 IE10+、Firefox 3.5+和 Chrome。

3、 媒體元素

??隨著音頻和視頻在 Web 上的迅速流行,大多數提供富媒體內容的站點為了保證跨瀏覽器兼容性,不得不選擇使用 Flash。
??HTML5 新增了兩個與媒體相關的標簽,讓開發(fā)人員不必依賴任何插件就能在網頁中嵌入跨瀏覽器的音頻和視頻內容。這兩個標簽就是<audio>和<video>。
??這兩個標簽除了能讓開發(fā)人員方便地嵌入媒體文件之外,都提供了用于實現常用功能的 JavaScriptAPI,允許為媒體創(chuàng)建自定義的控件。這兩個元素的用法如下。

<!-- 嵌入視頻 -->
<video src="conference.mpg" id="myVideo">Video player not available.</video>

<!-- 嵌入音頻 -->
<audio src="song.mp3" id="myAudio">Audio player not available.</audio>

??使用這兩個元素時,至少要在標簽中包含 src 屬性,指向要加載的媒體文件。
??還可以設置 width 和 height 屬性以指定視頻播放器的大??;
??而為 poster 屬性指定圖像的 URI 可以在加載視頻內容期間
顯示一幅圖像。
??另外,如果標簽中有 controls 屬性,則意味著瀏覽器應該顯示 UI 控件,以便用戶直接操作媒體。
??位于開始和結束標簽之間的任何內容都將作為后備內容,在瀏覽器不支持這兩個媒體元素的情況下顯示。

??因為并非所有瀏覽器都支持所有媒體格式,所以可以指定多個不同的媒體來源。為此,不用在標簽中指定 src 屬性,而是要像下面這樣使用一或多個<source>元素。

<!-- 嵌入視頻 -->
<video id="myVideo">
    <source src="conference.webm" type="video/webm; codecs='vp8, vorbis'">
    <source src="conference.ogv" type="video/ogg; codecs='theora, vorbis'">
    <source src="conference.mpg">
    Video player not available.
</video>

<!-- 嵌入音頻 -->
<audio id="myAudio">
    <source src="song.ogg" type="audio/ogg">
    <source src="song.mp3" type="audio/mpeg">
    Audio player not available.
</audio>

??關于視頻和音頻編解碼器的內容超出了本書討論的范圍。作者在此只想告訴大家,不同的瀏覽器支持不同的編解碼器,因此一般來說指定多種格式的媒體來源是必需的。支持這兩個媒體元素的瀏覽器有IE9+、Firefox 3.5+、Safari 4+、Opera 10.5+、Chrome、iOS 版 Safari 和 Android 版 WebKit。

3.1、屬性

??<video> 和 <audio> 元素都提供了完善的 JavaScript 接口。下表列出了這兩個元素共有的屬性,通過這些屬性可以知道媒體的當前狀態(tài)。

屬 性 數據類型 說 明
autoplay 布爾值 取得或設置 autoplay 標志
buffered 時間范圍 表示已下載的緩沖的時間范圍的對象
bufferedBytes 字節(jié)范圍 表示已下載的緩沖的字節(jié)范圍的對象
bufferingRate 整數???? 下載過程中每秒鐘平均接收到的位數
bufferingThrottled 布爾值 表示瀏覽器是否對緩沖進行了節(jié)流
controls 布爾值 取得或設置controls屬性,用于顯示或隱藏瀏覽器內置的控件
currentLoop 整數 媒體文件已經循環(huán)的次數
currentSrc 字符串 當前播放的媒體文件的URL
currentTime 浮點數 已經播放的秒數
defaultPlaybackRate 浮點數 取得或設置默認的播放速度。默認值為1.0秒
duration 浮點數 媒體的總播放時間(秒數)
ended 布爾值 表示媒體文件是否播放完成
loop 布爾值 取得或設置媒體文件在播放完成后是否再從頭開始播放
muted 布爾值 取得或設置媒體文件是否靜音
networkState 整數 表示當前媒體的網絡連接狀態(tài):0表示空,1表示正在加載,2表示正在加載元數據,3表示已經加載了第一幀,4表示加載完成
paused 布爾值 表示播放器是否暫停
playbackRate 浮點數 取得或設置當前的播放速度。用戶可以改變這個值,讓媒體播放速度變快或變慢,這與defaultPlaybackRate只能由開發(fā)人員修改的defaultPlaybackRate不同
played 時間范圍 到目前為止已經播放的時間范圍
readyState 整數 表示媒體是否已經就緒(可以播放了)。0表示數據不可用,1表示可以顯示當前幀,2表示可以開始播放,3表示媒體可以從頭到尾播放
seekable 時間范圍 可以搜索的時間范圍
seeking 布爾值 表示播放器是否正移動到媒體文件中的新位置
src 字符串 媒體文件的來源。任何時候都可以重寫這個屬性
start 浮點數 取得或設置媒體文件中開始播放的位置,以秒表示
totalBytes 整數 當前資源所需的總字節(jié)數
videoHeight 整數 返回視頻(不一定是元素)的高度。只適用于<video>
videoWidth 整數 返回視頻(不一定是元素)的寬度。只適用于<video>
volume 浮點數 取得或設置當前音量,值為0.0到1.0

??其中很多屬性也可以直接在<audio>和<video>元素中設置。

3.2、事件

??除了大量屬性之外,這兩個媒體元素還可以觸發(fā)很多事件。這些事件監(jiān)控著不同的屬性的變化,這些變化可能是媒體播放的結果,也可能是用戶操作播放器的結果。下表列出了媒體元素相關的事件。

事 件 觸發(fā)時機
abort 下載中斷
canplay 可以播放時;readyState值為 2
canplaythrough 播放可繼續(xù),而且應該不會中斷;readyState值為 3
canshowcurrentframe 當前幀已經下載完成;readyState值為 1
dataunavailable 因為沒有數據而不能播放;readyState值為 0
durationchange duration 屬性的值改變
emptied 網絡連接關閉
empty 發(fā)生錯誤阻止了媒體下載
ended 媒體已播放到末尾,播放停止
error 下載期間發(fā)生網絡錯誤
load 所有媒體已加載完成。這個事件可能會被廢棄,建議使用 canplaythrough
loadeddata 媒體的第一幀已加載完成
loadedmetadata 媒體的元數據已加載完成
loadstart 下載已開始
pause 播放已暫停
play 媒體已接收到指令開始播放
playing 媒體已實際開始播放
progress 正在下載
ratechange 播放媒體的速度改變
seeked 搜索結束
seeking 正移動到新位置
stalled 瀏覽器嘗試下載,但未接收到數據
timeupdate currentTime 被以不合理或意外的方式更新
volumechange volume 屬性值或 muted 屬性值已改變
waiting 播放暫停,等待下載更多數據

??這些事件之所以如此具體,就是為了讓開發(fā)人員只使用少量 HTML 和 JavaScript(與創(chuàng)建 Flash 影片相比)即可編寫出自定義的音頻/視頻播放器。

3.3、自定義媒體播放器

??使用<audio>和<video>元素的 play() 和 pause() 方法,可以手工控制媒體文件的播放。
??組合使用屬性、事件和這兩個方法,很容易創(chuàng)建一個自定義的媒體播放器,如下面的例子所示。

<div class="mediaplayer">
    <div class="video">
        <video id="player" src="movie.mov" poster="mymovie.jpg" width="300" height="200">
            Video player not available.
        </video>
    </div>
    <div class="controls">
        <input type="button" value="Play" id="video-btn">
        <span id="curtime">0</span>/<span id="duration">0</span>
    </div>
</div>

??以上基本的 HTML 再加上一些 JavaScript 就可以變成一個簡單的視頻播放器。以下就是 JavaScript 代碼。

// 取得元素的引用
var player = document.getElementById("player"),
    btn = document.getElementById("video-btn"),
    curtime = document.getElementById("curtime"),
    duration = document.getElementById("duration");

// 更新播放時間
duration.innerHTML = player.duration;

// 為按鈕添加事件處理程序
EventUtil.addHandler(btn, "click", function(event){
    if (player.paused){
        player.play();
        btn.value = "Pause";
    } else {
        player.pause();
        btn.value = "Play";
    }
});

// 定時更新當前時間
setInterval(function(){
    curtime.innerHTML = player.currentTime;
}, 250);

??以上 JavaScript 代碼給按鈕添加了一個事件處理程序,單擊它能讓視頻在暫停時播放,在播放時暫停。
??通過<video>元素的 load 事件處理程序,設置了加載完視頻后顯示播放時間。
??最后,設置了一個計時器,以更新當前顯示的時間。
??你可以進一步擴展這個視頻播放器,監(jiān)聽更多事件,利用更多屬性。而同樣的代碼也可以用于<audio>元素,以創(chuàng)建自定義的音頻播放器。

3.4、檢測編解碼器的支持情況

??如前所述,并非所有瀏覽器都支持<video>和<audio>的所有編解碼器,而這基本上就意味著你必須提供多個媒體來源。不過,也有一個 JavaScript API 能夠檢測瀏覽器是否支持某種格式和編解碼器。
??這兩個媒體元素都有一個 canPlayType() 方法,該方法接收一種格式/編解碼器字符串,返回"probably"、"maybe"或""( 空字符串)??兆址羌僦担虼丝梢韵裣旅孢@樣在 if 語句中使用 canPlayType():

if (audio.canPlayType("audio/mpeg")){
    // 進一步處理
}

??而"probably"和"maybe"都是真值,因此在 if 語句的條件測試中可以轉換成 true。

??如果給 canPlayType() 傳入了一種 MIME 類型,則返回值很可能是"maybe"或空字符串。這是因為媒體文件本身只不過是音頻或視頻的一個容器,而真正決定文件能否播放的還是編碼的格式。
??在同時傳入 MIME 類型和編解碼器的情況下,可能性就會增加,返回的字符串會變成"probably"。下面來看幾個例子。

var audio = document.getElementById("audio-player");

// 很可能"maybe"
if (audio.canPlayType("audio/mpeg")){
    // 進一步處理
}

// 可能是"probably"
if (audio.canPlayType("audio/ogg; codecs=\"vorbis\"")){
    // 進一步處理
}

??注意,編解碼器必須用引號引起來才行。下表列出了已知的已得到支持的音頻格式和編解碼器。

音 頻 字 符 串 支持的瀏覽器
AAC audio/mp4; codecs="mp4a.40.2" IE9+、Safari 4+、iOS版Safari
MP3 audio/mpeg IE9+、Chrome
Vorbis audio/ogg; codecs="vorbis" Firefox 3.5+、Chrome、Opera 10.5+
WAV audio/wav; codecs="1" Firefox 3.5+、Opera 10.5+、Chrome

??當然,也可以使用 canPlayType() 來檢測視頻格式。下表列出了已知的已得到支持的視頻格式和編解碼器。

視 頻 字 符 串 支持的瀏覽器
H.264 video/mp4; codecs="avc1.42E01E, mp4a.40.2" IE9+、Safari 4+、iOS版Safari、Android 版WebKit
Theora video/ogg; codecs="theora" Firefox 3.5+、Opera 10.5、Chrome
WebM video/webm; codecs="vp8, vorbis" Firefox 4+、Opera 10.6、Chrome

3.5、Audio 類型

??<audio>元素還有一個原生的 JavaScript 構造函數 Audio,可以在任何時候播放音頻。從同為 DOM 元素的角度看,Audio 與 Image 很相似,但 Audio 不用像 Image 那樣必須插入到文檔中。只要創(chuàng)建一個新實例,并傳入音頻源文件即可。

var audio = new Audio("sound.mp3");
EventUtil.addHandler(audio, "canplaythrough", function(event){
    audio.play();
});

??創(chuàng)建新的 Audio 實例即可開始下載指定的文件。下載完成后,調用 play() 就可以播放音頻。
??在 iOS 中,調用 play() 時會彈出一個對話框,得到用戶的許可后才能播放聲音。如果想在一段音頻播放后再播放另一段音頻,必須在 onfinish 事件處理程序中調用 play() 方法。

4、歷史狀態(tài)管理

??歷史狀態(tài)管理是現代 Web 應用開發(fā)中的一個難點。在現代 Web 應用中,用戶的每次操作不一定會打開一個全新的頁面,因此“后退”和“前進”按鈕也就失去了作用,導致用戶很難在不同狀態(tài)間切換。
??要解決這個問題,首選使用 hashchange 事件。HTML5 通過更新 history 對象為管理歷史狀態(tài)提供了方便。
??通過 hashchange 事件,可以知道 URL 的參數什么時候發(fā)生了變化,即什么時候該有所反應。而通過狀態(tài)管理 API ,能夠在不加載新頁面的情況下改變?yōu)g覽器的 URL 。
??為此,需要使用 history.pushState() 方法,該方法可以接收三個參數:狀態(tài)對象、新狀態(tài)的標題和可選的相對 URL。例如:

history.pushState({name:"Nicholas"}, "Nicholas' page", "nicholas.html");

??執(zhí)行 pushState() 方法后,新的狀態(tài)信息就會被加入歷史狀態(tài)棧,而瀏覽器地址欄也會變成新的相對 URL。
??但是,瀏覽器并不會真的向服務器發(fā)送請求,即使狀態(tài)改變之后查詢 location.href 也會返回與地址欄中相同的地址。
??另外,第二個參數目前還沒有瀏覽器實現,因此完全可以只傳入一個空字符串,或者一個短標題也可以。
??而第一個參數則應該盡可能提供初始化頁面狀態(tài)所需的各種信息。

??因為 pushState() 會創(chuàng)建新的歷史狀態(tài),所以你會發(fā)現“后退”按鈕也能使用了。按下“后退”按鈕,會觸發(fā) window 對象的 popstate 事件。
??popstate 事件發(fā)生后,事件對象中的狀態(tài)對象(event.state)是當前狀態(tài)。
??popstate 事件的事件對象有一個 state 屬性,這個屬性就包含著當初以第一個參數傳遞給 pushState() 的狀態(tài)對象。

EventUtil.addHandler(window, "popstate", function(event){
    var state = event.state;
    if (state){ // 第一個頁面加載時 state 為空
        processState(state);
    }
});

??得到這個狀態(tài)對象后,必須把頁面重置為狀態(tài)對象中的數據表示的狀態(tài)(因為瀏覽器不會自動為你做這些)。
??記住,瀏覽器加載的第一個頁面沒有狀態(tài),因此單擊“后退”按鈕返回瀏覽器加載的第一個頁面時,event.state 值為 null。

??要更新當前狀態(tài),可以調用 replaceState(),傳入的參數與 pushState() 的前兩個參數相同。
??調用這個方法不會在歷史狀態(tài)棧中創(chuàng)建新狀態(tài),只會重寫當前狀態(tài)。

history.replaceState({name:"Greg"}, "Greg's page");

??支持 HTML5 歷史狀態(tài)管理的瀏覽器有 Firefox 4+、Safari 5+、Opera 11.5+和 Chrome。
??在 Safari 和 Chrome 中,傳遞給 pushState() 或 replaceState() 的狀態(tài)對象中不能包含 DOM 元素。而 Firefox 支持在狀態(tài)對象中包含 DOM 元素。
??Opera 還支持一個 history.state 屬性,它返回當前狀態(tài)的狀態(tài)對象。
??在使用 HTML5 的狀態(tài)管理機制時,請確保使用 pushState() 創(chuàng)造的每一個“假”URL,在 Web 服務器上都有一個真的、實際存在的 URL 與之對應。否則,單擊“刷新”按鈕會導致 404 錯誤。

小結

??HTML5 除了定義了新的標記規(guī)則,還定義了一些 JavaScript API。這些 API 是為了讓開發(fā)人員創(chuàng)建出更好的、能夠與桌面應用媲美的用戶界面而設計的。本章討論了如下 API。

  • 跨文檔消息傳遞 API 能夠讓我們在不降低同源策略安全性的前提下,在來自不同域的文檔間傳遞消息。
  • 原生拖放功能讓我們可以方便地指定某個元素可拖動,并在操作系統要放置時做出響應。還可以創(chuàng)建自定義的可拖動元素及放置目標。
  • 新的媒體元素<audio>和<video>擁有自己的與音頻和視頻交互的 API。并非所有瀏覽器支持所有的媒體格式,因此應該使用 canPlayType() 檢查瀏覽器是否支持特定的格式。
  • 歷史狀態(tài)管理讓我們不必卸載當前頁面即可修改瀏覽器的歷史狀態(tài)棧。有了這種機制,用戶就可以通過“后退”和“前進”按鈕在頁面狀態(tài)間切換,而這些狀態(tài)完全由 JavaScript 進行控制。
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容