---
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>
```
### 標籤異動
|新增|備註|移除|備註|
|:---:|:---:|:---:|:--:|
|<article>|外部文本。|<acronym>|首字母縮寫,可用<abbr>取代。|
|<aside>|頁面內容之外的內容。|<applet>|可用<object>取代。|
|<audio>|聲音內容。|<basefont>|基準字體。|
|<canvas>|圖形。|<big>|大號文本。|
|<command>|命令按鈕。|<center>|居中文本。|
|<datalist>|下拉列表。|<dir>|目錄列表。|
|<details>|元素的細節。|<font>|文本的字體外觀。|
|<embed>|外部交互內容或插件。|<frame>|子窗口(框架)。|
|<figcaption>|figure 元素的標題。|<frameset>|框架的集。|
|<figure>|媒介內容的分組,以及它們的標題。|<isindex>|單行輸入域。|
|新增|備註|移除|備註|
|<footer>(<header>)|section 或 page 的頁腳(頁眉)。|<noframes>|無法處理框架時的提示文本。|
|<hgroup>|有關文檔中的 section 的信息。|<s>|加刪除線的文本。|
|<keygen>|生成密鑰。|<strike>|加刪除線的文本。|
|<mark>|有記號的文本。|<tt>|打字機文本。|
|<meter>|預定義範圍內的度量|<u>|下劃線文本。|
|<nav>|導航鏈接。|
|<output>|輸出的一些類型。|
|<section>|文本章節。|
|<summary>|details 元素的標題。|
|<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
* <audio>
* <video>
* <h1>
* <p>
* <a>
* <img>
* <br>
* <table> <tr> <th> <td>
* <form> <input type="text"> <input type="submit">
* <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);
}