一、canvas的介绍

  canvas是html5出现的新标签,像所有的DOM对象一样它有自己本身 的属性、方法和事件,其中就有绘图的方法,js能够调用它来进行绘图。canvas只有两个属性,而且是可选的,width、height,这两个属性也可以通过 js 来定义。

  canvas如果没有定义大小,则默认大小为宽 300px,高 150px。当然使用 css 也是可以设置 canvas 的大小,但如果css设置的宽高比例与 js 或在 标签的属性 width、height上设置的比例不一致,则会变形,而且如果css设置的值是它们的n倍,则图像也会放大n倍。

如下图第一个是长宽50的红色矩形,第二个是css设置为js设置的两倍是得出的图像,第三个是css设置宽高比为1.5:1时得出的图像且高是js设置的两倍。

  canvas在某些较老的浏览器(如IE9以下)中并不支持,我们可以在canvas标签中写些内容替代当canvas标签不被支持的时候。也可以使用 getContext 来判断是否支持canvas。

  1. <!-- 插入替换的图片或提示文本 -->
    <canvas id="canvas" width="500" height="500">
  2. <img src="data:images/canvas.png" width="500" height="500" alt=""/> //
  3. </canvas>
  1. var canvas = document.getElementById('canvas');
  2. if (canvas.getContext) {
  3. console.log('你的浏览器支持Canvas!');
  4. } else {
  5. console.log('你的浏览器不支持Canvas!');
  6. }

  canvas.getContext() 方法返回了一个相当于画笔的“东西”,通过它我们可以进行各种绘画。当然绘画时也会考虑是画2D的还是3D的,这个方法在传入参数后即可,如下:

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

如果想画3D的该怎么办,这样:

  1. var context = canvas.getContext("webgl");

本文只讨论2D的绘画的使用。

  画图首先要有一个参考系,原点在哪,x、y轴的方向,单位是什么?

如下图,坐标轴在左上角,x轴为横向向右,y轴为垂直向下,所以画图的坐标都是正值,如果是负值,图像就超出画布看不见了。坐标轴单位为px。

二、canvas绘制矩形

  画矩形首先要知道在哪里画,矩形的大小,边框的粗细,边框的颜色,是否是填充的这些要素。而我们的画笔的API可以做到这些。

  context.strokeRect(10,10,50,50),绘制无填充的矩形,四个参数分别表示:x轴坐标,y轴坐标,长,宽。。

  context.fillRect(10,10,50,50),绘制填充的矩形,默认填充黑色。

  1. var canvas = document.getElementById("canvas");
  2. canvas.width="500";
  3. canvas.height="500";
  4.  
  5. //第一步必须获取一个"画笔"
  6. var context = canvas.getContext("2d");
  7.  
  8. //画矩形
  9. //设置边框宽度  单位px
  10. context.lineWidth="6";
  11. //设置颜色
  12. context.strokeStyle="red";
  13. context.strokeRect(10,10,50,50)
  14. context.fillStyle = '#f00'
  15. context.fillRect(10,10,20,20)
  16.  
  17. //清除画笔的轨迹  将所在区域的矩形内容清空可以理解为橡皮擦
  18. context.clearRect(15,15,20,20)

画出图像如下,左上角白色的就是清除掉的部分。

如果你设置了边框宽度与颜色,但画出的图像没有,则是要在画之前进行设置,就是在context.strokeRect(10,10,100,100)之前定义,在其之后定义就只对后面画的有效了。

  还有一个方法可以绘画矩形:这两个API要永远在一起,分开的就无法绘画了。

  1. context.rect(10,10,100,100);
  2. //绘制的形状
  3. context.stroke()

三、canvas绘制圆和弧线

  context.arc(150, 150, 100, 0, 2*Math.PI, false),绘制圆,六个参数分别是:圆心的x轴坐标,圆心的y轴坐标,半径,圆弧的起始弧度,圆的结束弧度,绘制的方向(默认为false,顺时针方向)。圆弧的起始弧度默认为水平方向直径与圆弧的右边交点处,如下图A点处,结束位置是顺时针方向的弧度结束点。360°是2Π,90°是1/2Π。

  如果要绘制实心的圆则要用 context.fill() 表示绘制实心圆,这是你可以不写 context.stroke() 那么圆就没有边框。如果绘制的弧度小于2Π那绘制实心的时候出来的是一个月亮一样的图形。绘制图像如下图右边两张。故如果要绘制弧线则让所绘制的的弧度之差小于2Π即可。

  1. context.arc(150,150,100,0,2*Math.PI,false);
  2. context.strokeStyle="#c33"; //边框颜色
  3. context.fillStyle="red"; //填充颜色
  4. // context.fill(); //填充
  5. context.stroke(); //画边框
  6.  
  7. context.arc(150,150,100,.5*Math.PI,1*Math.PI); // 弧度从1/2*Π 到 Π,及90°到180°
  8. context.strokeStyle="#c33";
  9. // context.fill();
  10. context.stroke();

  

四、canvas绘制线

  context.moveTo(10,50),将“画笔”移动到指定的坐标x以及y上,context.lineTo()绘制路径的结束点。可以设置路径的粗细与颜色,粗细的单位是px。

  1. // 绘制一条线
  2. context.moveTo(10,150);
  3. context.lineTo(350,150);
  4. context.strokeStyle="black";
  5. context.lineWidth="6";
  6. context.stroke();

  所的图像如下,是一条长340px的直线。

  这时自然会想到如果同时绘制多条直线与弧会怎样,如下代码:

  1. context.moveTo(10,150);
  2. context.lineTo(350,150);
  3. context.strokeStyle="black";
  4. context.lineWidth="30";
  5. context.stroke();
  6.  
  7. context.moveTo(50,50);
  8. context.lineTo(200,200);
  9. context.strokeStyle="green";
  10. context.lineWidth="10";
  11. context.stroke();
  12.  
  13. context.arc(150,150,100,0.5*Math.PI,1.5*Math.PI,false);
  14. context.strokeStyle="#c33";
  15. context.lineWidth="2";
  16. context.stroke();

  所得如下图像,可以看到图中右很多线,但我们只画了三条。一条是中间的横线粗30px,黑色;一条是斜线粗10px,绿色;一条是一个粗1px红色的半圆。图上看起来好像有7条线。这是由于在canvas的绘制中,直线是由路径进行绘制的。

下面的痕迹也可以理解为你拿着画笔,笔一直没有离开画布画了这三条线。在不同的线之间移动时画笔也会留下痕迹,故多了那些多余的痕迹。

五、什么是路径?

  图形的基本元素是路径。路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。canvas只支持原生绘制矩形,其他的图形绘制都至少需要一条生成路径。

使用路径绘制图形需要一些额外的步骤。

  1. 你需要创建路径起始点。
  2. 然后你使用“画笔”去画出路径。
  3. 把路径封闭。
  4. 一旦路径生成,你就能通过描边或填充路径区域来渲染图形。

以下是所要用到的函数:

  1. beginPath()   新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。
  2. closePath()   闭合路径之后图形绘制命令又重新指向到上下文中。
  3. stroke()     通过线条来绘制图形轮廓。
  4. fill()      通过填充路径的内容区域生成实心的图形。

  调用beginPath()之后,或者canvas刚建的时候,第一条路径构造命令通常被视为是moveTo()。因为你几乎总是要在设置路径的起始位置。生成路径的步骤:

第一步叫做beginPath()。本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧形、等等)构成图形。而每次这个方法调用之后,列表清空重置,然后我们就可以重新绘制新的图形。

第二步就是调用函数指定绘制路径,本文稍后我们就能看到了。

第三,就是闭合路径closePath(),不是必需的。这个方法会通过绘制一条从当前点到开始点的直线来闭合图形。如果图形是已经闭合了的,即当前点为开始点,该函数什么也不做。

  当你调用fill()函数时,所有没有闭合的形状都会自动闭合,所以你不需要调用closePath()函数。但是调用stroke()时不会自动闭合。
 
将上面的代码闭合一下:
  1. ctx.beginPath();
  2. context.moveTo(10,150);
  3. context.lineTo(350,150);
  4. context.strokeStyle="black";
  5. context.lineWidth="30";
  6. context.stroke();
  7. ctx.closePath();
  8.  
  9. ctx.beginPath();
  10. context.moveTo(50,50);
  11. context.lineTo(200,200);
  12. context.strokeStyle="green";
  13. context.lineWidth="10";
  14. context.stroke();
  15. ctx.closePath();
  16.  
  17. ctx.beginPath();
  18. context.arc(150,150,100,0.5*Math.PI,1.5*Math.PI,true);
  19. context.strokeStyle="#c33";
  20. context.lineWidth="2";
  21. context.stroke();
  22. ctx.closePath();

所得图像如下:只有三条线了,没有其他的痕迹了。有人会说为啥半圆的方向变了,细心的朋友肯定发现了,其实是arc()的第六个参数改为 true导致圆弧绘制时的方向改变的缘故。

六、函数回调式的写法

  有时经常书写画笔的开始、闭合会很麻烦,这是会想可不可以将这些重复的代码封装以下,以后直接调用就行。没错就是这样,这是可以滴。如下:

  1. function draw(cb){
  2. context.beginPath();
  3. context.save();
  4. cb();
  5. context.restore();
  6. context.closePath();
  7. }
  8.  
  9. draw(()=>{
  10. context.moveTo(10,150);
  11. context.lineTo(350,150);
  12. context.strokeStyle="black";
  13. context.lineWidth="30";
  14. context.stroke();
  15. })
  16.  
  17. draw(()=>{
  18. context.moveTo(50,50);
  19. context.lineTo(200,200);
  20. context.strokeStyle="green";
  21. context.lineWidth="10";
  22. context.stroke();
  23. })
  24.  
  25. draw(()=>{
  26. context.arc(150,150,100,0.5*pi,1.5*pi,true);
  27. context.strokeStyle="#c33";
  28. context.lineWidth="2";
  29. context.stroke();
  30. })

如上代码画出的图像和上面的图是一样的,有人问这样写有什么用,这样写在画复杂图形时节省了起始与闭合代码的重复书写,也很清晰的能看出那些是一个画笔画出来的。

这是会有小伙伴问draw函数内的 save()、restore()是干什么的。

七、save()、restore()

  用于保存及恢复当前Canvas绘图环境的所有属性。其中save()可以保存当前状态,而restore()可以还原之前保存的状态。能起到保存绘制状态和防止污染状态栈。

  可以用栈来进行理解,当调用save()时会将“画笔”的状态(填充风格、线条风格、阴影风格的各种绘图状态)压入一个状态栈中,每调用一次就压入一次。当要使用时,用restore()来获取保存 的“画笔”状态,遵循栈的读取规则,先进后出,你最先取出的是你最后压入栈中的。如果你调用restore()的次数比save()多,那多余的次数画出的都是无效的。

  save()只是保存了状态,并没有保留之前绘制的图形。

  1. ctx.fillStyle = 'red';
  2. ctx.fillRect(10,30,15,15);
  3. ctx.save(); // 将第一个状态压入栈中
  4.  
  5. ctx.fillStyle = 'blue';
  6. ctx.fillRect(50,20,30,30);
  7. ctx.save(); // 将第二个状态压入栈中
  8.  
  9. ctx.fillStyle = 'green';
  10. ctx.fillRect(100,10,50,50);
  11. ctx.save(); // 将第三个状态压入栈中
  12.  
  13. ctx.restore(); // 取出栈3(第三个状态)
  14. ctx.beginPath();
  15. ctx.arc(200, 35, 30, 0, Math.PI*2, true);
  16. ctx.closePath();
  17. ctx.fill();
  18.  
  19. ctx.restore(); // 取出栈2(第二个状态)
  20. ctx.beginPath();
  21. ctx.arc(300, 35, 20, 0, Math.PI*2, true);
  22. ctx.closePath();
  23. ctx.fill();
  24.  
  25. ctx.restore(); // 取出栈1(第一个状态)
  26. ctx.beginPath();
  27. ctx.arc(400, 35, 10, 0, Math.PI*2, true);
  28. ctx.closePath();
  29. ctx.fill();
  30.  
  31. ctx.restore(); // 取出栈1(第一个状态)
  32. ctx.beginPath();
  33. ctx.arc(400, 35, 10, 0, Math.PI*2, true);
  34. ctx.closePath();
  35. ctx.fill();

看出最后多余的一次读取状态所绘图形并没有显示出来。所以读取次数要少于压入次数。否则无效。

八、绘制文字

  文字的绘制很简单,可以设置字体,大小,颜色,位置。

  字体的位置 第一个number参数时x轴坐标,第二number个为y轴坐标,字体的原点在字体的左下角,如下图所示位置。

  1. context.strokeStyle="#c33"; //设置颜色,如果要设置实心的字体颜色则要用 fillStyle
  2. context.font="40px 微软雅黑"; //设置大小与字体
  3. context.fillText("wertantan的博客园",80,80); //实心字体,设置所绘字体内容与位置
  4. context.strokeText("wertantan的博客园",80,150); //空心字体

九、绘制贝塞尔曲线

  二次贝塞尔曲线与三次贝塞尔曲线一般用来绘制复杂有规律的图形。可以用贝塞尔曲线组合直线、填充与矩形来绘制复杂的图形。

  quadraticCurveTo(cp1x, cp1y, x, y) 绘制二次贝塞尔曲线,cp1x,cp1y为一个控制点,x,y为结束点。

  bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) 绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。

  有人会问起始点去哪了,起始点可以通过moveTo()来定义,直接移动画笔到你所要绘制的起始点,之后再绘制贝塞尔曲线的控制点与结束点。每次绘制贝塞尔曲线都可以用moveTo()来定义起始点的位置。

  二次贝塞尔曲线有一个起始点(蓝色)、一个结束点(蓝色)以及一个控制点(红色),而三次贝塞尔曲线有两个控制点。

  1. // 二次贝塞尔曲线
  2. context.beginPath();
  3. context.moveTo(50,50);
  4. context.quadraticCurveTo(200,160,50,200);
  5. context.stroke();
  6. context.closePath();
  7.  
  8. // 三次贝塞尔曲线
  9. context.beginPath();
  10. context.moveTo(50,50);
  11. context.bezierCurveTo(200,80,200,170,50,200)
  12. context.stroke();
  13. context.closePath();

所绘制图像如下:

       

Canvas除了能绘制基本的形状和文本,还可以实现动画、缩放、各种滤镜和像素转换等高级操作。这里就不再说了,感兴趣的小伙伴可以去 MDN 看一看。

至此canvas的基础使用就结束了,希望各位能有所收获,如有问题还望指正!

本文所有图片均由canvas绘制,截取下来。

这里附上一个小案例,是一个方形从浏览器可视区的左上角移动到右下角的动画,代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>Document</title>
  8. <style>
  9. *{margin:0;padding:0;}
  10. </style>
  11. </head>
  12. <body>
  13. <canvas id="canvas"></canvas>
  14. </body>
  15. </html>
  16. <script>
  17. var canvas = document.getElementById("canvas");
  18. var iw = document.documentElement.clientWidth;
  19. var ih = document.documentElement.clientHeight;
  20. var context = canvas.getContext("2d");
  21.  
  22. canvas.width = iw-50;
  23. canvas.height= ih-50;
  24. var x = 0;
  25. var y = 0;
  26. var b = (ih-50)/(iw-50);
  27.  
  28. var timer = setInterval(function(){
  29. context.clearRect(0,0,iw,ih);
  30. x++;
  31. y+=b;
  32. context.fillRect(x,y,50,50*b);
  33. if(x>=iw-100){
  34. clearInterval(timer);
  35. }
  36. console.log(x)
  37. },30)
  38. </script>

效果如下:

转载请注明出处!

canvas 的基本使用的更多相关文章

  1. html5 canvas常用api总结(三)--图像变换API

    canvas的图像变换api,可以帮助我们更加方便的绘画出一些酷炫的效果,也可以用来制作动画.接下来将总结一下canvas的变换方法,文末有一个例子来更加深刻的了解和利用这几个api. 1.画布旋转a ...

  2. 【探索】利用 canvas 实现数据压缩

    前言 HTTP 支持 GZip 压缩,可节省不少传输资源.但遗憾的是,只有下载才有,上传并不支持.如果上传也能压缩,那就完美了.特别适合大量文本提交的场合,比如博客园,就是很好的例子. 虽然标准不支持 ...

  3. 简单入门canvas - 通过刮奖效果来学习

    一 .前言 一直在做PC端的前端开发,从互联网到行业软件.最近发现移动端已经成为前端必备技能了,真是不能停止学习.HTML5新增的一些东西,canvas是用的比较多也比较复杂的一个,简单的入门了一下, ...

  4. 获取Canvas当前坐标系矩阵

    前言 在我的另一篇博文 Canvas坐标系转换 中,我们知道了所有的平移缩放旋转操作都会影响到画布坐标系.那在我们对画布进行了一系列操作之后,怎么再知道当前矩阵数据状态呢. 具体代码 首先请看下面的一 ...

  5. Canvas坐标系转换

    默认坐标系与当前坐标系 canvas中的坐标是从左上角开始的,x轴沿着水平方向(按像素)向右延伸,y轴沿垂直方向向下延伸.左上角坐标为x=0,y=0的点称作原点.在默认坐标系中,每一个点的坐标都是直接 ...

  6. Canvas绘图之平移translate、旋转rotate、缩放scale

    画布操作介绍 画布绘图的环境通过translate(),scale(),rotate(), setTransform()和transform()来改变,它们会对画布的变换矩阵产生影响. 函数 方法 描 ...

  7. 用html5的canvas和JavaScript创建一个绘图程序

    本文将引导你使用canvas和JavaScript创建一个简单的绘图程序. 创建canvas元素 首先准备容器Canvas元素,接下来所有的事情都会在JavaScript里面. <canvas ...

  8. html5标签canvas函数drawImage使用方法

    html5中标签canvas,函数drawImage(): 使用drawImage()方法绘制图像.绘图环境提供了该方法的三个不同版本.参数传递三种形式: drawImage(image,x,y):在 ...

  9. 使用 JavaScript 和 canvas 做精确的像素碰撞检测

    原文地址:Pixel accurate collision detection with Javascript and Canvas 译者:nzbin 我正在开发一个需要再次使用碰撞检测的游戏.我通常 ...

  10. H5项目开发分享——用Canvas合成文字

    以前曾用Canvas合成.裁剪.图片等<用H5中的Canvas等技术制作海报>.这次用Canvas来画文字. 下图中"老王考到驾照后"这几个字是画在Canvas上的,与 ...

随机推荐

  1. Redis(三)--- Redis的五大数据类型的底层实现

    1.简介 Redis的五大数据类型也称五大数据对象:前面介绍过6大数据结构,Redis并没有直接使用这些结构来实现键值对数据库,而是使用这些结构构建了一个对象系统redisObject:这个对象系统包 ...

  2. HTML--网页练习--(360导航首页的一部分)

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  3. 如何简单地利用Bitmap为中介储存图片到数据库中

        这是我的第一篇博文,请大家多多指教!     大概一个月之前,在跟朋友合作开发一个APP的过程中,我们发现到一个问题:图片的存储.因为数据库没有图片这种数据类型,当用户上传的图片需要存储的时候 ...

  4. java性能优化--字符串优化处理

    String对象 String对象是java中重要的数据类型,在大部分情况下我们都会用到String对象.其实在Java语言中,其设计者也对String做了大量的优化工作,这些也是String对象的特 ...

  5. 关于引入js文件乱码的问题

    对于大多数的web页面,我们一般都是使用如下两种编码:UTF-8.GB2312.所以我们只需要同意页面和js编码就可以解决乱码问题: 对于GBK页面引用编码为UTF-8编码的JavaScript文件如 ...

  6. Linux学习笔记07之shell

    shell从广义上分为两类: GUI:GNOME KDE XFACE等 CLI:sh csh bash shell启动:当用户登录完成后,系统会自动启动shelll程序 进程:应用程序的副本,用PID ...

  7. 【MySQL】ON DUPLICATE KEY UPDATE

    之前没用过这个操作,甚至没见过--最近接触到,而且还挺有用. 作用:若 KEY 不重复,则插入记录:否则更新记录. 单条操作: INSERT INTO table(a, b, c) VALUES (1 ...

  8. js实现3D切换效果

    今天分享一个3d翻转动画效果,js+css3+h5实现,没有框架. 先看下html部分: <div class="box"> <ul> <li> ...

  9. 使用JavaScript的XMLHttpRequest+fromdata 传递blob到后端

    需要上传网页录音文件到服务器,写的艰辛,终于好了,C#端的代码失败的留作纪念,JS端也有失败的案例,就不放上来了 JavaScript: var form = new FormData(); form ...

  10. Java动态,安全追踪工具

    Java动态,安全追踪工具 在我们日常的开发中,总是难以避免的要解决线上的问题.如果线上的问题我们在本地调试的时候无论调试多少次发现明明本地调用了这个方法呀,怎么线上就是没调呢?还有就是出了问题的时候 ...