canvas入门

  • canvas简介
  • 画线
  • 画矩形
  • 画圆
  • canvas与贝塞尔曲线
  • 坐标平移旋转与缩放
  • canvas的save与restore
  • 背景填充
  • 线性渐变
  • 辐射渐变(径向渐变)
  • 阴影
  • 渲染文字
  • 线端样式

 一、canvas简介

最早在apple的safari 1.3中引入,ie9之前的浏览器不支持canvas,相关兼容性问题可以了解http://caniuse.com

  • 画布标签可以通过width和height属性设置宽高,也可以通过样式style的width和height样式属性设置宽高。如果在元素属性和样式属性中同时设置了宽高优先取样式宽高设置画布大小,但是真正作用画布的正确值应该使用元素属性的宽高
 1 <style>
 2     canvas{
 3         width: 300px;
 4         height: 300px;
 5         border: 1px solid #000;
 6     }
 7 </style>
 8 //html
 9 <canvas id="cans" width="300" height="300"></canvas>
10 <script>
11     var oCanvas = document.getElementById('cans');//获取画布DOM
12     var ctx = oCanvas.getContext('2d');//获取画布的上下文对象 -- 画笔
13 </script>

 二、canvas画线

  • ctx.moveTo(x,y) :设置画笔起点
  • ctx.lineTo(x,y):当前绘制直线到(x,y)点;可以设置多个绘制节点,每个节点以直线绘制;
  • ctx.stroke() :绘制,以当前设定绘制路径绘制。
  • ctx.lineWidth = npx; 设置线段宽度(设置的是当前整个绘画路径的宽度)
  • ctx.closePath(): 闭合路径(从最后的绘制节点直接绘制到画笔起点)
  • ctx.fill() :填充,基于路径闭合的图形填充,由lineTo(x,y)节点闭合的路径也可以实现闭合。测试非闭合折线会根据最后的一个节点与起点闭合填充,也是说是自动闭合。
1 //绘制折线
2 ctx.moveTo(100,100);
3 ctx.lineTo(100,200);
4 ctx.lineTo(200,200);
5 ctx.stroke();

注意如果需要绘制的折线每个线段的宽度不一致需要多段绘制,每绘制一段路劲需要使用ctx.beginPath()从起一段绘制路劲,并且需要重新设置画笔起点。

  • ctx.beginPath():重新开启一段绘制路径
  • ctx.strokeStyle = color:设置画笔颜色,还具备渐变和笔触效果,后面具体说明。

 注:这种分段绘制的折线不能使用闭合和填充

 1 ctx.lineWidth = 2; //设置第一个绘制路径的宽度
 2 ctx.strokeStyle = "#ff0";
 3 ctx.moveTo(100,100);
 4 ctx.lineTo(150,200);
 5 ctx.stroke();
 6 ctx.beginPath(); //重新开启一个路径
 7 ctx.lineWidth = 1; //设置第二个绘制路径的宽度
 8 ctx.strokeStyle = "#f0f";
 9 ctx.moveTo(150,200);
10 ctx.lineTo(200,100);
11 ctx.stroke();

 注:但是要注意在一个画布上花朵个图形,并非就一定需要使用beginPath()重新启动一个路径,使用moveTo重新设置一个其实点就可以实现,只是描述图形的线宽,颜色等特性会使用最后出现的值。所以也可以说明beginPath()可以完全重启一个绘制对象,可以具备独立的线宽、颜色等特性。示例:

 1 ctx.lineWidth = 20; //由于后面一个图形没有采用beginPath另起路径,会继续使用这个线宽
 2 ctx.moveTo(100,50);
 3 ctx.lineTo(100,100);
 4 ctx.lineTo(50,100);
 5 ctx.strokeStyle='red';
 6 ctx.closePath();
 7 ctx.stroke();
 8 ctx.moveTo(200,200);
 9 ctx.lineTo(200,400);
10 ctx.strokeStyle = '#000'; //最后上面绘制的三角形会统一成黑色
11 ctx.stroke();
两个没有独立绘制的图形

 三、canvas绘制矩形

  • ctx.rect(x,y,dx,dy) :绘制无填充的矩形,(x,y)用来定义绘制矩形的起始坐标(左上角的坐标),(dx,dy)用来定义矩形的宽高。 使用rect()需要调用stroke()绘制。
  • ctx.fillRect(x,y,dx,dy):绘制填充的矩形,参数与rect()一致,不需要调用stroke()可自动绘制。
  • ctx.strokeRect(x,y,w,h):绘制无填充的矩形,参数与rect()一致,不需要调用stroke()可自动绘制。
ctx.rect(50,50,200,100);
ctx.stroke();
  • ctx.clearRect(x,y,dx,dy):橡皮擦(清除画布内容),(x,y)用来定义橡皮擦清除内容的起始位置,(dx,dy)用来定义橡皮擦的大小(宽,高)。
  • 基于clearRect()实现矩形下落的动画:
1 var height = 50;
2 var timer = setInterval(function(){
3     ctx.clearRect(0,0,300,300);
4     ctx.fillRect(100,height,100,50);
5     height += 10;
6     if(height > 250){
7         clearInterval(timer);
8     }
9 },100)

 四、canvas绘制弧形、圆、圆角矩形

  • ctx.arc(x,y,r,起始弧度,结束弧度,弧形方向):绘制弧形
  • 绘制确定的弧线或者圆的必备数据:
  • ----圆心(x,y)
  • ----半径(r)
  • ----弧度(起始弧度,结束弧度):弧度使用Maht.PI来计算,一个Math.PI表示180度(弧度的起始位置是X轴的正方向)
  • ----绘制方向:0表示顺时针,1表示逆时针
1 // 画圆
2 ctx.arc(150,150,50,0,Math.PI * 2,0);//顺时针绘制路径
3 ctx.arc(150,150,50,0,Math.PI * 2,1);//逆时针绘制路径
4 ctx.stroke();

绘制扇形:

1 //使用闭合绘制扇形
2 ctx.arc(150,150,50,Math.PI / 6,Math.PI * 11/6,0);
3 ctx.lineTo(150,150);
4 ctx.closePath();
5 ctx.stroke();

 

也可以使用正余弦计算弧线坐标,然后使用lineTo()实现闭合,这个方案主要需要注意Math.sin(x)正弦和Math.cos(x)余弦得参数弧度是从Y轴得正方向开始计算得,也就是基于平面的Y轴来计算的。不同于ctx.arc()中的弧度起始位置是X轴的正方向,所以在计算正余弦时取弧度值要在ctx.arc()的弧度之上多出 Math.PI / 2

1 //基于正余弦绘制扇形
2 ctx.arc(150,150,50,Math.PI / 6,Math.PI * 11/6,0);
3 ctx.lineTo(150,150);
4 // ctx.lineTo(150 + Math.sin(Math.PI / 6 * 4) * 50,150 - Math.cos(Math.PI / 6 * 4) * 50); 通过下面的公式换算而来,如果使用动态参数,建议使用下面的计算方式更合适
5 ctx.lineTo(150 + Math.sin(Math.PI / 6 + Math.PI / 2) * 50,150 - Math.cos(Math.PI / 6 + Math.PI / 2) * 50);
6 ctx.stroke();

绘制圆角矩形

  • ctx.arcTo(x1,y1,x2,y2,r):绘制圆角矩形
  • 参数:
  • (x1,y1)表示绘制路径的实际矩形边线端点;
  • (x2,y2)表示绘制圆弧的方向;
  •  r 表示圆弧在一条边线上所占的长度(使用像素计算);
  • 除了ctx.arcTo()的圆角矩形绘制方法,还必须要使用画笔的起始坐标方法ctx.moveTo(),但要注意如果时闭合的完整圆角矩形需要给最后一个圆弧保留一个r的长度;
1 //绘制圆角矩形
2 ctx.moveTo(100,110);
3 ctx.arcTo(100,200,250,200,10);
4 ctx.arcTo(250,200,250,100,10);
5 ctx.arcTo(250,100,100,100,10);
6 ctx.arcTo(100,100,100,200,10);
7 ctx.stroke();

 五、绘制贝塞尔曲线

 关于贝塞尔曲线的原理我之前在css3动画那篇博客中有详细的描述:https://www.cnblogs.com/ZheOneAndOnly/p/10873090.html

  • 二次贝塞尔曲线:ctx.quadraticCurveTo(x1,y1,x2,y2)
  • 绘制方式:首先使用ctx.moveTo(x,y)设置画笔起点;然后基于(x1,y1)和(x2,y2)形成两个线段的折线,然后基于这两条折线绘制贝塞尔曲线
1 // 绘制贝塞尔曲线
2 ctx.moveTo(100,100);
3 ctx.quadraticCurveTo(200,200,250,100)
4 ctx.stroke();

这里时二次贝塞尔曲线原理图:

 

 1 // 二次绘制贝塞尔曲线原理图
 2 ctx.beginPath()
 3 ctx.moveTo(50,50);
 4 ctx.lineTo(200,200);
 5 ctx.lineTo(280,40);
 6 ctx.stroke();
 7 
 8 ctx.beginPath();
 9 ctx.arc(100,100,5,0,Math.PI * 2);
10 ctx.fill();
11 ctx.stroke();
12 
13 ctx.beginPath();
14 ctx.arc(200,200,5,0,Math.PI * 2);
15 ctx.fill();
16 ctx.stroke();
17 
18 ctx.beginPath();
19 ctx.arc(250,100,5,0,Math.PI * 2);
20 ctx.fill();
21 ctx.stroke();
22 
23 ctx.beginPath();
24 ctx.strokeStyle = '#f0f';
25 ctx.moveTo(100,100);
26 ctx.quadraticCurveTo(200,200,250,100)
27 ctx.stroke();
二次贝塞尔曲线原理图
  • 三次贝塞尔曲线:ctx.bezierCurveTo(x1,y1,x2,y2,x3,y3)
  • 同理二次白塞尔曲线,使用ctx.moveTo()设置起始点,然后基于(x1,y1)(x2,y2)(x3,y3)合成三段折线,然后基于该三段折线绘制三次贝塞尔曲线。
1 //三次贝塞尔曲线
2 ctx.beginPath();
3 ctx.strokeStyle = '#f0f';
4 ctx.moveTo(50,50);
5 ctx.bezierCurveTo(50,100,150,100,200,50)
6 ctx.stroke();

基于贝塞尔曲线实现波浪线动画:

 1 <canvas id="cans" width="500" height="300"></canvas>
 2 //js
 3 var oCanvas = document.getElementById('cans');//获取画布DOM
 4 var ctx = oCanvas.getContext('2d');//获取画布的上下文对象 -- 画笔
 5 // 基于三次贝塞尔曲线实现波浪线动画
 6 var width = 500;
 7 var height = 300;
 8 var offset = 0; //运动偏移量
 9 
10 setInterval(function(){
11     ctx.clearRect(0, 0, 500, 300); //清除上一次绘制的图形
12     ctx.beginPath(); //开启新的绘制路径
13 
14     ctx.moveTo(0 + offset - 500, height / 2);
15     ctx.quadraticCurveTo(width / 4 + offset - 500, height / 2 - 100, width / 2 + offset - 500 , height / 2);
16     ctx.quadraticCurveTo(width / 4 * 3 + offset - 500, height / 2 + 100, width  + offset - 500 , height / 2);
17 
18     ctx.moveTo(0 + offset, height / 2);
19     ctx.quadraticCurveTo(width / 4 + offset , height / 2 - 100, width / 2 + offset , height / 2);
20     ctx.quadraticCurveTo(width / 4 * 3 + offset , height / 2 + 100, width  + offset , height / 2);
21     ctx.stroke();
22     offset += 5;
23     offset %= 500;
24 },1000 / 30);

然后基于上面的基本实现增加震荡函数Math.sin()来实现波浪的峰值变换:

 1 // 基于三次贝塞尔曲线实现波浪线动画
 2 var width = 500;
 3 var height = 300;
 4 var offset = 0; //运动偏移量
 5 var num = 0;
 6 
 7 setInterval(function(){
 8     ctx.clearRect(0, 0, 500, 300); //清除上一次绘制的图形
 9     ctx.beginPath(); //开启新的绘制路径
10     console.log(Math.sin(num) * 120);
11     ctx.moveTo(0 + offset - 500, height / 2);
12     ctx.quadraticCurveTo(width / 4 + offset - 500, height / 2 - Math.sin(num) * 120, width / 2 + offset - 500 , height / 2);
13     ctx.quadraticCurveTo(width / 4 * 3 + offset - 500, height / 2 + Math.sin(num) * 120, width  + offset - 500 , height / 2);
14 
15     ctx.moveTo(0 + offset, height / 2);
16     ctx.quadraticCurveTo(width / 4 + offset , height / 2 - Math.sin(num) * 120, width / 2 + offset , height / 2);
17     ctx.quadraticCurveTo(width / 4 * 3 + offset , height / 2 + Math.sin(num) * 120, width  + offset , height / 2);
18     ctx.stroke();
19     offset += 5;
20     offset %= 500;
21     num += 0.02;
22 },1000 / 30);
基于Math.sin()实现波浪峰值变换

 六、canvas坐标轴平移、旋转、缩放及坐标系状态缓存与引用

  • ctx.rotate(h):基于画布原点旋转画布坐标系,h是旋转弧度,使用Math.PI
  • ctx.translate(x,y):基于画布原点平移画布坐标系,(x,y)表示将坐标原点是沿着当前坐标系移动到(x,y)的位置。
  • ctx.scale(a,b):基于画布当前坐标系横向以a为系数进行缩放,纵向以b为系数进行缩放。
1 // 旋转+坐标平移
2 ctx.beginPath();
3 ctx.rotate(Math.PI / 6); // 基于画布原点旋转
4 ctx.translate(100,100); //基于画布原点平移坐标(在坐标旋转的基础上实现平移)
5 
6 ctx.moveTo(0,0);
7 ctx.lineTo(100,0);
8 ctx.stroke();

canvas的缩放也是基于坐标缩放:

1 // 缩放
2 ctx.beginPath();
3 ctx.scale(2,0.5);
4 ctx.rect(100,100,100,100);
5 ctx.stroke();

上面的矩形缩放比较明显的说明了canvas缩放是基于坐标系的缩放,原本图形起点(100,100)通过缩放后移动到了(200,50),并且矩形的宽高同时也发生了压缩和拉伸效果。

通过前面的旋转平移可以发现一个问题,就是前后的坐标系变换会相互作用,但是在实际需求中可能是一个变换作用一个或有限的几个图形,而不是将整体的画布全局作用,这样的问题当然是可以解决的:ctx.save()、ctx.restore()

  • ctx.save() :存储当前坐标系的状态 【平移 | 旋转 | 缩放  | 默认状态】
  • ctx.restore() :引用最近存储的坐标系状态
 1 ctx.save();//存储坐标系初始状态
 2 
 3 ctx.beginPath();
 4 ctx.translate(100,100);
 5 ctx.rotate(Math.PI / 4);
 6 ctx.strokeRect(0,0,100,50);
 7 
 8 ctx.beginPath();
 9 ctx.restore(); //引用最近的save()存储的坐标系状态 (默认状态)
10 ctx.fillRect(200, 0, 100, 50);

 七、canvas背景填充、填充颜色渐变(线性渐变/径向渐变)

7.1背景填充

  • ctx.createPattern(img, 'on-repeat')
  • 定义和用法:
  • createPattern() 方法在指定的方向内重复指定的元素;元素可以是图片、视频,或者其他 <canvas> 元素。被重复的元素可用于绘制/填充矩形、圆形或线条等等。

context.createPattern(image,"repeat|repeat-x|repeat-y|no-repeat");

可以将createPattern()理解为合成一个可填充的内容,这个内容是基于图片合成(是否重复填充),除了使用图片作为填充内容,除了图片还可以是视频或者canvas元素。然后使用fillStyle指向这个填充内容。

1 // 背景填充
2 ctx.beginPath();
3 var img = new Image();
4 img.src = './logo_small.gif';
5 img.onload = function(){
6     var bg = ctx.createPattern(img, 'no-repeat');
7     ctx.fillStyle = bg;
8     ctx.fillRect(0,0,142,55);
9 }

但是这个填充内容它并不能只能跟随canvas的内容进行填充,而是在当前的绘制层上的原点作为起点填充,如果上面示例中的矩形绘制起点变成了(100,100)就看不到这个填充内容了,这时候就需要用到坐标系平移来实现这个需求ctx.translate(x,y):

 1 // 背景填充
 2 ctx.beginPath();
 3 ctx.translate(100,100);//坐标偏移到(100,100)
 4 var img = new Image();
 5 img.src = './logo_small.gif';
 6 img.onload = function(){
 7     var bg = ctx.createPattern(img, 'no-repeat');
 8     ctx.fillStyle = bg;
 9     ctx.fillRect(0,0,142,55);
10 }
使用translate()实现图像与填充内容整体偏移到指定位置

 如果需要填充整个画布就将矩形的大小设置为画布的大小。

7.2填充颜色渐变

  •  线性渐变填充
  • ctx.createLinearGradient(x1, y1, x2, y2):创建线性渐变路径(x1,y1)表示路径起点,(x2,y2)表示路径终点。
1 // 渐变色填充
2 ctx.beginPath();
3 var bg = ctx.createLinearGradient(0, 0, 500, 0); //线性渐变的方向
4 bg.addColorStop(0, '#fff'); //设置起始关键帧
5 bg.addColorStop(1, '#000'); //设置结束关键字
6 ctx.fillStyle = bg;
7 ctx.fillRect(0, 0, 500, 300);

注意关键帧的位置使用的是0~1之间的数值,除了设置起始和终点关键帧外还可以设置中间任意点的关键帧。

  • 径向渐变填充
  • ctx.createRadialGradient(x1,y1,r1,x2,y2,r2):参数(x,y)表示坐标,r表示半径

 一边非常详细的径向渐变剖析博客:https://www.cnblogs.com/tianma3798/p/5895811.html

//渐变色填充(径向渐变)
ctx.beginPath();
var bg = ctx.createRadialGradient(0, 0, 0, 0, 0, 100)
bg.addColorStop(0, "red");
bg.addColorStop(0.5, "yellow");
bg.addColorStop(1, "blue");
ctx.fillStyle = bg;
ctx.fillRect(0, 0, 200, 200);

径向渐变的两个坐标和半径之间的关系:

  • 1.当起始圆与结束圆的坐标重合,结束圆的半径大于起始圆半径:起始圆内全部被起始关键帧填充,结束圆外被结束关键帧填充,中间按照每帧的比例过渡填充。
  • 2.当起始圆与结束圆的坐标重合,起始圆的半斤大于结束圆半径:起始圆外全部被起始关键帧填充,结束圆内被接受关键帧填充,中间按照每帧的比例过渡填充。
  • 3.当起始圆与结束圆坐标重合,且半径相同时,背景填充为白色。
  • 4.当起始圆与结束圆的坐标不重合,但是起始圆与结束圆依然相互包含,依然适应1和2的规则。
  • 5.当两个圆相离,且半径不同时,会以半径小的圆心为顶点,向半径大的圆做放射状的扇形填充,各自保留圆内的纯色填充,圆外的过渡填充。
  • 6.当两个圆相离,且半径相同时,会以圆形大小做无限延申的矩形填充,圆内纯色填充,圆外的两圆的中间做渐变填充,两端做圆的纯色填充。(弧形方向指向起始圆)

 绘制空心圆:

1 //应用径向渐变绘制空心圆
2 ctx.beginPath();
3 var bg = ctx.createRadialGradient(200, 200, 30, 200, 200, 100)
4 bg.addColorStop(0, "#fff");
5 bg.addColorStop(0.5, "#000");
6 bg.addColorStop(1, "#fff");
7 ctx.fillStyle = bg;
8 ctx.fillRect(0, 0, 500, 500);

 八、canvas图形的阴影、渲染文字、线端样式

8.1图形阴影

  • ctx.shadowColor = "color" :设置阴影颜色
  • ctx.shadowBlur = num : 设置阴影宽度(默认状态阴影被图形边线居中分割)
  • ctx.shadowOffsetX = num : 设置阴影横向偏移量
  • ctx.shadowOffsetY = num : 设置阴影纵向偏移量
1 // canvas阴影
2 ctx.beginPath();
3 ctx.shadowColor= "#000";
4 ctx.shadowBlur = 30;
5 ctx.shadowOffsetX = 15;
6 ctx.shadowOffsetY = 15;
7 // ctx.strokeRect(150,150,200,200);
8 ctx.fillRect(150,150,200,200);

8.2渲染文字

  • ctx.font = "字体大小  字体" :设置字体大小与字体(同作用同一坐标系上的文字)
  • ctx.strokeText ('string',x,y):绘制描边文字
  • ctx.fillText('string',x,y) :绘制填充文字
1 // canvas文字渲染
2 ctx.beginPath();
3 ctx.font = "30px Georgia";
4 ctx.strokeStyle = "#ff0";
5 ctx.strokeText('他乡踏雪', 200,150);
6 
7 ctx.fillStyle = "#f0f";
8 ctx.fillText('他乡踏雪', 200,200);

8.3线端样式

  • ctx.lineCap = “butt  |  square  |  round”
  • ctx.lineJoin = "miter  |  bevel  |  round"
  • ctx.miterLimit = number  //当斜角预值大于设定预值时,lineJoin执行round效果

 lineCap 设置线端样式:

  butt:默认,无线端样式

  square:在线的两端添加同线宽一样长的线段(正方体)

  round:在线的两端添加同线宽一样的直径的半圆

lineJoin 设置折线角端的样式(斜角样式):

  round:将角端的斜角修成圆弧形

  bevel:将角端的斜角垂直斜角面直接砍断

  miter: 样式默认样式,如果折线的斜角角度足够小的话,会自动砍掉角端(bevel),

1 ctx.beginPath();
2 ctx.lineWidth = 15;
3 ctx.moveTo(100,100);
4 ctx.lineTo(200,100);
5 // ctx.lineCap = "round" //butt | square | round //线端样式
6 ctx.lineTo(100,120);
7 ctx.lineJoin = "miter"; //miter  |  bevel  | round //斜角样式(尖角)
8 ctx.miterLimit = 5; //当斜角预值大于设定预值时,lineJoin执行round效果
9 ctx.stroke();

 

posted @ 2019-08-02 13:38  他乡踏雪  阅读(331)  评论(0编辑  收藏  举报