---
# System prepended metadata

title: HTML5
tags: [' Web', 學習筆記]

---

---
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);
}