图片未找到苏格拉底没有底

浏览器2D_canvas

7/8/2024, 7:27:57 PM

开发

canvas 2D 学习文档

参考资料MDN-canvas教程

简介

<canvas>是一个可以使用脚本 (通常为JavaScript) 来绘制图形的 HTML 元素。例如,它可以用于绘制图表、制作图片构图或者制作简单的动画。

Canvas 的默认大小为 300 像素 ×150 像素(宽 × 高,像素的单位是 px)。但是,可以使用 HTML 的高度和宽度属性来自定义 Canvas 的尺寸。为了在 Canvas 上绘制图形,我们使用一个 JavaScript 渲染上下文对象(Context),它能动态创建图像。

和webgl的差异

Canvas 2D:主要用于二维绘图,支持绘制各种形状、图像以及动画效果,并且可以与其他页面元素进行交互。广泛应用于图表绘制、图像编辑等场景。 WebGL:主要面向3D图形的渲染(当然也可以渲染2D图形),具备强大的图形处理能力,能够呈现逼真的3D视觉效果。通常用于游戏开发、数据可视化以及虚拟现实等领域。

浏览器Canvas 2D 调试

启用Canvas Profiler:// 不行Canvas Profiler 被移除了 Chrome DevTools提供了一个Canvas Profiler工具,可以帮助你捕获和分析Canvas的绘制调用。

  1. 在Chrome地址栏输入about:flags,启用 “开发者工具实验”/"Enable Developer Tools experiments" 功能;
  2. 打开DevTools,点击右下角的齿轮图标,进入“实验”并启用“Canvas检查”;
  3. 关闭并重新打开DevTools,确保更改生效;
  4. 转到“Profiles”部分,启用Canvas Profiler

获取渲染上下文-Context

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画布 坐标空间

在我们开始画图之前,我们需要了解一下画布栅格(canvas grid)以及坐标空间。原点坐标(0,0)默认为为左上角。所有元素的位置都相对于原点定位。

canvas 渲染尺寸和元素尺寸

在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> 只支持两种形式的图形绘制:矩形和路径(由一系列点连成的线条)。

绘制矩形

canvas 提供了三种方法绘制矩形:(以下宽高单位均为px)

  1. fillRect(x, y, width, height): 绘制一个填充的矩形, ctx.fillStyle = "black"设置填充颜色,默认黑色;
  2. strokeRect(x, y, width, height): 绘制一个矩形的边框, ctx.strokeStyle = "black"设置描边颜色,默认黑色;
  3. rect(x, y, width, height):绘制一个左上角坐标为(x,y),宽高为 width 以及 height 的矩形路径。
  4. clearRect(x, y, width, height): 清除指定矩形区域,让清除部分完全透明。
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:

  1. beginPath(): 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径;
  2. closePath(): 闭合路径之后图形绘制命令又重新指向到上下文中;
  3. stroke(): 通过线条来绘制图形轮廓生成的图形;
  4. fill(): 通过填充路径的内容区域生成实心的图形;
  5. moveTo(x,y): 移动画笔到(x,y)坐标处
  6. 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:

  1. lineTo(x,y) 直线: 绘制一条从当前位置到指定 x 以及 y 位置的直线;
  2. 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();

canvas混合模式:

  • CanvasRenderingContext2D.globalCompositeOperation = type: 设置要在绘制新形状时应用的合成操作的类型,其中 type 是用于标识要使用的合成或混合模式操作的字符串
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默认后面绘制的图形在上层(后来居上),这里改为后面绘制的图形在下层(先来先得)

12 种遮盖方式

Path2D 对象

正如我们在前面例子中看到的,你可以使用一系列的路径和绘画命令来把对象“画”在画布上。 为了简化代码和提高性能,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); } }

应用样式和色彩

色彩和透明度

  • CanvasRenderingContext2D.fillStyle : 设置图形的填充颜色
  • CanvasRenderingContext2D.strokeStyle : 设置图形的轮廓颜色
  • CanvasRenderingContext2D.globalAlpha: 设置整个绘制的图形和图片透明度的属性。数值的范围从 0.0(完全透明)到 1.0(完全不透明);同样设置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.lineWidth: 线条厚度的属性(即线段的宽度)
  • CanvasRenderingContext2D.lineCap: 如何绘制每一条线段末端的属性。有 3 个可能的值,分别是:butt(线段末端以方形结束), round(线段末端以圆形结束) and square(线段末端以方形结束,但是增加了一个宽度和线段相同,高度是线段厚度一半的矩形区域。)。默认值是 butt。
  • CanvasRenderingContext2D.setLineDash(segments): 设置虚线方法接受一个数组,来指定线段与间隙的交替;而CanvasRenderingContext2D.lineDashOffset 属性设置起始偏移量。

渐变

设置渐变用下面的方法新建一个 canvasGradient 对象,然后赋给CanvasRenderingContext2D对象的 fillStyle 或 strokeStyle 属性:

  • CanvasRenderingContext2D.createLinearGradient(x1, y1, x2, y2): 根据两个给定的坐标值所构成的线段创建一个线性渐变的canvasGradient对象。
  • CanvasRenderingContext2D.createRadialGradient(x1, y1, r1, x2, y2, r2):方法接受 6 个参数,前三个定义一个以 (x1,y1) 为原点,半径为 r1 的圆(开始圆),后三个参数则定义另一个以 (x2,y2) 为原点,半径为 r2 的圆(结束圆),创建一个绘制放射性渐变的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 方法给它上色了。

  • gradient.addColorStop(position, color): addColorStop 方法接受 2 个参数,position 参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间。color 参数必须是一个有效的 CSS 颜色值(如 #FFF,rgba(0,0,0,1),等等)。

阴影

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) 位置绘制文本边框,绘制的最大宽度是可选的。

填充文本: fillText

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); }

更多绘制文本的属性和API

绘制图片

createPattern() 方法

  • CanvasRenderingContext2D.createPattern(image, type):该方法接受两个参数。Image 可以是一个 Image 对象(HTMLImageElement,即图片元素)的引用,或者另一个 canvas 对象。Type 必须是下面的字符串值之一:repeat,repeat-x,repeat-y 和 no-repeat(类似CSS的background-repeat)

与 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); }; }

drawImage() 方法

整个API有三种参数情况:

  • drawImage(image, x, y): 其中 image 是 image 或者 canvas 对象,x 和 y 是其在目标 canvas 里的起始坐标。
  • drawImage(image, x, y, width, height): 这个方法多了 2 个参数 width 和 height,这两个参数用来控制 当向 canvas 画入时应该缩小/放大 后的大小
  • drawImage(image, sx, sy, sWidth, sHeight, x, y, Width, Height): 将目标图像(imgcanvas等)区域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)

坐标系变换:变形-Transformations

变形是一种更强大的方法,可以将原点的位置、对网格/坐标系 进行旋转和缩放等; 注意这里是对坐标系进行变换而不是对图形本身进行变换。

存档点、快照:状态的保存和恢复

  • CanvasRenderingContext2D.save(): 通过将当前状态放入绘图状态栈中,保存 canvas 全部状态的方法;
  • CanvasRenderingContext2D.restore(): 在绘图状态栈中弹出顶端的状态,将 canvas 恢复到最近的保存状态的方法。如果没有保存状态,此方法不做任何改变。

每当save()方法被调用后,当前的状态就被推送到栈中保存。一个绘画状态包括:

  • 当前应用的变形(即移动,旋转和缩放,见下)
  • 以及下面这些属性:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled
  • 当前的裁切路径(clipping path)
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 范围以外了。

变换操作方法

  • CanvasRenderingContext2D.translate(x, y) 平移/改变原点(可以视为平移整个坐标系): 将 canvas 按原始 x 点的水平方向、原始的 y 点垂直方向进行平移变换
  • CanvasRenderingContext2D.rotate(angle): 在变换矩阵中增加旋转的方法。角度变量表示一个顺时针旋转角度并且用弧度表示
  • CanvasRenderingContext2D.scale(rateX,rateY): x 水平方向和 y 垂直方向缩放因子(大于1为放大,小于1为缩小),为 canvas 单位添加缩放变换的方法
  • CanvasRenderingContext2D.transform(a, b, c, d, e, f): 这个方法是将当前的变形矩阵乘上一个基于自身参数的矩阵(相当于前三种方法的聚合);各个参数的含义a (m11) 水平方向的缩放、b(m12)竖直方向的倾斜偏移、c(m21)水平方向的倾斜偏移、d(m22)竖直方向的缩放、e(dx)水平方向的移动、f(dy)竖直方向的移动;
  • CanvasRenderingContext2D.setTransform(a, b, c, d, e, f)方法:类似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); }

动画

定时任务+canvas渲染函数 = 动画

可以用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"在现有画布内容的后面绘制新的图形 或者其他值.
图片未找到

文章目录