Canvas基础图形与动画

canvas是 HTML5 新增的元素,可使用JavaScript脚本来绘制图形。基于渲染上下文的不同,WebGL使用canvas元素绘制三维图形,而本文主要围绕二维图形,也就是canvas.getContext('2d')

<canvas id="canvas" width="500" height="500"></canvas>

当没有设置宽度和高度的时候,canvas会初始化宽度为300像素和高度为150像素。

基础图形绘制

直线

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

ctx.strokeStyle='red'; //设置描边颜色为红色,跟CSS颜色类似,默认是黑色
ctx.moveTo(100,100); //移动起始点到坐标(x,y)
ctx.lineTo(200,100); //画一条长度100像素的线段
ctx.stroke();

矩形

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

ctx.storkeStyle='blue';
ctx.fillStyle='red';

ctx.fillRect(0,0,100,100);//绘制一个填充矩形,参数(x,y,width,height)
ctx.strokeRect(100,100,150,150);//绘制描边矩形


ctx.rect(250,250,100,50);
ctx.fill();//需调用ctx.fill()或ctx.stroke(),才能显示上面画的矩形

ctx.clearRect(25,25,50,50); //清除矩形区域,严格来说这不算是绘制API

圆弧

arc(x, y, radius, startAngle, endAngle, anticlockwise)

画一个以(x,y)为圆心的以radius为半径的圆弧(圆),从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针)来生成。

该方法有六个参数:x,y为绘制圆弧所在圆上的圆心坐标。radius为半径。startAngle以及endAngle参数用弧度定义了开始以及结束的弧度。这些都是以x轴为基准。参数anticlockwise 为一个布尔值。为true时,是逆时针方向,否则顺时针方向。

注意:arc()函数中的角度单位是弧度,不是度数。角度与弧度的js表达式:radians=(Math.PI/180)*degrees。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

// 空心圆
ctx.beginPath();
ctx.arc(50, 50, 50, 0, Math.PI * 2, true);
ctx.stroke();
// 实心圆
ctx.beginPath();
ctx.arc(150, 50, 50, 0, Math.PI * 2, true);
ctx.fill();
// 逆时针圆弧
ctx.beginPath();
ctx.arc(250, 50, 50, 0, Math.PI * 0.5, true);
ctx.stroke();
// 顺时针圆弧
ctx.beginPath();
ctx.arc(350, 50, 50, 0, Math.PI * 1.5, false);
ctx.stroke();

贝塞尔曲线

一次以及二次贝塞尔曲线都十分有用,一般用来绘制复杂有规律的图形。

quadraticCurveTo(cp1x, cp1y, x, y)

绘制贝塞尔曲线,cp1x,cp1y为控制点,x,y为结束点。

bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

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

上图能够很好的描述两者的关系,贝塞尔曲线有一个开始、结束点(蓝色)以及一个控制点(红色),而二次贝塞尔曲线使用两个控制点。

参数x、y在这两个方法中都是结束点坐标。cp1x,cp1y为坐标中的第一个控制点,cp2x,cp2y为坐标中的第二个控制点。

使用一次以及二次贝塞尔曲线是有一定的难度的,因为不同于像Adobe Illustrators这样的矢量软件,我们所绘制的曲线没有直接的视觉反馈给我们。这让绘制复杂的图形十分的困难。

一次贝塞尔曲线

这个例子使用多个贝塞尔曲线来渲染对话气泡。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

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

二次贝塞尔曲线

这个例子使用贝塞尔曲线绘制心形。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

ctx.beginPath();
ctx.moveTo(75, 40);
ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
ctx.fill();

动画

canvas动画是逐帧动画,元素的视觉变化,主要依靠不停的清除画布和绘制该元素的新变化。不停的重绘是相当费时的,性能依赖于电脑的速度。

动画的基本步骤

你需要进行以下一些步骤来画出一帧:

  1. 清空 canvas

    除非接下来要画的内容会完全充满 canvas (例如背景图),否则你需要清空所有。最简单的做法就是用 clearRect 方法。

  2. 保存 canvas 状态

    如果你要改变一些会改变 canvas 状态的设置(样式,变形之类的),又要在每画一帧之时都是原始状态的话,你需要先保存一下。

  3. 绘制动画图形(animated shapes)

    这一步才是重绘动画帧。

  4. 恢复 canvas 状态

    如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧。

操控动画

上面的步骤只是绘制了动画的一帧,为了实现动画,我们需要一些可以定时执行重绘的方法。有两种方法可以实现这样的动画操控。一是通过 setInterval 或 setTimeout 方法来控制定时绘制。二是,通过window.requestAnimationFrame()来定期执行一个绘制函数。现代浏览器(IE10及以上)基本都支持该方法,以下例子均采用window.requestAnimationFrame()。

一个简单示例

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var raf;

function fill() {
ctx.fillStyle = 'rgba(255,255,255,1)'
ctx.fillRect(0, 0, canvas.width, canvas.height)
}

var x = 0,
y = 0;

function draw() {
fill()//填成画布背景色,根据需要,也可以采用清除画布的方式
ctx.save()
ctx.beginPath()
ctx.strokeStyle = 'red'
ctx.arc(x, y, 10, 0, Math.PI * 2)
ctx.stroke()
ctx.restore()
x += 10
y += 10
if (x > 500) {//结束动画条件
cancelAnimationFrame(raf)
return;
}
raf = requestAnimationFrame(draw)
}

draw()

较复杂的例子

var sun = new Image();
var moon = new Image();
var earth = new Image();

function init() {
sun.src = 'https://mdn.mozillademos.org/files/1456/Canvas_sun.png';
moon.src = 'https://mdn.mozillademos.org/files/1443/Canvas_moon.png';
earth.src = 'https://mdn.mozillademos.org/files/1429/Canvas_earth.png';
window.requestAnimationFrame(draw);
}

function draw() {
var ctx = document.getElementById('canvas').getContext('2d');

ctx.globalCompositeOperation = 'destination-over';
ctx.clearRect(0, 0, 300, 300); // clear canvas

ctx.fillStyle = 'rgba(0,0,0,0.4)';
ctx.strokeStyle = 'rgba(0,153,255,0.4)';
ctx.save();
ctx.translate(150, 150);

// Earth
var time = new Date();
ctx.rotate(((2 * Math.PI) / 60) * time.getSeconds() + ((2 * Math.PI) / 60000) * time.getMilliseconds());
ctx.translate(105, 0);
ctx.fillRect(0, -12, 50, 24); // Shadow
ctx.drawImage(earth, -12, -12);

// Moon
ctx.save();
ctx.rotate(((2 * Math.PI) / 6) * time.getSeconds() + ((2 * Math.PI) / 6000) * time.getMilliseconds());
ctx.translate(0, 28.5);
ctx.drawImage(moon, -3.5, -3.5);
ctx.restore();

ctx.restore();

ctx.beginPath();
ctx.arc(150, 150, 105, 0, Math.PI * 2, false); // Earth orbit
ctx.stroke();

ctx.drawImage(sun, 0, 0, 300, 300);

window.requestAnimationFrame(draw);
}

init();

本文完

参考文档