--- tags: 學習筆記, Web --- HTML5 ====== ## HTML ### 新舊差異 before ```html <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Head First Lounge</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <link type="text/css" rel="stylesheet" href="lounge.css"> <script type="text/javascript" src="lounge.js"></script> </head> <body> <h1>Welcome to Head First Lounge</h1> <p> <img src="drinks.gif" alt="Drinks"> </p> <p> Join us any evening for refreshing <a href="elixirs.html">elixirs</a>, conversation and maybe a game or two of Tap Tap Revolution. Wireless access is always provided; BYOWS (Bring Your Own Web Server). </p> </body> </html> ``` after ```html <!doctype html> <html> <head> <title>Head First Lounge</title> <meta charset=“utf-8"> <link rel=“stylesheet" href=“lounge.css"> <script src=“lounge.js"></script> </head> <body> <h1>Welcome to Head First Lounge</h1> <p> <img src="drinks.gif" alt="Drinks"> </p> <p> Join us any evening for refreshing <a href="elixirs.html">elixirs</a>, conversation and maybe a game or two of Tap Tap Revolution. Wireless access is always provided; BYOWS (Bring Your Own Web Server). </p> </body> </html> ``` ### 標籤異動 |新增|備註|移除|備註| |:---:|:---:|:---:|:--:| |&lt;article>|外部文本。|&lt;acronym>|首字母縮寫,可用&lt;abbr>取代。| |&lt;aside>|頁面內容之外的內容。|&lt;applet>|可用&lt;object>取代。| |&lt;audio>|聲音內容。|&lt;basefont>|基準字體。| |&lt;canvas>|圖形。|&lt;big>|大號文本。| |&lt;command>|命令按鈕。|&lt;center>|居中文本。| |&lt;datalist>|下拉列表。|&lt;dir>|目錄列表。| |&lt;details>|元素的細節。|&lt;font>|文本的字體外觀。| |&lt;embed>|外部交互內容或插件。|&lt;frame>|子窗口(框架)。| |&lt;figcaption>|figure 元素的標題。|&lt;frameset>|框架的集。| |&lt;figure>|媒介內容的分組,以及它們的標題。|&lt;isindex>|單行輸入域。| |新增|備註|移除|備註| |&lt;footer>(<header>)|section 或 page 的頁腳(頁眉)。|&lt;noframes>|無法處理框架時的提示文本。| |&lt;hgroup>|有關文檔中的 section 的信息。|&lt;s>|加刪除線的文本。| |&lt;keygen>|生成密鑰。|&lt;strike>|加刪除線的文本。| |&lt;mark>|有記號的文本。|&lt;tt>|打字機文本。| |&lt;meter>|預定義範圍內的度量|&lt;u>|下劃線文本。| |&lt;nav>|導航鏈接。| |&lt;output>|輸出的一些類型。| |&lt;section>|文本章節。| |&lt;summary>|details 元素的標題。| |&lt;video>|影片。| 原則上異動標籤的遵循著同一個原則,被移除的tag多數在HTML4已不建議使用且多數可用css去調整效果;被新增的tag則有許多屬於原本透過div以及span搭配css產生特殊功能,因此專為此類特殊功能而新增出語意化標籤,加強開發人員的可讀性,以及使瀏覽器、搜尋引擎可針對這些語意化標籤來作特殊處理。 ### 新的屬性 * contenteditable: 可編輯與否 * data-*: 開發人員自定屬性 * draggable: 是否可拖曳 * hidden: 隱藏資訊 * spellcheck: 是否進行拼寫檢查 ### 載入流程 瀏覽器載入文件(包含HTML&CSS)->HTML產生樹形結構(Document Object Model)->讀取完頁面後載入JavaScript執行 ### HTML5 API * CSS3 * Web Workers(類似多執行緒可以解決以往js會卡住的問題) * Forms * offline Web Apps * audio & video * New Markup * Local Storage * Canvas * Geolocation ### 版本降級 部分瀏覽器仍不支援HTML5,部分則未完全支援所有API因此可以透過一些語法順利降級以適應未支援之瀏覽器。而且幾乎所有瀏覽器都有計畫接納HTML5,因此這種事情會更加的稀少。 ### HTML有哪些tag * &lt;audio> * &lt;video> * &lt;h1> * &lt;p> * &lt;a> * &lt;img> * &lt;br> * &lt;table> &lt;tr> &lt;th> &lt;td> * &lt;form> &lt;input type="text"> &lt;input type="submit"> * &lt;button> ## JS ### JavaScript插入位置 ```html <head></head> ``` 之間可以插入,於head中插入之JS會在頁面載入時執行; ```html <body></body> ``` 之間也可以插入,於body中插入之JS會在Body被載入時才執行。 ### JS如何影響頁面 JS和HTML是不同的東西,JS是一種程式語言,HTML則是一種標記式語言。 當瀏覽器載入網頁時會透過解析HTML來產生DOM,這個DOM將包含html中所寫的所有元素 ```html <!doctype html> <html> <head> <title>Head First Lounge</title> <meta charset=“utf-8"> <link rel=“stylesheet" href=“lounge.css"> <script src=“lounge.js"></script> </head> <body> <h1>Welcome to Head First Lounge</h1> <p> <img src="drinks.gif" alt="Drinks"> </p> <p> Join us any evening for refreshing <a href="elixirs.html">elixirs</a>, conversation and maybe a game or two of Tap Tap Revolution. Wireless access is always provided; BYOWS (Bring Your Own Web Server). </p> </body> </html> ``` ```graphviz digraph hierarchy { nodesep=1.0 // increases the separation between nodes node [color=Red,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Blue, style=dashed] //All the lines look like this document -> {html} html -> {head, body} head -> {meta, title, css, script} body -> {h1, p1, p2} p1 -> {img} p1, p2 [label="p"] } ``` 而JS則可以通過DOM來存取、創造、移除元素,當JS對DOM進行變動瀏覽器將會自動更新網頁,所以我們可以看到頁面發生改變。 ### 可以用什麼來識別element * id * class * name * tag name * class name ```javascript getElementById() getElementsByClassName() getElementsByName() getElementsByTagName() ``` ### element的屬性 DOM的元素本身也是物件,因此他們會有許多的屬性(property)可以取得,這些屬性我們可以對他們進行存取,看看這個元素的內容,甚至是改變他的內容。 常見的屬性包括: * innerHTML * innerText * style * chidren * value ### element的事件 DOM有許多的事件可以被觸發(主動or被動),我們也可以透過一些方式要求事件觸發時一起執行我們自行撰寫的code。 常見的事件包括: * load * click * mouseup * mousedown * mousemove * focus * keydown * keyup * keypress 最常見的情況,我們會對一個元素的onclick屬性進行設定,要求這個元素的click事件被觸發時執行我們設定的function。 ```html <script> function elementClickEvent() { alert('元素被點擊了!'); } </script> <button onclick="elementClickEvent()">按鍵一</button> ``` ```html <script> window.onload = init; function init() { var button = document.getElementById("addButton"); button.onclick = handleButtonClick; } function handleButtonClick() { alert('元素被點擊了!'); } </script> <button id="addButton">按鍵一</button> ``` 所以當頁面載入完成後,我們會將我們寫好的**elementClickEvent**載入**按鍵一**的onclick,接著只要使用者點擊那個按鍵,click就會一併觸發**elementClickEvent**。 ### element的新增 ```html <ul id='playlist'><li>1</li></ul> ``` <ul id='playlist'><li>1</li></li></ul> 執行下列程式碼後 ```javascript var li = document.createElement("li"); li.innerHTML = 'testList'; var ul = document.getElementById("playlist"); ul.appendChild(li); ``` 會變成這樣 ```html <ul id='playlist'><li>1</li><li>testList</li></ul> ``` <ul id='playlist'><li>1</li></li><li>testList</li></ul> ```html <html> <head> <title>PlayList</title> <meta charset=“utf-8"> <script> window.onload = init; function init() { var button = document.getElementById("addButton"); button.onclick = handleButtonClick; loadPlaylist(); } function handleButtonClick() { var textInput = document.getElementById("songTextInput"); var songName = textInput.value; var li = document.createElement("li"); li.innerHTML = songName; var ul = document.getElementById("list"); ul.appendChild(li); save(songName); } function save(item) { var playlistArray = getStoreArray("playlist"); playlistArray.push(item); localStorage.setItem("playlist",JSON.stringify(playlistArray)); } function loadPlaylist() { var playlistArray = getSavedSongs(); var ul = document.getElementById("playlist"); if (playlistArray != null) { for (var i = 0; i < playlistArray.length; i++) { var li = document.createElement("li"); li.innerHTML = playlistArray[i]; ul.appendChild(li); } } } function getSavedSongs() { return getStoreArray("playlist"); } function getStoreArray(key) { var playlistArray = localStorage.getItem(key); if (playlistArray == null || playlistArray == "") { playlistArray = new Array(); } else { playlistArray = JSON.parse(playlistArray); } return playlistArray; } </script> </head> <body> <input type = "text" id = "songTextInput"> <button id = "addButton">加入歌曲</button> <ul id = "list"> </ul> </body> </html> ``` 定義一個函式和他的參數(formal parameters) 呼叫一個函式並給予參數(actual arguments) ### 變數的生存空間 變數可以區分為global以及local兩種,全域變數在任何地方都可以被看到並使用,而區域變數則只能在該區域內看到。 ```javascript var globalVariable; function myFunction1() { var localVariable; } function myFunction2() { } // mark ```` globalVariable不只在外層mark區塊的地方可以使用也可以在myFunction1, myFunction2上使用,而localVariable則只能被myFunction1使用,離開myFunction1以後就看不到了。 除此之外,由於生存空間的關係,會存在一種變數被遮蔽的現象,如果存在同名的變數如下: ```javascript var globalVariable, variable = 3; function myFunction1() { var localVariable, variable = 0, i; // globalVariable, localVariable, variable:4, i for(i = 0; i < 10; ++i) variable = variable + 1; // globalVariable, localVariable, variable:14, i:10 } function myFunction2() { // globalVariable, variable:0 } myFunction1(); // globalVariable, variable:0 ``` 全域變數中存在一個變數叫做variable而myFunction1中也有一個variable,這時候如果在myFunction1中使用variable這個變數的話,會使用生存空間較為內部的那個variable,也就是在myFunction1中宣告出來的variable。 與其他語言不同的是,js中全域變數可以不經宣告直接使用,若直接使用則視該變數為全域變數,且js的區域變數只區分到function層級,function內if for的scope中所宣告的變數在整個function都是可見的。 ## 物件 開頭介紹了DOM,也說了關於elements的一些故事。跟一般變數不太一樣,他們都有屬性(properties)可以描述他們自身的一些資訊。 其實他們都是JavaScript的物件,而除了他們之外我們自己也可以產生一些物件來使用。 ### 物件的產生 ```javascript var fido = { name: "Fido", weight: 40, breed: "Mixed", loves: ["walks", "fetching balls"] }; ``` fido就是一個物件,他有name, weight, breed, loves這些屬性。 ### 物件的操作 而我們可以透過opreatoer . 來存取物件的屬性以及方法,或是也可以透過[""]來存取。 ```javascript if(fido.weight === 40) fido["weight"] = 50; ``` ### 物件屬性的新增移除 我們除了可以隨意存取他們的屬性以外,也可以視自己需要新增或移除一些properties。 新增: ```javascript= var fido = { name: "fido" } fido.weight = 50; ``` 在第四行被執行前fido是沒有weight這個屬性的,所以當我試圖對fido.weight進行存取時,JavaScript判斷現在fido沒有這個屬性,他就會立即幫我產生一個新屬性進入fido。 ```javascript=+ delete fido.weight; ``` 移除時則只要告訴JavaScript我想要**刪除**哪個**屬性**即可。 ### 將物件作為參數傳入函式會發生什麼 當我們將物件傳入函式後,若函式內部對這個物件進行了一些變動,這些變動也會如實反映到原物件上面,跟原本傳送數值做為參數時的行為是不同的。 ### 替物件寫一個方法並呼叫 ```javascript= fido = { name: "Fido", weight: 40, breed: "Mixed", loves: ["walks", "fetching balls"], eatToFat: function(gainWeight) { this.weight += gainWeight; } }; fido.eatToFat(30); ``` 在第12行物件創建完畢尚未呼叫eatToFat時,fido.weight是40,而在11行呼叫過後fido會執行eatToFat所代表的那個function並且對自己的weight做一個+10的動作。所以11行執行完之後fido.weight變成了50。 ### 讓物件可以循環再利用 前面幾種使用物件的方法,顯得物件之間像互相獨立的個體,我們沒辦法有兩個看起來相似的物件,除非我們**複製**同樣的程式碼。 但事實上我們有更棒的做法,我們可以替物件寫一個建構元(constructor) ```javascript= var fido, tiny, clifford; fido = { name: "Fido"; breed: "Mixed"; weight: 40; bark: bark; }; tiny = { name: "Tiny"; breed: "Chawalla"; weight: 8; bark: bark; }; clifford = { name: "Clifford"; breed: "Bloodhound"; weight: 65; bark: bark; }; function bark() { if (this.weight > 25) { alert(this.name + " says Woof!"); } else { alert(this.name + " says Yip!"); } } ``` ```javascript= function Dog(name, breed, weight) { this.name = name; this.breed = breed; this.weight = weight; this.bark = function() { if (this.weight > 25) { alert(this.name + " says Woof!"); } else { alert(this.name + " says Yip!"); } }; } var fido = new Dog("Fido", "Mixed", 40); var tiny = new Dog("Tiny", "Chawalla", 8); var clifford = new Dog("Clifford", "Bloodhound", 65); ``` 透過這樣的方式,我們可以建立許多具有相同行為的物件。 ### this是什麼 我們一直在function中看到this,那麼this的功能究竟是什麼呢? this可以指向實際呼叫這個function的物件,因此this.weigth就代表呼叫了本function的物件其中的屬性weight。 ### 原生的物件 在這之前我們期時使用過許多的物件,例如: * window * document * element * button * ul * input ```graphviz digraph G { fontname = "Bitstream Vera Sans" fontsize = 8 node [ fontname = "Bitstream Vera Sans" fontsize = 8 shape = "record" ] edge [ fontname = "Bitstream Vera Sans" fontsize = 8 ] window [ label = "{window | - location\l - status\l - onload\l - document\l | + alert\l + prompt\l + open\l + close\l + setTimeout\l + setInterval\l }" ] document [ label = "{document | - domain\l - title\l - URL\l | + getElementById\l + getElementsByName\l + getElementsByTagName\l + getElementsByClassName\l \l \l + createElement\l }" ] p [ label = "{p | - innerHTML\l - childElementCount\l - firstChild\l | + appendChild\l + insertBefore\l + setAttribute\l + getAttribute\l }" ] } ``` ## Geolocation HTML5這個API可以透過你的GPS或IP位置提供你的地理位置(採用度分秒的單位回傳你所在的經緯度) ```javascript <html> <head> <meta charset="utf-8"> <title>Where am I?</title <script src="http://maps.google.com/maps/api/js?sensor=true"></script> <script> window.onload = getMyLocation; function getMyLocation() { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(displayLocationAndInitMapToShow); } else { alert("Oops, no geolocation support"); } } function displayLocationAndInitMapToShow(position) { var latitude = position.coords.latitude; var longitude = position.coords.longitude; var div = document.getElementById("location"); div.innerHTML = "You are at Latitude: " + latitude + ", Longitude: " + longitude; initToShowMap(position.coords); } var map; function initToShowMap(coords) { var googleLatAndLong = new google.maps.LatLng(coords.latitude, coords.longitude); var mapOptions = { zoom: 10, center: googleLatAndLong, mapTypeId: google.maps.MapTypeId.ROADMAP }; var mapDiv = document.getElementById("map"); map = new google.maps.Map(mapDiv, mapOptions); } </script> </head> <body> <div id = "location"> Your location will go here. </div> <div id = "map"></div> </body> </html> ``` ```javascript getCurrentPosition(successHandler, errorHandler, options) ``` 關鍵就在這個函式中,如果瀏覽器可以確認使用者的位置他就會呼叫你傳給他的successHandler,而後面兩個參數可以選填,如果你希望在無法正確定位時也去做些事情的話可以傳function給errorHandler,options則提供一些選項客製化你的定位function(PositionOptions) ```javascript function displayLocation(position) { var latitude = position.coords.latitude; var longitude = position.coords.longitude; ``` 若瀏覽器可以定位,他就會呼叫你給他的函式並將position傳至該函式中。 在取得了經緯度後,我們甚至可以透過介接Google Map的API來替網站增加地圖的顯示,替使用者標註出所在位置。 除了查看當前位置外Geolocation也提供watchPosition以及clearWatch函式來開關監視位置改變的功能。 ```javascript var watchId = navigator.geolocation.watchPosition(displayLocation); navigator.geolocation.clearWatch(watchId); ``` 呼叫成功後watchPosition會回傳一個物件,並在位置發生改變時自動呼叫displayLocation,之後若要關閉持續監視則需要將這個物件傳給clearWatch。 ## 與其他網頁產生關聯 ```javascript var url = "https://zh.wikipedia.org/wiki/Wikipedia:%E9%A6%96%E9%A1%B5"; var request = new XMLHttpRequest(); request.open("GET", url); request.onload = function() { if (request.status == 200) { alert("Data received!"); alert(request.responseText); } }; request.send(null); request.send(null); ``` ### HTTP http(超文本傳輸協定)是一個對用戶和伺服器請求(request)和回應(response的標準(TCP),伺服器在收到請求後會向用戶端返回一個狀態(status)以及可能的返回內容:請求的檔案(html)、錯誤訊息或者其他資訊。 最常提到的請求方式則為: * POST * GET ### XML、JSON 為了方便於傳輸有人設計出了一些特殊的格式,XML跟JSON就是其中的一員。 * XML 與HTML一樣是一種標記式語言,不同在於HTML是用來作為表示資料的標記語言,XML則被設計來傳送及攜帶資料資訊。XML被廣泛的被使用來作為於跨平台之間資料的互動形式,主要針對資料的內容,透過不同的格式化描述手段可以完成最終的形式表達。 XML定義結構、儲存資訊、傳送資訊。下例為小張傳送給大元的便條,儲存為XML。 ```XML <?xml version="1.0"?> <小纸条> <收件人>大元</收件人> <發件人>小張</發件人> <主題>問候</主題> <具體內容>早啊,飯吃了沒? </具體內容> </小纸条> ``` 這XML文件僅是純粹的資訊標籤,這些標籤意義的展開依賴於應用它的程式。 * JSON JSON是JavaScript的衍生產物,以易於讓人閱讀的文字為基礎,用來傳輸由屬性值或者序列性的值組成的資料物件。儘管屬於JS的衍生產物,但JSON是獨立於語言的資料格式,現在已有許多程式語言支援JSON格式的生成與解析。 JSON用於描述資料結構,有兩種結構存在: * 物件 一個物件包含一系列非排序的名稱/值對(pair),一個物件以`{`開始,並以}結束。每個名稱`/`值對之間使用`**:**`分割。 * 陣列 一個陣列是一個值(value)的集合,一個陣列以`[`開始,並以`]`結束。陣列成員之間使用`,`分割。 名稱和值之間使用`:`隔開,一般的形式是: ```JSON {name:value} ``` ```JSON { "firstName": "John", "lastName": "Smith", "sex": "male", "age": 25, "address": { "streetAddress": "21 2nd Street","city": "New York", "state": "NY", "postalCode": "10021" }, "phoneNumber": [{ "type": "home", "number": "212 555-1234" }, { "type": "fax", "number": "646 555-4567" } ] } ``` ### JSON的處理 在取得JSON資料後我們可以透過JavaScript將資料解析為JSON物件,並進行存取。 ```javascript var sales = JSON.parse(responseText); // sales.firstName就是John // sales.phoneNumber[0].type就是home ``` ## Canvas ```html <html lang="en"> <head> <title>Look What I Drew</title> <meta charset="utf-8"> <style> canvas { border: 1px solid black; } </style> <script> window.onload = function() { var canvas = document.getElementById("tshirtCanvas"); var context = canvas.getContext("2d"); context.fillRect(10, 10, 100, 100); }; </script> </head> <body> <canvas width="600" height="200" id="tshirtCanvas"></canvas> </body> </html> ``` 一樣透過getElementById取得canvas的元素,接著我們可以透過呼叫getContext("2d")取得CanvasRenderingContext2D 的物件即可透過操作這個物件來作畫。 除了fillRect之外還提供了: * strokeRect() * fillText() * strokeText() * lineTo() * arc() * drawImage() 等方法協助繪製圖形。 另外有: * fillStyle * strokeStyle * lineCap * lineJoin * lineWidth 等屬性協助調整繪畫效果。 ## video HTML5提供了canvas和video,讓我們可以靠著簡單的HTML以及JavaScript來達到我們想要即時觀看影片的效果。 ```html= <!doctype html> <html lang="en"> <head> <title>Webville TV</title> <meta charset="utf-8"> </head> <body> <div id="tv"> <div id="tvConsole"> <div id="highlight"> <img src="images/highlight.png" alt="highlight for tv"> </div> <div id="videoDiv"> <video controls autoplay src="video/preroll.mp4" width="480" height="360" id="video"> </video> </div> </div> </div> </body> </html> ``` 第14行video tag中的src就是你將播放的影片來源。 ### video的屬性 同樣地,video這個tag在HTML中也提供了一些屬性以供客製化你的播放器。 * controls: 允許控制播放器 * autoplay: 將會在頁面載入完成後自動播放 * src: 影片的來源 * width, height: 播放器的視窗大小 * poster: 影片未播放時將顯示的圖片 ### 瀏覽器與video #### 影片類型 值得注意的是不同瀏覽器接受的影片類型是不同的 不同的影片格式差異在於編碼上的不同,video內部分為影像部分及音訊部份。而這每一部份都有獨特的編碼方式(為了減少容量以及始播放更有效率) #### 不同之處 |影片類型|影像編碼|音訊編碼|支援的瀏覽器| |:---:|:---:|:---:|:---:| |mp4|H.264|AAC|Safari, IE9+, 某些版本的chrome| |WebM|VP8|Vorbis|Firefox, Chrome, Opera| |ogg, ogv|Theora|Vorbis|Firefox, Chrome, Opera| 由於各個瀏覽器可以接受的影片類型不同,因此當你試圖播放一個影片時最好要做好其他瀏覽器的支援。 #### 解決方案 ```html <video id="video" poster="video/prerollposter.jpg" controls width="480" height="360"> <source src="video/preroll.mp4"> <source src="video/preroll.webm"> <source src="video/preroll.ogv"> <p>Sorry, your browser doesn’t support the video element</p> </video> ``` 不使用video的src屬性,轉而在video內部掛上不同source的影片來源,並在最後採用文字。 瀏覽器將會由上至下一一解讀這些元素。 ```html <video id="video" poster="video/prerollposter.jpg" controls width="480" height="360"> <source src="video/preroll.mp4" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"'> <source src="video/preroll.webm" type='video/webm; codecs="vp8, vorbis"'> <source src="video/preroll.ogv" type='video/ogg; codecs="theora, vorbis"'> <p>Sorry, your browser doesn't support the video element</p> </video> ``` 你甚至可以詳細的告知瀏覽器,哪個source屬於哪種type應該使用何種影像音訊編碼。 #### 控制介面 在不同瀏覽器,影片視窗的控制元件看起來會有些許的不同。 ### 不支援HTML5的video 這種情況下我們可以選擇在video內部加上`<object>...</object>`來播放FLASH當瀏覽器無法解讀video時會轉而使用object。 ### video所提供的API 在大致介紹完HTML所提供的video後,我們將進入更深入的層面。 該如何使用JavaScript來控制我們的影片播放,video給出了強大的API支援。 video element提供了屬性以供存取 * videoWidth * videoHeight * currentTime * duration * ended * error * loop * muted * paused * readyState * seeking * volume 有內建的mehtod來讓我們操作 * play * pause * load * canPlayType 我們也可以透過`addEventListener(type, listener[, options])`監聽以下事件的發生,來自訂一些行為 * play * pause * progress * error * timeupdate * ended * abort * waiting * loadeddata * loadedmetadata * volumechange 搭配這些事件以及前面提過的canvas,我們甚至能夠替影片製作特效效果(透過canvas畫布取得影片畫面(frame)並對其中每一個像素(pixel)進行處理,處理完成後再讓canvas呈現) ## Web Storage 在以往瀏覽器儲存資料必須利用coockie來儲存資料,然而隨著時代的改變coockie漸漸的不再能滿足我們的需求,因此Web Storage就出現了。Web Storage讓瀏覽器可以替我們儲存資料,並且提供了簡單的API供我們使用。 ### coockie的運作 當客戶端向伺服器請求資料時,伺服器可以同時丟一份屬於他的coockie資料過來,這份資料由key/value組成,當下次客戶端再次準備請求資料時會將coockie送回去給伺服器,伺服器可以根據這組coockie決定要回送哪些資料回來。 `Cookie: pet=dog; age=5; color=black` ### Web Storage的運作 透過Web Storage的API我們可以儲存許多的key/value在瀏覽器的local storage,這些資料可以被適當的保存,即使關閉頁面或關閉瀏覽器也不會消失。而在當我們需要時可以透過key輕易的取出對應的value。 ### Web Storage如何使用 我們透過呼叫 ```javascript localStorage.setItem("sticky_0", "Pick up dry cleaning"); ``` 來儲存資料,第一個參數就是我們的key,第二個則是value。 需要使用時則呼叫 ```javascript var sticky = localStorage.getItem("sticky_0"); ``` 將key作為參數傳入其中,我們就可以得到對應的value。而且我們得到的這筆資料仍然儲存於其中。 要注意key/value都必須使用字串(string)來儲存,即使我們呼叫 ```javascript localStorage.setItem("sticky_0", 9); ``` 實際儲存時JavaScript會自動幫我們將`9`轉換為`"9"`,而在我們getItem時JavaScript無法判斷出我們希望他是個整數型態,因此我們需要手動轉換。 ```javascript parseInt(localStorage.getItem("sticky_0")); ``` localStorage除了提供setter以及getter供我們存取,其實他也支援`localStorage['key']`的存取方式。 ```javascript localStorage['sticky_0'] = 9; parseInt(localStorage['sticky_0']); ``` 另外也提供了length屬性以及key這個method,讓我們可以透過 ```javascript for (var i = 0; i < localStorage.length; i++) { var key = localStorage.key(i); var value = localStorage[key]; alert(value); } ``` 來取得所有儲存的資料。(取出的順序是無法預期的) 儘管我們無法儲存string以外的資料,但我們可以透過一些方法將我們想存的物件轉換成字串存入,例如前面曾經提過的JSON就可以很好的幫我們完成這件工作,我們可以利用JSON紀錄物件甚至陣列,再將JSON轉換成字串,需要使用時將該字串轉換回JSON。 ```javascript let dataArray = [1, 3, 5, 7, 9, 2, 4, 6, 8]; let dataArrayString = JSON.stringify(dataArray); let newArray = JSON.parse(dataArrayString); ``` dataArrayString將儲存`"[1,3,5,7,9,2,4,6,8]"`因此可以被localStorage儲存,而JSON.parse可以解析這樣的字串併將他轉換回來因此newArray會是個跟dataArray長得一樣的陣列。 除了存取之外,LocalStorage也提供移除儲存資料的方法: * removeItem(key) * clear() 我們可以單獨移除一筆資料也能直接清空整個空間。 瀏覽器允許儲存5MB的資料,如果超出這個範圍仍試圖儲存的話將會出現錯誤,因此請做好錯誤處理。 ```javascript try { localStorage.setItem(myKey, myValue); } catch(e) { alert("Your browser blew up at" + fuse.length + " with exception: " + e); } ``` 除了localStorage外還有提供sessionStorage滿足其他需求,兩者提供的功能是一致的,然而localStorage儲存的資料只要沒有移除就會一直留存,而sessionStorage則會在頁面被關閉時清空。 ### 如何利用Web Storage 我們可以利用儲存功能替使用者紀錄資料,透過sessionStorage我們可以做個購物車功能,當頁面關閉時購物車資料會消除。透過localStorage的特性,我們還可以讓不同標籤頁但是屬於相同的協議、主機、port的網頁共用資料。甚至利用這些儲存的資料,我們可以預先載入一些資料而不浪費流量。 ## Web Workers 有時候我們開啟網頁時會看到一個消息**此頁面可能內含需大量執行時間的程式碼 (Script) 或已停止回應。您可以馬上停止它,或是繼續等待它完成。** 這是因為JavaScript是單執行緒(一件事情做完才能做下一件事)的程式,如果有個敘述將會花費大量時間不斷運算那麼其他事情就會被卡住。然而這樣的消息實在很煩,而且為了避免出現這類情形,使得網頁撰寫必須迴避許多事情。 因此HTML5提出了Web Workers的概念。 我們可以透過Web Workers產生另外一個執行緒,把需要大量時間運算的動作交付給他來作,而我們繼續做其他事情。 ### Web Workers的工作方式 為了使用Web Workers我們需要先請瀏覽器幫我們產生出一個或更多的workers,替每個workers定義一段屬於他們自己的JavaScript,這些JavaScript就是他們所需要做的工作。 而這些workers會存在一個獨立的空間,他無法讀取網頁的DOM,也看不到其他人的JavaScript。(安全性以及效能考量) 為了使workers工作,瀏覽器會送一段訊息告知我們需要他做什麼。當workers完成這樣任務後就會送回去一個訊息,告知瀏覽器工作完成以及成果。接著瀏覽器接收這些訊息。 ### 如何使用Web Workers ```javascript if (window.Worker) ``` 記得確認過是否支援Web Worker 主程式裡的js ```javascript= var worker = new Worker("worker.js"); worker.onmessage = function (event) { var message = "Worker says " + event.data; document.getElementById("output").innerHTML = message; }; worker.postMessage("ping"); /* worker.postMessage([1, 2, 3, 5, 11]); 也可以傳送陣列 .data[0]->1 .data[1]->2 worker.postMessage({"message": "ping", "count": 5}); 甚至是物件 .data.message -> "ping" 但不能傳送function */ ``` 第一行宣告讓Worker在worker.js工作,接著送一條訊息給他通知他該開始運轉了,onmessage方法則是一個事件監控:給他一個function,當worker完成任務回傳訊息時,我們希望他執行這個function。 裡面的參數event是由worker傳給你的,event.data是worker送來的訊息,如果希望知道是哪一個worker回傳則可以透過event.target查找。 worker.js ```javascript= onmessage = function (event) { var messageFromMain = event.data; postMessage("I am ready!"); } ``` worker裡面同樣有個onmessage,當他收到訊息時就會執行這個程式,直到完成後可以將結果postMessage回去。 上述例子最後結果就是在#output的innerHTML寫上`Worker says I am ready!` 執行順序為瀏覽器先產生一個worker,遵照worker.js的邏輯行動,定義了當這個worker傳訊息回來時要執行的function,接著post一個訊息給worker(這時worker就開始工作了)。而當worker回傳訊息時就會觸發我們所設定的那個function。 ### worker的script引入 ```javascript importScripts('func.js'); importScripts('foo.js', 'bar.js'); ``` 透過importScripts即可引入 ```javascript= var canvas; var ctx; var rowData; var nextRow = 0; var generation = 0; var i_max = 1.5; var i_min = -1.5; var r_min = -2.5; var r_max = 1.5; var max_iter = 1024; var escape = 1025; var palette = []; window.onload = init; function init() { canvas = document.getElementById("fractal") createFractal(); resizeToWindow(); } function computeRegion(task) { var iter = 0; var c_i = task.i; var max_iter = task.max_iter; var escape = task.escape * task.escape; task.values = []; for (var i = 0; i < task.width; i++) { var c_r = task.r_min + (task.r_max - task.r_min) * i / task.width; var z_r = 0 , z_i = 0; for (iter = 0; z_r * z_r + z_i * z_i < escape && iter < max_iter; iter++) { // z -> z^2 + c var tmp = z_r * z_r - z_i * z_i + c_r; z_i = 2 * z_r * z_i + c_i; z_r = tmp; } if (iter == max_iter) { iter = -1; } task.values.push(iter); } return task; } function resizeToWindow() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; redrawFractal(); } function createFractal() { makePalette(); canvas.onclick = function(event) { handleClick(event.x, event.y); } ; ctx = canvas.getContext("2d"); rowData = ctx.createImageData(canvas.width, 1); } function drawRow(workerData) { var values = workerData.values; var pixelData = rowData.data; for (var i = 0; i < rowData.width; i++) { // for each pixel in the row var red = i * 4; var green = i * 4 + 1; var blue = i * 4 + 2; var alpha = i * 4 + 3; pixelData[alpha] = 255; // set alpha to opaque if (values[i] < 0) { pixelData[red] = pixelData[green] = pixelData[blue] = 0; } else { var color = this.palette[values[i]]; pixelData[red] = color[0]; pixelData[green] = color[1]; pixelData[blue] = color[2]; } } ctx.putImageData(this.rowData, 0, workerData.row); } function redraw() { canvas.onclick = null; setTimeout("handleRow(0);", 0); } function handleRow(n) { if (n < canvas.height) { var task = createTask(n); var row = computeRegion(task); drawRow(row); n++; setTimeout("handleRow(" + n + ");", 0); } else { canvas.onclick = function(event) { handleClick(event.x, event.y); } ; } } function createTask(row) { var task = { row: row, // row number we're working on width: rowData.width, // width of the image generation: generation, // how far in we are r_min: r_min, r_max: r_max, i: i_max + (i_min - i_max) * row / canvas.height, max_iter: max_iter, escape: escape }; return task; } function makePalette() { function wrap(x) { x = ((x + 256) & 0x1ff) - 256; if (x < 0) x = -x; return x; } for (i = 0; i <= this.max_iter; i++) { this.palette.push([wrap(7 * i), wrap(5 * i), wrap(11 * i)]); } } function handleClick(x, y) { var width = r_max - r_min; var height = i_min - i_max; var click_r = r_min + width * x / canvas.width; var click_i = i_max + height * y / canvas.height; // zoom factor var zoom = 8; r_min = click_r - width / zoom; r_max = click_r + width / zoom; i_max = click_i - height / zoom; i_min = click_i + height / zoom; redraw(); } function redrawFractal() { var width = ((i_max - i_min) * canvas.width / canvas.height); var r_mid = (r_max + r_min) / 2; r_min = r_mid - width / 2; r_max = r_mid + width / 2; rowData = ctx.createImageData(canvas.width, 1); redraw(); } ``` ```javascript= function computeRegion(task) { var iter = 0; var c_i = task.i; var max_iter = task.max_iter; var escape = task.escape * task.escape; task.values = []; for (var i = 0; i < task.width; i++) { var c_r = task.r_min + (task.r_max - task.r_min) * i / task.width; var z_r = 0 , z_i = 0; for (iter = 0; z_r * z_r + z_i * z_i < escape && iter < max_iter; iter++) { // z -> z^2 + c var tmp = z_r * z_r - z_i * z_i + c_r; z_i = 2 * z_r * z_i + c_i; z_r = tmp; } if (iter == max_iter) { iter = -1; } task.values.push(iter); } postmessage(task); } onmessage = function (event) { computeRegion(event.data); } ``` ```javascript=+ var canvas; var ctx; var rowData; var nextRow = 0; var generation = 0; var i_max = 1.5; var i_min = -1.5; var r_min = -2.5; var r_max = 1.5; var max_iter = 1024; var escape = 1025; var palette = []; var workers = []; const workersAmount = 4; window.onload = init; function init() { canvas = document.getElementById("fractal") createFractal(); resizeToWindow(); createWorkers(); } function createWorkers() { var i; for(i = 0; i < workersAmount; ++i) { workers.push(new Worker("compute.js")); workers[i].onmessage = handleRow; } } function handleRow(event) { drawRow(event.data); } function resizeToWindow() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; redrawFractal(); } function createFractal() { makePalette(); canvas.onclick = function(event) { handleClick(event.x, event.y); } ; ctx = canvas.getContext("2d"); rowData = ctx.createImageData(canvas.width, 1); } function drawRow(workerData) { var values = workerData.values; var pixelData = rowData.data; for (var i = 0; i < rowData.width; i++) { // for each pixel in the row var red = i * 4; var green = i * 4 + 1; var blue = i * 4 + 2; var alpha = i * 4 + 3; pixelData[alpha] = 255; // set alpha to opaque if (values[i] < 0) { pixelData[red] = pixelData[green] = pixelData[blue] = 0; } else { var color = this.palette[values[i]]; pixelData[red] = color[0]; pixelData[green] = color[1]; pixelData[blue] = color[2]; } } ctx.putImageData(this.rowData, 0, workerData.row); } function redraw() { canvas.onclick = null; setTimeout("computeAllRow();", 0); } function computeAllRow() { var i; for(i = 0; i < canvas.height; ++i) { var task = createTask(i); var row = workers[i % workersAmount].postMessage(task); } canvas.onclick = function(event) { handleClick(event.x, event.y); } ; } function createTask(row) { var task = { row: row, // row number we're working on width: rowData.width, // width of the image generation: generation, // how far in we are r_min: r_min, r_max: r_max, i: i_max + (i_min - i_max) * row / canvas.height, max_iter: max_iter, escape: escape }; return task; } function makePalette() { function wrap(x) { x = ((x + 256) & 0x1ff) - 256; if (x < 0) x = -x; return x; } for (i = 0; i <= this.max_iter; i++) { this.palette.push([wrap(7 * i), wrap(5 * i), wrap(11 * i)]); } } function handleClick(x, y) { var width = r_max - r_min; var height = i_min - i_max; var click_r = r_min + width * x / canvas.width; var click_i = i_max + height * y / canvas.height; // zoom factor var zoom = 8; r_min = click_r - width / zoom; r_max = click_r + width / zoom; i_max = click_i - height / zoom; i_min = click_i + height / zoom; redraw(); } function redrawFractal() { var width = ((i_max - i_min) * canvas.width / canvas.height); var r_mid = (r_max + r_min) / 2; r_min = r_mid - width / 2; r_max = r_mid + width / 2; rowData = ctx.createImageData(canvas.width, 1); redraw(); } ``` ```html <!doctype html> <html> <head> <title>demo</title> <meta charset=utf-8"> <link rel="stylesheet" href="style.css"> <script src="demo.js"></script> </head> <body> <div class="floatLeft"> <canvas id="canvas" width="700px" height="394px"></canvas> <br /> <div> <label>圖片存檔</label> <select id="paintRecords"> </select> <br /> <button id="loadPaintButton">讀取</botton> <button id="savePaintButton">存檔</botton> <button id="clearPaintButton">清除畫布</botton> </div> </div> <div class="floatLeft"> <textarea id="textArea"></textarea> <br /> <div> <label>文字存檔</label> <select id="textRecords"> </select> <br /> <button id="loadTextButton">讀取</botton> <button id="saveTextButton">存檔</botton> </div> </div> </body> </html> ``` ```javascript let loadPaintButton, savePaintButton, clearPaintButton, paintRecords, canvas; let loadTextButton, saveTextButton, textRecords, textArea; let paintObject, textObject; let paintRecordsData, textRecordsData, totalData; window.onload = init; function init() { getElements(); clickEvent(); loadLocalStorageDataElseInitLocalStorage(); initRecordsOptions(); objectInit(); } function getElements() { loadPaintButton = document.getElementById('loadPaintButton'); savePaintButton = document.getElementById('savePaintButton'); clearPaintButton = document.getElementById('clearPaintButton'); paintRecords = document.getElementById('paintRecords'); canvas = document.getElementById('canvas'); loadTextButton = document.getElementById('loadTextButton'); saveTextButton = document.getElementById('saveTextButton'); textRecords = document.getElementById('textRecords'); textArea = document.getElementById('textArea'); initPaint(); } function clickEvent() { loadPaintButton.addEventListener('click', () => { load(paintObject); }); savePaintButton.addEventListener('click', () => { getSaveNameThenSave(paintObject); }); clearPaintButton.addEventListener('click', () => { context.clearRect(0, 0, context.canvas.width, context.canvas.height); }); loadTextButton.addEventListener('click', () => { load(textObject); }); saveTextButton.addEventListener('click', () => { getSaveNameThenSave(textObject); }); } function objectInit() { paintObject = { selectElement: paintRecords, data: canvas, localStorageData: paintRecordsData, getData: function() { return this.data.toDataURL(); }, setDataFromStorageForKey: function(key) { let newData = localStorage[key]; let img = new Image; img.src = newData; img.onload = function () { paintObject.data.getContext("2d").drawImage(img, 0, 0); }; }, pushAndSave: function(object) { this.localStorageData.push(object); localStorage['paintRecordsData'] = JSON.stringify(this.localStorageData); } }; textObject = { selectElement: textRecords, data: textArea, localStorageData: textRecordsData, getData: function() { return this.data.value; }, setDataFromStorageForKey: function(key) { this.data.value = localStorage[key]; }, pushAndSave: function(object) { this.localStorageData.push(object); localStorage['textRecordsData'] = JSON.stringify(this.localStorageData); } }; } function load(object) { let value = selectedValue(object.selectElement); object.setDataFromStorageForKey(value); } function selectedValue(selectElement) { let selectedIndex = selectElement.selectedIndex; let value = 0; if (selectedIndex !== -1) { value = parseInt(selectElement.options[selectedIndex].value); } return value; } function getSaveNameThenSave(object) { let recordName = prompt("請輸入你的新存檔名稱,若存檔名稱相同將會覆蓋。", "save1"); if(recordName === null) return; overWriteSave(object, recordName); } function overWriteSave(object, newOptionName) { let value = getValueAndDeleteSameNameOption(object.selectElement, newOptionName); object.selectElement.add(new Option(newOptionName, value)); if(value === totalData) { ++totalData; localStorage.totalData = totalData; object.pushAndSave({ value: value, text: newOptionName }); } localStorage[value.toString()] = object.getData(); } function getValueAndDeleteSameNameOption(selectElement, newOptionName) { let i, value = totalData; for(i = 0; i < selectElement.options.length; ++i) { if(selectElement.options[i].innerText === newOptionName) { value = selectElement.options[i].value; selectElement.options.remove(i); return value; } } return value; } function loadLocalStorageDataElseInitLocalStorage() { if(localStorage.getItem('totalData') !== null) { loadLocalStorageData(); } else { initLocalStorageDataToLoad(); } } function loadLocalStorageData() { paintRecordsData = JSON.parse(localStorage['paintRecordsData']); textRecordsData = JSON.parse(localStorage['textRecordsData']); totalData = parseInt(localStorage['totalData']); } function initLocalStorageDataToLoad() { localStorage['totalData'] = '3'; localStorage['textRecordsData'] = JSON.stringify([{value: 1, text: "save1"}]); localStorage['paintRecordsData'] = JSON.stringify([{value: 2, text: "save1"}]); localStorage['0'] = ''; localStorage['1'] = ''; localStorage['2'] = ''; loadLocalStorageData(); } function initRecordsOptions() { textRecordsData.forEach((recordData) => { textRecords.add(new Option(recordData.text, recordData.value)); }); paintRecordsData.forEach((recordData) => { paintRecords.add(new Option(recordData.text, recordData.value)); }); } let clickX = new Array(); let clickY = new Array(); let clickDrag = new Array(); let paint; let context; function initPaint() { context = canvas.getContext('2d'); canvas.onmousedown = function(e) { paint = true; context.strokeStyle = "#df4b26"; context.lineJoin = "round"; context.lineWidth = 5; context.beginPath(); context.moveTo(e.pageX - this.offsetLeft, e.pageY - this.offsetTop) }; canvas.onmousemove = function(e) { if(paint){ context.lineTo(e.pageX - this.offsetLeft, e.pageY - this.offsetTop); context.stroke(); } }; canvas.onmouseup = function(e) { paint = false; context.closePath(); }; canvas.onmouseleave = function(e) { paint = false; }; } function addClick(x, y, dragging) { clickX.push(x); clickY.push(y); clickDrag.push(dragging); }