一、添加一个 Canvas
1.布置画布:通过添加<canvas>标签,添加canvas元素
-
Canvas
在HTML5
中是透明的,是不可见的。 -
<canvas>
标签中的那段文本是什么意思呢?那是一旦浏览器执行HTML页面时不支持Canvas
,就会显示这段文字,换言之,只要你的浏览器支持Canvas,页面上就不会显示这个文本。 -
<canvas>
中的id
是标签的属性之一,在JavaScript代码中用来指定特定的<canvas>
,是唯一的。
2.获取canvas对象
var canvas = document.getElementById("canvas");
3.获得画笔(2D环境)
var context = canvas.getContext("2d");
二、绘制一条线段
1.移动画笔(moveTo())
- 以
canvas
画布的左上角为笛卡尔坐标系
的原点,且y轴
的正方向向下,x轴
的正方向向右。 -
context.moveTo(100,100)。
这句代码的意思是 移动画笔至(100, 100)
这个点(单位是px
)。 - moveTo()方法只是表示准备状态,准备要画,还没开始画。
笔画停点(lineTo())
- 从上一笔的停止点绘制到这里。
-
context.lineTo(600, 600)
是从 上一笔的停止点绘制到(600, 600)这里。 -
lineTo()
方法只是表示准备状态,准备要画,还没开始画。
3.选择画笔
-
context.lineWidth = 5
,这句话的意思是设置画笔(线条)的粗细为5px
。 -
context.strokeStyle = "#AA394C"
,这句话的意思是设置画笔(线条)的颜色为玫红色。
4.确定绘制
- 确定绘制只有两种方法,
fill()
和stroke()
。 -
fill()
是填充。 -
stroke()
是描边。
demo
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>从线条开始</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.moveTo(100,100);
context.lineTo(600,600);
context.lineWidth = 5;
context.strokeStyle = "#AA394C";
context.stroke();
}
</script>
</body>
</html>
三、多线条组成图形
1.绘制折线
复用lineTo()
就可以了。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>绘制折线</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.moveTo(100,100);
context.lineTo(300,300);
context.lineTo(100,500);
context.lineWidth = 5;
context.strokeStyle = "#AA394C";
context.stroke();
}
</script>
</body>
</html>
2.绘制多条折线
如果要画三条折线,分别是红色、蓝色、黑色,平移一下再改下画笔颜色就行了。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>绘制折线</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.moveTo(100,100);
context.lineTo(300,300);
context.lineTo(100,500);
context.lineWidth = 5;
context.strokeStyle = "red";
context.stroke();
context.moveTo(300,100);
context.lineTo(500,300);
context.lineTo(300,500);
context.lineWidth = 5;
context.strokeStyle = "blue";
context.stroke();
context.moveTo(500,100);
context.lineTo(700,300);
context.lineTo(500,500);
context.lineWidth = 5;
context.strokeStyle = "black";
context.stroke();
}
</script>
</body>
</html>
Canvas
是基于状态的绘制。这段代码每次使用stroke()
时,它都会把之前设置的状态再绘制一遍。第一次stroke()时,绘制一条红色的折线;第二次stroke()时,会再重新绘制之前的那条红色的折线,但是这个时候的画笔已经被更换成蓝色的了,所以画出的折线全是蓝色的。换言之,strokeStyle属性被覆盖了。同理,第三次绘制的时候,画笔颜色是最后的黑色,所以会重新绘制三条黑色的折线。所以,这里看到的三条折线,其实绘制了3次,一共绘制了6条折线。
3.使用 beginPath() 开始绘制
为了让绘制方法不重复绘制,我们可以在每次绘制之前加上beginPath()
,代表下次绘制的起始之处为beginPath()
之后的代码。我们在三次绘制之前分别加上context.beginPath()。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>绘制折线</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.beginPath();
context.moveTo(100,100);
context.lineTo(300,300);
context.lineTo(100,500);
context.lineWidth = 5;
context.strokeStyle = "red";
context.stroke();
context.beginPath();
context.moveTo(300,100);
context.lineTo(500,300);
context.lineTo(300,500);
context.lineWidth = 5;
context.strokeStyle = "blue";
context.stroke();
context.beginPath();
context.moveTo(500,100);
context.lineTo(700,300);
context.lineTo(500,500);
context.lineWidth = 5;
context.strokeStyle = "black";
context.stroke();
}
</script>
</body>
</html>
四、绘制矩形
1.使用closePath()闭合图形
首先我们用上节方法绘制一个矩形。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>绘制矩形</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.beginPath();
context.moveTo(150,50);
context.lineTo(650,50);
context.lineTo(650,550);
context.lineTo(150,550);
context.lineTo(150,50); //绘制最后一笔使图像闭合
context.lineWidth = 5;
context.strokeStyle = "black";
context.stroke();
}
</script>
</body>
</html>
</body>
</html>
这种情况是设置了
lineWidth
导致的。如果默认1笔触的话,是没有问题的。但是笔触越大,线条越宽,这种缺口就越明显。使用使用closePath()
闭合图形来避免这种情况。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>绘制矩形</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.beginPath();
context.moveTo(150,50);
context.lineTo(650,50);
context.lineTo(650,550);
context.lineTo(150,550);
context.lineTo(150,50); //最后一笔可以不画
context.closePath(); //使用closePath()闭合图形
context.lineWidth = 5;
context.strokeStyle = "black";
context.stroke();
}
</script>
</body>
</html>
- 每次开始绘制前都务必要使用
beginPath()
,为了代码的完整性,每次绘制结束后使用closePath()
。 - 最后一笔可以不画出来,直接使用
closePath()
,它会自动帮你闭合的。 - 如果你不想绘制闭合图形就不可以使用
closePath()
。
2.给矩形上色
- 这里我们要介绍一个和
stroke()
同等重要的方法fill()
。 - 和当初描边一样,我们在填色之前,也要先用
fillStyle
属性选择要填充的颜色。 - 比如我们要给上面的矩形涂上黄色,可以这样写。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>绘制矩形</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.beginPath();
context.moveTo(150,50);
context.lineTo(650,50);
context.lineTo(650,550);
context.lineTo(150,550);
context.lineTo(150,50); //最后一笔可以不画
context.closePath(); //使用closePath()闭合图形
context.fillStyle = "yellow"; //选择油漆桶的颜色
context.lineWidth = 5;
context.strokeStyle = "black";
context.fill(); //确定填充
context.stroke();
}
</script>
</body>
</html>
3.封装绘制方法
- 一个矩形可以由它的左上角坐标和其长宽唯一确定。
- 绘制矩形其实都是这样的四笔,我们可以使用JavaScript封装这些方法。
- 还需要知道线条的颜色,宽度,填充的颜色。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>封装绘制矩形方法</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
drawRect(context, 150, 50, 50, 50, "red", 5, "blue");
drawRect(context, 250, 50, 50, 50, "green", 5, "red");
drawRect(context, 350, 50, 50, 50, "yellow", 5, "black");
}
function drawRect(cxt, x, y, width, height, fillColor, borderWidth, borderColor){
cxt.beginPath();
cxt.moveTo(x, y);
cxt.lineTo(x + width, y);
cxt.lineTo(x + width, y + height);
cxt.lineTo(x, y + height);
cxt.lineTo(x, y);
cxt.closePath();
cxt.lineWidth = borderWidth;
cxt.strokeStyle = borderColor;
cxt.fillStyle = fillColor;
cxt.fill();
cxt.stroke();
}
</script>
</body>
</html>
4.使用rect()方法绘制矩形
由于绘制矩形是常用的方法,所以在Canvas AP
I中已经帮我们封装好了一个绘制矩形的方法——rect()
。这个方法接收4个参数x
, y
, width
, height
,实际调用时也就是:
context.rect(x, y, width, height);
基于此,我们帮刚才封装的方法优化一下:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>绘制魔性图形</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.beginPath();
context.rect(0, 0, 800, 600);
context.fillStyle = "#AA9033";
context.fill();
context.beginPath();
for(var i=0; i<20; i++){
drawWhiteRect(context, 200 + 10 * i, 100 + 10 * i, 400 - 20 * i, 400 - 20 * i);
drawBlackRect(context, 205 + 10 * i, 105 + 10 * i, 390 - 20 * i, 390 - 20 * i);
}
context.beginPath();
context.rect(395, 295, 5, 5);
context.fillStyle = "black";
context.lineWidth = 5;
context.fill();
context.stroke();
}
function drawBlackRect(cxt, x, y, width, height){
cxt.beginPath();
cxt.rect(x, y, width, height);
cxt.lineWidth = 5;
cxt.strokeStyle = "black";
cxt.stroke();
}
function drawWhiteRect(cxt, x, y, width, height){
cxt.beginPath();
cxt.rect(x, y, width, height);
cxt.lineWidth = 5;
cxt.strokeStyle = "white";
cxt.stroke();
}
</script>
</body>
</html>
五、线条的属性
1.线条的帽子lineCap
lineCap 定义上下文中线的端点,可以有以下 3 个值:
- butt:默认值,端点是垂直于线段边缘的平直边缘。
- round:端点是在线段边缘处以线宽为直径的半圆。
- square:端点是在选段边缘处以线宽为长、以一半线宽为宽的矩形。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>线条的帽子</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.lineWidth = 30;
context.strokeStyle = "#1BAAAA";
context.beginPath();
context.moveTo(100,100);
context.lineTo(700,100);
context.lineCap = "butt";
context.stroke();
context.beginPath();
context.moveTo(100,300);
context.lineTo(700,300);
context.lineCap = "round";
context.stroke();
context.beginPath();
context.moveTo(100,500);
context.lineTo(700,500);
context.lineCap = "square";
context.stroke();
//下面画两个基准线方便观察
context.lineWidth = 3;
context.strokeStyle = "black";
context.beginPath();
context.moveTo(100,0);
context.lineTo(100,600);
context.moveTo(700,0);
context.lineTo(700,600);
context.stroke();
}
</script>
</body>
</html>
2.线条的连接lineJoin
可能有三个值:
-
miter
,默认值,直接连接。 -
bevel
,连接处为线宽的一半的矩形。 -
round
,连接处为直径是线宽的圆。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>线条的连接</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.beginPath();
context.moveTo(100,100);
context.lineTo(300,300);
context.lineTo(100,500);
context.lineJoin = "miter";
context.lineWidth = 50;
context.strokeStyle = "red";
context.stroke();
context.beginPath();
context.moveTo(300,100);
context.lineTo(500,300);
context.lineTo(300,500);
context.lineJoin = "bevel";
context.lineWidth = 50;
context.strokeStyle = "blue";
context.stroke();
context.beginPath();
context.moveTo(500,100);
context.lineTo(700,300);
context.lineTo(500,500);
context.lineJoin = "round";
context.lineWidth = 50;
context.strokeStyle = "black";
context.stroke();
}
</script>
</body>
</html>
3.线宽lineWidth
lineWidth
定义线的宽度(默认值为 1.0
)。
4.笔触样式strokeStyle
strokeStyle
定义线和形状边框的颜色和样式。
六、填充颜色
填充颜色主要分为两种:
- 基本颜色
- 渐变颜色(又分为线性渐变与径向渐变)
1.填充基本颜色
fillStyle
属性用来设置画布上形状的基本颜色和填充。
context.fillStyle = "red";
2.填充渐变形状
- 在画布上创建渐变填充有两个基本选项:线性或径向。
- 线性渐变创建一个水平、垂直或者对角线的填充图案。
- 径向渐变自中心点创建一个放射状填充。
- 填充渐变形状分为三步:添加渐变线,为渐变线添加关键色,应用渐变。
2.1.线性渐变
三步走战略:
2.1.1.添加渐变线:
var grd = context.createLinearGradient(xstart,ystart,xend,yend);
2.1.2.为渐变线添加关键色(类似于颜色断点):
grd.addColorStop(stop,color);
这里的stop传递的是 0 ~ 1 的浮点数,代表断点到(xstart,ystart)的距离占整个渐变色长度是比例。
2.1.3.应用渐变:
context.fillStyle = grd;
context.strokeStyle = grd;
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>填充线性渐变</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.rect(200,100,400,400);
//添加渐变线
var grd = context.createLinearGradient(200,300,600,300);
//添加颜色断点
grd.addColorStop(0,"black");
grd.addColorStop(0.5,"white");
grd.addColorStop(1,"black");
//应用渐变
context.fillStyle = grd;
context.fill();
}
</script>
</body>
</html>
2.2.绘制矩形的快捷方法
fillRect(x,y,width,height)
、strokeRect(x,y,width,height)
这两个函数可以分别看做rect()
与fill()
以及rect()
与stroke()
的组合。
因为rect()
仅仅只是规划路径而已,而这两个方法实实在在的绘制。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>填充线性渐变</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
//添加渐变线
var grd = context.createLinearGradient(100,300,700,300);
//添加颜色断点
grd.addColorStop(0,"olive");
grd.addColorStop(0.25,"maroon");
grd.addColorStop(0.5,"aqua");
grd.addColorStop(0.75,"fuchsia");
grd.addColorStop(0.25,"teal");
//应用渐变
context.fillStyle = grd;
context.strokeStyle = grd;
context.strokeRect(200,50,300,50);
context.strokeRect(200,100,150,50);
context.strokeRect(200,150,450,50);
context.fillRect(200,300,300,50);
context.fillRect(200,350,150,50);
context.fillRect(200,400,450,50);
context.fillRect(0,550,800,25);
}
</script>
</body>
</html>
这两个页面都是水平渐变,但是要清楚线性渐变不一定是水平的,方向可以是任意的,通过渐变线的端点来设置方向。
2.3.径向渐变
- 线性渐变是基于两个端点定义的,但是径向渐变是基于两个圆定义的。
- 同样是三步走战略,只不过是第一步的所用方法变了。
2.3.1.添加渐变圆:
var grd = context.createRadialGradient(x0,y0,r0,x1,y1,r1);
2.3.2.为渐变线添加关键色(类似于颜色断点):
grd.addColorStop(stop,color);
2.3.3.应用渐变:
context.fillStyle = grd;
context.strokeStyle = grd;
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>填充径向渐变</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
//添加渐变线
var grd = context.createRadialGradient(400,300,100,400,300,200);
//添加颜色断点
grd.addColorStop(0,"olive");
grd.addColorStop(0.25,"maroon");
grd.addColorStop(0.5,"aqua");
grd.addColorStop(0.75,"fuchsia");
grd.addColorStop(1,"teal");
//应用渐变
context.fillStyle = grd;
context.fillRect(100,100,600,400);
}
</script>
</body>
</html>
createRadialGradient(x0,y0,r0,x1,y1,r1);
方法规定了径向渐变开始和结束的范围,即两圆之间的渐变。
七、填充图案
1.createPattern()简介
- 填充图案通过
createPattern()
函数进行初始化。 - 它需要传进两个参数
createPattern(img,repeat-style)
。 - 第一个是
Image
对象实例,第二个参数是String
类型,表示在形状中如何显示repeat
图案。 -
createPattern()
的第一个参数还可以传入一个canvas
对象或者video
对象。 - 可以使用这个函数加载图像或者整个画布作为形状的填充图案。
有以下4种图像填充类型:
- 平面上重复:repeat;
- x轴上重复:repeat-x;
- y轴上重复:repeat-y;
- 不使用重复:no-repeat;
2.创建并填充图案
创建Image对象,为Image对象指定图片源:
var img = new Image(); //创建Image对象
img.src = "8-1.jpg"; //为Image对象指定图片源
填充纹理:
var pattern = context.createPattern(img,"repeat");
context.fillStyle = pattern;
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>填充纹理</title>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
var img = new Image();
img.src = "https://upload-images.jianshu.io/upload_images/7016617-395eecefc4dd4c5a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240";
img.onload = function(){
var pattern = context.createPattern(img, "repeat");
context.fillStyle = pattern;
context.fillRect(0,0,800,600);
}
}
</script>
</body>
</html>
这里使用了
Image
的onload
事件,它的作用是对图片进行预加载处理,即在图片加载完成后才立即除非其后function
的代码体。这个是必须的,如果不写的话,画布将会显示黑屏。因为没有等待图片加载完成就填充纹理,导致浏览器找不到图片。
八、绘制标准圆弧
1.使用arc()绘制圆弧
arc()
的使用方法如下:
context.arc(x,y,radius,startAngle,endAngle,anticlockwise)
- 前面三个参数,分别是圆心坐标与圆半径。
-
startAngle
、endAngle
使用的是弧度值,不是角度值。 - anticlockwise表示绘制的方法,是顺时针还是逆时针绘制。它传入布尔值,true表示逆时针绘制,false表示顺时针绘制,缺省值为false。
-
弧度的规定是绝对的,如下图:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>圆</title>
<style>
#canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
</style>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.fillStyle = "#FFF";
context.fillRect(0,0,800,600);
context.beginPath()
context.arc(400, 300, 100, 0, 2*Math.PI, true)
context.strokeStyle = "#0078AA";
context.stroke();
}
</script>
</body>
</html>
2.绘制圆角矩形
下面,我们结合基本路径和高级路径的知识,绘制一个圆角矩形。
圆角矩形是由四段线条和四个1/4圆弧组成,拆解如下:
因为我们要写的是函数而不是一个固定的圆角矩形,所以这里列出的是函数需要的参数。分析好之后,直接敲出代码:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>圆角矩形</title>
<style>
#canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
</style>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.fillStyle = "#FFF";
context.fillRect(0,0,800,600);
drawRoundRect(context, 200, 100, 400, 400, 50);
context.strokeStyle = "#0078AA";
context.stroke();
}
function drawRoundRect(cxt, x, y, width, height, radius){
cxt.beginPath();
cxt.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2);
cxt.lineTo(width - radius + x, y);
cxt.arc(width - radius + x, radius + y, radius, Math.PI * 3 / 2, Math.PI * 2);
cxt.lineTo(width + x, height + y - radius);
cxt.arc(width - radius + x, height - radius + y, radius, 0, Math.PI * 1 / 2);
cxt.lineTo(radius + x, height +y);
cxt.arc(radius + x, height - radius + y, radius, Math.PI * 1 / 2, Math.PI);
cxt.closePath();
}
</script>
</body>
</html>
九、使用切点绘制圆弧
-
arcTo()
方法接收5个参数,分别是两个切点的坐标和圆弧半径。这个方法是依据切线画弧线,即由两个切线确定一条弧线。 具体如下。 -
arcTo(x1,y1,x2,y2,radius)
这个函数以给定的半径绘制一条弧线,圆弧的起点与当前路径的位置到(x1, y1)
点的直线相切,圆弧的终点与(x1, y1)
点到(x2, y2)
的直线相切。因此其通常配合moveTo()
或lineTo()
使用。 - 其能力是可以被更为简单的
arc()
替代的,其复杂就复杂在绘制方法上使用了切点。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>绘制弧线</title>
<style>
body { background: url("./images/bg3.jpg") repeat; }
#canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
</style>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.fillStyle = "#FFF";
context.fillRect(0,0,800,600);
drawArcTo(context, 200, 200, 600, 200, 600, 400, 100);
};
function drawArcTo(cxt, x0, y0, x1, y1, x2, y2, r){
cxt.beginPath();
cxt.moveTo(x0, y0);
cxt.arcTo(x1, y1, x2, y2, r);
cxt.lineWidth = 6;
cxt.strokeStyle = "red";
cxt.stroke();
cxt.beginPath();
cxt.moveTo(x0, y0);
cxt.lineTo(x1, y1);
cxt.lineTo(x2, y2);
cxt.lineWidth = 1;
cxt.strokeStyle = "#0088AA";
cxt.stroke();
}
</script>
</body>
</html>
arcTo()
绘制的起点是(x0, y0)
,但(x0, y0)
不一定是圆弧的切点。真正的arcTo()
函数只传入(x1, y1)
和(x2, y2)
。其中(x1, y1)
称为控制点,(x2, y2)
是圆弧终点的切点,它不一定在圆弧上。但(x0, y0)
一定在圆弧上。
十、二次贝塞尔曲线
在
Canvas
里,二次贝塞尔曲线的方法如下:
context.quadraticCurveTo(cpx,cpy,x,y);
- 这里和
acrTo()
有异曲同工之妙。 -
P0
是起始点,所以通常搭配moveTo()
或lineTo()
使用。 -
P1(cpx, cpy)
是控制点,P2(x, y)
是终止点,它们不是相切的关系。 - 一个工具可以简单调试直至得到你想要的效果。在线转换器。
十一、三次贝塞尔曲线
绘制三次贝塞尔曲线代码如下。
context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y);
- 这个方法可谓是绘制波浪线的神器。
- 根据之前的结论,n阶贝塞尔曲线就有n-1个控制点,所以三次贝塞尔曲线有1个起始点、1个终止点、2个控制点。
- 因此传入的6个参数分别为控制点
cp1 (cp1x, cp1y)
,控制点cp2 (cp2x, cp2y)
,与终止点(x, y)
。 - 这个方法也是不用大家去掌握参数具体是怎么填的,只要知道参数的意义就行。
- 和
quadraticCurveTo()
方法一样,bezierCurveTo()
的三次贝塞尔曲线网上也能找到互动的网页工具。
这里提供一个网页:Canvas Bézier Curve Example,大家可以动手试一下。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>XP壁纸</title>
<style>
body { background: url("./images/bg3.jpg") repeat; }
#canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
</style>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.fillStyle = "#FFF";
context.fillRect(0,0,800,600);
drawPrairie(context);
drawSky(context);
for(var i=0; i <5; i++){
var x0 = 500 * Math.random() + 50;
var y0 = 200 * Math.random() + 50;
var c0 = 100 * Math.random() + 50;
drawCloud(context, x0, y0, c0);
}
};
function drawSky(cxt){
cxt.save();
cxt.beginPath();
cxt.moveTo(0, 420);
cxt.bezierCurveTo(250, 300, 350, 550, 800, 400);
cxt.lineTo(800,0);
cxt.lineTo(0,0);
cxt.closePath();
var lineStyle = cxt.createRadialGradient(400, 0, 50, 400, 0, 200);
lineStyle .addColorStop(0, "#42A9AA");
lineStyle .addColorStop(1, "#2491AA");
cxt.fillStyle = lineStyle;
cxt.fill();
cxt.restore();
}
function drawPrairie(cxt){
cxt.save();
cxt.beginPath();
cxt.moveTo(0, 420);
cxt.bezierCurveTo(250, 300, 350, 550, 800, 400);
cxt.lineTo(800,600);
cxt.lineTo(0,600);
cxt.closePath();
var lineStyle = cxt.createLinearGradient(0, 600, 600, 0);
lineStyle .addColorStop(0, "#00AA58");
lineStyle .addColorStop(0.3, "#63AA7B");
lineStyle .addColorStop(1, "#04AA00");
cxt.fillStyle = lineStyle;
cxt.fill();
cxt.restore();
}
/*渲染单个云朵
context: canvas.getContext("2d")对象
cx: 云朵X轴位置
cy: 云朵Y轴位置
cw: 云朵宽度
*/
function drawCloud(cxt, cx, cy, cw) {
//云朵移动范围即画布宽度
var maxWidth = 800;
//如果超过边界从头开始绘制
cx = cx % maxWidth;
//云朵高度为宽度的60%
var ch = cw * 0.6;
//开始绘制云朵
cxt.beginPath();
cxt.fillStyle = "white";
//创建渐变
var grd = cxt.createLinearGradient(0, 0, 0, cy);
grd.addColorStop(0, 'rgba(255,255,255,0.8)');
grd.addColorStop(1, 'rgba(255,255,255,0.5)');
cxt.fillStyle = grd;
//在不同位置创建5个圆拼接成云朵现状
cxt.arc(cx, cy, cw * 0.19, 0, 360, false);
cxt.arc(cx + cw * 0.08, cy - ch * 0.3, cw * 0.11, 0, 360, false);
cxt.arc(cx + cw * 0.3, cy - ch * 0.25, cw * 0.25, 0, 360, false);
cxt.arc(cx + cw * 0.6, cy, cw * 0.21, 0, 360, false);
cxt.arc(cx + cw * 0.3, cy - ch * 0.1, cw * 0.28, 0, 360, false);
cxt.closePath();
cxt.fill();
}
</script>
</body>
</html>
保存和恢复Canvas状态
这里还使用到了两个新方法save()
和restore()
。之前说过了canvas是基于状态的绘制.保存(推送)当前状态到堆栈,调用以下函数:
context.save();
调出最后存储的堆栈恢复画布,使用以下函数:
context.restore();
十二、图形变换
图形变换是指用数学方法调整所绘形状的物理属性,其实质是坐标变形。所有的变换都依赖于后台的数学矩阵运算,所以我们只要使用变换的功能即可,无需去理解这些运算。谈到图形变换,不得不得说的三个基本变换方法就是:
- 平移变换:translate(x,y)
- 旋转变换:rotate(deg)
- 缩放变换:scale(sx,sy)
- 其实坐标变形的本质是变换矩阵,所以在最后我们会谈一谈一个万能的变换方法——矩阵变换transform()。
1.平移变换translate()
平移变换,故名思议,就是一般的图形位移。
比如这里我想将位于(100,100)的矩形平移至(200,200)点。
那么我只要在绘制矩形之前加上context.translate(100,100)即可。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>平移变换</title>
<style>
body { background: url("./images/bg3.jpg") repeat; }
#canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
</style>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.fillStyle = "#FFF";
context.fillRect(0,0,800,600);
context.fillStyle = "#00AAAA";
context.fillRect(100,100,200,100);
context.fillStyle = "red";
context.translate(100,100);
context.fillRect(100,100,200,100);
};
</script>
</body>
</html>
注意使用状态保存
其实这里有一个坑,我们如果想把矩形平移至(300,300)怎么办呢?或许我们会想,直接调用context.translate(200,200)就可以了。好,我们看看效果。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>平移变换</title>
<style>
body { background: url("./images/bg3.jpg") repeat; }
#canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
</style>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.fillStyle = "#FFF";
context.fillRect(0,0,800,600);
context.fillStyle = "#00AAAA";
context.fillRect(100,100,200,100);
context.fillStyle = "red";
context.translate(100,100);
context.fillRect(100,100,200,100);
context.fillStyle = "green";
context.translate(200,200);
context.fillRect(100,100,200,100);
};
</script>
</body>
</html>
这里的绿色矩形并没有如我们所愿在(300,300)位置处,而是跑到了(400,400)这里。为什么呢?想必大家已经知道了答案——Canvas是基于状态的绘制。在我们第一次平移之后,坐标系已经在(100,100)处了,所以如果继续平移,这个再基于新坐标系继续平移坐标系。那么要怎么去解决呢?很简单,有两个方法。
- 第一,在每次使用完变换之后,记得将坐标系平移回原点,即调用translate(-x,-y)。
- 第二,在每次平移之前使用context.save(),在每次绘制之后,使用context.restore()。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>平移变换</title>
<style>
body { background: url("./images/bg3.jpg") repeat; }
#canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
</style>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.fillStyle = "#FFF";
context.fillRect(0,0,800,600);
context.fillStyle = "#00AAAA";
context.fillRect(100,100,200,100);
context.save();
context.fillStyle = "red";
context.translate(100,100);
context.fillRect(100,100,200,100);
context.restore();
context.save();
context.fillStyle = "green";
context.translate(200,200);
context.fillRect(100,100,200,100);
context.restore();
};
</script>
</body>
</html>
2.旋转变换rotate()
- 同画圆弧一样,这里的rotate(deg)传入的参数是弧度,不是角度。
- 同时需要注意的是,这个的旋转是以坐标系的原点(0,0)为圆心进行的顺时针旋转。
- 所以,在使用rotate()之前,通常需要配合使用translate()平移坐标系,确定旋转的圆心。
即,旋转变换通常搭配平移变换使用的。 - 最后一点需要注意的是,Canvas是基于状态的绘制,所以每次旋转都是接着上次旋转的基础上继续旋转,所以在使用图形变换的时候必须搭配save()与restore()方法,一方面重置旋转角度,另一方面重置坐标系原点。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>旋转变换</title>
<style>
body { background: url("./images/bg3.jpg") repeat; }
#canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
</style>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.fillStyle = "#FFF";
context.fillRect(0,0,800,600);
for(var i = 0; i <= 12; i++){
context.save();
context.translate(70 + i * 50, 50 + i * 40);
context.fillStyle = "#00AAAA";
context.fillRect(0,0,20,20);
context.restore();
context.save();
context.translate(70 + i * 50, 50 + i * 40);
context.rotate(i * 30 * Math.PI / 180);
context.fillStyle = "red";
context.fillRect(0,0,20,20);
context.restore();
}
};
</script>
</body>
</html>
这里用for循环绘制了14对正方形,其中蓝色是旋转前的正方形,红色是旋转后的正方形。每次旋转都以正方形左上角顶点为原点进行旋转。每次绘制都被save()与restore()包裹起来,每次旋转前都移动了坐标系。
3.缩放变换scale()
缩放变换scale(sx,sy)
传入两个参数,分别是水平方向和垂直方向上对象的缩放倍数。例如context.scale(2,2)
就是对图像放大两倍。其实,看上去简单,实际用起来还是有一些问题的。我们来看一段代码。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>缩放变换</title>
<style>
body { background: url("./images/bg3.jpg") repeat; }
#canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
</style>
</head>
<body>
<div id="canvas-warp">
<canvas id="canvas">
你的浏览器居然不支持Canvas?!赶快换一个吧!!
</canvas>
</div>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
context.fillStyle = "#FFF";
context.fillRect(0,0,800,600);
context.strokeStyle = "red";
context.lineWidth = 5;
for(var i = 1; i < 4; i++){
context.save();
context.scale(i,i);
context.strokeRect(50,50,150,100);
context.restore();
}
};
</script>
</body>
</html>
看了上面的例子,大家一定对产生的结果有点奇怪。一是左上角顶点的坐标变了,而是线条的粗细也变了。因此,对于缩放变换有两点问题需要注意:
- 缩放时,图像左上角坐标的位置也会对应缩放。
- 缩放时,图像线条的粗细也会对应缩放。