JavaScript CSS3

Javascript 動態網頁程式設計 - 上課教材

動畫互動網頁程式設計 > 課程內容 > 第 11 章 - JSON 表示法與 Canvas 初探

第 11 章 - JSON 表示法與 Canvas 初探

上次更新日期 2020/12/26

在射擊遊戲時,對於產生飛碟這件事,我們很單純的只是設計一個方塊並且填滿顏色而已,實在是醜醜的! 如果可能,讓真的飛碟圖示取代小方塊應該是比較好的。那圖示要去找小圖檔嘛?這樣換個圖示得要重新去處理小圖檔。 有沒有可能自己繪製小圖片?答案是肯定的!可以!透過 canvas 這個小東西,就能夠輕鬆順利的在瀏覽器上面繪製圖案了! 另外,為了方便資料的輸入/輸出,學習一下 JSON 也是很不錯的啊!這個章節我們主要來介紹 JSON 與 Canvas 喔!

學習目標:

  1. 自己設計 JSON 物件的資料格式與內容
  2. 由 API 搭配 Ajax 取得 JSON 格式的方法
  3. Canvas 物件的設計方式
  4. Canvas 的繪圖方式
  5. 用 Canvas 匯入圖示的方式

11.1: 簡易使用 JSON 資料格式

寫網頁需要知道 HTML 的標籤 (tag) 意義,需要知道樣式表的格式 (CSS) 等等,寫網頁程式語言,需要知道 JavaScript 的語法, 需要知道 PHP 的語法,寫資料庫,也可能需要知道 SQL 的語法等等。那麼這些資料在進行變數設定時,當然也是有各自的格式。 不過,為了方便資料的攜帶性,於是便有了 JSON (JavaScript Object Notation, JSON) 這種資料設定格式!

  • JSON 簡易說明

有關 JSON 的說明,請參考文末的 JSON wiki 詳細說明, 這裡僅擷取出 JSON 的基本資料格式說明:

  • 數值:十進位數,不能有前導 0,可以為負數,可以有小數部分。還可以用 e 或者 E 表示指數部分。 不能包含非數,如 NaN。不區分整數與浮點數。JavaScript 用雙精度浮點數表示所有數值。
  • 字串:以雙引號 ("string") 括起來的零個或多個 Unicode 碼位。支援反斜槓開始的跳脫字元序列 (\)。
  • 布林值:表示為 true 或者 false。
  • 值的陣列 (array):零個或者多個陣列值。每個值可以為任意類型。陣列使用中括號 [ , ] 括起來。 元素之間用逗號 , 分割。形如: [ value, value ]
  • 物件 (object):若干無序的「鍵-值對」(key-value pairs),或是我們較常稱的『變數: 變數值』說明。 建議但不強制要求物件中的鍵 (變數) 是獨一無二的。物件以大括號 { 開始,並以 } 結束。 鍵-值對 (變數:變數值) 之間使用逗號分隔。鍵與值之間用冒號 : 分割。
  • null類型:值寫為 null

所以,JSON 基本上就只是一種資料表現的方式,然後我們可以拿這東西來作為物件、物件加陣列這樣的方式來設計 JSON 變數, 然後就可以直接將該變數應用到 JavaScript 程式碼當中了。根據上面的介紹,我們來製作一個最簡單的關於空氣品質的內容說明如下:

var aq = {
	"PM25": "粒徑小於 2.5 ug 的懸浮微粒,有原生與衍生的來源",
	"VOC": "揮發性有機物,主要來自於溶劑的使用或石油燃料等",
	"NOx": "主要由燃燒過程加入空氣,因為 N2 與 O2 而產生 NOx",
	"SOx": "主要由化石燃料,如燃煤等,在燃燒時產生",
	"CO": "燃燒化石燃料時,由於不完全燃燒所產生",
	"O3": "臭氧分子,由 VOC 與 NOx 光化學反應生成",
	"O3_limit": 120,
	"AQI": true,
	"PSI": false,
	"AQI_pol": [ "PM25", "O3", "O3 8hr", "PM10", "CO 8hr", "SO2", "NO2" ]
};

上述資料的呼叫方法,就可以透過類似底下的方式處理:

  • aq.PM25 會得到 "粒徑..."
  • aq.AQI_pol[0] 會得到 "PM25"

那就簡單得來應用一下:

例題 11-1-1:使用上述的資料,搭配相關的說明來輸出 JSON 資訊:
  1. 新增 unit11-1-1.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 這個檔案的內容貼到 unit11-1-1.php 當中,並查閱一下相關顯示
  3. 將上面的 aq 變數貼入這個 unit11-1-1.php 檔案內
  4. 設計當整體網頁載入後,會去執行 mymain 函數
  5. 在 mymain 函數內,將相關的資訊填進去。最終的畫面需要像底下一樣:
初次應用 JSON 格式
  • JSON 用於建立使用者資料的物件

如果你要提供一個關於你自己的物件,裡面包括你的大名、你的年紀、性別、生日、電話、多個興趣、暱稱等, 最簡單方法,可能就是透過 JSON 格式來建立了!請依據前一題的相關資料,實做底下這個案例:

例題 11-1-2:建立關於自己的一些簡易的自我介紹物件
  1. 新增 unit11-1-2.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 這個檔案的內容貼到 unit11-1-2.php 當中,並查閱一下相關顯示
  3. 設計當整體網頁載入後,會去執行 mymain 函數
  4. 在 mymain 函數內,將相關的資訊填進去。最終的畫面需要像底下一樣:
建立 JSON 的自我介紹物件

你或許會覺得,這不過就是個自我介紹啊~沒啥了不起。沒關係,現在,請隨便前往三個同學的網頁上面取得他的自我介紹, 然後將該介紹帶入自己的程式碼當中,變成一個 JSON 來的『陣列格式』的物件,然後透過該物件, 以 for 迴圈的功能,將大家的自我介紹資料以一個統一的格式來處理!這就是個重要的地方了!

例題 11-1-3:使用多人的資料,建立統一的樣式輸出之 JSON 物件
  1. 將 unit11-1-2.php 另存新檔成 unit11-1-3.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 取得其他三位同學的自我介紹資料 (所以應該有 4 筆紀錄),然後變更自己的 JSON 格式,使得自我介紹的物件, 以陣列的方式來處理。這裡要特別注意的是,陣列格式與物件格式並不相同,因此,需要特別注意中括號與大括號的使用情境!
  3. 修改資料輸出的方式,讓該方式成為類似 for 迴圈的功能,將資訊依序輸出
  4. 最終的畫面需要像底下一樣:
建立 JSON 的自我介紹物件
  • 透過 JSON.parse() 方法,轉純文字資料成為 JSON 物件

將剛剛建立的四個資料,放置到新檔 unit11-1-4.txt 檔案中,假裝這就是我們的資料庫 API 回傳的數據! 然後透過 Ajax 的功能,取得這個數據資料後,怎麼轉成 JSON 的資料呢?很簡單!就是透過 JSON.parse(變數) 這樣的格式來處置即可。 一般來說,我們從 API 裡面抓到的資訊,大部份使用的是純文字,因此,如何將純文字變成 JSON 的物件格式,當然就是很重要的一件事了! 在很多網站應用中,這是相當重要的技巧喔!

例題 11-1-4:假裝上一題設定的 JSON 樣式為 API 回傳值,這時就需要用到 JSON.parse 的轉換功能!
  1. 將 unit11-1-3.php 另存新檔成 unit11-1-4.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 新增 unit11-1-4.txt,將剛剛的 JSON 變數設定寫入檔案中,請再三確認這個檔案沒問題喔!
  3. 新增一個名為 getusers 的函數,內容為使用 Ajax 來設定取得 unit11-1-4.txt 的功能,使用的回應類型為 text 格式即可。 同時,當有任何狀態改變 (onreadystatechange) 時,執行 putusers 函數。
  4. 新增名為 putsers 的函數,內容為,判斷剛剛的 xhttp 變數是否正確?若正確,就取得 xhttp 的回應, 並將 (1)使用 trim() 方法,該回應的前後空白刪除;(2)設定 users 變數,內容就是 JSON.parse() 的結果。 (3)最終持行 myoutput() 函數。
  5. 設計 myoutput 函數,很簡單,將原本 mymain 的內容全部移動到這裡即可。
  6. 修改 mymain 函數,讓它執行 getusers() 即可。

輸出的結果會跟 unit11-1-3.php 相同,不過,看內容時,就會發現到,這是取得 JSON 的 API 之結果。 同時要注意, JSON 的資料不能寫註解,因此,所有格式必須正確。同時,在字串轉成 JSON 物件之前, 需要確定一開始的中括號或大括號前面沒有空格,否則會出問題!因此,我們這裡才用 trim() 去拿掉空白喔!

11.2: 使用 Canvas 繪製圖案

過去在網頁上面顯示圖案時,我們可能得要透過 div 或者是直接嵌入一些 CSS 樣式,或者是以固定不變的圖示來展示,對於修改圖檔, 恐怕很有點問題。現在,有個 Canvas 這玩意兒,我們可以在瀏覽器上面作畫喔!注意,是作畫喔!不是使用 CSS 調整方塊而已。 所以,Canvas 一開始會給我們一個方形區間,在這個區間裡面進行作畫這樣。

  • 第一個簡易的 canvas 畫框與方形圖案繪製

如果你要建立一個 canvas 畫框,可以簡單的這樣進行:

function mymain() {
  var mycanvas = document.getElementById('mycanvas');
  mycanvas.width = "300";
  mycanvas.height = "200";
  var myfig = mycanvas.getContext('2d');
  myfig.fillRect( 0, 0, 150, 100);
}

....
<canvas id='mycanvas'/></canvas>

簡單的說,Canvas 有提供 HTML 一個名為 canvas 的標籤 (tag),然後我們可以透過 JavaScript 將該標籤的 ID 取下元素控制權之後, 將該元素以 .getContext('2d') 這個方法 (mothod) 產生一個 Canvas 的物件!接下來,我們就可以對這個物件來進行各種繪圖了!上面的 .fillRect() 方法, 就是以填滿 (fill) 一個方形 (Rect) 的動作,來產生一個實心的方塊!大致的意思就是這樣。另外,如果需要設定填入方塊的顏色, 可以透過 .fillStyle() 方法來處理:

方法說明
.fillStyle(顏色)等等底下的方塊將會使用的是這個顏色來填滿的意思,顏色可以是英文顏色,如 yellow, red 等, 可以是 CSS 的色碼,也能使用 CSS 的 rgb 函數。
.fillRect(x,y,w,h)設定一個實心的方形,這個方形的左上角定位點為 (x, y) ,然後這個方形的寬度是 w 像素,高度是 h 像素這樣。 要注意的是,原點跟一般網頁的原點位置相同,都是左上角,然後向右、向下為正喔!
.strokeStyle(顏色)等等底下的框線方塊將會使用這個顏色來填滿的意思。
.strokeRect(x,y,w,h)與 fillRect 相似,只是 strokeRect 使用空心的,僅有框線的方塊之意。
.clearRect(x,y,w,h)在 (x,y) 定位點,將 w 寬 h 高的方形清空,其實有點類似繪製一個 w, h 的白色方塊啦!

特別要注意的是, Canvas 畫框的寬度與高度 (width, height) 不能使用 CSS 的 style 樣式表來設定, 需要使用原始的 HTML 格式來設定。這是因為 CSS 的高度與寬度,會將 Canvas 進行縮放動作,而不是直接調整方塊大小,這個設定很奇特! 因此,你會發現到上面的範例中,我們使用了 .width 與 .height,不是使用 style.width 之類的設計!要注意! 現在,讓我們來畫幾個實心與空心的方塊吧!

例題 11-2-1:
  1. 新增 unit11-2-1.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 這個檔案的內容 加入到 unit11-2-1.php 裡面,並查看網頁相關資訊
  3. 讓整個網頁載入後,立刻執行 mymain 函數
  4. 讓 mymain 函數具有幾個設計:
    • 將 canvas 這個元素抓下來後,並給予寬度 500 像素、高度 300 像素的設計 (請注意,不能使用 CSS 喔!所以,請用 javascript 直接去指定寬度與高度,不要套用 style 的設計。),然後以 CSS 的樣式,給予 1px solid gray 的框線。
    • 讓 canvas 元素具有繪圖的功能,設定物件名稱為 myfig 好了
    • 在定位 0,0 給一個實心的,藍色的 100x100 的方塊
    • 在定位 50,50 給一個實心的,紅色的 100x100 的方塊
    • 在定位 100,100 給一個空心的,框線顏色為 rgb(255,255,0) 的 150x100 的方塊
    • 在定位 70, 70 的位置,清出一個 20x20 的方塊空間。
使用 Canvas 繪圖

從上面,你也可以發現方塊的重疊,主要都是以後出現的覆蓋在先出現的方塊上面喔!跟一般 CSS 或 HTML 的情境是一樣的!

  • 繪製路徑、直線、圓形、扇形、多邊形等

Canvas 用來繪製直線、圓形、扇形、多邊形的時候,可能需要用到『路徑 (path)』的概念~意思是,假設我設定了多個點, 將這些點連結起來,就會是一個圖示!如果你需要設計兩個圖示,那就得要分別設計兩個路徑,這樣路徑才能『關閉』, 亦即是最後一個點可以連結到最原始的點,這樣才是完整的圖示啊!那需要用到的基本 Canvas 方法有這些:

方法說明
.beginPath()開始設計新一張圖的路徑,每張新的圖示,應該都要使用這個方法作為開始之用
.closePath()這張圖示的最後,請連結回第一個起始點之意。不見得需要,但是如果要連回原始點,就得要有這個比較好。
.stroke()將設計的許多點座標,以框線的方法繪製出來
.fill()將設計的許多點座標,以實心的填滿方式填滿繪製
.moveTo(x,y)將繪製點移動到 x, y 座標去
.lineTo(x,y)由目前的座標,繪製直線到 x,y 座標的意思
.rect(x,y,w,h)由 x, y 座標作為原點,繪製一個封閉的路徑,寬為 w 高為 h 之意。
.arc(x,y,r,angle1,angle2,[true|false])設計圓弧,以 x, y 座標為圓心,r 為半徑,繪製圓弧。圓弧的起始位置在 angle1, 結束位置在 angle2。至於角度的計算,主要使用弧度,亦即整個圓形為 2π 弧度,可以使用 2*Math.PI 來設計。如果是要繪製 1/4 圓形在第一象限, 可以透過 .arc( x, y, r, 1.5*Math.PI, 2*Math.PI, true) 來設計。至於最終的 false 與 true 分別代表順時針/逆時針方向的繪製。

除了上面的方法之外,還有貝茲曲線、直線相切圓弧點等。此外,比較重要的圓弧設計中,還得要注意角度的計算!與一般座標角度相同, 正右方為 0 度角,正左方為 180 度角,所以可以使用下圖來思考 (圖示來源):

Canvas 圓弧圖設計
例題 11-2-2:使用 canvas 繪製路徑圖示
  1. 新增 unit11-2-2.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 使用上面的方法,想辦法完成底下的圖示測試看看:
使用 Canvas 繪圖

還記得我們在 8-1-2 例題當中,透過圖片去載入星空圖嘛?現在讓我們來使用 Canvas 產生這張圖片看看!速度似乎比起放置圖片要快很多很多!

例題 11-2-3:使用 canvas 繪製星星圖示
  1. 新增 unit11-2-3.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 產生一個 600x300 左右的 canvas 元素,並取得這個元素,產生 myfig 的可繪圖功能
  3. 先產生一個與 canvas 畫框相同大小的方塊,並且圖上黑色填滿
  4. 使用迴圈方式,執行 1000 次左右的繪圖,繪圖的內容需求使用圓形,因此需要:
    • 亂數計算 x 座標,最大到 600
    • 亂數計算 y 座標,最大到 300
    • 亂數計算 r 半徑 (星星有大有小),最大到 3 或 4
    • 設計顏色,如果 r 大於等於 2 的時候,給予白色,否則就給予灰色 (gray) 或銀色 (silver)
    • 開始路徑、繪製圓形、設計 fillStyle 的顏色、填滿顏色、關閉路徑
使用 Canvas 繪圖
  • 線段的樣式

你可能也想要修改線段的樣式,包括線的寬度 (.lineWidth)、線兩頭的樣式 (.lineCap)、線條交叉接角的樣式 (.lineJoin) 等等,大致上常用的線段屬性如下:

.lineWidth = "10"   // 單位是像素喔!

.lineCap = "butt"   // butt 無樣式、 round 圓弧、 square 方形,預設為 butt

.lineJoin = "bevel" // bevel 無樣式、 round 圓弧、 miter 尖角,預設 bevel

根據上面的屬性設計,我們來繪製一個 UFO 看看喔!

例題 11-2-4:使用 canvas 繪製 UFO 飛碟
  1. 新增 unit11-2-4.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 產生一個 500x500 左右的 canvas 元素,並取得這個元素,產生 myfig 的可繪圖功能
  3. 設計等等繪圖需要的線段參數,包括寬度為 20 像素、線段為圓角、線段交叉也是圓角
  4. 開始設計最上面的飛碟圓形部份,圓心大致在 250,200 左右,半徑設計 120 左右,設計出這個圓形, 然後先進行填滿,填滿的顏色可以使用米黃色,填滿之後再繪製框線即可。
  5. 開始設計反光條,圓心與上面的圓相同,半徑大概在 90 左右,繪製第一象限的扇形,因為是扇形,所以不需要 closePath 喔!
  6. 開始設計中間的碟子,大致上的幾個轉則的座標,可以參考 130,230 / 370,230 / 490,290 / 450,350 / 50,350 / 10,290 等, 因為是需要填滿的,因此得要先進行 closePath 之後,再填滿,再畫框線。顏色同樣自訂。
  7. 最下方的空間部份,懶惰的話,直接畫方形也可以!顏色也自訂。
  8. 最後才是那三個圓圈圈,同樣自行設計吧!
使用 Canvas 繪圖

我們在第 9 章有使用砲彈打飛碟,當時的畫面真是有夠丑的!直接拿人家的圖示來處理也不好。現在,請使用 Canvas 繪製兩個元件, 一個是星空圖,直接拿來作為背景,一個是飛碟圖,直接拿來產生樣式,然後測試看看能不能適用於我們的遊戲中!?不過,如果是憑空出現的 canvas 物件, 得要透過 .appendChild 或者是將 canvas 轉成可視的圖檔 URL 來處理:

方法說明
.toDataURL()將 canvas 物件轉成一個瀏覽器認識的圖片網址列項目,然後透過 .style.backgroundImage = "url('imgfile')" 的樣式, 就可以將該圖片處理使用了。但是要記得 imgfile 的網址,使用 .toDataURL() 轉出來的變數即可

那就讓我們來美化一下射擊遊戲吧!

例題 11-2-5:使用 canvas 的功能進行美化作業
  1. 將 unit09-4-4.php 另存成 unit11-2-5.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 新增一個函數,名稱為 mybg() 好了,這個函數主要的目的在處理 playing 元素的背景圖,要用星空畫面來填滿。
    • 先將 11-2-3.php 的 javascript 程式碼貼過來,但是 mycan 不是在原始碼上面, 而是透過 document.createElement('canvas') 建立一個新的元件
    • 修改寬度、高度分別是 600 與 450,要搭配 playing 元素的高度與寬度才行,相關的寬度高度請通通自行修改
    • 因為是背景,所以星星的亮度不能太亮,否則會干擾到子彈的發射。請將顏色改成 rgba(100,100,100,1),若覺得還是太亮,將 1 透明度降低即可。
    • 使用 .toDataURL() 函數,將 canvas 物件轉成具有 URL 的圖片物件網址存在
    • 取得 playing 元素的控制權之後,修改 .style.backgroundImage ,將上面的網址列塞進來。
    最後,將 mybg() 函數寫到 mymain() 函數裡面,讓 mybg 可以一開始載入就執行!
  3. 增加一個名為 myufo() 的函數,目的就在產生每一個飛碟!所以,最終需要回傳 canvas 的飛碟物件:
    • 先將 11-2-4.php 的 javascript 程式碼貼過來,同樣使用 document.createElement('canvas') 建立一個新的元件
    • 將 border 拿掉,或者是框線設為 0
    • 最後面 return mycan; 之類的,將物件回傳!
  4. 修改 createufo() 的內容,讓原本的方塊變成飛碟!
    • 讓原本 ufo 產生新物件的方法,變成 ufo = myufo() 這樣的格式
    • 將 border, backgroundColor, padding, maring 與 height 通通拿掉,剩下 width 為 50 像素即可。
  5. 修改 CSS 的設定,以使符合我們的環境!相關 CSS 設定請自行處理。
使用 Canvas 繪圖

因為整個畫面都是 canvas 用 javascript 繪製的,不是使用圖片來處理的,因此速度又更快!而且畫面要好看許多啊! 另外,如果你想要讓飛碟會跑動,那麼可以使用 CSS 動畫效果來處理即可!找到 #playing 內的 canvas 元件進行 CSS 動畫, 例如,將底下的 CSS 程式碼貼上去 11-2-5 例題當中,就可以看到飛碟會有動畫呈現了!

#playing canvas {
	animation-name: ufogo;
	animation-duration: 0.5s;
	animation-direction: alternate;
	animation-iteration-count: infinite;
	
}
@keyframes ufogo {
	from { width: 50px; }
	to   { width: 60px; }
}
  • 以 .toDataURL() 產生可下載檔案

如果你的 canvas 繪製的圖示想要提供給用戶下載,那麼你可以透過 .toDataURL 來處理。不過要注意的是, .toDataURL 提供的網址列是 base64 加密的類型, 這種類型似乎僅有圖片 (img) 的 src 可以讀取!因此,你得要將網址列塞給某個圖示,然後讓某個圖示被開啟,那就應該比較 OK 啊! 比較簡單的方式,透過新開一個視窗,然後將圖片塞進去,這樣你就可以在下個視窗找到正確的圖片,而且是不會更動內容的樣式喔!

newwin = window.open('');
newwin.document.body.appendChild(dlfig);

透過上面的方式,就可以將 canvas 轉出來的圖片檔案 (dlfig) 貼到新開的視窗上面囉!

例題 11-2-6:提供新視窗放入建置好的圖片
  1. 將 unit11-2-3.php 另存成 unit11-2-6.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 將 mycan 與 myfig 設計成為全域變數,並且修改原本寫到 mymain 函數中的變數設計方式
  3. 在 html 原始碼當中,新增一個 button ,且按下該 button 之後,會執行 dlfig() 函數
  4. 新增 dlfig() 函數:
    • 建立 dlfig 變數,內容為新增 IMG 元素
    • 讓 dlfig 的圖片來源,設計為 mycan 的 toDataURL() 結果
    • 使用 window.open 的方法,開新視窗,網址為空的,視窗名稱為 'newwin' 這樣
    • 透過 newwin.document.body.appendChild 的方法,將 dlfig 加到新視窗當中。
使用 Canvas 繪圖 使用 Canvas 繪圖

透過這簡單的動作,你的 Canvas 產生的圖片,就可以在固定後進行下載。另外,其實你在 Canvas 圖片上面按下右鍵,事實上也是可以下載的! 就看你怎麼應用了!這裡只是提供你一個參考的方向而已喔!

11.3: 使用 Canvas 將已存照片重製

有時候我們可以透過 Canvas 對已經存在的照片進行一些繪製與擷取的動作喔!最簡單的方法,就是透過 .drawImage 來處理! 這個 .drawImage 可以加入不同份量的參數,如果是原圖要完整的載入畫布,可以這樣做:

方法說明
.drawImage(img,dx,dy)將 img 這張原圖放置到 canvas 的座標 (dx, dy) 上面展示出來
.drawImage(img,dx,dy,dw,dh)將 img 這張原圖放置到 canvas 的座標 (dx, dy) 上面展示出來, 但是原圖可能會被壓縮或放大成為 dw x dh (寬x高) 的樣式
.drawImage(img,sx,sy,sw,sh,dx,dy,dw,dh)將 img 這張原圖的 (sx, sw) 座標開始, 取出 sw x sh (寬x高) 的區域範圍,再將該範圍切割出來的圖示,放置到 canvas 的座標 (dx, dy) 上面展示出來, 且壓縮或放大成為 dw x dh (寬x高) 的樣式

因為來源的英文是 source,目標的英文是 destination,因此 sx 是原圖的 x 軸位置,而 dx 則是放置到 canvas 畫布的 x 軸位置的意思。 這幾個基本座標與寬高的樣式比例,套用 mozilla 網站的說明,圖示有點像這樣: (圖示來源:這裡)

載入圖示的功能

你可以根據自己的需求,將某張圖示整個放置到畫布中,也可以將某張圖示的某個區塊抓出來,放置到畫布的某個區塊去! 應用上也是相當有趣的!

例題 11-3-1: 將整張圖示放置到 canvas 畫布中,同時進行畫面重製處理等任務
  1. 新增 unit11-3-1.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 這個檔案的內容加入到 unit11-3-1.php 當中,並瀏覽一下輸出的結果。
  3. 這個圖檔下載,並上傳到你的 images 目錄下。
  4. 設計當載入整體網頁完成後,就執行 mymain 函數,同時設計 3 個全域變數,一個是 mycan 一個是 myfig 一個是 myimg, 分別作為 canvas 元素、繪圖元素、圖片元素之用
  5. 設計 mymain 函數的內容:
    • 設計 mycan 變數,內容是取得 mycan 的元素控制權
    • 設計 myfig 變數,內容就是讓 mycan 的 canvas 畫布
    • 設計 myimg 變數,內容就是增加一個 IMG 的元素控制權
    • 讓 myimg 的圖片網址成為剛剛上傳的那個 google 地圖檔案檔名 (注意,可能會有上層目錄喔!)
    • 設計兩個變數,分別是 figx 與 figy,個別是 800 與 500 兩個數值,預計作為畫布的寬與高
    • 指定 mycan 的寬度與高度,分別是 figx 與 figy 喔!
    • 指定 mycan 的 CSS 樣式,其框線為 1px solid gray,預先了解畫布的方塊位置。
  6. 設計 asia() 函數內容,預計載入剛剛的圖片到 Canvas 畫布中
    • 先設定等等要填滿畫布的顏色為銀色 (silver)
    • 依據座標 (0,0) 到 (figx,figy) 之間,填滿一個方形到畫布中
    • 載入 myimg 這張圖到畫布的 (0,0) 座標上,以原圖大小載入即可。
    • 設計完成後,按下按鈕看看結果,若一切順利,將 asia() 函數也寫入到 mymain() 函數內的最後一個指令。
  7. 設計 aisa2() 函數,目的是讓整張圖『縮放』到 800x500 的畫布環境中:
    • 查看一下剛剛下載的圖片,可以簡單的使用小畫家打開,然後看一下圖片的寬度與高度 (1023x830) 數值,因為要整張圖畫放置到 800x500 的環境下, 因此,計算圖片的長寬比,假設寬度為 1 時,則高度比例為 830/1023 = 0.811,因此,當寬度為 figx 時,高度應該就是 figx*0.811 了。
    • 先設定等等要填滿畫布的顏色為銀色 (silver)
    • 依據座標 (0,0) 到 (figx,figy) 之間,填滿一個方形到畫布中
    • 載入 myimg 這張圖到畫布的 (0,0) 座標上,原圖大小縮放為 figx, figx*0.811 的寬高。
  8. 設計 taiwan1() 函數,目的是,左側放一張縮小的圖,右側讓台灣附近的地理圖示切出來
    • 先設定等等要填滿畫布的顏色為銀色 (silver)
    • 依據座標 (0,0) 到 (figx,figy) 之間,填滿一個方形到畫布中
    • 載入 myimg 這張圖到畫布的 (0,0) 座標上,原圖大小縮放為 figx*0.25, figx*0.811*0.25 的寬高 (更加縮小了)。
    • 在原圖的座標 (390,370) 處,取出寬、高皆為 200 像素的區塊,然後填入畫布的座標 (figx*0.25+2, 0) 的座標, 並且放大到 2 倍,亦即以 400, 400 像素的寬高顯示出來!
    • 開始製作一個方塊,目的是將縮放的圖示中,顯示放大的位置區塊示意圖:
    • 計算縮小的圖示的 x, y 座標,注意,左上角縮放的圖示,縮放的比例應該是 800/1023*0.25 !所以,原圖的 (390, 370) 請計算出 x 與 y 變數,作為其座標。同時,寬度與高度也需要從 200 計算出成為縮小的比例,假設為 delta 這個變數
    • 等等畫方塊的顏色為 yellow 黃色
    • 將黃色框線填入 (.strokeRect)
  9. 設計 taiwan2() 函數,目的與前一個函數相似,只是,更貼近台灣地區
    • 與 taiwan1() 相同的,填滿銀色到畫布後,放上縮小的亞洲地區地圖
    • 在原圖的座標 (470,430) 處,取出寬 100 高 130 像素的區塊,然後填入畫布的座標 (figx*0.25+2, 0) 的座標, 並且放大到 4 倍左右,亦即以 384, 500 像素的寬高顯示出來!
    • 依據前一個函數的作法,分別計算縮小圖示的座標與寬高,然後畫出一個黃色的小區域顯示
不同的圖像展示方式 不同的圖像展示方式 不同的圖像展示方式 不同的圖像展示方式
  • 描繪文字

Canvas 也允許在畫布上面填寫文字,填寫文字之前,需要先設定文字的一些屬性,比較常見的屬性有:

方法說明
.font這個項目其實是屬性,透過 CSS 的 font 樣式來處理的。格式為 "字體厚度 字體大小 字體字型",例如 "bold 12pt Times New Roman", 最後的字體 (font-familiy) 不能加上單引號喔,否則會出錯。
.textAlign也是屬性,一行字的對齊模式,有 start, end, left, right, center 等,都與座標有關!
.textBaseline也是屬性,對齊的基線位置,有 top, hanging, middle, alphabetic, ideographic, bottom 等。
.fillText('文字',x,y[,最大寬])設計文字內容(也可以是變數),放置到 x, y 的位置上,使用的是實心的文字
.strokeText('文字',x,y[,最大寬])與前一個相似,只是字體為空心字

現在,透過前一個例題的類似案例,我們讓使用者按下某個按鈕時,可以展示在圖片上面比較顯眼的位置上。舉例來說,可以提示使用者的位置或者是想要查閱的地區。 同時提供 我們來看一下底下的案例:

例題 11-3-2: 讓照片顯示特殊樣式,提供焦點給使用者明瞭位置
  1. 新增 unit11-3-2.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 這個檔案的內容加入到 unit11-3-2.php 當中,並瀏覽一下輸出的結果。
  3. 這個圖檔下載,並上傳到你的 images 目錄下。
  4. 設計當載入整體網頁完成後,就執行 mymain 函數,同時設計 3 個全域變數,一個是 mycan 一個是 myfig 一個是 myimg, 分別作為 canvas 元素、繪圖元素、圖片元素之用
  5. 設計 mymain 函數的內容:
    • 設計 mycan 變數,內容是取得 mycan 的元素控制權
    • 設計 myfig 變數,內容就是讓 mycan 的 canvas 畫布
    • 設計 myimg 變數,內容就是增加一個 IMG 的元素控制權
    • 讓 myimg 的圖片網址成為剛剛上傳的那個資傳系地圖檔案檔名 (注意,可能會有上層目錄喔!)
    • 設計兩個變數,分別是 figx 與 figy,個別是 800 與 480 兩個數值,預計作為畫布的寬與高 (寬高設計過了,與圖片寬高比例相同)
    • 指定 mycan 的寬度與高度,分別是 figx 與 figy 喔!
    • 指定 mycan 的 CSS 樣式,其框線為 1px solid gray,預先了解畫布的方塊位置。
    • 設計一個 ratio 變數,內容為 800/1274 ,這是圖片縮小的比例。
  6. 設計 dicdraw() 函數,每次都會進行資傳系地圖的重新繪製 (因為畫布需要重整)
    • 使用 .clearRect(0,0,figx,figy) 清除整個畫布區域
    • 將圖片由 (0,0) 座標,及 1274x765 寬高,放入 (0,0) 座標,且為 figx*figy 的寬高上 (這樣就進行縮放了!)
    • 設計 .globalAlpha 為 1.0 的數值,這個是透明度。
    • 填滿的樣式為深藍色
    • 設計字體的 CSS 樣式為 "bold 40pt 標楷體" 的樣式
    • 使用實心字,填寫『資訊傳播系平面圖』的字樣,放置到座標 (50, 450) 的地方。
    • 將這個函數寫入到 mymain() 的最後一行執行,然後看看畫面是否正確
  7. 設計 dic() 函數:
    • 設計全域透明度 (.globalAlpha) 為 1.0 全不透明
    • 重新描繪 dicdraw() 函數
  8. 設計 i2511() 函數,目的在讓整個地圖變半透明,然後切割一個圓形位置到重點區域中,之後重新繪製地圖,讓圓形地區不透明而已。
    • 設計全域透明度 (.globalAlpha) 為 0.3 不透明
    • 重新描繪 dicdraw() 函數,然後到網頁點選 i2511 按鈕,看看顯示的狀態如何。
    • 設計全域透明度為 1.0
    • 利用 myfig.save() 儲存當下的樣式設定,這個目的是要避免等等圓形切割導致的問題
    • 開始路徑繪製 (.beginPath())
    • 繪製圓形,座標為 (864*ratio, 392*ratio) ,半徑是 50,角度從 0 到 2π 的圓形。
    • 利用 myfig.clip() 將圓形整個切割下來
    • 重新描繪 dicdraw() 函數。
    • 利用 myfig.restore() 復原之前儲存的樣式,這樣圓形設計就不會持續保留了。
  9. 若上述的展示沒有問題,請自行處理 i3501 與 i3502 的區域,自行找到教室的中心點座標,取代上述 (864,392) 的座標即可。
不同的圖像展示方式 不同的圖像展示方式

上面的樣式裡面,有關 .save() 與 .restore() 還有 .clip() 是挺有趣的:

  • .clip():
    當你描繪路徑結束之後,透過 .clip() 可以將描繪的路徑內容切出來,然後透過 .drawImage() 或者是 .fill() 或者是 .stroke() 等等, 就可以將切割出來的位置填上許多資料。一般來說, .clip() 內容大部分透過 .fill 填滿,或者是填入圖片的用途較多。
  • .save() 與 .restore() :
    可以儲存當下的樣式設定,以上面的案例來說,我們繪製圓形的不透明地圖時,需要進行裁切或者其他樣式的設定,那是僅有針對該樣式來設計, 之後需要回復到切割之前的樣式指定環境,這時就可以透過 .save() 將當下的樣式儲存起來,等到 .restore() 時,再恢復原有的樣式。 要注意, .save 與 .restore 主要在記載樣式,跟路徑等等其他設計無關喔!
  • 加上陰影

除了將重點區域圈起來之外,我能不能將該區域『放大』呢?說起來容易,但是做起來挺困難!主要還是因為我們的畫布與圖片的大小不一致, 因此,許多的座標都需要加上 ratio 之後,才以辦法找到正確的地區。另外,如果可以加上陰影的話,應該就會更炫了!如何加上陰影呢?

屬性說明
.shadowColor直接提供顏色色碼的功能
.shadowOffsetX向 X 軸偏移的像素,負值為陰影向左
.shadowOffsetY向 Y 軸偏移的像素,負值為陰影向上
.shadowBlur模糊度,0 為不模糊,數值越大越模糊

陰影的效果同樣需要在 .fill() 或 .stroke() 或 .clip() 之前就得要先設定,這樣才有辦法進行處理。此外,如果陰影只有某個區塊需要進行, 那麼進行陰影效果之前,還是得要透過 .save() 進行樣式儲存,然後在陰影處理完畢後再以 .restore() 復原樣式才好喔!

例題 11-3-3: 焦點之外,加上陰影與放大圖示的效果
  1. 將 unit11-3-2.php 另存成 unit11-3-3.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 修改 i2511() 函數的部份:
    • 在 myfig.arc 到 myfig.clip() 中間,插入底下的樣式:
      • 陰影的顏色為黃色
      • 陰影的模糊度設定為 20 (無須水平垂直位移)
      • 開始進行填滿 (.fill()) 的工作
    • 在 myfig.clip() 以及 myfig.restore() 中間,進行如下動作:
      • 刪除 dicdraw() 函數,不用原本的圖像了
      • 找到原圖中 i2511 的左上角座標,大概是 (800,335) 左右,整體教室大約是 250x250 寬高,因為整體放大, 所以最終放置到畫布中的 (800*ratio-20,335*ratio-20) 的座標,使用 250x250 原圖寬高, 將這些數據使用 .drawImage 繪製到畫布上
  3. 同理,自行找出 i3501, i3502 的左上方座標,進行同樣的修改設計。
不同的圖像展示方式
  • 變形功能

有學過 CSS 的同學應該都記得,有個 tranform 的玩意兒~裡面含有很多的函數!包括改變大小 (scale)、改變位置 (translate)、旋轉角度 (rotate) 等功能! 那麼,在 canvas 裡面能不能作到呢?應該是可以的吧!沒錯喔!甚至跟 CSS 也很類似!

方法說明
.scale(x, y)改變大小的比例,這個數值是比例,所以,1 代表不變,0.5 代表小一半,2 代表大一倍
.translate(x, y)改變初始位置的效果,水平位移 (x) 與垂直位移 (y)
.rotate(angle)從左上角為原點進行順時針旋轉的角度,角度同樣使用弧度。因此,轉 90 度需要的角度為 (2*π*90/360)

讓我們回想一下之前網頁設計的時候,曾經有繪製過同一個檔案旋轉 12 次,每次 30 度角的設計吧? 你可以參考 這裡的例題 6.4.A 的圖示看看。 既然有了 canvas 畫布,那麼可以簡單的達成這個目的嘛?應該是可以的!透過 .rotate 來處理即可喔!

只是旋轉的時候需要注意的是,旋轉預設使用左上角的座標作為原點來處理,例如底下的程式碼,可以產生四個方塊,四個方塊的相對位置如下所示:

mycan = document.getElementById('mycan');
myfig = mycan.getContext('2d');
myfig.translate(150,100);   // 先進行位移,向右移動一下,方便旋轉
var mycolor = [];
mycolor[1] = "blue";
mycolor[2] = "yellow";
mycolor[3] = "red";
mycolor[4] = "black";
for ( i=1; i<=4; i++){
	myfig.rotate(1/12*2*Math.PI);  // 開始旋轉
	myfig.strokeStyle = mycolor[i];
	myfig.strokeRect(0,0,70,70);   // 使用目前位置設計方塊
}
旋轉的概念

從上圖,我們可以知道,旋轉角度是會持續累加的!所以你可能還得要計算一下旋轉角度的累加情境~而且從旋轉的位置來看, 用左上角當原點,有的時候可能不會是我們要的。那怎辦?沒關係,在繪製圖示時,將圖示向左、向上各移動一半的距離, 就可以將旋轉的中心移動到畫面的中央了!因為上面我們用的是 70 像素的方塊,因此,你要讓方塊中心成為旋轉的原點時, 只要將 myfig.strokeRect(0,0,70,70); 改成 myfig.strokeRect(-35,-35,70,70); 即可!畫面會變成這樣:

旋轉的概念

現在,讓我們正式來處理企鵝轉一轉

例題 11-3-4: 將畫布的元素進行旋轉
  1. 設計 unit11-3-4.php 新檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
  2. 下載這個檔案,並且上傳到你的網頁空間
  3. 依據前面的方式,設計好 HTML 的 canvas 標籤區塊,設計 h1 標籤說明網頁的內容
  4. 當網頁全部載入之後,讓 javascript 執行 mymain 函數
  5. 設計 mymain 函數
    • 讓 mycan 變數取得 canvas 標籤控制權,然後設計寬高各為 500 像素,框線 1px 的畫布
    • 設計 myfig 為 canvas 畫布區域
    • 設計 myimg 為新的圖檔,且圖檔網址為剛剛上傳的那個 11-3-4a.png 的檔名
    • 使用 .translate(250,230) 左右,讓原點向右邊偏移過去,方便處理
    • 設計一個 for 迴圈,共執行 12 次 (12 張圖示),內容進行:
      • .rotate 旋轉,角度為 1/12*2*Math.PI
      • 繪製圖樣,因為圖示大小為 280x330,因此,原圖座標需要往左移動 -140, -165 才行!
不同的圖像展示方式

這就是簡易的 canvas 相關繪圖功能與圖片重製功能。下個章節我們再來聊聊 canvas 以及其他類似畫布的動畫設計功能。

11.4: 課後作業

  • 11-4-1、自訂 JSON 物件與列表

請依據底下的網頁內容,自行選擇 5 位老師以上的介紹內容,設計成為 JSON 的物件格式後,最終以表格輸出。

完成的作品應該會有點像底下這樣,要注意,需要配合迴圈,且專長的部份,應該也是另一個迴圈的展示才可以。

自訂 JSON 物件的練習
  • 11-4-2、處理 JSON 格式以及 Ajax 回傳的訊息處理

假設有個 API 小程式內容有點像底下的連結。請參考該連結資訊,列出所有的資料內容,將該內容匯入你的程式碼。

然後嘗試完成底下的任務:

  • 列出該 API 輸出資料的資料總筆數
  • 列出兩個可輸入的方框,讓使用者填入可以查詢的 (1)帳號名或 (2)使用者id
  • 按下查詢按鈕,即可進行查詢。但是,如果沒有填寫任何資料,(1)回應一個錯誤訊息以及 (2)讓上述的某個輸入框 focus。
  • 開始查詢時,會統計查詢到的結果,並列出該帳號的相關資訊。

完成後的圖示,會有點像底下這樣:

JSON 搭配 Ajax 物件 JSON 搭配 Ajax 物件 JSON 搭配 Ajax 物件
  • 11-4-3、描繪飛機或戰鬥機圖示

模仿本章 11-2-4 的練習,這次不是繪製飛碟,而是繪製戰鬥機,自行上網查詢看看有沒有適合的戰鬥機,可以用來取代砲台。 如果你有其他的想法,那就直接使用你的思考邏輯去繪製,最終繪製出一個 500x500 的 canvas 圖示,底下為一個簡易的示意圖, 同學們應該可以繪製的更好才對。

自訂 JSON 物件的練習

11.5: 參考資料