canvas画图教程(第75节Canvas绘图上)

HTML5新增一个元素Canvas以及与这个元素伴随而来的一套编程接口canvas API,可以在页面上绘制出任何想要的、动态的图形与图像,创造出丰富多彩的Web页面;它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面;

canvas元素相当于在页面上放置了一块画布,其本身并不能实现图形绘制,也没有任何外观,只是一块无色透明的区域,需要几组API进行操作,最重要的是就是具备基本绘图能力的2D上下文(context)及文本API,利用这些API,可以添加线条、图片、文字、绘画、加入高级动画等;现在还可以使用用于绘制硬件加速的WebGL的3D上下文;

基本用法:要使用canvas元素,必须先设置其width和height属性,指定可以绘图的区域大小;该标签中的内容,为后备信息,当浏览器不支持canvas时,就会显示这些信息,如:

<canvas id="drawing" width="400" height="300">你的浏览器不支持canvas</canvas>

canvas 的默认大小为 300px×150px,其对应的DOM元素也具有width和height属性,并且,可以通过CSS设置其样式;

<style> #drawing{background-color: lightblue; width:400px; height:300px;} </style> <canvas id="drawing"></canvas> <script> var drawing = document.getElementById("drawing"); console.log(drawing); console.log(drawing.width ":" drawing.height); // 还是默认的300pxX150px </script>

canvas是无色透明的,如果不添加任何样式或不绘制任何图形,在页面上是看不到该元素的;

绘制图形的步骤:1、取得canvas元素:

var canvas = document.getElementsByTagName("canvas")[0];

2、取得上下文(context):进行图形绘制时,需要使用到图形上下文(graphics context),图形上下文是一个封装了很多绘图功能的对象;使用canvas对象的getContext()方法获得图形上下文,在该方法中需要传入“2d”;

var context = canvas.getContext("2d");

3、设定绘图样式:在绘制的时候,首先要设定好绘图的样式,然后再调用有关方法进行图形的绘制,如:fillStyle属性,设定填充的样式,在该属性中填入颜色值;strokeStyle属性,设置边框的样式,值也是颜色值;使用lineWidth属性设置边框的宽度;

context.fillStyle = "yellow"; context.strokeStyle = "green"; context.lineWidth = 10;

4、填充与绘制边框:使用相应的绘制方法进行绘制,如使用fill()方法填充与stroke()方法描边;填充是指填充图形内部;描边是指不填充图形内部,只绘制图形的边框;对于矩形来说, fillrect()方法绘制矩形内部,strokeRect()方法绘制矩形边框;

context.fillRect(0,0,100,100); context.strokeRect(0,0,100,100);

2D上下文(context):使用canvas对象的getContext()方法获得图形上下文,在该方法中需要传入“2d”,就会获取一个在2D环境中绘制图形的CanvasRenderingContext2D类型的上下文对象,被称为渲染上下文(The rendering context),目前只支持2D,没有3D之类的,但不排除今后会扩展;

// 确定浏览器是否支持canvas元素 if(canvas.getContext){ var context = canvas.getContext("2d"); console.log(context); // CanvasRenderingContext2D // ... }

使用2D绘图上下文提供的方法,可以绘制简单的2D图形,比如矩形、弧线和路径;如果利用它们结合不同的填充和描边样式,就能绘制出非常复杂的图形;如:

context.fillStyle = "rgba(0, 0, 200, 0.5)"; context.fillRect (50, 50, 100, 100);

每个canvas只能有一个上下文对象,多次调用getContext()返回的是同一个对象;

var context = drawing.getContext("2d"); var context1 = drawing.getContext("2d"); console.log(context === context1); // true

其拥有一个canvas属性,是与当前上下文关联的HTMLCanvasElement对象的只读引用;

canvas的尺寸和坐标:canvas默认的坐标系是以<canvas>元素的左上角(0, 0)为坐标原点;所有图形元素的坐标都基于这个原点计算,x值越大表示越靠右,y值越大表示越靠下,反之亦然;

canvas画图教程(第75节Canvas绘图上)(1)

画布的尺寸和坐标系都是以CSS像素为单位的;

默认情况下,canvas的width和height表示水平和垂直两个方向上可用的像素数目;

画布的尺寸和CSS定义的尺寸是完全不同的概念:画布的尺寸是由<canvas>元素的width和height属性定义的,而CSS中定义的尺寸是画布元素在页面中显示的尺寸;如果两者定义的尺寸不相同,则画布上的像素会自动缩放,以适合CSS中定义的尺寸;另外,画布中的坐标,也是根据画布的width和height属性定义的;

画布上的点可以使用浮点数来指定坐标,但是它们不会自动转换成整型值,画布会采用反锯齿的方式来模拟部分填充的像素;

画布的尺寸是不能随意更改的,除非完全重置画布;重置画布的width和heigth属性,会清空整个画布,擦除当前的路径并且会重置所有的图形属性(包括当前的变换和裁剪区域)为初始状态;

绘制基本图形:

填充和描边:

2D上下文的基本绘图操作是填充和描边;填充,就是用指定的样式(颜色、渐变或图像)填充图形;描边,就是只在图形的边缘画线;(这个,前面讲过了)

大多数2D上下文操作都会细分为填充和描边两个操作,而操作的结果取决于两个属性:fillStyle和strokeStyle;

这两个属性的值可以是字符串、渐变对象或模式对象,而且它们的默认值都是“#000000”;如果为它们指定表示颜色的字符串值,格式是与CSS一致的,如:

// ... context.fillStyle = "#0000ff"; context.strokeStyle = "red"; context.fillRect(50,50,200,100); context.strokeRect(50,200,200,100); // ...

绘制矩形:在canvas中,绘图有两种形式:矩形和路径,除了矩形,所有其他类型的图形都是通过一条或者多条路径组合而成的;

矩形是唯一一种可以直接在2D上下文中绘制的形状,其分别使用fillRect(x,y,width,height)、strokeRect(x,y,width,height)方法来绘制填充矩形和绘制矩形边框,两者均接收4个参数:矩形的x、y坐标、矩形的宽和高,单位为像素;填充和描边的颜色通过fillStyle和strokeStyle两个属性指定,如:

function draw(id){ var drawing = document.getElementById(id); if(drawing.getContext){ var context = drawing.getContext("2d"); // 绘制画布背景 context.fillStyle = "#EEEEFF"; context.fillRect(0,0,canvas.width, canvas.height); // 绘制蓝色矩形 context.fillStyle = "rgba(0,0,255,0.5)"; context.strokeStyle = "blue"; context.fillRect(50,50,100,100); context.strokeRect(50,50,100,100); // 绘制红色矩形 context.strokeStyle = "#ff0000"; context.fillStyle = "rgba(255,0,0,0.2)"; context.strokeRect(100,100,100,100); context.fillRect(100,100,100,100); } } draw("canvas");

示例:绘制国际象棋棋盘:

for (i=0; i<8; i ){ // 行 for (j=0; j<8; j ){ // 列 if ((i j) % 2 == 0) context.fillStyle = 'black'; else context.fillStyle= 'white'; context.fillRect(j*50, i*50, 50, 50); } }

示例:绘制柱状图表

var data = [100, 50, 20, 30, 100]; var colors = ["red", "orange", "yellow", "green", "blue"]; context.fillStyle = "white"; context.fillRect(0, 0, canvas.width, canvas.height); for(var i=0; i<data.length; i ){ var v = data[i]; context.fillStyle = colors[i]; context.fillRect(25 i*50, 280-v*2, 50, v*2); }

关于矩形,还有一个clearRect(x, y, width, height)方法,该方法将指定的矩形区域中的图形进行擦除,使得矩形区域中的颜色全部变为透明,其参数与前两个方法一致;如:

// 在两个矩形重叠的部分清除一个小矩形 context.clearRect(110,110,30,30); // 重新绘制 context.strokeStyle = "black"; context.fillStyle = "black"; context.fillRect(250,25,100,100); context.clearRect(270,45,60,60); context.strokeRect(275,50,50,50);

以上绘制的矩形,由于没用到路径,所以一旦绘制,会立即生效,并在canvas中渲染;

清理画布和恢复画布状态:

<input type="button" value="清空画布" onClick="clearMap();" /> <script type="text/javascript"> var c = document.getElementById("canvas"); var context = c.getContext("2d"); context.fillStyle = "red"; context.strokeStyle = "yellow"; context.beginPath(); context.arc(200,150,100,-Math.PI*5/6,true); context.stroke(); function clearMap() { context.clearRect(0,0,300,200); } </script> 绘制路径: 图形的基本元素是路径,其是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合;一个路径,一般都是闭合的; 使用路径绘制图形需要一些步骤:创建路径起始点、使用相关命令画出路径、之后把路径封闭;一旦路径生成,就可以通过描边或填充路径区域来渲染图形;

2D上下文提供了多个绘制路径的方法,通过这些方法就可以绘制出复杂的图形和线条:beginPath()方法,新建一条路径,不需要参数,表示要开始绘制新路径,其后再调用一系列方法来实际地绘制路径;其后绘制的路径都是子路径,都属于同一条路径;再使用moveTo()方法,移动当前点,它并不绘制线条,它的作用就是确定路径起始点;接着使用绘图方法,例如:lineTo()方法,可以从上一点开始绘制一条线段;或者使用rect()方法,绘制一个矩形路径;再或者使用arc()方法,绘制一条弧线,等等;closePath()方法,闭合路径;此时,就会自动绘制一条连接到路径起点的线条,路径的创建工作就完成了;如:

context.beginPath(); context.moveTo(50,50); context.lineTo(100,50); context.arc(50,50,50,0,Math.PI/2); context.moveTo(200,70); context.arc(200,70,50,0,Math.PI/2,true); context.rect(300,50,100,100); context.closePath();

如果路径已经完成,可以设置绘图样式,并进行绘图,如用fillStyle设置填充样式,并调用fill()方法填充,生成实心的图形;或使用strokeStyle属性设置边框样式,并调用stroke()方法对路径描边,即勾勒图形轮廓;

// ... context.fillStyle = "green"; context.fill(); context.strokeStyle = "yellow"; context.lineWidth = 10; context.stroke();

本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧形等)构成一个完整的路径,当调用stroke()或fill()方法后就会绘制图形;

而当每次调用closePath()方法之后,列表就被清空重置,之后就可以重新绘制新的图形,如:

context.beginPath(); context.moveTo(50,50); context.lineTo(100,50); context.arc(50,50,50,0,Math.PI/2); context.closePath(); context.fillStyle = "red"; context.strokeStyle = "blue"; context.lineWidth = 12; context.fill(); context.stroke(); context.beginPath(); context.moveTo(200,70); context.arc(200,70,50,0,Math.PI/2,true); context.closePath(); context.fillStyle = "blue"; context.strokeStyle = "red"; context.lineWidth = 8; context.fill(); context.stroke(); context.beginPath(); context.rect(300,50,100,100); context.closePath(); context.fillStyle = "green"; context.strokeStyle = "yellow"; context.lineWidth = 4; context.fill(); context.stroke();

闭合路径closePath()方法不是必需的,如果不使用closePath(),则路径不闭合,如:

context.beginPath(); context.arc(150, 150,50, 0, Math.PI); // context.closePath(); context.stroke();

如果不使用beginPath()开始新路径,并不会关闭之前的路径,并且该路径会永远保留着;就算调用fill()方法或stroke()方法进行绘制,路径也不会消失;因此,后续的绘制,还会重复绘制该路径;

移动笔触(当前坐标点):moveTo(x,y)方法:将光标(笔触)移动到指定坐标点,绘制图形的时候就以这个坐标点为起点,也就是确定一个子路径的起点,当个起点也称为当前点;其并不绘制任何内容,但也属于路径描述的一部分;

context.moveTo(50, 50); context.lineTo(150, 150);

在画布中第一次调用相关的绘制路径的方法,有时候并不需要使用moveTo()指定起点位置,起点位置是由该方法的参数指定的;但后续的路径绘制,必须要明确起点位置

当调用beginPath()方法开始新的一段路径时,绘制的新路径的起点由当前的绘制方法自动确定,此时,并不需要明确调用moveTo()方法;当 canvas初始化或者beginPath()调用后,通常会使用moveTo()函数设置起点;

示例:笑脸

context.beginPath(); context.arc(75, 75, 50, 0, Math.PI * 2, true); context.moveTo(110, 75); context.arc(75, 75, 35, 0, Math.PI); // 口 (顺时针) context.moveTo(65, 65); context.arc(60, 65, 5, 0, Math.PI * 2, true); // 左眼 context.moveTo(95, 65); context.arc(90, 65, 5, 0, Math.PI * 2, true); // 右眼 context.closePath(); // 不是必须的 context.stroke();

绘制直线(线段):绘制线段需要lineTo()方法:lineTo(x,y)方法:指定从起点到终点(x, y)的一条直线;可重复使用lineTo方法,会以上一个lineTo()方法指定的点为起点,以当前lineTo()为终点再次创建直线;不断重复这个过程,可以绘制复杂的图形;

context.moveTo(50,50); context.lineTo(100,100); context.lineTo(100,200); 执行stroke()绘制直线; context.moveTo(50,50); context.lineTo(200,200); context.lineTo(50,200); context.stroke();

开始点和之前的绘制路径有关,之前路径的结束点就是接下来的开始点,开始点也可以通过moveTo()函数改变;绘制直线也属于路径,因此在必要的情况下,也需要使用beginPath(),否则也属于连续绘制,或调用closePath()方法闭合路径;

// ... context.beginPath(); context.moveTo(50,50); context.lineTo(200,200); context.lineTo(50,200); context.lineWidth = 20; context.closePath(); // ...

示例:绘制三角形

// 绘制三角形 context.beginPath(); context.moveTo(75, 50); context.lineTo(100, 75); context.lineTo(100, 25); context.fill();

示例:绘制多边形

CanvasRenderingContext2D.prototype.polygon = function(n ,x, y, r, angle, anticlockwise){ this.n = n || 3; this.x = x || 10; this.y = y || 10; this.r = r || 50; this.angle = angle || 0; this.anticlockwise = anticlockwise || false; this.moveTo(this.x this.r * Math.sin(this.angle), this.y - this.r * Math.cos(this.angle)); var delta = 2 * Math.PI / this.n; for(var i=1; i<this.n; i ){ this.angle = this.anticlockwise ? -delta : delta; this.lineTo(this.x this.r * Math.sin(this.angle), this.y - this.r * Math.cos(this.angle)); } this.closePath(); } var canvas = document.getElementsByTagName("canvas")[0]; if(canvas.getContext){ var context = canvas.getContext("2d"); context.beginPath(); context.polygon(3, 50, 70, 50); // 三角形 context.polygon(4, 150, 60, 50, Math.PI / 4); // 正方形 context.polygon(5, 255, 55, 50); // 五边形 context.polygon(6, 365, 53, 50, Math.PI / 6); // 六边形 context.polygon(4, 365, 53, 20, Math.PI / 4, true); // 六边形中的小正方形 context.fillStyle = "#ccc"; context.strokeStyle = "#008"; context.lineWidth = 5; context.fill(); context.stroke(); } </script>

填充规则:当用到fill()、clip()和isPointinPath()时,可以选择一个填充规则,即传入一个fillRule参数,该填充规则根据某处在路径的外面或者里面来决定该处是否被填充,这对于自己与自己路径相交或者路径被嵌套的情况是有用的;两个可能的值:

  • "nonzero":non-zero winding rule(非零环绕规则), 默认值;
  • "evenodd":even-odd winding rule;(奇偶环绕规则)

非零环绕原则(nonzero):主要解决绘图中交叉路径的填充问题;

canvas画图教程(第75节Canvas绘图上)(2)

要检测一个点P是否在路径的内部,使用非零环绕原则:想象一条从点P出发沿着任意方向无限延伸(一直延伸到路径所在的区域外)的射线;首先初始化一个计数器为0,然后对所有穿过这条射线的路径进行枚举;每当一条路径顺时针方向穿过射线的时候,计数器就加1;反之,就减1;最后,枚举完所有路径之后,如果计数器的值不是0,那么就认为P是在路径内;反之,如果计数器的值是0,则认为P在路径外;总之,由顺、逆时针穿插次数决定是否填充某一区域;

奇偶环绕规则(evenodd):奇数表示在路径内,偶数表示在路径外;从任意位置p作一条射线,若与该射线相交的路径的数目为奇数,则p是路径内部的点,否则是外部的点;

canvas画图教程(第75节Canvas绘图上)(3)

如:

context.beginPath(); context.arc(50, 50, 30, 0, Math.PI*2); context.arc(50, 50, 15, 0, Math.PI*2, true); // context.fill(); // context.fill("nonzero"); context.fill("evenodd");

如:绘制一个五角星

context.arc(100, 100, 100, 0, Math.PI*2); context.fillStyle = '#D43D59'; context.fill(); context.beginPath(); context.moveTo(100, 0); context.lineTo(100 Math.cos(Math.PI*3/10)*100, 100 Math.sin(Math.PI*3/10)*100); context.lineTo(100-Math.cos(Math.PI*1/10)*100, 100-Math.sin(Math.PI*1/10)*100); context.lineTo(100 Math.cos(Math.PI*1/10)*100, 100-Math.sin(Math.PI*1/10)*100); context.lineTo(100-Math.cos(Math.PI*3/10)*100, 100 Math.sin(Math.PI*3/10)*100); context.lineTo(100, 0); context.closePath(); context.strokeStyle = "rgb(0,0,0)"; context.fillStyle = "#246AB2" // context.fill('nonzero'); context.fill('evenodd'); context.stroke();

如:绘制一个大箭头图标

context.arc(250, 250, 150, 0, Math.PI * 2); context.moveTo(250, 150); context.lineTo(350, 250); context.lineTo(150, 250); context.closePath(); context.moveTo(200, 250); context.lineTo(300, 250); context.lineTo(300, 350); context.lineTo(200, 350); context.closePath(); context.fillStyle = "#0D6EB8"; // context.fill("nonzero"); context.fill("evenodd");

示例:使用数学方程绘制图案图形

var dx = 150, dy = 150; var s = 100; context.beginPath(); context.fillStyle = "rgb(100,255,100)"; context.strokeStyle = "rgb(0,0,100)"; var dig = Math.PI / 15 * 11; for(var i=0; i<30; i ){ var x = Math.sin(i * dig); var y = Math.cos(i * dig); console.log(dx x * s, dy y * s); context.lineTo(dx x * s, dy y * s); } context.closePath(); // context.fill(); context.fill("evenodd"); context.stroke(); // 以下包装成一个函数 function draw(offsetX, n){ var dig = Math.PI / 15 * n; context.beginPath(); for(var i = 0 ; i < 30 ; i ){ var x = Math.sin(i * dig); var y = Math.cos(i * dig); context.lineTo(offsetX x * 80, 150 y * 80); } context.closePath(); context.fillStyle = "green"; // context.fill(); context.fill("evenodd"); context.strokeStyle = "#666"; context.stroke(); } var data = [14,13,19,7,26]; data.forEach(function(v, i){ draw((i 1) * 160, v); });

示例:使用鼠标实时绘制图形

canvas.onmousedown = function(e) { this.X1 = e.offsetX; this.Y1 = e.offsetY; this.isMouseDown = true; }; canvas.onmousemove = function(e) { if (this.isMouseDown) { this.X2 = e.offsetX; this.Y2 = e.offsetY; this.drawing(this.X1, this.Y1, this.X2, this.Y2, e); } }; canvas.onmouseup = function(e) { this.isMouseDown = false; }; canvas.drawing = function (x1, y1, x2, y2, e) { if (!context) { return; } else { context.fillStyle = "red"; context.strokeStyle = "blue"; context.lineWidth = 5; context.beginPath(); context.moveTo(x1, y1); context.lineTo(x2, y2); context.stroke(); this.X1 = this.X2; this.Y1 = this.Y2; context.closePath(); } }

例子:迷你图迷你图(sparkline)是指用于显示少量数据的图形,通常会被嵌入文本流中,形如:server:小图;迷你图是由作者Edward Tufte杜撰的,他将该词用于描述“内嵌在文字、数字、图片中的小且高分辨率的图形”;迷你图是数据密集、设计简单、单词大小的图形,如:

<style> .sparkline{background-color:#ddd; color:red; display: inline-block;} </style> Load:<span class="sparkline">3 5 7 6 6 9 11 15</span>, source:<span class="sparkline" data-height="36" data-width="100">6 14 8 9 10 13 18</span> complete:<span class="sparkline" data-ymax="18" data-ymin="6">12,3,8,2,88</span> <script> window.onload = function(){ var elts = document.getElementsByClassName("sparkline"); main: for(var e = 0; e<elts.length; e ){ var elt = elts[e]; var content = elt.textContent || elt.innerText; var content = content.replace(/^\s |\s $/g, ""); var text = content.replace(/#.*$/gm, ""); text = text.replace(/[\n\r\t\v\f]/g, " "); var data = text.split(/\s |\s*,\s*/); for(var i=0; i<data.length; i ){ data[i] = Number(data[i]); if(isNaN(data[i])) continue main; } var style = getComputedStyle(elt, null); var color = style.color; var height = parseInt(elt.getAttribute("data-height")) || parseInt(style.fontSize) || 20; var width = parseInt(elt.getAttribute("data-width")) || data.length * (parseInt(elt.getAttribute("data-dx")) || 6); var ymin = parseInt(elt.getAttribute("data-ymin")) || Math.min.apply(Math, data); var ymax = parseInt(elt.getAttribute("data-ymax")) || Math.max.apply(Math, data); if(ymin >= ymax) ymax = ymin 1; var canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; canvas.title = content; elt.innerHTML = ""; elt.appendChild(canvas); var context = canvas.getContext("2d"); for(var i=0; i<data.length; i ){ var x = width * i / data.length; var y = (ymax - data[i]) * height / (ymax - ymin); context.lineTo(x,y); } context.strokeStyle = color; context.stroke(); } } </script>

绘制曲线:角度和弧度:

canvas画图教程(第75节Canvas绘图上)(4)

夹角:从一个点发射(延伸)出两条线段,两条线相交的部分会构成一个夹角;角度:两条相交直线中的任何一条与另一条相叠合时必须转动的量的量度,单位符号为°;周角:一条直线围绕起点需要与自己相叠合时必须转动的量的量度被称为周角,周角等分为360度;弧度:角的度量单位,弧长等于半径的弧其所对的圆心角为1弧度(弧长等于半径时,射线夹角为1弧度);角度与弧度的换算公式为:弧度 = 角度 * (Math.PI / 180);

在使用JavaScript编写代码进行相关计算的时候,经常需要使用Math提供的方法:

canvas画图教程(第75节Canvas绘图上)(5)

Math.sin(弧度) 夹角对面的边与斜边的比值;Math.cos(弧度) 夹角侧面的边与斜边的比值;

圆形上点坐标的计算公式:坐标 = ( x0 Math.cos(angle) x R,y0 Math.sin(angle) x R )其中x0和y0为圆心坐标,angle为弧度,R为圆的半径;

例如:使用三角函数来绘制曲线:

context.beginPath(); for(var x = 30, y = 0; x<1000; x ){ // 高度 * 波长 中心轴位置 y = 50 * Math.sin(x / 25) 100; context.lineTo(x, y); } context.stroke();

绘制弧形和圆形:arc(x, y, radius, startAngle, endAngle[, anticlockwise])方法:此方法可以绘制一条弧,其以(x, y)为圆心,以radius为半径绘制一条弧线(圆),从 startAngle开始到endAngle结束的弧度,按照anticlockwise给定的方向(默认为顺时针)来绘制;参数anticlockwise指定是否按逆时针方向进行绘制,为true时,逆时针,反之顺时针;

// context.arc(100,100,50,0, Math.PI*3/2); context.arc(100,100,50,0, Math.PI*3/2,true); context.stroke();

arc()方法中表示角的单位是弧度,不是角度;角度都是按顺序时针计算的,无论是按时针还是按逆时针;

由于arc()绘制的是路径,所以在必要的情况下,需要调用beginPath();如果需要扇形,可以使用closePath()方法闭合路径;

当第一个调用arc()或在beginPath()方法后第一个调用arc()方法时,会在确定其圆心、半径及开始角度的情况下,自动确定起点位置;其它情况下,需要使用moveTo()指定起点位置,例如:先将当前点和弧形的起点用一条直线连接,然后再用圆的一部分(圆弧)来连接弧形的起点和终点,并把弧形终点作为新的当前点;

// 绘制一个圆弧 context.beginPath(); context.moveTo(50, 50); context.arc(100, 100, 50, Math.PI*3/2, 0); context.lineTo(150,150); context.stroke(); // 绘制一个san形(楔形) context.beginPath(); context.moveTo(200, 50); context.arc(200, 50, 50, 0, Math.PI / 2, false); context.closePath(); context.stroke(); // 同样的san形(楔形),方向不同 context.beginPath(); context.moveTo(350, 100); context.arc(350, 100, 50, 0, Math.PI / 2, true); context.closePath(); context.stroke();

arc()不仅可以绘制圆弧,也可以用来绘制圆形,即startAngle, endAngle形成Math.PI*2(360度),如:

context.beginPath(); context.arc(100, 100, 80, 0, Math.PI*2); // context.arc(100, 100, 80, Math.PI / 4, Math.PI * 2 Math.PI / 4); context.closePath(); // 没有必要调用这一句 context.lineWidth = 10; context.fillStyle = "red"; context.fill(); context.stroke();

绘制有交叉路径时弧时,要注意填充规则,如:

context.beginPath(); context.arc(100, 100, 50, 0, Math.PI * 2, true); context.arc(100, 100, 60, 0, Math.PI * 2, false); context.fill(); context.beginPath(); context.arc(300, 100, 50, 0, Math.PI, true); context.arc(300, 100, 60, 0, Math.PI * 2, true); // 使用奇偶环绕规则 context.fill('evenodd'); context.beginPath(); context.arc(100, 100, 60, 0, Math.PI * 2, true); context.arc(140, 100, 60, 0, Math.PI * 2, false); context.arc(180, 100, 60, 0, Math.PI * 2, true); context.fill();

循环绘制多个,如:

for(var i=0;i<15;i ){ context.strokeStyle = "#FF00FF"; context.beginPath(); context.arc(0,350,i*10,0,Math.PI*3/2,true); context.closePath(); context.stroke(); } for(var i=0;i<10;i ){ context.beginPath(); context.arc(i*25,i*25,i*10,0,Math.PI*2,true); context.closePath(); context.fillStyle = 'rgba(255,0,0,0.25)'; context.fill(); }

绘制不同的圆弧:

for(var i = 0; i < 4; i ){ for(var j = 0; j < 3; j ){ context.beginPath(); var x = 25 j * 50; // x 坐标值 var y = 25 i * 50; // y 坐标值 var radius = 20; // 圆弧半径 var startAngle = 0; // 开始点 var endAngle = Math.PI (Math.PI * j) / 2; // 结束点 var anticlockwise = i % 2 == 0 ? false : true; // 顺时针或逆时针 context.arc(x, y, radius, startAngle, endAngle, anticlockwise); if (i>1){ context.fill(); } else { context.stroke(); } } }

绘制不同圆角的矩形:

// 绘制圆角矩形 context.beginPath(); context.moveTo(360, 0); context.arc(380, 30, 30, Math.PI * 3 / 2, 0); // 上边和右上角 context.arc(390, 100, 20, 0, Math.PI / 2); // 右上角和右下角 context.arc(340, 110, 10, Math.PI / 2, Math.PI); // 底边和左下角 context.arc(330, 0, 0, Math.PI, 0); // 左边和左上角 context.closePath(); context.fill(); context.stroke();

示例:绘制一个WIFI图标:

// wifi图标 context.lineWidth = 3; for(var i=0;i<3;i ){ context.beginPath(); context.arc(100, 150, 15 i*12, Math.PI, Math.PI*3/2); context.stroke(); } context.beginPath(); context.arc(98, 148, 3, 0, Math.PI*2); context.fill();

示例:绘制一个时钟表盘

context.beginPath(); // 外圆 context.arc(100,100,99,0,Math.PI * 2, false); // 内圆 context.moveTo(194, 100); context.arc(100,100,94,0,Math.PI * 2, false); // 分针 context.moveTo(100,100); context.lineTo(100,15); // 时针 context.moveTo(100,100); context.lineTo(35,100); context.closePath(); context.stroke();

示例:绘制一个手镯

// 使用了非零环绕规则 function bracelet(){ context.beginPath(); context.arc(300,190,150,0,Math.PI*2,false); //顺时针 context.arc(300,190,100,0,Math.PI*2,true); //逆时针 context.fillStyle="rgba(100,140,230,0.5)"; context.strokeStyle = context.fillStyle; context.shadowColor="rgba(0,0,0,0.8)"; context.shadowOffsetX = 12; context.shadowOffsetY = 12; context.shadowBlur = 15; context.fill(); context.stroke(); } bracelet();

示例:绘制等分圆:

//描边 drawCircle(100,100,40,2,true); drawCircle(200,100,40,3,true); drawCircle(300,100,40,4,true); drawCircle(400,100,40,20,true); drawCircle(500,100,40,100,true); drawCircle(600,100,40,200,true); //填充 drawCircle(100,200,40,2); drawCircle(200,200,40,3); drawCircle(300,200,40,4); drawCircle(400,200,40,20); drawCircle(500,200,40,100); drawCircle(600,200,40,200); function drawCircle(x,y,r,n,isStroke){ for(var i = 0 ; i < n ; i ){ //计算开始和结束的角度 var angle = 2 * Math.PI / n; var startAngle = angle * i; var endAngle = angle * (i 1); context.beginPath(); //设置绘制圆的起点 context.moveTo(x,y); context.arc(x,y,r,startAngle,endAngle,false); if(isStroke){ // context.strokeStyle = getRandomColor(); context.stroke(); }else{ context.fillStyle = getRandomColor(); context.fill(); } } } //获取填充的颜色/随机 function getRandomColor(){ var r = getRandom(); var g = getRandom(); var b = getRandom(); return "rgb(" r "," g "," b ")"; } function getRandom(){ return Math.floor(Math.random() * 256); }

示例:绘制饼形图

var data = [100, 50, 20, 30, 100]; context.fillStyle = "white"; context.fillRect(0,0,canvas.width,canvas.height); var colors = [ "red","orange", "yellow","green", "blue"]; var total = 0; for(var i=0; i<data.length; i ) total = data[i]; var prevAngle = 0; for(var i=0; i<data.length; i ) { var fraction = data[i]/total; var angle = prevAngle fraction * Math.PI * 2; context.fillStyle = colors[i]; context.beginPath(); context.moveTo(150,150); context.arc(150,150, 100, prevAngle, angle, false); context.lineTo(150,150); context.fill(); context.strokeStyle = "black"; context.stroke(); prevAngle = angle; }

示例:饼形的类

function PieChart(context){ this.context = context || document.getElementById("canvas").getContext("2d"); this.x = this.context.canvas.width / 2; this.y = this.context.canvas.height / 2; this.r = 120; this.outLine = 20; this.dataList = null; } PieChart.prototype = { constructor:PieChart, init:function(dataList){ this.dataList = dataList || [{value:100}]; this.transformAngle(); this.drawPie(); }, drawPie:function(){ var startAngle = 0,endAngle; for(var i = 0 ; i < this.dataList.length ; i ){ var item = this.dataList[i]; endAngle = startAngle item.angle; this.context.beginPath(); this.context.moveTo(this.x,this.y); this.context.arc(this.x,this.y,this.r,startAngle,endAngle,false); var color= this.context.strokeStyle= this.context.fillStyle= this.getRandomColor(); this.context.stroke(); this.context.fill(); this.drawPieLegend(i); startAngle = endAngle; } }, drawPieLegend:function(index){ var space = 10; var rectW = 40; var rectH = 20; var rectX = this.x this.r 80; var rectY = this.y (index * 30); this.context.fillRect(rectX,rectY,rectW,rectH); // this.context.beginPath(); }, getRandomColor:function(){ var r = Math.floor(Math.random() * 256); var g = Math.floor(Math.random() * 256); var b = Math.floor(Math.random() * 256); return 'rgb(' r ',' g ',' b ')'; }, transformAngle:function(){ var self = this; var total = 0; this.dataList.forEach(function(item,i){ total = item.value; }) this.dataList.forEach(function(item,i){ self.dataList[i].angle = 2 * Math.PI * item.value/total; }) }, } var data = [{value:20},{value:26}, {value:20},{value:63},{value:25}] var pie = new PieChart().init(data);

arcTo(x1, y1, x2, y2, radius)方法:根据控制点(x1, y1)、(x2, y2)和半径绘制圆弧路径;其根据当前点与给定的控制点1连接的直线,和控制点1与控制点2连接的直线,作为使用指定半径的圆的切线,画出两条切线之间的弧线路径;

canvas画图教程(第75节Canvas绘图上)(6)

说明:图例中的x0和y0,为当前点,x1和y1代表第一个控制点的坐标,x2和y2代表第二个控制点的坐标,radius代表圆弧半径;

context.beginPath(); context.moveTo(50,50); context.arcTo(200, 60, 250, 300, 60); context.stroke(); // 绘制提示点 context.fillRect(48,48, 4, 4); // 起点 context.fillRect(198,58, 4, 4); // 起点 context.fillRect(248,298, 4, 4); // 起点 // 绘制条切线 context.setLineDash([3]); context.beginPath(); context.moveTo(50, 50); context.lineTo(200, 60); context.lineTo(250, 300); context.stroke();

如果半径radius为0,则会绘制一点直线;

如,绘制一个圆角矩形:

context.moveTo(400, 50); context.arcTo(500, 50, 500, 150, 30); context.arcTo(500, 150, 400, 150, 20); context.arcTo(400, 150, 400, 50, 10); context.arcTo(400, 50, 500, 50, 0); context.stroke();

bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y)方法:贝塞尔曲线(也称为三次贝塞尔曲线),将从当前点到指定坐标点中间的贝塞尔曲线添加到路径中,贝塞尔曲线需要两个控制点,分别为cp1x、cp1y和cp2x、cp2y,x和y是贝塞尔曲线的终点坐标;

canvas画图教程(第75节Canvas绘图上)(7)

context.moveTo(100, 300); context.bezierCurveTo(160,200,280,300,320,250) context.stroke(); context.fillRect(97, 297, 6, 6); // 起点 context.fillRect(317, 247, 6, 6); // 终点 context.fillRect(160-3, 200-3, 6,6); // 标记控制点 context.fillRect(280-3, 300-3, 6,6); // 标记控制点 // 线 context.beginPath(); context.strokeStyle = "red"; context.moveTo(100, 300); context.lineTo(160, 200); context.moveTo(320, 250); context.lineTo(280, 300); context.stroke();

结合正反弦函数绘图:

var dx = dy = 150; var s = 100; context.beginPath(); context.fillStyle = "lightgreen"; var dig = Math.PI / 15 * 11; context.moveTo(dx,dy); for(var i=0; i<30; i ){ var x = Math.sin(i * dig); var y = Math.cos(i * dig); context.bezierCurveTo(dx x*s, dy y*s-100, dx x*s 100, dy y*s, dx x*s, dy y*s); } context.closePath(); context.fill(); context.stroke();

示例:绘制心形

//三次贝塞尔曲线,绘制心形 context.beginPath(); context.moveTo(75, 40); context.bezierCurveTo(75, 37, 70, 25, 50, 25); context.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5); context.bezierCurveTo(20, 80, 40, 102, 75, 120); context.bezierCurveTo(110, 102, 130, 80, 130, 62.5); context.bezierCurveTo(130, 62.5, 130, 25, 100, 25); context.bezierCurveTo(85, 25, 75, 37, 75, 40); context.fill();

quadraticCurveTo(cx, cy, x, y)方法:绘制二次贝塞尔曲线,相对来说,二次贝塞尔曲线的绘制比贝塞尔曲线的绘制容易一些,因为绘制贝塞尔曲线需要两个控制点,而绘制二次贝塞尔曲线时只需要一个控制点;因此quadraticCurveTo方法只需要四个参数就可以了,分别是控制点的坐标(cx, cy)、二次贝塞尔曲线终点的坐标(x,y);

canvas画图教程(第75节Canvas绘图上)(8)

context.moveTo(75, 250); context.quadraticCurveTo(100,200,175,250); context.stroke(); context.fillRect(72, 247, 6, 6); // 起点 context.fillRect(172, 247, 6, 6); // 起点 context.fillRect(100-3, 200-3, 6,6); // 控制点 // 线 context.beginPath(); context.strokeStyle = "red"; context.moveTo(75, 250); context.lineTo(100, 200); context.lineTo(175, 250); context.stroke();

示例:对话气泡

context.beginPath(); context.moveTo(75, 25); context.quadraticCurveTo(25, 25, 25, 62.5); context.quadraticCurveTo(25, 100, 50, 100); context.quadraticCurveTo(50, 120, 30, 125); context.quadraticCurveTo(60, 120, 65, 100); context.quadraticCurveTo(125, 100, 125, 62.5); context.quadraticCurveTo(125, 25, 75, 25); context.stroke();

绘制圆弧或椭圆:ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)方法:绘制一条椭圆路径;椭圆的圆心在(x,y)位置,半径分别是radiusX 和 radiusY ,按照anticlockwise (默认顺时针)指定的方向,从 startAngle 开始绘制,到 endAngle 结束;参数rotation表示椭圆的旋转角度(单位是度,不是弧度),而startAngle和endAngle单位是弧度;

context.lineWidth = 20; context.beginPath(); context.ellipse(200,100,100,50,0,0,Math.PI*2); context.stroke(); context.beginPath(); context.ellipse(200,250,50,30,Math.PI / 4,0,Math.PI*2,true); context.stroke();

如果radiusX与radiusY的值一致,绘制出来的是一个正圆;

// 把上例代码改成 context.ellipse(i*25,i*25,i*10,i*20,30,0,Math.PI*2,true);

该方法一开始不是标准方法,现在还未完全标准化,一开始只有Chrome支持,IE不支持此方法;

参数方程法绘制椭圆:

function ParamEllipse(context, x, y, a, b){ var step = (a > b ) ? 1 / a : 1 / b; context.beginPath(); context.moveTo(x a, y); for(var i=0; i<2*Math.PI; i = step){ context.lineTo(x a * Math.cos(i), y b * Math.sin(i)); } context.closePath(); context.stroke(); } context.lineWidth = 10; ParamEllipse(context, 130, 80, 100,20);

均匀压缩法绘制椭圆:

function EvenCompEllipse(context, x, y, a, b){ context.save(); var r = (a > b) ? a : b; var ratioX = a / r; // x轴缩放比 var ratioY = b / r; // y轴缩放比 context.scale(ratioX, ratioY); // 进行缩放(均匀压缩) context.beginPath(); // 从椭圆的左端点开始逆时针绘制 context.moveTo((x a) / ratioX, y / ratioY); context.arc(x / ratioX, y / ratioY, r, 0 , Math.PI*2); context.closePath(); context.stroke(); context.restore(); } context.lineWidth = 10; EvenCompEllipse(context, 130, 200, 100, 20);

使用三次贝塞尔曲线模拟椭圆:

function BezierEllipse(context, x, y, a, b){ // 0.5和0.6是两个关键系数 var ox = 0.5 * a, oy = 0.6 * b; context.save(); context.translate(x, y); context.beginPath(); // 从椭圆的下端点开始逆时针绘制 context.moveTo(0, b); context.bezierCurveTo(ox, b, a, oy, a, 0); context.bezierCurveTo(a, -oy, ox, -b, 0, -b); context.bezierCurveTo(-ox, -b, -a, -oy, -a, 0); context.bezierCurveTo(-a, oy, -ox, b, 0, b); context.closePath(); context.stroke(); context.restore(); } context.lineWidth = 10; BezierEllipse(context, 470, 80, 100, 20);

二次贝塞尔曲线模拟椭圆:

// 贝塞尔控制点x=(椭圆宽度/0.75)/2 CanvasRenderingContext2D.prototype.oval = function (x, y, width, height) { var k = (width/0.75)/2, w = width/2, h = height/2; this.beginPath(); this.moveTo(x, y-h); this.bezierCurveTo(x k, y-h, x k, y h, x, y h); this.bezierCurveTo(x-k, y h, x-k, y-h, x, y-h); this.closePath(); return this; } context.oval(300,100,200,50); context.lineWidth = 10; context.stroke();

rect(x, y, width, height)方法:绘制一个左上角坐标为(x,y),宽高为width以及height的矩形路径,其路径会自动闭合;当该方法执行的时候,moveTo()方法自动设置坐标参数(0,0),即当前笔触自动重置回默认坐标;

context.rect(10, 10, 100, 100); context.fill();

roundrect(x, y, width, height)方法:这是由Chrome实现的绘制圆角矩形的方法,其它浏览器都不支持,如:

context.beginPath(); context.roundRect(50,40,100,100,50); context.closePath(); context.stroke();

兼容处理:

if(!CanvasRenderingContext2D.prototype.roundRect){ CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r){ this.x = x; this.y = y; this.w = w; this.h = h; this.r = r; this.moveTo(this.x this.r ,this.y); this.arcTo(this.x this.w , this.y , this.x this.w , this.y this.h , this.r); this.arcTo(this.x this.w , this.y this.h , this.x , this.y this.h , this.r); this.arcTo(this.x , this.y this.h , this.x , this.y , this.r); this.arcTo(this.x , this.y , this.x this.r , this.y , this.r); } } var canvas = document.getElementsByTagName("canvas")[0]; if(canvas.getContext){ var context = canvas.getContext("2d"); context.beginPath(); context.roundRect(50,40,100,100,50); context.closePath(); context.stroke(); context.beginPath(); context.roundRect(200,40,100,100,50); context.closePath(); context.fill(); context.beginPath(); context.roundRect(350,40,100,100,10); context.stroke(); context.beginPath(); context.roundRect(500,40,100,100,20); context.closePath(); context.fill(); context.beginPath(); context.roundRect(650,40,120,100,30); context.closePath(); context.stroke(); }

组合应用:

var canvas = document.getElementsByTagName("canvas")[0]; if(canvas.getContext){ var context = canvas.getContext("2d"); roundedRect(context, 12, 12, 150, 150, 15); roundedRect(context, 19, 19, 150, 150, 9); roundedRect(context, 53, 53, 49, 33, 10); roundedRect(context, 53, 119, 49, 16, 6); roundedRect(context, 135, 53, 49, 33, 10); roundedRect(context, 135, 119, 25, 49, 10); context.beginPath(); context.arc(37, 37, 13, Math.PI / 7, -Math.PI / 7); context.lineTo(31, 37); context.fill(); for(var i = 0; i < 8; i ){ context.fillRect(51 i * 16, 35, 4, 4); } for(i = 0; i < 6; i ){ context.fillRect(115, 51 i * 16, 4, 4); } for(i = 0; i < 8; i ){ context.fillRect(51 i * 16, 99, 4, 4); } context.beginPath(); context.moveTo(83, 116); context.lineTo(83, 102); context.bezierCurveTo(83, 94, 89, 88, 97, 88); context.bezierCurveTo(105, 88, 111, 94, 111, 102); context.lineTo(111, 116); context.lineTo(106.333, 111.333); context.lineTo(101.666, 116); context.lineTo(97, 111.333); context.lineTo(92.333, 116); context.lineTo(87.666, 111.333); context.lineTo(83, 116); context.fill(); context.fillStyle = "white"; context.beginPath(); context.moveTo(91, 96); // context.bezierCurveTo(88, 96, 87, 99, 87, 101); // context.bezierCurveTo(87, 103, 88, 106, 91, 106); // context.bezierCurveTo(94, 106, 95, 103, 95, 101); // context.bezierCurveTo(95, 99, 94, 96, 91, 96); // 使用这一句代替以上的4个方法 context.ellipse(91,101,4,5,0,0,Math.PI*2); context.moveTo(103, 96); // context.bezierCurveTo(100, 96, 99, 99, 99, 101); // context.bezierCurveTo(99, 103, 100, 106, 103, 106); // context.bezierCurveTo(106, 106, 107, 103, 107, 101); // context.bezierCurveTo(107, 99, 106, 96, 103, 96); // 使用这一句代替以上的4个方法 context.ellipse(103,101,4,5,0,0,Math.PI*2); context.fill(); context.fillStyle = "black"; context.beginPath(); context.arc(101, 102, 2, 0, Math.PI * 2, true); context.fill(); context.beginPath(); context.arc(89, 102, 2, 0, Math.PI * 2, true); context.fill(); } // 封装的一个用于绘制圆角矩形的函数 function roundedRect(context, x, y, width, height, radius){ context.beginPath(); context.moveTo(x, y radius); context.lineTo(x, y height - radius); context.quadraticCurveTo(x, y height, x radius, y height); context.lineTo(x width - radius, y height); context.quadraticCurveTo(x width, y height, x width, y height - radius); context.lineTo(x width, y radius); context.quadraticCurveTo(x width, y, x width - radius, y); context.lineTo(x radius, y); context.quadraticCurveTo(x, y, x, y radius); context.stroke(); }

Path2D:Canvas 2D API包含一个Path2D的接口,此接口用来声明路径,此路径会被2D上下文使用; Path2D拥有2D上下文的相同的路径方法,它允许在canvas中根据需要创建可以保留并重用的路径;

可以使用Path2D对象的各种方法绘制直线、矩形、圆形、椭圆以及曲线;如:moveTo(x,y)、lineTo(x,y)、rect(x,y,w,h)、arc(x,y,radius,startAngle,endAngle[,anticlockwise])、arcTo(x1,1,x2,y2,radiusX[,radius,rotation])、ellipse(x,y,radius,radius,rotation,startAngle,endAngle[,anticlockwise])、bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y)、quadraticCurveTo(cpx,cpy,x,y)、closePath()

构造函数:Path([path | svgPath]),返回一个新的 Path2D 对象;参数path为另一个Path2D对象,这将该对象所代表的路径复制给新创建的Path2D对象;

new Path2D(); new Path2D(path); new Path2D(d);

还可以在Path2D的构造函数中传递一个代表SVG路径的字符串;

var path = new Path2D("M10 10 h 80 v 80 h -80 Z"); context.fill(path);

创建完Path2D对象后,就可以利用所有绘制路径的方法绘制路径,并可以调用closePath()方法闭合路径,如:

var path1 = new Path2D(); path1.rect(10,10,100,100); var path2 = new Path2D(path1); path2.moveTo(220,60); path2.arc(170,60,50,0,Math.PI*2);

之后可以使用context对象的fill(path)和stroke(path)方法进行填充和描边,参数path指向的Path2D对象;

context.stroke(path2); // ... for(var i=0;i<10;i ){ var path = new Path2D(); path.arc(i*25,i*25,i*10,0,Math.PI*2,true); path.closePath(); context.fillStyle = "rgba(255,0,0,0.25)"; context.fill(path); }

如:

var rectangle = new Path2D(); rectangle.rect(10, 10, 50, 50); var circle = new Path2D(); circle.moveTo(125, 35); circle.arc(100, 35, 25, 0, 2 * Math.PI); context.stroke(rectangle); context.fill(circle);

addPath(path [, transform])方法:添加一条新路径到对当前路径;参数path为需要添加的 Path2D 路径,参数transform是可选的,作为新增路径的变换矩阵;

var p1 = new Path2D(); p1.rect(0,0,100,100); var p2 = new Path2D(); p2.rect(0,0,100,100); var m = document.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGMatrix(); m.a = 1; m.b = 0; m.c = 0; m.d = 1; m.e = 300; m.f = 0; p1.addPath(p2, m); context.fill(p1);

图形属性:图形属性指定了画布的通用图形状态,例如fillStyle、strokeStyle等属性,绘图方法并没有设置这些属性,所以这些属性应该在调用相关绘图方法之前进行设置;这种将从图形状态和绘制指令分离的思想是画布API很重要的概念,和在HTML文档中应用CSS样式来实现结构和样式分离是类似的;

context对象上定义了15个图形属性:

  • fillStyle:填充颜色、渐变和图案
  • strokeStyle:勾勒线段时的颜色、渐变或图案
  • linewidth属性:指定线条的宽度,值可以是任意整数;
  • lineCap属性:如何渲染线段的末端,即为直线添加线帽,值:butt默认值,不为直线添加线帽,round圆形线帽,square正方形线帽;
  • lineJoin属性:如何渲染顶点,即指定两条直线交汇时的拐角形状,值:miter默认值,尖角;round圆角;bevel斜角;
  • miterLimit:斜接顶点的最大长度;
  • font:绘制文本时的CSS字体;
  • textAlign:文本水平对齐;
  • textBaseline:垂直对齐方式;
  • shadowBlur:阴影模糊程序;
  • shadowColor:阴影颜色;
  • shadowOffsetX:阴影水平偏移量;
  • shadowOffsetY:阴影垂直偏移量;
  • globalAlpha:绘制像素时要添加的透明度;
  • globalCompositeOperation:如何合并新的像素点和下面的像素点;

fillStyle和strokeStyle属性:指定了区域填充和线条勾勒的样式;即可以指定颜色,也可以把它们设置为CanvasPattern或者CanvasGradient对象,以实现背景图案或渐变色;如果使用颜色,其使用标准的CSS颜色值,默认为“#000000”;具体的颜色格式有:

var colors = [ "#f44", // 16进制RGB,红色 "#44ff44", // 16进制RRGGBB,绿色 "rgb(60,60,255)", // 0-255之间的整数表示的RGB,蓝色 "rgb(100%, 25%, 100%)", // 百分比表示的RGB,紫色 "rgba(100%, 25%, 100%, 0.5)", // RGB加上0-1的alpha,半透明紫色 "rgba(0,0,0,0)", // 全透明黑色 "transparent", // 全透明 "hsl(60,100%, 50%)", // 全饱和黄色 "hsl(60, 75%, 50%)", // 低包包黄色 "hsl(60, 100%, 75%)", // 全饱和暗黄色 "hsl(60, 100%, 25%)", // 全饱和亮黄色 "hsla(60, 100%, 50%, 0.5)" // 全饱和黄色,50%透明 ];

一旦设置了strokeStyle或fillStyle的值,那么这个新值就会成为新绘制的图形的默认值,如果要给后续绘制的图形使用不同的颜色,需要重新设置fillStyle或strokeStyle的值;

for (var i=0;i<6;i ){ for (var j=0;j<6;j ){ // 255/6=42.5 context.fillStyle = 'rgb(' Math.floor(255-42.5*i) ',' Math.floor(255-42.5*j) ',0)'; context.fillRect(j*25,i*25,25,25); } }

如:

for (var i=0;i<6;i ){ for (var j=0;j<6;j ){ context.strokeStyle = 'rgb(0,' Math.floor(255-42.5*i) ',' Math.floor(255-42.5*j) ')'; context.beginPath(); context.arc(12.5 j*25,12.5 i*25,10,0,Math.PI*2,true); context.stroke(); } }

如:

context.fillStyle = 'rgb(255,221,0)'; context.fillRect(0,0,150,37.5); context.fillStyle = 'rgb(102,204,0)'; context.fillRect(0,37.5,150,37.5); context.fillStyle = 'rgb(0,153,255)'; context.fillRect(0,75,150,37.5); context.fillStyle = 'rgb(255,51,0)'; context.fillRect(0,112.5,150,37.5); // 画半透明矩形 for (var i=0; i<10; i ){ context.fillStyle = 'rgba(255,255,255,' (i 1)/10 ')'; for (var j=0; j<4; j ){ context.fillRect(5 i*14, 5 j*37.5, 14, 27.5) } }

globalAlpha属性:全局透明度,其值是介于0到1之间的值,用于指定所有绘制的透明度,默认值为1;如:

context.fillStyle = "#ff0000"; context.fillRect(50,50,100,100); // 修改全局透明度 context.globalAlpha= 0.5; context.fillStyle = "rgba(0,0,255,1)"; context.fillRect(50,100,100,100);

如果将其设为0,所有绘制的图形都会变成全透明;绘制的每个像素都会将其alpha值乘以设置的globalAlpha值,例如:如果设置为0.5,那么所有绘制的原本不透明的像素都会变成50%的透明度,而原本是50%透明的,就变成25%的不透明度;globalAlpha 属性在需要绘制大量拥有相同透明度的图形时候相当高效;如:

// 画背景 context.fillStyle = '#FD0'; context.fillRect(0,0,75,75); context.fillStyle = '#6C0'; context.fillRect(75,0,75,75); context.fillStyle = '#09F'; context.fillRect(0,75,75,75); context.fillStyle = '#F30'; context.fillRect(75,75,75,75); context.fillStyle = '#FFF'; context.globalAlpha = 0.2; // 画半透明圆 for (var i=0;i<7;i ){ context.beginPath(); context.arc(75, 75, 10 10*i, 0, Math.PI*2, true); context.fill(); }

线型Line styles:lineWidth属性:指定线条的宽度,值可以是任意整数;实际上可以是小于1的小数

context.lineWidth = 0.5; context.strokeRect(100,50, 300, 200);

线可以通过stroke()、strokeRect()和strokeText()方法绘制;

lineWidth属性没有相像中的那么简单:我们可以将路径视为一个无限细的线条,当调用stroke()方法进行轮廓描边时,它们是处于路径的中间,两边都是lineWidth宽度的一半;

for (var i = 0; i < 10; i ){ context.lineWidth = 1 i; context.beginPath(); context.moveTo(5 i*14, 5); context.lineTo(5 i*14, 140); context.stroke(); }

示意图:

canvas画图教程(第75节Canvas绘图上)(9)

如果勾勒一条闭合的路径并只希望线段出现在路径之外,那么首先勾勒该路径,然后用不透明颜色填充闭合区域,将出现在路径内的勾勒部分隐藏;又或者如果只希望出在闭合路径内,可以调用clip()方法;

线段宽度是受当前坐标系变换影响的,如果通过坐标系变换来对坐标轴进行缩放,例如调用scale(2,1)方法就会对X轴进行缩放,但是对Y轴不产生影响,如此,垂直的线段要比原先和它一样宽的水平线段宽一倍;

lineCap属性:为直线添加线帽,值:butt默认值,不为直线添加线帽,即在线段端直接结束;round圆形线帽,即在原端点的基础上延长一个半圆;square正方形线帽,即在原端点的基础上,再延长线段宽度一半的长度;如图:

canvas画图教程(第75节Canvas绘图上)(10)

context.beginPath(); context.lineWidth = 10; context.lineCap = "round"; context.moveTo(20,20); context.lineTo(20,200); context.stroke(); var lineCap = ['butt','round','square']; // 绘制上下两条水平线条 context.strokeStyle = '#09f'; context.beginPath(); context.moveTo(60,10); context.lineTo(200,10); context.moveTo(60,140); context.lineTo(200,140); context.stroke(); // 绘制垂直三条不同端点的线条 context.strokeStyle = 'black'; for (var i = 0; i < lineCap.length; i ) { context.lineWidth = 15; context.lineCap = lineCap[i]; context.beginPath(); context.moveTo(80 i*50,10); context.lineTo(80 i*50,140); context.stroke(); }

lineJoin属性:指定两条线段交汇时的拐角形状,即在顶点处如何连接,值:miter默认值,尖角,表示一直延伸两条线段的外侧边缘直到在某一点汇合;round圆角,表示将汇合的顶点变成圆形;bevel斜角,表示把汇合的顶点切除;

canvas画图教程(第75节Canvas绘图上)(11)

context.beginPath(); context.lineWidth = 10; context.lineJoin = "bevel"; context.moveTo(20,150); context.lineTo(80,50); context.lineTo(160,150); context.stroke(); // 分别使用3种不同的lineJoin var lineJoin = ['round','bevel','miter']; context.lineWidth = 10; for (var i = 0; i < lineJoin.length; i ) { context.lineJoin = lineJoin[i]; context.beginPath(); context.moveTo(200, 50 i*40); context.lineTo(250, 95 i*40); context.lineTo(300, 50 i*40); context.lineTo(350, 95 i*40); context.lineTo(400, 50 i*40); context.stroke(); }

miterLimit属性:当只有lineJoin属性值是miter时才会起作用;当两条线段相交的夹角为锐角的时候,两条线段的斜接部分可以变得很长,该属性可以指定斜接部分长度的上限,默认值是10.0;

canvas画图教程(第75节Canvas绘图上)(12)

context.lineWidth = 20; console.log(context.miterLimit); // 10 context.miterLimit = 7; context.moveTo(50,50); context.lineTo(150,50); // context.lineTo(50,150); // context.lineTo(50,120); context.lineTo(50,80); // 夹角小,斜接长度更长 context.stroke();

斜接最大长度是当前线宽与miterLimit属性值的乘积的一半,即斜接限定值(miterLimit)是斜接长度与一半线宽的比值;如果指定的miterLimit值比这个比值还小的话,最终绘制出来的顶点就会是斜切的(bevel)而不是斜接的;当给属性赋值时,0、负数、 Infinity 和 NaN 都会被忽略;

setLineDash(segments)方法:自定义虚线形状;参数segments为一个数组,描述了线段和线段间隙的交替长度;

context.beginPath(); context.setLineDash([5, 15]); context.moveTo(0, 50); context.lineTo(300, 50); context.stroke();

如果数组只存在一个值,则表示线段与间隙长度都等于这个值;如果数组中的元素超过2个,则数组中的数值数量为偶数,即第奇个数代表线段长度,第偶数个数表示间隙长度;如果数组中的元素超过2个,且不为偶数,会自动复制一份该值,使其成为偶数量;如果要切换回至实线模式,将 dash list 设置为一个空数组即可;

context.setLineDash([5]); context.setLineDash([5,10,15,20]); // context.setLineDash([5,10,15])变成context.setLineDash([5,10,15,5,10,15]); context.setLineDash([5,10,15]); context.setLineDash([]);

常见的虚线模式,如:

var y = 15; drawDashedLine([]); drawDashedLine([1, 1]); // 或[1] drawDashedLine([10, 10]); drawDashedLine([20, 5]); drawDashedLine([15, 3, 3, 3]); drawDashedLine([20, 3, 3, 3, 3, 3, 3, 3]); drawDashedLine([12, 3, 3]); // Equals [12, 3, 3, 12, 3, 3] function drawDashedLine(pattern) { context.beginPath(); context.setLineDash(pattern); context.moveTo(0, y); context.lineTo(300, y); context.stroke(); y = 20; }

getLineDash方法:返回一个包含当前虚线样式、长度为非负偶数的数组,如果数组元素的数量是奇数,数组元素会被复制并重复,如:

console.log(context.getLineDash()); // [12, 3, 3, 12, 3, 3]

lineDashOffset属性:设置虚线偏移量,值是浮点数,初始值为0.0,可以为负;正值向左偏,负值向右偏,如:

context.lineDashOffset = 5;

示例:蚂蚁线效果:

function draw(id){ var canvas = document.getElementById(id); if(canvas==null){ return false; } var context = canvas.getContext("2d"); context.fillStyle = "#EEEEFF"; context.fillRect(0,0,400,300); march(context); // [mɑːtʃ] } var i=0; function march(context){ i ; if(i>16){ i=0; } console.log(i); context.clearRect(5,5,110,110); context.setLineDash([4,2]); context.lineDashOffset = -i; context.strokeRect(10,10,100,100); setTimeout(function(){ march(context); },50); } draw("canvas");

绘制渐变:绘制线性渐变:fillStyle方法除了填充指定颜色外,还可以用来指定填充的对象,比如渐变对象;createLinearGradient(x1, y1, x2, y2)方法:返回一个CanvasGradient渐变对象,参数x1与y1为渐变起始点坐标,x2和y2为结束点坐标;如:

var gradient = context.createLinearGradient(30, 30, 70, 70); console.log(gradient); // CanvasGradient

使用该对象的addColorStop(pos, color)方法指定色标,参数pos指定色标位置,即一个偏移量,值为0到1之间的浮动点,如0.5 表示颜色会出现在正中间,第二个参数color指定颜色;该方法必须调用二次以上;

gradient.addColorStop(0, "white"); gradient.addColorStop(1, "black");

创建了渐变对象并指定了颜色后,再把该对象赋值给fillStyle或strokeStyle属性,从而使用渐变来进行填充或描边;

context.fillStyle = gradient; context.strokeStyle = gradient; context.fillRect(0, 0, 50, 50); context.lineWidth = 20; context.strokeRect(30, 30, 50, 50);

渐变的坐标位置是相对于画布的,所以绘制的图形和渐变对象的坐标必须匹配;否则,有可能只会显示部分渐变效果;因此,确保渐变与图形对齐非常重要,如:

function createRectLinearGradient(context, x, y, width, height){ return context.createLinearGradient(x, y, x width, y height); } // ... 使用 var gradient = createRectLinearGradient(context, 30, 30, 50, 50); console.log(gradient); // CanvasGradient

如使用两个渐变对象分别进行填充和描边:

// Create gradients var lingrad = context.createLinearGradient(0,0,0,150); lingrad.addColorStop(0, '#00ABEB'); lingrad.addColorStop(0.5, '#fff'); lingrad.addColorStop(0.5, '#26C000'); lingrad.addColorStop(1, '#fff'); var lingrad2 = context.createLinearGradient(0,50,0,95); lingrad2.addColorStop(0.5, '#000'); lingrad2.addColorStop(1, 'rgba(0,0,0,0)'); context.fillStyle = lingrad; context.strokeStyle = lingrad2; context.fillRect(10,10,130,130); context.strokeRect(50,50,50,50);

如绘制若干个渐变圆和矩形:

var g = context.createLinearGradient(0,0,300,0); g.addColorStop(0,"rgba(0,0,255,0.5)"); g.addColorStop(1,"rgba(255,0,0,0.5)"); context.fillStyle = g; for(var i=0; i<10; i ){ // 渐变圆 // context.beginPath(); // context.arc(i*25,i*25,i*10,0,Math.PI*2,true); // context.closePath(); // context.fill(); // 渐变矩形 context.fillRect(i*25, i*25, i*10, i*10); }

绘制径向渐变:同理,可以使用createRadialGradient(x1, y1, radius1, x2, y2, radius2)方法来绘制径向渐变; x1和y1为开始圆的圆心坐标,radius1为开始圆的半径,x2和y2为结果圆的圆心坐标,radius2为结束圆的半径;其他同线性渐变相同,也是使用addColorStop()方法添加色标;

var g = context.createRadialGradient(300,300,100,300,300,300); g.addColorStop(0.0, "transparent"); g.addColorStop(0.7, "rgba(100,100,100,.9)"); g.addColorStop(1.0, "rgba(0,0,0,0)"); context.fillStyle = g; context.fillRect(0,0,drawing.width, drawing.height);

如,绘制渐变圆:

var g1 = context.createRadialGradient(400,0,0,400,0,400); g1.addColorStop(0.1,'rgb(255,255,0)'); g1.addColorStop(0.3,'rgb(255,0,255)'); g1.addColorStop(1,'rgb(0,255,255)'); context.fillStyle = g1; context.fillRect(0,0,400,300); var g2 = context.createLinearGradient(250,250,0,250,250,300); g2.addColorStop(0.1,"rgba(255,0,0,0.5)"); g2.addColorStop(0.7,"rgba(255,255,0,0.5)"); g2.addColorStop(1,"rgba(0,0,255,0.5)"); context.fillStyle = g2; for(var i=0;i<10;i ){ context.beginPath(); context.arc(i*25,i*25,i*10,0,Math.PI*2,true); context.closePath(); context.fill(); }

如:

// 创建渐变 var radgrad = context.createRadialGradient(40,40,0,52,50,30); radgrad.addColorStop(0, '#A7D30C'); radgrad.addColorStop(0.9, '#019F62'); radgrad.addColorStop(1, 'rgba(1,159,98,0)'); var radgrad2 = context.createRadialGradient(105,105,20,112,120,50); radgrad2.addColorStop(0, '#FF5F98'); radgrad2.addColorStop(0.75, '#FF0188'); radgrad2.addColorStop(1, 'rgba(255,1,136,0)'); var radgrad3 = context.createRadialGradient(95,15,10,102,20,40); radgrad3.addColorStop(0, '#00C9FF'); radgrad3.addColorStop(0.8, '#00B5E2'); radgrad3.addColorStop(1, 'rgba(0,201,255,0)'); var radgrad4 = context.createRadialGradient(0,150,50,0,140,90); radgrad4.addColorStop(0, '#F4F201'); radgrad4.addColorStop(0.8, '#E4C700'); radgrad4.addColorStop(1, 'rgba(228,199,0,0)'); // 画图形 context.fillStyle = radgrad4; context.fillRect(0,0,150,150); context.fillStyle = radgrad3; context.fillRect(0,0,150,150); context.fillStyle = radgrad2; context.fillRect(0,0,150,150); context.fillStyle = radgrad; context.fillRect(0,0,150,150);

模式(图案)Pattern:模式就是重复的图像,也称为填充图案,可以用来填充或描边图形;使用createPattern(image,type)方法即可创建新模式,参数image为一个<img>元素或image对象,参数type指定重复的类型,其值与CSS的background-repeat值相同:no-repeat、repeat-x、repeat-y、repeat;返回的对象是CanvasPattern类型的模式对象;创建完模式对象后,再赋给fillStyle即可,如:

var image = document.getElementsByTagName("img")[0]; var pattern = context.createPattern(image, "repeat"); console.log(pattern); // CanvasPattern context.fillStyle = pattern; context.fillRect(50, 50, 100, 100);

模式与渐变一样,都是从画布的原点(0,0)开始的;将模式对象赋给fillStyle属性,只表示在某个特定的区域内显示重复的图像,而不是要从某个位置开始绘制重复的图像;

还可以采用一个<canvas>元素作为另外一个<canvas>元素的图案,如:

var offscreen = document.createElement("canvas"); // 创建一个屏幕外canvas offscreen.width = offscreen.height = 10; // 设置大小 offscreen.getContext("2d").strokeRect(0,0,6,6); // 绘制 var pattern = context.createPattern(offscreen, "repeat"); context.fillStyle = pattern; context.fillRect(0,0, drawing.width,drawing.height);

CanvasPattern类型的模式对象没有任何属性,只有一个setTransform()方法,其用于对图案进行变形;createPattern()方法的第一个参数,也可以是一个video元素,或者另一个canvas元素

绘制阴影:2D上下文可以给图形绘制阴影效果,其使用context对象的关于阴影属性进行设置:shadowOffsetX、shadowOffsetY:横向或纵向位移,负值表示阴影会往上或左位移,正值则表示会往下或右位移,默认值为0;shadowColor:阴影颜色,默认是完全透明的,如果不指定透明度或颜色,该阴影是不可见的;shadowBlur:可选属性,表示图形阴影边缘的模糊范围,值设定在0到10之间;

context.shadowOffsetX = 5; context.shadowOffsetY = 5; context.shadowColor = "rgba(0, 0, 0, 0.5)"; context.shadowBlur = 4; context.fillStyle = '#FF0000'; context.fillRect(10,10, 50, 50); context.fillStyle = 'rgba(0, 0, 255, 0.5)'; context.fillRect(10, 100, 50, 50); context.shadowOffsetX = 10; context.shadowOffsetY = 10; context.shadowColor = "rgba(100,100,100,0.5)"; context.shadowBlur = 7.5; context.translate(0,50); for(var i=0;i<3;i ){ context.translate(50,50); createStar(context); context.fill(); } function createStar(context){ var dx = 100; var dy = 0; var s = 50; context.beginPath(); context.fillStyle = "rgba(255,0,0,0.5)"; var dig = Math.PI / 5 * 4; for(var i=0;i<5;i ){ var x = Math.sin(i*dig); var y = Math.cos(i*dig); context.lineTo(dx x*s,dy y*s); } context.closePath(); }

shadowColor不允许使用图案和渐变色;shadowOffsetX和shdowOffsetY属性总是在默认的坐标空间中度量的,它不受rotate()和scale()方法的影响;

保存与恢复状态:使用save()和restore()两个方法,可以分别用于保存和恢复图形上下文的当前绘制状态;这里的绘画状态指的是坐标原点、变换矩阵,以及图形上下文对象的当前属性值等很多内容;canvas状态存储在栈中,当调用save()方法时会将当前状态设置保存到栈中,当多次调用save()时,会在栈中保存多个状态,之后,在做完想做的工作后,再调用restore()从栈中取出之前保存的状态进行恢复,即在栈结构中向前返回一级,连续调用restore()则可以逐级返回;如:

context.fillStyle = "#FF0000"; context.save(); context.fillStyle = "#00FF00"; context.translate(100, 100); context.save(); context.fillStyle = "#0000FF"; // 从点(100,100)开始绘制蓝色矩形 context.fillRect(0,0, 100, 100); context.restore(); // 从点(110,110)开始绘制绿色矩形 context.fillRect(10,10,100,200); context.restore(); // 从点(0,0,)开始绘制红色矩形 context.fillRect(0,0,100,200); save()保存的只是对绘图上下文的设置和变换,不会保存绘图的内容;具体可以应用在以下场合:图像或图形变形(包括即移动,旋转和缩放)、图像裁剪、改变图形上下文的属性(strokeStyle、fillStyle、globalAlpha、lineWidth、lineCap、lineJoin、miterLimit、lineDashOfffset、shadowBlur、shadowColor、shadowOffsetX、shadowOffsetY、globalCompositeOperation、font、textAlign、textBaseline、direction、imageSmothingEnabled);

示例:

context.fillRect(0,0,150,150); // 使用默认设置绘制一个矩形 context.save(); // 保存默认状态 context.fillStyle = '#09F' // 在原有配置基础上对颜色做改变 context.fillRect(15,15,120,120); // 使用新的设置绘制一个矩形 context.save(); // 保存当前状态 context.fillStyle = '#FFF' // 再次改变颜色配置 context.globalAlpha = 0.5; context.fillRect(30,30,90,90); // 使用新的配置绘制一个矩形 context.restore(); // 重新加载之前的颜色状态 context.fillRect(45,45,60,60); // 使用上一次的配置绘制一个矩形 context.restore(); // 加载默认颜色配置 context.fillRect(60,60,30,30); // 使用加载的配置绘制一个矩形

绘制变换图形:在绘制图形的时候,有可能需要旋转、缩放图形等变形处理;

变换方法:translate(dx, dy):移动坐标原点到(dx, dy)处;参数dx和dy为X轴和Y轴的偏移量;

canvas画图教程(第75节Canvas绘图上)(13)

context.translate(100,100) context.strokeRect(0,0,200,100); context.beginPath(); context.arc(100,100,100,0,Math.PI*2); context.stroke();

示例:

for (var i = 0; i < 3; i ) { for (var j = 0; j < 3; j ) { context.save(); context.fillStyle = 'rgb(' (51 * i) ', ' (255 - 51 * i) ', 255)'; context.translate(10 j * 50, 10 i * 50); context.fillRect(0, 0, 25, 25); // 只关注宽和高,不再关注起始坐标 context.restore(); } }

在一般的使用中,在使用变换时,会与状态的保存与恢复配合使用,便于下一次的绘制;

rotate(angle):以坐标原点为中心点旋转,angle为弧度,为正以顺时针旋转,为负则以逆时针旋转;

canvas画图教程(第75节Canvas绘图上)(14)

context.rotate(Math.PI / 4); context.strokeRect(200,50,200,100); context.beginPath(); context.moveTo(200, 20); context.lineTo(300, 20); context.stroke();

在旋转时,是以原点为中心点旋转的,必要时,需要移动原点,否则有可能会旋转到画布的外侧;

context.translate(100,100); context.rotate(Math.PI / 4); context.fillRect(0,0,200,100);

示例:绘制若干小不同颜色的小圆

context.translate(75,75); for (var i=1; i<6; i ){ context.save(); context.fillStyle = 'rgb(' (51*i) ',' (255-51*i) ',255)'; for (var j=0; j<i*6; j ){ context.rotate(Math.PI*2/(i*6)); context.beginPath(); context.arc(0, i*12.5, 5, 0, Math.PI*2, true); context.fill(); } context.restore(); }

scale(scaleX, scaleY):放大缩小,两个参数都是实数,值为0到1,默认值为1;可以为负数,x 为水平缩放因子,y 为垂直缩放因子,如果比1小,会缩小图形,如果比1大会放大图形;

context.scale(2, 1); context.lineWidth = 20; context.strokeRect(50,50,100,50);

默认情况下,canvas的1个单位为1个像素,如果设置的缩放因子是0.5,那么1个单位就变成对应0.5个像素,这样绘制出来的形状就会是原先的一半;同理,设置为2.0时,1个单位就对应变成了2像素,绘制的结果就是图形放大了2倍;如果线宽为1像素,且缩放因子小于1,此时,还是1像素呈现

context.save(); context.scale(0.5, 1); // 线宽为1的,因子小于1的还是1像素呈现 // context.lineWidth = 2; context.strokeRect(50, 50, 100, 50); context.restore(); context.save(); context.scale(10, 3); context.fillRect(1, 100, 10, 10); context.restore();

画布初始情况下,是以左上角坐标为原点的第一象限,如果参数为负实数,相当于以 x 或 y 轴作为对称轴镜像反转;例如:

context.translate(0, canvas.height); context.scale(1,-1); context.save(); context.lineWidth = 20; context.moveTo(180,10); context.lineTo(330,150); context.lineTo(30,150); context.closePath(); context.stroke(); // 变成倒三角了 context.restore(); context.translate(canvas.width, 0); context.scale(-1, 1); context.font = '48px serif'; context.fillText('ZERO', 10, 200);

变换有可能很简单,有可能很复杂,这都要视情况而定,如绘制螺旋的长方形:

context.fillStyle = "#EEEEFF"; context.fillRect(0,0,400,300); context.translate(200,50); // context.strokeRect(0, 0, 4, 4); // 现原点 context.fillStyle = 'rgba(255,0,0,0.25)'; for(var i=0; i<50; i ){ // 把50转成3,观察绘制过程 context.translate(25,25); // context.strokeRect(0, 0, 4, 4); // 现原点 context.scale(0.95,0.95); context.rotate(Math.PI / 10); context.fillRect(0,0,100,50); }

示例:绘制表盘和表针

// ...把前的代码换成以下的 context.translate(100,100); context.rotate(1); // 分针 context.moveTo(0, 0); context.lineTo(0, -85); // 时针 context.moveTo(0,0); context.lineTo(-65,0); // ...

示例:科赫雪花分形:

var deg = Math.PI / 180; function snowflake(c, n, x, y, len){ c.save(); c.translate(x, y); c.moveTo(0, 0); draw(n); c.rotate(-120 * deg); draw(n); c.rotate(-120 * deg); draw(n); c.closePath(); c.restore(); function draw(n){ c.save(); if(n == 0){ c.lineTo(len, 0); }else{ c.scale(1/3, 1/3); draw(n - 1); c.rotate(60 * deg); draw(n - 1); c.rotate(-120 * deg); draw(n - 1); c.rotate(60 * deg); draw(n - 1); } c.restore(); c.translate(len, 0); } } snowflake(context, 0, 5, 115, 125); snowflake(context, 1, 145, 115, 125); snowflake(context, 2, 285, 115, 125); snowflake(context, 3, 425, 115, 125); snowflake(context, 4, 565, 115, 125); context.stroke();

矩阵变换:当利用坐标变换不能满足我们的需要时,可以利用矩阵变换的技术;矩阵变换是专门用来实现图形变形的,它与坐标一起配合使用,以达到变形的目的;当context创建完毕后,进行一系列的属性设置和绘图操作,都是使用默认的坐标系;除了这个默认的坐标系,还有一个默认的“当前变换矩阵“,其作为当前图形状态的一部分,定义了画布的当前坐标系;如果不对这个变换矩阵进行修改,那么接下来绘制的图形将以画布的最左上角为坐标原点绘制图形,绘制出来的图形也不经过缩放、变形处理,会被直接绘制,但是如果对这个变换矩阵进行修改,会导致使用不同的变换矩阵应用处理,从而产生不同的效果;当前变换矩阵是用来将指定的坐标转换成为默认坐标系中的等价坐标。

transform(a, b, c, d, e, f),变换矩阵;其使用一个新的变换矩阵与当前变换矩阵进行乘法运算,该变换矩阵的描述是:a(m11) c(m21) e(dx)b(m12) d(m22) f(dy)0 0 1其中,a(m11)、b(m12)、c(m21)、d(m22)这4个参数用来决定如何变形,分别表示水平缩放、垂直倾斜、水平倾斜、垂直缩放;e(dx)和f(dy)参数表示水平和垂直移动坐标原点;

context.save(); context.fillStyle = "red"; context.fillRect(0,0,100,100); context.fillRect(100,120,100,100); context.restore(); context.translate(100,120); context.transform(2, Math.PI / 4, -Math.PI / 4, 0.5, 50, 50); context.fillRect(0,0,100,100);

示例:通过变换绘制螺旋

context.translate(200,50); for(var i=0; i<50; i ){ context.save(); context.transform(0.95,0,0,0.95,30,30); context.rotate(Math.PI/12); context.beginPath(); context.fillStyle = 'rgba(255,0,0,0.5)'; context.arc(0,0,50,0,Math.PI*2,true); context.closePath(); context.fill(); }

示例:多条彩色弧

var colors = ["red","orange","yellow","green","blue","navy","purple"]; context.lineWidth = 10; context.transform(1,0,0,1,100,0); for(var i=0;i<colors.length;i ){ context.transform(1,0,0,1,0,10); context.strokeStyle = colors[i]; context.beginPath(); context.arc(50,100,100,0,Math.PI,true); context.stroke(); }

setTransform(a, b, c, d, e, f)方法:这个方法会将当前的变形矩阵重置为默认矩阵,然后用相同的参数调用transform()方法;从根本上来说,该方法是取消了当前变形,然后再设置为指定的变形,如:

// context.transform(1,1,0,1,0,0); context.setTransform(1,1,0,1,0,0); // 起到相同的效果 context.fillRect(0,0,100,100);

它经常被用在,临时将画布重置为默认坐标系,如:

context.strokeStyle = "red"; context.strokeRect(30,10,60,20); // 旋转45度 var rad = 45 * Math.PI / 180; context.setTransform(Math.cos(rad),Math.sin(rad),-Math.sin(rad),Math.cos(rad),0,0); context.strokeStyle = "blue"; context.strokeRect(30,10,60,20); // 放大2.5倍 context.setTransform(2.5,0,0,2.5,0,0); context.strokeStyle = "green"; context.strokeRect(30,10,60,20); // 移动坐标原点 context.setTransform(1,0,0,1,40,80); context.strokeStyle = "gray"; context.strokeRect(30,10,60,20); context.save(); // 保存当前默认坐标系 context.setTransform(1,0,0,1,0,0); // 恢复到默认坐标系 // 使用默认的坐标进行绘图操作 context.restore(); // 恢复保存的坐标系 setTransform()与transform()区别: // context.transform(2,0,0,1,0,0); context.setTransform(2,0,0,1,0,0); context.fillRect(0,0,100,100); // context.transform(2,0,0,2,0,0); context.setTransform(2,0,0,2,0,0); context.fillStyle = "red"; context.fillRect(50,50,100,100);

再如:

var sin = Math.sin(Math.PI/6); var cos = Math.cos(Math.PI/6); context.translate(100, 100); var c = 0; for (var i=0; i <= 12; i ) { c = Math.floor(255 / 12 * i); context.fillStyle = "rgb(" c "," c "," c ")"; context.fillRect(0, 0, 100, 10); context.transform(cos, sin, -sin, cos, 0, 0); } context.setTransform(-1, 0, 0, 1, 100, 100); context.fillStyle = "rgba(255, 128, 255, 0.5)"; context.fillRect(0, 50, 100, 100);

resetTransform():方法重置当前变形矩阵,它和调用以下语句是一样的:context.setTransform(1, 0, 0, 1, 0, 0);

context.rotate(45 * Math.PI / 180); context.fillRect(70,0,100,30); context.resetTransform();

深入讨论:translate、scale、rotate三个方法,实际上都是隐式的修改了变换矩阵;translate()方法只是简单的将坐标原点进行上下左右移动;而rotate()方法会将坐标轴根据指定的角度进行顺时针旋转;scale()方法实现对X轴或Y轴上的距离进行延长和缩短;假定变换后坐标系中的点的坐标是(x, y),其对应的默认坐标系的坐标为(x‘, y‘);

canvas画图教程(第75节Canvas绘图上)(15)

调用translate(dx, dy)方法等效于:

x’ = x dx; y’ = y dy;

调用scale(sx, sy)方法等效于:x’ = sx * x;y’ = sy * y;

调用rotate(a)方法,可以通过三角法则计算,如:x’ = x * cos(a) – y * sin(a);y’ = y * cos(a) x * sin(a);

坐标系变换是与变换顺序相关的;假设从画布默认的坐标系开始,先进行位移,再进行伸缩;如此操作后,要想将现有坐标系中的点(x, y)映射成默认坐标系中的点(x’’, y’’),必须首先应用等效缩放等式把该点映射到未缩放坐标系中的一个中间点(x’, y’),然后再使用等效的位移将中间点再映射到原来坐标系中的点(x’’, y’’),结果是:x’’ = sx * x dx;y’’ = sy * y dy;

如果先调用scale()再调用translate()的话,那等效的结果就不同了,结果是:x’’ = sx * (x dx);y’’ = sy * (y dy);

translate、scale、rotate三个方法都可以使用transform方法进行处理:translate(x, y)可以使用transform(1, 0, 0, 1, x, y)代替,参数1,0,0,1表示不对图形进行缩放倾斜操作,只是位移x和y;scale(x, y)可以使用transform(x, 0, 0, y, 0, 0)代替,参数x,0,0,y表示将图形横向扩大x倍,纵向扩大y倍;rotate(angle)替换方法:transform(Math.cos(angle * Math.PI / 180), Math.sin(angle * Math.PI / 180),-Math.sin(angle * Math.PI / 180), Math.cos(angle * Math.PI / 180),0,0)

其中前4个参数以三角函数的形式结合起来,共同完成图形按angle角度的顺时针旋转处理;

canvas画图教程(第75节Canvas绘图上)(16)

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页