1. 程式人生 > 其它 >前端開發系列026-基礎篇之Canvas繪圖(曲線)

前端開發系列026-基礎篇之Canvas繪圖(曲線)

title: '前端開發系列026-基礎篇之Canvas繪圖(曲線)'
tags:
  - javaScript系列
categories: []
date: 2017-07-23 08:20:13
本文將介紹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);