H5和微信小遊戲 Canvas API 整理
前言
這段時間閒下來,系統學習了微信小程式和微信小遊戲,發現還是挺有意思的。現在微信小遊戲的開發都離不開遊戲引擎,用原生小遊戲開發工具開發的很少很少。但是畢竟我不是專業遊戲開發,所有遊戲引擎就不搞了,我們就單純來看原生微信小遊戲開發。
原生微信小遊戲開發全是js,介面上所有的可見元素都是通過js canvas畫出來的。所以這就是這篇部落格的內容,我們要來整理下微信小遊戲Canvas的繪圖api。為什麼要單獨寫篇部落格整理呢,因為微信小遊戲的官方文件並沒有提供(反正我是沒有找到)。因為微信小遊戲的canvas繪製和H5的canvas繪製基本沒有卻別,這本身是屬於H5的範疇,並不是微信小遊戲的範疇,所以,廢話說了這麼多,下面開始正文。
(1)獲取canvas
要使用canvas繪製,首先得獲取到canvas例項,在H5中獲取canvas和獲取其它標籤一樣,通過document獲取。
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d");
但是微信小程式做了封裝,它不允許使用者直接操作dom,所以不能通過document獲取canvas,而是提供了一個微信api。
let canvas =wx.createCanvas(); let ctx = canvas.getContext('2d');
(2)填充色和線條色
填充色:線條封閉區域內全部著色
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.fillStyle = "#0000ff"; ctx.fillRect(20, 20, 150, 100);

線條色:只給線條著色,著色寬度就是線條寬度
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.strokeStyle = "#0000ff"; ctx.strokeRect(20, 20, 150, 100);

(3)陰影
陰影顏色:陰影的本質就是光線被擋而形成的暗淡,所以建議不要給陰影設定很鮮豔的顏色
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.shadowColor = "black"; ctx.fillStyle = "blue"; ctx.fillRect(20, 20, 100, 80);

陰影大小:所謂陰影大小就是陰影擴散的範圍
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.shadowBlur = 20; ctx.shadowColor = "black"; ctx.fillStyle = "blue"; ctx.fillRect(20, 20, 100, 80);

陰影偏移:光源的方位決定的陰影投射的方向
var canvas = document.getElementById("myCanvas"); var ctx = canvas .getContext("2d"); ctx.shadowOffsetX = 20; ctx.fillStyle = "blue"; ctx.fillRect(20, 20, 100, 80);

var canvas = document.getElementById("myCanvas"); var ctx = canvas .getContext("2d"); ctx.shadowOffsetY = 20; ctx.fillStyle = "blue"; ctx.fillRect(20, 20, 100, 80);

(4)漸變
漸變就要涉及到漸變顏色和漸變方向,H5中漸變方向是通過起始點決定的,在createLinearGradient方法中傳入兩個點的座標,這兩個點的連線方向就是漸變的方向。設定漸變顏色是通過addColorStop方法新增。
var canvas = document.getElementById("myCanvas"); var ctx = canva.getContext("2d"); var my_gradient = ctx.createLinearGradient(0, 0, 0, 170); my_gradient.addColorStop(0, "black"); my_gradient.addColorStop(1, "white"); ctx.fillStyle = my_gradient; ctx.fillRect(20, 20, 150, 100);

我們嘗試一下從做到右的漸變
var canvas = document.getElementById("myCanvas"); var ctx = canva.getContext("2d"); var my_gradient = ctx.createLinearGradient(0, 0, 170, 0); my_gradient.addColorStop(0, "black"); my_gradient.addColorStop(1, "white"); ctx.fillStyle = my_gradient; ctx.fillRect(20, 20, 150, 100);

漸變色可以新增多個,我們嘗試新增三個漸變色:黑 -> 紅 -> 白
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var my_gradient = ctx.createLinearGradient(0, 0, 170, 0); my_gradient.addColorStop(0, "black"); my_gradient.addColorStop(0.5, "red"); my_gradient.addColorStop(1, "white"); ctx.fillStyle = my_gradient; ctx.fillRect(20,20,150,100);

當然也可以給線條設定漸變
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var gradient = ctx.createLinearGradient(0, 0, 170, 0); gradient.addColorStop("0", "magenta"); gradient.addColorStop("0.5", "blue"); gradient.addColorStop("1.0", "red"); ctx.strokeStyle = gradient; ctx.lineWidth = 5; ctx.strokeRect(20, 20, 150, 100);

甚至可以給文字設定漸變
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var gradient = ctx.createLinearGradient(0, 0, 170, 0); gradient.addColorStop("0", "magenta"); gradient.addColorStop("0.5", "blue"); gradient.addColorStop("1.0", "red"); ctx.strokeStyle = gradient; ctx.strokeText("Big smile!", 10, 50);

(5)元素重複
createPattern() 方法在指定的方向內重複指定的元素。元素可以是圖片、視訊,或者其他 <canvas> 元素。被重複的元素可用於繪製/填充矩形、圓形或線條等等。
重複模式:repeat、repeat-x、repeat-y、no-repeat
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.clearRect(0, 0, 300, 150); var img = document.getElementById("img") var pat = ctx.createPattern(img, "repeat"); ctx.rect(0, 0, 260, 130); ctx.fillStyle = pat; ctx.fill();

(6)放射漸變
沒有和漸變放在一起,主要是函式不一樣
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var grd = ctx.createRadialGradient(75, 50, 5, 90, 60, 100); grd.addColorStop(0, "red"); grd.addColorStop(1, "white"); ctx.fillStyle = grd; ctx.fillRect(10, 10, 150, 100);

引數 | 描述 |
---|---|
x0 | 漸變的開始圓的 x 座標 |
y0 | 漸變的開始圓的 y 座標 |
r0 | 開始圓的半徑 |
x1 | 漸變的結束圓的 x 座標 |
y1 | 漸變的結束圓的 y 座標 |
r1 | 結束圓的半徑 |
這個引數理解起來有點麻煩,我們改下程式碼再看效果就明顯多了。
var canvas = document.getElementById("myCanvas"); var ctx =canvas.getContext("2d"); var grd = ctx.createRadialGradient(75, 50, 50, 150, 50, 50); grd.addColorStop(0, "red"); grd.addColorStop(1, "blue"); ctx.fillStyle = grd; ctx.fillRect(10, 10, 150, 100);

從上圖我們可以看出,漸變區域是由兩個圓決定的,超出兩個圓的區域,漸變停止,用外圍畫素填充。
(7)新增漸變色
前面講了這麼多漸變,最重要的一個函式卻沒有說,所有的漸變色都通過addColorStop()方法新增的。
addColorStop()這個函式是可以新增很多顏色,按照順序, 一次均勻漸變。
var canvas =document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var grd = ctx.createLinearGradient(0, 0, 170, 0); grd.addColorStop(0, "black"); grd.addColorStop("0.3", "magenta"); grd.addColorStop("0.5", "blue"); grd.addColorStop("0.6", "green"); grd.addColorStop("0.8", "yellow"); grd.addColorStop(1, "red"); ctx.fillStyle = grd; ctx.fillRect(20, 20, 150, 100);

(8)線端樣式
H5中支援三種線端樣式:
值 | 描述 |
---|---|
butt | 預設,向線條的每個末端新增平直的邊緣 |
round | 向線條的每個末端新增圓形線帽 |
square | 向線條的每個末端新增正方形線帽 |
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.lineCap = "round"; ctx.moveTo(20, 20); ctx.lineTo(20, 200); ctx.stroke();

(9)線交樣式
H5中支援三種線端樣式:
值 | 描述 |
---|---|
bevel | 建立斜角 |
round | 建立圓角 |
miter | 預設,建立尖角 |
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.lineJoin = "round"; ctx.moveTo(20, 20); ctx.lineTo(100, 50); ctx.lineTo(20, 100); ctx.stroke();

斜接長度
這裡不得不提一個很冷門的屬性叫斜接長度,它是隻兩條線段相交時,並且lineJoin="miter",內角和外交的距離。

... ctx.lineJoin = "miter"; ctx.miterLimit = 5; ...

為了避免斜接長度過長,我們可以使用 miterLimit 屬性。如果斜接長度超過 miterLimit 的值,邊角會以 lineJoin 的 "bevel" 型別來顯示

(10)設定線寬
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.lineWidth = 10; ctx.strokeRect(30, 30, 200, 80);

(11)繪製矩形
我們可以直接呼叫fillRect繪製矩形
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.fillStyle = "#00bcd4"; ctx.fillRect(20, 20, 150, 100);

也可以先呼叫rect,再呼叫fill
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.fillStyle = "#00bcd4"; ctx.rect(20, 20, 150, 100); ctx.fill();
把fill換成stroke也是一樣,效果一個是填充,一個是描邊。
(12)清除畫素
clearRect()方法可以清除一塊區域的所有畫素
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.fillStyle = "red"; ctx.fillRect(0, 0, 300, 150); ctx.clearRect(20, 20, 100, 50);

(13)貝塞爾曲線
這是一個大頭,和Android一樣,貝塞爾曲線是構建平面圖形很重要的一個知識點。H5中提供的貝塞爾曲線api還沒有Android中豐富,但是也足夠用了。
函式 | 釋義 |
---|---|
beginPath() | 開始一段路徑 |
moveTo() | 移動至一個新的起點,注意區分和beginPath的差異 |
closePath() | 關閉一段路徑 |
lineTo() | 連線到指定點 |
clip() | 從畫布中裁剪出一個可視區域,只有被剪下區域內的畫素才可見 |
quadraticCurveTo() | 二次貝塞爾曲線 |
bezierCurveTo() | 三階貝塞爾曲線 |
arc() | 建立圓弧 |
arcTo() | 建立介於兩個切線之間的弧 |
isPointInPath() | 判斷一個點是不是在封閉路徑內 |
先畫個最簡單的路徑
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(300, 150); ctx.stroke();

再畫個三次貝塞爾曲線
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.moveTo(20, 20); ctx.bezierCurveTo(20, 100, 200, 100, 200, 20); ctx.stroke();

畫弧線
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.arc(100, 75, 50, 0, Math.PI + Math.PI / 2); ctx.stroke();

連線切線弧
抱歉,這個api的引數我看了半天,還是沒懂,和我預期效果不一樣。
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.beginPath(); ctx.moveTo(20, 20);// 建立開始點 ctx.lineTo(100, 20);// 建立水平線 ctx.arcTo(150, 20, 150, 70, 50); // 建立弧 ctx.lineTo(150, 120);// 建立垂直線 ctx.stroke();// 進行繪製
(14)畫布操作
在任何繪圖語言中,都少不了操作畫布,js也一樣,canvas也支援幾種操作。
縮放
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.strokeRect(5, 5, 25, 15); ctx.scale(2, 2); ctx.strokeRect(5, 5, 25, 15); ctx.scale(2, 2); ctx.strokeRect(5, 5, 25, 15); ctx.scale(2, 2); ctx.strokeRect(5, 5, 25, 15);

旋轉
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.rotate(20 * Math.PI / 180); ctx.fillRect(50, 20, 100, 50);

位移
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.fillRect(10, 10, 100, 50); ctx.translate(70, 70); ctx.fillRect(10, 10, 100, 50);

變換
繪製一個矩形;通過 transform() 新增一個新的變換矩陣,再次繪製矩形;新增一個新的變換矩陣,然後再次繪製矩形。請注意,每當您呼叫 transform() 時,它都會在前一個變換矩陣上構建
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.fillStyle="yellow"; ctx.fillRect(0, 0, 250, 100) ctx.transform(1, 0.5, -0.5, 1, 30, 10); ctx.fillStyle="red"; ctx.fillRect(0, 0, 250, 100); ctx.transform(1, 0.5, -0.5, 1, 30, 10); ctx.fillStyle="blue"; ctx.fillRect(0, 0, 250, 100);

transform() 方法替換當前的變換矩陣。它以下面描述的矩陣來操作當前的變換矩陣:
ace bdf 001
引數 | 描述 |
---|---|
a | 水平縮放繪圖 |
b | 水平傾斜繪圖 |
c | 垂直傾斜繪圖 |
d | 垂直縮放繪圖 |
e | 水平移動繪圖 |
f | 垂直移動繪圖 |
重置變換矩陣
不管之前的變換矩陣是什麼,setTransform()都會重置掉,然後構建新的變換矩陣。所以在下面的例子中,不會顯示紅色矩形,因為它在藍色矩形下面。
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.fillStyle = "yellow"; ctx.fillRect(0, 0, 250, 100) ctx.setTransform(1, 0.5, -0.5, 1, 30, 10); ctx.fillStyle = "red"; ctx.fillRect(0, 0, 250, 100); ctx.setTransform(1, 0.5, -0.5, 1, 30, 10); ctx.fillStyle = "blue"; ctx.fillRect(0, 0, 250, 100);

(15)字型
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.font = "italic small-caps bold 40px Arial"; ctx.fillStyle = "red"; ctx.fillText("Hello World", 10, 50);

值 | 描述 |
---|---|
font-style | 規定字型樣式。可能的值:normal italic oblique |
font-variant | 規定字型變體。可能的值:normal small-caps |
font-weight | 規定字型的粗細。可能的值:normal bold bolder lighter 100 200 300 400 500 600 700 800 900 |
font-size / line-height | 規定字號和行高,以畫素計。 |
font-family | 規定字體系列。 |
caption | 使用標題控制元件的字型(比如按鈕、下拉列表等)。 |
icon | 使用用於標記圖示的字型。 |
menu | 使用用於選單中的字型(下拉列表和選單列表)。 |
message-box | 使用用於對話方塊中的字型。 |
small-caption | 使用用於標記小型控制元件的字型。 |
status-bar | 使用用於視窗狀態列中的字型。 |
(16)文字對齊基線
水平基線
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); // 在位置 150 建立藍線 ctx.strokeStyle = "blue"; ctx.moveTo(150, 20); ctx.lineTo(150, 170); ctx.stroke(); ctx.font = "15px Arial"; // 顯示不同的 textAlign 值 ctx.textAlign = "start"; ctx.fillText("textAlign = start", 150, 60); ctx.textAlign = "end"; ctx.fillText("textAlign = end", 150, 80); ctx.textAlign = "left"; ctx.fillText("textAlign = left", 150, 100); ctx.textAlign = "center"; ctx.fillText("textAlign = center", 150, 120); ctx.textAlign = "right"; ctx.fillText("textAlign = right", 150, 140);

值 | 描述 |
---|---|
start | 預設,文字在指定的位置開始。 |
end | 文字在指定的位置結束。 |
center | 文字的中心被放置在指定的位置。 |
left | 文字左對齊。 |
right | 文字右對齊。 |
垂直基線
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); //在位置 y=100 繪製藍色線條 ctx.strokeStyle="blue"; ctx.moveTo(5,100); ctx.lineTo(395,100); ctx.stroke(); ctx.font = "20px Arial" //在 y = 200 以不同的 textBaseline 值放置每個單詞 ctx.textBaseline = "top"; ctx.fillText("Top", 5, 100); ctx.textBaseline = "bottom"; ctx.fillText("Bottom", 50, 100); ctx.textBaseline = "middle"; ctx.fillText("Middle", 120, 100); ctx.textBaseline = "alphabetic"; ctx.fillText("Alphabetic", 190, 100); ctx.textBaseline = "hanging"; ctx.fillText("Hanging", 290, 100);

值 | 描述 |
---|---|
alphabetic | 預設。文字基線是普通的字母基線。 |
top | 文字基線是 em 方框的頂端。 |
hanging | 文字基線是懸掛基線。 |
middle | 文字基線是 em 方框的正中。 |
ideographic | 文字基線是表意基線。 |
bottom | 文字基線是 em 方框的底端。 |

(17)文字測量
注意:這個測量只能拿到寬度,不要想當然地取高度。
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.font = "25px Arial"; var txt = "Hello World" ctx.fillText("width:" + ctx.measureText(txt).width, 10, 50); ctx.fillText(txt, 10, 100);

(18)繪製圖片
繪製圖片提供了三個層級的api:簡單繪製、可控大小、可控裁剪
在看程式碼之前有必要說一下,和獲取canvas物件一樣,微信小遊戲和H5獲取image物件也不一樣,H5中是通過document.getElementById()獲取的,而微信小遊戲是通過wx.createImage()函式獲取的。
簡單繪製
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var img = document.getElementById("image"); ctx.drawImage(img, 10, 10);

可控大小
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var img = document.getElementById("image"); ctx.drawImage(img, 10, 10, 240, 160);

可控裁剪
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var img = document.getElementById("image"); ctx.drawImage(img, 90, 130, 90, 80, 20, 20, 90, 80);

(19)ImageData
這是一個比較好玩的類,它定義一個Image的資料,我們可以自己建立一個空的ImageData,然後手動給每一個畫素設定RGBA。
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var imageData = ctx.createImageData(100, 100); for (var i = 0;i < imageData.data.length; i+=4) { imageData.data[i + 0] = 255; imageData.data[i + 1] = 0; imageData.data[i + 2] = 0; imageData.data[i + 3] = 255; } ctx.putImageData(imageData, 10, 10);

createImageData方法會建立一個空的ImageData,它是一個數組,陣列長度是width * height * 4。每連續的4位代表一個畫素,分別是R、G、B、A,預設都是0。所以懂點色彩基礎的都知道,預設就是全透明黑色。
上面例子中,我們給每個畫素都賦值綠色,最後呼叫ctx.putImageData把畫素繪製到螢幕上。
另外,還提供了一個方法可以根據一個ImageData建立一個同樣大小的ImageData,但是不會複製資料。
var imageData = context.createImageData(imageData);
另外,還有一個更神奇的方法:getImageData,這個方法可以獲取螢幕上任意區域的畫素資訊。
var imgData = ctx.getImageData(10, 10, 100, 100);
利用這個可以實現一些很好玩的效果,比如對螢幕影象做色彩反向。
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var img = document.getElementById("image"); ctx.drawImage(img, 0, 0); var imgData = ctx.getImageData(0, 0, c.width, c.height); // 反轉顏色 for (var i = 0; i < imgData.data.length; i += 4) { imgData.data[i] = 255 - imgData.data[i]; imgData.data[i + 1] = 255 - imgData.data[i + 1]; imgData.data[i + 2] = 255 - imgData.data[i + 2]; imgData.data[i + 3] = 255; } ctx.putImageData(imgData, 0, 0);
稍微有點色彩理論基礎的都知道,255 - color就等於color的反響色。當然這是最簡單的,你還可以做很多其它的效果,比如老照片,浮雕等等。
(20)全域性透明度
globalAlpha可以設定全域性透明度,不同顏色會疊加顯示。
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.fillStyle = "red"; ctx.fillRect(20, 20, 75, 50); // 調節透明度 ctx.globalAlpha = 0.2; ctx.fillStyle = "blue"; ctx.fillRect(50, 50, 75, 50); ctx.fillStyle = "green"; ctx.fillRect(80, 80, 75, 50);

(21)圖層混合模式
這個和Android中的XFermode差不多。
var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); ctx.fillStyle = "red"; ctx.fillRect(20, 20, 75, 50); ctx.globalCompositeOperation = "source-over"; ctx.fillStyle = "blue"; ctx.fillRect(50, 50, 75, 50); ctx.fillStyle = "red"; ctx.fillRect(150, 20, 75, 50); ctx.globalCompositeOperation = "destination-over"; ctx.fillStyle = "blue"; ctx.fillRect(180, 50, 75, 50);

值 | 描述 |
---|---|
source-over | 預設。在目標影象上顯示源影象。 |
source-atop | 在目標影象頂部顯示源影象。源影象位於目標影象之外的部分是不可見的。 |
source-in | 在目標影象中顯示源影象。只有目標影象內的源影象部分會顯示,目標影象是透明的。 |
source-out | 在目標影象之外顯示源影象。只會顯示目標影象之外源影象部分,目標影象是透明的。 |
destination-over | 在源影象上方顯示目標影象。 |
destination-atop | 在源影象頂部顯示目標影象。源影象之外的目標影象部分不會被顯示。 |
destination-in | 在源影象中顯示目標影象。只有源影象內的目標影象部分會被顯示,源影象是透明的。 |
destination-out | 在源影象外顯示目標影象。只有源影象外的目標影象部分會被顯示,源影象是透明的。 |
lighter | 顯示源影象 + 目標影象。 |
copy | 顯示源影象。忽略目標影象。 |
xor | 使用異或操作對源影象與目標影象進行組合。 |