• javaScript系列 [11]Canvas绘图(路径)


    本文将介绍Canvas中的路径、矩形以及描边和填充等绘制方法,非零正交原则以及线性渐变等内容,并提供折线图和柱状图等综合案例。

    Canvas路径和状态

    **核心API介绍**

    设置绘制的起点

    语法 ctx.moveTo(x, y);
    参数 第一个参数和第二个参数都是相对于Canvas画布左上角的X轴和Y轴坐标。
    作用 设置Canvas上下文绘制路径的起点,相当于设置画笔从哪个位置开始移动。
    注意 使用Canvas上下文绘制路径前必须先设置起点,否则绘制无效。

    设置绘制目标点

    语法 ctx.lineTo(x, y);
    参数 第一个参数和第二个参数都是相对于Canvas画布左上角的X轴和Y轴坐标。
    作用 设置Canvas上下文绘制路径的目标点,相当于设置画笔移动的目标位置。

    设置描边

    语法 ctx.stroke();
    作用 根据路径来绘制(描边),可以在绘制前通过strokeStyle来设置描边样式。

    设置填充

    语法 ctx.fill();
    作用 对闭合路径的内容进行绘制(填充),可以通过fillStyle来设置样式,默认黑色。
    参数 fill方法有两个可选参数(nonzero | evenodd) ,控制填充时使用环绕原则(默认)或奇偶原则。

    矩形路径

    语法 ctx.rect(x, y, width, height);
    参数 第一个参数和第二个参数都是矩形左上角坐标的X和Y轴坐标,第三和第四个参数为矩形的宽高。
    注意 rect方法只是规划了矩形的路径,并没有填充和描边,因此还需要搭配strokefill使用。

    描边矩形

    语法 ctx.strokeRect(x, y, width, height);
    参数 第一个参数和第二个参数都是矩形左上角坐标的X和Y轴坐标,第三和第四个参数为矩形的宽高。
    作用 该方法绘制完矩形路径后立即进行stroke描边绘制,等价于rect + stroke组合。

    填充矩形

    语法 ctx.fillRect(x, y, width, height);
    参数 第一个参数和第二个参数都是矩形左上角坐标的X和Y轴坐标,第三和第四个参数为矩形的宽高。
    作用 该方法绘制完矩形路径后立即进行fill填充绘制,等价于rect + fill方法的组合。

    矩形擦除

    语法 ctx.clearRect(x, y, width, hegiht);
    参数 第一个参数和第二个参数都是矩形左上角坐标的X和Y轴坐标,第三和第四个参数为矩形的宽高。
    作用 该方法用于擦除指定矩形内绘制的内容,需注意如果重置画布宽度,内容将自动重绘。

    开始和闭合路径

    语法 ctx.beginPath();ctx.closePath();
    作用 开始路径的作用是将不同的绘制路径进行隔离,闭合路径会自动连接最开始和最后的点。
    注意 执行开始路径方法时表示将要重新绘制一个新的路径,可以分开设置和管理多个路径的样式。

    路径的核心属性介绍

    lineCap 设置或返回线条末端线帽样式,可选值有butt默认|round圆形|square正方形
    lineJoin设置或返回所创建边角的类型,可选值有miter默认|round圆角|bevel斜角
    lineWidth设置或返回当前线条的宽度,以像素计,默认值为1。
    miterLimit设置或返回最大斜接长度。
    fillStyle设置或返回用于填充绘画的颜色、渐变或模式。
    strokeStyle设置或返回用于笔触(描边)的颜色、渐变或模式。
    shadowColor设置或返回用于阴影的颜色,和shadowBlur一起使用。
    shadowBlur设置或返回用于阴影的模糊级别,和shadowColor一起使用。
    shadowOffsetX设置或返回阴影距形状的水平距离,0指示阴影位于形状的正下方。
    shadowOffsetY设置或返回阴影距形状的垂直距离,0指示阴影位于形状的正下方。

    Canvas路径绘制示例

    路径绘制Demo
    `Demo-1 绘制交叉和平行线` ![](https://img2020.cnblogs.com/blog/450136/202201/450136-20220101164522189-668228212.png) ```javaScript //01 绘制一条直线 var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d");
    ctx.moveTo(20,20.5);
    ctx.lineTo(200,20.5);
    ctx.strokeStyle = "#195";   //设置描边样式
    ctx.stroke();
    
    //02 绘制两条平行线
    ctx.moveTo(20.5,40);        //设置起点
    ctx.lineTo(20.5,120);       //设置目标点
    
    ctx.moveTo(40.5,40);        //设置起点
    ctx.lineTo(40.5,120);       //设置目标点
    ctx.stroke();               //绘制路径(描边)
    
    //03 绘制两条交叉线条
    ctx.moveTo(60,60);          //设置起点
    ctx.lineTo(100,100);        //设置目标点
    ctx.moveTo(100,60);         //设置起点
    ctx.lineTo(60,100);         //设置目标点
    ctx.stroke();               //绘制路径(描边) 
    
    **`Demo-2 开始路径和闭合路径`**
    ![](https://img2020.cnblogs.com/blog/450136/202201/450136-20220101164538226-586793573.png)
    ```javaScript
        var canvas = document.getElementById("canvas");
        var ctx    = canvas.getContext("2d");
    
        //001 绘制两条交叉的线(演示beginPath方法的使用)
        //(1) 设置并绘制第一条线
        ctx.moveTo(50,50);
        ctx.lineTo(150,150);
        ctx.stroke();
    
        //(2) 设置并绘制第二条线
        ctx.beginPath();         //重新开启路径
        ctx.moveTo(50,150);
        ctx.lineTo(150,50);
        //设置线条和描边的样式
        ctx.strokeStyle = "red";
        ctx.stroke();
    
        //002 绘制两条相接的线(演示closePath方法的使用)
        ctx.beginPath();        //重新开启路径
        ctx.moveTo(180.5,20);
        ctx.lineTo(180.5,180.5);
        ctx.lineTo(260,180.5);
        
        //设置关闭路径(自动连接两个点闭合以构成封闭区域)
        ctx.closePath();
        ctx.strokeStyle = "blue";
        ctx.stroke();
        
        //设置图形填充和样式
        ctx.fillStyle = "#eee";
        ctx.fill();
    

    Demo-3 绘制虚线的N种方式

        var canvas = document.getElementById("canvas");
        var ctx    = canvas.getContext("2d");
        
        //001 绘制虚线的第一种方式(通过fillRect矩形绘制API)
        for(var i = 0 ; i < 100 ; i++)
        {
            //第一个参数:矩形的起点X
            //第二个参数:矩形的起点Y
            //第三个参数:矩形的宽度
            //第四个参数:矩形的高度
            ctx.fillRect((i *2),30,1,20);
            ctx.fillRect((i *2),70,1,1);
            ctx.fillRect((i *5),100,1,1);
        }
    
        //002 绘制虚线的第二种方式(通过路径和setLineDash绘制API)
        ctx.moveTo(0,130.5);
        ctx.lineTo(200,130.5);
        ctx.setLineDash([5]);
        ctx.stroke();
    
        //开启路径绘制另一条虚线
        ctx.beginPath();
        ctx.moveTo(0,160.5);
        ctx.lineTo(200,160.5);
        //参数说明[第一段的长度、第二段的长度、第三段的长度 * 重复]
        ctx.setLineDash([5,10,15]);
        ctx.strokeStyle = "red";
        ctx.stroke();
        //获取虚线的排列方式(不重复那段的排列方式)
        console.log(ctx.getLineDash()); //[5, 10, 15, 5, 10, 15]
    
        //开启路径绘制另一条虚线(偏移量参照)
        ctx.beginPath();
        ctx.moveTo(0,180.5);
        ctx.lineTo(200,180.5);
        //设置虚线的偏移量
        ctx.lineDashOffset = -30;
        ctx.setLineDash([5,10,15]);
        ctx.strokeStyle = "red";
        ctx.stroke();
    

    Demo-4 绘制实心三角形和矩形(四边形)

        var canvas = document.getElementById("canvas");
        var ctx    = canvas.getContext("2d");
       
        //001 使用路径的方式绘制三角(边)形
        ctx.moveTo(20,20);
        ctx.lineTo(100,20);
        ctx.lineTo(80,100);
        ctx.closePath();
        // ctx.lineTo(20,20);
    
        //绘制(填充)
        ctx.fillStyle = "#195";
        ctx.fill();
        
        //绘制(描边)
        // ctx.stroke();
    
        //002 使用路径的方式绘制四角(边)形
        //备注:如果是填充的话,那么只需要四个点的坐标即可确定
        ctx.beginPath();
        ctx.moveTo(150,20);
        ctx.lineTo(350,20);
        ctx.lineTo(350,100);
        ctx.lineTo(150,100);
        ctx.lineTo(150,20);
        // ctx.closePath();
       
        //绘制(描边)
        // ctx.strokeStyle = "red";
        // ctx.stroke();
        
        //绘制(填充)
        ctx.fillStyle = "blue";
        ctx.fill();
    

    Demo-5 绘制矩形API使用示例

        var canvas = document.getElementById("canvas");
        var ctx    = canvas.getContext("2d");
    
        //绘制矩形API介绍
        //001 使用rect + stroke|fill 方法绘制矩形(非独立路径)
        //第一个参数:矩形左上角X
        //第二个参数:矩形左上角Y
        //第三个参数:矩形的宽度W
        //第四个参数:矩形的高度H
        ctx.rect(20,20,300,100);
        ctx.stroke();                   //绘制(描边)
        // ctx.fill();                  //绘制(填充)
    
        //002 使用fillRect绘制(独立路径)
        ctx.fillStyle = "green";        //设置填充颜色
        ctx.fillRect(20,150,200,40);
    
        //003 使用strokeRect绘制(独立路径)
        ctx.strokeStyle = "red";        //设置描边颜色
        ctx.strokeRect(20,210,200,50);
    
        //004 擦除画布
        //ctx.clearRect(20,150,50,40);
    
    **柱状图综合案例**
    ![](https://img2020.cnblogs.com/blog/450136/202201/450136-20220101164651400-758553549.png) ```javaScript //绘制柱状图的构造函数 var RectChart = function(ctx){ this.rects = null; this.ctx = ctx || document.getElementById("canvas").getContext("2d");
        //设置计算参数
        this.m      = 10;
        this.w      = this.ctx.canvas.width;
        this.h      = this.ctx.canvas.height;
        this.cols   = Math.floor(this.w / this.m);
        this.rows   = Math.floor(this.h / this.m);
        this.pointW = 6;
        this.x      = 50;
        this.y      = 350;
        this.rectW  = 40;
    }
    
    //设置原型对象
    RectChart.prototype = {
        constructor:RectChart,
        init:function(rects){
            this.rects = rects;
            this.drawGrid();
            this.drawAxis();
            this.drawRect();
        },
        drawGrid:function(){
            //002 设置路径
            //[1] 绘制所有的行
            for(var i = 1 ; i < this.rows ; i++)
            {
                this.ctx.moveTo(0,(i * this.m)+0.5);
                this.ctx.lineTo(this.w,(i * this.m)+0.5);
            }
    
            //[2] 绘制所有的列
            for(var j = 1 ; j < this.cols ; j++)
            {
                this.ctx.moveTo((j * this.m) + 0.5,0);
                this.ctx.lineTo((j * this.m) + 0.5,this.h);
            }
    
            //003 绘制网格
            this.ctx.strokeStyle = "#ddd";
            this.ctx.stroke();
        },
        drawAxis:function(){
            //004 绘制坐标(横坐标和纵坐标 X-Y)
            var x = this.x,
                y = this.y,
                xl = 650,
                yl = 300,
                m  = this.m;
            this.ctx.beginPath();
    
            //绘制X轴坐标
            this.ctx.moveTo(x,y);
            this.ctx.lineTo(x + xl,y);
            this.ctx.lineTo(x + xl - m,y - m/2);
            this.ctx.lineTo(x + xl - m,y - m/2 + m);
            this.ctx.lineTo(x + xl,y);
            this.ctx.fill();
    
            //绘制Y轴坐标
            this.ctx.moveTo(x,y);
            this.ctx.lineTo(x,y - yl);
            this.ctx.lineTo(x - m/2,y - yl + m);
            this.ctx.lineTo(x - m/2 + m,y - yl + m);
            this.ctx.lineTo(x,y - yl);
            this.ctx.fill();
    
            this.ctx.strokeStyle = "#000";
            this.ctx.stroke();
        },
        drawRect:function(){
            var self = this;
            //绘制坐标点的每条连接线
            this.ctx.beginPath();
            this.rects.forEach(function(rect){
                self.ctx.fillStyle = rect.color;
                self.ctx.fillRect(rect.x,self.y - rect.h,self.rectW,rect.h);
            })
            this.ctx.strokeStyle = "#000";
            this.ctx.stroke();
        }
    }
    
    //准备绘制数据
    var rects = [
        {x:100,h:50,color:"red"},
        {x:200,h:250,color:"pink"},
        {x:300,h:120,color:"#195"},
        {x:400,h:300,color:"#47e"},
        {x:500,h:20,color:"#302"}
      ]
    
    //调用构造函数绘制
    new RectChart().init(rects);
    
    ### Non-Zero Winding Number Rule & Odd-even Rule
    ![](https://img2020.cnblogs.com/blog/450136/202201/450136-20220101164710713-1176986553.png)
    
    <div class='titleX'>**非零正交(环绕)原则**</div>
    我们在使用绘图上下文对象的`fill`方法进行填充绘制的时候,如果传递参数(`nonzero`)或默认不传递任何参数,那么在填充的时候使用**非零正交(环绕)原则**。
    
    >**非零正交(环绕)原则 · 规则**
    >❐ 在路径包围的区域中,向外发射一条和所有围绕它的边相交的射线
    >❐ 开启一个计数器,计数器的初始值为0
    >❐ 如果这个射线遇到顺时针围绕,那么计数器 +1
    >❐ 如果这个射线遇到顺时针围绕,那么计数器 -1
    >❐ 如果最终计数器的值非〇,则这块区域在路径内浏览器会对其进行填充。
    
    这里我们可以给出两个非零正交(环绕)原则应用的典型案例-绘制镂空矩形和圆环。
    ![](https://img2020.cnblogs.com/blog/450136/202201/450136-20220101164730881-1793162659.png)
    ```javaScript
        var canvas = document.getElementById("canvas");
        var ctx    = canvas.getContext("2d");
        
        //绘制正方形(顺时针)
        ctx.moveTo(50,50);
        ctx.lineTo(150,50);
        ctx.lineTo(150,150);
        ctx.lineTo(50,150);
        ctx.lineTo(50,50);
    
        //绘制正方形(逆时针)
        ctx.moveTo(75,75);
        ctx.lineTo(75,125);
        ctx.lineTo(125,125);
        ctx.lineTo(125,75);
        ctx.lineTo(75,75);
        //设置填充(非零正交原则)
        ctx.fillStyle = "#299";
        ctx.fill();
    
        //绘制圆环
        ctx.beginPath();
        ctx.arc(300,100,60,0,2 * Math.PI,false);
        ctx.arc(300,100,40,0,2 * Math.PI,true);
        ctx.fillStyle = "rgba(250,50,79,1)";
        ctx.fill();
    
    **奇偶填充原则**
    我们在使用绘图上下文对象的`fill`方法进行填充绘制的时候,如果传递参数(`evenodd`)那么在填充的时候使用**奇偶填充原则**。

    奇偶填充原则 · 规则
    ❐ 在路径包围的区域中,向外发射一条和所有围绕它的边相交的射线
    ❐ 查看相交线的个数,如果为奇数,就填充,如果是偶数,就不填充。

        var canvas  = document.getElementById('canvas');
        var ctx     = canvas.getContext('2d');
        var x = ctx.canvas.width / 2,
            y = ctx.canvas.height/ 2,
            r = 50,
            start = - Math.PI / 2,
            end = Math.PI * 3 / 2;
    
        ctx.arc(x, y, r, start, end);
        ctx.fillStyle = "#000";
        ctx.fill();
        ctx.beginPath();
        ctx.moveTo(x, y - r);
        ctx.lineTo(x - r * Math.sin(Math.PI / 5), y + r * Math.cos(Math.PI / 5));
        ctx.lineTo(x + r * Math.cos(Math.PI / 10), y - r * Math.sin(Math.PI / 10));
        ctx.lineTo(x - r * Math.cos(Math.PI / 10), y - r * Math.sin(Math.PI / 10));
        ctx.lineTo(x + r * Math.sin(Math.PI / 5), y + r * Math.cos(Math.PI / 5));
        ctx.fillStyle = "#fff";
        ctx.fill();
    
        ctx.beginPath();
        ctx.arc(x + 150, y, r, start, end);
        ctx.fillStyle = "#000";
        ctx.fill();
        ctx.beginPath();
        ctx.moveTo(x + 150, y - r);
        ctx.lineTo(x + 150 - r * Math.sin(Math.PI / 5), y + r * Math.cos(Math.PI / 5));
        ctx.lineTo(x + 150 + r * Math.cos(Math.PI / 10), y - r * Math.sin(Math.PI / 10));
        ctx.lineTo(x + 150 - r * Math.cos(Math.PI / 10), y - r * Math.sin(Math.PI / 10));
        ctx.lineTo(x + 150 + r * Math.sin(Math.PI / 5), y + r * Math.cos(Math.PI / 5));
        ctx.fillStyle = "#fff";
        ctx.fill('evenodd');
    
  • 相关阅读:
    [leetcode] First Missing Positive
    [leetcode] Can Place Flowers
    [leetcode] Maximum Product of Three Numbers
    [leetcode] Generate Parentheses
    蓝桥杯 PREV-7 连号区间数
    蓝桥杯 PREV-3 带分数(dfs)
    蓝桥杯 PREV-2 打印十字图
    团体程序设计天梯赛 L3-016 二叉搜索树的结构 (30分)
    团体程序设计天梯赛 L3-020 至多删三个字符 (30分)(DP)
    团体程序设计天梯赛 L3-011 直捣黄龙 (30分)
  • 原文地址:https://www.cnblogs.com/wendingding/p/15755644.html
Copyright © 2020-2023  润新知