Canvas图形绘制 <small>API参考</small>
- 开始创建路径
ctx.beginPath()
,通过此方法避免前面的图形重复绘制。 - (以圆形为例)创建圆形路径
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)
<small>- x:圆心X坐标;
- y:圆心Y坐标;
- radius:圆半径;
- startAngle:起始弧度,一般为0;根据情况而定;
-
endAngle:结束弧度,
2 * Math.PI
表示360°; -
anticlockwise:可选值;默认为false,即顺时针绘制;如果为 true,逆时针绘制圆弧。
</small>
- 关闭路径
ctx.closePath()
:将笔点返回到当前子路径起始点的方法;它尝试从当前点到起始点绘制一条直线。 如果图形已经是封闭的或者只有一个点,那么此方法不会做任何操作。
<small>若不需要封闭非整圆,请勿使用该方法;否则会在非整圆终点至起始点绘制一条直线。</small>
- 设置画笔绘制样式<small>(填充
ctx.fillStyle
;划线ctx.strokeStyle
,线宽ctx.lineWidth
。)</small> - 开始绘制图形<small>(填充
ctx.fill()
,划线ctx.stroke()
)</small>
<!--canvas arc demo-->
<script>
"use strict"
window.onload = function() {
var canvas = document.getElementById("canvas");
if (canvas == null) {
return false;
}
// 获得一个2d渲染上下文
var ctx = canvas.getContext("2d");
for (var i = 1; i < 10; i++) {
// 创建路径;如果不重新创建路径,存在的图形路径会重复绘制
ctx.beginPath();
//创建图形路径;
ctx.arc(50 * i, 50 * i, 20 * i, 0, 2 * Math.PI, true);
//闭合路径;
ctx.closePath();
//设置填充颜色
ctx.fillStyle = "rgba(255,0,0,0.25)";
//开始填充图形
ctx.fill();
// 划笔样式
ctx.strokeStyle = "#ff0";
// 划线渲染
ctx.stroke();
}
}
</script>
<canvas id="canvas" width="1000" height="1000"></canvas>
</body>
moveTo & lineTo & beginPath & closePath
- moveTo(x,y):将画笔移动到x,y坐标(该坐标为第一个子路径的终点);
- lineTo(x,y):直线连接子路径的终点到x,y坐标(只连接,不绘制);
- beginPath():通过清空子路径列表开始一个新路径的方法。 当你想创建一个新的路径时,调用此方法;
- closePath():从当前点到起始点绘制一条直线。 如果图形已经是封闭的或者只有一个点,那么此方法不会做任何操作。
重点:没有子路径时(即没有moveTo或lineTo时),lineTo作用与moveTo相同
代码示例:
<body>
<script>
window.onload = function () {
var canvas = document.getElementById("canvas");
if (canvas == null) {
return false;
}
var context = canvas.getContext("2d");
//创建路径
context.beginPath();
//移动画笔 1.将画笔移动到50,50的坐标
context.moveTo(50, 50);
//画第一条线 2.从moveTo的50,50到lineTo的400,50坐标划一条线
context.lineTo(100, 50);
//画第二条线 3.从上一个lineTo的400,50到这个lineTo的50,400坐标划一条线
context.lineTo(50, 100);
//从当前点(50,400)到起始点(beginPath后moveTo的50,50)划一条线
context.closePath();
//画笔颜色
context.strokeStyle = "#ccc";
//线的宽度
context.lineWidth = "5";
//渲染,即开始绘画
context.stroke();
/*该例子中closePath()相当于lineto(50,50);故这条线是50,50到100,100两点的直线*/
context.lineTo(100, 100);
context.stroke();
/*清空路径列表,重新创建一个新的路径*/
context.beginPath();
/*当前不存在子路径,故此方法作用与moveTo(200,200)相同*/
context.lineTo(200, 200);
/*将200,200这个子路径终点连接到250,250*/
context.lineTo(250, 250);
/*将连接的直线绘制出来*/
context.stroke();
}
</script>
<canvas id="canvas" height="300" width="500" style="background-color: #ff0;"></canvas>
</body>
绘制贝塞尔曲线 <small>bezierCurve API参考</small>
-
参数:
- cp1x:第一个控制点的 x 轴坐标;
- cp1y:第一个控制点的 y 轴坐标;
- cp2x:第二个控制点的 x 轴坐标;
- cp2y:第二个控制点的 y 轴坐标;
- x:结束点的 x 轴坐标;
- y:结束点的 y 轴坐标。
- 重点:绘制beziercurve重点在于掌握cp1和cp2两个点的控制;曲线的起点为画笔的起点,曲线的终点为参数x,y的坐标,曲线的弯曲度由cp1和cp2两个点位置的牵引力决定。
<body>
<!-- beziercurve demo -->
<script>
window.onload = function() {
var canvas = document.getElementById("canvas");
if (canvas == null) {
return false;
}
var ctx = canvas.getContext("2d");
ctx.strokeStyle = "#ddd";
ctx.lineWidth = 1;
//画横线,y轴每隔10个像素画一条横线
for (var i = 0.5; i < 500; i++) {
//创建路径,如果没有此方法,循环会导致里面的线会重复渲染。可比较"画横线"与"画竖线"的区别。
//ctx.beginPath();
ctx.moveTo(0, i * 10);
ctx.lineTo(500, i * 10);
//若该渲染方法写在for循环之外,即使没有beginPath()方法,也不会重复渲染。
ctx.stroke();
}
//画竖线,x轴每隔10个像素画一条竖线
for (var i = 0.5; i < 500; i++) {
ctx.beginPath();
ctx.moveTo(i * 10, 0);
ctx.lineTo(i * 10, 500);
ctx.stroke();
}
// beziercurve begin
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.bezierCurveTo(100, 400, 400, 400, 400, 100);
//如果图形已经是封闭的或者只有一个点,那么此方法不会做任何操作。否则该方法会尝试从当前点到起始点绘制一条直线。
// ctx.closePath();
ctx.strokeStyle = "#f00";
ctx.stroke();
// beziercurve end
/*下方代码更好的说明bezierCurverTo六个参数的作用*/
//第一个控制点,曲线的牵引方向点;bezierCurveTo第一个x,y
drawArc(ctx, 100, 400, 5, 0, 2 * Math.PI, "#f00", "控制点1");
//第二个控制点,曲线的牵引方向点;bezierCurveTo第二个x,y
drawArc(ctx, 400, 400, 5, 0, 2 * Math.PI, "#f00", "控制点2");
//开始点,moveTo的点
drawArc(ctx, 100, 100, 5, 0, 2 * Math.PI, "#00f", "起始点");
//结束点,相当于lineTo的点;bezierCurveTo第三个x,y
drawArc(ctx, 400, 100, 5, 0, 2 * Math.PI, "#00f", "结束点");
}
// 绘制一个圆点及旁注
function drawArc(ctx, x, y, r, startAngle, endAngle, color, txt) {
ctx.beginPath();
ctx.arc(x, y, r, startAngle, endAngle);
ctx.fillStyle = color;
ctx.fill();
ctx.strokeStyle = color;
// 绘制字符
ctx.strokeText(txt, x + r + 5, y);
}
</script>
<canvas id="canvas" width="500" height="500"></canvas>
</body>
效果图如下:
Canvas的渐变
渐变过程中的颜色均使用
渐变对象.addColorStop(offset,color)
方法设置,多少种颜色渐变就添加多少次此方法。
- 线性渐变LinearGradient
该渐变对象只定义渐变的起始与终点位置及渐变的角度,在该两点位置之间实现对应角度的渐变。
渐变的有效范围:在canvas范围内根据两点位置及角度决定了渐变范围。<small>关于渐变色的角度:渐变色位于两点构成直线的垂直线范围内</small>
1. 创建LinearGradient对象
`var lgradient = ctx.createLinearGradient(xstart,ystart,xend,yend)`
2. 添加渐变颜色`lgradient.addColorStop(offset,color)`<small>多少种颜色渐变就添加多少次此方法</small>
3. 将图形绘制(`ctx.fillRect等`)在渐变对象有效的canvas范围内(非渐变对象的参数范围)
<!--LinearGradient & 角度弧度转化 & 根据角度、圆心、半径获取旋转后的目标坐标 DEMO-->
<body>
<script>
window.onload = function() {
var canvas = document.getElementById("canvas");
if (canvas == null) {
return false;
}
var ctx = canvas.getContext("2d");
// 创建一个线性渐变对象,并指定起始x,y坐标及结束x,y坐标
var gradient = ctx.createLinearGradient(0, 0, 100, 50);
// 渐变色添加 begin;真正的渐变为gradient范围内的50%到80%之间,从白色渐变到绿色
// offset=0.8,表示green的正色在gradient范围的80%处(从左往右);该offset后的范围均为green正色
gradient.addColorStop(0.8, "green");
// offset=0.5,表示white的正色在gradient范围的50%处(从左往右);该offset前的范围均为white正色
gradient.addColorStop(0.5, "white");
// 渐变色添加 end
ctx.fillStyle = gradient;
// 此处300已超出LinearGradient对象的范围及角度产生的渐变范围,故超出有效渐变范围会根据addColorStop设置的偏移和颜色决定,此处显示green色。
ctx.fillRect(10, 10, 300, 100);
// 渐变的范围,超出该范围,都使用接近的正色
ctx.strokeStyle = "#E3C41B";
ctx.strokeRect(0, 0, 100, 50);
// 绘制渐变的方向
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(100, 50);
ctx.stroke();
// 求斜线的长度,得出半径
var r = Math.sqrt(Math.pow(100, 2) + Math.pow(50, 2)) / 2;
ctx.beginPath();
ctx.moveTo(50, 25);
var degree = getDegree(0, 0, 100, 50);
// 注:*****所有的角度都根据当时坐标点上的坐标轴计算,正数则按顺时针计算*****
// 1角度=π/180弧度
//dx 应该是顺时针偏移斜线与x坐标轴的顺时针夹角+90度角算出的x轴坐标
var dx = 50 + Math.cos(degree * (Math.PI / 180) + Math.PI / 2) * r;
var dy = 25 + Math.sin(degree * (Math.PI / 180) + Math.PI / 2) * r;
ctx.lineTo(dx, dy);
ctx.strokeStyle = "#f00";
ctx.stroke();
// 弧度的辅助线
ctx.beginPath();
ctx.moveTo(75, 25);
var cpx1 = cpx2 = 100 - 35;
var cpy1 = cpy2 = dy - 30;
ctx.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, dx + (50 - dx) / 2, (dy - 25) / 2 + 25);
ctx.stroke();
ctx.strokeStyle = "#000";
ctx.strokeText("dx,dy参数中的弧度", cpx1, cpy1);
// 中心点坐标轴
ctx.beginPath();
ctx.moveTo(0, 25);
ctx.lineTo(100, 25);
ctx.strokeText("x轴", 100, 25);
ctx.moveTo(100 * 0.5, 0);
ctx.lineTo(100 * 0.5, 100);
ctx.strokeText("y轴 offset:0.5", 100 * 0.5, 100);
// 0.8offset的垂直线
ctx.moveTo(100 * 0.8, 0);
ctx.lineTo(100 * 0.8, 80);
ctx.strokeText("offset:0.8", 100 * 0.8, 80);
ctx.strokeStyle = "#00a";
ctx.stroke();
// 注释
ctx.strokeText("黄色斜线位置与方向:", 10, 150);
ctx.strokeText("offset就是颜色正色在该斜线x轴上的偏移量;", 10, 170);
ctx.strokeText("图中所示offset与黄色斜线相交点:0.8与0.5两个点;", 10, 190);
ctx.strokeText("两色之间做渐变,根据斜线方向及整个屏幕范围渐变;", 10, 210);
ctx.strokeText("将图形的style设置为渐变对象,并在渐变屏幕范围", 10, 230);
ctx.strokeText("内绘制即可得到渐变效果", 10, 250);
}
//获取两点之间的直线与起始点坐标x轴顺时针的角度
function getDegree(sx, sy, dx, dy) {
// 直角的边长
var x = Math.abs(sx - dx);
var y = Math.abs(sy - dy);
// 根据勾股定理 斜边的平方 = 两直角边的平方之和
var z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
// cos
var cos = x / z;
// 获取弧度
var angle = Math.acos(cos);
// 转化为角度;1弧度 = 180/π角度
var degree = 180 / Math.PI * angle;
console.log(degree);
return degree;
}
</script>
<canvas id="canvas" height="500" width="500"></canvas>
以上代码效果图如下所示:
- 径向渐变RadialGradient
该渐变对象实现射线式径向渐变,其有效范围为:通过起始圆形两边射线到结束圆形两边并相汇处
1. 创建RadialGradient对象
`var lgradient = ctx.createRadialGradient(xstart,ystart,rstart,xend,yend,rend)`
2. 添加渐变颜色`lgradient.addColorStop(offset,color)`<small>多少种颜色渐变就添加多少次此方法</small>
3. 将图形绘制(`ctx.fillRect等`)在渐变对象有效范围内(非渐变对象的参数范围)
<!-- RadialGradient Demo-->
<body>
<script>
window.onload = function() {
var canvas = document.getElementById("canvas");
if (canvas == null) {
return false;
}
var ctx = canvas.getContext("2d");
// 创建射线(径向)渐变对象,六个参数分别是:起始圆形坐标x,y,半径,结束圆形坐标x,y,半径;
// 通过起始圆形两边射线到结束圆形两边并相汇是结束
var radiaGradient = ctx.createRadialGradient(50, 50, 50, 200, 200, 400);
// 设置三个渐变颜色
radiaGradient.addColorStop(0.3, "#ff0");
radiaGradient.addColorStop(0.8, "rgb(255,140,0");
radiaGradient.addColorStop(1, "rgb(255,0,0");
// 设置填充颜色为渐变对象
ctx.fillStyle = radiaGradient;
// 绘制一个矩形
ctx.fillRect(0, 0, 500, 500);
}
</script>
<canvas id="canvas" height="500" width="500"></canvas>
以上代码效果图如下:
Canvas的缩放、平移、旋转与ctx的状态保存及还原
- 缩放:scale(x,y):x,y为缩放因子,会影响缩放对象的所有参数(如宽高、坐标、偏移量,但不影响角度);
- 平移:translate(x,y):x,y为坐标轴的偏移量,即x表示目标x轴与当前x轴的距离,y表示目标y轴与当前y轴的距离;
- 旋转:rotate(angle):旋转的弧度;angle = degree * (Math.PI / 180) ;degree为角度。旋转后,ctx的原坐标轴也会随着旋转,旋转后的ctx状态会导致后期绘制的图形其坐标轴也是基于旋转后的坐标轴绘制的;
注意三个方法前后顺序的变化所产生的影响。
1.translate、rotate、scale作用的是整个ctx
2.rotate的旋转的是ctx环境,是根据ctx的当前坐标轴x轴进行的,正弧度表示顺时针,反之逆时针;旋转后起始坐标轴更新到旋转后的坐标轴状态
3.translate的参数为距x轴的距离,距y轴的距离;即起始点x,y的偏移量;同样是根据ctx的当前状态坐标轴进行
translate、rotate、scale会被ctx记录状态,值会被叠加
- ctx.save():将当前上下文状态保存,以便后期使用;以便某状态被覆盖的时候使用;
- ctx.restore():还原至最近save的上下文状态。
<!--scale translate rotate & save restore demo-->
<body>
<script>
window.onload = function() {
var canvas = document.getElementById("canvas");
if (canvas == null) {
return false;
}
var ctx = canvas.getContext("2d");
ctx.strokeStyle = "#ddd";
ctx.lineWidth = 1;
//画横线,y轴每隔10个像素画一条横线
for (var i = 0.5; i < 500; i++) {
//创建路径,如果没有此方法,循环会导致里面的线会重复渲染。可比较"画横线"与"画竖线"的区别。
//ctx.beginPath();
ctx.moveTo(0, i * 10);
ctx.lineTo(500, i * 10);
//若该渲染方法写在for循环之外,即使没有beginPath()方法,也不会重复渲染。
ctx.stroke();
}
//画竖线,x轴每隔10个像素画一条竖线
for (var i = 0.5; i < 500; i++) {
ctx.beginPath();
ctx.moveTo(i * 10, 0);
ctx.lineTo(i * 10, 500);
ctx.stroke();
}
ctx.fillStyle = "#ff0";
ctx.fillRect(50, 50, 50, 100);
// 保存当前状态
ctx.save();
ctx.translate(50, 50);
ctx.fillStyle = "#0f0";
ctx.fillRect(50, 50, 50, 100);
// 使用restore后,最近的save状态就会被弹出;
ctx.restore();
// 重新保存还原后得到的第一个save状态
ctx.save();
for (var i = 0; i < 3; i++) {
// ctx.scale(0.9, 0.9);
// x,y分别正向偏移各50像素
ctx.translate(50, 50);
ctx.fillStyle = "rgba(255,0,0,0.25)";
// 以偏移后的50,50坐标点做为起始的0,0坐标点,下方的50,50坐标点以原始0,0计算则为100,100的坐标.
ctx.fillRect(50, 50, 100, 20);
}
// 恢复到save保存的状态
ctx.restore();
ctx.translate(300, 300);
// ctx会记录绘制的状态,循环会导致在记录的状态下不断的scale,不断的translate。
for (var i = 0; i < 50; i++) {
// 先缩放,后平移,会导致平移的50,50偏移量随缩放因子改变,及x,y实际偏移量分别为45,45。
// 即使先平移,后缩放,因循环也会导致平移的参数随缩放因子发生变化。
// 说明ctx的状态被实时覆盖保存,除非通过save()存档副本
ctx.scale(0.9, 0.9);
ctx.translate(10, 10);
// 顺时针30度角旋转
ctx.rotate(30 * Math.PI / 180);
ctx.fillStyle = "rgba(0,0,255,0.25)";
// scale的缩放因子为0.9,所以实际坐标为偏移后的x坐标+45,偏移后的y坐标+0;宽高为90,18。
ctx.fillRect(50, 0, 100, 20);
}
}
</script>
<canvas id="canvas" width="500" height="500"></canvas>
</body>
以上代码效果图如下:
/* translate rotate 重要知识点 */
/* 重点:
* 1.translate、rotate、scale作用的是整个ctx
* 2.rotate的旋转的是ctx环境,是根据ctx的当前坐标轴x轴进行的,正弧度表示顺时针,反之逆时针;旋转后起始坐标轴更新到旋转后的坐标轴状态
* 3.translate的参数为距x轴的距离,距y轴的距离;即起始点x,y的偏移量;同样是根据ctx的当前状态坐标轴进行
* translate、rotate、scale会被ctx记录状态,值会被叠加
* */
var secCanvas = document.getElementById("secCanvas");
secCanvas.style.backgroundColor = "#ccc";
ctx = secCanvas.getContext("2d");
for(var i = 0; i < 2; i++) {
ctx.beginPath();
//标识ctx的起始点 beigin (蓝色原点)
ctx.arc(0, 0, 5, 0, 2 * Math.PI, false);
ctx.fill();
//标识ctx的起始点 end
ctx.fillStyle = "royalblue";
//scale不影响角度,只影响宽高、坐标、偏移量
//ctx.scale(0.9,0.9);
var angle = 45 * (Math.PI / 180);
//基于起点坐标轴x轴顺时针旋转45度角
//rotate后,ctx的整个坐标轴就旋转了45度角(图中的红线为旋转后的x轴)
ctx.rotate(angle);/*因为ctx会更新状态,所以会作用到第二次循环*/
ctx.save(); /*保存旋转后的状态,以便下方划坐标轴用*/
//划旋转angle弧度后的线条 beigin
ctx.beginPath();
ctx.moveTo(0, 0);
//ctx已经旋转,这里获取旋转角度后对应100半径的坐标点,角度只需0即可
var dx = Math.cos(0) * 500;
var dy = Math.sin(0) * 500;
ctx.lineTo(dx, dy);
ctx.strokeStyle = "#FF0000";
ctx.stroke();
ctx.font = "14px Consolas";
ctx.strokeStyle = "#228B22";
ctx.strokeText("第"+(i+1)+"个循环旋转45度后的x坐标轴", dx, dy)
//划旋转angle弧度后的线条 end
//标识未translate(50,0)前的rect起始点(红色点)
ctx.strokeText("第"+(i+1)+"个循环旋转45度后的100,0坐标点", 100, 0)
ctx.beginPath();
ctx.arc(100, 0, 5, 0, 2 * Math.PI, false);
ctx.fillStyle = "#FF0000";
ctx.fill();
//基于起点坐标轴x,y轴的偏移距离
ctx.translate(50, 0);
ctx.fillRect(100, 0, 100, 50);
//标识translate(50,0)后的rect起始点(黄色点)
ctx.strokeText("第"+(i+1)+"个循环旋转45度且偏移50,0后的100,0坐标点", 100, 20)
ctx.beginPath();
ctx.arc(100, 0, 5, 0, 2 * Math.PI, false);
ctx.fillStyle = "#ff0";
ctx.fill();
//旋转45度后的坐标轴
ctx.restore(); /*还原到未translate前*/
ctx.beginPath();
//划第一个100,0坐标点的y轴
//移动到第一个100,0坐标点(红色)
ctx.moveTo(100, -100);
ctx.lineTo(100, 100);
ctx.stroke();
ctx.font = "14px Arial";
ctx.strokeText("第"+(i+1)+"个循环旋转45度后的y坐标轴", 110, -100);
//划第二个100,0坐标点的y轴
ctx.beginPath();
//基于第一个100,0坐标点,x轴方向偏移50
ctx.translate(50, 0)/*将作用于第二次循环的ctx起始点*/
//实际moveTo(150,-100)
ctx.moveTo(100, -100);
ctx.lineTo(100, 100);
ctx.stroke();
}