HTML5添加的最受欢迎的功能就是<canvas>元素。这个元素负责在页面中设定一个区域,然后就可以通过JavaScript动态地在这个区域中绘制图形。
本文为我的Canvas学习笔记,从基本用法到实例系统的记录,备忘以便查询。
基本用法
创建canvas元素
1 2 3
| <canvas id="canvas" width="200" height="200" style="border:1px solid #aaa;display:block;margin:50px auto;"> 当前浏览器不支持Canvas,请更换浏览器后在试 </canvas>
|
创建一个canvas元素即创建了一个canvas画布,画布默认的width为300,height为150。
我们可以像上例一样为它指定width和height,这样可同时指定画布的大小及内里的精度。不建议使用CSS的方式指定宽高,CSS的方式只能指定画布的大小。
canvas的方法
我们先通过js获取canvas元素
1
| var canvas = document.getElementById('canvas');
|
方法一:设置宽高
1 2
| canvas.width = 1024; canvas.height = 768;
|
方法二:获取上下文
取得绘图上下文对象的引用,需要调用getContext()方法并传入上下文的名字。传入”2d”,就可以取得2D上下文对象。
1
| var context = canvas.getContext('2d');
|
canvas自身的方法并不多,而绘图主要是使用通过canvas获取的上下文。
PS:使用canvas元素前,首先需要检测getContext()方法是否存在。(确定浏览器是否支持canvas元素)
有些浏览器会为HTML规范之外的元素创建默认的HTML元素对象。(假设你想在Firefox3中使用canvas元素。虽然浏览器会为该标签创建一个DOM对象,而且也可以引用它,但这个对象中并没有getContext()方法)
1 2 3 4
| var canvas = document.getElementById('canvas'); if(canvas.getContext){ var context = canvas.getContext("2d"); }
|
canvas绘制简单图形
canvas是基于状态绘制的。
绘制直线
代码:
1 2 3 4 5 6 7 8 9 10
| var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); context.moveTo(20,20); context.lineTo(120,120); context.stroke(); }
|
效果:
线条可设置如下属性:
1 2 3 4 5 6 7 8 9 10
| context.lineWidth = 3; context.strokeStyle = "#005588"; context.lineCap = "butt"(default)/"round"/"square"; context.lineJoin = "miter"(default)/"bevel"/"round"; context.miterLimit = 10(default);
|
绘制多边形
在以上绘制直线基础上设置多个移动到的点的状态,即绘制多条直线,从而完成多边形绘制。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); context.moveTo(20,20); context.lineTo(120,120); context.lineTo(120,20); context.lineTo(20,20); context.lineWidth = 3; context.strokeStyle = "#005588"; context.stroke(); }
|
绘制实心多边形
fillStyle 属性设置或返回用于填充绘画的颜色、渐变或模式。
使用fillStyle来指定颜色,如:context.fillStyle = “rgb(2,100,30)”;
并使用context.fill()绘制。
用fillStyle指定颜色时其值只要是CSS写法的都可以,可参考:CSS合法颜色值
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); context.moveTo(20,20); context.lineTo(120,120); context.lineTo(120,20); context.lineTo(20,20); context.lineWidth = 3; context.fillStyle = "rgb(2,100,30)"; context.fill(); context.lineWidth = 3; context.strokeStyle = "#005588"; context.stroke(); }
|
以上代码可以整理成:
1 2 3 4 5 6 7 8 9 10 11 12 13
| var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); context.moveTo(20,20); context.lineTo(120,120); context.lineTo(120,20); context.lineTo(20,20); context.lineWidth = 3; context.fillStyle = "rgb(2,100,30)"; context.strokeStyle = "#005588"; context.fill(); context.stroke(); }
|
PS:如果context.fill()和context.stroke()顺序调换效果不同
效果:
绘制多个图形
如果我想画多个图形怎么办?我们先做个试验。画一个红色的三角形和一条蓝色的线段。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); context.moveTo(20,20); context.lineTo(120,120); context.lineTo(120,20); context.lineTo(20,20); context.lineWidth = 3; context.strokeStyle = "red"; context.stroke(); context.moveTo(10,20); context.lineTo(110,120); context.lineWidth = 5; context.strokeStyle = "blue"; context.stroke(); }
|
效果:
我们发现结果和我们想要的不太一样。那么我们如何正确的绘制多个图形呢?
— — 这是因为canvas是基于状态绘图的。当第一个stroke调用时红色三角形是绘制了的,不过当第二个stroke调用时之前的状态仍然在起作用,用蓝色画了一个三角形,又用蓝色画了线段,只不过是把红色三角形给盖住了。
那我们该如何互不影响的绘制其他图形呢?
改进之后的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); context.beginPath(); context.moveTo(20,20); context.lineTo(120,120); context.lineTo(120,20); context.lineTo(20,20); context.closePath(); context.lineWidth = 3; context.strokeStyle = "red"; context.stroke(); context.beginPath(); context.moveTo(10,20); context.lineTo(110,120); context.closePath(); context.lineWidth = 5; context.strokeStyle = "blue"; context.stroke(); }
|
效果:
绘制矩形
你可以使用上面绘制多边形的方式绘制矩形也可以使用更简单的方法。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| window.onload=function(){ var canvas = document.getElementById("canvas"); canvas.width=400; canvas.height=200 if(canvas.getContext){ var context = canvas.getContext("2d"); drawRect(context,50,50,90,60,5,"blue","lightBlue"); drawRect(context,90,100,120,60,5,"green","lightGreen"); } } function drawRect(ctx,x,y,width,height,borderWidth,borderColor,fillColor){ ctx.beginPath(); ctx.rect(x,y,width,height); ctx.closePath(); ctx.lineWidth = borderWidth; ctx.fillStyle=fillColor; ctx.strokeStyle = borderColor; ctx.fill(); ctx.stroke(); } function drawRect2(ctx,x,y,width,height,borderWidth,borderColor,fillColor){ ctx.lineWidth = borderWidth; ctx.fillStyle=fillColor; ctx.strokeStyle = borderColor; ctx.fillRect(x,y,width,height); ctx.strokeRect(x,y,width,height); }
|
效果:
如果加了透明色又会又怎样的效果呢?
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| window.onload=function(){ var canvas = document.getElementById("canvas"); canvas.width=400; canvas.height=200 if(canvas.getContext){ var context = canvas.getContext("2d"); drawRect(context,50,50,90,60,5,"blue","lightBlue"); drawRect(context,90,100,120,60,5,"green","rgba(0,256,0,0.5)"); } } function drawRect(ctx,x,y,width,height,borderWidth,borderColor,fillColor){ ctx.beginPath(); ctx.rect(x,y,width,height); ctx.closePath(); ctx.lineWidth = borderWidth; ctx.fillStyle=fillColor; ctx.strokeStyle = borderColor; ctx.fill(); ctx.stroke(); } function drawRect2(ctx,x,y,width,height,borderWidth,borderColor,fillColor){ ctx.lineWidth = borderWidth; ctx.fillStyle=fillColor; ctx.strokeStyle = borderColor; ctx.fillRect(x,y,width,height); ctx.strokeRect(x,y,width,height); }
|
效果:
绘制曲线
绘制弧和圆
方法一:arc
context.arc(centerx,centery,radius,startingAngle,endingAngle,anticlockwise = false)
centerx:圆心横坐标
centery:圆心纵坐标
radius:圆半径
startingAngle:起始弧度值
endingAngle:结束弧度值
anticlockwise:逆时针(可选属性)默认为false,即默认为顺时针方向画弧。
圆弧度示意图为:
PS:顺、逆时针弧度的均为上图,0 PI、0.5 PI、1 PI、1.5 PI 的位置不随方向变化而变化。
我们先画一个圆 — —
代码:
1 2 3 4 5 6 7 8 9 10
| window.onload = function(){ var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); context.lineWidth = 3; context.strokeStyle = "lightBlue"; context.arc(100,100,50,0,2*Math.PI) context.stroke(); } }
|
效果:
几个弧度画法的实验 — —
代码 — A:
1 2 3 4 5 6 7 8 9 10
| window.onload = function(){ var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); context.lineWidth = 3; context.strokeStyle = "lightBlue"; context.arc(100,100,50,0,1.5*Math.PI) context.stroke(); } }
|
效果 — A:
代码 — B:
1 2 3 4 5 6 7 8 9 10
| window.onload = function(){ var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); context.lineWidth = 3; context.strokeStyle = "lightBlue"; context.arc(100,100,50,0,1.5*Math.PI,true) context.stroke(); } }
|
效果 — B:
代码 — C:
1 2 3 4 5 6 7 8 9 10
| window.onload = function(){ var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); context.lineWidth = 3; context.strokeStyle = "lightBlue"; context.arc(100,100,50,0,0.5*Math.PI,true) context.stroke(); } }
|
效果 — C:
代码 — D:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| window.onload = function(){ var canvas = document.getElementById("canvas"); canvas.width = 1000; canvas.height = 720; if(canvas.getContext){ var context = canvas.getContext("2d"); context.lineWidth = 3; context.strokeStyle = "lightBlue"; context.fillStyle = "lightBlue"; for(var i = 0; i < 10; i++){ context.beginPath(); context.arc(50 + i*90,60,40,0,2*Math.PI*(i+1)/10); context.closePath(); context.stroke(); } for(var i = 0; i < 10; i++){ context.beginPath(); context.arc(50 + i*90,180,40,0,2*Math.PI*(i+1)/10); context.stroke(); } for(var i = 0; i < 10; i++){ context.beginPath(); context.arc(50 + i*90,300,40,0,2*Math.PI*(i+1)/10,true); context.closePath(); context.stroke(); } for(var i = 0; i < 10; i++){ context.beginPath(); context.arc(50 + i*90,420,40,0,2*Math.PI*(i+1)/10,true); context.stroke(); } for(var i = 0; i < 10; i++){ context.beginPath(); context.arc(50 + i*90,540,40,0,2*Math.PI*(i+1)/10,true); context.closePath(); context.fill(); } for(var i = 0; i < 10; i++){ context.beginPath(); context.arc(50 + i*90,660,40,0,2*Math.PI*(i+1)/10,true); context.fill(); } } }
|
效果 — D:
PS:beginPath()和closePath()不一定要成对出现。
如果绘制出来的图形是不封闭图形closePath()会将其首尾用一条线段连接起来。如果不想让其首尾连接,则不要调用closePath()。
方法二:arcTo
context.moveTo(x0,y0);
context.arcTo(x1,y1,x2,y2,radius);
起始点:<x0,y0>
控制点:<x1,y1>
结束点:<x2,y2>
将绘制起始点<x0,y0>与控制点<x1,y1>,结束点<x2,y2>两个点的两条连线相切的圆弧
<x0,y0>是绘制的起始点却不一定是圆弧的起始点,圆弧的起始点和重点是圆弧与两条线段的切点,且切点却不一定在两条线段上
代码 — D:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| window.onload = function(){ var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); context.beginPath(); context.moveTo(20,20); context.lineTo(120,20); context.lineTo(120,120); context.lineWidth=2; context.strokeStyle="gray"; context.stroke(); context.beginPath(); context.moveTo(20,20); context.arcTo(120,20,120,120,60); context.lineWidth = 3; context.strokeStyle = "lightBlue"; context.stroke(); } }
|
效果 — D:
更灵活的曲线
你可以采用上述的绘制弧的方式来绘制曲线,也可以采用更灵活的方式。
二次贝塞尔曲线
context.moveTo(x0,y0)
context.quadraticCurveTo(x1,y1,x2,y2)
起始点:<x0,y0>
控制点:<x1,y1>
结束点:<x2,y2>
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| window.onload = function(){ var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); context.beginPath(); context.moveTo(20,20); context.lineTo(120,20); context.lineTo(120,120); context.lineWidth=2; context.strokeStyle="gray"; context.stroke(); context.beginPath(); context.moveTo(20,20); context.quadraticCurveTo(120,20,120,120) context.lineWidth = 3; context.strokeStyle = "lightBlue"; context.stroke(); } }
|
效果:
三次贝塞尔曲线
context.moveTo(x0,y0)
context.bezierCurveTo(x1,y1,x2,y2,x3,y3)
起始点:<x0,y0>
控制点1:<x1,y1>
控制点1:<x2,y2>
结束点:<x3,y3>
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| window.onload = function(){ var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); context.beginPath(); context.moveTo(70,120); context.lineTo(30,10); context.closePath(); context.lineWidth=2; context.strokeStyle="gray"; context.stroke(); context.beginPath(); context.moveTo(160,10); context.lineTo(120,120); context.closePath(); context.stroke(); context.beginPath(); context.moveTo(70,120); context.bezierCurveTo(30,10,160,10,120,120) context.lineWidth = 3; context.strokeStyle = "lightBlue"; context.stroke(); } }
|
效果:
文字渲染
//文本内容的当前字体属性
context.font = “font-style font-variant font-weight font-size font-family”;
//文本内容的当前对齐方式
context.textAlign=”center|end|left|right|start”;
//在绘制文本时使用的当前文本基线
context.textBaseline=”alphabetic|top|hanging|middle|ideographic|bottom”;
context.fillText(string,x,y,[maxlen]);
context.strokeText(string,x,y,[maxlen]);
//检查字体的宽度
context.measureText(text).width;
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| window.onload=function(){ var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); var txt = "Hello World"; context.font="bold 30px Arial"; context.fillStyle="#058"; context.fillText("width:"+context.measureText().width,40,130); context.fillText(txt,40,90); context.strokeStyle="#058"; context.strokeText("txt,40,170,100); } }
|
效果:
canvas绘制图形进阶
图形变换
缩放:
context.scale(scalewidth,scaleheight);
缩放当前绘图,更大或更小。
scalewidth:缩放当前绘图的宽度 (1=100%, 0.5=50%, 2=200%, 依次类推)
scaleheight:缩放当前绘图的高度 (1=100%, 0.5=50%, 2=200%, etc.)
旋转:
context.rotate(angle);
旋转当前的绘图。
angle:旋转角度,以弧度计。
如需将角度转换为弧度,请使用 degrees 乘以 Math.PI/180 公式进行计算。
举例:如需旋转 5 度,可规定下面的公式:5 乘以 Math.PI/180。
重新映射:
context.translate(x,y);
重新映射画布上的 (0,0) 位置
x:添加到水平坐标(x)上的值
y:添加到垂直坐标(y)上的值
图形变换
context.transform(a,b,c,d,e,f);
替换绘图的当前转换矩阵
a:水平缩放绘图
b:水平倾斜绘图
c:垂直倾斜绘图
d:垂直缩放绘图
e:水平移动绘图
f:垂直移动绘图
context.setTransform(a,b,c,d,e,f);
将当前转换重置为单位矩阵。然后运行 transform()
a:水平缩放绘图
b:水平倾斜绘图
c:垂直倾斜绘图
d:垂直缩放绘图
e:水平移动绘图
f:垂直移动绘图
填充样式
fillStyle 属性设置或返回用于填充绘画的颜色、渐变或模式。
context.fillStyle = color|gradient|image|canvas|video;
fillStyle = color
fillStyle = gradient
Linear Gradient
var grd = context.createLinearGradient(xstart,ystart,xend,yend);
Radial Gradient
var grd = context.createRadialGradient(x0,y0,r0,x1,y1,r1);
grd.addColorStop(stop,color);
fillStyle = image || canvas || video
createPattern(img,repeat-style)
createPattern(canvas,repeat-style)
createPattern(video,repeat-style)
repeat-style: no-repeat
repeat-x
repeat-y
repeat
绘制星星
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| window.onload=function(){ var canvas = document.getElementById("canvas"); canvas.width=800; canvas.height=800; if(canvas.getContext){ var context = canvas.getContext("2d"); for(var i=0;i<200;i++){ var r=Math.random()*10+10; var x=Math.random()*canvas.width; var y=Math.random()*canvas.height; var a=Math.random()*360; drawStar(context,x,y,r,r/2,a); } } } function drawStar(cxt,x,y,outerR,innerR,rot){ cxt.beginPath(); for(var i=0;i<5;i++){ cxt.lineTo(Math.cos((18+i*72-rot)/180*Math.PI)*outerR+x,-Math.sin((18+i*72-rot)/180*Math.PI)*outerR+y); cxt.lineTo(Math.cos((54+i*72-rot)/180*Math.PI)*innerR+x,-Math.sin((54+i*72-rot)/180*Math.PI)*innerR+y); } cxt.closePath(); cxt.fillStyle="#fb3"; cxt.strokeStyle="#fd5"; cxt.lineWidth=3; cxt.lineJoin="round"; cxt.fill(); cxt.stroke(); }
|
效果:
绘制月亮
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| window.onload=function(){ var canvas = document.getElementById("canvas"); if(canvas.getContext){ var context = canvas.getContext("2d"); fillMoon(context,2,100,100,80,20,0); } } function fillMoon(cxt,d,x,y,R,rot,fillColor){ cxt.save(); cxt.translate(x,y); cxt.rotate(rot*Math.PI/180); cxt.scale(R,R); pathMoon(cxt,d); cxt.fillStyle=fillColor||"#fb5"; cxt.fill(); cxt.restore(); } function pathMoon(cxt,d){ cxt.beginPath(); cxt.arc(0,0,1,0.5*Math.PI,1.5*Math.PI,true); cxt.moveTo(0,-1); cxt.arcTo(d,0,0,1,dis(0,-1,d,0)/d); cxt.closePath(); } function dis(x1,y1,x2,y2){ return Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)); }
|
效果: