CSDN博客

HTML5新特性淺談
發表于2016/10/17 21:25:58 ?7809人閱讀
分類: 前端
轉載請注明出處:
http://blog.csdn.net/gane_cheng/article/details/52819118
http://www.ganecheng.tech/blog/52819118.html (瀏覽效果更好)
2014年10月29日,W3C宣布,經過接近8年的艱苦努力,HTML5標準規范終于制定完成。
HTML5將會取代1999年制定的HTML 4.01、XHTML 1.0標準,以期能在互聯網應用迅速發展的時候,使網絡標準達到符合當代的網絡需求,為桌面和移動平臺帶來無縫銜接的豐富內容。
作為2010年入坑IT的程序員來說,可以說一步一步見證著HTML5的發展。這些年為了兼容IE6放棄了很多HTML5的新特性。但是今時不同以往,移動設備的流行,天然支持HTML5,以及桌面端IE最終被用戶和微軟唾棄,更多支持HTML5瀏覽器的受歡迎,我要重新研究一下HTML5帶來的這些新特性。
HTML5 的新特性
① 語義特性(Semantic)
HTML5賦予網頁更好的意義和結構。
② 本地存儲特性(OFFLINE & STORAGE)
基于HTML5開發的網頁APP擁有更短的啟動時間,更快的聯網速度,這些全得益于HTML5 APP Cache,以及本地存儲功能。
③ 設備訪問特性 (DEVICE ACCESS)
從Geolocation功能的API文檔公開以來,HTML5為網頁應用開發者們提供了更多功能上的優化選擇,帶來了更多體驗功能的優勢。HTML5提供了前所未有的數據與應用接入開放接口。使外部應用可以直接與瀏覽器內部的數據直接相連,例如視頻影音可直接與microphones及攝像頭相聯。
④ 連接特性(CONNECTIVITY)
更有效的連接工作效率,使得基于頁面的實時聊天,更快速的網頁游戲體驗,更優化的在線交流得到了實現。HTML5擁有更有效的服務器推送技術,Server-Sent Event和WebSockets就是其中的兩個特性,這兩個特性能夠幫助我們實現服務器將數據“推送”到客戶端的功能。
⑤ 網頁多媒體特性(MULTIMEDIA)
支持網頁端的Audio、Video等多媒體功能, 與網站自帶的APPS,攝像頭,影音功能相得益彰。
⑥ 三維、圖形及特效特性(3D, Graphics & Effects)
基于SVG、Canvas、WebGL及CSS3的3D功能,用戶會驚嘆于在瀏覽器中,所呈現的驚人視覺效果。
⑦ 性能與集成特性(Performance & Integration)
沒有用戶會永遠等待你的Loading——HTML5會通過XMLHttpRequest2等技術,解決以前的跨域等問題,幫助您的Web應用和網站在多樣化的環境中更快速的工作。
下面分別對這七個新特性進行研究。
① 語義特性(Semantic)
HTML5增加了新的內容標簽,這些標簽帶有一定的語義,使搜索引擎爬取你的網站信息更高效。
HTML4中的內容標簽級別相同,無法區分各部分內容。而HTML5中的內容標簽互相獨立,級別不同,搜索引擎以及統計軟件等均可快速識別各部分內容。
這些標簽在新聞類網站,博客類網站很有用。
最大的問題就是當使用這些新的語義元素時,那些不支持的瀏覽器如何處理這些元素。
見過的最多的解決方法是這樣的。
<section class="section"><!-- content --> </section>
- 1
- 2
- 3
- 4
- 5
.section
{color: blue; }
- 1
- 2
- 3
- 4
如果還想了解更多語義標簽兼容性問題,可以參考這篇文章http://html5.group.iteye.com/group/wiki/3293-html5
② 本地存儲特性(OFFLINE & STORAGE)
HTML5提供了網頁存儲的API,方便Web應用的離線使用。除此之外,新的API相對于cookie也有著高安全性,高效率,更大空間等優點。
先看W3C對離線存儲的介紹。
Web Apps can start faster and work even if there is no internet connection, thanks to the HTML5 App Cache, as well as the Local Storage, Indexed DB, and the File API specifications.
HTML5離線存儲包含 應用程序緩存
,本地存儲
,索引數據庫
,文件接口
。
下面依次展開介紹。
(1)應用程序緩存(Application Cache)
使用 HTML5,通過創建 cache manifest 文件,可以輕松地創建 web 應用的離線版本。
HTML5引入了應用程序緩存,這意味著 web 應用可進行緩存,并可在沒有因特網連接時進行訪問。
應用程序緩存為應用帶來三個優勢:
- 離線瀏覽 – 用戶可在應用離線時使用它們
- 速度 – 已緩存資源加載得更快
- 減少服務器負載 – 瀏覽器將只從服務器下載更新過或更改過的資源。
甭廢話,先來感受一下Application Cache的魅力。Shut up,show me the demo!
Demo鏈接:http://www.ganecheng.tech/welcome_offline.html
1.打開這個網頁,第一次等待加載完成之后,頁面和普通的網頁沒有區別。
2.點擊刷新按鈕,或者強制刷新按鈕,看一下第二次打開的速度。有沒有快到爆。(速度)
3.現在我要求你拔掉網線,斷開WiFi,再次點擊刷新按鈕,或者強制刷新按鈕,看一下第三次打開的速度。有沒有快到爆。注意,現在并沒有聯網,和服務器失去連接,依然秒開網頁,還能正常操作網頁。(離線瀏覽,減少服務器負載)
看完了效果,看一下App Cache的原理。
當我們第一次正確配置cache manifest后,瀏覽器會將清單文件中的資源緩存下來。當我們再次訪問該應用時,瀏覽器會直接返回緩存中的資源,然后檢查manifest文件是否有變動,如果有變動就會把相應的變動更新下來,同時改變瀏覽器里面的app cache。
使用方法
頁面聲明使用App Cache
<!DOCTYPE HTML> <html manifest="index.manifest">
- 1
- 2
清單文件中寫明資源。
CACHE MANIFEST
theme.css
logo.gif
main.jsNETWORK:
login.asp FALLBACK: /html5/ /404.html
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
manifest 文件可分為三個部分:
CACHE MANIFEST - 在此標題下列出的文件將在首次下載后進行緩存
NETWORK - 在此標題下列出的文件需要與服務器的連接,且不會被緩存
FALLBACK - 在此標題下列出的文件規定當頁面無法訪問時的回退頁面(比如 404 頁面)
CACHE MANIFEST,是必需的
NETWORK 規定文件 “login.asp” 永遠不會被緩存,且離線時是不可用的
FALLBACK 規定如果無法建立因特網連接,則用 “404.html” 替代 /html5/ 目錄中的所有文件
一旦應用被緩存,它就會保持緩存直到發生下列情況:
- 用戶清空瀏覽器緩存 ,用戶怎么做,頁面左右不了,說了等于沒說。
- manifest 文件被修改,請注意:更新清單中列出的某個文件并不意味著瀏覽器會重新緩存該資源。清單文件本身必須進行更改,一般是加一個注釋,注釋的內容是日期,想更新了,改一下日期。
- 由程序來更新應用緩存,這個稍微靠譜一點。
需要注意的是,更新后的資源需要下次打開頁面才能生效,本次打開的頁面在更新資源之前就已經從緩存中拿到資源并加載完畢了。
由程序來更新,需要依賴manifest文件被修改這一條,因為調用的是瀏覽器提供的接口,檢測 window.applicationCache.status 的值,如果是 UPDATEREADY,說明瀏覽器比較manifest文件完畢,可以更新緩存了。window.applicationCache.swapCache()。更新完了,不會立即生效,window.location.reload();重新加載一下頁面。
緩存有這么多狀態。
var appCache = window.applicationCache;
switch (appCache.status)
{ case appCache.UNCACHED: // UNCACHED == 0 return 'UNCACHED'; break; case appCache.IDLE: // IDLE == 1 return 'IDLE'; break; case appCache.CHECKING: // CHECKING == 2 return 'CHECKING'; break; case appCache.DOWNLOADING: // DOWNLOADING == 3 return 'DOWNLOADING'; break; case appCache.UPDATEREADY: // UPDATEREADY == 4 return 'UPDATEREADY'; break; case appCache.OBSOLETE: // OBSOLETE == 5 return 'OBSOLETE'; break; default: return 'UKNOWN CACHE STATUS'; break; };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
程序更新緩存的方法。
// Check if a new cache is available on page load. window.addEventListener('load', function(e) { window.applicationCache.addEventListener('updateready', function(e) { if (window.applicationCache.status == window.applicationCache.UPDATEREADY) { // Browser downloaded a new app cache. // Swap it in and reload the page to get the new hotness. window.applicationCache.swapCache(); if (confirm('A new version of this site is available. Load it?')) { window.location.reload(); } } else { // Manifest didn't changed. Nothing new to server. } }, false); }, false);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
更多更新緩存的方法,可以參考這篇文章:http://www.jb51.net/html5/67850.html
總的來說,App Cache的三個優點非常明顯。但是有幾個坑,卻會導致沒人愿意使用這個新特性。
1.使用了App Cache的頁面在清單文件更新之后去更新頁面資源,但是只在下次打開頁面才能生效,這意味著,我們需要使用代碼判斷是不是最新版本,不是的話,刷新一次頁面。這種體驗很不好。
2.使用了App Cache的頁面也會被緩存,這對于需要動態更新的頁面來說,幾乎是個噩夢。用戶訪問到的頁面不是最新的,會導致非常多的問題。
3.App cache與browser cache混合在一起會使更新機制變得更加復雜,主要有以下幾個因素:
1) App cache在各瀏覽器平臺實現上存在差異;
2) 各瀏覽器又提供了不同的頁面刷新機制;
3) app cache還與傳統的browser cache有著千絲萬縷的聯系;對于它倆如何協同工作,HTML5的相關規范沒有對app cache的細節給出非常明確的規定; 瀏覽器官方文檔有沒有給出非常明確的說明。
4) browser cache的更新機制本身就已經很復雜。
5) 如果manifest文件本身就有緩存時間,或設置為永遠都可用,那你的網頁永遠都不會被更新了。
App Cache更多的坑,可以參考這篇文章:http://dreclo.blog.163.com/blog/static/528789512014111675023409/ , 然后再決定要不要使用這一新特性。
從驚嘆于App Cache的強大,到填坑,W3C花了這么長時間,就弄出來這么個東西,真是令人失望。學會使用App Cache只用了不到一小時,填坑填了一下午。每個頁面都要有manifest,每個頁面都要加代碼去判斷去更新緩存,我可以罵人嗎。
(2)本地存儲(Local Storage)
本地存儲發展歷史.
最早的Cookies自然是大家都知道,問題主要就是太小,大概也就4KB的樣子,而且IE6只支持每個域名20個cookies,太少了。優勢就是大家都支持,而且支持得還蠻好。很早以前那些禁用cookies的用戶也都慢慢的不存在了,就好像以前禁用javascript的用戶不存在了一樣。
userData是IE的東西,垃圾。現在用的最多的是Flash吧,空間是Cookie的25倍,基本夠用。再之后Google推出了Gears,雖然沒有限制,但不爽的地方就是要裝額外的插件(沒具體研究過)。到了HTML5把這些都統一了,官方建議是每個網站5MB,非常大了,就存些字符串,足夠了。比較詭異的是居然所有支持的瀏覽器目前都采用的5MB,盡管有一些瀏覽器可以讓用戶設置,但對于網頁制作者來說,目前的形勢就5MB來考慮是比較妥當的。
首先自然是檢測瀏覽器是否支持本地存儲。在HTML5中,本地存儲是一個window的屬性,包括localStorage和sessionStorage,從名字應該可以很清楚的辨認二者的區別,前者是一直存在本地的,后者只是伴隨著session,窗口一旦關閉就沒了。二者用法完全相同,這里以localStorage為例。
if(window.localStorage)
{alert('This browser supports localStorage');
}
else { alert('This browser does NOT support localStorage'); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
存儲數據的方法就是直接給window.localStorage添加一個屬性,例如:window.localStorage.a 或者 window.localStorage[“a”]。它的讀取、寫、刪除操作方法很簡單,是以鍵值對的方式存在的,如下:
localStorage.a = 3;//設置a為"3"
localStorage["a"] = "sfsf";//設置a為"sfsf",覆蓋上面的值 localStorage.setItem("b","isaac");//設置b為"isaac" var a1 = localStorage["a"];//獲取a的值 var a2 = localStorage.a;//獲取a的值 var b = localStorage.getItem("b");//獲取b的值 localStorage.removeItem("c");//清除c的值
- 1
- 2
- 3
- 4
- 5
- 6
- 7
這里最推薦使用的自然是getItem()和setItem(),清除鍵值對使用removeItem()。如果希望一次性清除所有的鍵值對,可以使用clear()。另外,HTML5還提供了一個key()方法,可以在不知道有哪些鍵值的時候使用,如下:
var storage = window.localStorage;
function showStorage() { for(var i=0;i<storage.length;i++) { //key(i)獲得相應的鍵,再用getItem()方法獲得對應的值 document.write(storage.key(i)+ " : " + storage.getItem(storage.key(i)) + "<br>"); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
寫一個最簡單的,利用本地存儲的計數器:
var storage = window.localStorage;if (!storage.getItem("pageLoadCount")) {storage.setItem("pageLoadCount",0); } storage.pageLoadCount = parseInt(storage.getItem("pageLoadCount")) + 1;//必須格式轉換 document.getElementById("count").innerHTML = storage.pageLoadCount; showStorage();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
不斷刷新就能看到數字在一點點上漲,如下圖所示:
需要注意的是,HTML5本地存儲只能存字符串,任何格式存儲的時候都會被自動轉為字符串,所以讀取的時候,需要自己進行類型的轉換。這也就是上一段代碼中parseInt必須要使用的原因。
想要對localStorage了解更多請參考這篇文章:http://www.cnblogs.com/xiaowei0705/archive/2011/04/19/2021372.html
(3)索引數據庫(Indexed DB)
從本質上說,IndexedDB允許用戶在瀏覽器中保存大量的數據。任何需要發送大量數據的應用都可以得益于這個特性,可以把數據存儲在用戶的瀏覽器端。當前這只是IndexedDB的其中一項功能,IndexedDB也提供了強大的基于索引的搜索api功能以獲得用戶所需要的數據。
用戶可能會問:IndexedDB是和其他以前的存儲機制(如cookie,session)有什么不同?
Cookies是最常用的瀏覽器端保存數據的機制,但其保存數據的大小有限制并且有隱私問題。Cookies并且會在每個請求中來回發送數據,完全沒辦法發揮客戶端數據存儲的優勢。
再來看下Local Storage本地存儲機制的特點。Local Storage在HTML 5中有不錯的支持,但就總的存儲量而言依然是有所限制的。Local Storage并不提供真正的“檢索API”,本地存儲的數據只是通過鍵值對去訪問。Local Storage對于一些特定的需要存儲數據的場景是很適合的,例如,用戶的喜好習慣,而IndexedDB則更適合存儲如廣告等數據(它更象一個真正的數據庫)。
一般來說,有兩種不同類型的數據庫:關系型和文檔型(也稱為NoSQL或對象)。關系數據庫如SQL Server,MySQL,Oracle的數據存儲在表中。文檔數據庫如MongoDB,CouchDB,Redis將數據集作為個體對象存儲。IndexedDB是一個文檔數據庫,它在完全內置于瀏覽器中的一個沙盒環境中(強制依照(瀏覽器)同源策略)。
對數據庫的每次操作,描述為通過一個請求打開數據庫,訪問一個object store,再繼續。
打開數據庫的請求生命周期
IndexedDB是否適合我的應用程序?
現在最關鍵的問題:“IndexedDB是否適合我的應用程序?“像往常一樣,答案是肯定的:“視情況而定。“首先當你試圖在客戶端保存數據時,你會考慮HTML5本地存儲。本地存儲得到廣泛瀏覽器的支持,有非常易于使用的API。簡單有其優勢,但其劣勢是無法支持復雜的搜索策略,存儲大量的數據,并提供事務支持。
IndexedDB是一個數據庫。所以,當你想為客戶端做出決定,考慮你如何在服務端選擇一個持久化介質的數據庫。你可能會問自己一些問題來幫助決定客戶端數據庫是否適合您的應用程序,包括:
- 你的用戶通過瀏覽器訪問您的應用程序,(瀏覽器)支持IndexedDB API嗎 ?
- 你需要存儲大量的數據在客戶端?
- 你需要在一個大型的數據集合中快速定位單個數據點?
-
你的架構在客戶端需要事務支持嗎?
如果你對其中的任何問題回答了“是的”,很有可能,IndexedDB是你的應用程序的一個很好的候選。
IndexedDB用法
現在,你已經有機會熟悉了一些的整體概念,下一步是開始實現基于IndexedDB的應用程序。第一個步驟需要統一IndexedDB在不同瀏覽器的實現。您可以很容易地添加各種廠商特性的選項的檢查,同時在window對象上把它們設置為官方對象相同的名稱。下面的清單展示了window.indexedDB,window.IDBTransaction,window.IDBKeyRange的最終結果是如何都被更新,它們被設置為相應的瀏覽器的特定實現。
window.indexedDB = window.indexedDB ||window.mozIndexedDB ||window.webkitIndexedDB ||window.msIndexedDB; window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction; window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
現在,每個數據庫相關的全局對象持有正確的版本,應用程序可以準備使用IndexedDB開始工作。
打開數據庫
// 打開我們的數據庫,數據庫名稱,版本號
var request = indexedDB.open("MyTestDatabase", 3);
- 1
- 2
indexedDB的三個事件
//數據庫打開成功執行
request.onsuccess = function (event)
{// Better use "this" than "req" to get the result to avoid problems with // garbage collection. // db = request.result; db = this.result; console.debug("initDb DONE"); }; //數據庫打開失敗執行 request.onerror = function (event) { console.error("initDb:", evt.target.errorCode); }; //在數據庫第一次被打開時或者當指定的版本號高于當前被持久化的數據庫的版本號時,觸發此事件,可以在這個地方創建對象存儲空間結構,更新結構,加索引等. request.onupgradeneeded = function (event) { console.debug("initDb.onupgradeneeded"); var store = event.currentTarget.result.createObjectStore( DB_STORE_NAME, { keyPath: 'id', autoIncrement: true }); };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
添加數據或更新數據
// 我們的客戶數據看起來像這樣。
const customerData = [{ ssn: "444-44-4444", name: "Bill", age: 35, email: "bill@company.com" }, { ssn: "555-55-5555", name: "Donna", age: 32, email: "donna@home.org" } ]; var transaction = db.transaction(["customers"], "readwrite"); // 當所有的數據都被增加到數據庫時執行一些操作 transaction.oncomplete = function(event) { alert("All done!"); }; transaction.onerror = function(event) { // 不要忘記進行錯誤處理! }; var objectStore = transaction.objectStore("customers"); for (var i in customerData) { var request = objectStore.add(customerData[i]); request.onsuccess = function(event) { // event.target.result == customerData[i].ssn }; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
刪除數據
var request = db.transaction(["customers"], "readwrite").objectStore("customers").delete("444-44-4444"); request.onsuccess = function(event) { // 刪除數據成功! };
- 1
- 2
- 3
- 4
- 5
- 6
獲取數據
var transaction = db.transaction(["customers"]);
var objectStore = transaction.objectStore("customers");
var request = objectStore.get("444-44-4444"); request.onerror = function(event) { // 錯誤處理! }; request.onsuccess = function(event) { // 對 request.result 做些操作! alert("Name for SSN 444-44-4444 is " + request.result.name); };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
對于一個“簡單”的提取這里的代碼有點多了。下面看我們怎么把它再縮短一點,假設你在數據庫的級別上來進行的錯誤處理:
db.transaction("customers").objectStore("customers").get("444-44-4444").onsuccess = function(event) { alert("Name for SSN 444-44-4444 is " + event.target.result.name); };
- 1
- 2
- 3
- 4
使用游標獲取數據
使用 get() 要求你知道你想要檢索哪一個鍵。如果你想要遍歷對象存儲空間中的所有值,那么你可以使用游標。看起來會像下面這樣:
var customers = [];var objectStore = db.transaction("customers").objectStore("customers");objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; if (cursor) { customers.push(cursor.value); cursor.continue(); } else { alert("Got all customers: " + customers); } };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
這里有一個封裝好的完整的例子,簡化了這些操作。
<script type="text/javascript"> window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction; window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange; (function(window){ 'use strict'; var db = { version: 1, // important: only use whole numbers! objectStoreName: 'tasks', instance: {}, upgrade: function (e) { var _db = e.target.result, names = _db.objectStoreNames, name = db.objectStoreName; if (!names.contains(name)) { _db.createObjectStore( name, { keyPath: 'id', autoIncrement: true }); } }, errorHandler: function (error) { window.alert('error: ' + error.target.code); debugger; }, open: function (callback) { var request = window.indexedDB.open( db.objectStoreName, db.version); request.onerror = db.errorHandler; request.onupgradeneeded = db.upgrade; request.onsuccess = function (e) { db.instance = request.result; db.instance.onerror = db.errorHandler; callback(); }; }, getObjectStore: function (mode) { var txn, store; mode = mode || 'readonly'; txn = db.instance.transaction( [db.objectStoreName], mode); store = txn.objectStore( db.objectStoreName); return store; }, save: function (data, callback) { db.open(function () { var store, request, mode = 'readwrite'; store = db.getObjectStore(mode), request = data.id ? store.put(data) : store.add(data); request.onsuccess = callback; }); }, getAll: function (callback) { db.open(function () { var store = db.getObjectStore(), cursor = store.openCursor(), data = []; cursor.onsuccess = function (e) { var result = e.target.result; if (result && result !== null) { data.push(result.value); result.continue(); } else { callback(data); } }; }); }, get: function (id, callback) { id = parseInt(id); db.open(function () { var store = db.getObjectStore(), request = store.get(id); request.onsuccess = function (e){ callback(e.target.result); }; }); }, 'delete': function (id, callback) { id = parseInt(id); db.open(function () { var mode = 'readwrite', store, request; store = db.getObjectStore(mode); request = store.delete(id); request.onsuccess = callback; }); }, deleteAll: function (callback) { db.open(function () { var mode, store, request; mode = 'readwrite'; store = db.getObjectStore(mode); request = store.clear(); request.onsuccess = callback; }); } }; window.app = window.app || {}; window.app.db = db; }(window)); //將數據顯示在頁面上的方法 var bindData = function (data) { $("#IndexedDB").html(''); if(data.length === 0){ $("#IndexedDB").html("沒有數據"); return; } data.forEach(function (note) { $("#IndexedDB").append(note.id+","+note.title+","+note.text); }); }; //一個新數據 var note = { id:1, title: "數據1", text: "數據1的另一個字段" }; //又一個新數據 var note2 = { id:2, title: "數據2", text: "數據2的另一個字段" }; //插入或更新數據(insert or update) window.app.db.save(note,function() { //window.app.db.getAll(bindData); }); window.app.db.save(note2); //獲取數據 window.app.db.get(1,function(item) { //alert("window.app.db.get:"+item.id+","+item.title+","+item.text); }); //刪除數據 window.app.db.delete(1,function() { //alert("window.app.db.get:"+item.id+","+item.title+","+item.text); }); //刪除所有 window.app.db.deleteAll(function() { //alert("window.app.db.get:"+item.id+","+item.title+","+item.text); }); </script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
想對IndexedDB更多用法進行了解的可以參考這兩篇文章。
https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API/Using_IndexedDB
http://web.jobbole.com/81793/
除了IndexedDB以外,還有一種已經被W3C放棄的Web SQL。
瀏覽器支持本地數據庫并不是從IndexedDB才開始實現,它是在Web SQL實現之后的一種新方法。類似IndexedDB,Web SQL是一個客戶端數據庫,但它作為一個關系數據庫的實現,使用結構化查詢語言(SQL)與數據庫通信。Web SQL的歷史充滿了曲折,但底線是沒有主流的瀏覽器廠商對Web SQL繼續支持。
如果Web SQL實際上是一個廢棄的技術,為什么還要提它呢?有趣的是,Web SQL在瀏覽器里得到穩固的支持。Chrome, Safari, iOS Safari, and Android 瀏覽器都支持。另外,并不是這些瀏覽器的最新版本才提供支持,許多這些最新最好的瀏覽器之前的版本也可以支持。有趣的是,如果你為Web SQL添加支持來支持IndexedDB,你突然發現,許多瀏覽器廠商和版本成為支持瀏覽器內置數據庫的某種化身。
因此,如果您的應用程序真正需要一個客戶端數據庫,你想要達到的最高級別的采用可能,當IndexedDB不可用時,也許您的應用程序可能看起來需要選擇使用Web SQL來支持客戶端數據架構。雖然文檔數據庫和關系數據庫管理數據有鮮明的差別,但只要你有正確的抽象,就可以使用本地數據庫構建一個應用程序。
Web SQL的用法類似于操作SQLite數據庫,在這里不展開介紹了。
想對Web SQL了解更多,可以參考這篇文章:http://www.ibm.com/developerworks/cn/web/1210_jiangjj_html5db/
(4)文件接口(File API)
在之前我們操作本地文件都是使用flash、silverlight或者第三方的activeX插件等技術,由于使用了這些技術后就很難進行跨平臺、或者跨瀏覽器、跨設備等情況下實現統一的表現,從另外一個角度來說就是讓我們的web應用依賴了第三方的插件,而不是很獨立,不夠通用。在HTML5標準中,默認提供了操作文件的API讓這一切直接標準化。有了操作文件的API,讓我們的Web應用可以很輕松的通過JS來控制文件的讀取、寫入、文件夾、文件等一系列的操作。
先看一個demo。之前我們操作一個圖片文件,都是先將圖片上傳到服務器端,然后再使用一個img標簽指向到服務器的url地址,然后再進行一個使用第三方插件進行圖片處理,而現在這一切都不需要服務器端了,因為FileReader對象提供的幾個讀取文件的方法變得異常簡單,而且全部是客戶端js的操作。
http://html5demos.com/file-api/
現在我們自己來使用這些API。
先寫好我們的HTML頁面。
<input type="file" multiple="multiple" name="fileDemo" id="fileDemo" /> <br /> <input type="button" value="獲取文件的名字" id="btnGetFile" /> <input type="button" value="readAsDataURL" id="readAsDataURL" onclick="showDataByURL();" /> <input type="button" value="readAsBinaryString" id="readAsBinaryString" onclick="showDataByBinaryString();" /> <input type="button" value="readAsText" id="readAsText" onclick="showDataByText();" /> <div id="result"></div>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
獲取文件名
$("#btnGetFile").click(function(e){var fileList = document.getElementById("fileDemo").files; for (var i = 0; i < fileList.length; i++) { if (!(/image\/\w+/.test(fileList[i].type))) { $("#result").append("<span>type:" + fileList[i].type + "--******非圖片類型*****--name:" + fileList[i].name + "--size:" + fileList[i].size + "</span><br />"); } else { $("#result").append("<span>type:" + fileList[i].type + "--name:" + fileList[i].name + "--size:" + fileList[i].size + "</span><br />"); } } });
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
readAsDataURL()
開始讀取指定的Blob對象或File對象中的內容. 當讀取操作完成時,readyState屬性的值會成為DONE,如果設置了onloadend事件處理程序,則調用之.同時,result屬性中將包含一個data: URL格式的字符串以表示所讀取文件的內容.
這個方法很有用,比如,可以實現圖片的本地預覽.
function showDataByURL(){var resultFile = document.getElementById("fileDemo").files[0]; if (resultFile) { var reader = new FileReader(); reader.readAsDataURL(resultFile); reader.onload = function(e) { var urlData = this.result; document.getElementById("result").innerHTML += "<img src='" + urlData + "' alt='" + resultFile.name + "' />"; }; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
readAsBinaryString()
開始讀取指定的Blob對象或File對象中的內容. 當讀取操作完成時,readyState屬性的值會成為DONE,如果設置了onloadend事件處理程序,則調用之.同時,result屬性中將包含所讀取文件的原始二進制數據.
function showDataByBinaryString(){var resultFile = document.getElementById("fileDemo").files[0]; if (resultFile) { var reader = new FileReader(); //異步方式,不會影響主線程 reader.readAsBinaryString(resultFile); reader.onload = function(e) { var urlData = this.result; document.getElementById("result").innerHTML += urlData; }; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
readAsText()
開始讀取指定的Blob對象或File對象中的內容. 當讀取操作完成時,readyState屬性的值會成為DONE,如果設置了onloadend事件處理程序,則調用之.同時,result屬性中將包含一個字符串以表示所讀取的文件內容.
function showDataByText(){var resultFile = document.getElementById("fileDemo").files[0]; if (resultFile) { var reader = new FileReader(); reader.readAsText(resultFile, 'gb2312'); reader.onload = function(e) { var urlData = this.result; document.getElementById("result").innerHTML += urlData; }; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
事件處理程序
onabort
當讀取操作被中止時調用.
onerror
當讀取操作發生錯誤時調用.
onload
當讀取操作成功完成時調用.
onloadend
當讀取操作完成時調用,不管是成功還是失敗.該處理程序在onload或者onerror之后調用.
onloadstart
當讀取操作將要開始之前調用.
onprogress
在讀取數據過程中周期性調用.
在文件上傳時,HTML5還支持拖拽功能。
<div id="holder"></div> var holder = document.getElementById('holder');holder.ondrop = function (e) { e.preventDefault(); var file = e.dataTransfer.files[0], reader = new FileReader(); reader.onload = function (event) { console.log(event.target); holder.style.background = 'url(' + event.target.result + ') no-repeat center'; }; console.log(file); reader.readAsDataURL(file); return false; };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
想對HTML5文件接口操作文件了解更多請參考下面的文章:
https://developer.mozilla.org/zh-CN/docs/Using_files_from_web_applications
https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader
http://blog.csdn.net/testcs_dn/article/details/8695532
http://www.cnblogs.com/fly_dragon/archive/2012/06/02/2532035.html
③ 設備訪問特性 (DEVICE ACCESS)
來一段W3C對這個特性的介紹。
Beginning with the Geolocation API, Web Applications can present rich, device-aware features and experiences. Incredible device access innovations are being developed and implemented, from audio/video input access to microphones and cameras, to local data such as contacts & events, and even tilt orientation.
大致包含 地理位置API
,媒體訪問API
,訪問聯系人及事件
,設備方向
。
下面分別進行研究。
(1)地理位置API(Geolocation API)
HTML5 Geolocation API 用于獲得用戶的地理位置。
鑒于該特性可能侵犯用戶的隱私,除非用戶同意,否則用戶位置信息是不可用的。一般網頁在調用此信息時,會彈出權限申請窗口。
先上Demo:http://www.w3school.com.cn/tiy/t.asp?f=html5_geolocation_error
如果電腦獲取不到位置信息的話,就在手機上試一下吧。
getCurrentPosition()
getCurrentPosition() 方法來獲得用戶的位置。
標準用法如下:
<script>var x=document.getElementById("demo"); function getLocation() { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(showPosition); } else { x.innerHTML="Geolocation is not supported by this browser."; } } function showPosition(position) { x.innerHTML="Latitude: " + position.coords.latitude + "<br />Longitude: " + position.coords.longitude; } </script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
-檢測是否支持地理定位
-如果支持,則運行 getCurrentPosition() 方法。如果不支持,則向用戶顯示一段消息。
-如果getCurrentPosition()運行成功,則向參數showPosition中規定的函數返回一個coordinates對象
-showPosition() 函數獲得并顯示經度和緯度
上面的例子是一個非常基礎的地理定位腳本,不含錯誤處理。
完整的處理應該是這樣的.
<script>var x = document.getElementById("demo"); function getLocation() { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(showPosition, showError); } else { x.innerHTML = "Geolocation is not supported by this browser."; } } function showPosition(position) { x.innerHTML = "Latitude: " + position.coords.latitude + "<br />Longitude: " + position.coords.longitude; } function showError(error) { switch (error.code) { case error.PERMISSION_DENIED: x.innerHTML = "User denied the request for Geolocation." break; case error.POSITION_UNAVAILABLE: x.innerHTML = "Location information is unavailable." break; case error.TIMEOUT: x.innerHTML = "The request to get user location timed out." break; case error.UNKNOWN_ERROR: x.innerHTML = "An unknown error occurred." break; } } </script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
錯誤代碼:
-Permission denied - 用戶不允許地理定位
-Position unavailable - 無法獲取當前位置
-Timeout - 操作超時
-Unknown error - 未知錯誤
如果想對地理位置API了解更多,可以參考這個網址。http://www.w3school.com.cn/html5/html_5_geolocation.asp
(2)媒體訪問API(media API)
先上demo。麥克風和攝像頭也涉及到用戶隱私,所以瀏覽器會向用戶申請訪問權限。
https://www.ganecheng.tech/html5/media.html
getUserMedia()
以上的效果,都是通過getUserMedia()方法實現的。
<video id="video" autoplay="autoplay" style='width: 640px; height: 480px'></video> <button id='picture'>PICTURE</button> <canvas id="canvas" width="640" height="480"></canvas>
- 1
- 2
- 3
var video = document.getElementById("video");var context = canvas.getContext("2d");var errocb = function(code) { console.log(code); }; if (navigator.getUserMedia) { // 標準的API navigator.getUserMedia( { "video" : true }, function(stream) { video.src = stream; video.play(); }, errocb); } else if (navigator.webkitGetUserMedia) { // WebKit 核心的API console.log(navigator.webkitGetUserMedia); navigator.webkitGetUserMedia( { "video" : true }, function(stream) { video.src = window.webkitURL.createObjectURL(stream); video.play(); }, errocb); } // 將拍好的照片顯示在畫布上 document.getElementById("picture").addEventListener("click", function() { context.drawImage(video, 0, 0, 640, 480); });
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
視頻獲取就是這個樣子,音頻獲取添加"audio" : true
即可。
想對媒體API了解更多,參考文章:http://blog.csdn.net/u010359143/article/details/50326981
http://blog.csdn.net/renfufei/article/details/21168239
http://www.jb51.net/html5/81028.html
(3)聯系人管理API(Contacts Manager API)
這個純屬于W3C的美好設想,還沒有正式納入標準。目前沒有搜到靠譜的資料。
當初的提議可以參考這里。http://contacts-manager-api.sysapps.org/
但是有兩個類似的變通實現。
FirefoxOS(已結束生命)示例代碼。
var request = window.navigator.contacts.getAll();
var count = 0;
request.onsuccess = function () { if(this.result) { count++; // Display the name of the contact console.log(this.result.givenName + ' ' + this.result.familyName); // Move to the next contact which will call the request.onsuccess with a new result this.continue(); } else { console.log(count + 'contacts found.'); } } request.onerror = function () {}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
想要對這個已無生命氣息的web操作系統了解更多的參考這里 https://developer.mozilla.org/en-US/docs/Mozilla/B2G_OS/API/ContactManager
Cordova(Hybrid App)實現方式參考這里https://cordova.apache.org/docs/en/3.0.0/cordova/contacts/contacts.html
(4)設備方向和運動API(DeviceOrientation & DeviceMotion API)
DeviceOrientation常用于檢測重力感應方向,DeviceMotion常用于搖一搖功能。
① DeviceOrientation
重力感應也是原生APP中經常見到的一個功能,在Web App中的應用多見于判斷屏幕的旋轉方向,以及在此基礎上實現的場景應用,如控制頁面上物體的左右移動,加減速等。
在Web App中實現以上的功能,需要實時獲取屏幕的旋轉方向參數,這些參數可以從瀏覽器的利用HTML5的DeviceOrientation API獲得。
當瀏覽器的Orientation發生變化時,觸發DeviceOrientation事件,并返回一個DeviceOrientationEvent對象,其屬性列表如下:
屬性 | 釋義 |
---|---|
alpha | 設備指示的方向,根據指南針的設定情況而定 |
beta | 設備繞x軸旋轉的角度 |
gamma | 設備繞y軸旋轉的角度 |
注:不同版本的手機操作系統和瀏覽器,以及不同的應用程序中內置的瀏覽器對deviceorientation事件的支持不盡相同。尤其在Android平臺上,可能會出現有的設備正常工作,有的則毫無反應的情況。
工作原理
根據event對象的三個方向的參數來確定設備的旋轉角度。其中,alpha的取值范圍是0-360,這個需要根據設備的指南針設定情況而定,一般來說,設備指向正北方向時為0.beta值為設備繞x軸旋轉的角度,取值范圍為-180-180。gamma取值范圍-90-90.
這里面alpha值的意義并不大,主要參考beta和gamma值。
當屏幕從水平沿y軸向左傾斜時gamma值變為負值,向右傾斜變為正值。
檔屏幕從水平沿x軸向前傾斜時beta值變為正值,向后傾斜時變為負值。
所以,如果我們設定一個閾值,當beta和gamma的絕對值大于這個閾值時,我們就認為設備發生了旋轉。另外根據beta和gamma的值來判斷向左傾斜還是向右傾斜,以及傾斜的程度。
實現方式和示例
首先是為瀏覽器綁定deviceorientation事件和處理程序。
//add deviceorientation event listener
if(window.DeviceOrientationEvent)
{window.addEventListener('deviceorientation',DeviceOrientationHandler,false); }else { alert("您的瀏覽器不支持DeviceOrientation"); } function DeviceOrientationHandler(event) { var alpha = event.alpha, beta = event.beta, gamma = event.gamma; if(alpha != null || beta != null || gamma != null) { dataContainerOrientation.innerHTML = "alpha:" + alpha + "<br />beta:" + beta + "<br />gamma:" + gamma; //判斷屏幕方向 var html = ""; if( Math.abs(gamma) < GAMMA_MIN && Math.abs(beta) > BETA_MAX ){ html = "屏幕方向:Portrait"; } if( Math.abs(beta) < BETA_MIN && Math.abs(gamma) > GAMMA_MAX ){ html = "屏幕方向:Landscape"; } var gamma_html = ""; if( gamma > 0 ){ gamma_html = "向右傾斜"; }else{ gamma_html = "向左傾斜"; } html += "<br />"+gamma_html stage.innerHTML = html; }else { dataContainerOrientation.innerHTML = "當前瀏覽器不支持DeviceOrientation"; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
這個示例中展示了如何利用beta和gamma值來展示屏幕的旋轉方向和側翻方向。要實現更精確的物體判斷,還需要復雜的算法來計算。
擴展應用
使用DeviceOrientation API接口可以實現在web中獲取手機設備的屏幕旋轉方向參數,在示例的基礎上進行改進,可以擴展到在屏幕上控制頁面元素的移動,實現動畫或游戲的目的。例如通過調整屏幕的方向控制頁面上的小球走迷宮,控制小車的移動躲避障礙等。
② DeviceMotion
搖一搖功能是很多原生APP都可以實現的功能,如微信中的搖一搖找好友,QQ音樂中的搖一搖換歌等。它們都是利用了手機加速傳感器提供的API,當監聽到手機加速變化的事件時,根據獲取的加速值來執行不同的動作。
在Web APP中HTML5 也提供了類似的接口,就是DeviceMotionEvent。DeviceMotion封裝了運動傳感器數據的事件,可以獲取手機運動狀態下的運動加速度等數據。
DeviceMotionEvent對象屬性列表:
屬性 | 釋義 |
---|---|
event.accelaration | x(y,z):設備在x(y,z)方向上的移動加速度值 |
event.accelarationIncludingGravity | x(y,z):考慮了重力加速度后設備在x(y,z)方向上的移動加速度值 |
event.rotationRate | alpha,beta,gamma:設備繞x,y,z軸旋轉的角度 |
event.accelarationIncludingGravity與event.accelaration的區別在于前者加入了重力加速度,即在z軸方向加了9.8,在x,y方向上的值兩者相同。
旋轉速度rotationRate:alpha、beta、gamma的概念與DeviceOrientationEvent一致。
區別在于:
DeviceOrientationEvent的值是相對于初始狀態的差值,只要設備方向不變,怎么動都不會影響數值;
DeviceMotionEvent是相對于之前的某個瞬間值的差值時間比,即變化的速度,一旦設備靜止則會恢復為0。
實現搖一搖
function Shake(threshold, callback){//定義閾值 this.SHAKE_THRESHOLD = threshold ? threshold : 2000; this.last_update = 0; this.x = this.y = this.z = this.last_x = this.last_y = this.last_z = 0; this.init = function() { if (window.DeviceMotionEvent) { window.addEventListener('devicemotion', this.deviceMotionHandler, false); } else { alert('您的瀏覽器不支持DeviceMotion'); } }; var that = this; this.deviceMotionHandler = function(eventData) { var acceleration = eventData.accelerationIncludingGravity; var curTime = new Date().getTime(); if ((curTime - that.last_update) > 100) { var diffTime = curTime - that.last_update; that.last_update = curTime; that.x = acceleration.x; that.y = acceleration.y; that.z = acceleration.z; var speed = Math.abs(that.x + that.y + that.z - that.last_x - that.last_y - that.last_z) / diffTime * 10000; if (speed > that.SHAKE_THRESHOLD) { if (window.console && console.log) { console.log("shaked"); } if (callback != undefined) { callback(that); } } that.last_x = that.x; that.last_y = that.y; that.last_z = that.z; } } }; window.onload = function() { var shake1 = new Shake(2000, function(obj) { alert("shaked"); var r = document.getElementById("result"); r.innerHTML = "x:" + obj.x + ""; r.innerHTML += "y:" + obj.y + ""; r.innerHTML += "z:" + obj.z + ""; }); shake1.init(); };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
設備方向和運動API涉及到一些數學知識。想要對這一API了解更多,可以參考下面兩篇文章。
http://www.jianshu.com/p/5769075e9885
http://w3c.github.io/deviceorientation/spec-source-orientation.html
④ 連接特性(CONNECTIVITY)
先看W3C的定義。
More efficient connectivity means more real-time chats, faster games, and better communication. Web Sockets and Server-Sent Events are pushing (pun intended) data between client and server more efficiently than ever before.
HTTP是無連接的,一次請求,一次響應。想要實現微信網頁版掃一掃登錄,網頁版微信聊天的功能,需要使用輪詢的方式達到長連接的效果,輪詢的大部分時間是在做無用功,浪費網絡,浪費資源。現在HTML5為我們帶來了更高效的連接方案 Web Sockets 和 Server-Sent Events 。
下面對這兩種連接方式分別進行研究。
(1)網頁Sockets(Web Sockets)
運行原理
瀏覽器端示例
var wsServer = 'ws://localhost:8888/Demo'; //服務器地址
var websocket = new WebSocket(wsServer); //創建WebSocket對象 websocket.send("hello");//向服務器發送消息 alert(websocket.readyState);//查看websocket當前狀態 websocket.onopen = function (evt) { //已經建立連接 }; websocket.onclose = function (evt) { //已經關閉連接 }; websocket.onmessage = function (evt) { //收到服務器消息,使用evt.data提取 }; websocket.onerror = function (evt) { //產生異常 };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
服務器端
握手協議的客戶端數據已經由瀏覽器代勞了,服務器端需要我們自己來實現,目前市場上開源的實現也比較多如:
Kaazing WebSocket Gateway(一個 Java 實現的 WebSocket Server);
mod_pywebsocket(一個 Python 實現的 WebSocket Server);
Netty(一個 Java 實現的網絡框架其中包括了對 WebSocket 的支持);
node.js(一個 Server 端的 JavaScript 框架提供了對 WebSocket 的支持);
WebSocket4Net(一個.net的服務器端實現);
其實在目前的.net4.5框架中已經實現了WebSocket,不用官方實現,我們自己來寫個簡單的。服務器端需要根據協議來握手、接收和發送。
握手
/// <summary>
/// 生成Sec-WebSocket-Accept /// </summary> /// <param name="handShakeText">客戶端握手信息</param> /// <returns>Sec-WebSocket-Accept</returns> private static string GetSecKeyAccetp(byte[] handShakeBytes,int bytesLength) { string handShakeText = Encoding.UTF8.GetString(handShakeBytes, 0, bytesLength); string key = string.Empty; Regex r = new Regex(@"Sec-WebSocket-Key:(.*?)rn"); Match m = r.Match(handShakeText); if (m.Groups.Count != 0) { key = Regex.Replace(m.Value, @"Sec-WebSocket-Key:(.*?)rn", "$1").Trim(); } byte[] encryptionString = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); return Convert.ToBase64String(encryptionString); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
如果握手成功,將會觸發客戶端的onopen事件。
解析接收的客戶端信息
/// <summary>
/// 解析客戶端數據包 /// </summary> /// <param name="recBytes">服務器接收的數據包</param> /// <param name="recByteLength">有效數據長度</param> /// <returns></returns> private static string AnalyticData(byte[] recBytes, int recByteLength) { if (recByteLength < 2) { return string.Empty; } bool fin = (recBytes[0] & 0x80) == 0x80; // 1bit,1表示最后一幀 if (!fin){ return string.Empty;// 超過一幀暫不處理 } bool mask_flag = (recBytes[1] & 0x80) == 0x80; // 是否包含掩碼 if (!mask_flag){ return string.Empty;// 不包含掩碼的暫不處理 } int payload_len = recBytes[1] & 0x7F; // 數據長度 byte[] masks = new byte[4]; byte[] payload_data; if (payload_len == 126) { Array.Copy(recBytes, 4, masks, 0, 4); payload_len = (UInt16)(recBytes[2] << 8 | recBytes[3]); payload_data = new byte[payload_len]; Array.Copy(recBytes, 8, payload_data, 0, payload_len); } else if (payload_len == 127) { Array.Copy(recBytes, 10, masks, 0, 4); byte[] uInt64Bytes = new byte[8]; for (int i = 0; i < 8; i++) { uInt64Bytes[i] = recBytes[9 - i]; } UInt64 len = BitConverter.ToUInt64(uInt64Bytes, 0); payload_data = new byte[len]; for (UInt64 i = 0; i < len; i++) { payload_data[i] = recBytes[i + 14]; } }else { Array.Copy(recBytes, 2, masks, 0, 4); payload_data = new byte[payload_len]; Array.Copy(recBytes, 6, payload_data, 0, payload_len); } for (var i = 0; i < payload_len; i++) { payload_data[i] = (byte)(payload_data[i] ^ masks[i % 4]); } return Encoding.UTF8.GetString(payload_data); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
發送數據至客戶端
/// <summary>
/// 打包服務器數據 /// </summary> /// <param name="message">數據</param> /// <returns>數據包</returns> private static byte[] PackData(string message) { byte[] contentBytes = null; byte[] temp = Encoding.UTF8.GetBytes(message); if (temp.Length < 126) { contentBytes = new byte[temp.Length + 2]; contentBytes[0] = 0x81; contentBytes[1] = (byte)temp.Length; Array.Copy(temp, 0, contentBytes, 2, temp.Length); }else if (temp.Length < 0xFFFF) { contentBytes = new byte[temp.Length + 4]; contentBytes[0] = 0x81; contentBytes[1] = 126; contentBytes[2] = (byte)(temp.Length & 0xFF); contentBytes[3] = (byte)(temp.Length >> 8 & 0xFF); Array.Copy(temp, 0, contentBytes, 4, temp.Length); }else { // 暫不處理超長內容 } return contentBytes; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
Web Sockets 是一個非常棒的設計,想要了解更多,參考這里。
https://my.oschina.net/u/1266171/blog/357488
(2)服務器發送事件(Server-Sent Events)
運行原理
瀏覽器通過HTTP向服務器發送請求,服務器端拿出數據庫中的最新的信息,立即返回給客戶端,客戶端等待三秒后再次發出下一個請求。
客戶端示例
<script>
if(typeof(EventSource)!=="undefined") { var source=new EventSource("/example/html5/demo_sse.php"); source.onmessage=function(event) { document.getElementById("result").innerHTML+=event.data + "<br />"; }; } else { document.getElementById("result").innerHTML="Sorry, your browser does not support server-sent events..."; } </script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
服務器端示例
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');$time = date('r'); echo "data: The server time is: {$time}\n\n"; flush(); ?>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
想要對Server-Sent Events了解更多的,可以參考這兩篇文章。
http://www.w3school.com.cn/html5/html_5_serversentevents.asp
http://www.ibm.com/developerworks/cn/web/1307_chengfu_serversentevent/
服務器負載
-Long-polling占一小部分CPU資源,但是創建空的進程將浪費系統的內存
-Server-Sent Events工作的方式有很多,除非Server-Sent Events不必在每一次響應發出后都關閉連接。
-WebSockets,服務器只需要一個進程處理所有的請求,沒有循環,不必為每個客戶端都分配cpu和內存。
客戶端負載
-Long-polling取決于實現方式,但終究是一個異步的方式
-Server-Sent Events采用瀏覽器的內置的實現方式,只花費很少的一部分資源。
-WebSockets跟Server-Sent Events一樣,采用瀏覽器的內置的實現方式,只花費很少的一部分資源。
時間線
-Long-polling接近實時,但是發送新的請求和發送相應會有一定的時延。
-Server-Sent Events默認延時3秒,但是可以調整。
-WebSockets真正的實時
實現方式復雜度
-Long-polling實現起來非常簡單
-Server-Sent Events甚至比Long-polling更簡單
-需要一個WebSockets服務器處理事件,并開放一個端口
想要對輪詢,Web Sockets,Server-Sent Events三者的區別有所了解的,可以參考這篇文章。
https://www.oschina.net/question/82993_63312
⑤ 網頁多媒體特性(MULTIMEDIA)
看一下W3C的定義。
Audio and video are first class citizens in the HTML5 web, living in harmony with your apps and sites. Lights, camera, action!
看的出來HTML5原生支持音視頻讓W3C很興奮。也是廣大開發者多年的期待。終于可以將Flash踹入茅坑了。
Audio 和 Video 標簽
雖然支持音視頻很強大,但是確實沒有什么好說的,就是兩個標簽。
<audio controls="controls"><source src="song.ogg" type="audio/ogg"> <source src="song.mp3" type="audio/mpeg"> Your browser does not support the audio tag. </audio>
- 1
- 2
- 3
- 4
- 5
<video width="320" height="240" controls="controls"><source src="movie.ogg" type="video/ogg"> <source src="movie.mp4" type="video/mp4"> Your browser does not support the video tag. </video>
- 1
- 2
- 3
- 4
- 5
但是由于音視頻相對于其他新特性來說會被更多的使用,各個瀏覽器的兼容問題應該引起開發者的重視。統一各平臺的播放器ckplayer值得借鑒。可以參考此網頁http://www.ckplayer.com/。
Audio API
HTML5 音頻處理接口與Audio標簽是不一樣的。頁面上的Audio標簽只是HTML5更語義化的一個表現,而HTML5提供給JavaScript編程用的Audio API則讓我們有能力在代碼中直接操作原始的音頻流數據,對其進行任意加工再造。
先上demo,http://wayou.github.io/HTML5_Audio_Visualizer/,打開頁面之后,打開一首本地歌曲。然后可以顯示可視化效果。
原理
一段音頻到達揚聲器進行播放之前,半路對其進行攔截,于是我們就得到了音頻數據了,這個攔截工作是由window.AudioContext來做的,我們所有對音頻的操作都基于這個對象。通過AudioContext可以創建不同各類的AudioNode,即音頻節點,不同節點作用不同,有的對音頻加上濾鏡比如提高音色(比如BiquadFilterNode),改變單調,有的音頻進行分割,比如將音源中的聲道分割出來得到左右聲道的聲音(ChannelSplitterNode),有的對音頻數據進行頻譜分析。
所有的操作都是基于AudioContext這個對象進行的。
得到AudioContext對象。
window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext; try { var audioContext = new window.AudioContext(); } catch (e) { Console.log('!Your browser does not support AudioContext'); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
后續的操作,都是與音頻解碼相關的。想要了解更多HTML5 Audio API。可以參考下面的文章。
http://www.cnblogs.com/Wayou/p/3543577.html
https://developer.mozilla.org/zh-CN/docs/Web/API/AudioContext
搜索了一下,并沒有找到與Audio API這么強大的功能同一級別的Video API。
⑥ 三維、圖形及特效特性(3D, Graphics & Effects)
來看W3C的介紹。
Between SVG, Canvas, WebGL, and CSS3 3D features, you’re sure to amaze your users with stunning visuals natively rendered in the browser.
大致包含SVG, Canvas, WebGL, 和 CSS3 3D,下面分別進行研究。
(1)可縮放矢量圖形(SVG)
SVG是用于描述二維矢量圖形的一種圖形格式。
先上demo,http://www.ganecheng.tech/html5/svg.html
與其他圖像格式相比,使用 SVG 的優勢在于:
-SVG 可被非常多的工具讀取和修改(比如記事本)
-SVG 與 JPEG 和 GIF 圖像比起來,尺寸更小,且可壓縮性更強。
-SVG 是可伸縮的
-SVG 圖像可在任何的分辨率下被高質量地打印
-SVG 可在圖像質量不下降的情況下被放大
-SVG 圖像中的文本是可選的,同時也是可搜索的(很適合制作地圖)
-SVG 可以與 Java 技術一起運行
-SVG 是開放的標準
-SVG 文件是純粹的 XML
SVG有三種用法。
① 把SVG直接當成圖片放在網頁上
<img style="width: 250px;" alt="kiwi" src="./kiwi.svg">
- 1
② SVG動畫
畫一個五角星
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="190"> <polygon points="100,10 40,180 190,60 10,60 160,180" style="fill:red;stroke:blue;stroke-width:3;fill-rule:evenodd;" /> </svg>
- 1
- 2
- 3
- 4
③ SVG圖片的交互和濾鏡效果
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="612px" height="502.174px" viewBox="0 65.326 612 502.174" enable-background="new 0 65.326 612 502.174" xml:space="preserve" class="logo99"> <style> .kiwi { fill: #94d31b; // stroke: #909; // stroke-width: 10; // not sure which of these is correct // stroke-linejoin : butt | round | square; // stroke-linecap: butt| round| square; // stroke-dasharray: 5, 5, 10; // filter: url(#pictureFilter); // mask: url(#f1); // clip-path: url(); } .kiwi:hover { fill: #ace63c; } .ground { fill: #787f6a; } .ground:hover { filter: url(#pictureFilter); fill: #896d3d; } </style> <ellipse class="ground" cx="283.5" cy="487.5" rx="259" ry="80"></ellipse> <path class="kiwi" d="M210.333,65.331C104.367,66.105-12.349,150.637,1.056,276.449c4.303,40.393,18.533,63.704,52.171,79.03 c36.307,16.544,57.022,54.556,50.406,112.954c-9.935,4.88-17.405,11.031-19.132,20.015c7.531-0.17,14.943-0.312,22.59,4.341 c20.333,12.375,31.296,27.363,42.979,51.72c1.714,3.572,8.192,2.849,8.312-3.078c0.17-8.467-1.856-17.454-5.226-26.933 c-2.955-8.313,3.059-7.985,6.917-6.106c6.399,3.115,16.334,9.43,30.39,13.098c5.392,1.407,5.995-3.877,5.224-6.991 c-1.864-7.522-11.009-10.862-24.519-19.229c-4.82-2.984-0.927-9.736,5.168-8.351l20.234,2.415c3.359,0.763,4.555-6.114,0.882-7.875 c-14.198-6.804-28.897-10.098-53.864-7.799c-11.617-29.265-29.811-61.617-15.674-81.681c12.639-17.938,31.216-20.74,39.147,43.489 c-5.002,3.107-11.215,5.031-11.332,13.024c7.201-2.845,11.207-1.399,14.791,0c17.912,6.998,35.462,21.826,52.982,37.309 c3.739,3.303,8.413-1.718,6.991-6.034c-2.138-6.494-8.053-10.659-14.791-20.016c-3.239-4.495,5.03-7.045,10.886-6.876 c13.849,0.396,22.886,8.268,35.177,11.218c4.483,1.076,9.741-1.964,6.917-6.917c-3.472-6.085-13.015-9.124-19.18-13.413 c-4.357-3.029-3.025-7.132,2.697-6.602c3.905,0.361,8.478,2.271,13.908,1.767c9.946-0.925,7.717-7.169-0.883-9.566 c-19.036-5.304-39.891-6.311-61.665-5.225c-43.837-8.358-31.554-84.887,0-90.363c29.571-5.132,62.966-13.339,99.928-32.156 c32.668-5.429,64.835-12.446,92.939-33.85c48.106-14.469,111.903,16.113,204.241,149.695c3.926,5.681,15.819,9.94,9.524-6.351 c-15.893-41.125-68.176-93.328-92.13-132.085c-24.581-39.774-14.34-61.243-39.957-91.247 c-21.326-24.978-47.502-25.803-77.339-17.365c-23.461,6.634-39.234-7.117-52.98-31.273C318.42,87.525,265.838,64.927,210.333,65.331 z M445.731,203.01c6.12,0,11.112,4.919,11.112,11.038c0,6.119-4.994,11.111-11.112,11.111s-11.038-4.994-11.038-11.111 C434.693,207.929,439.613,203.01,445.731,203.01z"> </path> <filter id="pictureFilter"> <feGaussianBlur stdDeviation="15"></feGaussianBlur> </filter> </svg>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
如何用SVG畫出一條會動的線
先看demo,http://www.ganecheng.tech/html5/svg_animate_line.html
<path fill="none" stroke="deeppink" stroke-width="14" stroke-miterlimit="0" d="M11.6 269s-19.7-42.4 6.06-68.2 48.5-6.06 59.1 12.1l-3.03 28.8 209-227s45.5-21.2 60.6 1.52c15.2 22.7-3.03 47-3.03 47l-225 229s33.1-12 48.5 7.58c50 63.6-50 97-62.1 37.9" stroke-dasharray="988.0 988.0" stroke-dashoffset="988.0" />
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
var path = document.querySelector('.squiggle-animated path');
var length = path.getTotalLength();
// 清除之前的動作
path.style.transition = path.style.WebkitTransition ='none';
// 設置起始點 path.style.strokeDasharray = length + ' ' + length; path.style.strokeDashoffset = length; // 獲取一個區域,獲取相關的樣式,讓瀏覽器尋找一個起始點。 path.getBoundingClientRect(); // 定義動作 path.style.transition = path.style.WebkitTransition = 'stroke-dashoffset 2s ease-in-out'; // Go! path.style.strokeDashoffset = '0';
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
原理:使用d屬性得到一條貝塞爾曲線,使每段線段間距值為線段的1倍長度,慢慢降低偏移值,得到正向的移動效果。
屬性stroke-dasharray是讓你指定畫出的線段每段的長度,第二個值是各段之間空隙的長度。
屬性stroke-dashoffset是讓你指定每個小段的起始偏移量。
想對SVG了解更多的,可以參考下面的網址。
http://www.w3school.com.cn/svg/svg_examples.asp
http://www.w3school.com.cn/svg/svg_reference.asp
http://www.webhek.com/svg
http://www.webhek.com/animated-line-drawing-in-svg/
http://www.webhek.com/demo/svg/
(2)畫布(Canvas)
Canvas也是前端工程師炫技的最熱門的陣地。有許多demo。
http://www.17sucai.com/pins/demoshow/9386
http://www.17sucai.com/pins/demoshow/12676
http://www.17sucai.com/pins/demoshow/5937
http://www.html5tricks.com/demo/html5-canvas-waterful/index.html
http://www.html5tricks.com/demo/html5-canvas-loader/index.html
http://www.html5tricks.com/demo/html5-canvas-particle-effect/index.html
http://www.html5tricks.com/demo/html5-cool-fire-effect/index.html
HTML5 的 canvas 元素使用 JavaScript 在網頁上繪制圖像。
畫布是一個矩形區域,您可以控制其每一像素。
canvas 擁有多種繪制路徑、矩形、圓形、字符以及添加圖像的方法。
創建 Canvas 元素
<canvas id="myCanvas" width="200" height="100"></canvas>
- 1
通過 JavaScript 來繪制
canvas 元素本身是沒有繪圖能力的。所有的繪制工作必須在 JavaScript 內部完成:
<script type="text/javascript"> var c=document.getElementById("myCanvas"); var cxt=c.getContext("2d"); cxt.fillStyle="#FF0000"; cxt.fillRect(0,0,150,75); </script>
- 1
- 2
- 3
- 4
- 5
- 6
Canvas 與 SVG 對比
Canvas 和 SVG 都允許您在瀏覽器中創建圖形,但是它們在根本上是不同的。
SVG
SVG 是一種使用 XML 描述 2D 圖形的語言。
SVG 基于 XML,這意味著 SVG DOM 中的每個元素都是可用的。您可以為某個元素附加 JavaScript 事件處理器。
在 SVG 中,每個被繪制的圖形均被視為對象。如果 SVG 對象的屬性發生變化,那么瀏覽器能夠自動重現圖形。
Canvas
Canvas 通過 JavaScript 來繪制 2D 圖形。
Canvas 是逐像素進行渲染的。
在 canvas 中,一旦圖形被繪制完成,它就不會繼續得到瀏覽器的關注。如果其位置發生變化,那么整個場景也需要重新繪制,包括任何或許已被圖形覆蓋的對象。
Canvas 與 SVG 的比較
Canvas
-依賴分辨率
-不支持事件處理器
-弱的文本渲染能力
-能夠以 .png 或 .jpg 格式保存結果圖像
-最適合圖像密集型的游戲,其中的許多對象會被頻繁重繪
SVG
-不依賴分辨率
-支持事件處理器
-最適合帶有大型渲染區域的應用程序(比如谷歌地圖)
-復雜度高會減慢渲染速度(任何過度使用 DOM 的應用都不快)
-不適合游戲應用
想對Canvas了解更多的,可以參考下面鏈接。
http://www.w3school.com.cn/html/html5_canvas.asp
http://www.w3school.com.cn/tags/html_ref_canvas.asp
https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API
http://blog.csdn.net/clh604/article/details/8536059
(3)網頁圖形庫(WebGL)
先上demo。
https://www.cubeslam.com/ncokau
http://labs.gooengine.com/pearl-boy/indexBelow.html
http://www.cnblogs.com/lhb25/p/9-mind-blowing-webgl-demos.html
http://www.open-open.com/news/view/9d8136/
http://www.oschina.net/news/26547/webgl-chrome/
WebGL(全寫Web Graphics Library)是一種3D繪圖標準,這種繪圖技術標準允許把JavaScript和OpenGL ES 2.0結合在一起,通過增加OpenGL ES 2.0的一個JavaScript綁定,WebGL可以為HTML5 Canvas提供硬件3D加速渲染,這樣Web開發人員就可以借助系統顯卡來在瀏覽器里更流暢地展示3D場景和模型了,還能創建復雜的導航和數據視覺化。顯然,WebGL技術標準免去了開發網頁專用渲染插件的麻煩,可被用于創建具有復雜3D結構的網站頁面,甚至可以用來設計3D網頁游戲等等。
WebGL 基本原理
WebGL 的出現使得在瀏覽器上面實時顯示 3D 圖像成為現實,WebGL 本質上是基于光柵化的 API ,而不是基于 3D 的 API。
WebGL對象獲取
WebGL也是基于Canvas畫布做的,下面看一下WebGL獲取方法。
// Get A WebGL context
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("experimental-webgl");
- 1
- 2
- 3
WebGL 只關注兩個方面,即投影矩陣的坐標和投影矩陣的顏色。使用 WebGL 程序的任務就是實現具有投影矩陣坐標和顏色的 WebGL 對象即可。可以使用“著色器”來完成上述任務。頂點著色器可以提供投影矩陣的坐標,片段著色器可以提供投影矩陣的顏色。
無論要實現的圖形尺寸有多大,其投影矩陣的坐標的范圍始終是從 -1 到 1 。下面是一個關于實現 WebGL 對象的一個簡單例子。
// Get A WebGL context
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("experimental-webgl"); // setup a GLSL program var program = createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]); gl.useProgram(program); // look up where the vertex data needs to go. var positionLocation = gl.getAttribLocation(program, "a_position"); // Create a buffer and put a single clipspace rectangle in // it (2 triangles) var buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([ -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0]), gl.STATIC_DRAW); gl.enableVertexAttribArray(positionLocation); gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); // draw gl.drawArrays(gl.TRIANGLES, 0, 6);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
下面是兩個著色器。
<script id="2d-vertex-shader" type="x-shader/x-vertex"> attribute vec2 a_position; void main() { gl_Position = vec4(a_position, 0, 1); } </script> <script id="2d-fragment-shader" type="x-shader/x-fragment"> void main() { gl_FragColor = vec4(0, 1, 0, 1); // green } </script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
它將繪出一個綠色的長方形來填充整個畫板。
再往后面實在是太過專業了,需要掌握圖形圖像學的知識,通過demo確實能看出來非常強大,但是需要更專業的人才能搞了。
想對WebGL了解更多,參考這里。
https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL_API
http://blog.csdn.net/column/details/webgl.html
http://www.w3cschool.cn/webgl/i4gf1oh1.html
http://blog.163.com/hongshaoguoguo@126/blog/static/18046981201311310510380/
(4)CSS3 3D(CSS3 3D)
先上demo。
http://www.ganecheng.tech/html5/css33d.html
CSS3允許元素以3D的形式顯示。
下面講解一下這個demo是怎樣顯示出來的。
<!DOCTYPE html>
<html lang="zh-cn"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <style type="text/css"> *{margin: 0;padding: 0;} html,body{height: 80%;background: black;} .wrap { height: 100%;position: relative; -webkit-transform-style:preserve-3d; -webkit-perspective:0px; -moz-transform-style:preserve-3d; -moz-perspective:0px; -webkit-animation:mydhua 5s ease infinite; -moz-animation:mydhua 5s ease infinite; } .box { width: 200px; height: 200px; position: absolute; top: 50%; left: 50%; margin:-100px 0 0 -100px; line-height: 200px; text-align: center; font-size: 48px; color: white; } .box1{ -webkit-transform:rotatey(90deg) translatez(-100px); -moz-transform:rotatey(90deg) translatez(-100px); background: rgba(128,0,128,.5); } .box2{ -webkit-transform:rotatey(90deg) translatez(100px); -moz-transform:rotatey(90deg) translatez(100px); background: rgba(255,0,255,.5); } .box3{ -webkit-transform:rotatex(90deg) translatez(100px); -moz-transform:rotatex(90deg) translatez(100px); background: rgba(255,153,204,.5); } .box4{ -webkit-transform:rotatex(90deg) translatez(-100px); -moz-transform:rotatex(90deg) translatez(-100px); background: rgba(0,204,255,.5); } .box5{ -webkit-transform: translatez(-100px); -moz-transform:translatez(-100px); background: rgba(153,204,255,.5); } .box6{ -webkit-transform: translatez(100px); -moz-transform:translatez(100px); background: rgba(0,255,255,.5); } @-webkit-keyframes mydhua{ 0%{-webkit-transform:rotateX(0deg) rotateY(0deg) rotateZ(0deg);-webkit-transform-origin: center center;} 100%{-webkit-transform:rotateX(180deg) rotateY(180deg) rotateZ(180deg);-webkit-transform-origin: center center; } } @-moz-keyframes mydhua{ 0%{-moz-transform:rotateX(0deg) rotateY(0deg) rotateZ(0deg);-webkit-transform-origin: center center;} 100%{-moz-transform:rotateX(180deg) rotateY(180deg) rotateZ(180deg); -webkit-transform-origin: center center;} } </style> </head> <body style="width:960px;margin:0px auto;"> <div class="wrap"> <div class="box1 box">1</div> <div class="box2 box">2</div> <div class="box3 box">3</div> <div class="box4 box">4</div> <div class="box5 box">5</div> <div class="box6 box">6</div> </div> <script type="text/javascript"> </script> </body> </html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
首先是使子元素可以以3D的效果顯示出來。
.wrap
{height: 100%;position: relative; -webkit-transform-style:preserve-3d; -webkit-perspective:0px; -moz-transform-style:preserve-3d; -moz-perspective:0px; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
接下來規定6個div的初始位置。
rotateX()表示沿 X 軸的 3D 旋轉角度。
rotateY()表示沿 Y 軸的 3D 旋轉角度。
translateZ() 表示 3D 轉化,僅使用用于 Z 軸的值。表示在Z軸的移動位置,值越大,離屏幕越近,值越小,離屏幕越遠。在此網址感受一下translateZ()的效果。http://www.zhangxinxu.com/study/201209/transform-perspective-translateZ.html
<div class="box1 box">1</div> <div class="box2 box">2</div> <div class="box3 box">3</div> <div class="box4 box">4</div> <div class="box5 box">5</div> <div class="box6 box">6</div> .box { width: 200px; height: 200px; position: absolute; top: 50%; left: 50%; margin:-100px 0 0 -100px; line-height: 200px; text-align: center; font-size: 48px; color: white; } .box1{ -webkit-transform:rotatey(90deg) translatez(-100px); -moz-transform:rotatey(90deg) translatez(-100px); background: rgba(128,0,128,.5); } .box2{ -webkit-transform:rotatey(90deg) translatez(100px); -moz-transform:rotatey(90deg) translatez(100px); background: rgba(255,0,255,.5); } .box3{ -webkit-transform:rotatex(90deg) translatez(100px); -moz-transform:rotatex(90deg) translatez(100px); background: rgba(255,153,204,.5); } .box4{ -webkit-transform:rotatex(90deg) translatez(-100px); -moz-transform:rotatex(90deg) translatez(-100px); background: rgba(0,204,255,.5); } .box5{ -webkit-transform: translatez(-100px); -moz-transform:translatez(-100px); background: rgba(153,204,255,.5); } .box6{ -webkit-transform: translatez(100px); -moz-transform:translatez(100px); background: rgba(0,255,255,.5); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
現在定義關鍵幀動畫。
用@keyframes定義一個關鍵幀動畫,0%表示起始狀態,100%表示結束狀態,還可以根據需要定義50%等等。動畫的名稱叫mydhua。
@-webkit-keyframes mydhua{0%{-webkit-transform:rotateX(0deg) rotateY(0deg) rotateZ(0deg);-webkit-transform-origin: center center;} 100%{-webkit-transform:rotateX(180deg) rotateY(180deg) rotateZ(180deg);-webkit-transform-origin: center center; } } @-moz-keyframes mydhua{ 0%{-moz-transform:rotateX(0deg) rotateY(0deg) rotateZ(0deg);-webkit-transform-origin: center center;} 100%{-moz-transform:rotateX(180deg) rotateY(180deg) rotateZ(180deg); -webkit-transform-origin: center center;} }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
接下來就是使用動畫。
.wrap
{-webkit-animation:mydhua 5s ease infinite; -moz-animation:mydhua 5s ease infinite; }
- 1
- 2
- 3
- 4
- 5
這樣我們就得到了一個CSS3 3D旋轉的效果。
只要涉及到3D,都和空間立體幾何相關。如果想往這方面更進一步發展,桌前要常備這一類的書籍。
想要對CSS3 3D了解更多的,可以參考下面網址。
https://segmentfault.com/a/1190000003843764
http://www.w3school.com.cn/css3/css3_3dtransform.asp
http://www.cnblogs.com/duanhuajian/archive/2012/08/30/2664026.html
⑦ 性能與集成特性(Performance & Integration)
看看W3C的介紹。
Make your Web Apps and dynamic web content faster with a variety of techniques and technologies such as Web Workers and XMLHttpRequest 2. No user should ever wait on your watch.
性能與集成特性主要包括兩個東西,Web Workers 和 XMLHttpRequest 2。
下面依次介紹。
(1)網頁后臺任務(Web Workers)
先上demo,http://www.w3school.com.cn/tiy/t.asp?f=html5_webworker。
當在 HTML 頁面中執行腳本時,頁面的狀態是不可響應的,直到腳本已完成。
Ajax向服務器端發送請求,是異步接收響應的。不然頁面會卡住。
Web Workers 是運行在瀏覽器后臺的 JavaScript,獨立于其他腳本,不會影響頁面的性能。您可以繼續做任何愿意做的事情:點擊、選取內容等等,而此時 Web Workers 在后臺運行。
setInterval和setTimeout是單線程執行的。
雖然在JavaScript中有setInterval和setTimeout函數使javaScript看起來好像使多線程執行,單實際上JavaScript是單線程的,一次只能做一件事情。
看一個例子。
<!DOCTYPE html>
<html>
<head> <title>Web Workers</title> </head> <body> <h1>Web Workers</h1> <script type="text/javascript"> setTimeout(function(){ console.log('timeout function'); },1000); alert('do not close'); </script> </body> </html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
頁面一運行就會彈出一個對話框,如果setTimeout是在另外一個線程運行,那么過一秒鐘控制臺就會打印“timeout function”,事實是只要不關閉對話框,控制臺永遠不會輸出文字,這兩句話確實是在一個線程內運行的。
這樣的設計使JavaScript比較簡單,但有時候也很令人煩惱,因為單線程的設計意味著JavaScript代碼必須很快運行完,常見的問題就是一段復雜的JavaScript腳本會中斷頁面其它腳本執行,甚至會出現頁面失去響應,這也就是為什么Ajax的API要設計成異步的。
Web Workers 用法
在html5規范中引入了web workers概念,解決客戶端JavaScript無法多線程的問題,其定義的worker是指代碼的并行線程,不過web worker處于一個自包含的環境中,無法訪問主線程的window對象和document對象,和主線程通信只能通過異步消息傳遞機制。
我們需要把希望單獨執行的javascript代碼放到一個單獨的js文件中,然后在頁面中調用Worker構造函數來創建一個線程,參數是該文件路徑,參數存放如果是相對地址,那么要以包含調用Worker構造函數語句所在腳本為參照,如果是絕對路徑,需要保證同源(協議+主機+端口)。這個文件不需要我們在頁面使用script標簽顯示引用
w=new Worker("/example/html5/demo_workers.js");
- 1
worker對象只有兩個屬性,其實是兩個回調函數句柄。
onerror:當worker運行出現錯誤,并且沒有在worker中被捕獲,會在此捕獲
onmessage:當worker向主線程發送消息是調用
在其prototype內有兩個重要方法
postMessage:很熟悉的趕腳,之前我們介紹過window對象的postMessage()方法,woker的postMessage方法和window的比較類似,但參數略有不同,只需要傳遞消息內容就可以,而且支持所有JavaScript原生數據類型,當然不放心的話同樣也可以序列化為字符串傳遞
terminate:終止worker執行,有些worker執行比較慢,主線程可以主動終止其執行
demo_workers.js
var i=0;function timedCount() { i=i+1; postMessage(i); setTimeout("timedCount()",500); } timedCount();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
demo.html
<!DOCTYPE html>
<html>
<body> <p>計數: <output id="result"></output></p> <button onclick="startWorker()">開始 Worker</button> <button onclick="stopWorker()">停止 Worker</button> <br /><br /> <script> var w; function startWorker() { if(typeof(Worker)!=="undefined") { if(typeof(w)=="undefined") { w=new Worker("/example/html5/demo_workers.js"); } w.onmessage = function (event) { document.getElementById("result").innerHTML=event.data; }; } else { document.getElementById("result").innerHTML="Sorry, your browser does not support Web Workers..."; } } function stopWorker() { w.terminate(); } </script> </body> </html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
想對 Web Workers 了解更多的,參考下面鏈接。
http://www.w3school.com.cn/html5/html_5_webworkers.asp
http://www.cnblogs.com/dolphinX/p/3452684.html
https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers
(2)新的Ajax(XMLHttpRequest 2)
XMLHttpRequest是一個瀏覽器接口,使得Javascript可以進行HTTP(S)通信。也就是Ajax。
上一代Ajax有以下缺點。
- 只支持文本數據的傳送,無法用來讀取和上傳二進制文件。
- 傳送和接收數據時,沒有進度信息,只能提示有沒有完成。
- 受到”同域限制”(Same Origin Policy),只能向同一域名的服務器請求數據,不能跨域。
XMLHttpRequest 2也就是新的Ajax。針對老版本的缺點,做出了大幅改進,有下面的特點。
- 可以設置HTTP請求的時限。
- 可以使用FormData對象管理表單數據。
- 可以上傳文件。
- 可以請求不同域名下的數據(跨域請求)。
- 可以獲取服務器端的二進制數據。
- 可以獲得數據傳輸的進度信息。
XMLHttpRequest 2示例
前臺代碼.
<!DOCTYPE html>
<html><head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>第二代XmlHttpRequest異步上傳</title> <script type="text/javascript"> function upload(){ if (!window.FormData){ alert('您的瀏覽器不支持第二代XmlHttpRequest'); return; } // HTML5 新增對象 var formData = new FormData(document.getElementById('uploadForm')); //添加其他表單域 formData.append('user', 'haolin'); formData.append('pass', '111111'); var xhr = new XMLHttpRequest(); xhr.open('POST', 'upload'); //請求url //上傳完成回調函數 xhr.onload = function(event) { if (xhr.status === 200) { alert("上傳成功"); } else { alert('出錯了'); } }; xhr.send(formData); } </script> </head> <body> <h1>第二代XmlHttpRequest對象實現異步上傳</h1> <form id="uploadForm" action="" method="post" enctype="multipart/form-data"> <input id="upfile" type="file" name="upfile"/> <input type="button" value="上傳" onclick="upload()"/> </form> </body> </html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
后臺代碼
PrintWriter out = new PrintWriter(response.getOutputStream());
FileItemFactory factory = new DiskFileItemFactory();// 為該請求創建一個DiskFileItemFactory對象,通過它來解析請求。執行解析后,所有的表單項目都保存在一個List中。
ServletFileUpload upload = new ServletFileUpload(factory); List<FileItem> items; try { items = upload.parseRequest(request); Iterator<FileItem> itr = items.iterator(); while (itr.hasNext()) { FileItem item = (FileItem) itr.next(); System.out.println("是否是FormField: " + item.isFormField()); System.out.println("接收到域: " + item.getFieldName()); System.out.println("接收到值: " + item.getString("utf-8")); // 檢查當前項目是普通表單項目還是上傳文件。 if (item.isFormField()) {// 如果是普通表單項目,顯示表單內容。 String fieldName = item.getFieldName(); out.println("the field name is " + fieldName);// 顯示表單域名稱。 } else {// 如果是上傳文件,顯示文件名。 out.println("the upload file name is " + item.getName()); } } out.flush(); out.close(); } catch (FileUploadException e) { e.printStackTrace(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
對XMLHttpRequest 2進行封裝。
var AjaxForm = function(cfg){if (!window.FormData){alert("Sorry, your browser doesn't supoort FormData!");}/*** null or undefined 返回true, 否則false*/this.isNullOrUndefined = function(v, errMsg){ if (!v){ alert(errMsg); return true; } return false; }; var cfg = cfg || {}; if (this.isNullOrUndefined(cfg.id, "id can't be empty")) return; if (this.isNullOrUndefined(cfg.url, "url can't be empty")) return; this.id = cfg.id; // 表單id this.method = cfg.method || "POST"; //默認POST方法 this.url = cfg.url; this.async = !cfg.sync; //同步否 this.resultType = cfg.resultType || "text"; //返回結果類型 json對象或text this.formData = new FormData(document.getElementById(this.id)); //form數據 this.xhr = new XMLHttpRequest(); //當前請求對象 /** * 超時事件 * 配置格式: * timeout : xxx, * onTimeout: function(event){} */ if (cfg.timeout){ this.xhr.timeout = cfg.timeout; this.xhr.ontimeout = cfg.onTimeout; } /** * 發送過程事件 * 配置格式: * onProgress: function(loaded, total){} */ if (cfg.onProgress){ //發送數據過程 this.xhr.upload.onprogress = function(e){ if (e.lengthComputable) { cfg.onProgress(e.loaded, e.total); } }; } /** * 上傳完成事件 */ if (cfg.onComplete){ this.xhr.onload = function(event){ var res = event.target.responseText; if (this.resultType === 'json'){ if ((typeof JSON) === 'undefine'){ res = eval("("+res+")"); } else{ res = JSON.parse(res); } } cfg.onComplete(res); }; } /** * 發出請求 */ this.request = function(){ this.xhr.open(this.method, this.url, this.async); this.xhr.send(this.formData); }; };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
調用
var af = new AjaxForm({id: "uploadForm",url: 'upload',method: 'POST', timeout: 5000, onTimeout: function(event){ alert('It is timeout.'); }, onProgress: function(loaded, total){ var complete = (loaded / total * 100 | 0); var progress = document.getElementById('uploadProgress'); progress.value = complete; progress.innerHTML = complete; }, onComplete: function(result){ alert(result); } }); af.request();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
可以看出,雖然是XMLHttpRequest 2,但是還是XMLHttpRequest對象。使用新的特性,需要先判斷是否支持window.FormData。
如果想對XMLHttpRequest 2了解更多,可以參考下面文章。
https://www.w3.org/TR/XMLHttpRequest2
https://my.oschina.net/indestiny/blog/215472
http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html
http://blog.csdn.net/hills/article/details/41246939
后記
之前一直在做web開發。但是從來沒有對HTML5仔細研究過,寫這篇文章,前前后后花了4天時間,從驚嘆于HTML5的強大,到目睹有這么多的坑,瀏覽器的性能也跟不上。
但是我們不應該悲觀,解決方法總比困難多,Chrome和Firefox也一直在推動瀏覽器性能提升。前端再也不是切個圖,調調顏色,改改字體大小,放個Flash的時代了。HTML5會給我們一個更加美好的未來,前端開發者也有一個提升技術含量,迎接各種機遇的機會了。
祝愿前端越來越好。
參考文獻
https://www.w3.org/html/logo/
http://caniuse.com/
評論(1)
-
寫的太好了
2017-03-24 15:23回復

我的熱門文章
- Tomcat啟用HTTPS協議配置過程
- SSH(Struts,Spring,Hibernate)和SSM(SpringMVC,Spring,MyBatis)的區別
- HTML5新特性淺談
- 當 IDENTITY_INSERT 設置為 OFF 時,不能為表中的標識列插入顯式值
- 利用GitHub搭建個人網站
相關博文
百度智薦- elasticsearch java 增刪改查 版本2
2016-06-18201888次閱讀
- 探索通用可編程數據平面
2017-04-20148103次閱讀
- 微信發送amr文件導致web端無法顯示解決方案
2016-09-22146570次閱讀
- RxJava+Retrofit+OkHttp深入淺出-終極封裝二(網絡請求)
2016-07-18141923次閱讀
- linux下ffmpeg轉換amr為mp3大小為0的解決方案
2016-09-23134050次閱讀
- elasticsearch java 增刪改查 版本1
2016-06-18133047次閱讀
- webstorm 2017 激活破解
2016-09-06127252次閱讀
- 【公告】博客新皮膚上線啦
2016-06-14103367次閱讀
- sentinel搭建redis集群經驗總結
2016-09-13101770次閱讀



也想與你分享
