- canvas
- javascript
一、画布概述
canvas是h5新增的组件,它就像一块幕布,可以用js操作它的专属api在上面绘制各种图像和动画,目前兼容到ie9,例如下面这个动画,仅用了四五十行代码就实现了:
二、创建画布
<canvas id="myCanvas" width="600" height="400" style="background:#f1f1f1;"></canvas>
<script>
    var myCanvas = document.getElementById('myCanvas');
    myCanvas.width = 600;
    myCanvas.height = 400;
</script >要使用canvas,首先在结构里创建一个canvas标签,然后设置它的宽高,再获取canvas的节点。
拿到节点后再通过api来获取这个画布的上下文对象:
// 初始化canvas,获取上下文对象
var ctx = myCanvas.getContext('2d');get表示得到,context是上下文的意思。
开头必须这么写,王八的屁股—龟腚,getContext()只有一个参数"2d",在未来,如果canvas标签扩展到支持3d绘图,getContext()方法可能允许传递一个"3d"字符串参数。
拿到上下文对象后,就可以开始画东西了,canvas的坐标系是以左上角作为原点(0,0),例如一块长宽高为600*400的画布,左上角的点坐标是(0,0),右上角的点坐标是(600,0),左下角的点坐标是(0,400),右下角的点坐标是(600,400)。如图:

三、绘制矩形
先绘制一个实心矩形:
// 设置填充的颜色
ctx.fillStyle = '#f60';
// 绘制实心矩形, 参数意思为在坐标(200,100)的位置,宽高为120*60
ctx.fillRect(200, 100, 120, 60);再绘制一个描边矩形:
// 设置描边的颜色
ctx.strokeStyle = '#f60';
// 设置描边粗细, 不设置也默认为1
ctx.lineWidth = '1';
// 绘制描边矩形, 参数意思为在坐标(200,200)的位置,宽高为120*60
ctx.strokeRect(200, 200, 120, 60);
四、绘制线条和多边形
canvas中可以允许我们绘制自定义的笔触,绘制直线非常简单:
// 开始画路径
ctx.beginPath();
// 将画笔移动到一个位置
ctx.moveTo(100, 100);
// 设置一个点连接上一个位置,画一条线, 暂时看不见
ctx.lineTo(150, 150);
// 描边渲染出来, 此时可以看见了
ctx.stroke();
绘制多边形,其实就是用lineTo绘制多条线,然后闭合:
ctx.beginPath();
ctx.moveTo(350, 100);
ctx.lineTo(500, 200);
ctx.lineTo(500, 80);
// 最后要返回原点闭合, 或者直接使用ctx.closePath()自动闭合
ctx.lineTo(350, 100);
// 设置填充的颜色
ctx.fillStyle = 'skyblue';
// 进行填充
ctx.fill();
// 设置描边的颜色
ctx.strokeStyle = '#000';
// 设置描边粗细
ctx.lineWidth = '2';
// 进行描边
ctx.stroke();五、绘制弧和圆形
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);参数x、y是这个圆弧的圆心,radius是圆弧的半径,startAngle是圆弧开始的位置,endtAngle是圆弧结束的位置,anticlockwise是可选参数,默认为false,如果为 true,逆时针绘制圆弧,反之,顺时针绘制。
特别注意:参数startAngle和endtAngle在这里并不是角度制,而是弧度制,一个满圆的弧度是2π。
来绘制顺时针和逆时针两个圆弧比较:
ctx.beginPath();
// 顺时针, -2弧度开始到2弧度结束
ctx.arc(100, 330, 40, -2, 2, false);
ctx.stroke();
    
ctx.beginPath();
// 逆时针, -2弧度开始到2弧度结束
ctx.arc(200, 330, 40, -2, 2, true);
ctx.stroke();
再来绘制一个满圆,从0弧度开始到2π的弧度结束,无所谓顺逆时针,不描边只填充:
ctx.beginPath();
ctx.arc(420, 300, 60, 0, 2 * Math.PI);
ctx.fillStyle = 'seagreen';
ctx.fill();
六、画布的动画和原理
清屏 → 重绘 → 清屏 → 重绘 → 清屏 → 重绘 → 清屏 → 重绘 → 清屏 → 重绘 → 清屏 → ......
清屏的api:
ctx.clearRect(x, y, w, h);意思是清除一个矩形区域,清除从坐标xy开始,宽w高h的矩形区域,如果画布是是600*400的尺寸,那ctx.clearRect(0, 0, 600, 400)就是清屏的作用了。
来做一个简单的小球动画:
// 把圆心坐标抽成变量
var _x = 30;
var _y = 30;
// 为画布创建定时器, 一秒50帧
setInterval(function() {
  // 清屏
  ctx.clearRect(0, 0, 600, 400);
  ctx.beginPath();
  // 画圆
  ctx.arc(_x, _y, 15, 0, 2 * Math.PI);
  ctx.fillStyle = '#f60';
  ctx.fill();
  // 画完后改变圆心坐标变量
  _x += 6;
  _y += 4;
  if (_y > 400) {
    _x = 30;
    _y = 30;
  }
}, 20)

七、canvas动画与面向对象
画布上不管有几个元素在运动,一定只有一个定时器,这个定时器负责每一帧的清屏和绘制下一帧内容,如果页面上有很多元素在运动,这些元素的信息和状态真的难以维护,这时需要使用面向对象的思想来管理。
<body>
  <canvas id="myCanvas" width="600" height="400" style="background: #f1f1f1;"></canvas>
  <script type="text/javascript">
    // 获取画布节点
    var myCanvas = document.getElementById('myCanvas');
    // 设置画布的宽高
    myCanvas.width = 600;
    myCanvas.height = 400;
    // 获取画布的上下文对象
    var ctx = myCanvas.getContext('2d');
    // 小球的构造函数, 传坐标, 半径和速度形参
    function Ball(x, y, r, speed) {
      this.x = x;
      this.y = y;
      this.r = r;
      this.speed = speed;
      // new出来的时候直接调用下面写的渲染方法把小球放到画布上
      this.render();
    }
    // 渲染小球的方法
    Ball.prototype.render = function() {
      ctx.beginPath();
      ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
      ctx.fillStyle = 'skyblue';
      ctx.fill();
    }
    // 更新小球实例的字段的方法
    Ball.prototype.update = function() {
      this.x += this.speed * 3;
      this.y += this.speed * 2;
      if (this.y > 400) {
        this.x = 30;
        this.y = 30;
      }
    }
    // 创建出来的小球实例放在这个数组里
    var ballArr = [];
    // 每个小球实例的实参都可以不同
    ballArr[0] = new Ball(30, 30, 10, 2);
    ballArr[1] = new Ball(30, 70, 15, 1.5);
    ballArr[2] = new Ball(30, 110, 20, 1);
    // 画布唯一定时器, 每秒50帧, 每一帧都先清屏
    setInterval(function() {
      ctx.clearRect(0, 0, myCanvas.width, myCanvas.height);
      for (var i = 0; i < ballArr.length; i++) {
        // 每个小球的实例调用自己的update方法来改变自己的坐标字段
        ballArr[i].update();
        // 每个小球的实例调用自己的render方法来绘制改变坐标后的小球
        ballArr[i].render();
      }
    }, 20);
  </script>
</body>
我们不要用全局变量来维护某一个小球的x、y等各种信息,而是应该用对象来封装它,把画布上的每一个元素都看成一个演员,把他们放入数组,每一帧都让这些演员做出相应的改变,然后再渲染出新的一帧。


