HTML5 Canvas

一、添加一个 Canvas

1.布置画布:通过添加<canvas>标签,添加canvas元素
  • CanvasHTML5中是透明的,是不可见的。
  • <canvas>标签中的那段文本是什么意思呢?那是一旦浏览器执行HTML页面时不支持Canvas,就会显示这段文字,换言之,只要你的浏览器支持Canvas,页面上就不会显示这个文本。
  • <canvas>中的id是标签的属性之一,在JavaScript代码中用来指定特定的<canvas>,是唯一的。
2.获取canvas对象
var canvas = document.getElementById("canvas");
3.获得画笔(2D环境)
var context = canvas.getContext("2d");

二、绘制一条线段

1.移动画笔(moveTo())
  • canvas画布的左上角为笛卡尔坐标系的原点,且y轴的正方向向下,x轴的正方向向右。
  • context.moveTo(100,100)。这句代码的意思是 移动画笔至(100, 100)这个点(单位是px)。
  • moveTo()方法只是表示准备状态,准备要画,还没开始画。
笔画停点(lineTo())
  • 从上一笔的停止点绘制到这里。
  • context.lineTo(600, 600)是从 上一笔的停止点绘制到(600, 600)这里。
  • lineTo()方法只是表示准备状态,准备要画,还没开始画。
3.选择画笔
  • context.lineWidth = 5,这句话的意思是设置画笔(线条)的粗细为 5px
  • context.strokeStyle = "#AA394C",这句话的意思是设置画笔(线条)的颜色为玫红色。

4.确定绘制

  • 确定绘制只有两种方法,fill()stroke()
  • fill()是填充。
  • stroke()是描边。
demo
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>从线条开始</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        context.moveTo(100,100);
        context.lineTo(600,600);
        context.lineWidth = 5;
        context.strokeStyle = "#AA394C";
        context.stroke();
    }
</script>
</body>
</html>
绘制一条线段

三、多线条组成图形

1.绘制折线

复用lineTo()就可以了。

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>绘制折线</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        context.moveTo(100,100);
        context.lineTo(300,300);
        context.lineTo(100,500);
        context.lineWidth = 5;
        context.strokeStyle = "#AA394C";
        context.stroke();
    }
</script>
</body>
</html>

折线
2.绘制多条折线

如果要画三条折线,分别是红色、蓝色、黑色,平移一下再改下画笔颜色就行了。

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>绘制折线</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        context.moveTo(100,100);
        context.lineTo(300,300);
        context.lineTo(100,500);
        context.lineWidth = 5;
        context.strokeStyle = "red";
        context.stroke();

        context.moveTo(300,100);
        context.lineTo(500,300);
        context.lineTo(300,500);
        context.lineWidth = 5;
        context.strokeStyle = "blue";
        context.stroke();

        context.moveTo(500,100);
        context.lineTo(700,300);
        context.lineTo(500,500);
        context.lineWidth = 5;
        context.strokeStyle = "black";
        context.stroke();
    }
</script>
</body>
</html>
三条黑色的折线

Canvas是基于状态的绘制。这段代码每次使用stroke()时,它都会把之前设置的状态再绘制一遍。第一次stroke()时,绘制一条红色的折线;第二次stroke()时,会再重新绘制之前的那条红色的折线,但是这个时候的画笔已经被更换成蓝色的了,所以画出的折线全是蓝色的。换言之,strokeStyle属性被覆盖了。同理,第三次绘制的时候,画笔颜色是最后的黑色,所以会重新绘制三条黑色的折线。所以,这里看到的三条折线,其实绘制了3次,一共绘制了6条折线。

3.使用 beginPath() 开始绘制

为了让绘制方法不重复绘制,我们可以在每次绘制之前加上beginPath(),代表下次绘制的起始之处为beginPath()之后的代码。我们在三次绘制之前分别加上context.beginPath()。

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>绘制折线</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        context.beginPath();
        context.moveTo(100,100);
        context.lineTo(300,300);
        context.lineTo(100,500);
        context.lineWidth = 5;
        context.strokeStyle = "red";
        context.stroke();

        context.beginPath();
        context.moveTo(300,100);
        context.lineTo(500,300);
        context.lineTo(300,500);
        context.lineWidth = 5;
        context.strokeStyle = "blue";
        context.stroke();

        context.beginPath();
        context.moveTo(500,100);
        context.lineTo(700,300);
        context.lineTo(500,500);
        context.lineWidth = 5;
        context.strokeStyle = "black";
        context.stroke();
    }
</script>
</body>
</html>
三条颜色不一样的折线

四、绘制矩形

1.使用closePath()闭合图形

首先我们用上节方法绘制一个矩形。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>绘制矩形</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        context.beginPath();
        context.moveTo(150,50);
        context.lineTo(650,50);
        context.lineTo(650,550);
        context.lineTo(150,550);
        context.lineTo(150,50);     //绘制最后一笔使图像闭合
        context.lineWidth = 5;
        context.strokeStyle = "black";
        context.stroke();

    }
</script>
</body>
</html>
</body>
</html>
据说左上角会有缺口,为什么我没有?

据说左上角有一个缺口

这种情况是设置了lineWidth导致的。如果默认1笔触的话,是没有问题的。但是笔触越大,线条越宽,这种缺口就越明显。使用使用closePath()闭合图形来避免这种情况。

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>绘制矩形</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        context.beginPath();
        context.moveTo(150,50);
        context.lineTo(650,50);
        context.lineTo(650,550);
        context.lineTo(150,550);
        context.lineTo(150,50);     //最后一笔可以不画
        context.closePath();        //使用closePath()闭合图形

        context.lineWidth = 5;
        context.strokeStyle = "black";
        context.stroke();

    }
</script>
</body>
</html>
image.png
  • 每次开始绘制前都务必要使用beginPath(),为了代码的完整性,每次绘制结束后使用closePath()
  • 最后一笔可以不画出来,直接使用closePath(),它会自动帮你闭合的。
  • 如果你不想绘制闭合图形就不可以使用closePath()
2.给矩形上色
  • 这里我们要介绍一个和stroke()同等重要的方法fill()
  • 和当初描边一样,我们在填色之前,也要先用fillStyle属性选择要填充的颜色。
  • 比如我们要给上面的矩形涂上黄色,可以这样写。
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>绘制矩形</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        context.beginPath();
        context.moveTo(150,50);
        context.lineTo(650,50);
        context.lineTo(650,550);
        context.lineTo(150,550);
        context.lineTo(150,50);     //最后一笔可以不画
        context.closePath();        //使用closePath()闭合图形

        context.fillStyle = "yellow";   //选择油漆桶的颜色
        context.lineWidth = 5;
        context.strokeStyle = "black";

        context.fill();                 //确定填充
        context.stroke();

    }
</script>
</body>
</html>
中间填了黄色
3.封装绘制方法
  • 一个矩形可以由它的左上角坐标和其长宽唯一确定。
  • 绘制矩形其实都是这样的四笔,我们可以使用JavaScript封装这些方法。
  • 还需要知道线条的颜色,宽度,填充的颜色。
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>封装绘制矩形方法</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        drawRect(context, 150, 50, 50, 50, "red", 5, "blue");
        drawRect(context, 250, 50, 50, 50, "green", 5, "red");
        drawRect(context, 350, 50, 50, 50, "yellow", 5, "black");
    }

    function drawRect(cxt, x, y, width, height, fillColor, borderWidth, borderColor){
        cxt.beginPath();
        cxt.moveTo(x, y);
        cxt.lineTo(x + width, y);
        cxt.lineTo(x + width, y + height);
        cxt.lineTo(x, y + height);
        cxt.lineTo(x, y);
        cxt.closePath();

        cxt.lineWidth = borderWidth;
        cxt.strokeStyle = borderColor;
        cxt.fillStyle = fillColor;

        cxt.fill();
        cxt.stroke();
    }
</script>
</body>
</html>
三个矩形
4.使用rect()方法绘制矩形

由于绘制矩形是常用的方法,所以在Canvas API中已经帮我们封装好了一个绘制矩形的方法——rect()。这个方法接收4个参数x, y, width, height,实际调用时也就是:

context.rect(x, y, width, height);

基于此,我们帮刚才封装的方法优化一下:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>绘制魔性图形</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        context.beginPath();
        context.rect(0, 0, 800, 600);
        context.fillStyle = "#AA9033";

        context.fill();

        context.beginPath();
        for(var i=0; i<20; i++){
            drawWhiteRect(context, 200 + 10 * i, 100 + 10 * i, 400 - 20 * i, 400 - 20 * i);
            drawBlackRect(context, 205 + 10 * i, 105 + 10 * i, 390 - 20 * i, 390 - 20 * i);
        }

        context.beginPath();
        context.rect(395, 295, 5, 5);
        context.fillStyle = "black";
        context.lineWidth = 5;

        context.fill();
        context.stroke();
    }

    function drawBlackRect(cxt, x, y, width, height){
        cxt.beginPath();
        cxt.rect(x, y, width, height);

        cxt.lineWidth = 5;
        cxt.strokeStyle = "black";

        cxt.stroke();
    }

    function drawWhiteRect(cxt, x, y, width, height){
        cxt.beginPath();
        cxt.rect(x, y, width, height);

        cxt.lineWidth = 5;
        cxt.strokeStyle = "white";

        cxt.stroke();
    }
</script>
</body>
</html>
看多了会眼花

五、线条的属性

1.线条的帽子lineCap

lineCap 定义上下文中线的端点,可以有以下 3 个值:

  • butt:默认值,端点是垂直于线段边缘的平直边缘。
  • round:端点是在线段边缘处以线宽为直径的半圆。
  • square:端点是在选段边缘处以线宽为长、以一半线宽为宽的矩形。
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>线条的帽子</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        context.lineWidth = 30;
        context.strokeStyle = "#1BAAAA";

        context.beginPath();
        context.moveTo(100,100);
        context.lineTo(700,100);
        context.lineCap = "butt";
        context.stroke();

        context.beginPath();
        context.moveTo(100,300);
        context.lineTo(700,300);
        context.lineCap = "round";
        context.stroke();

        context.beginPath();
        context.moveTo(100,500);
        context.lineTo(700,500);
        context.lineCap = "square";
        context.stroke();

        //下面画两个基准线方便观察
        context.lineWidth = 3;
        context.strokeStyle = "black";

        context.beginPath();
        context.moveTo(100,0);
        context.lineTo(100,600);
        context.moveTo(700,0);
        context.lineTo(700,600);
        context.stroke();
    }
</script>
</body>
</html>
线宽为30
2.线条的连接lineJoin

可能有三个值:

  • miter,默认值,直接连接。
  • bevel,连接处为线宽的一半的矩形。
  • round,连接处为直径是线宽的圆。
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>线条的连接</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        context.beginPath();
        context.moveTo(100,100);
        context.lineTo(300,300);
        context.lineTo(100,500);
        context.lineJoin = "miter";
        context.lineWidth = 50;
        context.strokeStyle = "red";
        context.stroke();

        context.beginPath();
        context.moveTo(300,100);
        context.lineTo(500,300);
        context.lineTo(300,500);
        context.lineJoin = "bevel";
        context.lineWidth = 50;
        context.strokeStyle = "blue";
        context.stroke();

        context.beginPath();
        context.moveTo(500,100);
        context.lineTo(700,300);
        context.lineTo(500,500);
        context.lineJoin = "round";
        context.lineWidth = 50;
        context.strokeStyle = "black";
        context.stroke();
    }
</script>
</body>
</html>
线条连接处都不同
3.线宽lineWidth

lineWidth 定义线的宽度(默认值为 1.0)。

4.笔触样式strokeStyle

strokeStyle 定义线和形状边框的颜色和样式。

六、填充颜色

填充颜色主要分为两种:

  • 基本颜色
  • 渐变颜色(又分为线性渐变与径向渐变)
1.填充基本颜色

fillStyle属性用来设置画布上形状的基本颜色和填充。

context.fillStyle = "red";
2.填充渐变形状
  • 在画布上创建渐变填充有两个基本选项:线性或径向。
  • 线性渐变创建一个水平、垂直或者对角线的填充图案。
  • 径向渐变自中心点创建一个放射状填充。
  • 填充渐变形状分为三步:添加渐变线,为渐变线添加关键色,应用渐变。
2.1.线性渐变

三步走战略:

2.1.1.添加渐变线:
var grd = context.createLinearGradient(xstart,ystart,xend,yend);
2.1.2.为渐变线添加关键色(类似于颜色断点):
grd.addColorStop(stop,color);

这里的stop传递的是 0 ~ 1 的浮点数,代表断点到(xstart,ystart)的距离占整个渐变色长度是比例。

2.1.3.应用渐变:
context.fillStyle = grd;
context.strokeStyle = grd;
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>填充线性渐变</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        context.rect(200,100,400,400);

        //添加渐变线
        var grd = context.createLinearGradient(200,300,600,300);

        //添加颜色断点
        grd.addColorStop(0,"black");
        grd.addColorStop(0.5,"white");
        grd.addColorStop(1,"black");

        //应用渐变
        context.fillStyle = grd;

        context.fill();

    }
</script>
</body>
</html>
讲解图
2.2.绘制矩形的快捷方法

fillRect(x,y,width,height)strokeRect(x,y,width,height)这两个函数可以分别看做rect()fill()以及rect()stroke()的组合。
因为rect()仅仅只是规划路径而已,而这两个方法实实在在的绘制。

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>填充线性渐变</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        //添加渐变线
        var grd = context.createLinearGradient(100,300,700,300);

        //添加颜色断点
        grd.addColorStop(0,"olive");
        grd.addColorStop(0.25,"maroon");
        grd.addColorStop(0.5,"aqua");
        grd.addColorStop(0.75,"fuchsia");
        grd.addColorStop(0.25,"teal");

        //应用渐变
        context.fillStyle = grd;
        context.strokeStyle = grd;

        context.strokeRect(200,50,300,50);
        context.strokeRect(200,100,150,50);
        context.strokeRect(200,150,450,50);

        context.fillRect(200,300,300,50);
        context.fillRect(200,350,150,50);
        context.fillRect(200,400,450,50);

        context.fillRect(0,550,800,25);

    }
</script>
</body>
</html>
image.png

这两个页面都是水平渐变,但是要清楚线性渐变不一定是水平的,方向可以是任意的,通过渐变线的端点来设置方向。

2.3.径向渐变
  • 线性渐变是基于两个端点定义的,但是径向渐变是基于两个圆定义的。
  • 同样是三步走战略,只不过是第一步的所用方法变了。
2.3.1.添加渐变圆:
var grd = context.createRadialGradient(x0,y0,r0,x1,y1,r1);
2.3.2.为渐变线添加关键色(类似于颜色断点):
grd.addColorStop(stop,color);
2.3.3.应用渐变:
context.fillStyle = grd;
context.strokeStyle = grd;
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>填充径向渐变</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        //添加渐变线
        var grd = context.createRadialGradient(400,300,100,400,300,200);

        //添加颜色断点
        grd.addColorStop(0,"olive");
        grd.addColorStop(0.25,"maroon");
        grd.addColorStop(0.5,"aqua");
        grd.addColorStop(0.75,"fuchsia");
        grd.addColorStop(1,"teal");

        //应用渐变
        context.fillStyle = grd;

        context.fillRect(100,100,600,400);


    }
</script>
</body>
</html>
其实只有两个圆

createRadialGradient(x0,y0,r0,x1,y1,r1);方法规定了径向渐变开始和结束的范围,即两圆之间的渐变。

七、填充图案

1.createPattern()简介
  • 填充图案通过createPattern()函数进行初始化。
  • 它需要传进两个参数createPattern(img,repeat-style)
  • 第一个是Image对象实例,第二个参数是String类型,表示在形状中如何显示repeat图案。
  • createPattern()的第一个参数还可以传入一个canvas对象或者video对象。
  • 可以使用这个函数加载图像或者整个画布作为形状的填充图案。

有以下4种图像填充类型:

  • 平面上重复:repeat;
  • x轴上重复:repeat-x;
  • y轴上重复:repeat-y;
  • 不使用重复:no-repeat;
2.创建并填充图案

创建Image对象,为Image对象指定图片源:

var img = new Image();    //创建Image对象
img.src = "8-1.jpg";    //为Image对象指定图片源

填充纹理:

var pattern = context.createPattern(img,"repeat");
context.fillStyle = pattern;
下面代码用到的图
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>填充纹理</title>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas" style="border: 1px solid #aaaaaa; display: block; margin: 50px auto;">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");

        var img = new Image();
        img.src = "https://upload-images.jianshu.io/upload_images/7016617-395eecefc4dd4c5a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240";
        img.onload = function(){
            var pattern = context.createPattern(img, "repeat");
            context.fillStyle = pattern;
            context.fillRect(0,0,800,600);
        }

    }
</script>
</body>
</html>
铺满猪了

这里使用了Imageonload事件,它的作用是对图片进行预加载处理,即在图片加载完成后才立即除非其后function的代码体。这个是必须的,如果不写的话,画布将会显示黑屏。因为没有等待图片加载完成就填充纹理,导致浏览器找不到图片。

八、绘制标准圆弧

1.使用arc()绘制圆弧

arc()的使用方法如下:

context.arc(x,y,radius,startAngle,endAngle,anticlockwise)
  • 前面三个参数,分别是圆心坐标与圆半径。
  • startAngleendAngle使用的是弧度值,不是角度值。
  • anticlockwise表示绘制的方法,是顺时针还是逆时针绘制。它传入布尔值,true表示逆时针绘制,false表示顺时针绘制,缺省值为false。
  • 弧度的规定是绝对的,如下图:


    弧度值
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>圆</title>
    <style>
        #canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
    </style>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");
        context.fillStyle = "#FFF";
        context.fillRect(0,0,800,600);

        context.beginPath()
        context.arc(400, 300, 100, 0, 2*Math.PI, true)

        context.strokeStyle = "#0078AA";
        context.stroke();
    }
</script>
</body>
</html>
2.绘制圆角矩形

下面,我们结合基本路径和高级路径的知识,绘制一个圆角矩形。
圆角矩形是由四段线条和四个1/4圆弧组成,拆解如下:


圆角矩形的组成

因为我们要写的是函数而不是一个固定的圆角矩形,所以这里列出的是函数需要的参数。分析好之后,直接敲出代码:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>圆角矩形</title>
    <style>
        #canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
    </style>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");
        context.fillStyle = "#FFF";
        context.fillRect(0,0,800,600);

        drawRoundRect(context, 200, 100, 400, 400, 50);
        context.strokeStyle = "#0078AA";
        context.stroke();
    }

    function drawRoundRect(cxt, x, y, width, height, radius){
        cxt.beginPath();
        cxt.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2);
        cxt.lineTo(width - radius + x, y);
        cxt.arc(width - radius + x, radius + y, radius, Math.PI * 3 / 2, Math.PI * 2);
        cxt.lineTo(width + x, height + y - radius);
        cxt.arc(width - radius + x, height - radius + y, radius, 0, Math.PI * 1 / 2);
        cxt.lineTo(radius + x, height +y);
        cxt.arc(radius + x, height - radius + y, radius, Math.PI * 1 / 2, Math.PI);
        cxt.closePath();
    }
</script>
</body>
</html>
image.png

九、使用切点绘制圆弧

  • arcTo()方法接收5个参数,分别是两个切点的坐标和圆弧半径。这个方法是依据切线画弧线,即由两个切线确定一条弧线。 具体如下。
  • arcTo(x1,y1,x2,y2,radius)这个函数以给定的半径绘制一条弧线,圆弧的起点与当前路径的位置到(x1, y1)点的直线相切,圆弧的终点与(x1, y1)点到(x2, y2)的直线相切。因此其通常配合moveTo()lineTo()使用。
  • 其能力是可以被更为简单的arc()替代的,其复杂就复杂在绘制方法上使用了切点。
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>绘制弧线</title>
    <style>
        body { background: url("./images/bg3.jpg") repeat; }
        #canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
    </style>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");
        context.fillStyle = "#FFF";
        context.fillRect(0,0,800,600);

        drawArcTo(context, 200, 200, 600, 200, 600, 400, 100);
    };

    function drawArcTo(cxt, x0, y0, x1, y1, x2, y2, r){
        cxt.beginPath();
        cxt.moveTo(x0, y0);
        cxt.arcTo(x1, y1, x2, y2, r);

        cxt.lineWidth = 6;
        cxt.strokeStyle = "red";
        cxt.stroke();

        cxt.beginPath();
        cxt.moveTo(x0, y0);
        cxt.lineTo(x1, y1);
        cxt.lineTo(x2, y2);

        cxt.lineWidth = 1;
        cxt.strokeStyle = "#0088AA";
        cxt.stroke();
    }
</script>
</body>
</html>
结果
标注图

arcTo()绘制的起点是(x0, y0),但(x0, y0)不一定是圆弧的切点。真正的arcTo()函数只传入(x1, y1)(x2, y2)。其中(x1, y1)称为控制点,(x2, y2)是圆弧终点的切点,它不一定在圆弧上。但(x0, y0)一定在圆弧上。

十、二次贝塞尔曲线

二次贝塞尔曲线

Canvas里,二次贝塞尔曲线的方法如下:

context.quadraticCurveTo(cpx,cpy,x,y);
  • 这里和acrTo()有异曲同工之妙。
  • P0是起始点,所以通常搭配moveTo()lineTo()使用。
  • P1(cpx, cpy)是控制点,P2(x, y)是终止点,它们不是相切的关系。
  • 一个工具可以简单调试直至得到你想要的效果。在线转换器

十一、三次贝塞尔曲线

绘制三次贝塞尔曲线代码如下。

context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y);
  • 这个方法可谓是绘制波浪线的神器。
  • 根据之前的结论,n阶贝塞尔曲线就有n-1个控制点,所以三次贝塞尔曲线有1个起始点、1个终止点、2个控制点。
  • 因此传入的6个参数分别为控制点cp1 (cp1x, cp1y),控制点cp2 (cp2x, cp2y),与终止点 (x, y)
  • 这个方法也是不用大家去掌握参数具体是怎么填的,只要知道参数的意义就行。
  • quadraticCurveTo()方法一样,bezierCurveTo()的三次贝塞尔曲线网上也能找到互动的网页工具。
    这里提供一个网页:Canvas Bézier Curve Example,大家可以动手试一下。
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>XP壁纸</title>
    <style>
        body { background: url("./images/bg3.jpg") repeat; }
        #canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
    </style>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");
        context.fillStyle = "#FFF";
        context.fillRect(0,0,800,600);

        drawPrairie(context);
        drawSky(context);
        for(var i=0; i <5; i++){
            var x0 = 500 * Math.random() + 50;
            var y0 = 200 * Math.random() + 50;
            var c0 = 100 * Math.random() + 50;
            drawCloud(context, x0, y0, c0);

        }

    };

    function drawSky(cxt){
        cxt.save();

        cxt.beginPath();
        cxt.moveTo(0, 420);
        cxt.bezierCurveTo(250, 300, 350, 550, 800, 400);
        cxt.lineTo(800,0);
        cxt.lineTo(0,0);
        cxt.closePath();

        var lineStyle = cxt.createRadialGradient(400, 0, 50, 400, 0, 200);
        lineStyle .addColorStop(0, "#42A9AA");
        lineStyle .addColorStop(1, "#2491AA");

        cxt.fillStyle = lineStyle;

        cxt.fill();

        cxt.restore();
    }

    function drawPrairie(cxt){
        cxt.save();

        cxt.beginPath();
        cxt.moveTo(0, 420);
        cxt.bezierCurveTo(250, 300, 350, 550, 800, 400);
        cxt.lineTo(800,600);
        cxt.lineTo(0,600);
        cxt.closePath();

        var lineStyle = cxt.createLinearGradient(0, 600, 600, 0);
        lineStyle .addColorStop(0, "#00AA58");
        lineStyle .addColorStop(0.3, "#63AA7B");
        lineStyle .addColorStop(1, "#04AA00");

        cxt.fillStyle = lineStyle;
        cxt.fill();

        cxt.restore();
    }



    /*渲染单个云朵
     context:  canvas.getContext("2d")对象
     cx: 云朵X轴位置
     cy: 云朵Y轴位置
     cw: 云朵宽度
     */
    function drawCloud(cxt, cx, cy, cw) {
        //云朵移动范围即画布宽度
        var maxWidth = 800;
        //如果超过边界从头开始绘制
        cx = cx % maxWidth;
        //云朵高度为宽度的60%
        var ch = cw * 0.6;
        //开始绘制云朵

        cxt.beginPath();
        cxt.fillStyle = "white";
        //创建渐变
        var grd = cxt.createLinearGradient(0, 0, 0, cy);
        grd.addColorStop(0, 'rgba(255,255,255,0.8)');
        grd.addColorStop(1, 'rgba(255,255,255,0.5)');
        cxt.fillStyle = grd;

        //在不同位置创建5个圆拼接成云朵现状
        cxt.arc(cx, cy, cw * 0.19, 0, 360, false);
        cxt.arc(cx + cw * 0.08, cy - ch * 0.3, cw * 0.11, 0, 360, false);
        cxt.arc(cx + cw * 0.3, cy - ch * 0.25, cw * 0.25, 0, 360, false);
        cxt.arc(cx + cw * 0.6, cy, cw * 0.21, 0, 360, false);
        cxt.arc(cx + cw * 0.3, cy - ch * 0.1, cw * 0.28, 0, 360, false);
        cxt.closePath();

        cxt.fill();
    }
</script>
</body>
</html>
其实每次都不一样
保存和恢复Canvas状态

这里还使用到了两个新方法save()restore()。之前说过了canvas是基于状态的绘制.保存(推送)当前状态到堆栈,调用以下函数:

context.save();

调出最后存储的堆栈恢复画布,使用以下函数:

context.restore();

十二、图形变换

图形变换是指用数学方法调整所绘形状的物理属性,其实质是坐标变形。所有的变换都依赖于后台的数学矩阵运算,所以我们只要使用变换的功能即可,无需去理解这些运算。谈到图形变换,不得不得说的三个基本变换方法就是:

  • 平移变换:translate(x,y)
  • 旋转变换:rotate(deg)
  • 缩放变换:scale(sx,sy)
  • 其实坐标变形的本质是变换矩阵,所以在最后我们会谈一谈一个万能的变换方法——矩阵变换transform()。
1.平移变换translate()

平移变换,故名思议,就是一般的图形位移。
比如这里我想将位于(100,100)的矩形平移至(200,200)点。
那么我只要在绘制矩形之前加上context.translate(100,100)即可。

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>平移变换</title>
    <style>
        body { background: url("./images/bg3.jpg") repeat; }
        #canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
    </style>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");
        context.fillStyle = "#FFF";
        context.fillRect(0,0,800,600);

        context.fillStyle = "#00AAAA";
        context.fillRect(100,100,200,100);

        context.fillStyle = "red";
        context.translate(100,100);
        context.fillRect(100,100,200,100);

    };
</script>
</body>
</html>
为了更直观演示,宝绿色为红色平移前的位置

平移详解
注意使用状态保存

其实这里有一个坑,我们如果想把矩形平移至(300,300)怎么办呢?或许我们会想,直接调用context.translate(200,200)就可以了。好,我们看看效果。

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>平移变换</title>
    <style>
        body { background: url("./images/bg3.jpg") repeat; }
        #canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
    </style>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");
        context.fillStyle = "#FFF";
        context.fillRect(0,0,800,600);

        context.fillStyle = "#00AAAA";
        context.fillRect(100,100,200,100);

        context.fillStyle = "red";
        context.translate(100,100);
        context.fillRect(100,100,200,100);

        context.fillStyle = "green";
        context.translate(200,200);
        context.fillRect(100,100,200,100);

    };
</script>
</body>
</html>
有些出乎意料

这里的绿色矩形并没有如我们所愿在(300,300)位置处,而是跑到了(400,400)这里。为什么呢?想必大家已经知道了答案——Canvas是基于状态的绘制。在我们第一次平移之后,坐标系已经在(100,100)处了,所以如果继续平移,这个再基于新坐标系继续平移坐标系。那么要怎么去解决呢?很简单,有两个方法。

  • 第一,在每次使用完变换之后,记得将坐标系平移回原点,即调用translate(-x,-y)。
  • 第二,在每次平移之前使用context.save(),在每次绘制之后,使用context.restore()。
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>平移变换</title>
    <style>
        body { background: url("./images/bg3.jpg") repeat; }
        #canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
    </style>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");
        context.fillStyle = "#FFF";
        context.fillRect(0,0,800,600);

        context.fillStyle = "#00AAAA";
        context.fillRect(100,100,200,100);

        context.save();
        context.fillStyle = "red";
        context.translate(100,100);
        context.fillRect(100,100,200,100);
        context.restore();

        context.save();
        context.fillStyle = "green";
        context.translate(200,200);
        context.fillRect(100,100,200,100);
        context.restore();

    };
</script>
</body>
</html>
可以了
2.旋转变换rotate()
  • 同画圆弧一样,这里的rotate(deg)传入的参数是弧度,不是角度。
  • 同时需要注意的是,这个的旋转是以坐标系的原点(0,0)为圆心进行的顺时针旋转。
  • 所以,在使用rotate()之前,通常需要配合使用translate()平移坐标系,确定旋转的圆心。
    即,旋转变换通常搭配平移变换使用的。
  • 最后一点需要注意的是,Canvas是基于状态的绘制,所以每次旋转都是接着上次旋转的基础上继续旋转,所以在使用图形变换的时候必须搭配save()与restore()方法,一方面重置旋转角度,另一方面重置坐标系原点。
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>旋转变换</title>
    <style>
        body { background: url("./images/bg3.jpg") repeat; }
        #canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
    </style>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");
        context.fillStyle = "#FFF";
        context.fillRect(0,0,800,600);


        for(var i = 0; i <= 12; i++){
            context.save();
            context.translate(70 + i * 50, 50 + i * 40);
            context.fillStyle = "#00AAAA";
            context.fillRect(0,0,20,20);
            context.restore();

            context.save();
            context.translate(70 + i * 50, 50 + i * 40);
            context.rotate(i * 30 * Math.PI / 180);
            context.fillStyle = "red";
            context.fillRect(0,0,20,20);
            context.restore();
        }

    };
</script>
</body>
</html>
image.png

这里用for循环绘制了14对正方形,其中蓝色是旋转前的正方形,红色是旋转后的正方形。每次旋转都以正方形左上角顶点为原点进行旋转。每次绘制都被save()与restore()包裹起来,每次旋转前都移动了坐标系。

3.缩放变换scale()

缩放变换scale(sx,sy)传入两个参数,分别是水平方向和垂直方向上对象的缩放倍数。例如context.scale(2,2)就是对图像放大两倍。其实,看上去简单,实际用起来还是有一些问题的。我们来看一段代码。

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>缩放变换</title>
    <style>
        body { background: url("./images/bg3.jpg") repeat; }
        #canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
    </style>
</head>
<body>
<div id="canvas-warp">
    <canvas id="canvas">
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
    </canvas>
</div>

<script>
    window.onload = function(){
        var canvas = document.getElementById("canvas");
        canvas.width = 800;
        canvas.height = 600;
        var context = canvas.getContext("2d");
        context.fillStyle = "#FFF";
        context.fillRect(0,0,800,600);

        context.strokeStyle = "red";
        context.lineWidth = 5;
        for(var i = 1; i < 4; i++){
            context.save();
            context.scale(i,i);
            context.strokeRect(50,50,150,100);
            context.restore();
        }
    };
</script>
</body>
</html>
有点奇怪哦

看了上面的例子,大家一定对产生的结果有点奇怪。一是左上角顶点的坐标变了,而是线条的粗细也变了。因此,对于缩放变换有两点问题需要注意:

  • 缩放时,图像左上角坐标的位置也会对应缩放。
  • 缩放时,图像线条的粗细也会对应缩放。

十三、

参考资料

https://airingursb.gitbooks.io/canvas/02.htmls

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容