手记

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() 方法替换当前的变换矩阵。它以下面描述的矩阵来操作当前的变换矩阵:

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使用异或操作对源图像与目标图像进行组合。



作者:大公爵
链接:https://www.jianshu.com/p/4b2a4f99ca46


6人推荐
随时随地看视频
慕课网APP

热门评论

但是,请问怎么图片,没法显示?

请问怎么消除叠加在图像上面的图片,然后显示下面图像的内容,不是消除重画,比如写了个小提示,点击后弹出提示,但是怎么去掉画在最上面的提示图像或文字,然后显示原本没提示时的内容呢?是全JS写的,不参入html。使用clearRect就是去掉了那一块区域而已,不是想要的效果,难道在画很多东西的时候就为了去掉一个小东西就要全部重新画吗

查看全部评论