前端开发系列025-基础篇之Canvas绘图(路径)
一、Canvas路径和状态
设置绘制的起点
语法 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方法只是规划了矩形的路径,并没有填充和描边,因此还需要搭配stroke或fill使用。
描边矩形
语法 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();
作用 开始路径的作用是将不同的绘制路径进行隔离,闭合路径会自动连接最开始和最后的点。
注意 执行开始路径方法时表示将要重新绘制一个新的路径,可以分开设置和管理多个路径的样式。
二、 Canvas路径绘制示例
Demo-1 绘制交叉和平行线

//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 开始路径和闭合路径

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);

//绘制柱状图的构造函数
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

我们在使用绘图上下文对象的fill方法进行填充绘制的时候,如果传递参数(nonzero)或默认不传递任何参数,那么在填充的时候使用非零正交(环绕)原则。
非零正交(环绕)原则 · 规则
>❐ 在路径包围的区域中,向外发射一条和所有围绕它的边相交的射线
>❐ 开启一个计数器,计数器的初始值为0
>❐ 如果这个射线遇到顺时针围绕,那么计数器 +1
>❐ 如果这个射线遇到顺时针围绕,那么计数器 -1
>❐ 如果最终计数器的值非〇,则这块区域在路径内浏览器会对其进行填充。
这里我们可以给出两个非零正交(环绕)原则应用的典型案例-绘制镂空矩形和圆环。

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');
前端开发系列025-基础篇之Canvas绘图(路径)的更多相关文章
- 从0到1用react+antd+redux搭建一个开箱即用的企业级管理后台系列(基础篇)
背景 最近因为要做一个新的管理后台项目,新公司大部分是用vue写的,技术栈这块也是想切到react上面来,所以,这次从0到1重新搭建一个react项目架子,需要考虑的东西的很多,包括目录结构.代码 ...
- 前端开发:css基础知识之盒模型以及浮动布局。
前端开发:css基础知识之盒模型以及浮动布局 前言 楼主的蛮多朋友最近都在学习html5,他们都会问到同一个问题 浮动是什么东西? 为什么这个浮动没有效果? 这个问题楼主已经回答了n遍.今天则是把 ...
- ESP8266开发之旅 基础篇① 走进ESP8266的世界
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 基础篇② 如何安装ESP8266的Arduino开发环境
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 基础篇③ ESP8266与Arduino的开发说明
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- openlayers5-webpack 入门开发系列一初探篇(附源码下载)
前言 openlayers5-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载 ...
- leaflet-webpack 入门开发系列一初探篇(附源码下载)
前言 leaflet-webpack 入门开发系列环境知识点了解: node 安装包下载webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址 w ...
- 【Windows10 IoT开发系列】配置篇
原文:[Windows10 IoT开发系列]配置篇 Windows10 For IoT是Windows 10家族的一个新星,其针对不同平台拥有不同的版本.而其最重要的一个版本是运行在Raspberry ...
- ESP8266开发之旅 基础篇④ ESP8266与EEPROM
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 基础篇⑥ Ticker——ESP8266定时库
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
随机推荐
- Python科学计算系列8—矩阵
1.矩阵构造 代码如下: from sympy import * m1 = Matrix([[1, 2], [2, 0], [3, 4]]) # 构造单位矩阵 m2 = eye(3) # 构造零矩阵 ...
- 2319. 判断矩阵是否是一个 X 矩阵
2319. 判断矩阵是否是一个 X 矩阵 题解: 模拟 class Solution { public boolean checkXMatrix(int[][] grid) { int n = gri ...
- windows里的一些常用的dos命令
--------------------------------------------- 1. 中断命令执行 Ctrl + Z 2. 文件/目录 cd 切换目录 例:cd // 显示当前目录 ...
- 【代码】Python3|无GUI环境中使用Seaborn作图的学习路线及代码(阴影折线图)
我有个需求是需要画图,让GPT帮我生成了一下学习计划. 学习路线依照GPT的来的,使用的Prompt工具是https://github.com/JushBJJ/Mr.-Ranedeer-AI-Tuto ...
- 【记录】Truenas scale|NFSv4数据集的子目录或文件的ACL完全访问权限继承老是继承不了怎么回事
我遇到了数据集下新建文件夹或文件,新建的文件夹或文件没有和数据集的ACL设置相符合的情况.其根本原因是NFSv4的完全访问权限要想继承的话,它的访问设置权限要设置"用户"和&quo ...
- 操作系统综合题之“请填写信号量值并说明操作结果(正常、阻塞或唤醒。如阻塞或者唤醒,需说明阻塞或者被唤醒的是P1还是P2)(信号量操作流程-代码补充)”
1.问题:题36表是两个同步进程的模拟执行,生产者将物品放入共享缓冲区供消费者使用,缓冲区可放2件物品,使用2个信号量,并置初值为S1 = 2,S2=0.现已知操作情况,请填写信号量值并说明操作结果( ...
- Python 3.14 新特性盘点,更新了些什么?
Python 3.14.0 稳定版将于 2025 年 10 月正式发布,目前已进入 beta 测试阶段.这意味着在往后的几个月里,3.14 的新功能已冻结,不再合入新功能(除了修复问题和完善文档). ...
- dom操作补充
s5.html <!DOCTYPE html> <html lang="en"> <head> <meta charset=&qu ...
- vue3 基础-列表渲染
本篇讲列表渲染, 主要是对 v-on 指令配合 v-if 和一些数组相关的方法来体验 vue 的模板渲染方法. 数组元素的渲染 <!DOCTYPE html> <html lang= ...
- XML注入
XML注入 复现使用的题目为buuoj中的[NCTF2019]Fake XML cookbook 1和[NCTF2019]True XML cookbook 1 参考链接为https://xz.ali ...