书籍目录
1- 前言
2- canvas概述
3- 基本绘图
4- 文本绘制
5- 样式
6- 图像绘制
7- 转换
8- 合成图像
9- 动画
10- canvas优化
11- 应用案例-图像数据
12- 应用案例-柱状图
13- 结语
527
Canvas编程:绘制与实现入门
限时免费
共13小节
该书是一本适合初学者的指南,旨在介绍如何使用HTML5的Canvas元素进行图形绘制和动画效果实现。本书以简洁明了的方式介绍了Canvas的基本概念和使用方法,通过大量的实例演示,帮助读者建立起对Canvas编程的扎实基础,并掌握创建各种精彩图形的技巧和技术。无需深厚编程背景,只需有一些HTML和JavaScript知识即可学习Canvas编程。
离线

Renee

咨询作者

1- 前言

随着浏览器性能的提升,更多的网站使用个性化的交互性和炫酷的视觉效果,以提升用户体验。这就对前端开发者提出了更高的要求,他们需要学习并运用各种技术来创建生动、精美的用户界面。在众多技术中,Canvas在Web中的应用越来越广泛,特别是在绘图、游戏、图像处理、动画等方面。由此,Canvas也逐渐成为前端工程师必备的技能之一。

1.1 背景介绍

在网页开发领域,Canvas作为HTML5中的重要组成部分,通过其强大的绘图功能,使得开发者们能够轻松地实现动画、图表和游戏等各种酷炫的效果。Canvas提供了一个像素级别的绘图环境,开发者可以通过JavaScript代码直接操作画布上的像素,实现自定义的绘图逻辑。
Canvas具有广泛的应用场景,比如创建复杂的数据可视化图表、绘制交互式的游戏界面、制作动画效果以及构建虚拟现实和增强现实应用等。它不仅仅是一个绘图工具,更是一种创造令人惊叹的视觉体验的利器。

1.2 受众定位

本小册子主要面向那些具备一定HTML、CSS和JavaScript基础的前端开发者。同时,对于那些对于网页设计和动画开发感兴趣的初学者也适用。无论你是想为自己的网站添加一些互动效果,还是希望学习Canvas的基本特性,本书都能为你提供全面而系统的帮助。

1.3 内容概览

本小册子将带领你深入学习Canvas的基础知识,并结合实例简介,帮助你快速上手。

在学习之前,还需要注意一点,本书提及Canvas指的是2D API,因为WebGL的3D API。我不打算涉及到这块,主要是WebGL还在迅猛演进中,它比Canvas更高级也更难。

以下是本书的主要内容:

  1. canvas概述:介绍Canvas的基本概念。
  2. 基本绘图 :介绍Canvas的基本使用方法,绘制简单的点、线、形状。
  3. 文本绘制 :介绍使用Canvas绘制文本的基本方法。
  4. 样式 :对于canvas绘制的图形,添加基本样式,比如渐变色、阴影等。
  5. 图像绘制:利用canvas对于图片的常规操作。
  6. 转换:介绍canvas常用的转换方法。
  7. 合成图像 :图形合成处理。
  8. 动画:加入动画的属性。
  9. canvas优化: canvas常用的优化方法。
  10. 应用案例:结合应用案例,巩固知识点。

2- canvas概述

2.1 由来介绍

在HTML5到来之前,游戏和其他多媒体元素使用Flash在浏览器中呈现。但是HTML5到来之后,一切都改变了,HTML5带来了许多取代flash的新元素,其中之一就是canvas。

在现实世界中,画布通常用于绘画和绘画。通常使用铅笔和颜料。在编程中,画布是可以用代码绘制的元素。

<canvas> 标记最早是由 Apple 于2004年的时候在 Safari 1.3 Web 浏览器中引入。当时 Apple 希望 在 Safari 中的绘图能力也可以为 Mac OS X 桌面的 Dashboard 组件所使用,于是探究出一种方式在 Dashboard 中支持脚本化的图形, 并对 HTML 的这一根本进行了扩展。 如此canvas的雏形应用而生了。

在此之后,Firefox 1.5 和 Opera 9 都跟随了 Safari 的引领。这两个浏览器都支持 <canvas> 标记。再后来被添加到HTML5规范中。它比基本元素稍微复杂一点,但由于其灵活性而被广泛使用。

2.2 什么是画布

画布是HTML5中用于渲染图形,图像和文本的新元素。它用于在网页上绘制图形。
以下是 WHATWG 规范引入 canvas 元素的定义:

The canvas element provides scripts with a resolution-dependent bitmap canvas, which can be used for rendering graphs, game graphics, art, or other visual images on the fly.

canvas 元素为脚本提供与分辨率相关的位图画布,该画布可用于动态呈现图形、游戏图形、美术或其他视觉图像。

2.3 canvas与SVG

SVG 用于描述可缩放矢量图形,当你缩放矢量图,不会失真,而Canvas会像素化。这是一种保留模式图形模型,它保留在内存中,可以通过重新渲染中的代码结果进行操作,SVG也被称为“场景图”。

canvas 为绘制即时模式图形(包括矩形、路径和图像)提供了更程序化的体验,类似于SVG。即时模式图形渲染是一种“即发即忘”的模型,它将图形直接渲染到屏幕上,然后随后就没有关于所做操作的上下文。与保留模式相反,渲染的图形不被保存;每当需要一个新帧时,开发人员都需要重新调用描述整个场景所需的所有绘图命令,而不管实际变化如何。

Canvas SVG
基于像素(动态.png) 基于形状
单个 HTML 元素 多个图形元素,成为 DOM 的一部分
仅通过脚本修改 通过脚本和 CSS 进行修改
事件模型/用户交互是精细的 (x,y) 事件模型/用户交互是抽象的(矩形、路径)
使用较小的表面、较多的对象数 (>10k) 或两者时性能更好 对象数量较少 (<10k)、表面较大或两者兼而有之,性能更佳

2.4 画布可以做什么

canvas给网页中的视觉展示带来了革命性的变化,能够实现各种让人惊叹的视觉效果和高效的动画;canvas并不局限于桌面,还适用于各种设备的web应用;除此,Canvas的渲染性也是尤为突出,这也得益于canvas的渲染模式——即时模式。当你需要处理像素图像(区别于矢量图,矢量图可考虑SVG),控制多个静态区域做运动时,canvas是最好的选择。
基于canvas的基本特性和性能的加持下,canvas的目前常用场景如下:

  • 可视化数据:统计图表、电子表格、大屏屏展示
  • 动态特效:依据特殊的场景打造更加精巧的动画效果
  • 游戏:流畅度更好,立体感更强的游戏界面
  • 嵌入其他内容:在不使用任何插件的情况下,嵌入图表、音视频或者其他元素,更好地与web融合
  • 实时图像:签名、图像编辑等

2.5 使用画布

HTML5 <canvas> 元素创建一个用于在 2D 中绘制的区域。默认情况下,它的大小为 300 x 150 像素,但您可以使用 height 和 width 属性更改大小。

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>

  <body>
    <canvas id="canvas" width="200" height="200" style="border: 1px solid orange">
      您的浏览器不支持HTML5 canvas标签。
    </canvas>
  </body>

</html>

image

2.6 兼容性

image

  1. 桌面浏览器:主流的桌面浏览器(如 Chrome、Firefox、Safari、Edge)都对 Canvas 提供了良好的支持,并且基本上支持所有的 Canvas 功能。
  2. 移动浏览器:移动设备上的浏览器也通常支持 Canvas。Android 上的 Chrome 和苹果设备上的 Safari 对 Canvas 提供了良好的支持。
  3. Internet Explorer:在旧版的 Internet Explorer(IE8 及以下版本)中不支持 Canvas。然而,从 IE9 开始,Canvas 得到了支持,尽管存在一些功能和性能方面的差异。
  4. 其他浏览器:大多数其他现代浏览器,如 Opera 和国内的浏览器(如 QQ 浏览器、360 浏览器、UC 浏览器等),都支持 Canvas,但仍可能存在一些细微的差异。

为了确保在各种浏览器中获得最佳兼容性,你可以遵循以下几点:

  • 使用最新版本的 Canvas API,以确保支持最新的功能和优化。
  • 检测浏览器是否支持 Canvas,可以使用if语句或现代的特性检测方法,如typeof canvas.getContext === ‘function’。
  • 在使用 Canvas 之前,先检查是否支持,如果不支持,可以提供替代内容或友好的降级处理。
    比如在标签内注明“您的浏览器不支持HTML5 canvas标签”。
  • 注意一些已知的兼容性问题和限制,例如在某些浏览器中,Canvas 可能在某些情况下对图像的绘制会有轻微的模糊或锯齿效果。

2.7 渲染上下文

有了空白画布,接下来要显示某些内容,脚本首先需要使用 DOM API 来访问渲染上下文。调用方法getContext() 返回所得就是画布上的上下文对象。

上下文对象是具有属性和方法的对象,可帮助我们在画布中执行操作。如果在现实世界中我们使用铅笔绘制,那么在HTML5画布中对象 ctx 可以视为铅笔。此对象具有多种在画布上绘制的属性和方法,例如 fillRect() 、 arc() 、 fillText() 等。

目前我们只考虑2d类型,故调用getContext方法时,我们传入上下文类型标识为"2d"即可。若画布已设置其他上下文模式,则返回null。

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
console.log(ctx); // CanvasRenderingContext2D { /* … */ }

2.8 坐标系

绘制前,也就是拿起画笔需要从画布的哪一个点开始入手,对于canvas来说,ctx从哪个点开始绘制,这就需要明确canvas的坐标体系,才能确定一个点x,y的坐标,进而才能用程序描述需要绘制的方式。

image

2.9 绘制第一个图形

使用ctx绘制时,我们先绘制一个填充矩形。
首先设置填充样式,然后调用 fillRect() 方法将绘制矩形,矩形由其左上角 (20,20)、宽度 (80) 和高度 (60) 设置。

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>

  <body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid black">
      您的浏览器不支持HTML5 canvas标签。
    </canvas>
  </body>
  <script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    // 设置填充颜色
    ctx.fillStyle = "orange";
    // 绘制一个填充矩形
    ctx.fillRect(20, 20, 80, 60); // x,y,width,hight
  </script>

</html>

2.10 清除画布

CanvasRenderingContext2D.clearRect()这个方法通过把像素设置为透明色以达到擦除一个矩形区域的目的。
使用参数同fillRect(),clearRect(x, y, width, height)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="400" height="400" style="border: 1px solid black">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    // 设置填充颜色
    ctx.fillStyle = "orange";
    // 绘制一个填充矩形
    ctx.fillRect(20, 20, 80, 60); // x,y,width,hight
    // 清除一部分画布
    ctx.clearRect(10, 10, 50, 50);
</script>

</html>

3- 基本绘图

3.1 点

首先,我们需要先更新一下认知。在美术中,点是物体中占据空间的最小部分。而在数学几何中,点是没有长度、宽度和高度的 0 维对象。但在计算机中,点是屏幕 1 像素的大小。所以要在画布上绘制一个点,我们可以使用大小为 1x1 像素的方法 fillRect() 。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid black">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    // 设置填充颜色
    ctx.fillStyle = "red";
    // 绘制一个点
    ctx.fillRect(20, 20, 1, 1); // x,y,width,hight
</script>

</html>

image

3.2 路径

在 Canvas 中,可以直接绘制矩形,但对于其他形状,需要先创建路径。路径由一系列直线或曲线组成。要创建路径,首先需要调用 beginPath() 方法,然后可以对路径进行填充、描边或裁剪等操作。举个例子如下

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid black">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 创建路径
    ctx.beginPath();
    // 指定路径起点
    ctx.moveTo(10, 10);
    // 子路径终点,也是下一个子路径的起点
    ctx.lineTo(10, 100);
    ctx.lineTo(100, 100);
    ctx.lineTo(100, 10);
    // 从当前点到路径起点的直线
    ctx.closePath();

    // 使用当前描边样式描边(勾勒)当前路径或给定路径。
    ctx.strokeStyle = 'blue';
  	// 绘制路径
    ctx.stroke();
</script>

</html>

image

3.3 线

绘制一条起点(50,50)到终点(250, 150)的一条线。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid black">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 创建路径
    ctx.beginPath();
    // 指定路径起点
    ctx.moveTo(50, 50);
    // 指定路径终点
    ctx.lineTo(250, 150);
  // 绘制路径
    ctx.stroke();
</script>

</html>

image

3.3.1 设置线段的宽度和颜色

要设置线段的宽度和颜色,可以使用Canvas API提供的相关方法。

1.设置线段宽度(Stroke Width)
语法:ctx.lineWidth = value;
一个数字类型的值,单位为像素,表示线段的宽度。value 赋值为 0、负数、 Infinity 和 NaN 会被忽略。

2.设置线段颜色(Stroke Color)
语法:ctx.strokeStyle = color | gradient | pattern;

  • color 一个表示颜色的字符串,可以是CSS颜色值或十六进制颜色码。还可以使用其他颜色表示方式,如RGBA(带透明度的RGB值)、HSL(色调、饱和度、亮度)等。
  • gradient 线性渐变或放射性渐变
  • pattern 可重复的图片

设置完线段的宽度和颜色后,你可以使用Canvas API中的绘图方法(如context.moveTo()、context.lineTo()等)来绘制线段,并通过调用context.stroke()方法将线段绘制到Canvas上。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 创建路径
    ctx.beginPath();
    // 指定路径起点
    ctx.moveTo(50, 50);

    // 设置线条的宽度
    ctx.lineWidth = 15;
     // 设置线条的样式
    ctx.strokeStyle = "orange";
    
    // 指定路径终点
    ctx.lineTo(100, 150);
    // 绘制路径
    ctx.stroke();
</script>

</html>

image

3.3.2 设置线段末端的形态(线帽)

要设置线段末端的形态,也就是线帽,可以使用Canvas API提供的相关方法。

语法 :ctx.lineCap

  • butt 线段末端以方形结束,不增加任何长度。(默认值是 butt)
  • round 线段末端以圆形结束,增加线宽的一半的长度。
  • square 线段末端以方形结束,但是增加了一个宽度和线段相同,高度是线段厚度一半的矩形区域。

需要注意的是,设置线帽样式仅影响线段末端的形态,并不影响线段与其他线段的相交处。如果需要设置线段相交处的样式,可以使用context.lineJoin属性。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 绘制辅助线
    ctx.beginPath();
    ctx.moveTo(20, 20);
    ctx.lineTo(150, 20);
    ctx.moveTo(20, 150);
    ctx.lineTo(150, 150);
    ctx.strokeStyle = "orange";
    ctx.stroke();

    // 绘制线段
    ctx.strokeStyle = "black";
    ["butt", "round", "square"].forEach((lineCap, i) => {
        ctx.lineWidth = 15;
        ctx.lineCap = lineCap;
        ctx.beginPath();
        ctx.moveTo(25 + i * 50, 20);
        ctx.lineTo(25 + i * 50, 150);
        ctx.stroke();
    });
</script>

</html>

image

3.3.3 绘制连续的两条线段(折线)

在Canvas中,lineJoin属性用于设置线段的相交处的样式,包括折线的转角处。

语法:ctx.lineJoin

  • bevel 在相连部分的末端填充一个额外的以三角形为底的区域,每个部分都有各自独立的矩形拐角。
  • round 通过填充一个额外的,圆心在相连部分末端的扇形,绘制拐角的形状。圆角的半径是线段的宽度。
  • miter 通过延伸相连部分的外边缘,使其相交于一点,形成一个额外的菱形区域。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="400" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ["bevel", "round", "miter"].forEach((lineJoin, i) => {
        ctx.lineWidth = 15;
        // 设置线段连接形态
        ctx.lineJoin = lineJoin;
        ctx.beginPath();
        ctx.moveTo(25 + i * 100, 50);
        // 线段1
        ctx.lineTo(100 + i * 100, 150);
        // 线段2
        ctx.lineTo(150 + i * 100, 50);
        ctx.stroke();
    });


</script>

</html>

image

3.3.4 虚线

在Canvas中,绘制虚线可以通过设置线段的样式来实现。通常情况下,虚线由一系列的短线段和间隔组成,可以通过以下属性和方法来控制虚线的样式:

setLineDash(segments):该方法用于设置虚线的线段样式。segments参数是一个数组,表示虚线中每个线段以及间隔的长度。例如,[5, 3]表示先绘制5像素的实线,然后跳过3像素,如此往复。如果需要绘制连续的实线,则传递一个空数组[]即可。

lineDashOffset:该属性用于设置虚线的起始偏移量。起始偏移量决定了虚线的起始位置。默认情况下,起始偏移量为0,即虚线从起点开始绘制。可以通过设置正数或负数来改变虚线的起始位置。

语法:ctx.setLineDash(segment);

  • segment 一个Array数组。用来描述绘制线段和间距长度的数字。

需要注意的是,虚线的样式只会在调用stroke方法时生效,而不会影响到其他绘图操作。

【NOTE】
如果是空数组[],则绘制的是一条实线。
如果数组元素的数量是奇数,数组的元素会被复制并重复。例如[4,10,16] 会变成[4,10,16,4,10,16]

【扩展】
ctx.getLineDash() 获取当前线段的样式

以下是值为[10,5] 和 [10,5,5] 的示意图
image

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="300" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    let y = 20;

    function drawDashedLine(segment) {
        ctx.beginPath();
        ctx.setLineDash(segment);
        ctx.moveTo(20, y);
        ctx.lineTo(200, y);
        ctx.stroke();
        y += 20;
    }

    drawDashedLine([]);
    drawDashedLine([5]);
    drawDashedLine([1, 5]);
    drawDashedLine([5, 1]);
    drawDashedLine([10, 5, 5]);
    drawDashedLine([10, 5, 5, 5]);


</script>

</html>

image

语法:ctx.lineDashOffset = value;

  • value 偏移量是float进度的数字。初始值为0.0
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="300" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 设置虚线参数
    ctx.setLineDash([6, 16]);

    // 不带带偏移量的 
    ctx.beginPath();
    ctx.moveTo(50, 50);
    ctx.lineTo(250, 50);
    ctx.stroke();


    // 带偏移量的 ,蓝色虚线
    ctx.beginPath();
    ctx.lineDashOffset = 10;
    ctx.strokeStyle = "blue";
    ctx.moveTo(100, 100);
    ctx.lineTo(250, 100);
    ctx.stroke();

</script>

</html>

image

3.3.5 弧线

在Canvas中,可以使用arc来绘制弧线。

语法:ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

  • x,y 圆弧中心的x,y 坐标
  • radius 圆弧的半径
  • startAngle** **圆弧的起始点,X轴方向开始计算,单位以弧度表示
  • endAngle 圆弧的终点,单位以弧度表示
  • anticlockwise【可选、Boolean值】如果为true,逆时针绘制,反之,顺时针绘制。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 以100,50的坐标 绘制半径为50的圆弧, 从0度开始到90度 逆时针 
    ctx.beginPath();
    ctx.arc(100, 50, 50, 0, 0.5 * Math.PI);
    ctx.stroke();

    // 以100,200的坐标 绘制半径为50的圆弧, 从0度开始到90度 顺时针时针 
    ctx.beginPath();
    ctx.arc(100, 200, 50, 0, 0.5 * Math.PI, true);
    ctx.stroke();
</script>

</html>

image

通过控制点和半径添加圆弧,常用于制作圆角

语法:ctx.arcTo(x1, y1,x2, y2, radius);

  • x1,y1 第一个控制点的x,y 坐标
  • x2,y2 第二个控制点的x,y 坐标
  • radius 圆弧的半径。必须是非负数

原理:两个直线段,一个从起点到第一个控制点,然后到第二个控制点,如果没有arcTo(),两个线段将形成一个尖角,arcTo()创建了一个适合该角的平滑圆弧

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 控制线
    ctx.beginPath();
    ctx.strokeStyle = "gray";
    // 起点
    ctx.moveTo(200, 20);
    // 到控制点1
    ctx.lineTo(200, 130);
     // 到控制点2
    ctx.lineTo(50, 20);
    ctx.stroke();

    //圆弧
    ctx.beginPath();
    ctx.strokeStyle = "red";
    ctx.moveTo(200, 50);
    ctx.arcTo(200, 130, 50, 20, 40);
    ctx.stroke();



</script>

</html>

image

通过弧线绘制一个圆

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="250" height="250" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.moveTo(100, 50);
    ctx.arcTo(150, 50, 150, 100, 50);
    ctx.arcTo(150, 150, 100, 150, 50);
    ctx.arcTo(50, 150, 50, 100, 50);
    ctx.arcTo(50, 50, 100, 50, 50);
    ctx.stroke();


    // 绘制参考线
    ctx.beginPath();
    ctx.rect(50, 50, 100, 100);
    ctx.setLineDash([5, 5])
    ctx.stroke();

    // 绘制圆心
    ctx.beginPath();
    ctx.arc(100, 100, 1, 0, 2 * Math.PI);
    ctx.fillStyle = "red";
    ctx.fill();
</script>

</html>

image

3.3.6 曲线

在数学中,我们知道有多种方式表示曲线,比如多项式函数、三角函数等;对于Canvas来说,直接可使用贝塞尔曲线。

贝塞尔曲线(Bezier Curve)是一种常用于计算机图形学和曲线设计的数学曲线。它由法国工程师皮埃尔·贝塞尔(Pierre Bézier)在20世纪50年代开发。
贝塞尔曲线通过控制点来定义曲线的形状,其特点在于平滑、灵活和可控制。贝塞尔曲线可以是一条直线,也可以是复杂的曲线,可以是二维的或三维的。
贝塞尔曲线的形状由几个控制点决定,这些控制点可以位于曲线上也可以位于曲线外部。一般情况下,贝塞尔曲线的起点和终点不经过控制点,但曲线的形状会受到控制点的影响。通过调整控制点的位置,可以改变曲线的形状,使其具有平滑的弯曲或锐利的角度。
贝塞尔曲线的计算采用了插值和逼近的方法。根据贝塞尔曲线的阶数,可以使用不同的算法来计算曲线上的点。常见的贝塞尔曲线包括二次贝塞尔曲线(二阶)、三次贝塞尔曲线(三阶)和更高阶的贝塞尔曲线。
贝塞尔曲线广泛应用于计算机图形学中,用于绘制平滑曲线、创建路径动画、实现字体和图形变形等。许多绘图软件和计算机图形库都提供了贝塞尔曲线的函数或工具,方便开发者使用和操作贝塞尔曲线。

3.3.6.1 二次贝塞尔曲线

Canvas中的二次贝塞尔曲线(Quadratic Bezier Curve)是一种平滑曲线,由起点、控制点和终点三个关键点确定。它是一种二次多项式曲线,可以用于绘制各种曲线形状,如弧线、曲线路径等。

二次贝塞尔曲线的定义需要三个关键点:

  • 起点:曲线的起始点。
  • 控制点:影响曲线形状的关键点。它不在曲线上,但影响曲线的弯曲方向和程度。
  • 终点:曲线的结束点。

语法:ctx.quadraticCurveTo(cpx, cpy, x, y);

  • cpx,cpy 控制点的x,y 坐标
  • x,y 结束点的x,y 坐标
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="400" height="300" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");


    ctx.beginPath();
    ctx.strokeStyle = "red";
    ctx.moveTo(20, 60);
    ctx.quadraticCurveTo(150, 80, 240, 40);
    ctx.stroke();


    // 绘制开始点和结束点
    ctx.fillStyle = "blue";
    ctx.beginPath();
    ctx.arc(20, 60, 5, 0, 2 * Math.PI); // 开始点
    ctx.arc(240, 40, 5, 0, 2 * Math.PI); // 结束点
    ctx.fill();

    // 绘制控制点
    ctx.fillStyle = "red";
    ctx.beginPath();
    ctx.arc(150, 80, 5, 0, 2 * Math.PI); // 控制点
    ctx.fill();


</script>

</html>

image

3.3.6.2 三次贝塞尔曲线

Canvas中的三次贝塞尔曲线(Cubic Bezier Curve)是一种平滑的曲线,由起点、两个控制点和终点四个关键点确定。它是一种三次多项式曲线,可以用于绘制各种曲线形状,如弯曲路径、曲线表面等。

三次贝塞尔曲线的定义需要四个关键点:

  • 起点:曲线的起始点。
  • 第一个控制点:影响曲线形状的关键点。它不在曲线上,但影响曲线的弯曲方向和程度。
  • 第二个控制点:同样也是影响曲线形状的关键点。
  • 终点:曲线的结束点。

语法:ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);

  • cp1x,cp1y 第一个控制点的x,y 坐标
  • cp2x,cp2y 第二个控制点的x,y 坐标
  • x,y 结束点的x,y 坐标
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="400" height="300" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");


    ctx.beginPath();
    ctx.strokeStyle = "red";
    ctx.moveTo(20, 60);
    ctx.bezierCurveTo(150, 80, 100, 230, 240, 40);
    ctx.stroke();


    // 绘制开始点和结束点
    ctx.fillStyle = "blue";
    ctx.beginPath();
    ctx.arc(20, 60, 5, 0, 2 * Math.PI); // 开始点
    ctx.arc(240, 40, 5, 0, 2 * Math.PI); // 结束点
    ctx.fill();

    // 绘制控制点
    ctx.fillStyle = "red";
    ctx.beginPath();
    ctx.arc(150, 80, 5, 0, 2 * Math.PI); // 控制点1
    ctx.arc(100, 230, 5, 0, 2 * Math.PI); // 控制点2
    ctx.fill();


</script>

</html>

image

3.4 形状

3.4.1 三角形

尽管三角形是一种常见的图形,但在canvas中,并没有类似于rect()方法用于绘制三角形的功能。要画一个三角形,首先需要确定三个点的坐标位置,然后使用stroke()或fill()方法来生成三角形。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.moveTo(50, 50);
    ctx.lineTo(50, 150);
    ctx.lineTo(100, 150);
    ctx.closePath();
    ctx.stroke();

</script>

</html>

image

3.4.2 矩形

canvas提供了rect() 方法,可直接生矩形

语法:ctx.rect(x, y, width, height);

  • x,y 矩形起点的x,y 坐标
  • width, height 矩形的宽度和高度
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.rect(50, 50, 150, 100);
    ctx.stroke();

</script>

</html>

image

3.4.3 圆形

canvas 中绘制圆形的方法是 arc(), 可参考3.3.5 弧线中的使用语法。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 圆心为(100,100),半径为50
    ctx.beginPath();
    ctx.arc(100, 100, 50, 0, 2 * Math.PI);
    ctx.stroke();

    // 绘制圆心
    ctx.beginPath();
    ctx.fillStyle = "red";
    ctx.arc(100, 100, 1, 0, 2 * Math.PI);
    ctx.stroke();
    ctx.fill();
</script>

</html>

image

3.4.4 半圆

同上节所述,绘制半圆,也是需要使用绘制弧线的方法arc()方法

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="400" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    /** --------上半圆--------*/
    // 圆心为(100,100),半径为50
    ctx.beginPath();
    ctx.arc(100, 100, 50, 0, Math.PI, true);
    ctx.closePath();
    ctx.stroke();


    // 绘制圆心
    ctx.beginPath();
    ctx.fillStyle = "red";
    ctx.arc(100, 100, 1, 0, 2 * Math.PI);
    ctx.stroke();
    ctx.fill();


    /** --------下半圆--------*/
    // 圆心为(100,150),半径为50
    ctx.beginPath();
    ctx.arc(100, 150, 50, 0, Math.PI);
    ctx.closePath();
    ctx.stroke();


    // 绘制圆心
    ctx.beginPath();
    ctx.fillStyle = "red";
    ctx.arc(100, 150, 1, 0, 2 * Math.PI);
    ctx.stroke();
    ctx.fill();

    /** --------左半圆--------*/
    // 圆心为(250,100),半径为50
    ctx.beginPath();
    ctx.arc(250, 100, 50, 0.5 * Math.PI, 1.5 * Math.PI);
    ctx.closePath();
    ctx.stroke();


    // 绘制圆心
    ctx.beginPath();
    ctx.fillStyle = "red";
    ctx.arc(250, 100, 1, 0, 2 * Math.PI);
    ctx.stroke();
    ctx.fill();


    /** --------右半圆--------*/
    // 圆心为(300,100),半径为50
    ctx.beginPath();
    ctx.arc(300, 100, 50, 0.5 * Math.PI, 1.5 * Math.PI, true);
    ctx.closePath();
    ctx.stroke();


    // 绘制圆心
    ctx.beginPath();
    ctx.fillStyle = "red";
    ctx.arc(300, 100, 1, 0, 2 * Math.PI);
    ctx.stroke();
    ctx.fill();
</script>

</html>

image

3.4.5 椭圆

在 Canvas 中绘制椭圆可以通过建立一个椭圆的路径,并用 stroke() 或 fill() 方法来描边或填充椭圆。要绘制椭圆,我们需要使用 canvas 的 beginPath() 开始一个路径,之后使用 ellipse方法。

语法:ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);

  • x,y 椭圆圆心的x,y 坐标
  • radiusX 椭圆长轴的半径
  • radiusY 椭圆短轴的半径
  • rotation 椭圆的旋转角度,以弧度表示(非角度度数)
  • startAngle** **圆弧的起始点,X轴方向开始计算,单位以弧度表示
  • endAngle 圆弧的终点,单位以弧度表示
  • anticlockwise【可选、Boolean值】如果为true,逆时针绘制,反之,顺时针绘制。

需要注意的是,ellipse() 方法在不同的浏览器中可能存在兼容性问题,因此在使用时需要根据实际情况进行测试和处理。如果需要在较老的浏览器中支持绘制椭圆,可以使用其他方法,例如使用 bezierCurveTo() 方法来绘制近似的椭圆形状。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="400" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    /** --------椭圆--------*/
    // 圆心为(200,150),长轴半径为100,短轴半径为50,旋转90度
    ctx.beginPath();
    ctx.ellipse(200, 150, 100, 50, 0.5 * Math.PI, 0, 2 * Math.PI);
    ctx.closePath();
    ctx.stroke();


    // 绘制圆心
    ctx.beginPath();
    ctx.fillStyle = "red";
    ctx.arc(200, 150, 1, 0, 2 * Math.PI);
    ctx.stroke();
    ctx.fill();


    // 绘制长轴半径
    ctx.beginPath();
    ctx.setLineDash([5, 5]);
    ctx.moveTo(200, 150);
    ctx.lineTo(200, 50);
    ctx.stroke();

    // 绘制短轴半径
    ctx.beginPath();
    ctx.setLineDash([5, 5]);
    ctx.moveTo(200, 150);
    ctx.lineTo(250, 150);
    ctx.stroke();

</script>

</html>

image

3.4.6 矩形圆角

在 Canvas 中绘制矩形圆角可以使用 arc() 和 fillRect() 或 strokeRect() 方法,以及一些简单的数学计算来实现。

首先,我们需要使用 beginPath() 方法开始一个路径。然后,使用 arc() 方法将四个角设置为圆弧。对于每个角,我们可以指定其圆心坐标、半径、起始角度和结束角度。

接下来,我们可以使用 lineTo() 方法连接两个圆弧之间的两条线段,以便绘制出完整的矩形圆角路径。最后,我们可以使用 fill() 或 stroke() 方法来填充或描边出这个路径。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="400" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    /** --------矩形圆角--------*/
    ctx.beginPath();
    ctx.moveTo(50, 150);
    ctx.arcTo(50, 50, 100, 50, 50);
    ctx.lineTo(200, 50);
    ctx.arcTo(250, 50, 250, 100, 50);
    ctx.lineTo(250, 150);
    ctx.arcTo(250, 200, 200, 200, 50);
    ctx.lineTo(100, 200);
    ctx.arcTo(50, 200, 50, 150, 50);
    ctx.stroke();

</script>

</html>

image

如果需要绘制不同大小的圆角或者不同数量的圆角,可以根据自己的需求进行调整和计算。此外,还可以使用 fillRect() 方法填充整个矩形,或者使用 strokeRect() 方法描边整个矩形,而不是只描边出圆角部分。

画布 2D API 的 CanvasRenderingContext2D.roundRect() 方法向当前路径添加一个圆角矩形。

语法:roundRect(x, y, width, height, radii)

  • x, y 矩形起点的坐标
  • width, height 矩形的宽度和高度
  • radii 一个数字或列表,指定要用于矩形拐角的圆弧半径。当 width 和 height 为正数时,半径函数的数量和顺序与 border-radius CSS 属性相同
<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>

  <body>
    <canvas id="canvas" width="400" height="260" style="border: 1px solid #ccc">
      您的浏览器不支持HTML5 canvas标签。
    </canvas>
  </body>
  <script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 绘制矩形圆角
    ctx.beginPath();
    ctx.roundRect(20, 20, 200, 100, [0, 30, 50, 60]);
    ctx.strokeStyle = "green";
    ctx.stroke();

    ctx.beginPath();
    ctx.roundRect(40, 150, 200, 100, [30, 30, 40, 40]);
    ctx.strokeStyle = "red";
    ctx.stroke();


  </script>

</html>

image

3.4.7 多边形

Canvas 要画多边形,需要使用 moveTo() 、 lineTo() 和 closePath() 搭配使用。整体思路是标记多边形每个拐角的坐标点,然后足赤绘制线条最后闭合达成具体的形状。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="300" height="300" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.moveTo(80, 50);
    ctx.lineTo(30, 100);
    ctx.lineTo(100, 180);
    ctx.lineTo(170, 100);
    ctx.lineTo(120, 50);
    ctx.closePath()
    ctx.stroke();

</script>

</html>

image

3.4.8 不规则形状

在 Canvas 中绘制任意形状需要使用路径(path)方法来定义形状的轮廓,并使用填充或描边方法来渲染形状。

以下是一些常用的路径方法和绘制方法:

  • beginPath(): 开始一个新路径。
  • moveTo(x, y): 将笔触移动到指定的坐标点,不划线。
  • lineTo(x, y): 从当前笔触位置划一条直线到指定的坐标点。
  • arc(x, y, radius, startAngle, endAngle, anticlockwise): 以指定的中心点坐标和半径绘制一段弧线。
  • quadraticCurveTo(cpx, cpy, x, y): 绘制二次贝塞尔曲线,从当前笔触位置到指定的坐标点。
  • bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y): 绘制三次贝塞尔曲线,从当前笔触位置到指定的坐标点。
  • closePath(): 将当前路径闭合。
  • fill(): 填充当前路径。
  • stroke(): 描边当前路径。
    通过使用这些路径方法,你可以创建自己想要的任意形状。

下面是一个例子,展示了如何使用路径方法绘制一个花朵形状:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="300" height="300" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.moveTo(100, 130);
    ctx.bezierCurveTo(20, 100, 10, 20, 120, 80);
    ctx.bezierCurveTo(150, -80, 220, 80, 180, 100);
    ctx.bezierCurveTo(290, 120, 220, 210, 150, 160);
    ctx.bezierCurveTo(60, 300, 60, 120, 100, 130);
    ctx.lineWidth = 5;
    ctx.strokeStyle = 'green';
    ctx.stroke();

</script>

</html>

image

3.5 形状填充

在 Canvas 中,形状填充(Shape filling)是指将一个闭合的路径区域填充上颜色或图案。通过使用 fillRect() 或者 fill()方法,我们可以对先前定义好的路径进行填充操作。

以下是形状填充的基本步骤:

  • 使用路径方法(如 moveTo()、lineTo()、arc() 等)定义出一个封闭的路径,在路径的最后使用 closePath() 方法将路径封闭。
  • 选择填充的样式,可以是纯色、渐变、图案或其他复杂的效果。
  • 调用 fillRect() 或者 fill()方法来将路径区域进行填充。
<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>

  <body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid #ccc">
      您的浏览器不支持HTML5 canvas标签。
    </canvas>
  </body>
  <script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.fillStyle = "blue";
    ctx.fillRect(50, 50, 150, 100);

    ctx.fillStyle = "green";
    ctx.rect(10, 10, 150, 100);
    ctx.fill();

    ctx.fillStyle = "orange";
    ctx.arc(200, 175, 50, 0, 2 * Math.PI);
    ctx.fill();

  </script>

</html>

image

4- 文本绘制

4.1 写文字

在 Canvas 中,fillText() 方法可以被用来在画布上写文字。

语法:ctx.fillText(text,x, y, maxWidth);

  • text 要渲染的文本字符串
  • x,y 开始绘制文本开始点的坐标
  • maxWidth (可选) 文本渲染后的最大像素宽度

以下是使用 fillText() 方法在 Canvas 上写文字的基本步骤:

  • 无序列表选择合适的字体、字号、字体样式和颜色等属性。
  • 使用 fillText() 方法,传入需要写的文本和坐标信息。
  • 如果需要,可以使用 strokeText() 方法对文本进行描边效果。

首先通过font属性设置为 20 像素高的“衬线体”字体(默认的是10px serif字体),然后调用 fillText() 从坐标 (50, 50) 开始绘制文本“Hello canvas”

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    // 设置字体颜色
    ctx.fillStyle = "red";
    // 设置字体
    ctx.font = "20px serif";
    // 绘制文字
    ctx.fillText('Hello Canvas!', 50, 50);

</script>

</html>

image

4.2 写镂空文字

在 Canvas 中,要实现文本的镂空效果,可以先使用 fillText() 方法写出实心文本,再使用 strokeText() 方法对文本进行描边,并且把描边设置为背景色,这样就能够实现镂空效果。

以下是使用 fillText() 和 strokeText() 方法写出镂空文本的基本步骤:

  • 首先使用 fillText() 方法写出实心文本,设置好字体、颜色、位置等属性。
  • 然后使用 strokeText() 方法对文本进行描边,设置描边线条粗细和颜色,同时将填充和描边颜色设置为背景色。
  • 最后,清除填充和描边的设置,以免影响之后的绘制操作。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="360" height="160" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 设置字体
    ctx.font = "40px serif";
    // 绘制文字
    ctx.strokeText('Hello Canvas!', 50, 50);

</script>

</html>

image

4.3 设置字体

在 Canvas 中设置字体的方法是使用 font 属性。该属性用于设置字体的样式、大小和类型。

以下是 font 属性的常见值和用法:

  • 字体样式(font-style):可以设置为 “normal”(默认值)、“italic”(斜体)或 “oblique”(倾斜)。
  • 字体大小和单位(font-size):可以使用像素值(例如 “12px”)或其他合法的 CSS 单位(例如 “2rem”、“50%”)。
  • 字体类型和名称(font-family):可以指定一个或多个字体名称,用逗号分隔。如果字体名称包含空格或特殊字符,需要使用引号将其括起来(例如 “Arial”、“Helvetica, Arial, sans-serif”)。
  • 字体粗细(font-weight):可以设置为 “normal”(默认值)、“bold”(粗体)、“bolder”(更粗)、“lighter”(更细)或使用数值表示具体的粗细程度(例如 “400” 表示正常,“700” 表示粗体)。

语法:ctx.font = value;

  • value 符合css font 语法的字符串。默认字体是 10px sans-serif

css font 语法:
font-size (必须)、font-family (必须)、font-style (可选)、font-variant (可选)、font-weight (可选)、line-height (可选)

  • font-style, font-variant 和 font-weight 必须在 font-size 之前
  • line-height 必须跟在 font-size 后面,由 “/” 分隔,例如 “16px/3”
  • font-family 必须最后指定
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="360" height="160" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 设置字体颜色
    ctx.fillStyle = "orange";
    // 设置斜体、粗体
    ctx.font = "italic bold 20px serif";
    // 绘制文字
    ctx.fillText('Hello Canvas!', 50, 50);

</script>

</html>

image

4.4 填充和描边

填充样式设置:将画布上下文的fillStyle属性设置为颜色字符串、十六进制值或 RGB 值,然后使用 fillText() 方法对文本进行填充。

描边样式设置:将画布上下文的 strokeStyle 属性设置为颜色字符串、十六进制值或 RGB 值,然后使用 strokeText() 方法对文本进行描边。

注意: 要同时设置 画布文本的填充和描边,必须同时使用 fillText() 和 strokeText() 方法。最好在 strokeText() 方法之前使用 fillText() 方法,以便正确呈现笔触粗细。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="360" height="160" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    // 设置字体
    ctx.font = "40px sans-serif";
    // 绘制文字
    ctx.fillStyle = 'black';
    ctx.fillText('你好 Canvas!', 50, 50);
    ctx.strokeStyle = 'orange';
    ctx.strokeText('你好 Canvas!', 50, 50);

</script>

</html>

image

4.5 水平对齐方式

在 Canvas 中,可以通过 textAlign 和 textBaseline 两个属性来设置文本的对齐方式。

textBaseline 属性用于设置文本在垂直方向上的对齐方式,我们下一节去了解。textAlign 属性用于设置文本在水平方向上的对齐方式,可以接收以下几个值:

  • “left”:文本左对齐。
  • “right”:文本右对齐。
  • “center”:文本水平居中对齐。
  • “start”:默认值,文本在 canvas 画布上的起点处对齐(根据当前文本方向)。
  • “end”:文本在 canvas 画布上的终点处对齐(根据当前文本方向

语法:ctx.textAlign = "left" || "right" || "center" || "start" || "end";

以下是一个示例,展示了如何使用 textAlign 属性来对齐文本:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="360" height="160" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    const x = canvas.width / 2;
    // 在画布中间绘制一条参考线
    ctx.beginPath();
    ctx.moveTo(x, 0);
    ctx.lineTo(x, canvas.height);
    ctx.stroke();

    // 设置字体
    ctx.font = "30px serif";

    ctx.textAlign = "left";
    ctx.fillText("左对齐", x, 30);

    ctx.textAlign = "center";
    ctx.fillText("居中对齐", x, 80);

    ctx.textAlign = "right";
    ctx.fillText("右对齐", x, 130);

</script>

</html>

image

4.6 垂直对齐方式

textBaseline 属性用于设置文本在垂直方向上的对齐方式,可以接收以下几个值:

  • “top”:文本顶部对齐。
  • “hanging”:文本悬挂基线对齐。
  • “middle”:文本垂直居中对齐。
  • “alphabetic”:默认值,文本基线对齐。
  • “ideographic”:文本表意基线对齐。
  • “bottom”:文本底部对齐

语法:ctx.textBaseline = "top" || "hanging" || "middle" || "alphabetic" || "ideographic" || "bottom";

以下是一个示例,展示了如何使用 textBaseline 属性来对齐文本:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="600" height="160" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    const baselines = [
        "top",
        "hanging",
        "middle",
        "bottom",
        "alphabetic",
        "ideographic"
    ];
    ctx.font = "14px serif";
    ctx.strokeStyle = "red";

    // 绘制参考线
    ctx.beginPath();
    ctx.moveTo(0, 50);
    ctx.lineTo(canvas.width, 50);
    ctx.stroke();

    baselines.forEach((baseline, index) => {
        ctx.textBaseline = baseline;
        var x = 10 + index * 100;
        ctx.fillText(`${baseline}`, x, 50);
    });

</script>

</html>

image

4.7 文本换行

在 Canvas 中,可以通过一些技巧来实现文本的换行显示。常用的实现方式是使用 measureText() 方法计算文本长度, 使用 measureText() 方法获取文本在当前字体下的精确宽度,从而判断是否需要换行。当文本的宽度超过最大宽度时,将已有文本写入画布中,并将 y 坐标向下移动一个行高。

语法:ctx.measureText(text);

  • text 需要测量的文字

返回值: TextMetrics 对象
TextMetrics.width (只读) 使用 CSS 像素计算的内联字符串的宽度

<!DOCTYPE HTML>
<html>

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="460" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。

    </canvas>
</body>
<script>
    function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
        var words = text.split(' ');
        var line = '';

        for (var n = 0; n < words.length; n++) {
            var testLine = line + words[n] + ' ';
            var metrics = ctx.measureText(testLine);
            if (metrics.width > maxWidth && n > 0) {
                ctx.fillText(line, x, y);
                line = words[n] + ' ';
                y += lineHeight;
            }
            else {
                line = testLine;
            }
        }
        ctx.fillText(line, x, y);
    }

    var canvas = document.getElementById('canvas');
    var ctx = canvas.getctx('2d');
    var maxWidth = 400;
    var lineHeight = 25;
    var x = (canvas.width - maxWidth) / 2;
    var y = 50;
    var text = ' Lorem, ipsum dolor sit amet consectetur adipisicing elit. Obcaecati explicabo ducimus sit officiis similique doloremque. Esse, pariatur totam porro veniam corporis, unde ipsa fugit culpa, doloribus explicabo cum nulla eaque?';

    ctx.font = '16px serif';
    ctx.fillStyle = '#333';

    wrapText(ctx, text, x, y, maxWidth, lineHeight);
</script>
</body>

</html>

在这个例子中,我们按空格分割文本,并循环遍历每个单词。对于每个单词,我们测试它与已有文本拼接后的宽度是否超过最大宽度。如果超过了,就将已有文本写入画布中,并将 y 坐标向下移动一个行高;反之,则将当前单词加入已有文本。

image

5- 样式

5.1 线性渐变

要使用 Canvas 创建线性渐变,我们可以使用 createLinearGradient() 方法。线性渐变由一条假想线定义,该假想线定义了梯度的方向。创建渐变后,我们可以使用 addColorStop 属性插入颜色。

语法:ctx.createLinearGradient(x0, y0, x1, y1);

  • x0,y0 渐变的开始点
  • x1,y1 渐变的结束点

返回值: CanvasGradient对象 用于描述渐变的对象
CanvasGradient 对象 没有暴露属性,只有一个方法CanvasGradient.addColorStop(offset,color),用来添加一个由偏移值和颜色指定的渐变

  • offset 0-1 之间的数值
  • color css颜色值
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    var rectWidth = 150;
    var rectHeight = 100;
    ctx.beginPath();
    ctx.rect(50, 50, rectWidth, rectHeight);

    // 添加线性渐变
    var grd = ctx.createLinearGradient(0, 0, rectWidth, rectHeight);
    grd.addColorStop(0, '#ecf5ff');
    grd.addColorStop(1, '#337ecc');
    ctx.fillStyle = grd;
    ctx.fill();
</script>

</script>

</html>

image

5.2 径向渐变

要使用 HTML5 Canvas 创建径向渐变,我们可以使用 createRadialGradient() 方法。径向渐变由两个假想圆定义 - 起始圆和结束圆,其中渐变从起始圆开始并向结束圆移动。
创建渐变后,我们可以使用 CanvasGradient 对象的addColorStop 方法 插入颜色。

语法:ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);

  • x0,y0 渐变的开始点
  • r0 开始圆形的半径
  • x1,y1 渐变的结束点
  • r1 结束圆形的半径

**返回值: **CanvasGradient对象 用于描述渐变的对象

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="300" height="300" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 绘制圆形
    ctx.beginPath();
    ctx.arc(150, 150, 100, 0, 2 * Math.PI);
    ctx.stroke();

    // 添加径向渐变
    const grd = ctx.createRadialGradient(150, 150, 30, 150, 150, 100);
    grd.addColorStop(0, '#ecf5ff');
    grd.addColorStop(1, '#337ecc');
    ctx.fillStyle = grd;
    ctx.fill();
</script>

</html>

image

5.3 阴影

要使用 HTML5 Canvas 添加阴影,我们可以使用画布上下文的 shadowColor、shadowBlur、shadowOffsetX 和 shadowOffsetY 属性。

语法:
ctx.shadowColor = color;
描述阴影颜色的属性,color 为css color值的字符串,默认值是 fully-transparent black。

ctx.shadowBlur = level;
描述模糊效果程度的属性,level 为** **float 类型的值。默认值是 0。负数、 Infinity 或者 NaN 都会被忽略。

ctx.shadowOffsetX = offset;
阴影水平偏移距离的 float 类型的值。默认值是 0。 Infinity 或者 NaN 都会被忽略。

ctx.shadowOffsetY = offset;
阴影垂直偏移距离的 float 类型的值。默认值是 0。 Infinity 或者 NaN 都会被忽略。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 设置阴影
    ctx.shadowColor = "gray";
    ctx.shadowOffsetX = 10;
    ctx.shadowOffsetY = 25;
    ctx.shadowBlur = 10;


    // 绘制矩形
    var rectWidth = 150;
    var rectHeight = 100;
    ctx.fillStyle = "orange";
    ctx.fillRect(50, 50, rectWidth, rectHeight);


</script>

</html>

image

5.4 透明

要使用 HTML5 Canvas 设置元素的不透明度,我们可以将画布上下文的 globalAlpha 属性设置为 0 到 1 之间的实数,其中 0 表示完全透明,1 表示完全不透明。

语法:
ctx.globalAlpha = value;
用来描述在 canvas 上绘图之前,设置图形和图片透明度的属性。
value值在 0.0(完全透明)和 1.0(完全不透明)之间。默认值是 1.0。如果数值不在范围内,包括Infinity 和NaN ,无法赋值,并且 globalAlpha 会保持原有的数值。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="260" height="260" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");


    ctx.globalAlpha = 0.3;

    // 绘制矩形
    var rectWidth = 100;
    var rectHeight = 100;
    ctx.fillStyle = "green";
    ctx.fillRect(50, 50, rectWidth, rectHeight);

    // 绘制圆形
    ctx.beginPath();
    ctx.arc(100, 100, 50, 0, 2 * Math.PI);
    ctx.fillStyle = "orange";
    ctx.fill();


</script>

</html>

image

6- 图像绘制

6.1 绘制图像

要使用HTML5 Canvas绘制图像,我们可以使用drawImage()方法,该方法需要一个图像对象和一个目标点。目标点定义图像相对于画布左上角的位置。
由于 drawImage() 方法需要一个图像对象,我们有几种方式:

  1. 首先创建一个图像并等待它加载,然后再实例化 drawImage()。我们可以通过使用图像对象的 onload 属性来实现这一点。
  2. 如果图片是当前HTML文档的使用img标签,这时可以使用dom方法获取该图片元素

语法:
ctx.drawImage(image, dextX, dextY);

  • image 绘制到上下文的图像源
  • dextX, dextY 绘制到目标下上文中,image 裁剪的 左上角的坐标
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="220" height="280" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 加载图像
    var imageObj = new Image();
    imageObj.src = "./6.1.jpg";
    imageObj.onload = function () {
        ctx.drawImage(imageObj, 10, 10);
    };


</script>

</html>

image

6.2 图像大小

要使用 HTML5 Canvas 设置图像的大小,我们可以在 drawImage() 方法中添加两个额外的参数,宽度和高度。

语法:
ctx.drawImage(image, destX, destY, destWidth, destHeight);

  • destWidth, destHeight 绘制到目标下上文中,image 的宽度和高度
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <img id="image" src="./6.1.jpg" width="200" height="260">
    <button onclick="drawImage()">
        将左侧图像绘制到右侧画布
    </button>
    <canvas id="canvas" width="220" height="280" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 加载图像
    var imageObj = new Image();
    imageObj.src = "./6.2.webp";
    imageObj.onload = function () {
        ctx.drawImage(imageObj, 10, 10, 200, 260);
    };

    // 使用img标签绘制图像
    function drawImage() {
        let img = document.getElementById("image");
        ctx.drawImage(img, 10, 10, 200, 260);
    }


</script>

</html>

image

6.3 图像裁剪

要使用 HTML5 Canvas 裁剪图像,我们可以向 drawImage() 方法添加六个附加参数,sourceX、sourceY、sourceWidth、sourceHeight、destWidth 和 destHeight。这些参数定义了我们要从图像中剪切出来的矩形的位置和大小。

语法:
ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight);

  • sourceX, sourceY** ** 原图像的坐标
  • sourceWidth, sourceHeight 原图像的宽度和高度
  • destX,destY image的左上角在画布上的坐标
  • destWidth, destHeight 在目标画布上绘制的宽度和高度

image

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 加载图像
    var imageObj = new Image();
    imageObj.src = "./6.1.jpg";
    var sourceX = 10;
    var sourceY = 30;
    var sourceWidth = 500;
    var sourceHeight = 500;
    var destWidth = sourceWidth;
    var destHeight = sourceHeight;
    var destX = canvas.width / 2 - destWidth / 2;
    var destY = canvas.height / 2 - destHeight / 2;
    imageObj.onload = function () {
        ctx.drawImage(imageObj, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight);
    };


</script>

</html>

image

6.4 图像加载器

当 HTML5 画布应用程序需要多个图像时,通常最好先加载所有图像,然后再在画布上绘制。为了简化加载过程,使用图像加载器函数很方便,该函数接受图像源的数组,创建图像的数组,然后在加载所有图像时调用用户定义的函数。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="600" height="180" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    var sources = [
        './6.2.1.webp',
        './6.2.2.webp',
        './6.2.3.webp',
        './6.2.4.webp',
        './6.2.5.webp',
        './6.2.6.webp',
        './6.2.7.webp',
        './6.2.8.webp',
        './6.2.9.webp',
    ];

    // 加载图像
    function loadImages(sources, callback) {
        sources.forEach(src => {
            let images = new Image();
            images.src = src;
            images.onload = function () {
                callback(images);
            };
        });
    }


    let w = 10;
    loadImages(sources, function (images) {
        ctx.drawImage(images, 0, 0, 450, 450, w, 50, 50, 50);
        w += 60;
    });



</script>

</html>

image

7- 转换

7.1 移动

要移动 HTML5 Canvas 上下文,我们可以使用 translate()方法,然后在画布上绘制。

语法:
ctx.translate( x, y);
translate() 方法通过在网格上水平移动画布及其原点 x 个单位和垂直移动 y 个单位,将平移转换添加到当前矩阵。

  • x 要添加到水平(或 x)坐标的值。
  • y 要添加到垂直(或 y)坐标的值。

image

以下示例先绘制一张图片,然后执行移动方法,此时canvas上下文原点已变化,再绘制圆形,则此时的圆心也相应的移动

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="220" height="280" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 加载图像
    var imageObj = new Image();
    imageObj.src = "./7.1.jpg";
    imageObj.onload = function () {
        ctx.drawImage(imageObj, 0, 0, 200, 260);
    };

    // 原点从(0,0) 移动到 (50, 50)
    ctx.translate(50, 50);

    // 绘制圆形
    ctx.beginPath();
    ctx.arc(0, 0, 50, 0, 2 * Math.PI);
    ctx.fillStyle = "orange";
    ctx.fill();

</script>

</html>

image

7.2 缩放

画布 2D API 的 CanvasRenderingContext2D.scale() 方法在水平和/或垂直方向上向画布单元添加缩放设置。

语法:
ctx.scale( x, y);
scale() 方法用于按指定的水平 (x) 和垂直 (y) 因子缩放当前上下文。

  • x 水平缩放因子,其中 1 等于单位或 100% 缩放。
  • y 垂直比例因子

使用上下文的 scale(x, y) 方法,您可以扩展画布坐标系。您随后绘制的任何内容都会通过 x 和 y 值变大或变小。例如,上下文的 scale(2,2) 使图形的大小加倍。同样,scale(1,.5) 将上下文中使用的垂直(或 y 轴)值减半,并使水平(或 x 轴)值保持不变。

image

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="300" height="300" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.scale(0.5, 0.5);

    // 加载图像
    var imageObj = new Image();
    imageObj.src = "./7.2.webp";
    imageObj.onload = function () {
        ctx.drawImage(imageObj, 0, 0, 300, 300);
    };

</script>

</html>

缩放前
image

缩放后
image

7.3 旋转

要旋转 HTML5 画布,我们可以使用 rotate() 方法。旋转变换需要以弧度为单位的角度。要定义旋转点,我们需要首先平移画布上下文,使上下文的左上角位于所需的旋转点上。在本教程中,我们旋转了画布上下文,使上下文的左上角直接位于矩形的中心,从而围绕矩形中心进行旋转。

语法:
ctx.rotate( angle);
rotate() 方法用于旋转当前上下文坐标。

  • angle 旋转角度,以弧度为单位。

image

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="300" height="300" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.fillStyle = "green";
    ctx.fillRect(200, 0, 100, 100);

    // 旋转30度
    ctx.rotate((Math.PI / 180) * 30);

    ctx.fillStyle = "orange";
    ctx.fillRect(200, 0, 100, 100);

</script>

</html>

image

7.4 自定义转换

通过使用Canvas的transform()方法,我们可以实现对图形的自由变换。它可以实现平移(移动)、缩放(放大/缩小)和旋转等操作。该方法接受一组变换矩阵作为参数,通过改变绘图上下文的变换矩阵,从而实现对图形的自由变换。

语法:
ctx.transform( a,b,c,d,e,f);

  • a:水平缩放绘图。
  • b:水平倾斜绘图。
  • c:垂直倾斜绘图。
  • d:垂直缩放绘图。
  • e:水平移动绘图。
  • f:垂直移动绘图。

这六个参数对应变换矩阵如下:
| a c e |
| b d f |
| 0 0 1 |

下面是一个示例代码,演示如何使用transform()方法自由转换来平移、缩放和旋转一个矩形:

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>

  <body>

    <canvas id="canvas" width="400" height="300" style="border: 1px solid #ccc">
      您的浏览器不支持HTML5 canvas标签。
    </canvas>

  </body>
  <script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    // 绘制矩形
    ctx.fillRect(0, 0, 100, 100);

    // 平移画布
    ctx.transform(1, 0, 0, 1, 100, 0);
    ctx.fillStyle = "red";
    ctx.fillRect(0, 0, 100, 100);


    // 倾斜画布
    ctx.transform(0, Math.PI / 4, Math.PI / 4, 1, 100, 0);
    ctx.fillStyle = "orange";
    ctx.fillRect(0, 0, 100, 100);

    // 缩放画布
    ctx.transform(0.5, 0, 0, 0.5, -100, 100);
    ctx.fillStyle = "green";
    ctx.fillRect(0, 0, 100, 100);

  </script>

</html>

image

值得注意的是,使用相同的参数运行transform()方法,即可对当前的绘图环境进行缩放、旋转、移动和倾斜等操作。当我们需要对当前的绘图环境进行变换时,可以使用Context对象的setTransform()方法将当前的变换矩阵重置为单位矩阵,即没有任何变换。setTransform()方法只会对其之后的绘图操作产生影响,并不会改变之前已经绘制的图形。

7.5 重置转换

要重置 HTML5 Canvas 转换矩阵,我们可以使用 setTransform()方法将转换矩阵设置为其默认状态。setTransform()方法用于将当前的变换矩阵重置为标识矩阵,然后应用新的变换参数。它的参数形式与transform一致。
下面是一个示例代码,演示如何使用setTransform() 的方法 自由转换来平移、缩放和旋转一个矩形:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="400" height="300" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>

</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    // 设置初始变换矩阵
    ctx.transform(1, 0, 0, 1, 100, 0);

    // 绘制矩形
    ctx.fillStyle = "red";
    ctx.fillRect(0, 0, 100, 100);

    // 重置变换矩阵(移动)
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    // 绘制另一个矩形
    ctx.fillStyle = "blue";
    ctx.fillRect(0, 0, 100, 100);

    // 重置变换矩阵(旋转)
    ctx.setTransform(0, Math.PI / 4, Math.PI / 4, 1, 200, 0);
    // 绘制倾斜的形状
    ctx.fillStyle = "orange";
    ctx.fillRect(0, 0, 100, 100);

    // 重置变换矩阵(缩放)
    ctx.transform(0.5, 0, 0, 0.5, -100, 100);
    // 绘制倾斜的形状
    ctx.fillStyle = "green";
    ctx.fillRect(0, 0, 100, 100);
</script>

</html>

image

7.6 裁剪

clip()方法在Canvas中用于创建一个新的剪辑区域,剪辑区域是由当前路径与Canvas上下文的路径重叠部分形成的。在剪辑区域之外的所有绘图操作都会被忽略。

语法:
ctx.clip();

使用clip()方法需要先通过绘制路径来定义剪辑区域的形状。然后调用clip()方法将其应用到Canvas上下文中,这个形状就变为一个剪辑区域。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="300" height="300" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>

</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 绘制一个圆形路径(作为剪辑区域)
    ctx.beginPath();
    ctx.arc(100, 100, 100, 0, Math.PI * 2);
    ctx.closePath();

    // 剪裁圆形路径以内的区域
    ctx.clip();

    // 在剪裁区域内绘制一个矩形
    ctx.fillStyle = "red";
    ctx.fillRect(0, 0, 100, 100);

    // 在剪裁区域内绘制另一个矩形
    ctx.fillStyle = "orange";
    ctx.fillRect(100, 100, 100, 100);


</script>

</html>

image

7.7 保存与恢复

在Canvas中,save() 方法是用于保存当前的绘图状态。所谓绘图状态指的是包括当前的变换矩阵、剪辑区域以及绘图样式(如线宽、颜色等)等属性。
通过调用save()方法,我们可以保存当前的绘图状态并将其添加到一个栈中。之后,我们可以通过调用restore()方法来恢复之前保存的绘图状态。这对于在进行一系列绘图操作时,需要在某个点之后回滚到之前的绘图状态时非常有用。

语法:
ctx.save();
ctx.restore();

让我们通过一个简单的例子来理解save方法的使用。假设我们想要在一个红色的矩形之后绘制一个蓝色的矩形,然后再次绘制一个红色的矩形。如果没有使用save方法,那么第二次绘制的红色矩形将会完全覆盖第一次绘制的蓝色矩形。但是,如果我们在绘制每个矩形之前都调用save方法,那么我们就可以分别保存和恢复这两个矩形的状态。

<!DOCTYPE html>
<html>

<body>
    <canvas id="canvas" width="400" height="300" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>

</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 绘制第一个红色矩形
    ctx.fillStyle = "red";
    ctx.fillRect(10, 10, 100, 100);

    ctx.save(); // 保存当前状态

    // 绘制第二个蓝色矩形
    ctx.fillStyle = "blue";
    ctx.fillRect(120, 10, 100, 100);

    ctx.restore(); // 恢复之前保存的状态

    // 绘制第三个红色矩形
    ctx.fillRect(230, 10, 100, 100);
</script>
</body>

</html>

image

8- 合成图像

8.1 获取图像数据

要获取画布上矩形区域每个像素的图像数据,我们可以使用画布上下文的 getImageData() 方法获取图像数据对象,然后从 data 属性访问像素数据。图像数据中的每个像素都包含四个分量:红色、绿色、蓝色和 Alpha 分量。使用图像数据对象访问像素数据有三种常用技术。

语法:
const imageData = context.getImageData(x, y, width, height);

  • x、y 是矩形区域的左上角坐标
  • width、height 是矩形区域的宽度和高度

getImageData()方法返回一个ImageData对象,该对象包含了指定矩形区域中每个像素的 RGBA 值信息。可以通过该对象的data属性来访问这些像素的值。data属性是一个Uint8ClampedArray类型的对象,其长度为 (width * height * 4),即表示矩形区域中每个像素的 RGBA 值所需的字节数。data数组中的每四个连续元素代表一个像素的 RGBA 值(以红色、绿色、蓝色和透明度的顺序存储)。

8.2 输出图像数据

putImageData() 方法是 Canvas 2D API 中的一个用于绘图的方法,用于将像素数据放回画布中。通俗来讲,就是把经过处理的图像重新绘制到画布上。

语法:
context.putImageData(imagedata, x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight)

  • imagedata 是一个 ImageData 对象,它包含了要绘制的像素数据。
  • x、y 是目标位置的坐标。
  • dirtyX、dirtyY、dirtyWidth、dirtyHeight 是可选参数,它们指定了在源图像中要绘制的部分,以及在目标画布中绘制的尺寸和位置。

需要注意的是,使用 putImageData() 方法时需要保证目标像素区域和源像素区域的尺寸一致,否则可能会导致显示错误或异常。

8.3 反转颜色

在 Canvas 中,反转颜色可以通过将 RGB 值的每个分量取反来实现。具体原理如下:
对于一个像素的颜色,可以表示为RGBA(红色、绿色、蓝色、透明度)四个分量的值。每个分量的取值范围是 0 到 255,其中 0 表示没有颜色,255 表示完全饱和的颜色。
要反转颜色,就是将原始颜色的每个分量值取反,并保持透明度不变。取反的方式是用255减去原始值。例如,原始颜色的红色分量值为r,那么反转后的红色分量值为255-r。
以下是一个反转颜色的示例代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="220" height="220" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 绘制一张图片
    const img = new Image();
    img.src = "./8.1.png";
    img.addEventListener('load', function () {
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, 0, 0);

        // 获取图像数据
        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        const data = imageData.data;

        // 反转颜色
        for (let i = 0; i < data.length; i += 4) {
            data[i] = 255 - data[i]; // 红色取反
            data[i + 1] = 255 - data[i + 1]; // 绿色取反
            data[i + 2] = 255 - data[i + 2]; // 蓝色取反
            // data[i + 3] 不需要改变透明度
        }

        // 将修改后的图像数据放回画布
        ctx.putImageData(imageData, 0, 0);
    });

</script>

</html>

原图
image

反转后
image

8.4 灰度(黑白色)

在 Canvas 中,将彩色图像转换为灰度图(黑白色)可以通过以下方法来实现:

  1. 遍历图像的每个像素点。
  2. 计算每个像素点的灰度值,即将该像素点的红、绿、蓝三个分量的平均值作为灰度值。
  3. 将该像素点的红、绿、蓝三个分量的值都设置为计算得到的灰度值。

这样处理后,原始的彩色图像就会变成黑白图像。
以下是一个使用 Canvas 将图像转换为灰度图的示例代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="220" height="220" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 绘制一张图片
    const img = new Image();
    img.src = "./8.1.png";
    img.onload = function () {
        ctx.drawImage(img, 0, 0);

        // 获取画布上指定区域的图像数据
        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

        // 遍历像素,将图像转换为黑白色
        for (let i = 0; i < imageData.data.length; i += 4) {
            const r = imageData.data[i];
            const g = imageData.data[i + 1];
            const b = imageData.data[i + 2];
            const gray = (r + g + b) / 3; // 计算灰度值
            imageData.data[i] = gray; // 设置红色分量为灰度值
            imageData.data[i + 1] = gray;// 设置绿色分量为灰度值
            imageData.data[i + 2] = gray; // 设置蓝色分量为灰度值
        }

        // 将修改后的图像数据放回画布
        ctx.putImageData(imageData, 0, 0);
    };

</script>

</html>

原图
image

灰度处理后
image

8.5 透明度

在 Canvas 中,可以通过修改图像数据的透明度分量来实现图像透明。
透明度是图像的第四个分量,即 RGBA 模式中的 A,取值范围是 0 到 255。0 表示完全透明,255 表示完全不透明。
以下是一个使用 Canvas 实现图像透明的示例代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="220" height="220" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
    <input type="range" id="alphaSlider" min="0" max="255" value="128">
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 绘制一张图片
    const img = new Image();
    img.src = "./8.1.png";
    img.addEventListener('load', function () {
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, 0, 0);

        // 获取图像数据
        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        const data = imageData.data;

        // 设置透明度
        const alphaSlider = document.getElementById('alphaSlider');
        alphaSlider.addEventListener('input', function () {
            const alpha = parseInt(alphaSlider.value); // 获取进度条的值
            for (let i = 3; i < data.length; i += 4) {
                data[i] = alpha; // 设置透明度分量为进度条的值
            }
            ctx.putImageData(imageData, 0, 0); // 实时更新图像的透明度
        });
    });

</script>

</html>

在这个示例中,我们添加了一个 input 元素作为进度条,并给它设置了 min、max 和 value 属性来限制取值范围和设置初始值。并使用 JavaScript 监听进度条的变化。在事件处理函数中,我们将进度条的值(即透明度)转换为整数,并将图像数据中的透明度分量设置为该值。最后,通过调用 putImageData() 方法,实时更新画布上的图像显示。

image

8.6 亮度

在 Canvas 中,可以通过修改图像数据的 RGB 分量来实现图像亮度调整。
每个像素的 RGB 分量分别代表了红色、绿色和蓝色的亮度值,取值范围为 0 到 255。因此,如果要调整图像的亮度,只需将每个像素的 RGB 分量加上或减去一个数值即可。
以下是一个使用 Canvas 实现图像亮度调整的示例代码,同时示例中也包括了操作透明度的进度条:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="220" height="220" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
    <input type="range" id="brightnessSlider" min="-100" max="100" value="0">
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 绘制一张图片
    const img = new Image();
    img.src = "./8.1.png";

    img.addEventListener('load', function () {
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, 0, 0);
    });

    // 设置亮度的控制
    const brightnessSlider = document.getElementById('brightnessSlider');
    brightnessSlider.addEventListener('input', function () {
        applyBrightness();
    });

    // 应用亮度调整
    function applyBrightness() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(img, 0, 0); // 重新绘制原图

        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        const data = imageData.data;
        const brightness = parseInt(brightnessSlider.value);

        for (let i = 0; i < data.length; i += 4) {
            data[i] += brightness;     // 调整红色通道
            data[i + 1] += brightness; // 调整绿色通道
            data[i + 2] += brightness; // 调整蓝色通道
        }

        ctx.putImageData(imageData, 0, 0);
    }
</script>

</html>

image

8.7 获取数据网址 toDataURL

要获取画布的图像数据 URL,我们可以使用 canvas 对象的 toDataURL() 方法,该方法将画布绘图转换为 64 位编码的 PNG URL。如果您希望图像数据 URL 采用 jpeg 格式,则可以将 image/jpeg 作为 toDataURL() 方法中的第一个参数传递。如果要控制 jpeg 图像的图像质量,可以将 0 到 1 之间的数字作为 toDataURL() 方法的第二个参数传递。
注意:toDataURL() 方法要求绘制到画布上的任何图像都托管在与执行它的代码具有相同域的 Web 服务器上。如果不满足此条件,则会引发SECURITY_ERR异常。

语法:
canvas.toDataURL([type], [encoderOptions])

  • type:可选参数,表示图片的类型,默认为 “image/png”。除此之外,还可以设置为 “image/jpeg”、"image/webp"等格式。
  • encoderOptions:可选参数,仅适用于 type 为 “image/jpeg” 或 “image/webp” 的情况下。表示图片的压缩质量,范围从 0 到 1,默认为 0.92。

返回值
toDataURL() 方法返回一个字符串,表示转换后的图像。

以下是一个简单的示例,演示如何使用 toDataURL() 方法将 png 图片 转换为 data URL,我们首先绘制了一个图像到 canvas 中,然后当用户点击按钮时,将 canvas 转换为 data URL,并打印到浏览器的控制台中。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="220" height="220" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
    <button id="saveBtn">保存图像</button>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 绘制一张图片
    const img = new Image();
    img.src = "./8.1.png";

    img.addEventListener('load', function () {
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, 0, 0);
    });

    // 保存图像
    const saveBtn = document.getElementById('saveBtn');
    saveBtn.addEventListener('click', function () {
        const dataURL = canvas.toDataURL();
        console.log(dataURL); // 输出 data URL 到控制台
    });
</script>

</html>

image
当我们将 type 参数设置为 “image/jpeg” 时,可以通过第二个参数 encoderOptions 来指定 JPEG 图片的压缩质量。以下是一个改进后的示例代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="220" height="220" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
    <button id="saveBtn">保存图像</button>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 绘制一张图片
    const img = new Image();
    img.src = "./8.1.png";

    img.addEventListener('load', function () {
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, 0, 0);
    });

    // 保存图像
    const saveBtn = document.getElementById('saveBtn');
    saveBtn.addEventListener('click', function () {
        const dataURL = canvas.toDataURL("image/jpeg", 0.5); // 设置 JPEG 压缩质量为 0.5
        console.log(dataURL); // 输出 data URL 到控制台
    });
</script>

</html>

image

8.8 加载数据网址

cancas 要使用图像数据 URL 加载画布,我们可以进行 AJAX 调用以获取数据 URL,使用 URL 创建一个图像对象,然后使用画布上下文的 drawImage() 方法将图像绘制到画布上。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="220" height="220" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    // AJAX 请求获取图像数据 URL
    function loadImageData(url) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open('GET', url, true);
            xhr.responseType = 'blob';
            xhr.onload = function () {
                if (this.status === 200) {
                    const blob = this.response;
                    const reader = new FileReader();
                    reader.onloadend = function () {
                        resolve(reader.result);
                    };
                    reader.readAsDataURL(blob);
                } else {
                    reject(new Error('Failed to load image'));
                }
            };
            xhr.onerror = function () {
                reject(new Error('XMLHttpRequest error'));
            };
            xhr.send();
        });
    }

    // 加载图像数据 URL 到画布
    function loadImageToCanvas(url) {
        loadImageData(url)
            .then(dataURL => {
                const img = new Image();
                img.src = dataURL;
                img.addEventListener('load', function () {

                    ctx.drawImage(img, 0, 0, 100, 100);
                });
            })
            .catch(error => {
                console.error(error);
            });
    }

    // 示例调用
    const imageURL = 'https://picx.zhimg.com/70/v2-746accdb4a55ed34b7b4e6e86f1d39d2_1440w.avis?source=172ae18b&biz_tag=Post';
    loadImageToCanvas(imageURL);
</script>

</html>

image

在这个示例中,我们定义了两个函数:loadImageData(url) 和 loadImageToCanvas(url)。loadImageData(url) 函数使用 AJAX 请求获取图像数据 URL,并返回一个 Promise 对象。在 Promise 的回调函数中,将获取的图像数据转换为 data URL,并通过 resolve() 方法传递给 Promise 对象。
loadImageToCanvas(url) 函数调用 loadImageData(url) 来获取图像数据 URL,并将图像绘制到画布上。当图像加载完成后,使用 drawImage() 方法将图像绘制到画布上,以展示在页面中。

8.9 保存图像

我们首先使用 toDataURL() 方法将 canvas 中的图像转化为 data URL,并将该 URL 保存为图片文件。以下是保存 canvas 图像到本地的示例。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="220" height="220" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
    <button id="saveBtn">保存图像</button>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 绘制图像
    const img = new Image();
    img.src = "example.png";
    img.addEventListener('load', function () {
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, 0, 0);
    });

    // 保存图像
    const saveBtn = document.getElementById('saveBtn');
    saveBtn.addEventListener('click', function () {
        saveCanvasAsImage(canvas, 'myImage.png');
    });

    // 保存 canvas 图像为本地文件
    function saveCanvasAsImage(canvas, fileName) {
        const dataURL = canvas.toDataURL();
        const img = new Image();
        img.src = dataURL;
        const link = document.createElement('a');
        link.download = fileName || 'image.png';
        link.href = img.src;
        link.click();
    }
</script>

</html>

在以上示例中,我们首先绘制了一个图像到 canvas 中,然后当用户点击按钮时,调用 saveCanvasAsImage() 函数将 canvas 图像保存为本地文件。

该函数体内,首先通过 toDataURL() 方法获取 canvas 的 data URL,然后使用该 URL 创建一个新的图片对象并加载到浏览器缓存中。接着创建一个 a 标签,并设置其 download 属性为保存的文件名,href 属性为图片对象的 URL。最后自动触发该 a 标签的点击事件,将该图片下载到本地。

8.10 图案模式

createPattern() 方法是 Canvas API 提供的用于创建图案模式的函数。可以通过将图片或颜色作为参数传入,创建出多种样式的图案。这个函数通常应用于填充图形,可以使用它来为矩形、三角形、圆形等图形设置不同的图案,例如网格、方格纹、渐变、图片等。另外,为了提高性能,建议在绘制大量重复元素(如地图)时使用 createPattern() 函数,因为它会通过平铺图案来优化性能。

语法:
CanvasPattern createPattern(image, repetition);

  • image:可以是 HTMLImageElement,HTMLVideoElement 或 HTMLCanvasElement 对象。
  • repetition:表示如何重复图案,可取值:
    • repeat:默认值,图案在横向和纵向上重复;
    • repeat-x:图案只在横向上重复
    • repeat-y:图案只在纵向上重复
    • no-repeat:图案不重复

以下是一个简单的示例代码,演示如何使用 createPattern() 方法为 Canvas 上的图形添加纹理填充。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="380" height="380" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 创建图形模式
    const img = new Image();
    img.src = './8.1.png';
    img.onload = function () {
        const pattern = ctx.createPattern(img, 'repeat');

        // 绘制矩形并填充
        ctx.fillStyle = pattern;
        ctx.fillRect(20, 20, 380, 380);
    };
</script>

</html>

以上代码中,我们首先创建了一个 Image 对象,并将其加载到画布中。在图片加载完成后,我们调用 createPattern() 方法为图形创建了一个纹理填充模式,其中图案重复模式设置为 ‘repeat’。
接着,我们将该纹理填充模式设置给画布上的矩形,使用 fillRect() 方法绘制并填充矩形。最终效果如下图所示:
image

9- 动画

Canvas 动画是利用 HTML5 的 Canvas 元素和 JavaScript 编程,在网页上创建交互性和视觉效果的技术。通过绘制不同的图形和更新动画状态,可以实现各种动态效果,从简单的移动、渐变到复杂的粒子效果、游戏动画等。需要注意的是,Canvas 动画通常需要考虑性能优化,避免频繁的重绘和资源占用过多。可以通过合理控制帧率、使用硬件加速、优化绘制算法等手段来提高动画的性能和流畅度。

9.1 动画帧

Canvas 动画的帧是指动画中每一次更新画面的过程。在 Canvas 动画中,每一帧都需要进行重新绘制,以展示动画的连续性和流畅度。帧率(FPS)是指每秒钟更新画面的次数,通常使用帧率来衡量 Canvas 动画的流畅度。
在 Canvas 动画中,一般使用 requestAnimationFrame() 方法或 setInterval() 方法来控制帧率和更新画面。这些方法可以让 JavaScript 代码按照一定的时间间隔执行,从而实现动画的效果。
requestAnimationFrame() 方法是 HTML5 中提供的用于动画循环的方法,它会在浏览器下一次重绘之前执行回调函数。这个方法优于 setInterval() 方法,因为它能够根据浏览器的刷新频率自动调整帧率,避免了由于帧率不稳定而导致的卡顿、抖动等问题。
下面是一个简单的 Canvas 动画帧的示例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="220" height="280" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    var x = 0;
    var y = 0;
    var dx = 1;
    var dy = 1;

    function draw() {
        // 清除画布
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // 绘制圆形
        ctx.beginPath();
        ctx.arc(x, y, 20, 0, Math.PI * 2);
        ctx.fillStyle = "red";
        ctx.fill();

        // 更新位置
        x += dx;
        y += dy;

        // 碰撞检测
        if (x + dx > canvas.width || x + dx < 0) {
            dx = -dx;
        }
        if (y + dy > canvas.height || y + dy < 0) {
            dy = -dy;
        }

        // 循环动画
        requestAnimationFrame(draw);
    }

    draw();

</script>

</html>

image

在这个示例中,我们使用 requestAnimationFrame() 方法来实现动画的循环。在 draw() 函数中,我们首先清除画布,然后绘制一个圆形,并更新圆形的位置。最后,使用碰撞检测来改变圆形的运动方向,并通过 requestAnimationFrame() 方法来实现动画的循环。
需要注意的是,帧率的设置需要根据实际情况进行调整。如果帧率过低,动画会显得卡顿和不流畅;如果帧率过高,会导致资源浪费和电池消耗。一般来说,60 FPS 是比较理想的帧率,但也需要考虑设备性能和动画复杂度等因素。

9.2 直线运动

直线运动动画是一种在 HTML5 Canvas 中创建的动画效果,它通过改变物体的位置,使其沿着一条直线路径移动。这种动画效果常用于展示平移、滑动或线性运动。要使用 HTML5 Canvas 创建线性运动动画,我们可以根据速度方程递增每帧对象的 x、y 或同时递增 x 和 y 位置:距离 = 速度 * 时间。下面是一个简单的示例,展示如何使用 Canvas 创建线性运动动画:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="220" height="280" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    var x = 0; // 初始位置 x
    var y = 100; // 初始位置 y
    var dx = 2; // 每帧水平移动距离

    function draw() {
        // 清除画布
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // 绘制矩形
        ctx.fillStyle = "red";
        ctx.fillRect(x, y, 50, 50);

        // 更新位置
        x += dx;

        // 边界检测
        if (x + 50 > canvas.width || x < 0) {
            dx = -dx; // 反向移动
        }

        // 循环动画
        requestAnimationFrame(draw);
    }

    draw();
</script>

</html>

image

在这个示例中,我们创建了一个 <canvas> 元素,并使用 JavaScript 获取其上下文对象。然后,我们定义了初始位置 x 和 y,以及每一帧水平移动的距离 dx。在 draw() 函数中,我们首先清除画布,然后绘制一个红色的矩形,并根据 dx 更新矩形的位置。最后,我们进行边界检测,当矩形到达画布边界时,改变移动方向。通过调用 requestAnimationFrame() 方法实现动画的循环。
这个示例展示了一个简单的线性运动动画,矩形会在画布上水平移动,当到达边界时会反向移动。你可以根据需要修改初始位置、移动距离和边界检测条件,来创建不同的线性运动效果。
需要注意的是,在实际开发中,可以使用更复杂的算法和技术来实现更丰富的线性运动动画效果,例如使用缓动函数(easing functions)来实现平滑的加速和减速效果,或者结合时间和距离来计算移动速度。

9.3 圆周运动

圆周运动动画是一种在 HTML5 Canvas 中创建的动画效果,它通过改变圆的位置和角度,使圆围绕一个中心点以半径为基准进行旋转。这种动画效果常用于展示旋转、循环或周期性的运动。
要创建圆周运动动画,你需要了解以下几个关键概念:

  1. 圆心位置:确定圆周运动的中心点位置,通常使用 Canvas 的坐标系统来指定。例如,centerX 和 centerY 可以表示圆心的 x 和 y 坐标。
  2. 半径:定义圆周运动的半径大小,决定了圆的大小和运动轨迹的大小。你可以根据需要设置合适的半径值。
  3. 角度:用来描述圆周运动的角度,通常使用弧度制表示。初始角度决定了圆周运动的起始位置,而角速度则决定了圆周运动的速度。
  4. 角速度:表示每帧圆周运动的角度变化量。通过调整角速度,你可以控制圆周运动的速度和方向。

在动画循环函数中,你可以使用三角函数(如正弦和余弦)来计算圆的位置。通过不断更新角度,并使用三角函数计算新的 x 和 y 坐标,你可以在每一帧重新绘制画布,从而实现圆周运动的动画效果。
需要注意的是,圆周运动动画可以改变圆的半径、速度、颜色等属性,或者添加其他的变换效果,以创建更加复杂和多样化的圆周运动动画。
下面是一个简单的示例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="300" height="300" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    var centerX = canvas.width / 2; // 圆心 x 坐标
    var centerY = canvas.height / 2; // 圆心 y 坐标
    var radius = 50; // 圆的半径
    var angle = 0; // 初始角度
    var speed = 0.02; // 角速度

    function draw() {
        // 清除画布
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // 计算圆的位置
        var x = centerX + Math.cos(angle) * radius;
        var y = centerY + Math.sin(angle) * radius;

        // 绘制圆
        ctx.fillStyle = "red";
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        ctx.closePath();
        ctx.fill();

        // 更新角度
        angle += speed;

        // 循环动画
        requestAnimationFrame(draw);
    }

    draw();
</script>

</html>

image

以上示例中圆会围绕圆心以半径为50的距离进行运动。通过改变角度和使用三角函数计算圆的位置,实现了圆周运动的效果。你还可以改变圆的半径、速度、颜色等属性,或者使用缓动函数来实现平滑的加速和减速效果。此外,你还可以添加边界检测、碰撞检测等功能,以创建更复杂的圆周运动动画效果。

9.4 加速度

Canvas 加速度动画是一种可以在 Canvas 中创建的动画效果,它模拟了物体在重力或其他力的作用下的加速度运动。这种动画效果常用于展示自由落体、弹跳等物理现象。通过不断更新速度和位置,并使用绘图函数重新绘制画布,你可以在每一帧实现物体的加速度运动效果。
要创建 Canvas 加速度动画,你需要了解以下几个关键概念:

  1. 位置和速度:定义物体的位置和速度,通常使用 x 和 y 坐标表示。初始位置和速度决定了物体的起始状态。
  2. 加速度:表示每帧物体速度的变化量。通过调整加速度,你可以控制物体的加速度和方向。
  3. 时间间隔:表示两帧之间的时间差,通常使用 requestAnimationFrame 函数提供的参数来计算。时间间隔决定了每帧物体速度的变化量。

在动画循环函数中,你可以使用加速度和时间间隔来计算物体的速度和位置,为每帧增加 vx(水平速度)、vy(垂直速度)或同时增加对象的 vx 和 vy,然后根据加速度方程更新对象的位置:distance = velocity * time + 1/2 * acceleration * time ^2。

  • distance 表示物体的位移;
  • velocity 表示物体的初始速度;
  • time 表示物体运动的时间;
  • acceleration 表示物体的加速度。

这个公式可以分解为两部分,第一部分是物体在初始速度下运动的距离,即 velocity * time;第二部分是物体在加速度的作用下运动的距离,即 1/2 * acceleration * time^2。两部分相加就是物体的总位移。
例如,如果我们想计算一个初始速度为 10 m/s,加速度为 2 m/s^2 的物体在 t=5 s 时的位移,可以将公式中的 velocity 设为 10,acceleration 设为 2,time 设为 5,代入公式中计算:
distance = 10 * 5 + 1/2 * 2 * 5^2 = 50 + 1/2 * 2 * 25 = 75
因此,在 t=5 s 时,物体的位移为 75 米。

下面是一个完整的 Canvas 加速度动画示例,它演示了一个小球在重力作用下的自由落体和弹跳效果:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="400" height="400" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    // 定义小球属性
    var ball = {
        x: canvas.width / 2, // 初始位置 x 坐标
        y: 50, // 初始位置 y 坐标
        radius: 20, // 小球半径
        color: "red", // 小球颜色
        speedX: 0, // 初始速度 x 分量
        speedY: 0, // 初始速度 y 分量
        acceleration: 0.1, // 加速度
        bounceFactor: 0.8, // 弹跳系数
        isBouncing: false // 是否正在弹跳
    };

    // 创建动画循环函数
    function draw() {
        // 清除画布
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // 绘制小球
        ctx.beginPath();
        ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
        ctx.fillStyle = ball.color;
        ctx.fill();

        // 更新速度和位置
        if (!ball.isBouncing) {
            ball.speedY += ball.acceleration;
            ball.y += ball.speedY;
        } else {
            ball.speedY *= -ball.bounceFactor;
            ball.y = canvas.height - ball.radius;
        }

        // 边界检测和弹跳
        if (ball.y + ball.radius > canvas.height) {
            ball.isBouncing = true;
        } else {
            ball.isBouncing = false;
        }

        // 循环动画
        requestAnimationFrame(draw);
    }
    // 启动动画循环
    draw();
</script>

</html>

image

9.5 振幅

Canvas 振幅是指在 Canvas 中绘制波浪等图形时,波浪的振动范围。通常情况下,我们使用正弦函数来模拟波浪的振动,其中振幅就是正弦函数的一个参数。振幅越大,波浪的振动范围越广。我们可以使用简单谐波运动的公式来设置每帧的形状位置:
x(t) = amplitude * sin(t * 2PI / period) + x0

  • x(t) 表示在时间 t 时的位移;
  • amplitude 表示振幅,即物体振动时的最大位移量;
  • t 表示时间;
  • period 表示周期,即物体完成一次完整振动所需要的时间;
  • x0 表示物体在 t=0 时的初始位置。

sin() 函数表示正弦函数,它的参数是弧度值。在这个公式中,我们将时间 t 转换为弧度值,用 2PI 表示一个完整的圆周,再除以周期 period,就可以得到每个时刻物体的位移。
例如,如果我们想计算一个振幅为 10,周期为 2 秒的物体在 t=1 秒时的位移,可以将公式中的 amplitude 设为 10,period 设为 2,x0 设为 0,t 设为 1,代入公式中计算:
x(1) = 10 * sin(1 * 2PI / 2) + 0 = 10 * sin(PI) ≈ 0
因为在 t=1 秒时,正弦函数的取值为 0,所以物体的位移也为 0。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="400" height="400" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    // 定义波浪属性
    var wave = {
        amplitude: 50, // 振幅
        wavelength: 100, // 波长
        speed: 0.1, // 波速
        frequency: 0.01, // 频率
        phase: 0 // 相位
    };

    // 创建动画循环函数
    function draw() {
        // 清除画布
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // 绘制波浪
        ctx.beginPath();
        ctx.moveTo(0, canvas.height / 2);

        for (var x = 0; x < canvas.width; x += 10) {
            var y = wave.amplitude * Math.sin(wave.phase + x * wave.frequency);
            ctx.lineTo(x, canvas.height / 2 + y);
        }

        ctx.strokeStyle = "blue";
        ctx.stroke();

        // 更新相位
        wave.phase += wave.speed;

        // 循环动画
        requestAnimationFrame(draw);
    }

    // 启动动画循环
    draw();
</script>

</html>

image

在这个示例中,我们定义了一个波浪对象 wave,其中包含了振幅、波长、波速、频率和相位等属性。然后我们使用 Math.sin() 函数计算每个点的 y 坐标,并使用 ctx.lineTo() 方法连接所有点,绘制出波浪效果。最后,我们更新相位并使用 requestAnimationFrame() 方法循环执行动画。

9.6 启动和停止

在 Canvas 中启动和停止动画可以使用 requestAnimationFrame 和 cancelAnimationFrame 这两个方法。
要启动动画,您可以创建一个循环函数,并在该函数中执行绘制操作。然后,使用 requestAnimationFrame 方法调用该函数,以便在下一次浏览器刷新时触发动画。
下面是一个简单的代码示意,展示了如何启动和停止 Canvas 动画:

var requestId; // 用于保存 requestAnimationFrame 的返回值

function startAnimation() {
  // 清除之前的动画请求
  cancelAnimationFrame(requestId);

  // 执行动画循环函数
  function animationLoop() {
    // 在这里执行绘制操作

    // 请求下一帧动画
    requestId = requestAnimationFrame(animationLoop);
  }

  // 启动动画循环
  requestId = requestAnimationFrame(animationLoop);
}

function stopAnimation() {
  // 停止动画
  cancelAnimationFrame(requestId);
}

在上面的代码中,startAnimation 函数用于启动动画。首先,它会调用 cancelAnimationFrame 方法,以确保之前的动画请求被清除。然后,它定义了一个名为 animationLoop 的循环函数,在该函数中执行绘制操作,并请求下一帧动画。最后,使用 requestAnimationFrame 方法调用 animationLoop,从而启动动画循环。
stopAnimation 函数用于停止动画。它会调用 cancelAnimationFrame 方法,传入之前保存的请求 ID,以停止动画循环。

当结合 Canvas 的其他操作时,可以创建一个完整的示例来展示如何启动和停止动画,并在动画中进行绘制操作。下面是一个示例,展示了如何在 Canvas 上创建一个简单的动画,其中小球在画布上移动:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

    <canvas id="canvas" width="400" height="400" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext('2d');

    var x = 50; // 小球的初始 x 坐标
    var y = 100; // 小球的初始 y 坐标
    var radius = 20; // 小球的半径
    var speed = 2; // 小球的移动速度

    var requestId; // 用于保存 requestAnimationFrame 的返回值

    function drawBall() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        ctx.fillStyle = 'red';
        ctx.fill();
        ctx.closePath();
    }

    function updatePosition() {
        x += speed;

        if (x + radius > canvas.width || x - radius < 0) {
            speed = -speed; // 当小球碰到画布边缘时改变方向
        }
    }

    function animationLoop() {
        updatePosition();
        drawBall();

        requestId = requestAnimationFrame(animationLoop);
    }

    function startAnimation() {
        // 清除之前的动画请求
        cancelAnimationFrame(requestId);

        // 启动动画循环
        requestId = requestAnimationFrame(animationLoop);
    }

    function stopAnimation() {
        // 停止动画
        cancelAnimationFrame(requestId);
    }

    startAnimation(); // 启动动画

    setTimeout(stopAnimation, 5000); // 5 秒后停止动画
</script>
</body>

</html>

image

在上面的示例中,我们首先获取了 Canvas 元素和绘图上下文。然后,定义了小球的初始位置、半径和移动速度。
drawBall 函数用于绘制小球,它使用 arc 方法绘制一个圆,并设置填充颜色为红色。
updatePosition 函数用于更新小球的位置,使其沿着 x 轴方向移动。当小球碰到画布边缘时,改变移动方向。
animationLoop 函数是动画循环函数,它在每一帧中更新小球的位置并重新绘制小球。
startAnimation 函数用于启动动画,它清除之前的动画请求,并调用 animationLoop 函数启动动画循环。
stopAnimation 函数用于停止动画,它调用 cancelAnimationFrame 方法,传入之前保存的请求 ID。
最后,在示例中我们调用了 startAnimation 来启动动画,并在 5 秒后调用 stopAnimation 来停止动画。

10- canvas优化

Canvas 是一个强大的 HTML5 元素,可以用于绘制各种图形和动画。然而,在处理大量图形或动画时,Canvas 可能会变得非常慢,这可能会影响用户的体验。因此,优化 Canvas 性能是非常重要的。

优化 Canvas 的技巧

  • 减少绘制操作

在 Canvas 中进行绘制操作是非常耗费资源的,因此应该尽量减少绘制操作。例如,可以使用缓存技术,将静态元素绘制到一个缓存 Canvas 上,并在需要时将它们作为一个整体绘制到主 Canvas 上。

  • 使用 requestAnimationFrame

使用 requestAnimationFrame 方法可以让浏览器更好地管理动画帧率,避免过度绘制或跳帧现象。此外,使用 requestAnimationFrame 还可以让浏览器在需要时暂停或恢复动画。

  • 避免频繁的 Canvas 清除

清除 Canvas 的操作也很耗费资源,因此应该尽量避免频繁的 Canvas 清除。例如,可以只在需要时清除 Canvas,而不是每一帧都清除。

  • 使用 Canvas 缓存技术

使用 Canvas 缓存技术可以避免重复绘制相同的元素,从而提高性能。例如,可以将静态元素绘制到一个缓存 Canvas 上,并在需要时将它们作为一个整体绘制到主 Canvas 上。该缓存画布也被成为离屏画布(Offscreen Canvas)。

  • 使用合适的 Canvas 大小

使用合适的 Canvas 大小可以避免浪费资源。如果 Canvas 太大,将会占用更多的内存和处理器资源。因此,应该尽量使用合适的 Canvas 大小。

  • 避免使用阴影和模糊效果

阴影和模糊效果是比较耗费资源的,因此应该尽量避免使用这些效果。如果必须使用这些效果,可以尝试减少其强度或使用其他更轻量级的效果。

  • 避免使用复杂的路径和形状

复杂的路径和形状需要更多的计算资源,因此应该尽量避免使用这些形状。如果必须使用这些形状,可以尝试使用简化的形状或减少其数量。

  • 使用合适的图像格式

在 Canvas 中使用图像时,应该使用合适的图像格式。例如,对于不透明的图像,可以使用 JPEG 格式,而对于透明的图像,应该使用 PNG 格式。

  • 避免频繁的属性设置

在 Canvas 中设置属性也会耗费资源,因此应该尽量避免频繁的属性设置。例如,可以将属性设置为全局变量,并在需要时更新这些变量。

  • 使用硬件加速

一些浏览器支持使用硬件加速来加速 Canvas 渲染。例如,可以使用 CSS3 transform 或 3D transform 来启用硬件加速。

  • 使用Web Workers

将耗时的计算任务放在Web Workers中执行,避免阻塞主线程。

总之,优化 Canvas 性能是非常重要的,可以提高用户体验并减少资源消耗。

避免过度绘制的示例

为了避免过度绘制,我们可以使用以下方法:

  • 使用will-change属性提前通知浏览器哪些元素可能会发生变化。
  • 使用CSS3的transform和opacity属性来实现动画效果,而不是通过Canvas重新绘制整个场景。
  • 使用requestAnimationFrame来控制动画帧率,避免过高的帧率导致性能下降。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #canvas {
            will-change: transform;
        }
    </style>
</head>

<body>

    <canvas id="canvas" width="400" height="400" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    let x = 0;
    let y = 0;

    function animate() {
        ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除画布上的旧内容
        ctx.fillStyle = 'red';
        ctx.fillRect(x, y, 50, 50); // 绘制新的矩形
        x += 1;
        y += 1;
        if (x > canvas.width || y > canvas.height) {
            x = 0;
            y = 0;
        }
        requestAnimationFrame(animate); // 请求下一帧动画
    }

    requestAnimationFrame(animate); // 开始动画
</script>
</body>

</html>

image

使用离屏Canvas进行批处理操作示例

离屏Canvas(Offscreen Canvas)是一种在内存中绘制图像的技术,它允许我们在不占用主线程资源的情况下进行复杂的绘图操作。这对于需要大量计算和绘制的场景非常有用,例如游戏开发、图形处理等。当需要对多个对象进行相同的绘制操作时,可以考虑使用离屏Canvas进行批处理。这样可以减少主Canvas上的绘制次数,从而提高性能。例如,我们可以在一个离屏Canvas上绘制多个矩形,然后将它们一次性复制到主Canvas上。

下面是一个完整示例及介绍:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #canvas {
            will-change: transform;
        }
    </style>
</head>

<body>

    <canvas id="canvas" width="800" height="600" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    // 创建离屏Canvas
    var offscreenCanvas = document.createElement('canvas');
    var offscreenCtx = offscreenCanvas.getContext('2d');

    // 设置离屏Canvas的尺寸
    offscreenCanvas.width = 800;
    offscreenCanvas.height = 600;

    // 定义矩形对象
    var rectList = [
        { x: 0, y: 0, w: 200, h: 150, color: 'red', vx: 5, vy: 3 },
        { x: 400, y: 0, w: 200, h: 150, color: 'green', vx: -3, vy: 4 },
        { x: 0, y: 300, w: 200, h: 150, color: 'blue', vx: 4, vy: -5 },
        { x: 400, y: 300, w: 200, h: 150, color: 'yellow', vx: -6, vy: -4 }
    ];

    // 执行批处理操作
    function batchProcess() {
        // 在离屏Canvas上进行绘制操作
        offscreenCtx.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
        for (var i = 0; i < rectList.length; i++) {
            var rect = rectList[i];
            offscreenCtx.fillStyle = rect.color;
            offscreenCtx.fillRect(rect.x, rect.y, rect.w, rect.h);

            // 更新矩形的位置
            rect.x += rect.vx;
            rect.y += rect.vy;

            // 碰撞检测
            if (rect.x < 0 || rect.x + rect.w > offscreenCanvas.width) {
                rect.vx = -rect.vx;
            }
            if (rect.y < 0 || rect.y + rect.h > offscreenCanvas.height) {
                rect.vy = -rect.vy;
            }
        }

        // 将离屏Canvas渲染到屏幕上
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(offscreenCanvas, 0, 0);

        // 循环调用批处理操作,实现动画效果
        requestAnimationFrame(batchProcess);
    }

    // 在页面加载完成后执行批处理操作
    window.onload = function () {
        batchProcess();
    };

</script>
</body>

</html>

image

在上面的示例中,首先创建了一个离屏Canvas,并设置其尺寸为800x600。然后定义了一个包含四个矩形对象的数组rectList,每个矩形对象包含位置、尺寸、颜色和速度等属性。
在batchProcess函数中,首先使用clearRect方法清空离屏Canvas上的内容。然后循环遍历rectList数组,分别设置不同颜色的填充样式,使用fillRect方法绘制四个矩形,并更新矩形的位置和速度。
在更新矩形位置时,进行了碰撞检测,如果矩形碰到了边界,则将速度取反,让矩形反弹回来。
最后,在调用drawImage方法将离屏Canvas渲染到屏幕上时,传入离屏Canvas和坐标(0, 0)。为了实现动画效果,使用requestAnimationFrame方法循环调用batchProcess函数。
在页面加载完成后,调用batchProcess函数,即可看到屏幕上出现四个不同颜色的矩形,这些矩形会随机移动并不断反弹,形成一个有趣的动画效果。

总结

Canvas优化是一个复杂且重要的过程,它涉及到多个方面,包括减少API调用、使用离屏画布、利用缓存和避免过度绘制等。通过这些方法,我们可以有效地提高Canvas的性能,从而提供更好的用户体验。需要注意的是,不同的应用场景可能需要采用不同的优化策略,因此在实际应用中需要根据具体情况进行选择和调整。

11- 应用案例-图像数据

通过 Canvas,开发者可以直接操作像素级别的图像数据,实现各种图形和动画效果。Canvas 在图像数据处理中具有广泛的应用场景,以下是一些常见的应用场景:

  1. 图像编辑和处理:Canvas 可以用于实现图像编辑器,如裁剪、旋转、缩放、调整亮度、对比度等功能。开发者可以直接操作图像的像素数据,实现各种滤镜效果、颜色调整等图像处理操作。
  2. 图像合成与拼接:Canvas 可以将多张图像合成为一张,并进行位置调整、透明度设置等操作。这在图像拼接、图像融合以及合成特效等方面非常有用。
  3. 图像标注与测量:Canvas 可以用于在图像上进行标注和测量,如在地图上标记位置、在医学图像上测量尺寸等。开发者可以通过绘制线条、文字等元素实现这些功能。
  4. 图像滤镜和特效:Canvas 可以实现各种图像滤镜和特效,如模糊、锐化、黑白化、马赛克等。通过操作图像的像素数据,开发者可以自定义滤镜算法,实现各种独特的图像处理效果。
  5. 图像动画和交互:Canvas 可以实现图像的动画效果,如平移、缩放、旋转等。通过 JavaScript 控制 Canvas 上的图像元素,可以实现交互式的图像操作和动画效果。

总之,Canvas 在图像数据处理中提供了强大的功能和灵活性,可以满足各种图像处理和展示的需求。开发者可以利用 Canvas 的特性和 API,结合自己的创意和需求,实现各种有趣和实用的图像处理应用。

11.1 图像数据

在 Canvas 中,图像数据的基本表示方式是通过像素数组来实现。Canvas 提供了一个像素级别的绘图区域,通过 JavaScript 可以直接操作该区域的像素数据。

在 Canvas 中,可以使用 getImageData() 方法获取当前绘图区域特定矩形范围内的像素数据。该方法返回一个ImageData 对象,其中包含了图像数据的信息,包括宽度、高度和一个一维的像素数组。

像素数组是一个一维数组,每四个连续的元素表示一个像素的 RGBA(红绿蓝透明度)值。RGBA 值分别表示红色、绿色蓝色和透明度的强度,每个分量的取值范围通常是 0 到 255。通过组合不同的 RGBA 值,可以得到具体的颜色。

例如,对于一个宽度为 100 像素、高度为 50 像素的 Canvas 区域,其像素数组的长度将为 100 * 50 * 4 = 20000。数组中的第一个元素表示左上角像素的 RGBA 值,第二个元素表示右上角像素的 RGBA 值,以此类推。

开发者可以通过修改像素数组中的元素值来改变 Canvas 区域的图像。通过对像素数组的操作,可以实现图像的各种处理效果,如颜色调整、滤镜效果、图像合成等。修改完像素数组后,可以使用 putImageData() 方法将修改后的图像数据重新绘制到 Canvas 上。

需要注意的是,在进行像素数组操作时,由于直接操作像素数据,可能会涉及到性能方面的考虑。大规模的像素数组操作可能会导致性能下降,因此在实际应用中需要注意优化和合理使用像素数组操作。

11.2 图片编辑器案例

本小节我们使用 Fabric.js 实现一个基本的图像编辑器。使用自由画笔或者图案模式来绘制图形,并且可以撤销、清空、删除、更改颜色、导入和保存操作。请注意,这只是一个基本的示例,您可以根据需要对其进行修改和扩展。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Fabric.js 图像编辑器</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.5.0/fabric.min.js"></script>
    <style>
        canvas {
            border: 1px solid #ccc;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }

        button {
            padding: 8px 12px;
            font-size: 14px;
            border-radius: 4px;
            border: none;
            background-color: #4CAF50;
            color: white;
            cursor: pointer;
            margin-right: 10px;
        }

        input[type="color"] {
            vertical-align: middle;
        }
    </style>
</head>

<body>
    <!-- 创建一个画布容器 -->
    <div>
        <!-- 操作按钮 -->
        <button onclick="drawShape('rectangle')">绘制矩形</button>
        <button onclick="drawShape('circle')">绘制圆形</button>
        <button onclick="drawShape('line')">绘制线段</button>
        <input type="color" id="colorPicker" onchange="changeColor(this.value)">

    </div>
    <div style="margin-top: 10px;">
        <button onclick="importImage()">导入图片</button>
    </div>
    <div style="margin-top: 10px;">
        <button onclick="drawingMode()">自由绘制</button>
        <input type="color" id="colorPicker" onchange="changeFreeDrawingBrushColor(this.value)">
        <input type="range" id="drawWidth" min="1" max="10" step="1" value="1"
            onchange="changeFreeDrawingBrushWidth(this.value)">
    </div>
    <div style="margin-top: 10px;">
        <button onclick="undo()">撤销上一步</button>
        <button onclick="redo()">重做</button>
        <button onclick="saveImage()">保存图片</button>
    </div>
    <br>
    <!-- 画布 -->
    <canvas id="canvas" width="800" height="600"></canvas>

    <script>
        // 创建一个Fabric.js canvas对象
        var canvas = new fabric.Canvas('canvas');

        // 默认颜色
        var currentColor = '#000000';

        // 绘制基本图形
        function drawShape(shapeType) {
            var shape;
            if (shapeType === 'rectangle') {
                shape = new fabric.Rect({
                    left: 100,
                    top: 100,
                    width: 200,
                    height: 150,
                    fill: currentColor
                });
            } else if (shapeType === 'circle') {
                shape = new fabric.Circle({
                    left: 300,
                    top: 200,
                    radius: 75,
                    fill: currentColor
                });
            } else if (shapeType === 'line') {
                shape = new fabric.Line([50, 50, 200, 200], {
                    stroke: currentColor,
                    strokeWidth: 5
                });
            }
            canvas.add(shape);
            canvas.setActiveObject(shape);
        }

        // 更改图形颜色
        function changeColor(color) {
            currentColor = color;
        }


        // 图形选择和编辑
        function enableEditing() {
            canvas.isDrawingMode = false; // 禁用绘图模式
            canvas.selection = true; // 启用选中模式
            canvas.forEachObject(function (obj) {
                obj.set('selectable', true);
            });
        }

        // 撤销操作
        function undo() {
            var objects = canvas.getObjects();
            canvas.remove(objects[objects.length - 1]);
        }

        // 重做操作
        function redo() {
            canvas.clear();
        }


        // 导入图片
        function importImage() {
            var input = document.createElement('input');
            input.type = 'file';
            input.accept = 'image/*';
            input.onchange = function () {
                var file = this.files[0];
                var reader = new FileReader();
                reader.onload = function (event) {
                    var img = new Image();
                    img.onload = function () {
                        var fabricImg = new fabric.Image(img);
                        canvas.add(fabricImg);
                        canvas.setActiveObject(fabricImg);
                    };
                    img.src = event.target.result;
                };
                reader.readAsDataURL(file);
            };
            input.click();
        }

        // 保存图片
        function saveImage() {
            var link = document.createElement('a');
            link.href = canvas.toDataURL();
            link.download = 'image.png';
            link.click();
        }

        // 自由绘制
        function drawingMode() {
            canvas.isDrawingMode = true;
        }

        // 绘制笔刷颜色
        function changeFreeDrawingBrushColor(color) {
            canvas.freeDrawingBrush.color = color;

        }

        // 绘制笔刷粗细
        function changeFreeDrawingBrushWidth(width) {
            canvas.freeDrawingBrush.width = parseInt(width, 10);

        }



        // 初始化编辑器
        function initEditor() {
            enableEditing();
        }

        // 监听鼠标右击事件
        canvas.on('mouse:down', function (e) {
            if (e.button === 3) {
                const target = canvas.findTarget(e.e);

                if (target) {
                    // 删除选中对象
                    canvas.remove(target);
                }
            }
        });

        // 监听键盘事件
        document.addEventListener('keydown', function (e) {
            // 撤销上一步操作
            if (e.ctrlKey && e.key === 'Z') {
                undo();
            }

            // 清除整个画布重做
            if (e.ctrlKey && e.shiftKey && e.key === 'Z') {
                redo();
            }
        });

        // 执行初始化
        initEditor();
    </script>
</body>

</html>

image

12.3 Canvas 的高级图像处理技术

Canvas的高级图像处理技术在人脸识别、图像分割、图像重建等领域有着广泛的应用。

  • 在人脸识别方面, Canvas可以通过图像处理技术来检测人脸并进行识别。例如,可以使用OpenCV库中的人脸检测算法来检测人脸,并使用Canvas来显示检测结果。此外,还可以使用深度学习技术来进行人脸识别,例如使用TensorFlow.js来训练神经网络模型,然后使用Canvas来进行实时的人脸识别。
  • 在图像分割方面, Canvas可以通过图像处理技术来将图像分割成不同的区域。例如,可以使用基于深度学习的语义分割算法来将图像分割成不同的物体区域,并使用Canvas来显示分割结果。此外,还可以使用基于传统图像处理技术的分割算法,例如基于边缘检测的分割算法。
  • 在图像重建方面, Canvas可以通过图像处理技术来对图像进行重建。例如,可以使用基于深度学习的超分辨率算法来将低分辨率图像重建成高分辨率图像,并使用Canvas来显示重建结果。此外,还可以使用基于传统图像处理技术的重建算法,例如基于插值的重建算法。

未来,Canvas的高级图像处理技术将会得到更广泛的应用。随着深度学习技术的不断发展,Canvas将会成为实现实时图像处理的重要工具。同时,随着WebGL技术的不断发展,Canvas将会成为实现高性能图像处理的重要工具。

12- 应用案例-柱状图

12.1 Canvas柱状图的介绍和使用场景

Canvas柱状图是一种常见的数据可视化方式,是前端最基本的图表之一,它主要用于展示不同类别或组之间的比较关系。它由一组垂直或水平的柱形表示数据,其中每个柱子的高度(或宽度)代表相应类别的数值大小。这种图表的创建过程包括以下步骤:首先,我们需要创建一个Canvas对象,并指定其大小和背景颜色。接着,计算每个矩形的位置和大小,然后在Canvas上绘制每个矩形。特别的,矩形的部分可以直接使用fillRect()方法来绘制。
此外,我们可以通过编程控制每一个细节,以适应不同的需求。例如,我们可以使图表根据年份的多少自动分配柱的宽度,高度会有由低到高的运动效果,当鼠标移入时,当前柱的颜色会加深。
然而,虽然现有的库如echarts可以大大提高我们的工作效率,但是它们可能会造成资源的浪费,并且如果业务需求变更,改进,那么依靠图表库,可能不能满足业务的,失去了灵活性。因此,了解并掌握Canvas柱状图的制作过程是十分有益的。
Canvas柱状图的使用场景非常广泛,主要用于在二维空间中表示分类数据的数量或比例。以下是一些具体的应用场景:

  • 展示历史数据变化趋势:通过横轴代表时间,纵轴代表产量,可以用图表会根据年份的多少自动分配柱的宽度,高度会有由低到高的运动效果,形象地展示出一段时间内的数据变化情况。
  • 比较不同类别数据的量:当我们需要比较不同类别(例如不同产品、不同部门等)的数据量时,可以使用Canvas柱状图来直观地展示这种数量或比例关系。
  • 适用于实时数据展示:Canvas柱状图可以结合WebSocket等技术,实时更新数据并重新绘制图表,因此非常适合用于需要实时展示数据的场景。
  • 应用于用户交互:例如,当鼠标移入时,当前柱的颜色会加深,这样可以提高用户的交互体验。

12.2 绘制简单柱状图

在Canvas上绘制简单柱状图,需要经过以下步骤:

  1. 准备数据:首先,准备好需要展示的数据。例如,我们有一个数组data,其中包含了每个柱状图的高度值。
  2. 计算参数:根据数据计算出绘制柱状图所需的各种参数,包括柱状图的宽度、间距、坐标轴的位置等。
  3. 绘制坐标轴:使用Canvas的绘图API,在指定位置绘制坐标轴,包括横轴和纵轴。
  4. 绘制柱状图:根据数据和参数,使用Canvas的绘图API,在指定位置绘制柱状图。可以使用矩形来表示每个柱状图的高度,可以设置不同的颜色或样式。
  5. 添加标签:可以根据需要,在柱状图上添加标签,标明每个柱状图的具体数值或类别。

下面是一个简单的示例代码,演示如何在Canvas上绘制简单柱状图:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        canvas {
            background: linear-gradient(to bottom, #b5b8b5, #FFFFFF);
            box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
        }
    </style>
</head>

<body>

    <canvas id="barChart" width="400" height="400" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>
<script>
    // 获取 Canvas 元素
    var canvas = document.getElementById('barChart');
    const ctx = canvas.getContext('2d');

    // 数据
    const data = [
        { year: '2010', value: 20, color: 'red' },
        { year: '2011', value: 30, color: 'green' },
        { year: '2012', value: 50, color: 'blue' },
        { year: '2013', value: 40, color: 'orange' },
        { year: '2014', value: 70, color: 'purple' },
    ];

    // 参数
    const barWidth = 30; // 柱状图宽度
    const barSpacing = 15; // 柱状图间距
    const startX = 70; // 柱状图起始位置的x坐标
    const startY = canvas.height - 50; // 柱状图起始位置的y坐标
    const maxValue = Math.max(...data.map(item => item.value)); // 数据中的最大值

    // 绘制X轴
    ctx.beginPath();
    ctx.moveTo(startX, startY);
    ctx.lineTo(canvas.width - 50, startY); // X轴位置在下方
    ctx.stroke();

    // 绘制Y轴
    ctx.beginPath();
    ctx.moveTo(startX, startY);
    ctx.lineTo(startX, 50); // Y轴位置在左侧
    ctx.stroke();

    // 绘制X轴标注
    ctx.font = '12px Arial';
    ctx.fillStyle = 'black';
    data.forEach((item, index) => {
        const x = startX + barSpacing + (barWidth + barSpacing) * index;
        const y = startY + 15;
        ctx.fillText(item.year, x, y);
    });

    // 绘制Y轴标注
    const step = Math.ceil(maxValue / 5); // 刻度间隔,向上取整
    for (let i = 0; i <= 5; i++) {
        const x = startX;
        const y = startY - (i / 5) * (startY - 50);
        ctx.beginPath();
        ctx.moveTo(x, y);
        ctx.lineTo(x - 5, y);
        ctx.stroke();
        ctx.fillText((step * i).toString(), x - 25, y + 5);
    }

    // 绘制柱状图
    ctx.fillStyle = 'blue';
    data.forEach((item, index) => {
        const barHeight = (item.value / maxValue) * (startY - 50); // 计算柱状图高度
        const x = startX + barSpacing + (barWidth + barSpacing) * index;
        const y = startY - barHeight;
        ctx.fillStyle = item.color;
        ctx.fillRect(x, y, barWidth, barHeight);

        // 添加数值标注
        ctx.fillStyle = 'black';
        ctx.fillText(item.value.toString(), x + barWidth / 2 - 10, y - 10);
    });
</script>
</body>

</html>

image

12.3 美化柱状图

通过CSS样式对柱状图进行美化包括修改柱状图颜色、添加渐变效果、添加动画效果等操作

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        canvas {
            background: linear-gradient(to bottom, #b5b8b5, #FFFFFF);
            box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
        }
    </style>
</head>

<body>

    <canvas id="myCanvas" width="400" height="400" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>
</body>

<script src="./12.3.js"></script>
<script>

    // 示例数据
    const data = {
        dataArr: [
            [2018, 322],
            [2019, 735],
            [2020, 543],
            [2021, 436],
            [2022, 354],
            [2023, 735]
        ],
        xAxisLabel: '年份', // X轴
        yAxisLabel: '数量', // Y轴
    };

    // 示例调用
    const options = {
        barWidth: 30,
        barSpacing: 15,
        startX: 70,
    };

    // 绘制柱状图
    const barChart = new BarChart('myCanvas', data, options);

</script>
</body>

</html>
class BarChart {
  constructor(canvasId, data, options) {
    this.canvas = document.getElementById(canvasId);
    this.ctx = this.canvas.getContext("2d");
    this.options = {
      chartMargin: 30,
      chartSpace: 60,
      barMargin: 20,
      xAxisColor: "black",
      yAxisColor: "black",
      font: "12px Arial",
      labelColor: "black",
      speed: 10,
      yGridlineCount: 10,
      ...options,
    };
    this.data = data;
    // 图表相关
    this.chartWidth = 0;
    this.chartHeight = 0;
    this.originX = 0;
    this.originY = 0;
    // 柱体相关
    this.barMargin = 0;
    this.barWidth = 0;
    this.maxValue = 0;
    this.yGridlineCount = 0;
    this.gradient = null;
    // 动画相关
    this.ctr = 1;
    this.numctr = 100;
    // 鼠标相关
    this.mousePosition = {};
    this.mouseTimer = null;
    this.drawChart(this.canvas, this.ctx, this.options);
  }

  drawChart(canvas, ctx, options) {
    this.initChart(canvas, ctx, options);
    this.drawMarkers();
    this.drawBarAnimate();
    canvas.addEventListener("mousemove", this.handleMouseMove.bind(this));
  }

  // 图表初始化
  initChart(canvas, ctx, options) {
    this.chartHeight =
      canvas.height - options.chartMargin * 2 - options.chartSpace;
    this.chartWidth =
      canvas.width - options.chartMargin * 2 - options.chartSpace;
    this.originX = options.chartMargin + options.chartSpace;
    this.originY = options.chartMargin + this.chartHeight;

    this.barMargin = options.barMargin;
    this.barWidth = parseInt(
      this.chartWidth / this.data.dataArr.length - this.barMargin
    );
    for (var i = 0; i < this.data.dataArr.length; i++) {
      var barVal = parseInt(this.data.dataArr[i][1]);
      if (barVal > this.maxValue) {
        this.maxValue = barVal;
      }
    }
    this.maxValue += 50;
    this.yGridlineCount = options.yGridlineCount;

    this.gradient = ctx.createLinearGradient(0, 0, 0, 300);
    this.gradient.addColorStop(0, "green");
    this.gradient.addColorStop(1, "rgba(145,198,116,1)");
  }

  // 绘制图表轴、标签和标记
  drawMarkers() {
    this.ctx.translate(0.5, 0.5);
    this.ctx.font = this.options.font;
    this.ctx.lineWidth = 1;
    this.ctx.fillStyle = this.options.labelColor;
    this.ctx.strokeStyle = this.options.labelColor;

    this.drawLine(
      this.originX,
      this.originY,
      this.originX,
      this.options.chartMargin
    );
    this.drawLine(
      this.originX,
      this.originY,
      this.originX + this.chartWidth,
      this.originY
    );

    this.drawYMarkers();
    this.drawXMarkers();

    this.ctx.save();
    this.ctx.rotate(-Math.PI / 2);
    this.ctx.fillText(
      this.data.yAxisLabel,
      -this.canvas.height / 2,
      this.options.chartSpace - 10
    );
    this.ctx.restore();
    this.ctx.fillText(
      this.data.xAxisLabel,
      this.originX + this.chartWidth / 2,
      this.originY + this.options.chartSpace / 2 + 10
    );

    this.ctx.translate(-0.5, -0.5);
  }

  drawYMarkers() {
    this.ctx.strokeStyle = "#E0E0E0";
    var oneVal = parseInt(this.maxValue / this.yGridlineCount);
    this.ctx.textAlign = "right";
    for (var i = 0; i <= this.yGridlineCount; i++) {
      var markerVal = i * oneVal;
      var xMarker = this.originX - 5;
      var yMarker =
        parseInt(this.chartHeight * (1 - markerVal / this.maxValue)) +
        this.options.chartMargin;
      this.ctx.fillText(
        markerVal,
        xMarker,
        yMarker + 3,
        this.options.chartSpace
      );
      if (i > 0) {
        this.drawLine(
          this.originX,
          yMarker,
          this.originX + this.chartWidth,
          yMarker
        );
      }
    }
  }

  drawXMarkers() {
    this.ctx.textAlign = "center";
    for (var i = 0; i < this.data.dataArr.length; i++) {
      var markerVal = this.data.dataArr[i][0];
      var xMarker = parseInt(
        this.originX +
          this.chartWidth * (i / this.data.dataArr.length) +
          this.barMargin +
          this.barWidth / 2
      );
      var yMarker = this.originY + 15;
      this.ctx.fillText(markerVal, xMarker, yMarker, this.options.chartSpace);
    }
  }

  drawLine(x, y, X, Y) {
    this.ctx.beginPath();
    this.ctx.moveTo(x, y);
    this.ctx.lineTo(X, Y);
    this.ctx.stroke();
    this.ctx.closePath();
  }

  drawBarAnimate(mouseMove) {
    for (var i = 0; i < this.data.dataArr.length; i++) {
      var barVal = this.data.dataArr[i][1];
      var barH = parseInt(
        (((this.chartHeight * barVal) / this.maxValue) * this.ctr) / this.numctr
      );
      var y = this.originY - barH;
      var x =
        this.originX + (this.barWidth + this.barMargin) * i + this.barMargin;
      this.drawRect(x, y, this.barWidth, barH, mouseMove);
      this.ctx.fillText(
        parseInt((barVal * this.ctr) / this.numctr),
        x + 15,
        y - 8
      );
    }
    if (this.ctr < this.numctr) {
      this.ctr++;
      setTimeout(() => {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.drawMarkers();
        this.drawBarAnimate();
      }, this.options.speed);
    }
  }

  //绘制方块
  drawRect(x, y, X, Y, mouseMove) {
    this.ctx.beginPath();
    this.ctx.rect(x, y, X, Y);
    if (
      mouseMove &&
      this.ctx.isPointInPath(this.mousePosition.x, this.mousePosition.y)
    ) {
      this.ctx.fillStyle = "black";
    } else {
      this.ctx.fillStyle = this.gradient;
      this.ctx.strokeStyle = this.gradient;
    }
    this.ctx.fill();
    this.ctx.closePath();
  }

  handleMouseMove(e) {
    e = e || window.event;
    if (e.layerX || e.layerX == 0) {
      this.mousePosition.x = e.layerX;
      this.mousePosition.y = e.layerY;
    } else if (e.offsetX || e.offsetX == 0) {
      this.mousePosition.x = e.offsetX;
      this.mousePosition.y = e.offsetY;
    }

    clearTimeout(this.mouseTimer);
    this.mouseTimer = setTimeout(() => {
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      this.drawMarkers();
      this.drawBarAnimate(true);
    }, 10);
  }
}

image

12.4 数据动态更新:实现动态柱状图

通为了实现动态柱状图的数据动态更新,可以通过JavaScript添加事件监听器来实现。具体步骤包括使用异步请求向服务端获取新的数据,修改数据并重新绘制柱状图。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        canvas {
            background: linear-gradient(to bottom, #b5b8b5, #FFFFFF);
            box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
            /* animation: slide-in 2s; */
        }

        @keyframes slide-in {
            from {
                transform: translateX(-100%);
            }

            to {
                transform: translateX(0);
            }
        }
    </style>
</head>

<body>

    <canvas id="myCanvas" width="400" height="400" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>

    <div style="margin-top: 10px;">
        <button onclick="refresh()">刷新</button>
    </div>
</body>

<script src="./12.3.js"></script>
<script>
    // 配置项
    const options = {
        barWidth: 30,
        barSpacing: 15,
        startX: 70,
    };

    document.addEventListener("DOMContentLoaded", function (event) {
        // 在此处执行页面加载完成后的操作
        console.log("页面加载完成");

        // 可以在这里调用其他函数、初始化变量、绑定事件等等
        init();
    });

    function init() {

        // 示例数据
        var data = {
            dataArr: [
                [2018, 322],
                [2019, 735],
                [2020, 543],
                [2021, 436],
                [2022, 354],
                [2023, 735]
            ],
            xAxisLabel: '年份', // X轴
            yAxisLabel: '数量', // Y轴
        };

        // 绘制柱状图
        drawChart(data);
    }

    function fetchData() {
        return fetch('/api/data') // 使用fetch函数向服务端发送异步请求
            .then(function (response) {
                return response.json(); // 将响应结果转换为JSON格式
            })
            .then(function (data) {
                return data; // 返回获取到的新数据
            });
    }


    function refresh() {
        // fetchData数据
        const data = {
            dataArr: [
                [2018, 236],
                [2019, 1245],
                [2020, 934],
                [2021, 542],
                [2022, 134],
                [2023, 453]
            ],
            xAxisLabel: '年份', // X轴
            yAxisLabel: '数量', // Y轴
        };

        drawChart(data);

    }


    function drawChart(data) {
        // 绘制柱状图
        const barChart = new BarChart('myCanvas', data, options);
    }



</script>
</body>

</html>

image

12.5 实战应用:柱状图数据可视化

数据可视化是指使用图表、图形、地图等可视化手段将数据转化为直观、易于理解的可视形式。通过数据可视化,人们可以更容易地发现数据中的模式、趋势和关联性,从而更好地理解数据的含义和价值。
数据可视化有助于将抽象的数据转化为可感知的视觉元素,通过图表、图形等形式展示数据的特征和规律。它不仅可以帮助人们更好地理解数据,还能够有效地传达信息、支持决策,并促进见解和发现。
数据可视化可以应用于各个领域,包括商业、金融、科学、医疗、社交媒体等。在商业领域,数据可视化可以帮助企业分析销售趋势、市场份额和客户行为,以支持市场营销决策和业务战略制定。在科学研究中,数据可视化可以帮助科学家观察和分析实验结果,从而推动科学发现和创新。
数据可视化通常涉及选择合适的图表类型、设计视觉元素(如颜色、形状、大小等)、添加交互功能(如悬停提示、缩放、过滤等)以及呈现数据的方式。常见的数据可视化工具和库(如D3.js、Chart.js、Highcharts、ECharts等)提供了丰富的功能和选项,使得开发者可以更轻松地创建各种类型的数据可视化。

12.5.1 常见的数据可视化库分析

  1. D3.js:

优势:

  • 功能强大:D3.js 是一个功能强大且灵活的数据可视化库,提供了丰富的图表类型和交互功能。
  • 高度定制化:D3.js 允许开发者自由地控制可视化的每个细节,可以实现高度定制化的可视化效果。
  • 数据驱动:D3.js 的设计理念是将数据和文档绑定在一起,使得数据驱动可视化,非常适合处理大规模和复杂的数据集。

劣势:

  • 学习曲线陡峭:相比其他库,D3.js 的学习曲线较陡峭,需要对前端开发有一定的了解。
  • 编写更多的代码:由于其灵活性和自由度高,使用 D3.js 可能需要编写更多的代码来实现特定的可视化需求。
  1. Chart.js:

优势:

  • 简单易用:Chart.js 是一个简单易用的数据可视化库,提供了常见的图表类型,并具有良好的文档和示例。
  • 快速创建基本图表:Chart.js 适合快速创建基本的图表,配置选项较少,使用起来非常方便。

劣势:

  • 定制性和扩展性较弱:相比其他库,Chart.js 的定制性和扩展性较弱,不太适合处理复杂的可视化需求。
  • 不支持高级交互和动画效果:Chart.js 的功能相对较简单,不支持一些高级的交互和动画效果。
  1. Highcharts:

优势:

  • 商业级品质:Highcharts 是一个功能强大且易于使用的商业级数据可视化库,提供了多种图表类型和丰富的配置选项。
  • 兼容性和浏览器支持:Highcharts 具有良好的兼容性,支持主流浏览器,并且提供了响应式布局。

劣势:

  • 商业产品:Highcharts 是一个商业产品,虽然提供了免费版本,但在某些特定用途下可能需要购买许可证。
  • 自定义能力稍弱:相比其他开源库,Highcharts 的自定义能力稍弱。
  1. ECharts:

优势:

  • 多样化的图表类型:ECharts 提供了丰富的图表类型,可以满足各种不同的数据可视化需求。
  • 强大的交互功能:ECharts 支持多种交互方式,用户可以通过交互方式深入探索数据。
  • 可定制性强:ECharts 提供了丰富的配置选项,可以自定义图表的样式、布局和交互行为。

劣势:

  • 学习曲线较陡峭:相比一些简单易用的库,ECharts 的学习曲线可能较陡峭,需要花费一定的时间来熟悉其使用方法和配置选项。
  • 不适合大规模数据:在处理大规模数据时,ECharts 的性能可能有一定限制。
  1. AntV(包括 G2、G6 等):

优势:

  • 专注于数据可视化:AntV 是一个专注于数据可视化的开源库,提供了多个子库(如 G2、G6),具有丰富的图表类型和交互功能。
  • 大数据可视化支持:AntV 的子库 G6 特别适合处理大规模数据的可视化,具有出色的性能和扩展性。
  • 社区活跃:AntV 拥有活跃的开发者社区,提供了详细的文档、示例和教程。

劣势:

  • 学习曲线较陡峭:与其他库类似,AntV 的学习曲线可能较陡峭,需要一定的时间来熟悉其使用方法和配置选项。
  • 部分文档不完善:由于 AntV 是相对较新的库,一些子库的文档可能不如其他库那样完善。

综上所述,每个数据可视化库都有其特点和适用场景。选择最合适的库取决于项目需求、技术背景和个人偏好。如果需要灵活和定制化的可视化效果,D3.js 和 AntV(如 G2)是不错的选择。对于快速创建基本图表或简单的可视化需求,Chart.js 和 Highcharts 提供了方便易用的解决方案。而 ECharts 则提供了丰富的图表类型和交互功能。最终,根据具体需求和项目要求,选择最适合的库是关键。

12.5.2 基于Echart.js的示例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        canvas {
            background: linear-gradient(to bottom, #b5b8b5, #FFFFFF);
            box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
            /* animation: slide-in 2s; */
        }

        @keyframes slide-in {
            from {
                transform: translateX(-100%);
            }

            to {
                transform: translateX(0);
            }
        }
    </style>
    <!-- 引入 ECharts.js -->
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.1.2/dist/echarts.min.js"></script>
</head>

<body>
    <canvas id="chart" width="400" height="400" style="border: 1px solid #ccc">
        您的浏览器不支持HTML5 canvas标签。
    </canvas>

    <script>
        // 初始化图表实例
        var myChart = echarts.init(document.getElementById('chart'));

        // 配置项和数据
        var option = {
            title: {
                text: '图表'
            },
            tooltip: {},
            legend: {
                data: ['数量']
            },
            xAxis: {
                data: ['一月', '二月', '三月', '四月', '五月', '六月']
            },
            yAxis: {},
            series: [{
                name: '数量',
                type: 'bar',
                data: [120, 200, 150, 80, 70, 110]
            }]
        };

        // 使用刚指定的配置项和数据显示图表
        myChart.setOption(option);

    </script>
</body>

</html>

image

这个示例展示了如何使用 ECharts.js 创建一个简单的柱状图。首先,我们在 HTML 中定义了一个容器来放置图表。然后,在 JavaScript 中,我们初始化了一个图表实例,并通过配置项和数据来定义图表的样式和内容。最后,使用 setOption 方法将配置项和数据应用到图表中,从而显示出柱状图。
在这个示例中,我们定义了一个标题、提示框、图例、X 轴、Y 轴和一个系列。系列是柱状图的主要组成部分,它包含了柱子的颜色、宽度、高度等信息。

12.6 总结

Canvas 可以用于创建各种类型的图表,包括柱状图。下面是 Canvas 柱状图的应用和实现方法的总结:
应用:

  1. 数据可视化:Canvas 柱状图可以用于展示数据,帮助人们更好地理解和分析数据。
  2. 图表报告:Canvas 柱状图可以用于生成图表报告,支持决策和创新。
  3. 游戏开发:Canvas 柱状图可以用于游戏开发,用于绘制游戏场景和角色。

实现方法:

  1. 准备工作:首先,需要创建一个 Canvas 元素,并设置其宽度和高度。然后,需要获取 Canvas 的上下文(context),这个上下文是用于绘制图形的关键对象。
  2. 绘制坐标轴:在 Canvas 上绘制柱状图之前,需要先绘制坐标轴。坐标轴通常包括 X 轴和 Y 轴,可以使用 Canvas 的绘图方法(如 moveTo、lineTo)来绘制。
  3. 绘制柱子:绘制柱子是 Canvas 柱状图的核心部分。可以使用 Canvas 的 fillRect 方法来绘制柱子,该方法需要指定柱子的位置、宽度和高度等属性。
  4. 添加交互功能:为了增强用户体验,可以为 Canvas 柱状图添加交互功能,如悬停提示、点击事件等。可以使用 JavaScript 代码来监听鼠标事件,并根据事件的位置来显示提示框或执行其他操作。

总之,Canvas 柱状图是一种强大的数据可视化工具,可以用于创建各种类型的柱状图,包括简单的静态图表和复杂的动态图表。要实现 Canvas 柱状图,需要掌握 Canvas 的基本绘图方法和 JavaScript 的事件处理机制,并能够将数据转化为柱子的位置和高度等属性。

13- 结语

在本书的学习过程中,我们深入了解了 Canvas 技术的基础知识,并学会了如何绘制基本图形和路径,使用样式和渐变填充,进行图像处理和像素操作,以及实现动画和交互效果。通过不断地实践和探索,我们逐渐掌握了 Canvas 的各种技巧和方法。

回顾本书的内容,我们可以得出以下几点学习收获。首先,我们对 Canvas 技术有了更深入的认识和理解。我们了解了 Canvas 的工作原理和基本概念,知道如何创建和管理 Canvas 元素,并掌握了 Canvas 的绘图环境和 API。其次,我们学会了绘制基本图形和路径的技巧,包括直线、曲线、矩形、圆形等。我们还学会了使用样式和渐变填充来装饰图形,并掌握了图像处理和像素操作的方法,如图像加载、裁剪、缩放、滤镜等。最后,我们理解了动画和交互的实现原理,掌握了动画的绘制和更新方法,以及如何处理用户输入和事件。

在未来,Canvas 技术将继续发展和演进,为开发者提供更多的创作空间和技术可能性。为了更好地应对这一挑战,我们可以采取以下几点学习建议。

  • 首先,深入学习 Canvas 相关知识,包括学习 WebGL 和 Three.js 等高级图形库,以及学习 Canvas 应用开发的相关技术和框架。这将帮助我们更好地理解和应用 Canvas 技术,拓展我们的技术广度和深度。
  • 其次,我们需要通过实践和项目经验来提升自己的能力。参与开源项目或自己开发项目,培养良好的代码风格和团队协作能力,不断积累实践经验。
  • 最后,我们要持续学习和更新知识。关注最新的 Web 技术和趋势,参加技术社区和会议,与同行交流分享经验,保持对技术的敏锐度和热情。

在结束之际,我要向所有读者表达由衷的感谢和鼓励。感谢你们选择阅读本书,并投入时间和精力来学习 Canvas 技术。希望本书能够帮助你们掌握 Canvas 的基础知识和技巧,为你们在 Web 开发中绘制出更丰富和吸引人的图形和动画效果提供帮助。同时,也要感谢本书的编辑和发布团队,他们的辛勤工作和专业素养使得本书得以顺利完成。最后,我要特别感谢那些在本书的撰写和发布过程中给予支持和协助的人员,没有你们的帮助,本书将无法如期完成。

最后,我衷心祝愿所有读者在 Canvas 学习和应用的道路上取得更进一步的成就。无论是继续深入学习 Canvas 相关知识,还是通过实践和项目经验不断提升自己的能力,都希望你们能够保持热情和耐心,坚持不懈地追求技术的进步和创新。再次感谢大家的支持和关注,祝愿你们在未来的学习和工作中一帆风顺,取得更大的成功!