canvas
绘制平行线
- 以下是绘制平行线的例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// 绘制线条
ctx.moveTo(100,100);
ctx.lineTo(200,100);
ctx.moveTo(100,200);
ctx.lineTo(200,200);
// 描边
ctx.stroke();
</script>
</body>
</html>
线条模糊问题
- canvas关于线条的问题
- 默认的宽度是1px
- 默认的颜色是黑色
- 产生线条模糊的原因
canvas的每条线都有一条无限细的“中线”,线条的宽度是从中线向两侧延伸的。
也就是说,我绘制1px,他会从上述的“中线“,向左右(或上下)各衍生出0.5px。
此时又有个问题,因为计算机不允许出现小于1px的图形,所以他做了个折中的问题,把两边都绘制了。
所以,如此一来,本来1px的线条,看起来2px宽的线条。而且颜色还有点灰,不是纯黑色,按照自己的
理解,应该是拉伸的是0.5px的线条,所以颜色还没有达到饱和。
-
解决线条模糊
现在我们的原因找到了,Canvas中的line把中线与像素的起点对齐了,而不是像素的中间点。
所以,我们让线条的中线和像素的中间点对齐就行了。
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// 绘制线条
ctx.moveTo(100.5,100.5);
ctx.lineTo(200.5,100.5);
ctx.moveTo(100.5,200.5);
ctx.lineTo(200.5,200.5);
// 描边
ctx.stroke();
</script>
- 加0.5,让线条的中线和像素的中间点对齐
- 不过貌似这样一来我们画线的时候就非常纠结,难道每次都去加这个让人郁闷的0.5?当然不是,因为我们大部分时间都是用变量保存值的,就不用给每个值加0.5 了
而且,对于lineWidth>1 的线,我们也不用管它:因为只有线条宽1px的时候,这个问题才最明显。
绘制三条不同颜色和宽度的平行线
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// 绘制线条
// 开启新的轨迹
ctx.beginPath();
// 设置线条样式
ctx.lineWidth = 10;
ctx.strokeStyle = 'red';
ctx.moveTo(100.5, 100.5);
ctx.lineTo(200.5, 100.5);
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 20;
ctx.strokeStyle = 'yellow';
ctx.moveTo(100.5, 200.5);
ctx.lineTo(200.5, 200.5);
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 30;
ctx.strokeStyle = 'blue';
ctx.moveTo(100.5, 300.5);
ctx.lineTo(200.5, 300.5);
// 描边
ctx.stroke();
</script>
</body>
</html>
- 用到的新知识解释
- ctx.beginPath()开启一个新的轨迹,
- ctx.lineWidth,设置线条的宽度
上面一例,为什么要这么写呢?
首先,我们不管设置线的宽度还是线的颜色,他都是上下文context的一个属性,他最后描边呈现出现的就是我们设置的值。结合例子来说,假设我们上述例子没有使用ctx.beginPath(),结果会是三条宽度30和颜色为blue的线。所以,正常我们的逻辑,就是描轨迹,然后使用stroke描边,那么,我们将他变成3条轨迹,并且ctx.stroke();描边即可。beginPath()就是这么一个方法,开启新的轨迹。
绘制三角形
###填充三角形
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// 绘制线条
// 设置线条样式
ctx.strokeStyle = 'red';
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineTo(200, 200);
ctx.fillStyle = 'red';
ctx.fill();
</script>
</body>
- ctx.fill();填充方法,用该方法不需要使用ctx.stroke();描边
- ctx.fillStyle 设置填充的颜色
描边三角形
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// 绘制线条
// 设置线条样式
ctx.strokeStyle = 'red';
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineTo(200, 200);
// 关闭路径
ctx.closePath();
ctx.stroke();
</script>
</body>
- closePath(): 自动闭合路径,上述例子只画了两条直角边,使用关闭路径可以自动闭合
绘制镂空的正方形
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// 绘制线条
// 设置线条样式
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineTo(200, 200);
ctx.lineTo(100, 200);
// 关闭路径
ctx.closePath();
// 绘制中间的正方形
ctx.moveTo(125, 125);
ctx.lineTo(125, 175);
ctx.lineTo(175, 175);
ctx.lineTo(175, 125);
// 关闭路径
ctx.closePath();
// 填充图形
ctx.fill();
</script>
</body>
</html>
填充的非零环绕规则
在填充的时候会遵从我们的非零环绕规则
判断是否填充方法
- 看一块区域是否填充
- 从这个区域拉一条直线
- 看和这条直线相交的轨迹
- 如果顺时针轨迹+1
- 如果逆时针轨迹-1
- 所有的轨迹的值计算出来
- 如果是非0那么填充
- 如果是0那么不填充
和线相关的属性和方法
-
ctx.lineCap: 两端样式,即给线条两端带帽子
butt(默认,不带帽子) || square(方体) || round(圆)
-
ctx.lineJoin: 相交线的拐点
miter(默认) || round(圆) || bevel(斜角)
-
ctx.setLineDash([]) : 设置虚线
方法传递一个数组,该数组是用来描述排列方式的
如[5],则实虚都按5px排,如[5,10],则实按5px,虚按10px
如果是[5,10,15]又是怎么排呢?其实这个排列方式是按 实虚实虚实虚…这样来的
那么,这是一个奇数数组,那么就会按照实5虚10实15虚5实10虚15这样排列
-
ctx.getLineDash(): 获取虚线的排列方式
获取的是不重复的那一段排列方式
-
ctx.lineDashOffset: 偏移
如果是正的值 往后偏移 例:左右方向,整体往左
如实是负的值 往前偏移 例:左右方向,整体往右
绘制一个从黑到白的渐变矩形
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// 绘制线条
// 设置线条样式
ctx.lineWidth = 30;
for(var i = 0; i<255;i++){
ctx.beginPath();
ctx.moveTo(100 + i, 100);
ctx.lineTo(101 + i, 100);
ctx.strokeStyle = 'rgb('+ i +','+i +','+i+')';
ctx.stroke();
}
</script>
</body>
- 线是由点构成的,利用for循环生成若干个点
绘制网格
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// 1. 绘制网格
// 2. 网格的大小
var gridSize = 10;
// 3.画多少条x轴方向的线 横向的条数 画布高度
// a. 第一种方法拿到宽高
// var canvasHeight = myCanvas.height;
// var canvasWidth = myCanvas.width;
// b. 第二种方法拿到宽高
var canvasHeight = ctx.canvas.height;
var canvasWidth = ctx.canvas.width;
var xLineTotal = Math.floor(canvasHeight / gridSize);
var yLineTotal = Math.floor(canvasWidth / gridSize);
for (var i = 0; i < xLineTotal; i++) {
ctx.beginPath();
ctx.moveTo(0, i * gridSize - 0.5);
ctx.lineTo(canvasWidth, i * gridSize - 0.5);
ctx.strokeStyle = '#eee';
ctx.stroke();
}
// 画多少条y轴方向的线
for (var i = 0; i < yLineTotal; i++) {
ctx.beginPath();
ctx.moveTo(i * gridSize - 0.5, 0);
ctx.lineTo(i * gridSize - 0.5, canvasHeight);
ctx.strokeStyle = '#eee';
ctx.stroke();
}
</script>
</body>
</html>
如何获取canvas的宽高
- myCanvas.height和myCanvas.width
- 从canvas元素里获取
- ctx.canvas.height和ctx.canvas.width
- 从ctx上下文中获取canvas元素,再从中获取宽高
绘制思路
- 获取元素和上下文
- 设置网格大小,并取得canvas宽高
- 通过canvas宽高和网格大小,计算出我们网格需要绘制多少条横线和竖线
- 利用循环来绘制
绘制坐标系
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
var space = 20;
var arrowSize = 10;
// 计算原点
var canvasWidth = ctx.canvas.width;
var canvasHeight = ctx.canvas.height;
var x0 = space;
var y0 = canvasHeight - space;
// 绘制x轴
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(canvasWidth - space, y0)
// 箭头
ctx.lineTo(canvasWidth - space - arrowSize, y0 + arrowSize / 2);
ctx.lineTo(canvasWidth - space - arrowSize, y0 - arrowSize / 2);
ctx.lineTo(canvasWidth - space, y0);
ctx.stroke();
ctx.fill();
// 绘制y轴
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(space, space)
// 箭头
ctx.lineTo(space + arrowSize/2,space + arrowSize);
ctx.lineTo(space - arrowSize/2,space + arrowSize);
ctx.lineTo(space,space);
ctx.stroke();
ctx.fill();
</script>
</body>
</html>
绘制思路
- 绘制坐标系
- 确定原点
- 确定距离画布旁边的距离
- 确定坐标轴的长度
- 确定箭头的大小 是个等腰三角形
- 绘制箭头填充
绘制点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// 1.绘制点
// 2.点的尺寸
// 3.以坐标中心绘制点
// 设置点坐标,随便设一个点
var coordinate = {
x: 120,
y: 240
}
// 设置点尺寸
var dottedSize = 6;
// 开始绘制
ctx.moveTo(coordinate.x - dottedSize / 2, coordinate.y - dottedSize / 2);
ctx.lineTo(coordinate.x + dottedSize / 2, coordinate.y - dottedSize / 2);
ctx.lineTo(coordinate.x + dottedSize / 2, coordinate.y + dottedSize / 2);
ctx.lineTo(coordinate.x - dottedSize / 2, coordinate.y + dottedSize / 2);
ctx.closePath();
ctx.fill();
</script>
</body>
</html>
绘制思路
- 获取坐标
- 每一个坐标,是由一个正方形表示的,坐标是这个正方形的中心点
- 确定点的尺寸,即正方形的边长
- 通过计算,可以得到坐标周围的四个点
绘制折线图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 1. 构造函数
var LineChart = function (ctx) {
// 获取绘图工具
this.ctx = ctx || document.querySelector('canvas').getContext('2d');
// 画布的大小
this.canvasWidth = this.ctx.canvas.width;
this.canvasHeight = this.ctx.canvas.height;
// 绘制网格需要的属性
// 网格的大小
this.gridSize = 10;
// 绘制坐标系需要的属性
// 坐标系的间距
this.space = 20;
// 坐标原点
this.x0 = this.space;
this.y0 = this.canvasHeight - this.space;
// 箭头的大小
this.arrowSize = 10;
// 绘制点
this.dottedSize = 6;
}
// 2. 行为方法
LineChart.prototype.init = function (data) {
this.drawGrid();
this.drawAxis();
this.drawDotted(data);
}
// 绘制网格
LineChart.prototype.drawGrid = function () {
var xLineTotal = Math.floor(this.canvasHeight / this.gridSize);
var yLineTotal = Math.floor(this.canvasWidth / this.gridSize);
for (var i = 0; i < xLineTotal; i++) {
this.ctx.beginPath();
this.ctx.moveTo(0, i * this.gridSize - 0.5);
this.ctx.lineTo(this.canvasWidth, i * this.gridSize - 0.5);
this.ctx.strokeStyle = '#eee';
this.ctx.stroke();
}
// 画多少条y轴方向的线
for (var i = 0; i < yLineTotal; i++) {
this.ctx.beginPath();
this.ctx.moveTo(i * this.gridSize - 0.5, 0);
this.ctx.lineTo(i * this.gridSize - 0.5, this.canvasHeight);
this.ctx.strokeStyle = '#eee';
this.ctx.stroke();
}
}
// 绘制坐标系
LineChart.prototype.drawAxis = function () {
// 绘制x轴
this.ctx.beginPath();
this.ctx.strokeStyle = "#000";
this.ctx.moveTo(this.x0, this.y0);
this.ctx.lineTo(this.canvasWidth - this.space, this.y0)
// 箭头
this.ctx.lineTo(this.canvasWidth - this.space - this.arrowSize, this.y0 + this.arrowSize / 2);
this.ctx.lineTo(this.canvasWidth - this.space - this.arrowSize, this.y0 - this.arrowSize / 2);
this.ctx.lineTo(this.canvasWidth - this.space, this.y0);
this.ctx.stroke();
this.ctx.fill();
// 绘制y轴
this.ctx.beginPath();
this.ctx.moveTo(this.x0, this.y0);
this.ctx.lineTo(this.space, this.space)
// 箭头
this.ctx.lineTo(this.space + this.arrowSize / 2, this.space + this.arrowSize);
this.ctx.lineTo(this.space - this.arrowSize / 2, this.space + this.arrowSize);
this.ctx.lineTo(this.space, this.space);
this.ctx.stroke();
this.ctx.fill();
}
// 绘制所有点
LineChart.prototype.drawDotted = function (data) {
// 1.数据的坐标 需要转换 canvas坐标
// 2.再进行点的绘制
// 3.把线连起来
// 存当前对象
var that = this;
// 记录当前坐标,默认是原点
var prevCanvasX = this.x0;
var prevCanvasY = this.y0;
data.forEach(function (item, i) {
// 这里面的this是window对象,所以要在调用之前把之前的对象存起来
// console.log(this);
// x = 原点的坐标 + 数据的坐标
// y = 原点的坐标 - 数据的坐标
var canvasX = that.x0 + item.x;
var canvasY = that.y0 - item.y;
// 开始绘制
that.ctx.moveTo(canvasX - that.dottedSize / 2, canvasY - that.dottedSize / 2);
that.ctx.lineTo(canvasX + that.dottedSize / 2, canvasY - that.dottedSize / 2);
that.ctx.lineTo(canvasX + that.dottedSize / 2, canvasY + that.dottedSize / 2);
that.ctx.lineTo(canvasX - that.dottedSize / 2, canvasY + that.dottedSize / 2);
that.ctx.closePath();
that.ctx.fill();
// 点的连线
// 第一个点的上一个点是 原点 x0 y0
// 当不是第一个点时 起点为上一个点
that.ctx.beginPath();
that.ctx.moveTo(prevCanvasX, prevCanvasY);
that.ctx.lineTo(canvasX, canvasY);
that.ctx.stroke();
// 记录当前坐标,下一个循环要用
prevCanvasX = canvasX;
prevCanvasY = canvasY;
});
}
// 3. 初始化
// 点的坐标需要传参,由实例对象调用方法绘制,实现数据可视化
// 点的坐标由一个数组记录,假设点的坐标的数据是
var data = [{
x: 100,
y: 120
}, {
x: 150,
y: 130
}, {
x: 200,
y: 150
}, {
x: 260,
y: 170
}, {
x: 300,
y: 20
}, ];
var lineChart = new LineChart();
lineChart.init(data);
</script>
</body>
</html>
绘制思路
- 首先思考一下,我们折线图是由什么东西组成的?要有网格,坐标系,还有点吧,后面,我们还需要把点连线起来。
- 那么,我们可以知道,绘制折线图,需要用到很多属性和方法,比如网格中格子的大小啦,坐标系中的原点,绘制的时候,如何绘制等等。
- 思路分析到这,可以得知,我们画一个折线图,可以用构造函数来构造。
绘制矩形
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// 绘制矩形路径 不是独立路径
// ctx.rect(100,100,200,100)
// ctx.fillStyle = 'blue';
// ctx.fill();
// ctx.stroke();
// 绘制矩形 有自己的独立路径
// ctx.strokeRect(100,100,200,100);
ctx.fillRect(100,100,200,100);
// 清除矩形的内容
ctx.clearRect(0,0,150,150);
// 清除画布所有内容
// ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
</script>
</body>
</html>
###方法说明
-
ctx.rect(x0,y0,x1,y1)
绘制一个矩形路径 不是独立路径,因为是路径,所以需要自行描边或者填充
说明参数:
x0,canvas坐标系的x轴
y0,canvas坐标系的y轴
x1,水平方向长度,也可以说是宽
y1,垂直方向长度,也可以说是高
下面的方法,参数类似,不赘述
-
ctx.strokeRect(x0,y0,x1,y1)
描边矩形,有自己独立的路径
-
ctx.fillRect(x0,y0,x1,y1)
填充矩形,有自己独立的路径
-
ctx.clearRect(x0,y0,x1,y1)
擦除一个矩形 相当于橡皮擦
绘制渐变的矩形
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// fillStyle 'pink' '#000' 'rgb()' 'rgba()'
// 除了上述4种方式,也可以使用一个渐变的方案来填充图形
// 创建一个渐变的方案
// 渐变是有长度和方向的,是通过起始点还有结束点来确定的
// 参数1,2 起始点 参数3,4结束点
var linearGradient = ctx.createLinearGradient(100,100,400,100);
// 添加颜色点
linearGradient.addColorStop(0,'pink');
linearGradient.addColorStop(0.5,'red');
linearGradient.addColorStop(1,'blue');
// 设置方案
ctx.fillStyle = linearGradient;
ctx.fillRect(100,100,400,100);
</script>
</body>
</html>
-
ctx.createLinearGradient(x0,y0,x1,y1) 创建一个渐变方案
参数1,2 起始点 参数3,4结束点,需要说明的是这四个都是canvas坐标系的坐标
-
渐变方案.addColorStop(0,‘pink’) 添加颜色点
-
通过ctx.fillStyle 设置方案
绘制圆弧
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// 绘制圆弧
// 确定圆心
// 确定半径
// 确定起始绘制的位置和结束绘制的位置 确定弧的长度和位置 startAngle endAngle 弧度
// 取得绘制的方向 direction 默认是顺时针 false 逆时针 true
// 在中心位置画一个半径为100px的圆弧
var w = ctx.canvas.width;
var h = ctx.canvas.height;
ctx.arc(w/2,h/2,100,Math.PI/2,Math.PI,false);
// 描边
ctx.stroke();
</script>
</body>
</html>
绘制步骤
- ctx.arc(圆心x,圆心y,半径,起始绘制位置,结束绘制位置,绘制的方向)
- 绘制圆弧
- 确定圆心
- 确定半径
- 确定起始绘制的位置和结束绘制的位置 确定弧的长度和位置 startAngle endAngle 弧度
- 取得绘制的方向 direction 默认是顺时针 false 逆时针 true
绘制扇形
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
var w = ctx.canvas.width;
var h = ctx.canvas.height;
// 将点先移至圆心位置
ctx.moveTo(w/2,h/2);
// 绘制弧线
ctx.arc(w/2,h/2,100,0,Math.PI * 3/2,true);
// 闭合路径即可 勾勒出扇形
ctx.closePath();
ctx.fill();
// 描边
ctx.stroke();
</script>
</body>
</html>
绘制步骤
- 先将点移至圆心位置
- 接着画出圆弧
- 然后闭合路径即可
绘制n等分的随机颜色的圆
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
var w = ctx.canvas.width;
var h = ctx.canvas.height;
// 分成几等分
var num = 6;
var angle = Math.PI * 2 / num;
var x0 = w / 2;
var y0 = h / 2;
// 获取随机颜色
var 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 + ')';
}
// 上一次绘制的结束弧度等于当前次的起始弧度
for (var i = 0; i < num; i++) {
var startAngle = i * angle;
var endAngle = (i + 1) * angle;
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.arc(x0, y0, 100, startAngle, endAngle);
ctx.closePath();
ctx.fillStyle = getRandomColor();
ctx.fill();
}
</script>
</body>
</html>
- 根据需要几等分,然后算出几等分的角度
- 通过for循环来描绘每一个扇形拼成一个圆
绘制饼图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
var w = ctx.canvas.width;
var h = ctx.canvas.height;
var x0 = w / 2;
var y0 = h / 2;
// 获取随机颜色
var 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 + ')';
}
// 假设拿到的数据
var data = [5,30,10,5];
// 需要把数据转换成弧度,即数组里每个元素与总量的比值*2π
var angleList = [];
// 数组里面的元素总量
var total = 0;
// 算出总量
data.forEach(function(item,i){
total += item;
})
// 算成弧度,并添加进数组里
data.forEach(function(item,i){
var angle = Math.PI * 2 * (item/total);
angleList.push(angle);
})
var startAngle = 0;
// 根据弧度绘制扇形
angleList.forEach(function(item,i){
var endAngle = startAngle + item;
ctx.beginPath();
ctx.moveTo(x0,y0);
ctx.arc(x0,y0,100,startAngle,endAngle);
ctx.fillStyle = getRandomColor();
ctx.fill();
// 记录当前的结束为止作为下一次的起始为止
startAngle = endAngle;
})
</script>
</body>
</html>
- 根据数据绘制饼图
- 算出数据在总量中占多少分,可以算出对应的弧度
在画布中心绘制文字
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
var w = ctx.canvas.width;
var h = ctx.canvas.height;
var x0 = w / 2;
var y0 = h / 2;
// 随便申明一段文字
var str = '葡萄成熟时';
ctx.beginPath();
ctx.moveTo(0, y0 - 0.5);
ctx.lineTo(w, y0 - 0.5);
ctx.moveTo(x0 - 0.5, 0);
ctx.lineTo(x0 - 0.5, h);
ctx.strokeStyle = '#eee';
ctx.stroke();
// 绘制文本
ctx.beginPath();
ctx.strokeStyle = '#000';
// 注意: 起点位置在文字的左下角
// 文本属性, 尺寸 字体 左右对齐方式 垂直对齐方式
ctx.font = '40px Microsoft YaHei';
// 左右对齐方式(center left right start end) 基准起始坐标
ctx.textAlign = 'center';
// 垂直对齐的方式 基线 baseline(top,bottom,middle) 基准起始坐标
ctx.textBaseline = 'middle';
// ctx.direction = 'rtl';
// ctx.strokeText(str,x0,y0);
ctx.fillText(str,x0,y0);
// 画一个和文字一样长的下划线
var width = ctx.measureText(str).width;
ctx.moveTo(x0-width/2,y0+20);
ctx.lineTo(x0+width/2,y0+20);
ctx.stroke();
</script>
</body>
</html>
方法说明
- ctx.font :设置字体属性
- ctx.textAlign :设置左右对齐方式
- ctx.textBaseline :设置垂直对齐方式
- ctx.direction : rtl || ltr ,与左右对齐的start end有关,很少用,用到时查文档
- ctx.strokeText(字符串,x,y) : 描边文字
- ctx.fillText(字符串,x,y) : 填充文字
- ctx.measureText(字符串) : 获得一个对象,里面有width属性,可以用这个方法获得字符串的宽度
绘制完整饼状图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// 1. 根据数据绘制一个饼状图
// 2. 绘制标题 从扇形的弧中心伸出一条线在画一条横线在横线的上面写上文字标题
// 3. 在画布的左上角 绘制说明 一个和扇形一样颜色的矩形 旁边就是文字说明
var PieChart = function (ctx) {
this.ctx = ctx || document.querySelector('canvas').getContext('2d');
// 绘制饼图中心
this.w = this.ctx.canvas.width;
this.h = this.ctx.canvas.height;
// 绘制饼图中心
this.x0 = this.w/2 + 20;
this.y0 = this.h/2;
// 半径
this.radius = 100;
// 伸出去线的长度
this.outLine = 20;
// 说明的矩形大小
this.rectW = 30;
this.rectH = 16;
// 间隔上面距离
this.space = 20;
// 方块间距
this.rectSpace = 10;
}
PieChart.prototype.init = function (data) {
// 1. 准备数据
this.drawPie(data);
};
PieChart.prototype.drawPie = function () {
// 先存好实例对象
var that = this;
// 1. 转换弧度
var angleList = this.transformAngle(data);
// 2.绘制饼状图
var startAngle = 0;
angleList.forEach(function (item,i) {
// 当前的结束弧度要等于下一次的起始弧度
var endAngle = startAngle + item.angle;
that.ctx.beginPath();
that.ctx.moveTo(that.x0,that.y0);
that.ctx.arc(that.x0,that.y0,that.radius,startAngle,endAngle);
var color = that.ctx.fillStyle = that.getRandomColor();
that.ctx.fill();
// 下一次要使用当前的这一次的结束角度
// 绘制标题
that.drawTitle(startAngle,item.angle,color,item.title);
that.drawDesc(i,item.title);
startAngle = endAngle;
});
};
PieChart.prototype.drawTitle = function (startAngle,angle,color,title) {
// 绘制思路
// 1. 确定伸出去的线 通过圆心点 通过伸出去的点 确定这个线
// 2. 确定伸出去的点 需要确定伸出去的线的长度
// 3. 固定伸出去的线的长度
// 4. 计算这个点的坐标
// 5. 需要根据角度和斜边的长度
// 5.1 使用弧度 当前扇形的起始弧度 + 对应的弧度的一半
// 5.2 半径 + 伸出去的长度
// 5.3 求得公式
// 5.3.1 outX = x0 + cos( angle ) * (r + outLine)
// 5.3.2 outY = y0 + sin( angle ) * (r + outLine)
// 斜边
var edge = this.radius + this.outLine;
// x轴方向的直角边
var edgeX = Math.cos(startAngle + angle / 2) * edge;
// y轴方向的直角边
var edgeY = Math.sin(startAngle + angle / 2) * edge;
// 计算出去的点坐标
var outX = this.x0 + edgeX;
var outY = this.y0 + edgeY;
this.ctx.beginPath();
this.ctx.moveTo(this.x0,this.y0);
this.ctx.lineTo(outX,outY);
this.ctx.strokeStyle = color;
// 画文字和下划线
// 判断线的方向
// 伸出去的点在x0的左边 线的方向就是左边
// 伸出去的点在x0的右边 线的方向就是右边
// 结束的点坐标 和文字大小
this.ctx.font = '14px Microsoft YaHei';
var textWidth = this.ctx.measureText(title).width;
if(outX > this.x0){
this.ctx.lineTo(outX + textWidth,outY);
this.ctx.textAlign = 'left';
}else{
this.ctx.lineTo(outX - textWidth,outY);
this.ctx.textAlign = 'right';
}
this.ctx.stroke();
this.ctx.textBaseline = 'bottom';
this.ctx.fillText(title,outX,outY);
};
PieChart.prototype.drawDesc = function (index,title) {
// 绘制说明
// 矩形的大小 this.rectW this.rectH
// 距离上和左边的间距 this.space
// 矩形之间的间距 this.rectSpace
this.ctx.fillRect(this.space,this.space + index * (this.space + this.rectSpace),this.rectW,this.rectH);
// 绘制文字
this.ctx.beginPath();
this.ctx.textBaseline = 'top';
this.ctx.textAlign = 'left';
this.ctx.font = '12px Microsoft YaHei';
this.ctx.fillText(title,this.space + this.rectW + 10 ,this.space + index * (this.space + this.rectSpace))
};
PieChart.prototype.transformAngle = function(data){
// 返回的数据内容包含弧度的
var total = 0;
data.forEach(function (item,i) {
total +=item.num;
});
// 计算弧度 并且追加到当前的对象内容
data.forEach(function(item,i){
var angle = item.num / total * Math.PI * 2;
item.angle = angle;
});
return data;
};
PieChart.prototype.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 + ')';
}
// 准备数据
var data = [{
title: '10-20岁',
num: 6
}, {
title: '20-30岁',
num: 30
},
{
title: '30-40岁',
num: 10
},
{
title: '40-50岁',
num: 4
},
];
var pieChart = new PieChart();
pieChart.init(data);
</script>
</body>
</html>
###需求分析
- 根据数据绘制一个饼状图
- 绘制标题 从扇形的弧中心伸出一条线在画一条横线在横线的上面写上文字标题
- 在画布的左上角 绘制说明 一个和扇形一样颜色的矩形 旁边就是文字说明
绘制饼图
-
绘制思路看参考之前的
-
根据我们的需求,我们再绘制饼图的同时,就可以绘制饼图的标题以及描述,这样可以少用几个循环
绘制标题
-
绘制思路
-
// 1. 确定伸出去的线 通过圆心点 通过伸出去的点 确定这个线 // 2. 确定伸出去的点 需要确定伸出去的线的长度 // 3. 固定伸出去的线的长度 // 4. 计算这个点的坐标 // 5. 需要根据角度和斜边的长度 // 5.1 使用弧度 当前扇形的起始弧度 + 对应的弧度的一半 // 5.2 半径 + 伸出去的长度 // 5.3 求得公式 // 5.3.1 outX = x0 + cos( angle ) * (r + outLine) // 5.3.2 outY = y0 + sin( angle ) * (r + outLine)
绘制描述
- 绘制矩形,我们以左上角的点为基准,x坐标不变,y坐标根据观察可得公式,this.space + index * (this.space + this.rectSpace),这样就可以画出n个矩形
- 绘制文字,与绘制矩形类似,算出公式并且调整样式对齐
绘制图片
绘制图片的三种方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// 创建对象
// dom元素创建对象
// var img = document.createElement('img');
// 构造函数创建对象
var img = new Image();
// 给图片添加加载完成事件
img.onload = function(){
// 实现图片绘制
// 绘制图片的三种方式
// 3参数 图片对象 canvas坐标x,canvas坐标坐标y
// ctx.drawImage(img,100,100);
// 5参数 图片对象 canvas坐标x,canvas坐标坐标y 图片宽 图片高
// 注意 是缩放 而不是裁剪
// ctx.drawImage(img,100,100,100,100);
// 9参数
// 图片对象 图片上定位的坐标 x y
// 在图片上截取多大的区域 w h
// 绘制在画布上的坐标 x y
// 图片大小 宽 高
ctx.drawImage(img,10,10,50,50,100,100,50,50)
}
// 参数
img.src = 'image/head.png';
</script>
</body>
</html>
- ctx.drawImage()
- 3参数 图片对象 canvas坐标x,canvas坐标坐标y ctx.drawImage(img,x,y)
- 5参数 图片对象 canvas坐标x,canvas坐标坐标y 图片宽 图片高 ctx.drawImage(img,x,y,w,h)
- 9参数 图片对象 图片上定位的坐标 x y 在图片上截取多大的区域 w h 绘制在画布上的坐标 x1 y1 图片w1h1宽高ctx.drawImage(img,x,y,w,h,x1,y2,w1,h1)
帧动画
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
var image = new Image();
// 给图片添加加载完成事件
image.onload = function(){
// 图片加载完成
// 动态的去获取当前图片的尺寸
var imageWidth = image.width;
var imageHeight = image.height;
// 计算出每一个小人物的尺寸
var personWidth = imageWidth/4;
var personHeight = imageHeight/4;
// 位截取图片
// 帧动画 在固定的时间间隔更换显示的图片 根据图片的索引
var index = 0;
// 绘制在画布的中心
var x0 = ctx.canvas.width /2;
var y0 = ctx.canvas.height /2;
ctx.drawImage(image,0,0,personWidth,personHeight,x0,y0,personWidth,personHeight);
// 设置定时器
setInterval(function(){
index++;
ctx.clearRect(0,0,ctx.canvas.height,ctx.canvas.width);
ctx.drawImage(image,index * personWidth,0,personWidth,personHeight,x0,y0,personWidth,personHeight);
if(index>=3){
index = -1;
}
},100)
}
// 参数
image.src = 'image/xp.png';
</script>
</body>
</html>
帧动画案例-小人行走
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
var Person = function (ctx) {
// 绘制工具
this.ctx = ctx || document.querySelector('canvas').getContext('2d');
// 图片路径
this.src = 'image/xp.png';
// 画布的大小
this.canvasWidth = this.ctx.canvas.width;
this.canvasHeight = this.ctx.canvas.height;
// 行走相关参数
this.stepSize = 10;
// 0 前 1 左 2 右 3 后 和图片的行数包含的图片对应上
this.direction = 0;
// 轴偏移 控制小人行走
this.stepX = 0;
this.stepY = 0;
// 初始化
this.init();
};
Person.prototype.init = function () {
var that = this;
// 1. 加载图片
this.loadImage(function (image) {
// 动态的去获取当前图片的尺寸
that.imageWidth = image.width;
that.imageHeight = image.height;
// 计算出每一个小人物的尺寸
that.personWidth = that.imageWidth / 4;
that.personHeight = that.imageHeight / 4;
// 绘制图片的起点
that.x0 = that.canvasWidth / 2 - that.personWidth / 2;
that.y0 = that.canvasHeight / 2 - that.personHeight / 2;
// 2. 默认绘制在中心位置正面朝外
that.ctx.drawImage(image,0,0,
that.personWidth,that.personHeight,
that.x0,that.y0,
that.personWidth,that.personHeight);
// 记录索引位置
that.index = 0;
// 3. 能通过方向键去控制人物行走
document.onkeydown = function(e){
if(e.keyCode == 40){
// 下
that.stepY ++;
that.direction = 0;
that.drawImage(image) ;
}else if (e.keyCode == 37){
// 左
that.stepX --;
that.direction = 1;
that.drawImage(image) ;
}else if (e.keyCode == 39){
// 右
that.stepX ++;
that.direction = 2;
that.drawImage(image) ;
}else if (e.keyCode == 38){
// 上
that.stepY --;
that.direction = 3;
that.drawImage(image) ;
}
}
});
};
// 加载图片
Person.prototype.loadImage = function (callback) {
var image = new Image();
image.onload = function () {
callback && callback(image);
};
image.src = this.src;
}
// 绘制图片
Person.prototype.drawImage = function (image) {
// console.log(this);
this.index ++;
// 清除画布
this.ctx.clearRect(0,0,this.ctx.canvas.height,this.ctx.canvas.width);
this.ctx.drawImage(image,
this.index * this.personWidth,this.direction * this.personHeight,
this.personWidth,this.personHeight,
this.x0 + this.stepX * this.stepSize,this.y0 + this.stepY * this.stepSize,
this.personWidth,this.personHeight);
if(this.index >= 3 ){
this.index = -1;
}
}
new Person();
</script>
</body>
</html>
- 模拟游戏中,角色的行走
- 图片是一张精灵图,通过修改精灵图里的定位与画布的位置,实现角色在画布中行走
canvas的转换
- ctx.translate(x,y) 平移 移动画布的原点
- ctx.scale(x,y) 表示宽高的缩放比例
- ctx.rotate(angle) 表示旋转角度
- 上述方法都与canvas坐标系坐标原点有关
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
canvas {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas width="400" height="400"></canvas>
<!-- js -->
<script>
// 获取元素
var myCanvas = document.querySelector('canvas');
// 获取上下文
var ctx = myCanvas.getContext('2d');
// ctx.translate(100,100);
// ctx.scale(0.5,1);
// ctx.rotate(Math.PI/6);
var startAngle = 0;
ctx.translate(100,100);
setInterval(function(){
startAngle += Math.PI/180;
ctx.rotate(startAngle);
ctx.strokeRect(-50,0,100,100);
},1000)
</script>
</body>
</html>