以前写了《教你:一天入门写网页》,讲的是最基础的HTML和CSS,只是能快速上手,哪怕就是HTML和CSS都还有非常多的东西。
前段时间看博友小凯哥的博客,他在写重学前端,写到HTML的语义化标签,我嗤之以鼻地说“不是所有浏览器都可行的标签是不用学的,JS才是前端的基础”,一个DIV可以走遍天下。
确实一个DIV就可以模拟近乎所有的标签,但如果你是前端的从业者肯定都要学,跳槽的时候面试题准备好了么。这次和大家来分享一下HTML5中很有趣的canvas。
HTML5 <canvas> 标签用于绘制图像(通过脚本,通常是 JavaScript)。不过,<canvas> 元素本身并没有绘制能力(它仅仅是图形的容器)。
① canvas的坐标系
canvas中的坐标系与数学的笛卡尔坐标系是不同的。
坐标以canvas画布的左上的角为原点(0,0),右侧横向为X轴,下侧竖向为Y轴。
② canvas的原理以及与svg的不同
<canvas>标记和 SVG 以及 VML 之间的一个重要的不同是,它有一个基于 JavaScript 的绘图 API,而 SVG 和 VML 使用一个 XML 文档来描述绘图。
这两种方式在功能上是等同的,任何一种都可以用另一种来模拟。从表面上看,它们很不相同,可是,每一种都有强项和弱点。例如,SVG 绘图很容易编辑,只要从其描述中移除元素就行。
要从同一图形的一个 标记中移除元素,往往需要擦掉绘图重新绘制它。
另外canvas不是矢量的,svg是矢量的,又由于svg是用XML描述的,意味着在网页中svg是dom,是dom就可以使用我们熟悉的css和js来制作动效了,所以用svg可以写一些小的游戏,下次有空我再来写一篇svg吧。
<canvas id="myCanvas" width="400" height="400"></canvas>
<!-- 在html上创建canvas -->
<script type="text/javascript">
var c=document.getElementById("myCanvas");
//用id获取canvas元素
var cxt=c.getContext("2d");
//创建 context 对象
cxt.moveTo(10,10);
//将绘图光标挪到(10,10)
cxt.lineTo(400,300);
cxt.lineTo(100,300);
cxt.stroke();
</script>
解释一下上面的代码:cxt.moveTo(10,10);
表示将绘图标挪到坐标(10,10),cxt.lineTo(400,300);
表示以(10,10)到(400,300)画直线,cxt.lineTo(100,300);
表示以(400,300)到(100,300)画直线。cxt.stroke();
表示执行以上绘图。
通过以上示例,我们可以画一个正方形
<canvas id="myCanvas2" width="400" height="400"></canvas>
<!-- 在html上创建canvas -->
<script type="text/javascript">
var c=document.getElementById("myCanvas2");
//用id获取canvas元素
var cxt=c.getContext("2d");
//创建 context 对象
cxt.moveTo(20,20);
cxt.lineTo(320,20);
cxt.lineTo(320,320);
cxt.lineTo(20,320);
cxt.lineTo(20,20);
cxt.strokeStyle="green";
cxt.stroke();
</script>
以上的示例,理解起来很简单,就是从(20,20)开始,链接(320,20),(320,320),(20,320),(20,20)而连接成了一个边长为300px的正方形,cxt.strokeStyle="green";
的意思是边线为绿色。strokeStyle
可以设置封闭图形的颜色。
当然,对于绘制矩形,有更方便的方法来写,用strokeRect(x,y,width,height)
<canvas id="myCanvas3" width="400" height="240"></canvas>
<!-- 在html上创建canvas -->
<script type="text/javascript">
var c=document.getElementById("myCanvas3");
var ctx=c.getContext("2d");
ctx.strokeStyle="orange";
ctx.lineWidth=5;
//线的宽度为5px
ctx.strokeRect(30,30,300,200);
</script>
绘制方块,用fillRect(x,y,width,height);
方块颜色用fillStyle
<canvas id="myCanvas7" width="400" height="240"></canvas>
<!-- 在html上创建canvas -->
<script type="text/javascript">
var c=document.getElementById("myCanvas7");
var ctx=c.getContext("2d");
ctx.fillStyle="#F5BDBB";
ctx.lineWidth=5;
//线的宽度为5px
ctx.fillRect(30,30,300,200);
</script>
应该注意到了,strokeStyle可以设置线的颜色,但其实除了可以设置单一颜色还可以设置渐变
<canvas id="myCanvas5" width="400" height="240"></canvas>
<!-- 在html上创建canvas -->
<script type="text/javascript">
var c=document.getElementById("myCanvas5");
var ctx=c.getContext("2d");
var gradient=ctx.createLinearGradient(0,0,400,0);
gradient.addColorStop("0","magenta");
gradient.addColorStop("0.5","blue");
gradient.addColorStop("0.7","skyblue");
gradient.addColorStop("1.0","red");
ctx.strokeStyle=gradient;
// 用渐变进行填充
ctx.lineWidth=5;
ctx.strokeRect(30,30,300,200);
</script>
context.createLinearGradient(x0,y0,x1,y1);x0代表渐变开始的x坐标,y0代表渐变开始y坐标,x1渐变结束的x坐标,y1渐变结束的y坐标。
strokeRect(30,30,300,200);
的四个参数分别为(X,Y,Widht,Height),什么意思呢,就是以(30,30)为原点,绘制宽度为300px,高度为200px的矩形。
更多属性和函数请查看HTML 5 Canvas 参考手册
通过以上的示例,我们可不可以画一些正多边形呢?应该非常简单(原谅我的狂妄自大,sin,cos,tan哪个是对边比临边我都忘记了的人,确实推导这个多边形坐标公式想了好一会儿),我们以正三角形为例来推导公式。
根据canvas的坐标系,绘制了如下正三角形,设边长为Z,并作辅助线三角形ABC的外切圆,外切圆圆心为O设为(x0,y0),三角形的三个顶点链接外切圆圆心O,由此正三角形被分成了3个等腰三角形。等腰三角形的顶角设为α,外切圆的半径设为r。
由上我们可得顶角 α=2π/3 ,由三角函数可以算出外切圆半径 r = z/(2*sin(α/2))
由三角函数可继续算出B点X坐标 XB= x0 + r*sinα
B点Y坐标XY = y0 - r*cosα (这里为什么是减,是因为canvas坐标系与笛卡尔坐标系的不同)
C点X坐标 XC = x0 + r*sin(α*2)
C点Y坐标 YC = y0 - r*cos(α*2)
由此我们已经可以得到通用公式,绘制了坐标为(200,200),边长为50px的正3~20多边形,代码如下:
<canvas id="canvas4" width="400" height="400" style="border: 0.0625rem;"></canvas>
<script>
// 绘制多边形函数
// x0,y0 : 正多边形外切圆圆心,即(x0,y0)
// n :正多边形的边数
// z: 正多边形每个边的边长
// dinJiao:连接n边形与外接圆圆心构成的等腰三角形的顶角
// r:外接圆的半径
function create_polygon(x0,y0,n,z){
var canvas = document.getElementById('canvas4');
var gudou = canvas.getContext("2d");
gudou.beginPath();
var dinJiao = 2*Math.PI/n;
var r = z/(2*Math.sin(dinJiao/2));
for (var i=0;i<n;i++) {
var x = x0 + r*Math.sin(dinJiao*i)
var y = y0 - r*Math.cos(dinJiao*i)
console.log(x,y)
gudou.lineTo(x,y);
}
gudou.closePath();//闭合线段,否则还需要lineTo第一个坐标点
gudou.stroke();
}
for (var i = 3; i < 20; i++) {
create_polygon(200,200,i,50);
}
</script>
context.arc(x,y,r,sAngle,eAngle,counterclockwise);用于创建圆形或部分圆。其中圆心坐标为(x,y),半径为r,起始角为sAngle,结束角为eAngle,counterclockwise为是否顺时针true为顺时针false为逆时针。
<canvas id="canvas7" width="400" height="400" ></canvas>
<script>
var canvas = document.getElementById('canvas7');
var gudou = canvas.getContext("2d");
gudou.beginPath();
gudou.arc(150,150,100,0*Math.PI/180,360*Math.PI/180,false);
gudou.closePath();
gudou.fillStyle="grey";
//设置填充颜色
gudou.fill();
//填充当前绘图(路径)
gudou.strokeStyle="red";
//设置线颜色
gudou.stroke();
</script>
可以看到上面的代码用closePath()
将绘制的线条结束,从而形成了一个闭合的图形,如果不写这一句,且开始角和结束角不是2π的话,可以绘制圆形曲线,自己试一试。
看到上面这个公式懵不懵逼,其实贝塞尔曲线就是我们各种绘图软件中的钢笔工具,比如ps,ai,sketch等等,控制点到结束点的连线就相当于绘图工具中的力臂。
在网上找到了一个贝塞尔曲线的示例程序,翻译了一下,可以很直观的了解贝塞尔曲线。
github:https://github.com/karlew/bezier-tool-for-canvas
下面这个不是图片,来拖动这几个点试试code
需要两个点。第一个点是用于二次贝塞尔计算中的控制点,第二个点是曲线的结束点。曲线的开始点是当前路径中最后一个点。如果路径不存在,那么请使用 beginPath()
和 moveTo()
方法来定义开始点。
说明,通过以下函数使用表示二次贝塞尔曲线的指定控制点,向当前路径添加一个点。context.quadraticCurveTo(cpx,cpy,x,y);
其中(cpx,cpy)表示控制点的坐标,(x,y)表示结束点的坐标
举个例子:绘制一个如下要求的贝塞尔曲线
- 开始点:(20,20)
- 控制点:(20,100)
- 结束点:(200,20)
<canvas id="canvas9" width="400" height="400" ></canvas>
<script type="text/javascript">
var canvas = document.getElementById('canvas9');
var gudou = canvas.getContext("2d");
gudou.beginPath();
gudou.moveTo(20,20)
gudou.quadraticCurveTo(20,100,200,20)
gudou.stroke();
</script>
需要三个点。前两个点是用于三次贝塞尔计算中的控制点,第三个点是曲线的结束点。曲线的开始点是当前路径中最后一个点。如果路径不存在,那么请使用 beginPath()
和 moveTo()
方法来定义开始点。
说明,通过以下函数使用表示三次贝塞尔曲线的指定控制点,向当前路径添加一个点。context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y);
其中(cp1x,cp1y)表示控制点2的坐标,(cp2x,cp2y)表示控制点2的坐标,(x,y)表示结束点的坐标。
其实上面这个不用理解,想了解贝赛尔曲线的过程,用上面的示例软件或者一切有钢笔功能的绘图软件都可以很直观地查看。
<canvas id="canvas10" width="400" height="400" ></canvas>
<script type="text/javascript">
var canvas = document.getElementById('canvas10');
var gudou = canvas.getContext("2d");
gudou.beginPath();
gudou.moveTo(20,20)
gudou.bezierCurveTo(20,100,200,100,200,20);
gudou.stroke();
</script>
试一试画一个NIKE的logo。
<canvas id="canvas16" width="400" height="400" style="border: 0.0625rem;"></canvas>
<script>
canvas = document.getElementById("canvas16");
gudou = canvas.getContext("2d")
gudou.strokeStyle = "#1572b5";
gudou.lineWidth = 2;
gudou.beginPath();
gudou.moveTo(156, 91);
gudou.bezierCurveTo(118, 197, 216, 147, 439, 88);
gudou.moveTo(439, 82);
gudou.lineTo(185,198)
gudou.bezierCurveTo( 128, 222,68, 185, 156, 89);
gudou.stroke();
gudou.closePath();
</script>
会用了贝塞尔曲线,那么我们把正N边形的公式改一下,试试能画出一朵花么?我感觉可以。懒得计算了,画出了个冠状病毒😂
<canvas id="canvas14" width="400" height="400" style="border: 0.0625rem;"></canvas>
<script>
function create_polygon(x0, y0, n, z) {
var canvas = document.getElementById('canvas14');
var gudou = canvas.getContext("2d");
gudou.beginPath();
var dinJiao = 2 * Math.PI / n;
var r = z / (2 * Math.sin(dinJiao / 2));
for (var i = 0; i < n; i++) {
var x = x0 + r * Math.sin(dinJiao * i)
var y = y0 - r * Math.cos(dinJiao * i)
console.log(x, y)
gudou.quadraticCurveTo(x0,y0,x,y)
if (i==n-1) {
gudou.quadraticCurveTo(x0,y0,x0,y0 - r)
// gudou.lineTo(x0,);
}
}
gudou.strokeStyle = "green";
gudou.stroke();
}
// 绘制中心点O(100,100),边长为50的正八边形
for (var i = 3; i < 15; i++) {
create_polygon(200, 200, i, 100);
}
</script>
再演示一下清除canvas的clearRect()
和旋转的功能rotate()
吧,加上定时器写了个例子
<canvas id="canvas15" width="400" height="400" style="border: 0.0625rem;"></canvas>
<script>
var cc = 0;
function create_polygon(x0, y0, n, z) {
var canvas = document.getElementById('canvas15');
var gudou = canvas.getContext("2d");
if (cc == 0) {
gudou.translate(200, 200);
}
gudou.clearRect(-200, -200, 800, 800);
gudou.beginPath();
gudou.arc(0, 0, 161, 0 * Math.PI / 180, 360 * Math.PI / 180, false);
gudou.closePath();
gudou.stroke();
cc++;
gudou.beginPath();
gudou.rotate(cc * Math.PI / 180);
var dinJiao = 2 * Math.PI / n;
var r = z / (2 * Math.sin(dinJiao / 2));
for (var i = 0; i < n; i++) {
var x = x0 + r * Math.sin(dinJiao * i)
var y = y0 - r * Math.cos(dinJiao * i)
console.log(x, y)
if (i != 0) {
gudou.quadraticCurveTo(x0, y0, x, y)
} else {
gudou.moveTo(x, y)
}
if (i == n - 1) {
gudou.quadraticCurveTo(x0, y0, x0, y0 - r)
}
}
gudou.closePath();
gudou.strokeStyle = "green";
gudou.stroke();
setTimeout(function() {
create_polygon(0, 0, 10, 100);
}, 300)
}
create_polygon(0, 0, 10, 100);
</script>
图形学实在高深,可惜我的专业连高数都没有。
哎,高数是我重修次数最多的学科
写的不错,渐变我没有写,你这里写了
写了这篇文章才发现这种东西其实还不好写
canvas我也非常少用,有机会再写写svg
厉害了我的哥!
不复杂,我就是三角函数还给老师了,写一写又要翻一下对边比临边是cos还是sin,哈哈
看了博主的文章受益匪浅
爱你我的涛