高级Canvas绘图
高级Canvas绘图
绘制图像
绘制上下文提供了drawImage()方法
,用于在画面上绘制图片。使用这个方法很简单,调用它的时候传入相应的图片对象及起点坐标即可:
js
ctx.drawImage(img, 10, 10)
虽然,在调用drawImage()
之前,需要准备好图片对象。HTML5为此提供了三个方案:
- 使用
createImageData()
一个像素一个像素地创建图像。 - 使用网页中已有的
<img>元素
:
html
<img id="arrow_left" src="arrow_left.png">
js
var img = document.getElementById('arrow_left');
ctx.drawImage(img, 10, 10);
- 在代码中创建一个图片对象,然后把一个外部图片加载进来。这个方案有一个缺点,即必须先等待图片加载完毕,然后才能把图片对象传递给
drawImage()
使用。
js
// 创建图片对象
var img = new Image();
// 加载图片文件
img.src = 'maze.png';
img.onload = function () {
ctx.drawImage(img, 0, 0);
}
裁剪、切割和伸缩图片
drawImage()
接受一些可选参数,可以影响在画面上绘制图片的方式。比如,如果想改变图片的大小,可以添加宽度和高度:
js
ctx.drawImage(img, 10, 10, 30, 30);
如果想裁剪掉一部分图片,可以再为drawImage()
传入4个参数,这4个参数从图片对象参数后面开始:
js
ctx.drawImage(img, source_x, source_y, source_width, source_height, x, y, width, height);
绘制文本
首先,在绘制文本之前要设置绘图上下文的font属性
。这个属性的值是一个字符串,与设置CSS的font属性
时使用的“多合一”的值相同。
js
ctx.font = '20px Arial';
// 或者
ctx.font = '20px Verdana,sans-serif';
// 或者,实现加粗效果,要把它放在字符串开头
ctx.font = 'bold 20px Arial';
设置好字体后,就可以调用fillText()
绘制文本内容了。
js
ctx.textBaseline = 'top';
ctx.fillStyle = 'black';
ctx.fillText('这里是一行文本内容!', 10, 10);
注意,fillText()
每次只能绘制一行文本,如果要绘制多行文本,那只能多次调用。
除了fillText()
,还有另一个绘制文本的方法:strokeText()
。这个方法用于绘制文本的轮廓,轮廓的颜色取自strokeStyle属性
,而轮廓的宽度取自lineWidth属性
。
js
ctx.font = 'bold 40px Verdana,sans-serif';
ctx.lineWidth = 1;
ctx.strokeStyle = 'red';
ctx.StrokeText('Hello Canvas', 20, 50);
使用strokeText()
时,文本的中部是空白。
阴影与填充
添加阴影
为绘制的任何内容添加阴影是Canvas
最便利的功能。
属性 | 说明 |
---|---|
shadowColor | 设置阴影颜色 |
shadowBlur | 设置阴影的模糊程度 |
shadowOffsetX、shadowOffsetY | 设置阴影相对于内容的位置 |
js
// 绘制矩形阴影
ctx.rect(20, 20, 200, 100);
ctx.fillStyle = '#8ed6ff';
ctx.shadowColor = '#bbb';
ctx.shadowBlur = 20;
ctx.shadowOffseX = 15;
ctx.shadowOffseY = 15;
ctx.fill();
// 绘制星形阴影
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowBlur = 4;
img = document.getElementById('star');
ctx.drawImage(img, 250, 30);
ctx.textBaseline = 'top';
ctx.font = 'bold 20px Arial';
// 绘制三行文本的阴影
ctx.shadowBlur = 3;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.fillStyle = 'steelblue';
ctx.fillText('这是第一行文本', 10, 175);
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 20;
ctx.shadowOffsetY = 20;
ctx.fillStyle = 'steelblue';
ctx.fillText('这是第二行文本', 10, 225);
ctx.shadowBlur = 15;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowColor = '#000';
ctx.fillStyle = 'steelblue';
ctx.fillText('这是第三行文本', 10, 300);
填充图案
要实现用图案填充,首先要选择一张小图片,而且能够前后拼接在一起覆盖一块大区域。我们可以利用绘图上下文的createPattern()方法
创建一个图案对象,可以选择图案是水平(repeat-x
)、垂直(repeat-y
),还是在两个方向(repeat
)重复:
js
var img = document.getElementById('brickTile');
var pattern = ctx.createPattern(img, 'repeat');
// 使用图案对象设置fillStyle或者strokeStyle属性
ctx.fillStyle = pattern
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fill();
填充渐变
使用渐变填充的第一步是创建渐变对象。绘图上下文为此提供了两个方法:createLinearGradient()
和createRadialGradient()
。
js
// 创建一个从(10, 0)到(100, 0)的渐变
var gradient = ctx.createLinearGradient(10, 0, 100, 0);
// 添加两种颜色
gradient.addColorStop(0, 'magenta');
gradient.addColorStop(1, 'yellow');
// 调用一个自己写的函数来绘制一个心形
drawHerat(60, 50);
// 填充心形
ctx.fillStyle = gradient;
ctx.fill();
ctx.stroke();
这里是创建线性渐变,因此我们给createLinearGradient()
传入两个坐标点,分别表示渐变的起点和终点。起点和终点构成了颜色逐渐过渡的区间。如果需要填充的对象超过这个区间,也就是超出了渐变的范围,那么其他地方的颜色将会变成实色。
一般来说,我们创建的渐变只要恰好比要填充的图形大一点即可。
确定了渐变线的宽度和角度之后(上传是水平的渐变,因为y坐标都 是0),接下来该实际地设置构成渐变的颜色了。要设置渐变颜色,需要使用渐变对象的addColorStop()方法
。每次调用这个方法,都需要添加一个0-1的偏移值和一个颜色值。其中,偏移值决定颜色在渐变中的位置:0表示位于渐变的起点,1表示位于渐变的终点。
js
var gradient = ctx.createLinearGradient(10, 0, 100, 0);
gradient.addColorStop(0, 'magenta');
gradient.addColorStop(.25, 'blue');
gradient.addColorStop(.5, 'green');
gradient.addColorStop(.75, 'yellow');
gradient.addColorStop(1, 'red');
drawHeart(60, 200);
ctx.fillStyle = gradient;
ctx.fill();
ctx.stroke();
创建放射性渐变需要指定两个圆。因为放射性渐变就是颜色从一个小圆过渡到一个更大的、包含它的圆。要定义圆,需要提供圆心坐标和半径。
js
var gradient = ctx.createRadialGradient(180, 100, 10, 180, 100, 50);
gradient.addColorStop(0, 'magenta');
gradient.addColorStop(1, 'yellow');
drawHeart(180, 80);
ctx.fillStyle = gradient;
ctx.fill();
ctx.stroke();
注意,把两个圆设置为同心圆是最常见的做法。不过,当然可以给内圆和外圆设置不同的圆心,这样可以实现拉伸、压缩或其他颜色变形的效果。
赋予图形交互能力
记录绘制的内容
为了修改和重绘画面,必须先知道要修改和重绘什么内容。
js
var circles = [];
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
function Circle (x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.isSelected = false;
}
function randomFromTo (min, max) {
return min + Math.round(Math.random() * (max - min));
}
function addRandomCircle () {
// 为圆圈计算一个随机大小和位置
var radius = randomFromTo(10, 60);
var x = randomFromTo(0, canvas.width);
var y = randomFromTo(0, canvas.height);
// 为圆圈计算一个随机颜色
var colors = ['green', 'blue', 'red', 'yellow', 'megenta', 'orange', 'brown', 'purple', 'pink'];
var color = colors[randomFromTo(0, colors.length - 1)];
// 创建一个新圆圈
var circle = new Circle(x, y, radius, color);
// 把它保存在数组中
circles.push(circle);
// 重新绘制画布
drawCircles();
}
// 绘制圆圈
function drawCircles () {
// 清除画面,准备绘制
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = .85;
ctx.strokeStyle = 'black';
// 遍历所有的圆
circles.forEach(function (circle) {
// 绘制圆圈
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI);
ctx.fillStyle = circle.color;
ctx.fill();
ctx.stroke();
})
}
addRandomCircle();
drawCircles();
基于坐标的碰撞检测
只要创建交互图形,几乎就一定要用到碰撞检测,也就是测试某个点是否“碰到”了某个图形。在绘制圆圈的程序中,我们需要检测用户单击的点是否碰到某个圆圈,或者是否点击了空白区域。
为了做碰撞测试,就要检测每一个开关,计算鼠标点击的那个点是否落在某个形状里面。如果是,则说明单击“碰到”了该形状。
js
var circles = [];
var previousSelectedCircle = null;
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
function Circle (x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.isSelected = false;
}
function randomFromTo (min, max) {
return min + Math.round(Math.random() * (max - min));
}
function addRandomCircle () {
// 为圆圈计算一个随机大小和位置
var radius = randomFromTo(10, 60);
var x = randomFromTo(0, canvas.width);
var y = randomFromTo(0, canvas.height);
// 为圆圈计算一个随机颜色
var colors = ['green', 'blue', 'red', 'yellow', 'megenta', 'orange', 'brown', 'purple', 'pink'];
var color = colors[randomFromTo(0, colors.length - 1)];
// 创建十个圆圈
var circle = new Circle(x, y, radius, color);
// 把它保存在数组中
circles.push(circle);
// 重新绘制画布
drawCircles();
}
// 绘制圆圈
function drawCircles () {
// 清除画面,准备绘制
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = .85;
ctx.strokeStyle = 'black';
// 遍历所有的圆
circles.forEach(function (circle) {
// 绘制圆圈
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI);
ctx.fillStyle = circle.color;
if (circle.isSelected) {
ctx.lineWidth = 5;
} else {
ctx.lineWidth = 1;
}
ctx.fill();
ctx.stroke();
})
}
addRandomCircle();
function handleClick (event) {
var e = event || window.event;
// 取得画面上被单击的点
var clickX = e.pageX - canvas.offsetLeft;
var clickY = e.pageY - canvas.offsetTop;
// 查找被单击的圆圈
for (var i = circles.length - 1; i >= 0; i--) {
var circle = circles[i];
// 使用勾股定理计算这个点与圆心之间的距离
var distanceFromCenter = Math.sqrt(Math.pow(circle.x - clickX, 2) + Math.pow(circle.y - clickY, 2));
console.log(circle, clickX, clickY)
if (distanceFromCenter <= circle.radius) {
// 清除之前选择的圆圈
if (previousSelectedCircle != null) {
previousSelectedCircle.isSelected = false;
}
previousSelectedCircle = circle;
// 选择新圆圈
circle.isSelected = true;
// 更新显示
drawCircles();
return;
}
}
}
给Canvas添加动画
基本的动画
在HTML5中利用Canvas实现动画非常容易,只需要设置一个定时器(setTimeout()或setInterval()
),反复调用(一般每次30-40次)绘图函数即可。每次调用,都会重会整个画面。
js
var canvas,ctx;
window.onload = function () {
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
setTimeout(drawFrame, 20);
function drawFrame () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ...
}
}