好玩的Canvas 学习:曲线图形的绘制(贝塞尔曲线的妙用)

直线图形和曲线图形是Canvas中的两大基本图形,《好玩的Canvas:直线(线段)、图形的绘制》已经介绍了直线图形的绘制,本文将讲述如何在Canvas中绘制曲线图形。

初中数学里面的曲线定义如下:

任何一根连续的线条都称为曲线,包括直线、折线、线段、圆弧等。

弧线是圆周的一部分,且每个点曲率相同,曲线包含弧线。本文讲述的曲线主要是常规的曲线和特殊的曲线——弧线,包含圆形绘制、弧线绘制、贝塞尔曲线绘制。

Canvas圆形的绘制

描边圆绘制

canvas arc
在Canvas中,使用arc()方法来绘制圆形,代码如下

cxt.beginPath();
cxt.arc(x,y,radius,startAngle,endAngle,anticlockwise);
cxt.closePath();

解释:

  • x和y是圆心坐标,也就是图中O(x0,y0);
  • radius是圆的半径;
  • startAngle和endAngle是图中起始角和结束角,单位为弧度;
  • anticlockwise指绘制的方向,是一个布尔值,true代表逆时针方向绘制,false代表顺时针方向绘制,默认是false,顺时针方向绘制;

注意:
需要先调用beginPath()方法来开始一个新路径,然后才可以开始绘制,绘制完成之后,还需要调用closePath()方法来关闭当前路径。

下面是一段代码示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>圆形的绘制</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
    </body>
    <script>
        function get_dom(id){
            return document.getElementById(id);
        }

        var canvas = get_dom('canvas');
        var cxt = canvas.getContext("2d");

        cxt.beginPath();
        cxt.arc(150,150,100,0*Math.PI/180,360*Math.PI/180,false);
        cxt.closePath();
        cxt.stroke();
    </script>
</html>

效果如下:
canvas画圆

上面这个示例画的圆就采用stroke()方法进行描边了,我们还可以给边设置属性,代码如下:

cxt.strokeStyle = "颜色值";

下面是一个代码示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>圆形的绘制</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
    </body>
    <script>
        function get_dom(id){
            return document.getElementById(id);
        }

        var canvas = get_dom('canvas');
        var cxt = canvas.getContext("2d");

        cxt.beginPath();
        cxt.arc(150,150,100,0*Math.PI/180,360*Math.PI/180,false);
        cxt.closePath();
        cxt.stroke();

        cxt.beginPath();
        cxt.arc(450,150,100,0*Math.PI/180,360*Math.PI/180,false);
        cxt.closePath();
        cxt.strokeStyle="green";
        cxt.stroke();

        //1/4圆 逆时针方向绘制
        cxt.beginPath();
        cxt.arc(150,350,100,0*Math.PI/180,45*Math.PI/180,false);
        cxt.closePath();
        cxt.strokeStyle="green";
        cxt.stroke();

        //1/4圆顺时针方向绘制变成 3/4圆
        cxt.beginPath();
        cxt.arc(450,350,100,0*Math.PI/180,45*Math.PI/180,true);
        cxt.closePath();
        cxt.strokeStyle="green";
        cxt.stroke();
    </script>
</html>

效果如下:
canvas画圆

填充圆绘制

在Canvas中,采用fill()方法来绘制填充圆。

cxt.beginPath();
cxt.arc(x,y,radius,startAngle,endAngle,anticlockwise);
cxt.closePath();
cxt.fillStyle = "颜色值";
cxt.fill();

下面是一个代码示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>圆形的绘制</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
    </body>
    <script>
        function get_dom(id){
            return document.getElementById(id);
        }

        var canvas = get_dom('canvas');
        var cxt = canvas.getContext("2d");

        //描边圆
        cxt.beginPath();
        cxt.arc(150,150,100,0*Math.PI/180,360*Math.PI/180,false);
        cxt.closePath();
        cxt.stroke();

        //绿色边描边圆
        cxt.beginPath();
        cxt.arc(250,150,100,0*Math.PI/180,360*Math.PI/180,false);
        cxt.closePath();
        cxt.strokeStyle="green";
        cxt.stroke();

        //红色填充圆
        cxt.beginPath();
        cxt.arc(350,150,100,0*Math.PI/180,360*Math.PI/180,false);
        cxt.closePath();
        cxt.fillStyle="red";
        cxt.fill();

        //绿色填充圆
        cxt.beginPath();
        cxt.arc(450,150,100,0*Math.PI/180,360*Math.PI/180,false);
        cxt.closePath();
        cxt.fillStyle="green";
        cxt.fill();

        //蓝色描边 绿色填充圆(先填充后描边,不然样式会被覆盖)
        cxt.beginPath();
        cxt.arc(550,150,100,0*Math.PI/180,360*Math.PI/180,false);
        cxt.closePath();
        cxt.fillStyle="green";
        cxt.fill();
        cxt.strokeStyle = "blue";
        cxt.stroke();

        //1/4圆 逆时针方向绘制 
        cxt.beginPath();
        cxt.arc(150,350,100,0*Math.PI/180,45*Math.PI/180,false);
        cxt.closePath();
        cxt.fillStyle="blue";
        cxt.fill();
    </script>
</html>

效果如下:
canvas绘制圆形

注意:
后面的操作呈现的样式会覆盖前面的操作呈现的样式。

弧线的绘制

arc()绘制弧线

前面我们采用arc()绘制了圆形和1/4圆,代码最后一行有closePath(),将这一行代码去掉就变成了圆弧,因为圆弧不是闭合路径,closePath()方法的作用是关闭路径、连接起点与终点。一个简单示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>弧线的绘制</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
    </body>
    <script>
        function get_dom(id){
            return document.getElementById(id);
        }

        var canvas = get_dom('canvas');
        var cxt = canvas.getContext("2d");

        //1/4圆 逆时针方向绘制 
        cxt.beginPath();
        cxt.arc(150,350,100,0*Math.PI/180,45*Math.PI/180,false);
        // cxt.closePath();
        cxt.stroke();
    </script>
</html>

效果如下:
canvas画弧线

stroke()fill()只能绘制当前路径,不能同时绘制几个路径,下面是一个示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>弧线的绘制</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
    </body>
    <script>
        function get_dom(id) {
            return document.getElementById(id);
        }

        var canvas = get_dom('canvas');
        var cxt = canvas.getContext("2d");

        //情况1:
        // 先绘制直线
        cxt.moveTo(250, 50);
        cxt.lineTo(250, 350);
        // cxt.stroke();//画完直线不使用stroke()
        //再逆时针方向绘制 1/4圆 
        cxt.beginPath();
        cxt.arc(150, 350, 100, 0 * Math.PI / 180, 45 * Math.PI / 180, false);
        cxt.stroke();

        //情况2:
        // 先绘制绘制直线
        cxt.moveTo(300, 50);
        cxt.lineTo(300, 350);
        cxt.stroke(); //画完直线使用stroke()
        //再逆时针方向绘制 1/4圆 
        cxt.beginPath();
        cxt.arc(200, 350, 100, 0 * Math.PI / 180, 45 * Math.PI / 180, false);
        cxt.stroke();
    </script>
</html>

效果如下:
stroke

可以看出stroke()fill()只能绘制当前路径,不能同时绘制几个路径。

arcTo()方法绘制弧线

除了使用arc()方法可以绘制弧线外,我们还可以使用arcTo()方法绘制弧线。
arcTO()

cxt.arcTo(cx,cy,x2,y2,radius);

解释:

  • cx,cy是图中控制点坐标(cx,cy);
  • x2,y2是图中结束点坐标(x2,y2);
  • radius是图中圆O的半径;

开始点、控制点、结束点这三个点组成两条如图中直线,两条直线夹一个角,在这个角中做与两条直线相切的圆,所夹弧即为绘制的圆弧。此时会有一个问题,圆的半径不同,那么两个切点位置也不同,所以切点不一定就是开始点和控制点,也就是说切点在两条线段延长线上

细心的朋友还会发现上面语法中没有开始点的坐标,那么开始点由谁来设置呢?

答案很简单,一般由moveTo()lineTo()提供开始点,下面是一个示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>弧线的绘制</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
    </body>
    <script>
        function get_dom(id) {
            return document.getElementById(id);
        }

        var canvas = get_dom('canvas');
        var cxt = canvas.getContext("2d");

        //情况1:切点正好与开始点和结束点重合
        cxt.moveTo(20,40);
        cxt.arcTo(40,40,40,60,20);
        cxt.stroke();

        //情况2:切点与开始点和结束点不重合
        cxt.moveTo(20,40);
        cxt.arcTo(90,40,90,60,20);
        cxt.stroke();

        //情况3:直线弧线结合做出情况2效果
        cxt.moveTo(120,40);
        cxt.lineTo(140,40);
        cxt.arcTo(160,40,160,60,20);
        cxt.stroke();
    </script>
</html>

效果如下:
arcTo()

从情况2和情况3对比可以看出如果切点和开始点不重合时,arcTo()方法还将添加一条当前切点到开始点的直线线段。

圆角按钮的绘制

canvas
上面是一个示例,代码如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>圆角按钮的绘制</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
    </body>
    <script>
        function get_dom(id) {
            return document.getElementById(id);
        }

        function create_button(cxt,x,y,w,h,r,fillStyle=""){
            cxt.beginPath();
            cxt.moveTo(x+r,y);
            cxt.lineTo(x+w-r,y);
            cxt.arcTo(x+w,y,x+w,y+r,r);

            cxt.lineTo(x+w,y+h-r);
            cxt.arcTo(x+w,y+h,x+w-r,y+h,r);

            cxt.lineTo(x+r,y+h);
            cxt.arcTo(x,y+h,x,y+h-r,r);

            cxt.lineTo(x,y+r);
            cxt.arcTo(x,y,x+r,y,r);
            if(fillStyle){
                cxt.fillStyle = fillStyle;
                cxt.fill();
            }else{
                cxt.stroke();
            }
        }

        var canvas = get_dom('canvas');
        var cxt = canvas.getContext("2d");
        create_button(cxt,5,5,100,50,10);
        create_button(cxt,120,5,100,50,10,"red");
    </script>
</html>

效果如下:
canvas画按钮

绘制贝塞尔曲线

前面已经介绍如何使用arc()arcTo()绘制圆形和圆弧,接下来介绍一下如何使用Canvas绘制曲线,说到曲线,做前端开发的都应该听说的贝塞尔曲线的大名,这里也只介绍一下贝塞尔曲线的用法。

贝塞尔曲线简介

贝塞尔曲线又叫贝兹曲线、贝济埃曲线,是计算机图形学中非常重要的曲线,前端开发中可应用于轨迹的绘制以及定义动画曲线。

贝塞尔曲线原理

贝塞尔曲线是由n个点来决定,其曲线轨迹公式为:
贝塞尔曲线公式

n代表贝塞尔曲线是几阶曲线。
详细原理可以参考下面给出的链接:

https://baike.baidu.com/item/%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF/1091769?fr=aladdin
https://segmentfault.com/a/1190000018502700#item-1

二阶贝塞尔曲线绘制

和前面一样,二阶贝塞尔曲线也需要开始点、控制点、结束点,但是不需要半径,具体如何绘制出来的可以参考上面发的链接,还可以看看photoshop的钢笔工具。

cxt.quadraticCurveTo(cx,cy,x2,y2);

解释:

  • cx,cy是控制点的坐标;
  • x2,y2是结束点坐标;
  • 起始点坐标由moveTo()lineTo()确定;

下面是一段代码示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>二阶贝塞尔曲线的绘制</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
    </body>
    <script>
        function get_dom(id) {
            return document.getElementById(id);
        }

        var canvas = get_dom('canvas');
        var cxt = canvas.getContext("2d");
        cxt.moveTo(50,50);
        cxt.quadraticCurveTo(50,100,100,50);
        cxt.stroke();
    </script>
</html>

效果如下:

二阶贝塞尔曲线绘制

手工计算然后再采用贝塞尔曲线绘制我们想要的图形有点小难,我们可以用下面工具辅助设计:

演示:http://wx.karlew.com/canvas/bezier/

源码:https://github.com/karlew/bezier-tool-for-canvas

下面是一个用贝塞尔曲线绘制云朵的案例,首先打开演示地址,点击upload上传效果图图片,根据图片线条,手动匹配出相应的贝塞尔曲线。

卡通云朵

将上面这张云朵图片上传,然后手动调整贝塞尔曲线匹配轮廓,这个工具的一些使用技巧就不在此处阐述。

贝塞尔曲线辅助设计

完整代码如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>二阶贝塞尔曲线的绘制</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
    </body>
    <script>
        function get_dom(id) {
            return document.getElementById(id);
        }

        var canvas = get_dom('canvas');
        var cxt = canvas.getContext("2d");
        // cxt.lineWidth = 10;//设置线条宽度
        cxt.beginPath();
        cxt.moveTo(79, 143);
        cxt.quadraticCurveTo(90, 18, 214, 84);

        cxt.moveTo(352, 91);
        cxt.quadraticCurveTo(284, 18, 214, 84);

        cxt.moveTo(352, 91);
        cxt.quadraticCurveTo(541, 115, 399, 204);

        cxt.moveTo(283, 239);
        cxt.quadraticCurveTo(359, 292, 399, 204);

        cxt.moveTo(283, 239);
        cxt.quadraticCurveTo(222, 285, 158, 243);

        cxt.moveTo(79, 143); //与前面形成闭合
        cxt.quadraticCurveTo(13, 243, 158, 243);

        cxt.stroke();
    </script>
</html>

效果如下:

贝塞尔曲线绘制的云朵

三阶贝塞尔曲线

cxt.bezierCurveTo(cx1,cy1,cx2,cy2,x,y);

三阶贝塞尔曲线有两个控制点,下面是一个简单demo:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>三阶贝塞尔曲线的绘制</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
    </body>
    <script>
        function get_dom(id) {
            return document.getElementById(id);
        }

        var canvas = get_dom('canvas');
        var cxt = canvas.getContext("2d");
        cxt.beginPath();
        cxt.moveTo(30,30);
        cxt.bezierCurveTo(40,240,840,500,280,365);
        cxt.stroke();
    </script>
</html>

效果如下:
三阶贝塞尔曲线绘制的图形

下面还是使用前面的辅助设计工具,绘制一个爱心,爱心素材如下:
爱心
在工具中去获取坐标点:
贝塞尔曲线

代码如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>三阶贝塞尔曲线绘制爱心</title>
    </head>
    <body>
        <canvas id="canvas" width="800" height="800"></canvas>
    </body>
    <script>
        function get_dom(id) {
            return document.getElementById(id);
        }

        var canvas = get_dom('canvas');
        var cxt = canvas.getContext("2d");
        cxt.beginPath();
        cxt.moveTo(245, 383);
        cxt.bezierCurveTo(610, 144, 412, -7, 250, 84);

        cxt.moveTo(245, 383);
        cxt.bezierCurveTo(-3, 206, 11, 3, 250, 84);

        cxt.fillStyle = "red";
        cxt.fill();
    </script>
</html>

效果如下:

贝塞尔曲线绘制的爱心

总结

理论上可以利用Canvas的曲线绘制各种图形,也可以利用曲线绘制一些图表。

© 版权声明
THE END
请我喝怡宝
点赞5
分享
评论 共1条
    • 人间不值得
    • 伍子蛇0

      分析得很好,很赞

      6月前回复