前言
微信小游戏推出后一度爆火,一个简单的跳一跳让大家玩的不亦乐乎,那么很多人就在想自己尝试学学微信小游戏,开发一个属于自己的微信小游戏。现在微信小游戏的开发都离不开游戏引擎,用原生小游戏开发工具开发的很少很少。但是毕竟我不是专业游戏开发,所有游戏引擎就不搞了,我们就单纯来看原生微信小游戏开发。
原生微信小游戏开发全是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() 方法替换当前的变换矩阵。它以下面描述的矩阵来操作当前的变换矩阵:
a c e
b d f
0 0 1
参数 | 描述 |
---|---|
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 | 使用异或操作对源图像与目标图像进行组合。 |