浏览器2D_canvas
7/8/2024, 7:27:57 PM
开发
<canvas>
是一个可以使用脚本 (通常为JavaScript) 来绘制图形的 HTML 元素。例如,它可以用于绘制图表、制作图片构图或者制作简单的动画。
Canvas 的默认大小为 300 像素 ×150 像素(宽 × 高,像素的单位是 px)。但是,可以使用 HTML 的高度和宽度属性来自定义 Canvas 的尺寸。为了在 Canvas 上绘制图形,我们使用一个 JavaScript 渲染上下文对象(Context),它能动态创建图像。
Canvas 2D:主要用于二维绘图,支持绘制各种形状、图像以及动画效果,并且可以与其他页面元素进行交互。广泛应用于图表绘制、图像编辑等场景。 WebGL:主要面向3D图形的渲染(当然也可以渲染2D图形),具备强大的图形处理能力,能够呈现逼真的3D视觉效果。通常用于游戏开发、数据可视化以及虚拟现实等领域。
启用Canvas Profiler:// 不行Canvas Profiler 被移除了 Chrome DevTools提供了一个Canvas Profiler工具,可以帮助你捕获和分析Canvas的绘制调用。
about:flags
,启用 “开发者工具实验”/"Enable Developer Tools experiments" 功能;canvas 起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。<canvas>
元素有一个叫做 getContext()
的方法,这个方法是用来获得渲染上下文和它的绘画功能。getContext()接受一个参数,即上下文的类型。对于 2D 图像而言,如本教程,你可以使用 CanvasRenderingContext2D接口的渲染上下文。
CanvasRenderingContext2D 接口是 Canvas API 的一部分,可为
<canvas>
元素的绘图表面提供 2D 渲染上下文; 它用于绘制形状,文本,图像和其他对象。
<canvas></canvas> <script> const canvas = document.querySelector('canvas'); const context2D = canvas.getContext('2d'); // 获取2D 渲染上下文(即 CanvasRenderingContext2D对象) // 注: getContext()方法参数'2d'不可省略,如果进行webgl渲染则传入'webgl' console.log(context2D); context2D.fillStyle = "rgb(200,0,0)"; context2D.fillRect(10, 10, 55, 50); context2D.fillStyle = "rgba(0, 0, 200, 0.5)"; context2D.fillRect(30, 30, 55, 50); </script>
在我们开始画图之前,我们需要了解一下画布栅格(canvas grid)以及坐标空间。原点坐标(0,0)默认为为左上角。所有元素的位置都相对于原点定位。
在HTML的<canvas>
元素中,width和height属性定义了canvas的绘图区域的实际尺寸(以像素为单位)。这些属性是内联的HTML属性,它们直接影响canvas元素的绘图上下文(context)的尺寸。
另一方面,CSS样式(例如通过<style>
标签或外部CSS文件设置的样式)可以控制canvas元素在页面上的显示尺寸和布局。然而,CSS样式并不改变canvas内部绘图区域的实际尺寸;它们只是改变了canvas元素在页面上占据的空间大小。如果CSS样式设置了canvas的宽高,并且与canvas属性的宽高不匹配,那么canvas的内容可能会被拉伸或压缩以适应CSS样式指定的尺寸。
<canvas id="myCanvas" width="300" height="200" style="width: 600px; height: 400px;"></canvas>
如上,canvas的绘图表面是300x200像素,但它在页面上渲染的尺寸是600x400像素。因此,任何在canvas上绘制的图形都会被拉伸以适应这个较大的尺寸,导致图形失真。
可以用js设置属性的方式设置canvas的绘制大小
canvas = document.createElement("CANVAS"); canvas.setAttribute("width", 132); canvas.setAttribute("height", 150);
<canvas>
只支持两种形式的图形绘制:矩形和路径(由一系列点连成的线条)。
canvas 提供了三种方法绘制矩形:(以下宽高单位均为px)
ctx.fillStyle = "black"
设置填充颜色,默认黑色;ctx.strokeStyle = "black"
设置描边颜色,默认黑色;const canvas = document.getElementById("canvas"); if (canvas.getContext) { const ctx = canvas.getContext("2d"); ctx.fillRect(25, 25, 100, 100);// 绘制了一个边长为 100px 的黑色正方形 ctx.clearRect(45, 45, 60, 60); // 从正方形的中心开始擦除了一个 60*60px 的正方形 ctx.strokeRect(50, 50, 50, 50); // 在清除区域内生成一个 50*50 的正方形边框 ctx.beginPath(); ctx.rect(0,0,200,200); ctx.stroke(); // 画一个矩形路径,然后描绘其轮廓 }
什么是路径? 路径是指在Canvas上绘制图形的一系列点和线的集合。路径可以由多条子路径组成,每条子路径包含了一系列的线段或曲线,这些线段或曲线之间通过特定的方式连接。路径不仅限于直线,还包括曲线(如贝塞尔曲线、弧线等),并且可以通过闭合路径来形成一个封闭的图形。
作用:
fill
)、描边(stroke
)、闭合(closePath
)、裁剪(clip
)等,这些操作允许对路径进行丰富的样式和效果设置;使用路径绘制图形需要一些额外的步骤。
画路径操作API:
beginPath()
: 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径;closePath()
: 闭合路径之后图形绘制命令又重新指向到上下文中;stroke()
: 通过线条来绘制图形轮廓生成的图形;fill()
: 通过填充路径的内容区域生成实心的图形;moveTo(x,y)
: 移动画笔到(x,y)坐标处clip()
: 将当前正在构建的路径转换为当前的裁剪路径,用裁剪路径绘制图形function draw1() { var canvas = document.getElementById("canvas1"); if (canvas.getContext) { var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.moveTo(75, 50); ctx.lineTo(100, 75); ctx.lineTo(100, 25); ctx.fill(); } }
线条和路径的区别 线条是路径中最基本的元素之一,特指由起点和终点确定的直线段/曲线段。
画线条类型API:
lineTo(x,y)
直线: 绘制一条从当前位置到指定 x 以及 y 位置的直线;arc(x, y, radius, startAngle, endAngle, anticlockwise)
曲线: 画一个以(x,y)为圆心的以 radius 为半径的圆弧(圆),从 startAngle 开始到 endAngle 结束(这里角度使用的是弧度制,而不是360的角度制),按照 anticlockwise 给定的方向(默认为顺时针,为 true 时,是逆时针方向,否则顺时针方向)来生成;function drawArc() { var canvas = document.getElementById("canvas1"); if (canvas.getContext) { var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // 绘制 ctx.moveTo(110, 75); ctx.arc(75, 75, 35, 0, Math.PI, false); // 嘴巴 (顺时针) ctx.moveTo(65, 65); ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // 左眼 ctx.moveTo(95, 65); ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // 右眼 ctx.stroke(); } }
贝塞尔曲线:
3. quadraticCurveTo(cp1x, cp1y, x, y)
: 绘制二次贝塞尔曲线,cp1x,cp1y 为一个控制点,x,y 为结束点;
4. bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
: 绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。
画出来的曲线如下所示:
// 二次贝塞尔曲线 function draw() { var canvas = document.getElementById("canvas"); if (canvas.getContext) { var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.moveTo(75, 25); ctx.quadraticCurveTo(25, 25, 25, 62.5); ctx.quadraticCurveTo(25, 100, 50, 100); ctx.quadraticCurveTo(50, 120, 30, 125); ctx.quadraticCurveTo(60, 120, 65, 100); ctx.quadraticCurveTo(125, 100, 125, 62.5); ctx.quadraticCurveTo(125, 25, 75, 25); ctx.stroke(); } } draw(); //三次贝塞尔曲线 function draw1() { var canvas = document.getElementById("canvas2"); if (canvas.getContext) { var ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.moveTo(75, 40); ctx.bezierCurveTo(75, 37, 70, 25, 50, 25); ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5); ctx.bezierCurveTo(20, 80, 40, 102, 75, 120); ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5); ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25); ctx.bezierCurveTo(85, 25, 75, 37, 75, 40); ctx.fill(); } } draw1();
const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); ctx.globalCompositeOperation = "xor"; // canvas在重叠处变为透明,并在其他地方正常绘制 ctx.fillStyle = "blue"; ctx.fillRect(10, 10, 100, 100); ctx.fillStyle = "red"; ctx.fillRect(50, 50, 100, 100); // 绘制了 2 个矩形在重叠时, 重叠区域透明、不重叠区域绘制 的情况 ctx.globalCompositeOperation = "destination-over"; // canvas默认后面绘制的图形在上层(后来居上),这里改为后面绘制的图形在下层(先来先得)
正如我们在前面例子中看到的,你可以使用一系列的路径和绘画命令来把对象“画”在画布上。 为了简化代码和提高性能,Path2D对象已可以在较新版本的浏览器中使用,用来缓存或记录绘画命令,这样你将能快速地回顾路径。
Canvas的Path2D主要用于声明和存储绘图路径,以便后续可以被CanvasRenderingContext2D
对象使用。
Path2D对象的使用可以简化代码、提高性能,并使得路径的复用和管理变得更加容易。
function draw() { var canvas = document.getElementById("canvas"); if (canvas.getContext) { var ctx = canvas.getContext('2d'); // 创建Path2D对象并声明矩形路径 var rectangle = new Path2D(); rectangle.rect(10, 10, 50, 50); // 创建另一个Path2D对象并声明圆形路径 var circle = new Path2D(); circle.moveTo(125, 35); circle.arc(100, 35, 25, 0, 2 * Math.PI); // 绘制矩形和圆形 ctx.stroke(rectangle); /** * 等同于 * ctx.rect(10, 10, 50, 50); * ctx.stroke(); * */ ctx.fill(circle); } }
fillStyle
/strokeStyle
为RGBA颜色也可以实现同样的效果。一旦你设置了 strokeStyle 或者 fillStyle 的值,那么这个新值就会成为新绘制的图形的默认值。如果你要给每个图形上不同的颜色,你需要重新设置 fillStyle 或 strokeStyle 的值。
// 这些 fillStyle 的值均为 '橙色' ctx.fillStyle = "orange"; ctx.fillStyle = "#FFA500"; ctx.fillStyle = "rgb(255,165,0)"; ctx.fillStyle = "rgba(255,165,0,1)"; // 设置不透明度 ctx.globalAlpha = 0.2;
CanvasRenderingContext2D.lineDashOffset
属性设置起始偏移量。设置渐变用下面的方法新建一个 canvasGradient
对象,然后赋给CanvasRenderingContext2D
对象的 fillStyle 或 strokeStyle 属性:
canvasGradient
对象。canvasGradient
对象 。const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); // Create a linear gradient // The start gradient point is at x=20, y=0 // The end gradient point is at x=220, y=0 const gradient = ctx.createLinearGradient(20, 0, 220, 0); // Add three color stops gradient.addColorStop(0, "green"); // 0% green gradient.addColorStop(0.5, "cyan"); // 50% cyan gradient.addColorStop(1, "green"); // 100% green // Set the fill style and draw a rectangle ctx.fillStyle = gradient; ctx.fillRect(20, 20, 200, 100); var radialgradient = ctx.createRadialGradient(75, 75, 0, 75, 75, 100);
创建出 canvasGradient
对象后,我们就可以用 addColorStop
方法给它上色了。
function draw() { var ctx = document.getElementById("canvas").getContext("2d"); ctx.shadowOffsetX = 2; ctx.shadowOffsetY = 2; // shadowOffsetX 和 shadowOffsetY 用来设定阴影在 X 和 Y 轴的延伸距离(类似CSS3的box-shadow) ctx.shadowBlur = 2; // 设定阴影的模糊程度(类似CSS3的box-shadow) ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; // 设定阴影颜色效果,默认是全透明的黑色 ctx.font = "20px Times New Roman"; ctx.fillStyle = "Black"; ctx.fillText("Sample String", 5, 30); // 绘制文本 }
fillText(text, x, y [, maxWidth]): 在指定的 (x,y) 位置填充指定的文本,绘制的最大宽度是可选的。
strokeText(text, x, y [, maxWidth]): 在指定的 (x,y) 位置绘制文本边框,绘制的最大宽度是可选的。
function draw() { var ctx = document.getElementById("canvas").getContext("2d"); ctx.font = "48px serif"; ctx.fillText("Hello world", 10, 50); }
function draw() { var ctx = document.getElementById("canvas").getContext("2d"); ctx.font = "48px serif"; ctx.strokeText("Hello world", 10, 50); }
与 CanvasRenderingContext2D.drawImage 有点不同,你需要确认 image 对象已经装载完毕,否则图案可能效果不对的;但是实测,drawImage也是需要确保图片加载完毕的,否则绘制的是空白。
function draw() { var ctx = document.getElementById("canvas").getContext("2d"); // 创建新 image 对象,用作图案 var img = new Image(); img.src = "https://img2.baidu.com/it/u=1185208597,831327927&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500"; img.onload = function () { // 创建图案 var ptrn = ctx.createPattern(img, "repeat"); ctx.fillStyle = ptrn; ctx.fillRect(0, 0, 800, 800); // 使用 drawImage的话 // ctx.drawImage(img,0,0,800,800); }; }
整个API有三种参数情况:
img
和canvas
等)区域A(起点sx、sy,宽高sWidth、sHeight)的图片内容复制到canvas的目标区域B(起点x、y,宽高sWidth、Height), 即图片内容复制。function draw() { var ctx = document.getElementById("canvas").getContext("2d"); var img = new Image(); img.onload = function () { ctx.drawImage(img, 0, 0); ctx.beginPath(); ctx.strokeStyle = 'blue'; ctx.moveTo(30, 96); ctx.lineTo(70, 66); ctx.lineTo(103, 76); ctx.lineTo(170, 15); ctx.stroke(); }; img.src = "https://img0.baidu.com/it/u=2637193417,3812472833&fm=253&fmt=auto&app=138&f=JPEG?w=600&h=428"; img.style.width = '400px'; img.style.height = '200px'; img.style.objectFit = 'contain'; }
图片切片/组合成一张新图:
function draw() { var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); // Draw frame ctx.drawImage(document.getElementById("frame"), 0, 0); // Draw slice,后面绘制的图片层级在之前绘制的上面 ctx.drawImage( document.getElementById("source"), 200, 180, 60, 60, 100, 100, 60, 60, ); } // 确保图片加载完成 setTimeout(()=>{ draw(); },1500)
变形是一种更强大的方法,可以将原点的位置、对网格/坐标系 进行旋转和缩放等; 注意这里是对坐标系进行变换而不是对图形本身进行变换。
每当save()方法被调用后,当前的状态就被推送到栈中保存。一个绘画状态包括:
function draw() { var ctx = document.getElementById("canvas").getContext("2d"); ctx.fillRect(0, 0, 150, 150); // 使用默认设置绘制一个矩形 ctx.save(); // 保存默认状态 ctx.fillStyle = "yellow"; // 在原有配置基础上对颜色做改变 ctx.fillRect(15, 15, 120, 120); // 使用新的设置绘制一个矩形 ctx.save(); // 保存当前状态 ctx.fillStyle = "blue"; // 再次改变颜色配置 ctx.globalAlpha = 0.7; ctx.fillRect(30, 30, 90, 90); // 使用新的配置绘制一个矩形 ctx.restore(); // 重新加载之前的颜色状态,即最近一次的存档点(yellow) ctx.fillRect(45, 45, 60, 60); // 使用上一次的配置绘制一个矩形 ctx.restore(); // 加载默认颜色配置, // 因为yellow的存档点已经使用了/从栈中弹出去了,所以现在栈顶弹出的是 第一个状态————默认状态 }
在做变形之前先保存状态是一个良好的习惯。大多数情况下,调用 restore 方法比手动恢复原先的状态要简单得多。如果你是在一个循环中做位移(移动原点操作)但没有保存和恢复 canvas 的状态,很可能到最后会发现怎么有些东西不见了,那是因为它很可能已经超出 canvas 范围以外了。
transform()
方法,但是该方法会重置当前的变换矩阵为单位矩阵(即没有进行任何变换的原始状态),然后应用新的变换。这意味着,无论之前对Canvas进行了何种变换,setTransform()都会忽略这些变换,将坐标系统重置为初始状态,并在此基础上应用新的变换。function draw() { var ctx = document.getElementById("canvas").getContext("2d"); var sin = Math.sin(Math.PI / 6); var cos = Math.cos(Math.PI / 6); ctx.translate(100, 100); var c = 0; for (var i = 0; i <= 12; i++) { c = Math.floor((255 / 12) * i); ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")"; ctx.fillRect(0, 0, 100, 10); ctx.transform(cos, sin, -sin, cos, 0, 0); } ctx.setTransform(-1, 0, 0, 1, 100, 100); ctx.fillStyle = "rgba(255, 128, 255, 0.5)"; ctx.fillRect(0, 50, 100, 100); }
以上setTransfrom()
相当于:
function draw() { var ctx = document.getElementById("canvas").getContext("2d"); var sin = Math.sin(Math.PI / 6); var cos = Math.cos(Math.PI / 6); ctx.save(); // 保存初始状态 ctx.translate(100, 100); var c = 0; for (var i = 0; i <= 12; i++) { c = Math.floor((255 / 12) * i); ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")"; ctx.fillRect(0, 0, 100, 10); ctx.transform(cos, sin, -sin, cos, 0, 0); } ctx.restore(); // 重置为初始状态 ctx.transform(-1, 0, 0, 1, 100, 100); // 设置变形 ctx.fillStyle = "rgba(255, 128, 255, 0.5)"; ctx.fillRect(0, 50, 100, 100); }
可以用window.setInterval()
, window.setTimeout()
,和windowrequestAnimationFrame()
来设定定期执行一个指定的渲染函数.
一般采用 window.requestAnimationFrame()
实现动画效果。这个方法提供了更加平缓并更加有效率的方式来执行动画,当系统准备好了重绘条件的时候,才调用绘制动画帧。一般每秒钟回调函数执行 60 次,也有可能会被降低。
以下是地球围绕太阳转的动画:
<body> <canvas id="canvas" width="300" height="300"></canvas> <canvas id="sun" width="300" height="300"></canvas> <canvas id="moon" width="10" height="10"></canvas> <canvas id="erth" width="40" height="40"></canvas> <script> const sun = new Image(); const moon = new Image(); const earth = new Image(); function drawSun(){ const ctx = document.getElementById("sun").getContext("2d"); ctx.fillRect(0,0,300, 300); ctx.beginPath(); ctx.arc(150,150, 80, 0, Math.PI*2); ctx.fillStyle = 'yellow'; ctx.fill(); } function drawMoon(){ const ctx = document.getElementById("moon").getContext("2d"); ctx.beginPath(); ctx.arc(5,5, 5, 0, Math.PI*2); ctx.fillStyle = 'white'; ctx.fill(); } function drawEarth(){ const ctx = document.getElementById("erth").getContext("2d"); ctx.beginPath(); ctx.arc(20,20, 20, 0, Math.PI*2); ctx.fillStyle = 'blue'; ctx.fill(); } function init() { drawSun(); drawMoon(); drawEarth(); setTimeout(()=>{ // canvas 内容转为图片路径 sun.src = document.getElementById("sun").toDataURL('image/png'); moon.src = document.getElementById("moon").toDataURL('image/png'); earth.src = document.getElementById("erth").toDataURL('image/png'); window.requestAnimationFrame(draw); },100) } function draw() { const ctx = document.getElementById("canvas").getContext("2d"); ctx.globalCompositeOperation = "destination-over"; ctx.clearRect(0, 0, 300, 300); // 清除画布 ctx.fillStyle = "rgb(0 0 0 / 40%)"; ctx.strokeStyle = "rgb(0 153 255 / 40%)"; ctx.save(); ctx.translate(150, 150); // 地球 const time = new Date(); ctx.rotate( ((2 * Math.PI) / 60) * time.getSeconds() + ((2 * Math.PI) / 60000) * time.getMilliseconds(), ); ctx.translate(105, 0); ctx.fillRect(0, -12, 40, 24); // 阴影 ctx.drawImage(earth, -12, -12); // 月亮 ctx.save(); ctx.rotate( ((2 * Math.PI) / 6) * time.getSeconds() + ((2 * Math.PI) / 6000) * time.getMilliseconds(), ); ctx.translate(0, 28.5); ctx.drawImage(moon, -3.5, -3.5); ctx.restore(); ctx.restore(); ctx.beginPath(); ctx.arc(150, 150, 105, 0, Math.PI * 2, false); // 地球轨道 ctx.stroke(); ctx.drawImage(sun, 0, 0, 300, 300); window.requestAnimationFrame(draw); } init(); </script>
CanvasRenderingContext2D.globalCompositeOperation
是"source-over"
,即在现有画布上绘制新图形;可以改为"destination-over"
在现有画布内容的后面绘制新的图形 或者其他值.文章目录