HTML5标准中增加的canvas元素可以进行图形图像的绘制工作,为客户端提供更加丰富的编程体验。本文介绍如何使用JavaScript代码和canvas元素绘制图形、图像、文本,以及如何使用渐变色;最后,会创建一个小球在canvas元素中不断运动、回弹的综合示例。
canvas有一些基本的属性,如id属性值可以用于JavaScript代码获取canvas元素,width和height属性指定canvas的宽度的高度。下面的代码会在页面中创建一个canvas元素,并做一些初始化工作。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <canvas id="cv" width="600" height="400" style="border:2px solid gray;"></canvas> </body> </html> <script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); g.beginPath(); // 绘制 // g.closePath(); </script>
代码中创建了id为"cv"的canvas元素,宽度为600像素,高度为400像素,边框宽度2像素,颜色为灰色(gray)。JavaScript代码中,首先,使用document对象的getElementById()方法获取canvas元素,然后,使用canvas元素的getContext("2d")方法获取图形绘制的上下文对象;一组绘制操作使用图形对象的beginPath()开始,由closePath()方法关闭,如果只有一组绘制操作,也可以省略beginPath()和closePath()方法。下面介绍各种图形、文本和图像的绘制。
绘制线段相关的方法包括:
此外,指定线条宽度可以使用lineWidth属性设置,单位为像素;strokeStyle属性指定线条的颜色,可以使用颜色名或CSS中支持的颜色格式;lineCap属性则指定线段两端的线帽风格,其值包括:
下面的代码,通过两条线段绘制了一条折线。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); g.beginPath(); // 绘制 g.lineWidth = 5; g.lineCap = "round"; g.moveTo(10, 10); g.lineTo(200, 100); g.lineTo(500, 50); g.strokeStyle = "red"; g.stroke(); // g.closePath(); </script>
绘制效果如下图所示。
绘制矩形时,同样可以使用lineWidth属性设置线条宽度,使用strokeStyle属性设置线条颜色,矩形参数则使用rect()方法指定,参数分别是左上角水平坐标和垂直坐标、矩形宽度和高度。下面的代码演示了矩形的绘制。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); g.beginPath(); // 绘制 g.lineWidth = 5; g.rect(60, 60, 200, 100); g.strokeStyle = "red"; g.stroke(); // g.closePath(); </script>
创建填充矩形参数时使用fillRect()方法,参数分别是左上角水平坐标和垂直坐标、矩形宽度和高度。填充颜色使用fillStyle属性指定。下面的代码会绘制一个线条宽度为10像素的矩形和一个填充矩形。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); g.beginPath(); // 绘制 g.lineWidth = 10; g.rect(60, 60, 200, 100); g.rect(60, 180, 200, 100); g.strokeStyle = "red"; g.stroke(); // 填充矩形 g.fillStyle = "yellow"; g.fillRect(60, 60, 200, 100); // g.closePath(); </script>
请注意观察矩形和填充矩形的绘制特点,上面的图形,矩形和填充矩形使用了相同的参数,而绘制的矩形区域有所不同。这里,矩形的左上角坐标是设置坐标分别减去线宽(lineWidth)的一半,所占区域的宽度是设置宽度加上线宽,所占高度则是设置高度加上线宽;而填充矩形则是按设置的坐标和尺寸绘制,工作中应计算好不同的图形实际绘制区域的位置和尺寸。
多边形的绘制,可以由多条线段绘制成一个封闭图形来实现,如下面的代码绘制了一个三角形。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); g.beginPath(); // 绘制 g.lineWidth = 3; g.moveTo(100, 100); g.lineTo(50, 200); g.lineTo(100, 300); g.lineTo(100, 100); g.strokeStyle = "red"; g.stroke(); // g.closePath(); </script>
绘制封闭图形时,起始坐标与结束坐标应相同,绘制效果如下图所示。
绘制填充封闭图形时,moveTo()和lineTo()方法只需要指定顶点坐标,并使用fillStyle属性设置填充色,使用fill()方法提交绘制操作。下面的代码演示了相关应用。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); g.beginPath(); // 绘制 g.lineWidth = 2; g.moveTo(100, 100); g.lineTo(50, 200); g.lineTo(100, 300); g.lineTo(100, 100); g.strokeStyle = "red"; g.stroke(); // g.closePath(); // g.beginPath(); g.moveTo(100, 100); g.lineTo(50, 200); g.lineTo(100, 300); g.fillStyle = "yellow"; g.fill(); g.closePath(); // g.beginPath(); g.moveTo(300, 100); g.lineTo(250, 200); g.lineTo(300, 300); g.fillStyle = "yellow"; g.fill(); g.closePath(); </script>
代码执行效果如下图所示。
绘制圆形和弧线都使用arc()方法,其参数依次为圆心X坐标、圆心Y坐标、圆的半径、开始角度、结束角度、是否逆时针方向;其中,角度使用π值。如下面代码可以绘制一个圆形。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); g.beginPath(); // 绘制 g.lineWidth = 5; g.arc(300, 200, 100, 0, 2 * Math.PI); g.strokeStyle = "blue"; g.stroke(); // g.closePath(); </script>
下图显示了arc()参数设置的示意图,其中,(x,y)为圆心坐标,水平方向向右为0π,向左为1π,垂直方向向下为0.5π,向上为1.5π。
下面的代码,通过两条弧线组成一个圆形。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); // g.beginPath(); g.lineWidth = 5; g.arc(300, 200, 100, 0, Math.PI); g.strokeStyle = "blue"; g.stroke(); g.closePath(); // g.beginPath(); g.lineWidth = 5; g.arc(300, 200, 100, 0, Math.PI, true); g.strokeStyle = "red"; g.stroke(); g.closePath(); </script>
本例,下面的半圆使用默认的顺时针方向绘制,上面的半圆则使用逆时针方向绘制,结果如下图所示。
绘制填充圆形时,同样使用fillStyle属性设置填充色,使用fill()方法完成绘制,如下面的代码。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); // g.beginPath(); g.lineWidth = 5; g.arc(300, 200, 100, 0, 2 * Math.PI); g.fillStyle = "blue"; g.fill(); g.closePath(); </script>
arcTo()方法可以定义基于角的两条边内切圆的弧线,参数依次为A点水平坐标、A点垂直坐标、B点水平坐标、B点垂直坐标、内切圆的半径;A点为角的顶点,一条边是经过AB两点的线;另一条边上的点可以使用moveTo()方法指定,这里称为C点,也是弧线的起始点,则另一条边是连接AC两点的线段。arcTo()方法绘制的弧线起点在起始点(C点),然后经过内切圆与线段AC和线段AB的切点。下面的代码演示了arcTo()方法的应用,并绘制了两条线段作为参考。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); g.lineWidth = 3; // g.beginPath(); g.moveTo(50, 50); g.lineTo(300,50); g.lineTo(50, 200); g.strokeStyle = "gray"; g.stroke(); g.closePath(); // g.beginPath(); g.moveTo(50, 50); g.arcTo(300, 50, 50, 200, 50); g.strokeStyle = "black"; g.stroke(); g.closePath(); </script>
绘制效果如下图所示,A、B、C为标注的三个坐标点。
绘制椭圆时使用ellipse()方法,其参数依次是中心X坐标、中心Y坐标、水平方向半径、垂直方向半径、旋转角度、开始角度、结束角度、是否逆时针方向绘制。下面的代码代码绘制了两个椭圆形。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); // g.lineWidth = 5; g.strokeStyle = "blue"; // g.beginPath(); g.ellipse(150, 200, 100, 50, 0, 0, 2 * Math.PI); g.stroke(); g.closePath(); // g.beginPath(); g.ellipse(400, 200, 100, 50, 0.2 * Math.PI, 0, 2 * Math.PI); g.stroke(); g.closePath(); </script>
下面的代码演示了绘制填充椭圆形。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); // g.beginPath(); g.ellipse(150, 200, 100, 50, 0, 0, 2 * Math.PI); g.fillStyle = "blue"; g.fill(); g.closePath(); </script>
首先介绍bezierCurveTo()方法,用于定义贝塞尔曲线的绘制参数。绘制贝塞尔曲线需要定义四个点,绘制起始点使用moveTo()方法指定,bezierCurveTo()方法的6个参数分别指定第一控制点、第二控制点、结束点的坐标。下面的代码演示了贝塞尔曲线的绘制。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); g.lineWidth = 3; // g.beginPath(); g.moveTo(50, 300); g.bezierCurveTo(150,100,350,300,500,30); g.strokeStyle = "blank"; g.stroke(); g.closePath(); // g.strokeStyle = "gray"; g.beginPath(); g.moveTo(50, 300); g.lineTo(150, 50); g.stroke(); g.closePath(); // g.beginPath(); g.moveTo(350, 300); g.lineTo(500, 30); g.stroke(); g.closePath(); </script>
quadraticCurveTo()方法用于绘制二次贝塞尔曲线,需要三个点坐标,其中,起始点坐标使用moveTo()方法指定,quadraticCurveTo()方法的4个参数,分别定义控制点坐标和结束点坐标。下面的代码演示了二次贝塞尔曲线的绘制。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); g.lineWidth = 3; // g.beginPath(); g.moveTo(100, 300); g.quadraticCurveTo(100,100,350,50); g.strokeStyle = "blank"; g.stroke(); g.closePath(); // g.strokeStyle = "gray"; g.beginPath(); g.moveTo(100, 100); g.lineTo(100, 300); g.stroke(); g.closePath(); // g.beginPath(); g.moveTo(100, 100); g.lineTo(350, 50); g.stroke(); g.closePath(); </script>
绘制文本时可以关注如下属性和方法:
下面的代码演示了文本绘制的实现。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); g.lineWidth = 3; // g.beginPath(); g.lineWidth = 1; g.strokeStyle = "red"; g.font = "bold 50px Arial"; g.strokeText("绘制文本ABCabc123",50,50); g.closePath(); // g.beginPath(); g.fillStyle = "blue"; g.font = "bold 50px Arial"; g.fillText("绘制文本ABCabc123", 50, 150); g.closePath(); </script>
绘制图像是使用drawImage()方法,其完整参数设置如下:
此外,绘制不需要裁剪和缩放的图像时可以只使用三个参数,依次为图像对象、绘制区域左上角水平坐标和垂直坐标;需要指定绘制区域尺寸时,可以在上述三个参数后再指定区域的宽度和高度,绘制的图像会按指定的尺寸缩放。
下面的代码演示了读取、裁剪和绘制/demo/earth.jpg图片的操作。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <canvas id="cv" width="800" height="550" style="border:2px solid gray;"></canvas> </body> </html> <script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); // let img = new Image() img.src = "/demo/earth.jpg"; // g.beginPath(); g.drawImage(img, 0, 0); g.drawImage(img, 10, 220, 400, 92); g.drawImage(img, 200, 0, 400, 195, 10, 330, 400, 195); g.closePath(); </script>
本例使用的图像尺寸为800*195像素,代码中修改了canvas元素的尺寸,宽度为800像素,高度为550像素,图像绘制效果如下图所示。
代码中,第一个drawImage()方法绘制了完整的图像。第二个drawImage()方法通过设置宽度和高度指定了绘制区域的尺寸,图像会缩放会显示。第三个drawImage()方法裁剪了图像的一部分,然后绘制到canvas元素中。
此外,图像也可以从页面中的img元素中获取,如下面的代码。
let img = document.getElementById("earth"); g.drawImage(img,0,0);
代码会从id为earth的元素中获取图像并绘制到canvas元素的左上角。
绘制填充图形或文本时,除了使用颜色填充,还可以使用图像或创建渐变色填充,下面的代码,先来看如何使用图像填充文本。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); // g.beginPath(); let img = new Image() img.src = "/demo/moon1.jpg"; let pattern = g.createPattern(img, "repeat"); g.fillStyle = pattern; g.font = "bold 50px Arial"; g.fillText("图像填充文本",50,50); g.closePath(); </script>
下图中,上面为/demo/moon1.jpg图片,尺寸为800*50像素;下面为填充文本效果。
canvas支持线性渐变色和放射性渐变色,其中,线性渐变色使用createLinearGradient()方法创建,其参数依次为渐变起始X坐标、起始Y坐标、结束X坐标、结束Y坐标。下面的代码演示了水平方向线性渐变色的应用。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); // g.beginPath(); let lg = g.createLinearGradient(50, 50, 250, 50); lg.addColorStop(0, "yellow"); lg.addColorStop(1, "red"); g.fillStyle = lg; g.rect(50, 50, 200, 100); g.fill(); g.closePath(); </script>
代码显示效果如下图所示。
创建渐变色时,还需要在渐变对象中使用addColorStop()方法指定位置和颜色,其中,参数一是0到1的数值,表示颜色的位置,如0表示起始位置,1表示结束位置,0.5表示中间位置。参数二指定颜色,可以使用颜色名称,也可以使用CSS支持的颜色格式,如十六进制颜色值等,如"red"、"#FF0000"都表示红色。
创建水平方向的渐变色时,应注意createLinearGradient()方法中的两个Y坐标应相同,如前例中的代码;创建垂直方向的渐变色时,两个X坐标应相同,如下面的代码。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); // g.beginPath(); let lg = g.createLinearGradient(50, 50, 50, 150); lg.addColorStop(0, "yellow"); lg.addColorStop(1, "#FF0000"); g.fillStyle = lg; g.rect(50, 50, 200, 100); g.fill(); g.closePath(); </script>
显示效果如下图所示。
下面的代码演示了从左上角到右下角的渐变效果。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); // g.beginPath(); let lg = g.createLinearGradient(50, 50, 250, 150); lg.addColorStop(0, "yellow"); lg.addColorStop(1, "red"); g.fillStyle = lg; g.rect(50, 50, 200, 100); g.fill(); g.closePath(); </script>
createRadialGradient()方法创建放射性渐变图像,其参数依次为第一个圆形圆心的X坐标、Y坐标、半径、第二个圆形圆心的X坐标、Y坐标、半径;定义放射性渐变图像对象后,同样需要使用addColorStop()方法添加颜色。下面的代码演示了放射性渐变图像的实现。
<script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); // g.beginPath(); let rg = g.createRadialGradient(300,200,50,300,200,150); rg.addColorStop(0, "yellow"); rg.addColorStop(1, "green"); g.fillStyle = rg; g.arc(300, 200, 150, 0, 2 * Math.PI); g.fill(); g.closePath(); </script>
下面的代码会创建一个困在屏幕中的小球,小球会在canvas元素区域不停的运动,碰撞到边界时会改变方向回弹。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>困在屏幕里的小球</title> </head> <body> <canvas id="cv" width="600" height="400" style="border:2px solid gray;"></canvas> <div> <button type="button" onclick="gameState=1;">开始</button> <button type="button" onclick="gameState=0;">暂停</button> </div> </body> </html> <script> let cv = document.getElementById("cv"); let g = cv.getContext("2d"); // 屏幕尺寸 const screenWidth = 600; const screenHeight = 400; // 小球参数 let ballRadius = 16; let speedX = 10; let speedY = 10; // let curX = 0; let curY = 0; let nextX = 0; let nextY = 0; // 游戏状态,1执行,0暂停 let gameState = 0; // 绘制小球 function drawBall(cx, cy) { g.beginPath(); g.arc(cx, cy, ballRadius, 0, 2 * Math.PI); g.fillStyle = "blue"; g.fill(); g.closePath(); } // 游戏循环,每帧代码 function gameLoop() { if (gameState === 0) return; if (gameState === 1) { // 清屏 g.clearRect(0, 0, screenWidth, screenHeight); // 计算小球位置 nextX = curX + speedX; nextY = curY + speedY; // 左右边界碰撞检查 if (nextX < ballRadius) { nextX = ballRadius; speedX = 0 - speedX; } else if (nextX > screenWidth - ballRadius) { nextX = screenWidth - ballRadius; speedX = 0 - speedX; } // 上下边界碰撞检查 if (nextY < ballRadius) { nextY = ballRadius; speedY = 0 - speedY; } else if (nextY > screenHeight - ballRadius) { nextY = screenHeight - ballRadius; speedY = 0 - speedY; } // 保存当前坐标 curX = nextX; curY = nextY; // 绘制小球 drawBall(curX, curY); } } // 设置小球开始位置 curX = Math.floor(Math.random() * 300) + ballRadius; curY = Math.floor(Math.random() * 200) + ballRadius; drawBall(curX, curY); // 开始游戏 setInterval(gameLoop, 25); </script>
页面初始画面如下图。请注意,小球的初始位置是随机生成的,所以,每次开始的小球位置并不相同。
页面中定义了三个主要元素,分别是:
JavaScript代码中,首先定义了一系列的变量和常量,下面分类说明。
cv这是获取了canvas元素对象,g变量则是绘制所需的上下文对象。
screenWidth和screenHeight常量分别定义了小球活动区域的宽度和高度,和canvas元素的宽度和高度相同。
小球参数中,ballRadius变量定义了小球的半径,speedX变量定义了小球水平方向运动的速度,speedY变量定义了小球垂直方向运动的速度。curX和curY变量为小球当前坐标,nextX和nextY变量为小球下一帧到达的坐标,这四个变量会在计算小球位置时使用。
gameState变量表示游戏运行状态,约定1表示正常运行,0表示暂停。
drawBall()函数的功能是在canvas元素中绘制小球,参数为小球圆心的坐标。
gameLoop()函数定义了处理小球运动每一帧的代码,首先,游戏状态(gameState)为0时什么也不做,保持静态。游戏状态(gameState)为1时,会调用clearRect()方法清除canvas元素,为下次绘制小球做准备。clearRect()方法的参数是清除区域的左上角X坐标和Y坐标、清除区域的宽度和高度,代码中设置的参数会清理canvas元素的全部区域。
计算小球的新位置时,首先使用nextX和nextY分别加上水平方向速度(speedX)和垂直方向速度(speedY)计算下次显示的位置。然后会进行四个边界的碰撞测试,当新位置到达边界时,会改变其速度,使用0减去速度,得到其相反的数据,这样,小球就会向相反的方向运动。curX和curY变量会保存小球的新坐标,并调用drawBall()函数绘制小球。
代码的最后,先使用随机数给出小球的初始位置,然后调用drawBall()函数显示小球。启动游戏循环时使用了setInterval()函数,参数一指定游戏的每一帧会执行gameLoop()函数,参数二指定每帧间隔为25毫秒,即每秒40帧。