第 11 章 - JSON 表示法與 Canvas 初探
上次更新日期 2020/12/26
在射擊遊戲時,對於產生飛碟這件事,我們很單純的只是設計一個方塊並且填滿顏色而已,實在是醜醜的! 如果可能,讓真的飛碟圖示取代小方塊應該是比較好的。那圖示要去找小圖檔嘛?這樣換個圖示得要重新去處理小圖檔。 有沒有可能自己繪製小圖片?答案是肯定的!可以!透過 canvas 這個小東西,就能夠輕鬆順利的在瀏覽器上面繪製圖案了! 另外,為了方便資料的輸入/輸出,學習一下 JSON 也是很不錯的啊!這個章節我們主要來介紹 JSON 與 Canvas 喔!
學習目標:
- 自己設計 JSON 物件的資料格式與內容
- 由 API 搭配 Ajax 取得 JSON 格式的方法
- Canvas 物件的設計方式
- Canvas 的繪圖方式
- 用 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"
那就簡單得來應用一下:
- 新增 unit11-1-1.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將這個檔案的內容貼到 unit11-1-1.php 當中,並查閱一下相關顯示
- 將上面的 aq 變數貼入這個 unit11-1-1.php 檔案內
- 設計當整體網頁載入後,會去執行 mymain 函數
- 在 mymain 函數內,將相關的資訊填進去。最終的畫面需要像底下一樣:
- JSON 用於建立使用者資料的物件
如果你要提供一個關於你自己的物件,裡面包括你的大名、你的年紀、性別、生日、電話、多個興趣、暱稱等, 最簡單方法,可能就是透過 JSON 格式來建立了!請依據前一題的相關資料,實做底下這個案例:
- 新增 unit11-1-2.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將這個檔案的內容貼到 unit11-1-2.php 當中,並查閱一下相關顯示
- 設計當整體網頁載入後,會去執行 mymain 函數
- 在 mymain 函數內,將相關的資訊填進去。最終的畫面需要像底下一樣:
你或許會覺得,這不過就是個自我介紹啊~沒啥了不起。沒關係,現在,請隨便前往三個同學的網頁上面取得他的自我介紹, 然後將該介紹帶入自己的程式碼當中,變成一個 JSON 來的『陣列格式』的物件,然後透過該物件, 以 for 迴圈的功能,將大家的自我介紹資料以一個統一的格式來處理!這就是個重要的地方了!
- 將 unit11-1-2.php 另存新檔成 unit11-1-3.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 取得其他三位同學的自我介紹資料 (所以應該有 4 筆紀錄),然後變更自己的 JSON 格式,使得自我介紹的物件, 以陣列的方式來處理。這裡要特別注意的是,陣列格式與物件格式並不相同,因此,需要特別注意中括號與大括號的使用情境!
- 修改資料輸出的方式,讓該方式成為類似 for 迴圈的功能,將資訊依序輸出
- 最終的畫面需要像底下一樣:
- 透過 JSON.parse() 方法,轉純文字資料成為 JSON 物件
將剛剛建立的四個資料,放置到新檔 unit11-1-4.txt 檔案中,假裝這就是我們的資料庫 API 回傳的數據! 然後透過 Ajax 的功能,取得這個數據資料後,怎麼轉成 JSON 的資料呢?很簡單!就是透過 JSON.parse(變數) 這樣的格式來處置即可。 一般來說,我們從 API 裡面抓到的資訊,大部份使用的是純文字,因此,如何將純文字變成 JSON 的物件格式,當然就是很重要的一件事了! 在很多網站應用中,這是相當重要的技巧喔!
- 將 unit11-1-3.php 另存新檔成 unit11-1-4.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 新增 unit11-1-4.txt,將剛剛的 JSON 變數設定寫入檔案中,請再三確認這個檔案沒問題喔!
- 新增一個名為 getusers 的函數,內容為使用 Ajax 來設定取得 unit11-1-4.txt 的功能,使用的回應類型為 text 格式即可。 同時,當有任何狀態改變 (onreadystatechange) 時,執行 putusers 函數。
- 新增名為 putsers 的函數,內容為,判斷剛剛的 xhttp 變數是否正確?若正確,就取得 xhttp 的回應, 並將 (1)使用 trim() 方法,該回應的前後空白刪除;(2)設定 users 變數,內容就是 JSON.parse() 的結果。 (3)最終持行 myoutput() 函數。
- 設計 myoutput 函數,很簡單,將原本 mymain 的內容全部移動到這裡即可。
- 修改 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 之類的設計!要注意! 現在,讓我們來畫幾個實心與空心的方塊吧!
- 新增 unit11-2-1.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將 這個檔案的內容 加入到 unit11-2-1.php 裡面,並查看網頁相關資訊
- 讓整個網頁載入後,立刻執行 mymain 函數
- 讓 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 的方塊空間。
從上面,你也可以發現方塊的重疊,主要都是以後出現的覆蓋在先出現的方塊上面喔!跟一般 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 度角,所以可以使用下圖來思考 (圖示來源):
- 新增 unit11-2-2.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 使用上面的方法,想辦法完成底下的圖示測試看看:
還記得我們在 8-1-2 例題當中,透過圖片去載入星空圖嘛?現在讓我們來使用 Canvas 產生這張圖片看看!速度似乎比起放置圖片要快很多很多!
- 新增 unit11-2-3.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 產生一個 600x300 左右的 canvas 元素,並取得這個元素,產生 myfig 的可繪圖功能
- 先產生一個與 canvas 畫框相同大小的方塊,並且圖上黑色填滿
- 使用迴圈方式,執行 1000 次左右的繪圖,繪圖的內容需求使用圓形,因此需要:
- 亂數計算 x 座標,最大到 600
- 亂數計算 y 座標,最大到 300
- 亂數計算 r 半徑 (星星有大有小),最大到 3 或 4
- 設計顏色,如果 r 大於等於 2 的時候,給予白色,否則就給予灰色 (gray) 或銀色 (silver)
- 開始路徑、繪製圓形、設計 fillStyle 的顏色、填滿顏色、關閉路徑
- 線段的樣式
你可能也想要修改線段的樣式,包括線的寬度 (.lineWidth)、線兩頭的樣式 (.lineCap)、線條交叉接角的樣式 (.lineJoin) 等等,大致上常用的線段屬性如下:
.lineWidth = "10" // 單位是像素喔! .lineCap = "butt" // butt 無樣式、 round 圓弧、 square 方形,預設為 butt .lineJoin = "bevel" // bevel 無樣式、 round 圓弧、 miter 尖角,預設 bevel
根據上面的屬性設計,我們來繪製一個 UFO 看看喔!
- 新增 unit11-2-4.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 產生一個 500x500 左右的 canvas 元素,並取得這個元素,產生 myfig 的可繪圖功能
- 設計等等繪圖需要的線段參數,包括寬度為 20 像素、線段為圓角、線段交叉也是圓角
- 開始設計最上面的飛碟圓形部份,圓心大致在 250,200 左右,半徑設計 120 左右,設計出這個圓形, 然後先進行填滿,填滿的顏色可以使用米黃色,填滿之後再繪製框線即可。
- 開始設計反光條,圓心與上面的圓相同,半徑大概在 90 左右,繪製第一象限的扇形,因為是扇形,所以不需要 closePath 喔!
- 開始設計中間的碟子,大致上的幾個轉則的座標,可以參考 130,230 / 370,230 / 490,290 / 450,350 / 50,350 / 10,290 等, 因為是需要填滿的,因此得要先進行 closePath 之後,再填滿,再畫框線。顏色同樣自訂。
- 最下方的空間部份,懶惰的話,直接畫方形也可以!顏色也自訂。
- 最後才是那三個圓圈圈,同樣自行設計吧!
我們在第 9 章有使用砲彈打飛碟,當時的畫面真是有夠丑的!直接拿人家的圖示來處理也不好。現在,請使用 Canvas 繪製兩個元件, 一個是星空圖,直接拿來作為背景,一個是飛碟圖,直接拿來產生樣式,然後測試看看能不能適用於我們的遊戲中!?不過,如果是憑空出現的 canvas 物件, 得要透過 .appendChild 或者是將 canvas 轉成可視的圖檔 URL 來處理:
方法 | 說明 |
---|---|
.toDataURL() | 將 canvas 物件轉成一個瀏覽器認識的圖片網址列項目,然後透過 .style.backgroundImage = "url('imgfile')" 的樣式, 就可以將該圖片處理使用了。但是要記得 imgfile 的網址,使用 .toDataURL() 轉出來的變數即可 |
那就讓我們來美化一下射擊遊戲吧!
- 將 unit09-4-4.php 另存成 unit11-2-5.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 新增一個函數,名稱為 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 ,將上面的網址列塞進來。
- 增加一個名為 myufo() 的函數,目的就在產生每一個飛碟!所以,最終需要回傳 canvas 的飛碟物件:
- 先將 11-2-4.php 的 javascript 程式碼貼過來,同樣使用 document.createElement('canvas') 建立一個新的元件
- 將 border 拿掉,或者是框線設為 0
- 最後面 return mycan; 之類的,將物件回傳!
- 修改 createufo() 的內容,讓原本的方塊變成飛碟!
- 讓原本 ufo 產生新物件的方法,變成 ufo = myufo() 這樣的格式
- 將 border, backgroundColor, padding, maring 與 height 通通拿掉,剩下 width 為 50 像素即可。
- 修改 CSS 的設定,以使符合我們的環境!相關 CSS 設定請自行處理。
因為整個畫面都是 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) 貼到新開的視窗上面囉!
- 將 unit11-2-3.php 另存成 unit11-2-6.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將 mycan 與 myfig 設計成為全域變數,並且修改原本寫到 mymain 函數中的變數設計方式
- 在 html 原始碼當中,新增一個 button ,且按下該 button 之後,會執行 dlfig() 函數
- 新增 dlfig() 函數:
- 建立 dlfig 變數,內容為新增 IMG 元素
- 讓 dlfig 的圖片來源,設計為 mycan 的 toDataURL() 結果
- 使用 window.open 的方法,開新視窗,網址為空的,視窗名稱為 'newwin' 這樣
- 透過 newwin.document.body.appendChild 的方法,將 dlfig 加到新視窗當中。
透過這簡單的動作,你的 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 網站的說明,圖示有點像這樣: (圖示來源:這裡)
你可以根據自己的需求,將某張圖示整個放置到畫布中,也可以將某張圖示的某個區塊抓出來,放置到畫布的某個區塊去! 應用上也是相當有趣的!
- 新增 unit11-3-1.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將這個檔案的內容加入到 unit11-3-1.php 當中,並瀏覽一下輸出的結果。
- 將這個圖檔下載,並上傳到你的 images 目錄下。
- 設計當載入整體網頁完成後,就執行 mymain 函數,同時設計 3 個全域變數,一個是 mycan 一個是 myfig 一個是 myimg, 分別作為 canvas 元素、繪圖元素、圖片元素之用
- 設計 mymain 函數的內容:
- 設計 mycan 變數,內容是取得 mycan 的元素控制權
- 設計 myfig 變數,內容就是讓 mycan 的 canvas 畫布
- 設計 myimg 變數,內容就是增加一個 IMG 的元素控制權
- 讓 myimg 的圖片網址成為剛剛上傳的那個 google 地圖檔案檔名 (注意,可能會有上層目錄喔!)
- 設計兩個變數,分別是 figx 與 figy,個別是 800 與 500 兩個數值,預計作為畫布的寬與高
- 指定 mycan 的寬度與高度,分別是 figx 與 figy 喔!
- 指定 mycan 的 CSS 樣式,其框線為 1px solid gray,預先了解畫布的方塊位置。
- 設計 asia() 函數內容,預計載入剛剛的圖片到 Canvas 畫布中
- 先設定等等要填滿畫布的顏色為銀色 (silver)
- 依據座標 (0,0) 到 (figx,figy) 之間,填滿一個方形到畫布中
- 載入 myimg 這張圖到畫布的 (0,0) 座標上,以原圖大小載入即可。
- 設計完成後,按下按鈕看看結果,若一切順利,將 asia() 函數也寫入到 mymain() 函數內的最後一個指令。
- 設計 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 的寬高。
- 設計 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)
- 設計 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[,最大寬]) | 與前一個相似,只是字體為空心字 |
現在,透過前一個例題的類似案例,我們讓使用者按下某個按鈕時,可以展示在圖片上面比較顯眼的位置上。舉例來說,可以提示使用者的位置或者是想要查閱的地區。 同時提供 我們來看一下底下的案例:
- 新增 unit11-3-2.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 將這個檔案的內容加入到 unit11-3-2.php 當中,並瀏覽一下輸出的結果。
- 將這個圖檔下載,並上傳到你的 images 目錄下。
- 設計當載入整體網頁完成後,就執行 mymain 函數,同時設計 3 個全域變數,一個是 mycan 一個是 myfig 一個是 myimg, 分別作為 canvas 元素、繪圖元素、圖片元素之用
- 設計 mymain 函數的內容:
- 設計 mycan 變數,內容是取得 mycan 的元素控制權
- 設計 myfig 變數,內容就是讓 mycan 的 canvas 畫布
- 設計 myimg 變數,內容就是增加一個 IMG 的元素控制權
- 讓 myimg 的圖片網址成為剛剛上傳的那個資傳系地圖檔案檔名 (注意,可能會有上層目錄喔!)
- 設計兩個變數,分別是 figx 與 figy,個別是 800 與 480 兩個數值,預計作為畫布的寬與高 (寬高設計過了,與圖片寬高比例相同)
- 指定 mycan 的寬度與高度,分別是 figx 與 figy 喔!
- 指定 mycan 的 CSS 樣式,其框線為 1px solid gray,預先了解畫布的方塊位置。
- 設計一個 ratio 變數,內容為 800/1274 ,這是圖片縮小的比例。
- 設計 dicdraw() 函數,每次都會進行資傳系地圖的重新繪製 (因為畫布需要重整)
- 使用 .clearRect(0,0,figx,figy) 清除整個畫布區域
- 將圖片由 (0,0) 座標,及 1274x765 寬高,放入 (0,0) 座標,且為 figx*figy 的寬高上 (這樣就進行縮放了!)
- 設計 .globalAlpha 為 1.0 的數值,這個是透明度。
- 填滿的樣式為深藍色
- 設計字體的 CSS 樣式為 "bold 40pt 標楷體" 的樣式
- 使用實心字,填寫『資訊傳播系平面圖』的字樣,放置到座標 (50, 450) 的地方。
- 將這個函數寫入到 mymain() 的最後一行執行,然後看看畫面是否正確
- 設計 dic() 函數:
- 設計全域透明度 (.globalAlpha) 為 1.0 全不透明
- 重新描繪 dicdraw() 函數
- 設計 i2511() 函數,目的在讓整個地圖變半透明,然後切割一個圓形位置到重點區域中,之後重新繪製地圖,讓圓形地區不透明而已。
- 設計全域透明度 (.globalAlpha) 為 0.3 不透明
- 重新描繪 dicdraw() 函數,然後到網頁點選 i2511 按鈕,看看顯示的狀態如何。
- 設計全域透明度為 1.0
- 利用 myfig.save() 儲存當下的樣式設定,這個目的是要避免等等圓形切割導致的問題
- 開始路徑繪製 (.beginPath())
- 繪製圓形,座標為 (864*ratio, 392*ratio) ,半徑是 50,角度從 0 到 2π 的圓形。
- 利用 myfig.clip() 將圓形整個切割下來
- 重新描繪 dicdraw() 函數。
- 利用 myfig.restore() 復原之前儲存的樣式,這樣圓形設計就不會持續保留了。
- 若上述的展示沒有問題,請自行處理 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() 復原樣式才好喔!
- 將 unit11-3-2.php 另存成 unit11-3-3.php 檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 修改 i2511() 函數的部份:
- 在 myfig.arc 到 myfig.clip() 中間,插入底下的樣式:
- 陰影的顏色為黃色
- 陰影的模糊度設定為 20 (無須水平垂直位移)
- 開始進行填滿 (.fill()) 的工作
- 在 myfig.clip() 以及 myfig.restore() 中間,進行如下動作:
- 刪除 dicdraw() 函數,不用原本的圖像了
- 找到原圖中 i2511 的左上角座標,大概是 (800,335) 左右,整體教室大約是 250x250 寬高,因為整體放大, 所以最終放置到畫布中的 (800*ratio-20,335*ratio-20) 的座標,使用 250x250 原圖寬高, 將這些數據使用 .drawImage 繪製到畫布上
- 在 myfig.arc 到 myfig.clip() 中間,插入底下的樣式:
- 同理,自行找出 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); 即可!畫面會變成這樣:
現在,讓我們正式來處理企鵝轉一轉
- 設計 unit11-3-4.php 新檔案,並在 index.php 裡面加上相關的超連結,target 指向 js 視窗。
- 下載這個檔案,並且上傳到你的網頁空間
- 依據前面的方式,設計好 HTML 的 canvas 標籤區塊,設計 h1 標籤說明網頁的內容
- 當網頁全部載入之後,讓 javascript 執行 mymain 函數
- 設計 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 的物件格式後,最終以表格輸出。
完成的作品應該會有點像底下這樣,要注意,需要配合迴圈,且專長的部份,應該也是另一個迴圈的展示才可以。
- 11-4-2、處理 JSON 格式以及 Ajax 回傳的訊息處理
假設有個 API 小程式內容有點像底下的連結。請參考該連結資訊,列出所有的資料內容,將該內容匯入你的程式碼。
然後嘗試完成底下的任務:
- 列出該 API 輸出資料的資料總筆數
- 列出兩個可輸入的方框,讓使用者填入可以查詢的 (1)帳號名或 (2)使用者id
- 按下查詢按鈕,即可進行查詢。但是,如果沒有填寫任何資料,(1)回應一個錯誤訊息以及 (2)讓上述的某個輸入框 focus。
- 開始查詢時,會統計查詢到的結果,並列出該帳號的相關資訊。
完成後的圖示,會有點像底下這樣:
- 11-4-3、描繪飛機或戰鬥機圖示
模仿本章 11-2-4 的練習,這次不是繪製飛碟,而是繪製戰鬥機,自行上網查詢看看有沒有適合的戰鬥機,可以用來取代砲台。 如果你有其他的想法,那就直接使用你的思考邏輯去繪製,最終繪製出一個 500x500 的 canvas 圖示,底下為一個簡易的示意圖, 同學們應該可以繪製的更好才對。