canvas的学习

309 阅读8分钟

一、基础API和用法

1、兼容性内容替换

由于某些较老的浏览器(尤其是IE9之前的IE浏览器)或者文本浏览器不支持HTML5元素 canvas,在这些浏览器上应该总是能展示替代的内容。

    <canvas id="canvas" width="150" height="150">
        您的浏览器暂时不支持canvas,请升级或更换您的浏览器!
        <!-- 当然也可以替换任意的文本或者其他的HTML代码 -->
    </canvas>

2、canvas 2d 模板骨架

设置了一个id为canvas并且宽度和高度分别为150像素的 canvas 画布,并且在文档加载完成之后调用 draw 函数进行绘制,函数获得元素也进行了兼容性判断后开始操作。

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Canvas</title>
</head>
<body onload="draw()">
    <canvas id="canvas" width="150" height="150">
        您的浏览器暂时不支持canvas,请升级或更换您的浏览器!
    </canvas>
</body>
</html>
<script>
    function draw() {
        var canvas = document.getElementById('canvas');
        // 通过是否具有getContext这个方法来判断是否具有2d绘制功能
        if (canvas.getContext) {
            // 获取2d上下文,所有的操作都是通过这个上下文进行操作的
            var ctx = canvas.getContext('2d');
        }
    }
</script>

3、canvas 2d 形状绘制

(1)矩形(Rectangular)

绘制的矩形主要有三种:实心矩形(fillRect)、空心矩形(strokeRect)、清除矩形区域(clearRect),无论是绘制实心矩形还是清除矩形区域,四个参数分别是 xywidthheight

    // 矩形
    ctx.fillRect(25, 25, 100, 100); // 实心矩形(填充)
    ctx.clearRect(40, 40, 70, 70); // 清除矩形区域
    ctx.strokeRect(50, 50, 50, 50); // 空心矩形(描边)

(2)圆弧(arc)

arc(x, y, radius, startAngle, endAngle, anticlockwise) : 以 xy 为圆心点的以 radius 为半径的圆弧(圆),从 startAngle 开始到 endAngle 结束生成;参数 anticlockwise为一个布尔值,为 true 时,是逆时针方向,否则顺时针方向(默认顺时针)

    // 圆弧(圆)
    ctx.clearRect(0, 0, 150, 150); // 清除整个画布
    ctx.arc(75, 75, 50, 0, 2 * Math.PI, false); // arc方法做内存的计算处理,Math.PI代表180°
    // 执行stroke或fill方法之后才真正绘制圆弧
    ctx.stroke(); // 描边绘制
    // ctx.fill(); // 填充绘制

(3)路径(Paths)

图形的基本元素是路径。而路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。

    // 路径
    ctx.clearRect(0, 0, 150, 150); // 清除整个画布内容
    ctx.beginPath(); // 开始绘制路径
    ctx.moveTo(50, 50); // 把画笔移动到坐标(50, 50)
    ctx.lineTo(100, 75); // 把画笔从上个点往(100, 75)绘制直线
    ctx.lineTo(55, 135); // 再从上个点往(55,, 135)绘制直线
    ctx.stroke(); // 描边绘制
    // ctx.fill(); // 填充绘制出一个三角形

值得注意的是:stroke 方法是用来绘制描边的图形,而 fill 方法是用来绘制一个填充的图形,有  stroke 的地方就可以用 fill 来替换,不过一个是描边,一个是填充。

二、样式与颜色

1、canvas 2d 色彩(colors)

如果想要给图形上色,有两个重要的属性可以做到: fillStylestrokeStylefillStyle 用于填充颜色, strokeStyle 用于描边颜色,只要是符合W3C的颜色格式都支持的。

    // 绘制矩形
    ctx.fillStyle = "#ff0000"; // 填充颜色:十六进制(红色)
    ctx.fillRect(25, 25, 100, 100); // 实心矩形(填充)
    ctx.clearRect(40, 40, 70, 70); // 清除矩形区域
    ctx.strokeStyle = "rgb(0, 255, 0)"; // 描边颜色:rgb格式(绿色)
    ctx.strokeRect(50, 50, 50, 50); // 空心矩形(描边)

除了可以绘制实色图形,我们还可以用 canvasglobalAlpha 来绘制不同透明度的图形。

    ctx.globalAlpha = 0.3; // 设置透明度

此外,像一般的绘图软件一样,我们可以用线性(从左到右或从上到下)或者径向(从内向外)的渐变来填充或描边。

    // 从坐标(0, 0)到坐标(150, 150)的线性渐变
    var lineargradient = ctx.createLinearGradient(0, 0, 150, 150); 
    // 从左上角的白色到右下角的黑色渐变
    lineargradient.addColorStop(0, '#fff');
    lineargradient.addColorStop(1, '#000');
    ctx.fillStyle = lineargradient;
    // 从半径为10,圆心坐标为(75, 75)的內圆开始;向半径为100,圆心坐标为(75, 75)的外圆径向渐变
    var radialgradient = ctx.createRadialGradient(75, 75, 10, 75, 75, 100);
    radialgradient.addColorStop(0, '#fff');
    radialgradient.addColorStop(1, '#000');
    ctx.fillStyle = radialgradient;
    ctx.arc(75, 75, 50, 0, 2 * Math.PI, false); 
    ctx.fill();

2、canvas 2d 线型(line styles)

通过 lineWidth 来设置线条宽度:

ctx.lineWidth = 5; // 设置线条宽度: value为整数

通过 lineCap 来设置线条末端样式,对应属性有 'butt', 'round' , 'square'

ctx.lineCap = 'round'; // 设置线条末端样式

通过 lineJoin 来设置线条与线条间结合处的样式,对应属性有 'round', 'bevel' , 'miter'

ctx.lineJoin = 'round'; // 设置线条与线条间结合处的样式

设置虚线的样式:

ctx.setLineDash([5, 15]); // 参数segments:数组值,线段与间隔之间的长度形成的数组
ctx.lineDashOffset = 8; // 参数value:整数
ctx.getLineDash();

三、曲线和高级路径

1、贝塞尔曲线

二次及三次贝塞尔曲线都十分有用,一般用来绘制复杂有规律的图形。 上图可以很好地描述两者的关系,左边第一个蓝点为开始点,通过 moveTo 来确定;红点为控制点,改变控制点的坐标可以改变图形的弧度,第二个蓝点为结束点。

绘制二次贝塞尔曲线:

quadraticCurveTo(cp1x, cp1y, x, y)cp1xcp1y为一个控制点,xy为结束点:

    ctx.beginPath(); // 开始一段新的路径
    ctx.moveTo(0, 150); // 将画笔移动到开始点(0, 150)
    ctx.quadraticCurveTo(75, 0, 150, 150); // 控制点为(75, 0), 结束点为(150, 150);
    ctx.stroke();

绘制三次贝塞尔曲线:

bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)cp1xcp1y为第一个控制点, cp2xcp2y为第二个控制点,xy为结束点:

    ctx.clearRect(0, 0, 150, 150); // 清空画布
    ctx.beginPath();
    ctx.moveTo(0, 100); // 将画笔移动到开始点(0, 100)
    // 控制点1为(50, 50),控制点2为(100, 150), 结束点为(150, 100);
    ctx.bezierCurveTo(50, 50, 100, 150, 150, 100); 
    ctx.stroke();

画贝塞尔曲线可以找到用PS使用钢笔工具画线条通过控制点改变弧度的感觉呢。

2、高级路径值Path2D

Path2D 对象用来缓存或记录绘画命令,以便快速地回顾路径。

    ctx.clearRect(0, 0, 150, 150); // 清空画布
    var circle = new Path2D();
    circle.moveTo(125, 35);
    circle.arc(100, 35, 25, 0, 2*Math.PI);
    ctx.fill(circle);

所有的路径方法比如 moveTo, rect, arcquadraticCurveTo 等,如我们前面见过的,都可以在 Path2D 中使用。

Path2D API有另外一个强大的特点:使用 SVG path data 来初始化 canvas 上的路径:

    ctx.clearRect(0, 0, 150, 150); // 清空画布
    var svg = new Path2D("M10 10 h 80 v 80 h -80 Z");
    ctx.fill(svg);

上条这条路径先移动到点(M10 10), 然后再水平移动80个单位(h 80),然后下移80个单位(v 80),接着左移80个单位(h -80),再回到起点处(Z)。这样一个正方形图形就画出来了。

关于 svg data 的语法和理解,可以参考第三方文档和资料。

四、状态、变形以及动画

1、状态控制

有2个开始绘制复杂图形时必不可少的方法:

save():保存画布的所有状态,canvas 的状态就是当前画面应用的所有样式和变形的一个快照。

restore():恢复画布的状态,这两个方法都没有参数。

Canvas 的状态存储在栈中,每当 save() 方法被调用后,当前的状态就被推送到栈中保存。 一个绘画状态包括:

1.当前应用的变形(即移动,旋转和缩放等)

2.strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap , lineJoin, miterLimit等等属性的值

3.当前的裁切路径(clipping path)

你可以调用任意多次的 save 方法,每一次调用 restore 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。

2、各种变形

移动(Translating):translate(x, y), translate 方法接受两个参数,x 是左右偏移量,y 是上下偏移量。

缩放(Scaling):scale(x, y), 参数都是实数,可为负数,x为水平缩放,y为垂直缩放,如果比1小会缩小图形,如果比1大会放大图形。默认值为1。

旋转(Rotating):rotate(angle), 这个方法只接受一个参数,旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。

变形(Transforms):tranfor(m11, m12, m21, m22, dx, dy),这个方法是将当前的变形矩阵乘上一个基于自身参数的矩阵。

下面来简单的实践一下:

    function draw() {
        var canvas = document.getElementById('canvas');
        if (canvas.getContext) {
            var ctx = canvas.getContext('2d'); 
            ctx.lineWidth = 10;
            ctx.strokeStyle = "red";
            ctx.translate(75, 75); // 偏移
            ctx.rotate(30 / 180 * Math.PI); // 这里注意旋转30°的写法
            ctx.beginPath();
            ctx.moveTo(0, -30);
            ctx.lineTo(0, -50);
            ctx.stroke();

            ctx.rotate(30 / 180 * Math.PI);
            ctx.beginPath();
            ctx.moveTo(0, -0);
            ctx.lineTo(0, -20);
            ctx.stroke();
            ctx.save(); // 保存
            ctx.rotate(30 / 180 * Math.PI);
            ctx.beginPath();
            ctx.moveTo(0, -30);
            ctx.lineTo(0, -50);
            ctx.stroke();
            // ctx.restore(); // 恢复

            ctx.rotate(30 / 180 * Math.PI);
            ctx.beginPath();
            ctx.moveTo(0, -0);
            ctx.lineTo(0, -20);
            ctx.stroke();
        }
    }

3、动画的基本实现步骤

可以通过以下的步骤来画出一帧:

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

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

(3)绘制动画图形(animated shapes):这一步才是重绘动画帧。

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