前端开发系列026-基础篇之Canvas绘图(曲线)

本文将介绍Canvas中的弧度、曲线、圆弧以及文字的绘制方法以及径向渐变等内容,并提供饼状图等综合案例。

一、Canvas中的弧度、曲线和圆弧

专业术语

夹角 从一个点发射(延伸)出两条线段,两条线相交的部分会构成一个夹角。

角度 两条相交直线中的任何一条与另一条相叠合时必须转动的量的量度,单位符号为°

周角 一条直线围绕起点需要与自己相叠合时必须转动的量的量度被称为周角,周角等分为360度。

弧度 角度量单位,弧长等于半径的弧所对圆心角为1弧度(弧长等于半径时射线夹角为1弧度)。

公式 弧度 = 角度 * π / 180

在使用JavaScript编写代码进行相关计算的时候,经常需要使用Math提供的成员,这里简单说明。

Math.PI 代表着π

Math.sin(弧度) 夹角对面的边 与 斜边的比值。

Math.cos(弧度) 夹角侧面的边 与 斜边的比值。

这里给出圆形上点坐标的计算公式,其中x0y0为圆心坐标,rad为弧度,R为圆的半径。

坐标 ( x0 + Math.cos(rad) x R , y0 + Math.sin(rad) x R )

核心API介绍

绘制圆弧

语法 ctx.arc(x,y,r,startAngle,endAngle,counterclockwise);

作用 通过该方法来绘制圆弧或者(半)圆。

参数

  • x 圆心X轴坐标
  • y 圆心Y轴坐标
  • r 圆的半径
  • startAngle                开始弧度
  • endAngle                  结束弧度
  • counterclockwise   是否逆时针旋转(默认为false)

绘制圆弧曲线

语法 ctx.arcTo(x1,y1,x2,y2,r);

作用 参考两个点并根据指定半径来创建一条圆弧路径。

备注 绘制的圆弧与当前点到第一个点的连线相切且与第一第二个点的连线也相切。

说明 arcTo方法的这些特性决定了该方法非常适合用来绘制圆角矩形。

参数

  • x1 第一个参考点的X轴坐标
  • y1 第一个参考点的Y轴坐标
  • x2 第二个参考点的X轴坐标
  • y3 第二个参考点的Y轴坐标
  • r    圆的半径

圆形渐变

语法 ctx.createRadialGradient(x0,y0,r0,x1,y1,r1);

作用 通过该方法来绘制圆弧或者(半)圆。

参数

  • x0 渐变开始圆的X轴坐标
  • y0 渐变开始圆的Y轴坐标
  • r0 开始圆的半径
  • x1 渐变结束圆的X轴坐标
  • y1 渐变结束圆的Y轴坐标
  • r1 结束圆的半径

二、Canvas曲线-圆弧绘制示例

数学方程绘制图形案例(一)
    var canvas  = document.getElementById("canvas");
    var ctx     = canvas.getContext("2d");

    //001 通过代数方程来绘制直线
    //设置路径(起点)
    ctx.moveTo(0,0);
    for(var x = 30,y = 0; x < 1000 ; x++)
    {
        //通过代数方程来绘制直线
        y = x / 2 * 0.3;
        ctx.lineTo(x,y);
    }
    //设置描边的颜色样式
    ctx.strokeStyle = "#0Af";
    //描边绘制出图案
    ctx.stroke();

    //002 通过三角函数来绘制曲线(正玄/余弦)
    ctx.beginPath();
    for(var x = 30,y = 0; x < 1000 ; x++)
    {   
        // 高度 * 波长 + 中心轴位置
        y = 50 * Math.sin(x/25) + 100;
        ctx.lineTo(x,y);
    }
    //设置描边的颜色样式
    ctx.strokeStyle = "red";
    //描边绘制出图案
    ctx.stroke();
数学方程绘制图形案例(二)
    var canvas  = document.getElementById("canvas");
    var ctx     = canvas.getContext("2d");
    function draw(offsetX,n){
        var dig = Math.PI / 15 * n;
        ctx.beginPath();
        for(var i = 0 ; i < 30 ; i++)
        {
            var x = Math.sin(i * dig);
            var y = Math.cos(i * dig);
            ctx.lineTo(offsetX + x * 80,150 + y * 80);
        }
        //闭合路径
        ctx.closePath();
        //设置样式并填充
        ctx.fillStyle = "#fff";
        ctx.fill();
        //设置样式并描边
        ctx.strokeStyle = "#666";
        ctx.stroke();
    }

    var data = [14,13,19,7,26];
    data.forEach(function(n,i){
        draw((i + 1) * 160,n);
    })
绘制相切曲线案例
    var canvas = document.getElementById("canvas");
    var ctx    = canvas.getContext("2d");

    var x0 = 100,y0 = 100,
        x1 = 500,y1 = 100,
        x2 = 450,y2 = 150;

    ctx.beginPath();
    ctx.moveTo(x0,y0);
    ctx.arcTo(x1,y1,x2,y2,30);
    ctx.strokeStyle = "red";
    ctx.stroke();

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

    ctx.fillText('x0,y0',x0,y0+10)
    ctx.fillText('x1,y1',x1+10,y1+10)
    ctx.fillText('x2,y2',x2+10,y2)
    ctx.strokeStyle = "#333";
    ctx.stroke();
绘制圆角矩形案例
    var canvas = document.getElementById("canvas");
    var ctx    = canvas.getContext("2d");

    function drawRoundedRect(x,y,w,h,r,isFill,isStrokeRect){
        ctx.beginPath();
        ctx.moveTo( x + r , y );
        ctx.arcTo(  x + w , y , x + w , y + h , r);
        ctx.arcTo(  x + w , y + h , x , y + h , r);
        ctx.arcTo(  x , y + h , x , y , r);
        ctx.arcTo(  x , y ,  x + r , y , r);

        if(isFill) {
            ctx.fillStyle = getRandomColor();
            ctx.fill();
        }else
        {
            ctx.strokeStyle = getRandomColor();
            ctx.stroke();
        }

       if(isStrokeRect)
        {
            ctx.beginPath();
            ctx.moveTo( x + r , y );
            ctx.lineTo(x + w  , y );
            ctx.lineTo(x + w  , y + h);
            ctx.lineTo(x  , y + h);
            ctx.lineTo(x  , y);
            ctx.lineTo(x + r  , y);

            ctx.fillStyle = "#000";
            ctx.fillText("x0,y0",x + r,y);
            ctx.fillText("x1,y1",x + w,y);
            ctx.fillText("x2,y2",x + w,y + h + 10);
            ctx.fillText("x3,y3",x-10,y + h + 10);
            ctx.fillText("x4,y4",x - 10,y-10);
            ctx.fillText("x5,y5",x + r,y + 10);
            ctx.stroke();
        }
    }

    drawRoundedRect(50,40,100,100,50,false,true);
    drawRoundedRect(200,40,100,100,50,true,false);
    drawRoundedRect(350,40,100,100,10,true);
    drawRoundedRect(500,40,100,100,20);
    drawRoundedRect(650,40,120,100,30,true);

    function getRandomColor(){
        var r = getRandom();
        var g = getRandom();
        var b = getRandom();
        return "rgb("+r+","+g+","+b+")";
    }

    function getRandom(){
        return Math.floor(Math.random() * 256);
    }
绘制弧线、扇形、圆弧和圆案例
    var canvas  = document.getElementById("canvas");
    var ctx     = canvas.getContext("2d");

    //绘制弧线
    ctx.arc(100,100,50,3/2 * Math.PI,2 * Math.PI,false);
    ctx.stroke();

    //绘制扇形
    ctx.beginPath();
    ctx.moveTo(200,100);
    ctx.arc(200,100,50,3/2 * Math.PI,2 * Math.PI,false);
    ctx.lineTo(200,100);
    ctx.strokeStyle = "#f09";
    ctx.stroke();

    //填充扇形
    ctx.beginPath();
    ctx.moveTo(300,100);
    ctx.arc(300,100,50,3.2/2 * Math.PI,3.8/2 * Math.PI,false);
    ctx.lineTo(300,100);
    ctx.fillStyle = "#195";
    ctx.fill();

    //绘制半圆
    ctx.beginPath();
    ctx.strokeStyle = "#666";
    ctx.arc(450,100,50,Math.PI,2 * Math.PI,false);
    ctx.closePath();
    ctx.stroke();

    //绘制圆形
    ctx.beginPath();
    ctx.strokeStyle = "#000";
    ctx.arc(570,80,40,0,2 * Math.PI,false);
    ctx.stroke();
绘制五环图案案例
    var canvas  = document.getElementById("canvas");
    var ctx     = canvas.getContext("2d");
    var x = 100;
    var y = 100;
    var r = 50;
    for(var i = 0 ; i < 5 ; i++)
    {   ctx.beginPath();
        if( i >=3)
        {
            ctx.arc(x + (i * 80) -200,y + 60,r,0,2*Math.PI,false);
        }else{
            ctx.arc(x + (i * 80),y,r,0,2*Math.PI,false);
        }
        ctx.strokeStyle = getRandomColor();
        ctx.stroke();
    }

    function getRandomColor(){
        var r = getRandom();
        var g = getRandom();
        var b = getRandom();
        return "rgb("+r+","+g+","+b+")";
    }
    function getRandom(){
        return Math.floor(Math.random() * 256);
    }
绘制等分的圆案例
    var canvas  = document.getElementById("canvas");
    var ctx     = canvas.getContext("2d");

    //描边
    drawCircle(100,100,40,2,true);
    drawCircle(200,100,40,3,true);
    drawCircle(300,100,40,4,true);
    drawCircle(400,100,40,20,true);
    drawCircle(500,100,40,100,true);
    drawCircle(600,100,40,200,true);

    //填充
    drawCircle(100,200,40,2);
    drawCircle(200,200,40,3);
    drawCircle(300,200,40,4);
    drawCircle(400,200,40,20);
    drawCircle(500,200,40,100);
    drawCircle(600,200,40,200);

    function drawCircle(x,y,r,n,isStroke){
        for(var  i = 0 ; i < n ; i++)
        {
            //计算开始和结束的角度
            var angle = 2 * Math.PI / n;
            var startAngle  = angle * i;
            var endAngle    = angle * (i + 1);

            //开始路径
            ctx.beginPath();

            //设置绘制圆的起点
            ctx.moveTo(x,y);
            ctx.arc(x,y,r,startAngle,endAngle,false);

            if(isStroke)
            {
                // ctx.strokeStyle = getRandomColor();
                ctx.stroke();
            }else
            {
                ctx.fillStyle = getRandomColor();
                ctx.fill();
            }
        }
    }
    //获取填充的颜色/随机
    function getRandomColor(){
        var r = getRandom();
        var g = getRandom();
        var b = getRandom();
        return "rgb("+r+","+g+","+b+")";
    }

    function getRandom(){
        return Math.floor(Math.random() * 256);
    }
绘制饼状图综合示例
    function PieChart (ctx){
        this.ctx        = ctx || document.getElementById("canvas").getContext("2d");
        this.x          = this.ctx.canvas.width/2 - 30;
        this.y          = this.ctx.canvas.height/2;
        this.r          = 120;
        this.outLine    = 20;
        this.dataList   = null;
    }

    PieChart.prototype = {
        constructor:PieChart,
        init:function(dataList){
            this.dataList = dataList || [{title:"默认",value:100}];
            
            //根据数据来计算并转换弧度
            this.transformAngle();

            //绘制饼状图
            this.drawPie();
        },
        drawPie:function(){

            var startAngle = 0,endAngle;

            for(var i = 0 ; i < this.dataList.length ; i++)
            {
                var item = this.dataList[i];
                endAngle = startAngle + item.angle;

                this.ctx.beginPath();
                this.ctx.moveTo(this.x,this.y);
                this.ctx.arc(this.x,this.y,this.r,startAngle,endAngle,false);
                var color= this.ctx.strokeStyle= this.ctx.fillStyle= this.getRandomColor();
                this.ctx.stroke();
                this.ctx.fill();

                //绘制标题
                this.drawPieTitle(startAngle,item.angle,color,item.title)
    
                //绘制图例
                this.drawPieLegend(i,item.title);
                startAngle = endAngle;
            }

        },
        drawPieTitle:function(startAngle,angle,color,title){

            var edge    = this.r + this.outLine;
            var edgeX   = Math.cos(startAngle + angle / 2) * edge; /*x轴方向的直角边*/
            var edgeY   = Math.sin(startAngle + angle / 2) * edge; /*y轴方向的直角边*/
            var outX    = this.x + edgeX;                          /*计算延伸出去的点坐标*/
            var outY    = this.y + edgeY;

            //画出坐标点
            this.ctx.beginPath();
            this.ctx.moveTo(this.x,this.y);
            this.ctx.lineTo(outX,outY);
            this.ctx.strokeStyle = color;
            this.ctx.stroke();

            //绘制文字下划线
            var textWidth   = this.ctx.measureText(title).width + 5;
            var lineX       = outX > this.x ? outX + textWidth : outX - textWidth;
            this.ctx.lineTo(lineX,outY);
            this.ctx.stroke();

            //绘制文字
            this.ctx.font           = "15px KaiTi";
            this.ctx.textAlign      = outX > this.x ? "left" : "right";
            this.ctx.textBaseline   = "bottom";
            this.ctx.fillText(title,outX,outY);
        },
        drawPieLegend:function(index,title){

            //在计算的时候最好的能够反着计算
            var space = 10;
            var rectW = 40;
            var rectH = 20;
            var rectX = this.x + this.r + 80;
            var rectY = this.y + (index * 30);
            //绘制矩形
            this.ctx.fillRect(rectX,rectY,rectW,rectH);
            // this.ctx.beginPath();
            // 绘制文字
            this.ctx.textAlign      = 'left';
            this.ctx.textBaseline   = 'top';
            this.ctx.fillStyle      = "#000";
            this.ctx.fillText(title,rectX + rectW + space,rectY);
        },
        getRandomColor:function(){
            var r = Math.floor(Math.random() * 256);
            var g = Math.floor(Math.random() * 256);
            var b = Math.floor(Math.random() * 256);
            return 'rgb('+r+','+g+','+b+')';
        },
        transformAngle:function(){
            var self    = this;
            var total   = 0;
            this.dataList.forEach(function(item,i){
                total += item.value;
            })
            this.dataList.forEach(function(item,i){
                self.dataList[i].angle = 2 * Math.PI * item.value/total;
            })
        },
    }
    var  data = [{value:20,title:"UI"},{value:26,title:"java"},
                {value:20,title:"iOS"},{value:63,title:"H5"},{value:25,title:"Node"}]
    var  pie  = new PieChart().init(data);

三、Canvas中的贝塞尔曲线

贝塞尔曲线(Bézier curve),最初由法国物理学家和数学家Paul de Casteljau发明,1962年被法国工程师皮埃尔·贝塞尔(Pierre Bézier)广泛发表并运用在汽车的车身设计上,现在多应用在计算机图形系统中。

贝塞尔曲线分为平方(quadratic)贝塞尔曲线和立方(cubic)贝塞尔曲线两其中平方贝塞尔曲线是一种二次曲线,由两个锚点和一个控制点总共三个点来定义,而立方贝塞尔曲线是一种三次曲线,由两个锚点和两个控制点共四个点来定义。它们的区别在于立方贝塞尔曲线能够在两个方向上弯曲。

Canvas支持两种贝塞尔曲线,分别由quadraticCurveTobezierCurveTo方法来实现。

二次贝塞尔曲线

语法 ctx.quadraticCurveTo(x0,y0,x1,y1);;

作用 通过使用表示二次贝塞尔曲线的指定控制点,向当前路径添加一个点绘制曲线。

参数

  • x0 控制点的X轴坐标
  • y0 控制点的Y轴坐标
  • x1 结束点(锚点)的X轴坐标
  • y1 结束点(锚点)的Y轴坐标

说明 二次贝塞尔曲线需要两个点。分别是用于二次贝塞尔计算中的控制点和曲线的结束点。

注意 曲线还需要一个开始点(路径最后的点)如果路径不存在,那么可以使用moveTo() 方法来定义。

三次贝塞尔曲线

语法 ctx.bezierCurveTo(x0,y0,x1,y1,x2,y2);;

作用 通过使用表示二次贝塞尔曲线的指定控制点,向当前路径添加一个点绘制曲线。

参数

  • x0 第一个控制点的X轴坐标
  • y0 第一个控制点的Y轴坐标
  • x1 第二个控制点的X轴坐标
  • y1 第二个控制点的Y轴坐标
  • x2 结束点(锚点)的X轴坐标
  • y2 结束点(锚点)的Y轴坐标

说明 三次贝塞尔曲线需要三个点,两个控制点和一个锚点。

注意 曲线还需要一个开始点(路径最后的点)如果路径不存在,那么可以使用moveTo() 方法来定义。

四、Canvas贝塞尔曲线绘制示例

二次贝塞尔曲线示例
    var canvas = document.getElementById("canvas");
    var ctx    = canvas.getContext("2d");

    //设置曲线的起点(当前路径的最后点没有则通过moveTo设置)
    ctx.moveTo(100,100);
    ctx.quadraticCurveTo(100,300,500,200);
    ctx.stroke();

    //绘制文字
    var margin = 15;
    ctx.fillText("(100,100)",100 - margin,100 - margin);
    ctx.fillText("(100,300)",100 - margin,300 + margin);
    ctx.fillText("(500,200)",500 - margin,200 + margin);

    //绘制线条
    ctx.beginPath();
    ctx.moveTo(100,100);
    ctx.lineTo(100,300);
    ctx.lineTo(500,200);
    ctx.strokeStyle = "red";
    ctx.stroke();

    //绘制点
    ctx.beginPath();
    ctx.arc(100,100,3,0,2 * Math.PI);
    ctx.arc(100,300,3,0,2 * Math.PI);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(500,200,3,0,2 * Math.PI);
    ctx.fill();
三次贝塞尔曲线示例
    var canvas = document.getElementById("canvas");
    var ctx    = canvas.getContext("2d");

    //设置曲线的起点(当前路径的最后点没有则通过moveTo设置)
    ctx.moveTo(100,100);
    ctx.bezierCurveTo(100,300,300,50,500,200);
    ctx.stroke();

    //绘制文字
    var margin = 15;
    ctx.fillText("起点 (100,100)",100 - margin,100 - margin);
    ctx.fillText("控制点 (100,300)",100 - margin,300 + margin);
    ctx.fillText("(300,50)",300 - margin,50 - margin);
    ctx.fillText("(500,200)",500 - margin,200 + margin);
    //绘制线条
    ctx.beginPath();
    ctx.moveTo(100,100);
    ctx.lineTo(100,300);
    ctx.lineTo(300,50);
    ctx.lineTo(500,200);
    ctx.strokeStyle = "red";
    ctx.stroke();

    //绘制点
    ctx.beginPath();
    ctx.arc(100,100,3,0,2 * Math.PI);
    ctx.arc(100,300,3,0,2 * Math.PI);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(300,50,3,0,2 * Math.PI);
    ctx.arc(500,200,3,0,2 * Math.PI);
    ctx.fill();
贝塞尔曲线复杂图形示例
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    //绘制贝塞尔曲线
    function drawBezierCurve(dx,dy,n){

        var s = 120;
        var x0,x1,x3,y1,y2,y3;
        var dig = Math.PI / 15 * n;

        ctx.beginPath();
        for(var i = 0 ; i < 30 ; i++)
        {
            var  X = Math.sin(i * dig);
            var  Y = Math.cos(i * dig);
            x0 = dx + X * s;
            x1 = dx + X * s + 100;
            x2 = dx + X * s;

            y0 = dy + Y * s - 100;
            y1 = dy + Y * s;
            y2 = dy + Y * s;
            ctx.bezierCurveTo(x0 , y0 , x1 , y1 , x2 , y2);
        }
        ctx.closePath();

        //绘制和填充
        ctx.fillStyle   = "#eee";
        ctx.strokeStyle = "red";
        ctx.fill();
        ctx.stroke();
    }

    drawBezierCurve(150,250,13);
    drawBezierCurve(480,250,24);
    drawBezierCurve(820,250,31);

posted on 2022-12-10 20:01  文顶顶  阅读(781)  评论(0编辑  收藏  举报

导航