在網頁應用程式中使用本地檔案 - Web APIs

在網頁應用程式中使用本地檔案
==============

現在可以透過新增至 HTML5 DOM 的 File API 讓 web 內容要求使用者選取本地端的檔案後讀取被選取檔案中的內容。檔案的選取動作可以使用 HTML 的 `input` 元素，或是用拖曳檔案（drag and drop）的方式來完成。

如果你想要使用 DOM 檔案 API 的文件擴展或是其他 Chrome 程式碼，你可以參考使用 DOM 檔案 API 在 FireFox 外觀代碼中。

使用 HTML 選擇本地檔案
--------------

HTML 語法：

```html
<input type="file" id="input" />
```

File API 可以從 `File` 物件中讀取 `FileList` ，`FileList` 內包含使用者所選取的檔案。

如果使用者只選擇一個檔案，那麼我們只需要考慮第一個檔案物件。

使用 DOM 獲取選擇的檔案：

```js
var selectedFile = document.getElementById("input").files[0];
```

使用 jQuery 獲取選擇的檔案：

```js
var selectedFile = $("#input").get(0).files[0];

var selectedFile = $("#input")[0].files[0];
```

**備註：** 如果獲取 "files is undefined" 錯誤: 代表未選擇正確的 HTML 元素, 這時忘記 jQuery 回傳符合 DOM 元素的清單. 改使用 DOM 元素呼叫 "files" 方法.

使用 change event 獲取選擇的檔案
-----------------------

使用 File API 選擇單一檔案是非常簡單的，如下

```html
<input type="file" id="input" onchange="handleFiles(this.files)" />
```

當使用者選取一個檔案，呼叫 `handleFiles()` 會得到一個 `FileList` 的物件。`FileList` 裡面還會有一個 `File` 的物件，裡面的東西就是使用者選取的檔案。

如果你想要讓使用者一次選擇多個檔案，可以在 `input` 元素中使用 `multiple` 的屬性：

```html
<input
 type="file"
 id="input"
 multiple="true"
 onchange="handleFiles(this.files)" />
```

在上述這個例子中，檔案名單會傳遞到 `handleFiles()` 函數，其中包含了使用者選的每個檔案 `File` 物件。

### 使用 EventListener 動態地監聽

如果使用了其他的函數庫（jQuery），你會需要使用 `EventTarget.addEventListener()` 去監聽事件，例如：

```js
var inputElement = document.getElementById("inputField");
inputElement.addEventListener("change", handleFiles, false);

function handleFiles() {
  var fileList = this.files;

  /\* now you can work with the file list \*/
}
```

在這個例子中，`handleFiles()` 函數找尋檔案清單而非接收參數, 因為這樣增加的 event listeners 不能接受參數.

獲得選取檔案的資訊
---------

由 DOM 提供的 `FileList` 物件代表使用者選取的所有檔案，每個又是 `File` 物件。可以藉由 `FileList` 的 length 屬性得知使用者選取的檔案數量：

```js
var numFiles = files.length;
```

使用陣列獲取 `File` 物件：

```js
for (var i = 0; i < files.length; i++) {
  var file = files[i];
  ..
}
```

上述的例子顯示獲取在檔案清單裡所有檔案物件的方法。

`File` 提供三個包含檔案重要訊息的屬性。

`name`

唯讀的檔案名稱，並未包含檔案路徑。

`size`

為 64 位元的整數，用以表示檔案的 byte 的長度。

`type`

為唯讀字串。表示檔案的 MIME-type 。若是無法取得檔案的 Mime-type ，則其值會是一個空字串 `""`。

使用 click() 方法隱藏檔案輸入元素
---------------------

從 Gecko 2.0 開始，為了顯示個人化開啟檔案的 UI 和使用者選擇的檔案可以隱藏 `<input>` (en-US) 元素和顯示個人化的設計。可以藉由設置 CSS 「display:none」 和對 `<input>` (en-US) 元素呼叫 `click()` 方法。

HTML 如下：

```html
<input
 type="file"
 id="fileElem"
 multiple="true"
 accept="image/\*"
 style="display:none"
 onchange="handleFiles(this.files)" />
<a href="#" id="fileSelect">Select some files</a>
```

`doClick()` 方法:

```js
var fileSelect = document.getElementById("fileSelect"),
  fileElem = document.getElementById("fileElem");

fileSelect.addEventListener(
  "click",
  function (e) {
    if (fileElem) {
      fileElem.click();
    }
    e.preventDefault(); // prevent navigation to "#"
  },
  false,
);
```

很明顯的，可以使用 CSS 來設計新的上傳檔案的按鈕。

使用拖放選取檔案
--------

使用者可以使用拖放來選取檔案，首先要設置放的區域，確定文件可以接收放的檔案，方法如下：

```js
var dropbox;

dropbox = document.getElementById("dropbox");
dropbox.addEventListener("dragenter", dragenter, false);
dropbox.addEventListener("dragover", dragover, false);
dropbox.addEventListener("drop", drop, false);
```

在這個範例中，我們將 ID "dropbox" 設為放的區域，這是由於我們監聽了 `dragenter`、`dragover` 和 `drop`事件。

我們甚至不需要處理 `dragenter` 和 `dragover` 事件，所以這些函數很簡單。他們阻止了事件的傳播和預設事件的發生：

```js
function dragenter(e) {
  e.stopPropagation();
  e.preventDefault();
}

function dragover(e) {
  e.stopPropagation();
  e.preventDefault();
}
```

神奇的 `drop()` 函數:

```js
function drop(e) {
  e.stopPropagation();
  e.preventDefault();

  var dt = e.dataTransfer;
  var files = dt.files;

  handleFiles(files);
}
```

`這邊我們用 dataTransfer` 來獲取檔案清單, 並傳遞給 `handleFiles()`。 我們可以發現，不論使用拖放或是其他取得檔案，處理檔案的方式都是相同。

範例：顯示選取的圖片
----------

假設要開發一個分享照片的網站，想使用 HTML5 來讓使用者在上傳圖片前預覽縮圖。簡單來說就是像先前討論地一樣建立 input 元素或是 drop 區域，接著再呼叫類似 `handleFiles()` 的函數。

```js
function handleFiles(files) {
  for (var i = 0; i < files.length; i++) {
    var file = files[i];
    var imageType = /image.\*/;

    if (!file.type.match(imageType)) {
      continue;
    }

    var img = document.createElement("img");
    img.classList.add("obj");
    img.file = file;
    preview.appendChild(img);

    var reader = new FileReader();
    reader.onload = (function (aImg) {
      return function (e) {
        aImg.src = e.target.result;
      };
    })(img);
    reader.readAsDataURL(file);
  }
}
```

這邊迴圈處理了使用者選取的每個檔案並檢查每個檔案的類型是不是圖檔(藉由使用正規表達式檢查是否符合字串 "image.\*")。每一個是圖片的檔案，我們創建一個 `img` 元素。CSS 被使用來美化外框、陰影、還有設定圖片的尺寸，所以那些並不需要在這邊寫入。

為了使圖片可以在 DOM 裡面更容易被找到，所以每個圖片都有設定 CSS class 「obj」。 我們也在每個圖檔標記 `file` 屬性以辨認 `File`；這使我們更容易取得真正要上傳的圖檔。最後我們使用`Node.appendChild()` (en-US) 在文件中增加縮圖的元素。

`FileReader` 處理要非同步讀取的圖檔並跟 `img` 元素連接。在創建 `FileReader` 物件後，我們設置了 `onload`並 呼叫 `readAsDataURL()` 在背景呼叫讀取的程序。當所有圖檔都被讀取時，他們被轉換為傳到 `onload callback` 的 `data` URL。 這個範例簡易的設置`img` 元素的 `src` 屬性來讀取圖檔並在螢幕上顯示。

使用 object URLs
--------------

Gecko 2.0 支援 DOM 的`window.URL.createObjectURL()` (en-US) 和 `window.URL.revokeObjectURL()` (en-US) 方法。可以藉由這些方法創建表示任何為 DOM `File` 物件的 data URL 字串，包含了使用者電腦上的檔案。

可以使 `File` 物件作為 HTML 元素 URL 的參考，創建 object URL 的方法：

```js
var objectURL = window.URL.createObjectURL(fileObj);
```

object URL 為表示 `File` 物件的字串。即使已經對相同檔案創建了 object URL，每次呼叫 `window.URL.createObjectURL()` (en-US)，就會創建一個 object URL。當文檔卸載時他們將會被自動釋放，如果要動態地使用，需要呼叫 `window.URL.revokeObjectURL()` (en-US) 釋放：

```js
window.URL.revokeObjectURL(objectURL);
```

範例：使用 object URLs 顯示圖片
----------------------

這個範例使用 object URLs 顯示圖像縮圖。此外也顯示了其他包含檔案名稱和檔案大小的訊息。線上範例 (註:瀏覽器版本要求 11/22 之後的火狐版本)。

**備註：** 這個 API 在較早的 Firefox 4 betas 存在但是 11/22 號後的版本有改變, 所以確定瀏覽器在最新的版本!

HTML：

```html
<input
 type="file"
 id="fileElem"
 multiple
 accept="image/\*"
 style="display:none"
 onchange="handleFiles(this.files)" />
<a href="#" id="fileSelect">Select some files</a>
<div id="fileList">
  <p>No files selected!</p>
</div>
```

This establishes our file `<input>` (en-US) element, as well as a link that invokes the file picker, since we keep the file input hidden to prevent that less-than-attractive UI from being displayed. This is explained above in the section Using hidden file input elements using the click() method, as is the `doClick()` method that invokes the file picker.

The `handleFiles()` method follows:

```js
var fileSelect = document.getElementById("fileSelect"),
  fileElem = document.getElementById("fileElem"),
  fileList = document.getElementById("fileList");

fileSelect.addEventListener(
  "click",
  function (e) {
    if (fileElem) {
      fileElem.click();
    }
    e.preventDefault(); // prevent navigation to "#"
  },
  false,
);

function handleFiles(files) {
  if (!files.length) {
    fileList.innerHTML = "<p>No files selected!</p>";
  } else {
    var list = document.createElement("ul");
    for (var i = 0; i < files.length; i++) {
      var li = document.createElement("li");
      list.appendChild(li);

      var img = document.createElement("img");
      img.src = window.URL.createObjectURL(files[i]);
      img.height = 60;
      img.onload = function () {
        window.URL.revokeObjectURL(this.src);
      };
      li.appendChild(img);

      var info = document.createElement("span");
      info.innerHTML = files[i].name + ": " + files[i].size + " bytes";
      li.appendChild(info);
    }
    fileList.appendChild(list);
  }
}
```

This starts by fetching the URL of the `<div>` with the ID "fileList". This is the block into which we'll insert out file list, including thumbmails.

If the `FileList` object passed to `handleFiles()` is `null`, we simply set the inner HTML of the block to display "No files selected!". Otherwise, we start building our file list, as follows:

1. A new unordered list (`<ul>` (en-US) element is created.
2. The new list element is inserted into the `<div>` block by calling its `element.appendChild()` method.
3. For each `File` in the `FileList` represented by `files`:
    1. Create a new list item (`<li>` (en-US)) element and insert it into the list.
    2. Create a new image (`<img>` (en-US)) element.
    3. Set the image's source to a new object URL representing the file, using `window.URL.createObjectURL()` (en-US) to create the blob URL.
    4. Set the image's height to 60 pixels.
    5. Set up the image's load event handler to release the object URL, since it's no longer needed once the image has been loaded. This is done by calling the `window.URL.revokeObjectURL()` (en-US) method, passing in the object URL string as specified by `img.src`.
    6. Append the new list item to the list.
範例：上傳檔案
-------

接下來這個範例顯示如何非同步的上傳檔案到伺服器。

### 新增上傳的工作

接續先前創建縮圖的範例，將每個縮圖都設置 CSS class 「obj」， 這使得我們可以很容易地使用`Document.querySelectorAll()` (en-US) 選擇使用者要上傳的圖檔，例如：

```js
function sendFiles() {
  var imgs = document.querySelectorAll(".obj");

  for (var i = 0; i < imgs.length; i++) {
    new FileUpload(imgs[i], imgs[i].file);
  }
}
```

第二行創建了 `imgs` 陣列，存放著所有文件中 CSS class 為 「obj」 的 Node。在這個範例中，我們使用這個來創建縮圖。Once we have that list, it's trivial to go through the list, creating a new `FileUpload` instance for each. Each of these handles uploading the corresponding file.

### 處理上傳檔案的程序

`FileUpload` 函數接受圖檔和檔案.

```js
function FileUpload(img, file) {
  var reader = new FileReader();
  this.ctrl = createThrobber(img);
  var xhr = new XMLHttpRequest();
  this.xhr = xhr;

  var self = this;
  this.xhr.upload.addEventListener(
    "progress",
    function (e) {
      if (e.lengthComputable) {
        var percentage = Math.round((e.loaded \* 100) / e.total);
        self.ctrl.update(percentage);
      }
    },
    false,
  );

  xhr.upload.addEventListener(
    "load",
    function (e) {
      self.ctrl.update(100);
      var canvas = self.ctrl.ctx.canvas;
      canvas.parentNode.removeChild(canvas);
    },
    false,
  );
  xhr.open(
    "POST",
    "http://demos.hacks.mozilla.org/paul/demos/resources/webservices/devnull.php",
  );
  xhr.overrideMimeType("text/plain; charset=x-user-defined-binary");
  reader.onload = function (evt) {
    xhr.sendAsBinary(evt.target.result);
  };
  reader.readAsBinaryString(file);
}
```

`FileUpload()` 函數創建被用來顯示上傳進度的 throbber，接著創建 `XMLHttpRequest` 上傳檔案.

傳輸資料前的幾個準備工作:

1. The `XMLHttpRequest`'s upload "progress" listener is set to update the throbber with new percentage information, so that as the upload progresses, the throbber will be updated based on the latest information.
2. The `XMLHttpRequest`'s upload "load" event handler is set to update the throbber with 100% as the progress information (to ensure the progress indicator actually reaches 100%, in case of granularity quirks during the process). It then removes the throbber, since it's no longer needed. This causes the throbber to disappear once the upload is complete.
3. The request to upload the image file is opened by calling `XMLHttpRequest`'s `open()` method to start generating a POST request.
4. The MIME type for the upload is set by calling the `XMLHttpRequest` function `overrideMimeType()`. In this case, we're using a generic MIME type; you may or may not need to set the MIME type at all, depending on your use case.
5. The `FileReader` object is used to convert the file to a binary string.
6. Finally, when the content is loaded the `XMLHttpRequest` function `sendAsBinary()` is called to upload the file's content.

**備註：** 範例中非標準的 `sendAsBinary` 方法已經在 Gecko 31 廢棄且很快將會被移除。可以改使用標準的 `send(Blob data)。`

### 非同步處理上傳檔案的程序

```js
function fileUpload(file) {
  // Please report improvements to: marco.buratto at tiscali.it

  var fileName = file.name,
    fileSize = file.size,
    fileData = file.getAsBinary(), // works on TEXT data ONLY.
    boundary = "xxxxxxxxx",
    uri = "serverLogic.php",
    xhr = new XMLHttpRequest();

  xhr.open("POST", uri, true);
  xhr.setRequestHeader(
    "Content-Type",
    "multipart/form-data, boundary=" + boundary,
  ); // simulate a file MIME POST request.
  xhr.setRequestHeader("Content-Length", fileSize);

  xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
      if ((xhr.status >= 200 && xhr.status <= 200) || xhr.status == 304) {
        if (xhr.responseText != "") {
          alert(xhr.responseText); // display response.
        }
      }
    }
  };

  var body = "--" + boundary + "\r\n";
  body +=
    "Content-Disposition: form-data; name='fileId'; filename='" +
    fileName +
    "'\r\n";
  body += "Content-Type: application/octet-stream\r\n\r\n";
  body += fileData + "\r\n";
  body += "--" + boundary + "--";

  xhr.send(body);
  return true;
}
```

*使用二進制數據時，這些程式碼還需要修改。*

參見
--

* `File`
* `FileList`
* `FileReader`
* 使用 XMLHttpRequest
* Using the DOM File API in chrome code
* `XMLHttpRequest`


URL.createObjectURL() - Web APIs

URL.createObjectURL()
=====================

**實驗性質:** **這是一個實驗中的功能 (en-US)**
此功能在某些瀏覽器尚在開發中，請參考兼容表格以得到不同瀏覽器用的前輟。

摘要
--

靜態方法 **`URL.createObjectURL()`** 用於建立一個帶有 URL 的 `DOMString` 以代表參數中所傳入的物件. 該 URL 的生命週期與創造它的 window 中的 `document` (en-US)一致. 這個新的物件 URL 代表了所指定的 `File` 物件 或是 `Blob` 物件。

**備註：** 此功能可在 Web Worker (en-US) 中使用

語法
--
```

objectURL = URL.createObjectURL(blob);
```

參數
--

*blob*

一個用以建立物件 URL 的 `File` 物件 或是 `Blob` 物件.

範例
--

參見 Using object URLs to display images.(藉由物件 URL 來顯示圖像)

注意事項
----

每次呼叫 `createObjectURL()` 都會產生一個新的 URL, 不論是否曾以同一物件產生過. 當你不再需要它們的時候必須對每一個都呼叫 `URL.revokeObjectURL()` (en-US) 來釋放它們. 瀏覽器會在 document 被 unload 時自動釋放它們; 然而, 為了最佳化效能與記憶體用量, 當有安全的時機請務必手動釋放它們.

規範文件
----

| Specification |
| --- |
| File API # dfn-createObjectURL |

瀏覽器相容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

另見
--

* `URL.revokeObjectURL()` (en-US)
* Using files from web applications


KeyboardEvent() - Web APIs

KeyboardEvent()
===============

**`KeyboardEvent()`** constructor 能用來建立一個新的 `KeyboardEvent`。

語法
--
```

 event = new KeyboardEvent(typeArg, KeyboardEventInit);
```

### 參數

`type`

一 `DOMString` 用來表示事件名稱。

`options` 選擇性

一個 `KeyboardEventInit` dictionary，能接受以下參數：

`key` 選擇性

一個字符串，默認值為 `""`，用來設定 `KeyboardEvent.key` (en-US) 的值。

`code` 選擇性

一個字符串，默認值為 `""`，用來設定 `KeyboardEvent.code` (en-US) 的值。

`location` 選擇性

一個 `unsigned long`，默認值為 `0`，用來設定 `KeyboardEvent.location` (en-US) 的值。

`ctrlKey` 選擇性

一個 `Boolean`，默認值為 `false`，用來設定 `KeyboardEvent.ctrlKey` (en-US) 的值。

`shiftKey` 選擇性

一個 `Boolean`，默認值為 `false`，用來設定 `KeyboardEvent.shiftKey` (en-US) 的值。

`altKey` 選擇性

一個 `Boolean`，默認值為 `false`，用來設定 `KeyboardEvent.altKey` (en-US) 的值。

`metaKey` 選擇性

一個 `Boolean`，默認值為 `false`，用來設定 `KeyboardEvent.metaKey` (en-US) 的值。

`repeat` 選擇性

一個 `Boolean`，默認值為 `false`，用來設定 `KeyboardEvent.repeat` (en-US) 的值。

`isComposing` 選擇性

一個 `Boolean`，默認值為 `false`，用來設定 `KeyboardEvent.isComposing` (en-US) 的值。

`charCode` 選擇性

一個 `unsigned long`，默認值為 `0`，用來設定 `KeyboardEvent.charCode` (en-US) 的值。

`keyCode` 選擇性

一個 `unsigned long`，默認值為 `0`，用來設定 `KeyboardEvent.keyCode` (en-US) 的值。

`which` 選擇性

一個 `unsigned long`，默認值為 `0`，用來設定 `KeyboardEvent.which` (en-US) 的值

**備註：** *`KeyboardEventInit` dictionary 亦接受 `UIEventInit` 和`EventInit` 所接受的參數。*

規格
--

| Specification |
| --- |
| UI Events # dom-keyboardevent-keyboardevent |

瀏覽器支援度
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

延伸閱讀
----

* `KeyboardEvent` ，此 constructer 所建立的 object 的 interface


FormData.get() - Web APIs

FormData.get()
==============

`FormData` 的 **`get()`** 方法會返回 `FormData 物件中，指定` key` 值所對應之第一組物件中的 value 值。然而，如果你想要獲得多組以及全部的 value，那應該使用 `getAll()` (en-US) 方法。

**注意**: 這個方法已可以在 Web Worker (en-US) 中使用。

語法
--

```js
formData.get(name);
```

### 參數

`name`

一個 `USVString` (en-US)，代表你想要得到的 value 所對應的 key 值名稱。

### 回傳值

A `FormDataEntryValue` (en-US) containing the value.

範例
--

下面一行程式會產生一個空的 `FormData` 物件：

```js
var formData = new FormData();
```

用 `FormData.append` (en-US) 方法新增兩組 `username` 值

```js
formData.append("username", "Chris");
formData.append("username", "Bob");
```

接下來使用 `get()` 方法，將只會返回上一步驟，第一組新增的 `username` 所對應的值

```js
formData.get("username"); // 返回 "Chris"
```

規範
--

| Specification |
| --- |
| XMLHttpRequest Standard # dom-formdata-get |

瀏覽器相容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

參見
--

* `XMLHTTPRequest`
* 使用 XMLHttpRequest
* 使用 FormData 物件
* `<form>` (en-US)


MediaSource.readyState - Web APIs

MediaSource.readyState
======================

**實驗性質:** **這是一個實驗中的功能 (en-US)**
此功能在某些瀏覽器尚在開發中，請參考兼容表格以得到不同瀏覽器用的前輟。

`MediaSource` 介面的唯讀屬性 **`readyState`** 回傳表示當前 `MediaSource` 狀態的列舉值。三種可能的值為：

* `closed`: 來源 (source) 目前沒有附加到媒體元件 (media element) 。
* `open`: 來源已經附加且可以接收 `SourceBuffer` (en-US) 物件。
* `ended`: 來源已經附加但是串流已經經由 `MediaSource.endOfStream()` (en-US) 結束。
語法
--

```js
var myReadyState = mediaSource.readyState;
```

### 回傳值

一個 `DOMString`。

範例
--

以下片段是由 Nick Desaulniers 所撰寫的簡單範例（在此實際觀看，或者下載原始碼以更進一步研究）

```js
if ("MediaSource" in window && MediaSource.isTypeSupported(mimeCodec)) {
  var mediaSource = new MediaSource();
  //console.log(mediaSource.readyState); // closed
  video.src = URL.createObjectURL(mediaSource);
  mediaSource.addEventListener("sourceopen", sourceOpen);
} else {
  console.error("Unsupported MIME type or codec: ", mimeCodec);
}

function sourceOpen(\_) {
  //console.log(this.readyState); // open
  var mediaSource = this;
  var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
  fetchAB(assetURL, function (buf) {
    sourceBuffer.addEventListener("updateend", function (\_) {
      mediaSource.endOfStream();
      video.play();
      //console.log(mediaSource.readyState); // ended
    });
    sourceBuffer.appendBuffer(buf);
  });
}
```

規格
--

| Specification |
| --- |
| Media Source Extensions™ # dom-mediasource-readystate |

相容性表格
-----

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相關資料
----

* `SourceBuffer` (en-US)
* `SourceBufferList` (en-US)


MediaSource.duration - Web APIs

MediaSource.duration
====================

**實驗性質:** **這是一個實驗中的功能 (en-US)**
此功能在某些瀏覽器尚在開發中，請參考兼容表格以得到不同瀏覽器用的前輟。

`MediaSource` 介面的 **`duration`** 屬性用來取得以及設置正被表示的媒體時間長度。

語法
--

```js
mediaSource.duration = 5.5; // 5.5 seconds

var myDuration = mediaSource.duration;
```

### 回傳值

單位為秒的 double 型別。

### 錯誤

當設置此屬性一個新的值時以下錯誤可能發生。

| 錯誤 | 解釋 |
| --- | --- |
| `InvalidAccessError` | 嘗試設置的時間長度是負值，或者 `NaN`。 |
| `InvalidStateError` | `MediaSource.readyState` 不是 `open`，或者 `MediaSource.sourceBuffers` (en-US) 中一個或多個以上的 `SourceBuffer` (en-US) 物件正在被更新（例如：他們的 `SourceBuffer.updating` (en-US) 屬性為 `true`。） |

範例
--

以下的片段基於 Nick Desaulniers 所編纂的簡單範例（觀看實際演示，或者下載原始碼以利更進一步研究。）

```js
function sourceOpen (\_) {
  //console.log(this.readyState); // open
  var mediaSource = this;
  var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
  fetchAB(assetURL, function (buf) {
    sourceBuffer.addEventListener('updateend', function (\_) {
      mediaSource.endOfStream();
      mediaSource.duration = 120;
      video.play();
      //console.log(mediaSource.readyState); // ended
    });
    sourceBuffer.appendBuffer(buf);
  });
};

...
```

規格
--

| Specification |
| --- |
| Media Source Extensions™ # dom-mediasource-duration |

相容性表格
-----

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相關資料
----

* `SourceBuffer` (en-US)
* `SourceBufferList` (en-US)


MediaSource.activeSourceBuffers - Web APIs

MediaSource.activeSourceBuffers
===============================

**實驗性質:** **這是一個實驗中的功能 (en-US)**
此功能在某些瀏覽器尚在開發中，請參考兼容表格以得到不同瀏覽器用的前輟。

**`activeSourceBuffers`** 是 `MediaSource` 介面的唯讀屬性，回傳一個 `SourceBufferList` (en-US) 物件，含有在 `SourceBuffers` 之中的 `SourceBuffer` (en-US) 物件子集合—物件的串列提供被選擇的影片軌 (video track), 啟用的音軌 (audio tracks), 以及顯示或隱藏的字軌。

語法
--

```js
var myActiveSourceBuffers = mediaSource.activeSourceBuffers;
```

### 回傳值

一個 `SourceBufferList` (en-US) 。

範例
--

以下的片段基於 Nick Desaulniers 所編纂的簡單範例（觀看實際演示，或者下載原始碼 以利更進一步研究。）

```js
function sourceOpen (\_) {
  //console.log(this.readyState); // open
  var mediaSource = this;
  var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
  fetchAB(assetURL, function (buf) {
    sourceBuffer.addEventListener('updateend', function (\_) {
      mediaSource.endOfStream();
      console.log(mediaSource.activeSourceBuffers);
      // will contain the source buffer that was added above,
      // as it is selected for playing in the video player
      video.play();
      //console.log(mediaSource.readyState); // ended
    });
    sourceBuffer.appendBuffer(buf);
  });
};

...
```

規格
--

| Specification |
| --- |
| Media Source Extensions™ # dom-mediasource-activesourcebuffers |

相容性表格
-----

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相關資料
----

* `SourceBuffer` (en-US)
* `SourceBufferList` (en-US)


MediaSource.MediaSource() - Web APIs

MediaSource.MediaSource()
=========================

**實驗性質:** **這是一個實驗中的功能 (en-US)**
此功能在某些瀏覽器尚在開發中，請參考兼容表格以得到不同瀏覽器用的前輟。

`MediaSource` 介面的 **`MediaSource()`** 建構子建構且回傳一個沒有與任何來源緩衝 (source buffer) 關聯的新 `MediaSource` 物件。

語法
--

```js
var mediaSource = new MediaSource();
```

### 參數

無。

範例
--

以下的片段擷取自 Nick Desaulniers 所編纂的簡單範例（觀看實際演示，或者下載原始碼 以利更進一步研究。）

```js
var video = document.querySelector('video');

var assetURL = 'frag\_bunny.mp4';
// Need to be specific for Blink regarding codecs
// ./mp4info frag\_bunny.mp4 | grep Codec
var mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';

if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
  var mediaSource = new MediaSource;
  //console.log(mediaSource.readyState); // closed
  video.src = URL.createObjectURL(mediaSource);
  mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
  console.error('Unsupported MIME type or codec: ', mimeCodec);
}

...
```

瀏覽器相容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相關資料
----

* `SourceBuffer` (en-US)
* `SourceBufferList` (en-US)


Screen.orientation - Web APIs

Screen.orientation
==================

**實驗性質:** **這是一個實驗中的功能 (en-US)**
此功能在某些瀏覽器尚在開發中，請參考兼容表格以得到不同瀏覽器用的前輟。

`Screen.orientation` 屬性可以取得螢幕目前的方向。

語法
--
```

var orientation = window.screen.orientation.type;
```

回傳值
---

回傳值為一個代表螢幕方向的字串，可能是 `portrait-primary`、`portrait-secondary`、`landscape-primary` 或 `landscape-secondary`（請參考 `lockOrientation` 以瞭解更多資訊）。

範例
--

```js
var orientation =
  screen.orientation || screen.mozOrientation || screen.msOrientation;

if (orientation.type === "landscape-primary") {
  console.log("That looks good.");
} else if (orientation.type === "landscape-secondary") {
  console.log("Mmmh... the screen is upside down!");
} else if (
  orientation.type === "portrait-secondary" ||
  orientation.type === "portrait-primary"
) {
  console.log("Mmmh... you should rotate your device to landscape");
}
```

規範
--

| Specification |
| --- |
| Screen Orientation # dom-screen-orientation |

瀏覽器相容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

參見
--

* `Screen.orientation`
* `Screen.unlockOrientation()` (en-US)
* `Screen.onorientationchange` (en-US)
* 控制畫面方向


PositionError.code - Web APIs

PositionError.code
==================

**`PositionError.code`** 是一個唯讀無符號整數（`unsigned short`）表示錯誤碼。以下列出可能的值：

| 值 | 相對應的常數 | 描述 |
| --- | --- | --- |
| `1` | `PERMISSION_DENIED` | 取得地理資訊失敗，因為此頁面沒有獲取地理位置信息的權限。 |
| `2` | `POSITION_UNAVAILABLE` | 取得地理資訊失敗，因為至少有一個地理位置信息內的資訊回傳了錯誤。 |
| `3` | `TIMEOUT` | 取得地理資訊超過時限，利用 `PositionOptions.timeout` i 來定義取得地理資訊的時限。 |

語法
--
```

typeErr = poserr.code
```

規格
--

| Specification |
| --- |
| Geolocation API # code-attribute |

瀏覽器的相容性
-------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

請參考
---

* Using geolocation (en-US)
* 屬於 `PositionError` 。


PositionError.message - Web APIs

PositionError.message
=====================

**`PositionError.message`** 是一個可讀的 `DOMString` 來描述錯誤的詳細訊息。

語法
--
```

msg = positionError.message
```

規格
--

| Specification |
| --- |
| Geolocation API # message-attribute |

瀏覽器的相容性
-------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

請參考
---

* Using geolocation (en-US)
* 屬於 `PositionError`。


DragEvent.dataTransfer - Web APIs

DragEvent.dataTransfer
======================

**`DataEvent.dataTransfer`** 屬性保留了拖曳操作中的資料（指向一個 `DataTransfer` (en-US) 物件）。

此屬性為 Read only 。

語法
--
```

var data = dragEvent.dataTransfer;
```

### 回傳值

`data`

一個保存 `DragEvent` 當中資料的 `DataTransfer` (en-US) 物件。

範例
--

This example illustrates accessing the drag and drop data within the `dragend` (en-US) event handler.

```js
function process\_data(d) {
  // Process the data ...
}

dragTarget.addEventListener(
  "dragend",
  function (ev) {
    // Call the drag and drop data processor
    if (ev.dataTransfer != null) process\_data(ev.dataTransfer);
  },
  false,
);
```

規範
--

| Specification |
| --- |
| HTML Standard # dom-dragevent-datatransfer-dev |

瀏覽器相容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


Console.assert() - Web APIs

Console.assert()
================

如果斷言（assertion）為非（false），主控台會顯示錯誤訊息；如果斷言為是（true），則不發生任何事。

**備註：** 此功能可在 Web Worker (en-US) 中使用

**備註：** 在 Node.js 內 `console.assert()` 方法的實做，與瀏覽器並不相同。瀏覽器內呼叫 falsy 的 `console.assert()` 斷言出現 `message`，但不會中斷程式碼的執行。然而在 Node.js 裡面，falsy 斷言會拋出 `AssertionError` 錯誤。

語法
--

```js
assert(assertion, obj1)
assert(assertion, obj1, obj2)
assert(assertion, obj1, obj2, /\* …, \*/ objN)

assert(assertion, msg)
assert(assertion, msg, subst1)
assert(assertion, msg, subst1, /\* …, \*/ substN)
```

### 參數

`assertion`

布林表達式。如果斷言為非，訊息會出現在主控台上。

`obj1` ... `objN`

要印出來的 JavaScript 物件名單。 The string representations of each of these objects are appended together in the order listed and output.

`msg`

包含零個以上的 JavaScript 替代（substitution）字串。

`subst1` ... `substN`

JavaScript objects with which to replace substitution strings within `msg`. This parameter gives you additional control over the format of the output.

請參見 `console` (en-US) 的 Outputting text to the console (en-US) 以獲取詳細資訊。

範例
--

以下程式碼示範一個 JavaScript 物件的斷言使用：

```js
const errorMsg = "the # is not even";
for (let number = 2; number <= 5; number += 1) {
  console.log("the # is " + number);
  console.assert(number % 2 === 0, { number: number, errorMsg: errorMsg });
  // or, using ES2015 object property shorthand:
  // console.assert(number % 2 === 0, {number, errorMsg});
}
// output:
// the # is 2
// the # is 3
// Assertion failed: {number: 3, errorMsg: "the # is not even"}
// the # is 4
// the # is 5
// Assertion failed: {number: 5, errorMsg: "the # is not even"}
```

請注意，雖然包含替換字符串的字符串在 Node 中用作 `console.log` 的參數，但很多（如果不是大多數）瀏覽器...

```js
console.log("the word is %s", "foo");
// output: the word is foo
```

...在所有瀏覽器中，使用此類字符串目前無法作為 console.assert 的參數使用：

```js
console.assert(false, "the word is %s", "foo");
// correct output in Node (e.g. v8.10.0) and some browsers
// (e.g. Firefox v60.0.2):
// Assertion failed: the word is foo
// incorrect output in some browsers
// (e.g. Chrome v67.0.3396.87):
// Assertion failed: the word is %s foo
```

有關詳細信息，請參閱 `console` (en-US) 文檔中的將文本輸出到控制台 (en-US)。

規範
--

| Specification |
| --- |
| Console Standard # assert |

瀏覽器相容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

參見
--

* WHATWG Console Standard: console.assert
* Opera Dragonfly documentation: Console
* MSDN: Using the F12 Tools Console to View Errors and Status
* Chrome Developer Tools: Using the Console


HTMLMediaElement: ratechange - Web APIs

HTMLMediaElement: ratechange
============================

`ratechange` 事件將在播放速度改變時被觸發

基本資訊
----

規格

HTML5 Media

介面

事件

是否冒泡

否

是否可取消

否

目標

元素

預設行為

無

屬性
--

| 屬性 | 類型 | 描述 |
| --- | --- | --- |
| `target` Read only  | `EventTarget` | 事件目標（DOM 樹中最頂層的目標) |
| `type` Read only  | `DOMString` | 事件類型 |
| `bubbles` Read only  | `Boolean` | 事件是否觸發冒泡 |
| `cancelable` Read only  | `Boolean` | 事件是否可取消 |

規範
--

| Specification |
| --- |
| HTML Standard # event-media-ratechange |
| HTML Standard # handler-onratechange |

相關事件
----

* `playing` (en-US)
* `waiting` (en-US)
* `seeking` (en-US)
* `seeked` (en-US)
* `ended` (en-US)
* `loadedmetadata` (en-US)
* `loadeddata` (en-US)
* `canplay` (en-US)
* `canplaythrough` (en-US)
* `durationchange` (en-US)
* `timeupdate` (en-US)
* `play` (en-US)
* `pause` (en-US)
* `ratechange`
* `volumechange` (en-US)
* `suspend` (en-US)
* `emptied` (en-US)
* `stalled` (en-US)


HTMLMediaElement.readyState - Web APIs

HTMLMediaElement.readyState
===========================

**`HTMLMediaElement.readyState`** 屬性回傳目前媒體的就緒狀態。

語法
--
```

var readyState = audioOrVideo.readyState;
```

### 值

一個 `unsigned short`，可能的值有：

| 常數 | 值 | 描述 |
| --- | --- | --- |
| HAVE\_NOTHING | 0 | 沒有可用的媒體資源。 |
| HAVE\_METADATA | 1 | 已經取得足夠的媒體資源並已初始化元資料。繼續取得媒體資源不會導致例外。 |
| HAVE\_CURRENT\_DATA | 2 | 媒體資料已經足夠播放目前的時間，但沒有足夠的資料再播放一幀。 |
| HAVE\_FUTURE\_DATA | 3 | 資料已經足夠播放目前的時間，而且有至少一點點資料可以播放未來的時間（換句話說，可能只多了一到兩幀）。 |
| HAVE\_ENOUGH\_DATA | 4 | 資料足夠，且下載率夠高。媒體可以播放到結束而不被中斷。 |

範例
--

下面這個例子會監聽 `example` 這個元素，並檢查是否已載入足夠的媒體資源。如果是的話，它會繼續播放。

```html
<audio id="example" preload="auto">
  <source src="sound.ogg" type="audio/ogg" />
</audio>
```

```js
var obj = document.getElementById("example");

obj.addEventListener("loadeddata", function () {
  if (obj.readyState >= 2) {
    obj.play();
  }
});
```

標準
--

| Specification |
| --- |
| HTML Standard # dom-media-readystate-dev |

瀏覽器相容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

也參考看看
-----

* The interface defining it, `HTMLMediaElement` (en-US).


abort - Web APIs

abort
=====

當資源載入被拒絕時將會觸發\*\*`abort`\*\*事件。

一般資訊
----

規範

DOM L3

介面

若由使用者介面產生，為 UIEvent，否則為 Event。

是否向上(冒泡)

否

是否為可取消

否

目標對象

Element

預設行為

無

屬性
--

| 屬性 | 型態 | 描述 |
| --- | --- | --- |
| `target` Read only  | `EventTarget` | 事件的目標對象 (DOM 樹中最頂層的對象)。 |
| `type` Read only  | `DOMString` | 事件的型態。 |
| `bubbles` Read only  | `Boolean` (en-US) | 事件是否向上冒泡。 |
| `cancelable` Read only  | `Boolean` (en-US) | 事件是否能夠取消。 |
| `view` Read only  | `WindowProxy` | `document.defaultView` (該文檔(Document)的`window`) |
| `detail` Read only  | `long` (`float`) | 0. |

規範
--

| Specification |
| --- |
| HTML Standard # event-media-abort |
| HTML Standard # handler-onabort |


Canvas 教學文件 - Web APIs

Canvas 教學文件
===========

**`<canvas>`** 是一個 HTML 元素，我們可以利用程式腳本在這個元素上繪圖（通常是用 JavaScript）。除了繪圖，我們還可以合成圖片或做一些簡單（或是不那麼簡單）的動畫。右方的影像便是一些運用 <canvas> 的例子，接下來我們將會在教學文件中一一說明。

本教學從基礎知識開始，描述如何利用 <canvas> 進行 2D 繪圖。教學中的範例會讓各位清楚瞭解 <canvas> 該如何運用，另外也會提供程式碼範例，讓大家嘗試製作自己的內容。

`<canvas>` 最早是由 Apple 為 Mac OS X Dashboard 所提出，之後 Safari 和 Google Chrome 也都採用。Gecko 1.8 作基礎的瀏覽器，如 Firefox 1.5 也都提供了支援。`<canvas>` 元素是 WhatWG Web applications 1.0（也就是 HTML5）規範的一部分，目前所有主流的瀏覽器都已支援。

在開始之前
-----

`<canvas>` 並不困難，但你需要了解基本的 HTML 與 JavaScript。部分舊版瀏覽器不支援 `<canvas>`，不過基本上現今所有主流的瀏覽器都有支援。預設的畫布大小是 300px \* 150px（寬 \* 高）。但你也可以透過 HTML 寬、高屬性（attribute）自訂。為了在畫布上作畫，我們使用了一個 JavaScript context 物件來即時繪製圖形。

教學文件
----

* 基本用途
* 繪製圖形
* 套用樣式與顏色
* 繪製文字
* 使用影像
* 變形效果
* 合成效果
* 基礎動畫 (en-US)
* 進階動畫
* 像素控制
* Hit regions and accessibility
* 最佳化 canvas (en-US)
* Finale (en-US)
參見
--

* Canvas topic page
* Adobe Illustrator to Canvas plug-in
* HTML5CanvasTutorials
* 31 days of canvas tutorials
* Drawing Graphics with Canvas (en-US)
* Canvas examples
* HTML5 Tutorial
* Drawing Text Using a Canvas
* Adding Text to Canvas
* Canvas Demos - Games, applications, tools and tutorials
* Canvas Drawing and Animation Application
* interactive canvas tutorial
* Canvas Cheat Sheet with all attributes and methods
* Adobe Illustrator to Canvas plug-in
* HTML5CanvasTutorials
* How to draw N grade Bézier curves with the Canvas API
* 31 days of canvas tutorials
* W3C Standard
致歉各位貢獻者
-------

由於 2013/6/17 那一週的不幸技術錯誤，所有有關本教學的歷史紀錄，包括過去所有貢獻者的紀錄都遺失了，我們深感抱歉，希望各位可以原諒這一次不幸的意外。

* 次頁 »


使用影像 - Web APIs

使用影像
====

* « 前頁
* 次頁 »

使用影像是`<canvas>`另一個有趣的功能，這個功能可以用來動態組合圖片或作為背景等等。任何瀏覽器支援的外部圖片格式都可以使用，例如 PNG, GIF, 或 JPEG，甚至也可以利用同一份頁面上其他畫布元素產生的影像.

載入影像到畫布中基本上需要兩個步驟：

1. 取得`HTMLImageElement` (en-US)物件或其他畫布元素的參照(reference)作為來源，透過單純提供 URL 或圖片位置的方式是行不通的.
2. 用 drawImage()函數在畫布上畫影像.

接下來便來看看要怎麼做.

取得影像
----

畫布 API 能接受以下資料型態作為影像來源:

`HTMLImageElement` (en-US)

用 Image()建構成的影像或是`<img>` (en-US)元素.

`HTMLVideoElement` (en-US)

用`HTMLVideoElement` (en-US)元素作影像來源，抓取影片目前的影像畫格當作影像使用.

`HTMLCanvasElement` (en-US)

用另一個`HTMLCanvasElement` (en-US)元素當影像來源.

`ImageBitmap` (en-US)

可以被快速渲染的點陣圖(bitmap)，點陣圖能由上述所有來源產生.

這些來源統一參照 CanvasImageSource型態.

有好幾種方法能夠取得影像用於畫布.

### 使用同一份網頁上的影像

我們能透過下面幾個方法取得影像:

* 從`document.images` (en-US)
* `document.getElementsByTagName()` (en-US)方法
* 如果知道特定影像的 ID，那可以`document.getElementById()` (en-US)方法
### 使用來自其他網域的影像

Using the `crossOrigin` (en-US) attribute on an 透過`<htmlimageelement>`的`crossOrigin` (en-US)屬性, 我們可以要求從另一個網域載入影像來使用，若是寄存網域(thehosting domain)准許跨網路存取該影像，那麼我們便可以使用它而不用污染(taint)我們的畫布，反之，使用該影像會污染畫布(taint the canvas (en-US))。

### 使用其他畫布元素

如同取得其他影像，我們一樣能用`document.getElementsByTagName()` (en-US)或`document.getElementById()` (en-US)方法取得其他畫布元素，但是在使用之前請記得來源畫布上已經有繪上圖了。

使用其他畫布元素作為影像來源有很多有用的應用用途，其中之一便是建立第二個小畫布作為另一個大畫布的縮小影像.

### 創造全新的影像

產生新的`HTMLImageElement` (en-US)物件也能當作影像來源，這邊，我們可以用 Image()來建構一個新影像元素:

```js
var img = new Image(); // Create new img element
img.src = "myImage.png"; // Set source path
```

上述程式碼執行後會載入影像.

在影像載入完成前呼叫 drawImage()不會有任何效果，甚至某些瀏覽器還會拋出例外狀況，所以應該要透過利用載入事件來避免這類問題:

```js
var img = new Image(); // Create new img element
img.addEventListener(
  "load",
  function () {
    // execute drawImage statements here
  },
  false,
);
img.src = "myImage.png"; // Set source path
```

若是只要載入一份影像，可以用上面的方法，不過當需要載入、追蹤多個影像時，我們就需要更好的方法了，雖然管理多個影像載入已經超出本教學的範疇，然而如果有興趣的話，可以參考JavaScript Image Preloader這份文件.

### 以 data:URL 嵌入影像

另一個載入影像的方法是利用data: url (en-US)，透過 data URL 可以直接將影像定義成 Base64 編碼的字串，然後嵌入程式碼之中.

```js
var img_src =
  "data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==";
```

data URL 的好處之一是立即產生影像而不用再和伺服器連線，另一個好處是這樣便能夠將影像包入你的CSS, JavaScript, HTML之中，讓影像更具可攜性.

壞處則是影像將不會被快取起來，而且對大影像來說編碼後的 URL 會很長.

### Using frames from a video

我們還能夠使用`<video>` (en-US)元素中的影片的影片畫格(縱使影片為隱藏)，例如，現在我們有一個 ID 為「myvideo」 的`<video>` (en-US)元素:

```js
function getMyVideo() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    return document.getElementById("myvideo");
  }
}
```

 上面的方法會回傳一個`HTMLVideoElement` (en-US)的影像物件，如前所述，這個物件可以被視為 CanvasImageSource 類別的物件來使用。
 關於如何利用<video>元素於畫布上的進階說明，可以參考 html5Doctor 的「video + canvas = magic」一文.

影像繪圖
----

一旦我們取得來源影像物件的參照(reference)，便可以用 drawImage()方法將影像渲染到畫布上，drawImage()方法是一個多載(overload)方法，有數個型態，待會我們會看到這項特性，現在我們先來看 drawImage()最基本的型態:

`drawImage(image, x, y)`

從座標點(x, y)開始畫上 image 參數指定的來源影像(CanvasImageSource).

### 範例: 一條簡單的線段影像

這個範例會使用外部影像作為一個小型線圖的背景。利用預先劃好的圖作為背景的話就不用再靠程式來產生背景，如此一來可以顯著地減少程式碼。下面藉由影像物件的 load 事件處理器來處理繪圖作業，其中 drawImage()方法把背景圖片放置在畫布左上角，座標點(0, 0)位置.
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="180" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  var img = new Image();
  img.onload = function () {
    ctx.drawImage(img, 0, 0);
    ctx.beginPath();
    ctx.moveTo(30, 96);
    ctx.lineTo(70, 66);
    ctx.lineTo(103, 76);
    ctx.lineTo(170, 15);
    ctx.stroke();
  };
  img.src = "backdrop.png";
}
```

結果如下:

| Screenshot | Live sample |
| --- | --- |
|  |  |

縮放
--

drawImage()的第二個型態增加了兩個新參數，讓我們在畫布上放置影像的同時並縮放影像.

`drawImage(image, x, y, width, height)`

當放置影像於畫布上時，會按照參數 width(寬)、height(高)來縮放影像.

### 範例: 排列影像

本例我們會取一張影像作為桌布，然後透過簡單的迴圈來重複縮放、貼上影像於畫布上。在程式碼中，第一個迴圈走一遍每一列，第二個迴圈走一遍每一行，影像則縮小成原始影像的三分之一，50 x 38 像素.

**備註：** 過度縮放影像可能會造成影像模糊或產生顆粒感，所以如果影像中有文字需要閱讀，最好不要縮放影像.
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  var img = new Image();
  img.onload = function () {
    for (var i = 0; i < 4; i++) {
      for (var j = 0; j < 3; j++) {
        ctx.drawImage(img, j \* 50, i \* 38, 50, 38);
      }
    }
  };
  img.src = "rhino.jpg";
}
```

結果如下:

| Screenshot | Live sample |
| --- | --- |
|  |  |

切割影像
----

drawImage()第三個型態接受 9 個參數，其中 8 個讓我們從原始影像中切出一部分影像、縮放並畫到畫布上.

`drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)`

image 參數是來源影像物件，(sx, sy)代表在來源影像中以(sx, sy)座標點作為切割的起始點，sWidth 和 sHeight 代表切割寬和高，(dx, dy)代表放到畫布上的座標點，dWidth 和 dHeight 代表縮放影像至指定的寬和高.

![](/zh-TW/docs/Web/API/Canvas_API/Tutorial/Using_images/canvas_drawimage.jpg)請參照右圖，前四個參數定義了在來源影像上切割的起始點和切割大小，後四個參數定義了畫到畫布上的位置和影像大小.

切割是一個很有用的工具，我們可以把所有影像放到一張影像上，然後利用切割來組成最終完整的影像，比如說，我們可以把所有需要用來組成一張圖表的文字放到一張 PNG 圖檔內，之後只需要單純地再依據資料來縮放圖表，另外，我們也不用多次載入多張影像，這樣對提升載入影像效能頗有幫助.

### 範例: 畫一個有畫框的影像

本例用和前一個範例一樣的犀牛圖，然後切出犀牛頭部影像部分再放入一個影像畫框，這個影像畫框是一個有陰影的 24 位元 PNG 圖檔，因為 24 位元 PNG 影像具備完整的 8 位元不透明色版(alpha channel)，所以不像 GIF 影像和 8 位元 PNG 影像，它能夠放任何背景之上而無須擔心產生消光色(matte color).

```html
<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
    <div style="display:none;">
      <img id="source" src="rhino.jpg" width="300" height="227" />
      <img id="frame" src="canvas\_picture\_frame.png" width="132" height="150" />
    </div>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");

  // Draw slice
  ctx.drawImage(
    document.getElementById("source"),
    33,
    71,
    104,
    124,
    21,
    20,
    87,
    104,
  );

  // Draw frame
  ctx.drawImage(document.getElementById("frame"), 0, 0);
}
```

這次我們不產生新的`HTMLImageElement` (en-US)物件，改採用直接把影像包入 HTML 的`<img>` (en-US)標籤，然後再取得影像元素，其中 HTML 上的影像已經透過設定 CSS 屬性`display` (en-US)為 none 來隱藏起來了.

| Screenshot | Live sample |
| --- | --- |
|  |  |

程式碼相當簡單，每個`<img>` (en-US)有自己的 ID 屬性，這樣便可以利用`document.getElementById()` (en-US)輕易取得，之後再簡單地用 drawImage()方法切割犀牛影像然後縮放並放到畫布上，最後第二個 drawImage()再把畫框放到上面.

畫廊範例
----

在本章的最後一個範例，我們將建造一個小畫廊。當網頁載入完成時，我們會為每一張影像產生一個`<canvas>`元素，並且加上畫框.

本範例中，每一張影像的寬高是固定的，畫框也是一樣，你可以嘗試看看改進程式碼，依據影像的寬高來設定畫框，使畫框能剛剛好框住影像.

從下方的程式碼範例可以很清楚看到，我們為`document.images` (en-US)容器內的影像，一張一張地新建畫布，其中，對於不熟悉文件物件模型 (DOM)的人來說，大慨比較值得注意之處在於使用到`Node.insertBefore` (en-US) 方法；insertBefore()是影像元素的父節點(亦即<td>元素)的一個方法，這個方法會把新畫布元素插入於影像元素之前.

```html
<html>
  <body onload="draw();">
    <table>
      <tr>
        <td><img src="gallery\_1.jpg" /></td>
        <td><img src="gallery\_2.jpg" /></td>
        <td><img src="gallery\_3.jpg" /></td>
        <td><img src="gallery\_4.jpg" /></td>
      </tr>
      <tr>
        <td><img src="gallery\_5.jpg" /></td>
        <td><img src="gallery\_6.jpg" /></td>
        <td><img src="gallery\_7.jpg" /></td>
        <td><img src="gallery\_8.jpg" /></td>
      </tr>
    </table>
    <img id="frame" src="canvas\_picture\_frame.png" width="132" height="150" />
  </body>
</html>
```

這些是一些設定樣式的 CSS:

```css
body {
  background: 0 -100px repeat-x url(bg\_gallery.png) #4f191a;
  margin: 10px;
}

img {
  display: none;
}

table {
  margin: 0 auto;
}

td {
  padding: 15px;
}
```

綜合起來這就是建造出我們小畫廊的程式碼:

```js
function draw() {
  // Loop through all images
  for (var i = 0; i < document.images.length; i++) {
    // Don't add a canvas for the frame image
    if (document.images[i].getAttribute("id") != "frame") {
      // Create canvas element
      canvas = document.createElement("canvas");
      canvas.setAttribute("width", 132);
      canvas.setAttribute("height", 150);

      // Insert before the image
      document.images[i].parentNode.insertBefore(canvas, document.images[i]);

      ctx = canvas.getContext("2d");

      // Draw image to canvas
      ctx.drawImage(document.images[i], 15, 20);

      // Add frame
      ctx.drawImage(document.getElementById("frame"), 0, 0);
    }
  }
}
```

控制影像縮放行為
--------

如前所述，縮放影像會導致影像模糊化或是顆粒化，你可以嘗試透過繪圖環境的 imageSmoothingEnabled 屬性來控制影像平滑演算法的使用，預設上這個屬性的值為 true，也就是說當縮放時，影像會經過平滑處理。如果要關掉這個功能，你可以這麼做:

```js
ctx.mozImageSmoothingEnabled = false;
```

* « 前頁
* 次頁 »


變形效果 - Web APIs

變形效果
====

* « 前頁
* 次頁 »
畫布狀態儲存與復原
---------

在使用變形效果等複雜繪圖方法之前，有兩個不可或缺的方法(method)必須要先了解一下:

`save()`

儲存現階段畫布完整狀態。

`restore()`

復原最近一次儲存的畫布狀態。

每一次呼叫 save()，畫布狀態便會存進一個堆疊(stack)之中。畫布狀態包含了:

* 曾經套用過的變形效果，如 translate, rotate 和 scale(稍後說明)。
* `strokeStyle`, `fillStyle`, `globalAlpha`, `lineWidth`, `lineCap`, `lineJoin`, `miterLimit`, `shadowOffsetX`, `shadowOffsetY`, `shadowBlur`, `shadowColor`, `globalCompositeOperation` 屬性的屬性值
* 目前截圖路徑(稍後說明)。

我們可以呼叫 save()的次數不限，而每一次呼叫 restore()，最近一次儲存的畫布狀態便會從堆疊中被取出，然後還原畫布到此畫布狀態。

### 畫布狀態儲存與復原範例

本例會畫一連串矩形圖案來說明畫布狀態堆疊是如何運作。

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");

  ctx.fillRect(0, 0, 150, 150); // Draw a rectangle with default settings
  ctx.save(); // Save the default state

  ctx.fillStyle = "#09F"; // Make changes to the settings
  ctx.fillRect(15, 15, 120, 120); // Draw a rectangle with new settings

  ctx.save(); // Save the current state
  ctx.fillStyle = "#FFF"; // Make changes to the settings
  ctx.globalAlpha = 0.5;
  ctx.fillRect(30, 30, 90, 90); // Draw a rectangle with new settings

  ctx.restore(); // Restore previous state
  ctx.fillRect(45, 45, 60, 60); // Draw a rectangle with restored settings

  ctx.restore(); // Restore original state
  ctx.fillRect(60, 60, 30, 30); // Draw a rectangle with restored settings
}
```

```
<canvas id="canvas" width="150" height="150"></canvas>
```

```
draw();
```

預設用黑色填滿這個矩形

每種狀態可以看成是一層層堆疊的步驟紀錄

第一步：畫出黑色矩形 接著把第一個狀態儲存下來(用黑色填滿)

第二步：畫出藍色矩形 接著把第二個狀態儲存下來(用藍色填滿)

第三步：畫出半透明矩形

第四步：再畫出矩形 這時候我們取用最新儲存過的藍色(第二狀態)

第五步：再畫一個矩形 我們再取出更早之前儲存的黑色(第一狀態)

| Screenshot | Live sample |
| --- | --- |
|  |  |

移動畫布
----

![](/zh-TW/docs/Web/API/Canvas_API/Tutorial/Transformations/canvas_grid_translate.png)第一個變形效果方法是 translate()。translate()是用來移動畫布，如右圖，原先畫布的原點在網格(0, 0)位置，我們可以移動畫布，使其原點移到(x, y)位置。

`translate(x, y)`

移動網格上的畫布，其中 x 代表水平距離、y 代表垂直距離。

最好在做任何變形效果前先儲存一下畫布狀態，如此當我們需要復原先前的狀態時，只需要呼叫一下 restore()即可，而且有一種情況是當我們在迴圈中移動畫布，如果不記得儲存和回復畫布狀態，繪圖區域很容易最後就超出邊界，然後出現圖案不見的狀況。

### 移動畫布範例

下面程式碼示範了利用 translate()畫圖的好處，裡面，我們用了 drawSpirograph()函數畫萬花筒類的圖案，如果沒有移動畫布原點，那麼每個圖案只會有四分之一會落在可視範圍，藉由移動畫布原點我們便可以自由變換每個圖案的位置，使圖案完整出現，而且省去手動計算調整每個圖案的座標位置。

另外一個 draw()函數透過兩個 for 迴圈移動畫布原點、呼叫 drawSpirograph()函數、復歸畫布圓點位置共九次。

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  ctx.fillRect(0, 0, 300, 300);
  for (var i = 0; i < 3; i++) {
    for (var j = 0; j < 3; j++) {
      ctx.save();
      ctx.strokeStyle = "#9CFF00";
      ctx.translate(50 + j \* 100, 50 + i \* 100);
      drawSpirograph(
        ctx,
        (20 \* (j + 2)) / (j + 1),
        (-8 \* (i + 3)) / (i + 1),
        10,
      );
      ctx.restore();
    }
  }
}

function drawSpirograph(ctx, R, r, O) {
  var x1 = R - O;
  var y1 = 0;
  var i = 1;
  ctx.beginPath();
  ctx.moveTo(x1, y1);
  do {
    if (i > 20000) break;
    var x2 =
      (R + r) \* Math.cos((i \* Math.PI) / 72) -
      (r + O) \* Math.cos(((R + r) / r) \* ((i \* Math.PI) / 72));
    var y2 =
      (R + r) \* Math.sin((i \* Math.PI) / 72) -
      (r + O) \* Math.sin(((R + r) / r) \* ((i \* Math.PI) / 72));
    ctx.lineTo(x2, y2);
    x1 = x2;
    y1 = y2;
    i++;
  } while (x2 != R - O && y2 != 0);
  ctx.stroke();
}
```

```
<canvas id="canvas" width="300" height="300"></canvas>
```

```
draw();
```

| Screenshot | Live sample |
| --- | --- |
|  |  |

旋轉
--

![](/zh-TW/docs/Web/API/Canvas_API/Tutorial/Transformations/canvas_grid_rotate.png)rotate()函數可以畫布原點作中心，旋轉畫布。

`rotate(x)`

以畫布原點為中心，順時針旋轉畫布 x 弧度(弧度 = Math.PI \* 角度 / 180)。

我們可以呼叫 translate()方法來移動旋轉中心(亦即畫布原點)。

### 旋轉範例

本範例我們呼叫 rotate()方法來畫一系列環狀圖案。如果不用 rotate()，同樣的效果也可以藉由個別計算 x, y 座標點(x = r\*Math.cos(a); y = r\*Math.sin(a))達成；呼叫 rotate()和個別計算 x, y 座標點不同之處在於，個別計算 x, y 座標點只有旋轉圓形圓心，而圓形並沒有旋轉，呼叫 rotate()則會旋轉圓形和圓心，不過因為我們的圖案是圓形，所以兩種作法產生的效果不會有差異。

我們執行了兩個迴圈來作圖，第一個迴圈決定的圓環個數和該圓環上圓環上圓點的個數的顏色，第二個迴圈決定了圓環上圓點的個數，每一次作圖前我們都儲存了原始畫布狀態，以便結束時可以復原狀態。畫布旋轉的弧度則以圓環上圓點的個數決定，像是最內圈的圓環共有六個圓點，所以每畫一個原點，畫布就旋轉 60 度(360 度/6)，第二的圓環有 12 個原點，所以畫布一次旋轉度數為 30 度(360 度/12)，以此類推。

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  ctx.translate(75, 75);

  for (var i = 1; i < 6; i++) {
    // Loop through rings (from inside to out)
    ctx.save();
    ctx.fillStyle = "rgb(" + 51 \* i + "," + (255 - 51 \* i) + ",255)";

    for (var j = 0; j < i \* 6; j++) {
      // draw individual dots
      ctx.rotate((Math.PI \* 2) / (i \* 6));
      ctx.beginPath();
      ctx.arc(0, i \* 12.5, 5, 0, Math.PI \* 2, true);
      ctx.fill();
    }

    ctx.restore();
  }
}
```

```
<canvas id="canvas" width="150" height="150"></canvas>
```

```
draw();
```

| Screenshot | Live sample |
| --- | --- |
|  |  |

縮放
--

接下來這一個變形效果為縮放圖形。

scale(x, y)

x 代表縮放畫布水平網格單位 x 倍，y 代表縮放畫布垂直網格單位 y 倍，輸入 1.0 不會造成縮放。如果輸入負值會造成座標軸鏡射，假設輸入 x 為-1，那麼原本畫布網格 X 軸上的正座標點都會變成負座標點、負座標點則變成正座標點。

只要利用 scale()，我們可以建立著名的笛卡兒座標系；執行 translate(0,canvas.height)先移動畫布原點到左下角，再執行 scale(1,-1)顛倒 Y 軸正負值，一個笛卡兒座標系便完成了。

預設上畫布網格前進一單位等於前進一像素大小，所以縮小 0.5 倍，就會變成前進 0.5 的像素大小，亦即縮小圖像一半大小，反之，放大 2 倍將放大圖像 2 倍。

### 縮放範例

本程式碼範例會畫出一系列不同縮放比例的萬花筒樣式圖案。

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  ctx.strokeStyle = "#fc0";
  ctx.lineWidth = 1.5;
  ctx.fillRect(0, 0, 300, 300);

  // Uniform scaling
  ctx.save();
  ctx.translate(50, 50);
  drawSpirograph(ctx, 22, 6, 5);

  ctx.translate(100, 0);
  ctx.scale(0.75, 0.75);
  drawSpirograph(ctx, 22, 6, 5);

  ctx.translate(133.333, 0);
  ctx.scale(0.75, 0.75);
  drawSpirograph(ctx, 22, 6, 5);
  ctx.restore();

  // Non uniform scaling (y direction)
  ctx.strokeStyle = "#0cf";
  ctx.save();
  ctx.translate(50, 150);
  ctx.scale(1, 0.75);
  drawSpirograph(ctx, 22, 6, 5);

  ctx.translate(100, 0);
  ctx.scale(1, 0.75);
  drawSpirograph(ctx, 22, 6, 5);

  ctx.translate(100, 0);
  ctx.scale(1, 0.75);
  drawSpirograph(ctx, 22, 6, 5);
  ctx.restore();

  // Non uniform scaling (x direction)
  ctx.strokeStyle = "#cf0";
  ctx.save();
  ctx.translate(50, 250);
  ctx.scale(0.75, 1);
  drawSpirograph(ctx, 22, 6, 5);

  ctx.translate(133.333, 0);
  ctx.scale(0.75, 1);
  drawSpirograph(ctx, 22, 6, 5);

  ctx.translate(177.777, 0);
  ctx.scale(0.75, 1);
  drawSpirograph(ctx, 22, 6, 5);
  ctx.restore();
}
function drawSpirograph(ctx, R, r, O) {
  var x1 = R - O;
  var y1 = 0;
  var i = 1;
  ctx.beginPath();
  ctx.moveTo(x1, y1);
  do {
    if (i > 20000) break;
    var x2 =
      (R + r) \* Math.cos((i \* Math.PI) / 72) -
      (r + O) \* Math.cos(((R + r) / r) \* ((i \* Math.PI) / 72));
    var y2 =
      (R + r) \* Math.sin((i \* Math.PI) / 72) -
      (r + O) \* Math.sin(((R + r) / r) \* ((i \* Math.PI) / 72));
    ctx.lineTo(x2, y2);
    x1 = x2;
    y1 = y2;
    i++;
  } while (x2 != R - O && y2 != 0);
  ctx.stroke();
}
```

```
<canvas id="canvas" width="300" height="300"></canvas>
```

```
draw();
```

第一排第一個黃色圖案是沒有縮放的圖案，然後往右到了第二個圖案，我們程式碼中輸入了 (0.75, 0.75)的縮放倍率，到了第三個圖案，我們還是輸入了 (0.75, 0.75)的縮放倍率，而基於之前有縮小過一次，所以第三個圖案相對於第一個沒有縮放的圖案是 0.75 × 0.75 = 0.5625 倍。

第二排藍色圖案我們只改變 Y 軸的縮放倍率，X 軸維持不變，因而產生一個比一個更扁的橢圓圖形。同理，第三排綠色圖案改變 X 軸的縮放倍率，Y 軸維持不變。

| Screenshot | Live sample |
| --- | --- |
|  |  |

變形
--

最後一個方法是設定變形矩陣，藉由改變變形矩陣，我們因此可以營造各種變形效果；其實先前所提到的 rotate, translate, scale 都是在設定變形矩陣，而這邊的這個方法就是直接去改變變形矩陣。

`transform(m11, m12, m21, m22, dx, dy)`

呼叫 Transform 會拿目前的變形矩陣乘以下列矩陣:`plain m11 m21 dx m12 m22 dy 0 0 1` 運算後的新矩陣將取代目前的變形矩陣。其中 m11 代表水平縮放圖像，m12 代表水平偏移圖像，m21 代表垂直偏移圖像，m22 代表垂直縮放圖像，dx 代表水平移動圖像，dy 代表垂直移動圖像。如果輸入`Infinity` 值，不會引起例外錯誤，矩陣值會依照輸入設成無限。

`setTransform(m11, m12, m21, m22, dx, dy)`

復原目前矩陣為恆等矩陣(Identiy matrix，也就是預設矩陣)，然後再以輸入參數呼叫 transform()。

### `transform` / `setTransform` 範例

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");

  var sin = Math.sin(Math.PI / 6);
  var cos = Math.cos(Math.PI / 6);
  ctx.translate(100, 100);
  var c = 0;
  for (var i = 0; i <= 12; i++) {
    c = Math.floor((255 / 12) \* i);
    ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
    ctx.fillRect(0, 0, 100, 10);
    ctx.transform(cos, sin, -sin, cos, 0, 0);
  }

  ctx.setTransform(-1, 0, 0, 1, 100, 100);
  ctx.fillStyle = "rgba(255, 128, 255, 0.5)";
  ctx.fillRect(0, 50, 100, 100);
}
```

```
<canvas id="canvas" width="200" height="250"></canvas>
```

```
draw();
```

| Screenshot | Live sample |
| --- | --- |
|  |  |

* « 前頁
* 次頁 »


使用canvas繪製文字 - Web APIs

使用canvas繪製文字
============

* « 前頁
* 次頁 »

`canvas`元素支援在標準 HTML 5 特色以及少許實驗性的 Mozilla 方法和功能上繪製文字。

文字可以包括任何 Unicode 字元，即使用那些超出「基本多文種平面」的字元也可以。

方法概述
----

|  |
| --- |
| `void fillText(in DOMString text, in float x, in float y, [optional] in float maxWidth);` |
| nsIDOMTextMetrics `measureText(in DOMString textToMeasure);` |
| `void mozDrawText(in DOMString textToDraw);
已棄用` |
| `float mozMeasureText(in DOMString textToMeasure);` `已棄用` |
| `void mozPathText(in DOMString textToPath);` |
| `void mozTextAlongPath(in DOMString textToDraw, in boolean stroke);` |
| `void strokeText(in DOMString text, in float x, in float y, [optional] in float maxWidth);` |

屬性
--

|  |  |  |
| --- | --- | --- |
| 屬性 | 型別 | 描述 |
| `font` | `DOMString` |
 當前的文字樣式被用在繪製文字。該字串使用和
 CSS font（樣式表字型）相同的語法。要改變繪製文字的樣式，只要簡單的改變它的屬性值即可，就像下面展示的，預設的字型是10px（像素）
 sans-serif（字型名稱）

例如:
```

ctx.font = "20pt Arial";
```
 |
| `mozTextStyle`
已棄用
 | `DOMString` | 由上面的Html5`字型` 屬性取代 |
| `textAlign` | `DOMString` | 當前繪製文字所使用的文字對齊方式。 可使用的值：

left
文字靠左對齊。
right
文字靠右對齊。
center
文字置中對齊。
start

 文字依照行首對齊
 (書寫習慣由左到右的地區就靠左對齊，書寫習慣由右到左的就靠右對齊。).

end
文字依照行尾對齊（書寫習慣由左到右的地區就靠右對齊，書寫習責由右到左的地區就靠左對齊。）

預設的值是 `start`. |
| `textBaseline` | `DOMString` | 當前繪製文字的基線位置 可使用的值：

top
基線在字元區塊的頂部（圖中top of the squre位置）。
hanging（懸掛）

 文字基線在拼音文字頂部的位置（圖中hanging baseline）
 *當前仍未支援；會顯示 **alphabetic**代替。*

middle
文字基線在字元區塊的中間。
alphabetic（拼音文字）
這是一般拼音文字底線的位置。
ideographic（表意文字）

 文字在表意文字（如漢字）底部的位置
 *當前仍未支援；會顯示**alphabetic**代替。*

bottom

 基線在拼音文字下伸部的位置
 這與ideographic的基線位置不同，因為表意文字沒有下伸部

預設使用 `alphabetic`. |

下圖展示了 textBaseline 屬性所支援的各種基線，感謝 WHATWG.

![top of em squre（字元區塊頂部）大致在字型中所有字母的最頂部位置，hanging basline（懸掛基線）則是在一些特殊（較小的,像是「आ」）字母頂部，middle則是在top of em squre（字元區塊頂部和bottom of em squre（字元區塊底部）的中間，alphabetic（拼音文字）的基線位置則是在一般拼音字母如Á，ÿ，f，Ω的底線位置。ideographic（表意文字）的基線在字元的底部位置，bottom of em squre（字元區塊底部）則大致是字型中所有字母的最底部位置。而top and bottom of the bounding box（上下的區域範圍線）則比這些基線都來得更遠，基於字母的高度可能超過字元區塊頂部和底部的範圍。](http://www.whatwg.org/specs/web-apps/current-work/images/baselines.png)

方法
--

### fillText()

繪製文字使用`font`屬性指定的文字樣式，對齊則使用`textAlign`屬性，而指定基線則使用`textBaseline`. 填充文字當前使用`fillStyle`，而`strokeStyle`則被忽略

**備註：** 這個方法在 Gecko 1.9.1 (Firefox 3.5)時引進，且是 HTML 5 標準的一部分.
```

void fillText(
   in DOMString textToDraw,
   in float x,
   in float y,
   [optional] in float maxWidth
);
```

##### 參數

`textToDraw`

將文字繪製到文本中。

`x`

繪製位置的 x 座標。

`y`

繪製位置的 y 座標。

`maxWidth`

最大寬度，可選用的；繪製字串最大長度 如果指定此參數，當字串被計算出比這個值更寬，它會自動選擇水平方向更窄的字型（如果有可用的字型或是有可讀的字型可以嵌入當前字型之中），或者縮小字型。

##### 範例

```js
ctx.fillText("Sample String", 10, 50);
```

### measureText()

測量文字。返回一個物件包含了寬度，像素值，所指定的文字會以當前的文字樣式繪製。

**備註：** 這個方法在 Gecko 1.9.1 (Firefox 3.5) 引進，且是 HTML 5 標準的一部分。
```

nsIDOMTextMetrics measureText(
  in DOMString textToMeasure
);
```

#### 參數

`textToMeasure`

該字串的像素值。

#### 返回值

`nsIDOMTextMetrics`物件的`width`屬性在繪製時會將數字設定給 CSS 的像素值寬度。

### mozDrawText()

**已棄用:** 不推薦使用此功能。雖可能有一些瀏覽器仍然支援它，但也許已自相關的網頁標準中移除、正準備移除、或僅為了維持相容性而保留。避免使用此功能，盡可能更新現有程式；請參考頁面底部的相容性表格來下決定。請注意：本功能可能隨時停止運作。

繪製文字使用由`mozTextStyle`屬性的文字樣式。文本當前的填充顏色被用來當做文字顏色。

**備註：** 這個方法已經不被建議使用,請使用正式的 HTML 5 方法 `fillText()` and `strokeText()`.
```

void mozDrawText(
   in DOMString textToDraw
);
```

#### 參數

`textToDraw`

將文字繪製到文本。

#### 範例

```js
ctx.translate(10, 50);
ctx.fillStyle = "Red";
ctx.mozDrawText("Sample String");
```

這個範例將文字「Sample String」繪製到畫布（canvas）上。

### mozMeasureText()

**已棄用:** 不推薦使用此功能。雖可能有一些瀏覽器仍然支援它，但也許已自相關的網頁標準中移除、正準備移除、或僅為了維持相容性而保留。避免使用此功能，盡可能更新現有程式；請參考頁面底部的相容性表格來下決定。請注意：本功能可能隨時停止運作。

返回寬度，像素值，指定文字

**備註：** 這個方法已經已宣告棄用，請使用正式的 HTML 5 方法`measureText()`.
```

float mozMeasureText(
  in DOMString textToMeasure
);
```

#### 參數

`textToMeasure`

字串的寬度像素值

#### 返回值

文字的寬度像素值

#### 範例

```js
var text = "Sample String";
var width = ctx.canvas.width;
var len = ctx.mozMeasureText(text);
ctx.translate((width - len) / 2, 0);
ctx.mozDrawText(text);
```

這個範例測量了字串的寬度，接著使用這個資訊將它畫在畫布（canvas）的水平中心。

### mozPathText()

給文字路徑加上外框線，如果你想要的話，它允許你替文字加上框線代替填充它。
```

void mozPathText(
  in DOMString textToPath
);
```

#### 參數

`textToPath`

為當前的文字路徑加上框線

#### Example

```js
ctx.fillStyle = "green";
ctx.strokeStyle = "black";
ctx.mozPathText("Sample String");
ctx.fill();
ctx.stroke();
```

這個範例繪出文字「Sample String」，填充顏色是綠色，外框顏色是黑色。

### mozTextAlongPath()

Adds (or draws) the specified text along the current path.
```

void mozTextAlongPath(
  in DOMString textToDraw,
  in boolean stroke
);
```

#### 參數

`textToDraw`

沿著指定路徑繪出文字

`stroke`

如果參數是 `true`（真值），文字會沿著指定路徑繪製。如果 `false`（假值），這個文字則會加入到路徑之中，再沿著當前路徑繪製。

#### 備註

字體不會沿著路徑曲線縮放或變形，反而在彎曲路徑下，字體每次計算都會當成是直線在處理。這可以用來建立一些特殊的效果。

### strokeText()

繪製文字使用`font`屬性指定的文字樣式，對齊則使用`textAlign`屬性，而指定基線則使用`textBaseline`. 當前使用`strokeStyle`來建立文字外框。

**備註：** 這個方法在 Gecko 1.9.1 (Firefox 3.5)時引進，且是 HTML 5 標準的一部分。
```

void strokeText(
   in DOMString textToDraw,
   in float x,
   in float y,
   [optional] in float maxWidth
);
```

##### 參數

`textToDraw`

將文字繪製到文本中。

`x`

繪製位置的 x 座標。

`y`

繪製位置的 y 座標

`maxWidth`

最大寬度，可選用的；繪製字串最大長度 如果指定此參數，當字串被計算出比這個值更寬，它會自動選擇水平方向更窄的字型（如果有可用的字型或是有可讀的字型可以嵌入當前字型之中），或者縮小字型。

##### 範例

```js
ctx.strokeText("Sample String", 10, 50);
```

備註
--

* 請見 WHATWG specification 關於 HTML 5 canvas text 的說明。
* 你不需要特別的文本來使用這些功能；2D 的文本就可以執行得很好。
* 所有的繪圖都使用即時變化來完成。


最佳化canvas - Web APIs

最佳化canvas
=========

`<canvas>`在網頁 2D 繪圖上被大量運用於遊戲和複雜視覺化效果上。隨著繪圖複雜度越來越高，效能問題也會逐一浮現，所以最後我們在這邊列出一些最佳化畫布的方法，避免一些效能問題:

* 在畫面之外的畫布上先行渲染相似或重複的物件
* 批次性執行繪圖，例如一次畫數個線條而非分開一次一次畫
* 使用整數位座標值，盡量避免小數點值
* 避免不必要的畫布狀態改變
* 只渲染不同處，不要全部重新渲染
* 對於複雜需求可以利用多個畫布分層構成
* 盡量不要用 shadowBlur 屬性
* 使用`window.requestAnimationFrame()`
* 用JSPerf測試效能
* « 前頁 (en-US)


合成效果 - Web APIs

合成效果
====

* « 前頁
* 次頁 »

在前述的範例中，新繪製的圖形總會覆蓋在之前的圖形上，對大多數情況來說這相當正常，不過它也限制了圖形繪製的順序。其實我們可以透過 globalCompositeOperation 屬性來改變這項預設行為。

`globalCompositeOperation`
--------------------------

利用 globalCompositeOperation，我們可以將新圖形繪製在舊圖形之下、遮蓋部分區域、清除畫布部分區域 (不同於 clearRect() 函式只能清除矩形區域)。

`globalCompositeOperation = type`

type 字串可指定為以下 12 種合成設定之一，每一種合成設定均將套用到新繪製的圖形上。

See compositing examples (en-US) for the code of the following examples.

裁剪路徑
----

![](/zh-TW/docs/Web/API/Canvas_API/Tutorial/Compositing/canvas_clipping_path.png)裁剪路徑就像是一般畫布圖形繪圖，但就如同遮罩一樣，會蓋掉不需要的部分，如右圖所示。紅邊星星是我們的裁剪路徑，在路徑區域以外部分都不會出現在畫布上。

和上述 globalCompositeOperation 相比，可以發現 source-in 和 source-atop 這兩種構圖組合所達到的效果，和裁剪路徑類似，而其中最大差異在於裁剪路徑不需加入新圖形，消失的部分也不會出現在畫布上，所以，如果想要限定繪圖區域，裁剪路徑會是更理想的作法。

在繪畫圖形一章中，我們只提到 stroke() 和 fill() 函式，但其實還有第三個函式，那就是 clip() 函式。

`clip()`

轉換目前繪圖路徑為裁剪路徑。

呼叫 clip() 除了會替代 closePath() 來關閉路徑之外，還會轉換目前填滿或勾勒繪圖路徑為裁剪路徑。

`<canvas>` 畫布預設有一個等同於本身大小的裁剪路徑，等同於無裁剪效果。

### `clip` 範例

本範例使用了圓形的裁剪路徑，來限定畫星星時的繪圖區域。

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  ctx.fillRect(0, 0, 150, 150);
  ctx.translate(75, 75);

  // Create a circular clipping path
  ctx.beginPath();
  ctx.arc(0, 0, 60, 0, Math.PI \* 2, true);
  ctx.clip();

  // draw background
  var lingrad = ctx.createLinearGradient(0, -75, 0, 75);
  lingrad.addColorStop(0, "#232256");
  lingrad.addColorStop(1, "#143778");

  ctx.fillStyle = lingrad;
  ctx.fillRect(-75, -75, 150, 150);

  // draw stars
  for (var j = 1; j < 50; j++) {
    ctx.save();
    ctx.fillStyle = "#fff";
    ctx.translate(
      75 - Math.floor(Math.random() \* 150),
      75 - Math.floor(Math.random() \* 150),
    );
    drawStar(ctx, Math.floor(Math.random() \* 4) + 2);
    ctx.restore();
  }
}

function drawStar(ctx, r) {
  ctx.save();
  ctx.beginPath();
  ctx.moveTo(r, 0);
  for (var i = 0; i < 9; i++) {
    ctx.rotate(Math.PI / 5);
    if (i % 2 == 0) {
      ctx.lineTo((r / 0.525731) \* 0.200811, 0);
    } else {
      ctx.lineTo(r, 0);
    }
  }
  ctx.closePath();
  ctx.fill();
  ctx.restore();
}
```

```
<canvas id="canvas" width="150" height="150"></canvas>
```

```
draw();
```

一開始我們先畫了一個黑色矩形作為畫布背景，然後移動畫布原點到中央，接著我們繪製弧線並呼叫 clip()，藉以建立圓形的裁剪路徑。畫布儲存狀態亦可儲存裁剪路徑。若要保留原本的裁剪路徑，則可於繪製新的裁剪路徑之前，先行儲存畫布狀態。

繪製裁剪路徑之後，所產生的所有圖形都只會出現在路徑以內，從後來繪製的漸層背景中可看出此特性。我們用自訂的 drawStar() 函數產生 50 個隨機散佈、大小不一的星星。這些星星同樣只會出現在裁剪路徑的範圍之內。

| Screenshot | Live sample |
| --- | --- |
|  |  |

* « 前頁
* 次頁 » (en-US)


Canvas 基本用途 - Web APIs

Canvas 基本用途
===========

* « 前頁
* 次頁 »

Let's start this tutorial by looking at the `<canvas>` HTML element itself. At the end of this page, you will know how to set up a canvas 2D context and have drawn a first example in your browser.

`<canvas>` 元素
-------------

```html
<canvas id="tutorial" width="150" height="150"></canvas>
```

首先，先來看看 `<canvas>`，它看起來有點像 `<img>` (en-US) 元素，其中的差異點在於 `<canvas>` 沒有 `src` 和 `alt` 屬性，`<canvas>` 只有 `width` 與 `height` 這兩個屬性，這兩個屬性皆為非必須、能透過 DOM 屬性設定；若是沒有設定 `width` 和 `height` 屬性，畫布寬預設值為 **300 pixels**、高預設值為 **150 pixels**，我們可以用 CSS 強制設定元素尺寸，但當渲染時，影像會縮放以符合元素的尺寸。

**備註：** 如果繪圖結果看起來有些扭曲，可以改試著用<canvas>自身的 width 和 height 屬性而不要用 CSS 來設定寬高。

幾乎所有 HTML 元素都有 id 屬性，<canvas>也不例外，為了方便於程式碼腳本找到需要的<canvas>，每次都設定 id 是一項不錯的作法。

如同一般的影像可以設定如邊界(margin)、邊框(border)、背景(background)等等，<canvas>元素一樣可以設定這些樣式，然而，這些樣式規則不會影響 canvas 實際繪圖，稍後我們會看到相關範例。當沒有套用樣式規定時，<canvas>會被初始成全透明。

### 錯誤替代內容(Fallback content)

因為舊版瀏覽器(特別是 IE9 之前的 IE)不支援{<canvas>}元素，我們應該為這些瀏覽器準備錯誤替代內容。

當不支援<canvas>的瀏覽器看到不認識的<canvas>時會忽略<canvas>，而此時在<canvas>下瀏覽器認識的替代內容則會被瀏覽器解析顯示，至於支援<canvas>的瀏覽器則是會正常解析<canvas>，忽略替代內容。

例如，我們可以準備一段 canvas 內容的說明文字或 canvas 繪圖完成後的靜態圖片，如下所示:

```html
<canvas id="stockGraph" width="150" height="150">
  current stock price: $3.15 +0.15
</canvas>

<canvas id="clock" width="150" height="150">
  <img src="images/clock.png" width="150" height="150" alt="" />
</canvas>
```

需要</canvas>標籤
-------------

不像`<img>` (en-US)元素，`<canvas>`元素必須要有</canvas>結束標籤。

**備註：** 縱使早期 Apple 的 Safari 瀏覽器不需要結束標籤，但是基於規範，這是必須的，所以，為了相容性考量，應該要有結束標籤。Safari 2.0 以前的版本會同時解析 canvas 以及替代內容，除非我們用 CSS 去遮蓋內容，不過幸運的是，現在已經沒有甚麼人在用這些舊版 Safari。

如果不需要錯誤替代內容，簡單的<canvas id="foo" ...></canvas>便可以完全相容於所有支援的瀏覽器。

渲染環境(rendering context)
-----------------------

`<canvas>`產生一個固定大小的繪圖畫布，這個畫布上有一或多個渲染環境(rendering context)，我們可以用渲染環境來產生或操作顯示內容的渲染環境(rendering context)。不同環境(context)可能會提供不同型態的渲染方式，好比說WebGL (en-US)使用OpenGL ES的 3D 環境(context)，而這裡我們主要將討論 2D 渲染環境(rendering context)。

一開始 canvas 為空白，程式碼腳本需要先存取渲染環境，在上面繪圖，然後才會顯現影像。`<canvas>` 素有一個方法 (en-US)叫 getContext()，透過此方法可以取得渲染環境及其繪圖函數（function）；getContext() 輸入參數只有渲染環境類型一項，像本教學所討論的 2D 繪圖，就是輸入「2d」。

```js
var canvas = document.getElementById("tutorial");
var ctx = canvas.getContext("2d");
```

上面第一行先呼叫`document.getElementById()` (en-US)來取得`<canvas>`元素，一旦取得元素後，便可以用其 getContext()取得渲染環境。

支援性檢查
-----

替代內容會被不支援`<canvas>`.的瀏覽器所顯示。程式碼腳本也可以利用檢查 getContext()方法是否存在來檢查是否支援<canvas>，我們可以修改上面例子成如下:

```js
var canvas = document.getElementById("tutorial");

if (canvas.getContext) {
  var ctx = canvas.getContext("2d");
  // drawing code here
} else {
  // canvas-unsupported code here
}
```

一個範本
----

這裡是一個最簡單的範本，之後就是我們範例的起始點。

```html
<html>
  <head>
    <title>Canvas tutorial</title>
    <script type="text/javascript">
 function draw() {
 var canvas = document.getElementById("tutorial");
 if (canvas.getContext) {
 var ctx = canvas.getContext("2d");
 }
 }
 </script>
    <style type="text/css">
 canvas {
 border: 1px solid black;
 }
 </style>
  </head>
  <body onload="draw();">
    <canvas id="tutorial" width="150" height="150"></canvas>
  </body>
</html>
```

一旦網頁載入完成後，程式碼會呼叫 draw()函數(這是利用 document 上的 load 事件完成)，這類 draw()函數也可以透過`window.setTimeout()` (en-US), `window.setInterval()` (en-US)或其他事件處理函數來呼叫，只要呼叫的時間點是在網頁載入完後。

這是我們的範本實際看起來的樣子:

一個簡單的範例
-------

首先，讓我們先來畫兩個相交的正方形，其中一個正方形有 alpha 透明值，之後我們會說明這是如何達成的。

```html
<html>
  <head>
    <script type="application/javascript">
 function draw() {
 var canvas = document.getElementById("canvas");
 if (canvas.getContext) {
 var ctx = canvas.getContext("2d");

 ctx.fillStyle = "rgb(200,0,0)";
 ctx.fillRect(10, 10, 55, 50);

 ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
 ctx.fillRect(30, 30, 55, 50);
 }
 }
 </script>
  </head>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

本範例的結果如下:

| Screenshot | Live sample |
| --- | --- |
|  |  |

* « 前頁
* 次頁 »


繪製圖形 - Web APIs

繪製圖形
====

* « 前頁
* 次頁 »

網格(Grid)

![](/zh-TW/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes/canvas_default_grid.png)在開始繪圖前，我們必須先了解畫布 (canvas) 網格，或著是說座標空間。在前一頁教學中的 HTML 範本有一個寬 150 pixels (像素)、高 150 pixels 的畫布。如右圖，你在畫布預設網格上繪圖，網格上 1 單位相當於畫布上 1 pixel，網格的原點 (座標 (0, 0) ) 坐落於左上角，所有元素定位皆相對於此左上角原點，所以藍色方塊的位置為從左往右推 x pixels、從上往下推 y pixels (亦即座標 (x, y) )。現在我們先專注在預設設定上，之後我們會看到如何轉換原點位置、旋轉網格以及縮放網格。

畫矩形
---

不同於SVG，`<canvas>`只支援一種原始圖形，矩形。所有的圖形都必須由一或多個繪圖路徑構成，而我們正好有一些繪圖路徑函數可以讓我們畫出複雜的圖形。

首先來看看矩形，共有三個矩形繪圖函數:

`fillRect(x, y, width, height)`

畫出一個填滿的矩形。

`strokeRect(x, y, width, height)`

畫出一個矩形的邊框

`clearRect(x, y, width, height)`

清除指定矩形區域內的內容，使其變為全透明。

這三個函數都接受一樣的參數: x, y 代表從原點出發的座標位置，width, height 代表矩形的寬高。

### 矩形範例
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    ctx.fillRect(25, 25, 100, 100);
    ctx.clearRect(45, 45, 60, 60);
    ctx.strokeRect(50, 50, 50, 50);
  }
}
```

本例結果如下:

| Screenshot | Live sample |
| --- | --- |
|  |  |

fillRect()函數畫出一個寬高都 100 pixels 的矩形，clearRect()函數清除中央 60 x 60 pixels 大的正方形區域，接著 strokeRect()在被清除區域內畫上一個 50 x 50 pixels 的矩形邊框。

之後我們會看到另外兩種代替 clearRect()的方法，還有如何改變圖形顏色與筆畫樣式。

不像之後會看到的路徑繪圖函數，這三個函數會立即在畫布上畫出矩形。

路徑繪製
----

使用路徑 (path) 來畫圖形需要多一點步驟，一開始先產生路徑，然後用繪圖指令畫出路徑，然後再結束路徑，一旦路徑產生後便可以用畫筆或填滿方式來渲染生成，這裡是一些可用函數:

`beginPath()` (en-US)

產生一個新路徑，產生後再使用繪圖指令來設定路徑。

`closePath()` (en-US)

閉合路徑好讓新的繪圖指令來設定路徑。

路徑 API (en-US)

路徑 API，這些 API 便是繪圖指令

`stroke()` (en-US)

畫出圖形的邊框。

`fill()` (en-US)

填滿路徑內容區域來產生圖形。

第一步呼叫 beginPath() 產生一個路徑，表面下，路徑會被存在一個次路徑 (sub-path) 清單中，例如直線、曲線等，這些次路徑集合起來就形成一塊圖形。每一次呼叫這個方法，次路徑清單就會被重設，然後我們便能夠畫另一個新圖形。

**備註：** 當目前路徑為空(例如接著呼叫 beginPath()完後)或是在一個新畫布上，不論為何，第一個路徑繪圖指令總是 moveTo()；因為每當重設路徑後，你幾乎都會需要設定繪圖起始點。

第二步是呼叫各式方法來實際設定繪圖路徑，稍後我們將會介紹這部分。

第三步，也是非必要的一步，就是呼叫 closePath()。這個方法會在現在所在點到起始點間畫一條直線以閉合圖形，如果圖形已經閉合或是只含一個點，這個方法不會有任何效果。

**備註：** 當呼叫 fill()，任何開放的圖形都會自動閉合，所以不需要再呼叫 closePath()，但是 stroke()並非如此。

### 畫一個三角形

這是一個畫出三角形的程式碼範例。
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.moveTo(75, 50);
    ctx.lineTo(100, 75);
    ctx.lineTo(100, 25);
    ctx.fill();
  }
}
```

結果如下:

| Screenshot | Live sample |
| --- | --- |
|  |  |

### 移動畫筆

moveTo()是一個很有用的函數，moveTo()不會畫任何圖形，但卻是上述路徑清單的一部分，這大概有點像是把筆從紙上一點提起來，然後放到另一個點。

`moveTo(x, y)` (en-US)

移動畫筆到指定的(x, y)座標點

當初始化畫布或是呼叫 beginPath()，通常會想要使用 moveTo()來指定起始點，我們可以用 moveTo()畫不連結的路徑，看一下笑臉圖範例，圖中紅線即為使用到 moveTo()的位置。

你可以拿下面的程式碼，放進先前的 draw()函數，自己試試看效果。
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.arc(75, 75, 50, 0, Math.PI \* 2, true); // Outer circle
    ctx.moveTo(110, 75);
    ctx.arc(75, 75, 35, 0, Math.PI, false); // Mouth (clockwise)
    ctx.moveTo(65, 65);
    ctx.arc(60, 65, 5, 0, Math.PI \* 2, true); // Left eye
    ctx.moveTo(95, 65);
    ctx.arc(90, 65, 5, 0, Math.PI \* 2, true); // Right eye
    ctx.stroke();
  }
}
```

結果如下:

| Screenshot | Live sample |
| --- | --- |
|  |  |

移除 moveTo() 便可以看到線條連結起來。

**備註：** 有關 arc()，請參照下方弧形。

### 線條

用 lineTo() 方法畫直線。

`lineTo(x, y)` (en-US)

從目前繪圖點畫一條直線到指定的(x, y)座標點。

本方法接受 x, y 參數作為線條結束點的座標位置，至於起始點則視前一個繪圖路徑，由前一個繪圖路徑的結束點作為起始點，當然，起始點也可以用 moveTo()方法來變更。

下面畫兩個三角形，一個填滿，一個空心。
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    // Filled triangle
    ctx.beginPath();
    ctx.moveTo(25, 25);
    ctx.lineTo(105, 25);
    ctx.lineTo(25, 105);
    ctx.fill();

    // Stroked triangle
    ctx.beginPath();
    ctx.moveTo(125, 125);
    ctx.lineTo(125, 45);
    ctx.lineTo(45, 125);
    ctx.closePath();
    ctx.stroke();
  }
}
```

從呼叫 beginPath()起始一個新圖形路徑，然後用 moveTo()移到我們想要的起始點，然後再畫兩條線形成三角形的兩邊。

| Screenshot | Live sample |
| --- | --- |
|  |  |

我們可以看到填滿(fill)三角形和勾勒(stroke)三角形的區別；當填滿時，圖形會自動閉合，不過勾勒則不會，所以如果沒有呼叫 closePaht()的話，只會畫出兩條線而非三角形。

### 弧形

用 arc()方法來畫弧形或圓形。雖然也可以用 arcTo()，但這個方法比較不可靠，所以這裡我們不討論 arcTo()。

`arc(x, y, radius, startAngle, endAngle, anticlockwise)` (en-US)

畫一個弧形

本方法接受五個參數: x, y 代表圓心座標點，radius 代表半徑，startAngle, endAngle 分別代表沿著弧形曲線上的起始點與結束點的弧度，弧度測量是相對於 x 軸，anticlockwise 為 true 代表逆時針作圖、false 代表順時針作圖。

**備註：** arc()方法用的是弧度(radians)而非角度(degrees)，如果要在弧度與角度間換算，可以利用以下 javascript 程式碼: radians = (Math.PI/180) \* degrees.

以下例子比較複雜，它會畫出 12 個不同的弧形。

兩個 for 迴圈走一遍弧形圖列的列跟行，每一個弧形由呼叫 beginPath()開始新的繪圖路徑，為了清楚，我們在程式範例中用變數儲存參數，你不一定要這麼做。

x, y 座標點的部分應該相當淺顯，radius 和 startAngle 是定值，endAngle 從 180 度(半圓)開始，然後每一行增加 90 度，最後一行便會形成一個完整的圓。

第 1, 3 列的 anticlockwise 為 false，所以會順時針作圖，2, 4 列的 anticlockwise 為 true，所以會逆時針作圖。最後的 if 決定下半部是用填滿圖形，上半部是勾勒圖形。
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="200"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    for (var i = 0; i < 4; i++) {
      for (var j = 0; j < 3; j++) {
        ctx.beginPath();
        var x = 25 + j \* 50; // x coordinate
        var y = 25 + i \* 50; // y coordinate
        var radius = 20; // Arc radius
        var startAngle = 0; // Starting point on circle
        var endAngle = Math.PI + (Math.PI \* j) / 2; // End point on circle
        var anticlockwise = i % 2 == 0 ? false : true; // clockwise or anticlockwise

        ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

        if (i > 1) {
          ctx.fill();
        } else {
          ctx.stroke();
        }
      }
    }
  }
}
```

| Screenshot | Live sample |
| --- | --- |
|  |  |

### 貝茲曲線(Bezier curve)與二次曲線(quadratic curve)

二次與三次貝茲曲線(Bézier curves)是另一種可用來構成複雜有機圖形的路徑。

`quadraticCurveTo(cp1x, cp1y, x, y)` (en-US)

從目前起始點畫一條二次貝茲曲線到 x, y 指定的終點，控制點由 cp1x, cp1y 指定。

`bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)` (en-US)

從目前起始點畫一條三次貝茲曲線到 x, y 指定的終點，控制點由(cp1x, cp1y)和(cp2x, cp2y)指定。

![](/zh-TW/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes/canvas_curves.png)二次和三次的差別可以從右圖看出；貝茲曲線的起始和終點以藍點標示，其中二次貝茲曲線只有一個控制點(如紅點標示)而三次貝茲曲線有兩個控制點。

二次和三次貝茲曲線都用 x, y 參數定義終點座標，然後用 cp1x, xp1y 定義第一個控制點座標、cp2x, xp2y 定義第二個控制點座標。

用二次和三次貝茲曲線作圖相當具有挑戰性，因為不像使用 Adobe illustrator 的向量繪圖軟體，我們在繪圖時無法即時看到繪圖狀況，所以畫複雜的圖形十分困難。下面的範例我們畫了一些圖形，如果你有時間與耐心，可以畫出更複雜的圖形。

#### 二次貝茲曲線

本例用了數個二次貝茲曲線畫了一個會話框。
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    // Quadratric curves example
    ctx.beginPath();
    ctx.moveTo(75, 25);
    ctx.quadraticCurveTo(25, 25, 25, 62.5);
    ctx.quadraticCurveTo(25, 100, 50, 100);
    ctx.quadraticCurveTo(50, 120, 30, 125);
    ctx.quadraticCurveTo(60, 120, 65, 100);
    ctx.quadraticCurveTo(125, 100, 125, 62.5);
    ctx.quadraticCurveTo(125, 25, 75, 25);
    ctx.stroke();
  }
}
```

| Screenshot | Live sample |
| --- | --- |
|  |  |

#### 三次貝茲曲線

這個範例畫了一個愛心。
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    // Quadratric curves example
    ctx.beginPath();
    ctx.moveTo(75, 40);
    ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
    ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
    ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
    ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
    ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
    ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
    ctx.fill();
  }
}
```

| Screenshot | Live sample |
| --- | --- |
|  |  |

### 矩形

除了在{畫矩形}段落中提到的三個方法，還有 rect()方法能夠在畫布上畫矩形；rect()方法會在目前路徑下加入一個矩形繪圖路徑。

`rect(x, y, width, height)` (en-US)

畫一個左上角位於(x, y)、寬 width、高 height 的矩形。

呼叫這個方法，moveTo()方法會以(0, 0)參數被自動呼叫，所以目前的下筆點跟者自動被設為預設座標。

### 多樣組合

截至目前為止，我們都只用一種路徑函數在各個範例裡作圖，不過，其實繪圖時並沒有任何使用數量或種類上的路徑函數限制，所以最後我們來試著組合各樣路徑繪圖函數來畫一些十分有名的遊戲角色。
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    roundedRect(ctx, 12, 12, 150, 150, 15);
    roundedRect(ctx, 19, 19, 150, 150, 9);
    roundedRect(ctx, 53, 53, 49, 33, 10);
    roundedRect(ctx, 53, 119, 49, 16, 6);
    roundedRect(ctx, 135, 53, 49, 33, 10);
    roundedRect(ctx, 135, 119, 25, 49, 10);

    ctx.beginPath();
    ctx.arc(37, 37, 13, Math.PI / 7, -Math.PI / 7, false);
    ctx.lineTo(31, 37);
    ctx.fill();

    for (var i = 0; i < 8; i++) {
      ctx.fillRect(51 + i \* 16, 35, 4, 4);
    }

    for (i = 0; i < 6; i++) {
      ctx.fillRect(115, 51 + i \* 16, 4, 4);
    }

    for (i = 0; i < 8; i++) {
      ctx.fillRect(51 + i \* 16, 99, 4, 4);
    }

    ctx.beginPath();
    ctx.moveTo(83, 116);
    ctx.lineTo(83, 102);
    ctx.bezierCurveTo(83, 94, 89, 88, 97, 88);
    ctx.bezierCurveTo(105, 88, 111, 94, 111, 102);
    ctx.lineTo(111, 116);
    ctx.lineTo(106.333, 111.333);
    ctx.lineTo(101.666, 116);
    ctx.lineTo(97, 111.333);
    ctx.lineTo(92.333, 116);
    ctx.lineTo(87.666, 111.333);
    ctx.lineTo(83, 116);
    ctx.fill();

    ctx.fillStyle = "white";
    ctx.beginPath();
    ctx.moveTo(91, 96);
    ctx.bezierCurveTo(88, 96, 87, 99, 87, 101);
    ctx.bezierCurveTo(87, 103, 88, 106, 91, 106);
    ctx.bezierCurveTo(94, 106, 95, 103, 95, 101);
    ctx.bezierCurveTo(95, 99, 94, 96, 91, 96);
    ctx.moveTo(103, 96);
    ctx.bezierCurveTo(100, 96, 99, 99, 99, 101);
    ctx.bezierCurveTo(99, 103, 100, 106, 103, 106);
    ctx.bezierCurveTo(106, 106, 107, 103, 107, 101);
    ctx.bezierCurveTo(107, 99, 106, 96, 103, 96);
    ctx.fill();

    ctx.fillStyle = "black";
    ctx.beginPath();
    ctx.arc(101, 102, 2, 0, Math.PI \* 2, true);
    ctx.fill();

    ctx.beginPath();
    ctx.arc(89, 102, 2, 0, Math.PI \* 2, true);
    ctx.fill();
  }
}

// A utility function to draw a rectangle with rounded corners.

function roundedRect(ctx, x, y, width, height, radius) {
  ctx.beginPath();
  ctx.moveTo(x, y + radius);
  ctx.lineTo(x, y + height - radius);
  ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
  ctx.lineTo(x + width - radius, y + height);
  ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
  ctx.lineTo(x + width, y + radius);
  ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
  ctx.lineTo(x + radius, y);
  ctx.quadraticCurveTo(x, y, x, y + radius);
  ctx.stroke();
}
```

結果如下:

畫出這樣的圖其實沒有想像中的困難，所以我們就不再描述細節了，其中比較需要注意的是，我們在繪圖環境上用了 fillStyle 屬性以及一個自定義的效用函數(roundedRect())，利用效用函數來執行時常重複的繪圖工作可以幫忙減少程式碼數量與複雜度。

稍後我們會更進一步介紹 fillStyle 屬性，這個範例我們所做是的透過 fillStyle 屬性來改變路徑填滿色為白色，然後再改回預設黑色，來變換填滿顏色，。

* « 前頁
* 次頁 »
Path2D objects
--------------

如同前面的範例，canvas 上常常會畫上一連串的繪圖路徑，為了簡化程式碼還有改善效能，我們可以利用 `Path2D` (en-US) 物件 (目前在較先進的瀏覽器上已經有提供了)。Path2D 讓我們可以快取和記錄繪圖指令，方便快速重複地繪圖，底下我就來看看如何使用 Path2D :

`Path2D()` (en-US)
Path2D 的建構子，可接受的參數有無參數、另一個 Path2D 物件、 字元表式的 SVG path:

```js
new Path2D(); // 不傳入參數會回傳一個空的 Path2D 物件
new Path2D(path); // 複製傳入的 Path2D 物件，然後以之建立 Path2D 物件
new Path2D(d); // 以傳入的 SVG 路徑建立 Path2D 物件
```

所有已知的 路徑 API (en-US)，如 rect, arc 等等，都可以在 Path2D 上找到。

Path2D 物件還可以加入其他 Path2D 物件，這讓我們可以很方便的組合多個物件使用。

`Path2D.addPath(path [, transform])` (en-US)
addPath 增加一個 Path2D 物件，其中的非必要參數是變形矩陣。

### Path2D example

這個例子用 Path2D 物件將矩形和圓形的繪圖路徑存起來，以供之後使用。而配合新的 Path2D API，有一些繪圖方法更接受傳入 Path2D 作為繪圖路徑使用，例如下方本例所用到的 stroke 和 fill。

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    var rectangle = new Path2D();
    rectangle.rect(10, 10, 50, 50);

    var circle = new Path2D();
    circle.moveTo(125, 35);
    circle.arc(100, 35, 25, 0, 2 \* Math.PI);

    ctx.stroke(rectangle);
    ctx.fill(circle);
  }
}
```

| Screenshot | Live sample |
| --- | --- |
|  |  |

### 使用向量路徑 (SVG paths)

另一個強而有力的特色是在 SVG 和 Canvas 中我們都可以使用 SVG path。

下面的路徑會移到座標點 (10, 10) (M10, 10)，然後水平右移 80 點 (h 80)，垂至下移 80 點 (v 80) 水平左移 80 點 (h -80) 最後回到起始點 (z)，請到`Path2D` 建構子頁面 (en-US)看繪圖範例結果。

```js
var p = new Path2D("M10 10 h 80 v 80 h -80 Z");
```

* « 前頁
* 次頁 »


Advanced animations - Web APIs

Advanced animations
===================

* « 前頁
* 次頁 »

在上一章節，我們做了一些基礎動畫且知道它的移動方式。在這部分我們更仔細的介紹它的動畫效果且並增加一些特效，使它看起來更高級。

畫一顆球
----

在這次的動畫練習中使用球來練習。照著下面的步驟完成 canvas 設定。

```html
<canvas id="canvas" width="600" height="300"></canvas>
```

照常理，先在 canvas 上需要先畫一顆球。創造一個 `ball` object，它包含的屬性和`draw()`的方法，使 canvas 可以在上面繪圖。

```js
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

var ball = {
  x: 100,
  y: 100,
  radius: 25,
  color: "blue",
  draw: function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI \* 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};

ball.draw();
```

這裡沒什麼特別的，透過`arc()` (en-US)的方法，球事實上只是畫下簡單的圓。

添加速度
----

現在有了一顆球，準備添加基礎的動畫像我們從上章節學到的課程。再次使用`window.requestAnimationFrame()`控制動畫。添加移動的向量速度使球移動到向量點。對於每個幀(frame)，我們使用clear (en-US)來清除 canvas 舊的移動幀(frame)。

```js
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;

var ball = {
  x: 100,
  y: 100,
  vx: 5,
  vy: 2,
  radius: 25,
  color: "blue",
  draw: function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI \* 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ball.draw();
  ball.x += ball.vx;
  ball.y += ball.vy;
  raf = window.requestAnimationFrame(draw);
}

canvas.addEventListener("mouseover", function (e) {
  raf = window.requestAnimationFrame(draw);
});

canvas.addEventListener("mouseout", function (e) {
  window.cancelAnimationFrame(raf);
});

ball.draw();
```

邊界
--

沒有任何邊界碰撞下，球很快就會跑出 canvas。這時需要確認球的 `x` and `y` 是否超出 canvas 尺寸，若超出則將球的向量顛倒。所以，我們添加了確認條件在`draw`方法:

```js
if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
  ball.vy = -ball.vy;
}
if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
  ball.vx = -ball.vx;
}
```

### 第一個示範

讓我們看看，看似很遠的行徑它如何行徑。移動你的滑鼠在 canvas，使動畫開始。
```

<canvas id="canvas" style="border: 1px solid" width="600" height="300"></canvas>
```

```
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;

var ball = {
  x: 100,
  y: 100,
  vx: 5,
  vy: 2,
  radius: 25,
  color: "blue",
  draw: function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI \* 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ball.draw();
  ball.x += ball.vx;
  ball.y += ball.vy;

  if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
    ball.vy = -ball.vy;
  }
  if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
    ball.vx = -ball.vx;
  }

  raf = window.requestAnimationFrame(draw);
}

canvas.addEventListener("mouseover", function (e) {
  raf = window.requestAnimationFrame(draw);
});

canvas.addEventListener("mouseout", function (e) {
  window.cancelAnimationFrame(raf);
});

ball.draw();
```

加速性能
----

為了使移動看起來更真實，你可以照著範例改變速度:

```js
ball.vy \*= 0.99;
ball.vy += 0.25;
```

這個使每個幀(frame)的垂直向量減少，所以球將只會在地板彈跳直到結束。
```

<canvas id="canvas" style="border: 1px solid" width="600" height="300"></canvas>
```

```
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;

var ball = {
  x: 100,
  y: 100,
  vx: 5,
  vy: 2,
  radius: 25,
  color: "blue",
  draw: function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI \* 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ball.draw();
  ball.x += ball.vx;
  ball.y += ball.vy;
  ball.vy \*= 0.99;
  ball.vy += 0.25;

  if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
    ball.vy = -ball.vy;
  }
  if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
    ball.vx = -ball.vx;
  }

  raf = window.requestAnimationFrame(draw);
}

canvas.addEventListener("mouseover", function (e) {
  raf = window.requestAnimationFrame(draw);
});

canvas.addEventListener("mouseout", function (e) {
  window.cancelAnimationFrame(raf);
});

ball.draw();
```

追蹤效果
----

直到現在我們已經使用`clearRect` (en-US)方法清除之前的幀(frames)。如果使用重置半透明`fillRect` (en-US)這個方法，可以更淺顯的看出創造追蹤效果。

```js
ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
```

```
<canvas id="canvas" style="border: 1px solid" width="600" height="300"></canvas>
```

```
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;

var ball = {
  x: 100,
  y: 100,
  vx: 5,
  vy: 2,
  radius: 25,
  color: "blue",
  draw: function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI \* 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};

function draw() {
  ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ball.draw();
  ball.x += ball.vx;
  ball.y += ball.vy;
  ball.vy \*= 0.99;
  ball.vy += 0.25;

  if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
    ball.vy = -ball.vy;
  }
  if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
    ball.vx = -ball.vx;
  }

  raf = window.requestAnimationFrame(draw);
}

canvas.addEventListener("mouseover", function (e) {
  raf = window.requestAnimationFrame(draw);
});

canvas.addEventListener("mouseout", function (e) {
  window.cancelAnimationFrame(raf);
});

ball.draw();
```

增加滑鼠控制
------

為了能控制球使它跟著滑鼠移動，在這個範例使用`mousemove` (en-US) 效果。當 `click` (en-US) 事件觸發了這顆球，它又會開始彈跳。
```

<canvas id="canvas" style="border: 1px solid" width="600" height="300"></canvas>
```

```js
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;
var running = false;

var ball = {
  x: 100,
  y: 100,
  vx: 5,
  vy: 1,
  radius: 25,
  color: "blue",
  draw: function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI \* 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};

function clear() {
  ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
}

function draw() {
  clear();
  ball.draw();
  ball.x += ball.vx;
  ball.y += ball.vy;

  if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
    ball.vy = -ball.vy;
  }
  if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
    ball.vx = -ball.vx;
  }

  raf = window.requestAnimationFrame(draw);
}

canvas.addEventListener("mousemove", function (e) {
  if (!running) {
    clear();
    ball.x = e.clientX;
    ball.y = e.clientY;
    ball.draw();
  }
});

canvas.addEventListener("click", function (e) {
  if (!running) {
    raf = window.requestAnimationFrame(draw);
    running = true;
  }
});

canvas.addEventListener("mouseout", function (e) {
  window.cancelAnimationFrame(raf);
  running = false;
});

ball.draw();
```

用你的滑鼠移動這顆球且點擊鬆放它。

突破性(遊戲)
-------

這個小章節只有解釋一些創造高級動畫的技巧。這裡還有更多!如何增加槳,磚塊,到這個 到 Breakout game demo 去看，有我們更多遊戲研發的文章!

延伸閱讀
----

* `window.requestAnimationFrame()`
* Efficient animation for web games (en-US)
* « 前頁
* 次頁 »


Pixel manipulation with canvas - Web APIs

Pixel manipulation with canvas
==============================

* « 前頁
* 次頁 »

直到目前為止，我們還沒真正了解 pixels 在 canvas 上的運用。使用`ImageData`物件，可直接對 pixel 裡的陣列資料**讀(read)**和**寫(write)**。在接下的內容中，也可了解到如何使影像平滑化(反鋸齒)及如何將影像保存在 canvas 之中。

`ImageData`物件
-------------

`ImageData` (en-US) 物件代表 canvas 區中最基礎的像素。

包含它只可讀的屬性:

`width`

影像中的寬度，以 pixels 為單位

`height`

影像中的高度，以 pixels 為單位

`data`

`Uint8ClampedArray` (en-US) 代表一維陣列包含 RGBA 格式。整數值介於 0 到 255 之間(包含 255)。

`data` 屬性返回一個`Uint8ClampedArray` (en-US)，它可被當作為 pixel 的初始資料。每個 pixel 用 4 個 1byte 值做代表分別為**紅**，**綠**，**藍**，**透明值**(也就是**RGBA**格式)。每個顏色組成皆是介於整數值介於 0 到 255 之間。而每個組成在一個陣列中被分配為一個連續的索引。從左上角 pixel 的紅色組成中的陣列由**索引 0** 為始。Pixels 執行順序為從左到右,再由上到下,直到整個陣列。

`Uint8ClampedArray` (en-US) 包含`height` × `width`× 4 bytes 的資料,同索引值從 0 到 (`height`×`width`×4)-1

例如,讀取影像的藍色組成的值。從 pixel 的第 200 欄、第 50 行，你可以照著下面的步驟:

```js
blueComponent = imageData.data[50 \* (imageData.width \* 4) + 200 \* 4 + 2];
```

使用`Uint8ClampedArray.length`屬性來讀取影像 pixel 的陣列大小

```js
var numBytes = imageData.data.length;
```

創造一個 `ImageData`物件
------------------

可以使用`createImageData()` (en-US)方法創造一個全新空白的`ImageData` 物件。

這裡有兩種`createImageData()`的方法:

```js
var myImageData = ctx.createImageData(width, height);
```

這個方法是有規定大小尺寸.所有 pixels 預設是透明的黑色。

下面的方法一樣是由`anotherImageData`參考尺寸大小，由`ImageData` 物件創造一個與新的一樣的大小。這些新的物件的 pixel 皆預設為透明的黑色。

```js
var myImageData = ctx.createImageData(anotherImageData);
```

得到 pixel 資料的內容
--------------

可以使用`getImageData()`這個方法，去取得 canvas 內容中`ImageData` 物件的資料含 pixel 數據(data)

```js
var myImageData = ctx.getImageData(left, top, width, height);
```

這個方法會返回`ImageData`物件，它代表著在這 canvas 區域之中 pixel 的數據(data) 。從各角落的點代表著 (`left`,`top`), (`left+width`, `top`), (`left`, `top+height`), and (`left+width`, `top+height`)。這些作標被設定為 canvas 的空間座標單位。

**備註：** 在`ImageData` 物件中，任何超出 canvas 外的 pixels 皆會返回透明的黑色的形式。

這個方法也被展示在使用 canvas 操作影像 (en-US)之中。

### 調色盤

這個範例使用getImageData() (en-US) 方法去顯示在鼠標下的顏色。

首先，需要一個正確的滑鼠點`layerX` 和 `layerY`。在從getImageData() (en-US) 提供 pixel 陣列中(array)該點的 pixel 數據(data) 。最後，使用陣列數據(array data)在`<div>`中設置背景色和文字去顯示該色。
```

<canvas id="canvas" width="300" height="227" style="float:left"></canvas>
<div id="color" style="width:200px;height:50px;float:left"></div>
```

```js
var img = new Image();
img.src = "rhino.jpg";
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
img.onload = function () {
  ctx.drawImage(img, 0, 0);
  img.style.display = "none";
};
var color = document.getElementById("color");
function pick(event) {
  var x = event.layerX;
  var y = event.layerY;
  var pixel = ctx.getImageData(x, y, 1, 1);
  var data = pixel.data;
  var rgba =
    "rgba(" +
    data[0] +
    ", " +
    data[1] +
    ", " +
    data[2] +
    ", " +
    data[3] / 255 +
    ")";
  color.style.background = rgba;
  color.textContent = rgba;
}
canvas.addEventListener("mousemove", pick);
```

在內容中寫入 pixel 資料
---------------

可以使用putImageData() (en-US) 方法將自訂 pixel 數據(data) 放入內容中:

```js
ctx.putImageData(myImageData, dx, dy);
```

`dx` 和 `dy`參數表示填入你所希望的座標，將它代入內容中左上角的 pixel 數據(data)。

For example, to paint the entire image represented by `myImageData` to the top left corner of the context, you can simply do the following:

```js
ctx.putImageData(myImageData, 0, 0);
```

### 灰階和負片效果

In this example we iterate over all pixels to change their values, then we put the modified pixel array back to the canvas using putImageData() (en-US). The invert function simply subtracts each color from the max value 255. The grayscale function simply uses the average of red, green and blue. You can also use a weighted average, given by the formula `x = 0.299r + 0.587g + 0.114b`, for example. See Grayscale on Wikipedia for more information.
```

<canvas id="canvas" width="300" height="227"></canvas>
<div>
  <input id="grayscalebtn" value="Grayscale" type="button" />
  <input id="invertbtn" value="Invert" type="button" />
</div>
```

```js
var img = new Image();
img.src = "rhino.jpg";
img.onload = function () {
  draw(this);
};

function draw(img) {
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0);
  img.style.display = "none";
  var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  var data = imageData.data;

  var invert = function () {
    for (var i = 0; i < data.length; i += 4) {
      data[i] = 255 - data[i]; // red
      data[i + 1] = 255 - data[i + 1]; // green
      data[i + 2] = 255 - data[i + 2]; // blue
    }
    ctx.putImageData(imageData, 0, 0);
  };

  var grayscale = function () {
    for (var i = 0; i < data.length; i += 4) {
      var avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
      data[i] = avg; // red
      data[i + 1] = avg; // green
      data[i + 2] = avg; // blue
    }
    ctx.putImageData(imageData, 0, 0);
  };

  var invertbtn = document.getElementById("invertbtn");
  invertbtn.addEventListener("click", invert);
  var grayscalebtn = document.getElementById("grayscalebtn");
  grayscalebtn.addEventListener("click", grayscale);
}
```

放大和平滑化(反鋸齒)
-----------

With the help of the `drawImage()` (en-US) method, a second canvas and the `imageSmoothingEnabled` (en-US) property, we are able to zoom into our picture and see the details.

We get the position of the mouse and crop an image of 5 pixels left and above to 5 pixels right and below. Then we copy that one over to another canvas and resize the image to the size we want it to. In the zoom canvas we resize a 10×10 pixel crop of the original canvas to 200×200.

```js
zoomctx.drawImage(
  canvas,
  Math.abs(x - 5),
  Math.abs(y - 5),
  10,
  10,
  0,
  0,
  200,
  200,
);
```

Because anti-aliasing is enabled by default, we might want to disable the smoothing to see clear pixels. You can toggle the checkbox to see the effect of the `imageSmoothingEnabled` property (which needs prefixes for different browsers).

### Zoom example
```

<canvas id="canvas" width="300" height="227"></canvas>
<canvas id="zoom" width="300" height="227"></canvas>
<div>
  <label for="smoothbtn">
    <input type="checkbox" name="smoothbtn" checked="checked" id="smoothbtn" />
    Enable image smoothing
  </label>
</div>
```

```js
var img = new Image();
img.src = "rhino.jpg";
img.onload = function () {
  draw(this);
};

function draw(img) {
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0);
  img.style.display = "none";
  var zoomctx = document.getElementById("zoom").getContext("2d");

  var smoothbtn = document.getElementById("smoothbtn");
  var toggleSmoothing = function (event) {
    zoomctx.imageSmoothingEnabled = this.checked;
    zoomctx.mozImageSmoothingEnabled = this.checked;
    zoomctx.webkitImageSmoothingEnabled = this.checked;
    zoomctx.msImageSmoothingEnabled = this.checked;
  };
  smoothbtn.addEventListener("change", toggleSmoothing);

  var zoom = function (event) {
    var x = event.layerX;
    var y = event.layerY;
    zoomctx.drawImage(
      canvas,
      Math.abs(x - 5),
      Math.abs(y - 5),
      10,
      10,
      0,
      0,
      200,
      200,
    );
  };

  canvas.addEventListener("mousemove", zoom);
}
```

儲存圖片
----

The `HTMLCanvasElement` (en-US) provides a `toDataURL()` method, which is useful when saving images. It returns a data URI (en-US) containing a representation of the image in the format specified by the `type` parameter (defaults to PNG). The returned image is in a resolution of 96 dpi.

`canvas.toDataURL('image/png')`

Default setting. Creates a PNG image.

`canvas.toDataURL('image/jpeg', quality)`

Creates a JPG image. Optionally, you can provide a quality in the range from 0 to 1, with one being the best quality and with 0 almost not recognizable but small in file size.

Once you have generated a data URI from you canvas, you are able to use it as the source of any `<image>` (en-US) or put it into a hyper link with a download attribute (en-US) to save it to disc, for example.

You can also create a `Blob` from the canvas.

`canvas.toBlob(callback, type, encoderOptions)` (en-US)

Creates a `Blob` object representing the image contained in the canvas.

延伸閱讀
----

* `ImageData` (en-US)
* Manipulating video using canvas (en-US)
* Canvas, images and pixels – by Christian Heilmann
* « 前頁
* 次頁 »