canvas 教程 指南 万字总结 从入门到放弃 面试 前段 JavaScript HTML H5
创始人
2025-06-01 19:45:03

前端妹子问我为什么官网上面只有一个canvas标签,里面什么都没有…

我脸色一变”完了!官网挂了!“。走过去一看,原来是canvas的动画啊。

”这个你想学啊~我教你啊,到时候你可别说听不懂“。

基础知识

使用 元素不是非常难,但你需要一些基本的HTML和JavaScript知识。除一些过时的浏览器不支持 元素外,所有的新版本主流浏览器都支持它。Canvas 的默认大小为 300 像素 ×150 像素(宽 × 高,像素的单位是 px)。但是,可以使用 HTML 的高度和宽度属性来自定义 Canvas 的尺寸。为了在 Canvas 上绘制图形,我们使用一个 JavaScript 上下文对象,它能动态创建图像(creates graphics on the fly)。

基本使用

标签只有两个属性**——** widthheight。这些都是可选的,并且同样利用 DOM properties 来设置。当没有设置宽度和高度的时候,canvas 会初始化宽度为 300 像素和高度为 150 像素(这个和svg一样)。该元素可以使用CSS来定义大小,但在绘制时图像会伸缩以适应它的框架尺寸:如果 CSS 的尺寸与初始画布的比例不一致,它会出现扭曲。

如果你绘制出来的图像是扭曲的,尝试用 width 和 height 属性为明确规定宽高,而不是使用 CSS。

id属性并不是元素所特有的,而是每一个 HTML 元素都默认具有的属性(比如 class 属性)。给每个标签都加上一个 id 属性是个好主意,因为这样你就能在我们的脚本中很容易的找到它。

替代内容

在一些不支持canvas的浏览器或者某些特定版本下,需要加入替代内容。

这非常简单:我们只是在标签中提供了替换内容。不支持的浏览器将会忽略容器并在其中渲染后备内容。而支持的浏览器将会忽略在容器中包含的内容,并且只是正常渲染 canvas

由于当然浏览器不支持canvas,就只显示我啦~

上下文Context

canvas元素创造了一个固定大小的画布,它公开了一个或多个渲染上下文,其可以用来绘制和处理要展示的内容。后面会着重介绍2D相关内容。

canvas 起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。canvas元素有一个叫做 getContext()的方法,这个方法是用来获得渲染上下文和它的绘画功能。getContext()接受一个参数,即上下文的类型。

但是要注意识别某些无法使用canvas的情况,这样才更加鲁棒!

const canvas = document.getElementById('canvas');
if (canvas.getContext) {const ctx = canvas.getContext('2d');// 鲁棒的代码……
} else {// 无法识别情况……
}

绘制

不同于 SVGcanvas只支持两种形式的图形绘制:矩形和路径(由一系列点连成的线段)。所有其他类型的图形都是通过一条或者多条路径组合而成的。不过,我们拥有众多路径生成的方法让复杂图形的绘制成为了可能。

Tips: SVG的入门指南也有更新,在我的主页里面就可以看见。

绘制矩形

canvas 提供了三种方法绘制矩形:

  • fillRect(x, y, width, height)

    绘制一个填充的矩形

  • strokeRect(x, y, width, height)

    绘制一个矩形的边框

  • clearRect(x, y, width, height)

    清除指定矩形区域,让清除部分完全透明。

上面提供的方法之中每一个都包含了相同的参数。x 与 y 指定了在 canvas 画布上所绘制的矩形的左上角(相对于原点)的坐标。width 和 height 设置矩形的尺寸。

纸上得来终觉浅,绝知此事要躬行!撸起袖子,开敲!




在这里插入图片描述
细心的人已经发现矩形很模糊,而且没发现的还沉浸在自己成功的喜悦之中。

高清的Canvas

那为什么会模糊?

主要是现在都是高清屏,就是分辨率上来了,画图的时候不是这样画的,只是等比例放大,所以模糊。

那么怎么办?把画布也同步放大!

加入这样的一个函数,传入canvas的上下文进去,让他来改造。

function hdCanvas(canvas, ratio) {const rect = canvas.getBoundingClientRect();canvas.width = rect.width * ratio;canvas.height = rect.height * ratio;canvas.style.width = rect.width + "px"canvas.style.height = rect.height + "px"
}

修改之后就高清多啦~

矩形源码

绘制路径

绘制路径大致有下面4个步骤

  1. 首先,你需要创建路径起始点。
  2. 然后你使用画图命令去画出路径。
  3. 之后你把路径封闭。
  4. 一旦路径生成,你就能通过描边或填充路径区域来渲染图形。

以下是所要用到的函数:

  • beginPath()

    新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。

  • closePath()

    闭合路径之后图形绘制命令又重新指向到上下文中。

  • stroke()

    通过线条来绘制图形轮廓。

  • fill()

    通过填充路径的内容区域生成实心的图形。

画一个三角形

三角形就是3条简单的直线。

function draw() {const canvas = document.getElementById("canvas");if (canvas.getContext) {const ctx = canvas.getContext("2d");const ratio = window.devicePixelRatio || 1hdCanvas(canvas, ratio)// 放大倍数ctx.scale(ratio, ratio);ctx.lineWidth = 10// 空心 - 没有 closePathctx.beginPath();ctx.moveTo(75, 50);ctx.lineTo(100, 75);ctx.lineTo(100, 25);ctx.lineTo(75, 50);// ctx.closePath();ctx.stroke();// 空心 - closePathctx.beginPath();ctx.moveTo(275, 50);ctx.lineTo(300, 75);ctx.lineTo(300, 25);// ctx.lineTo(275, 50);// 有 closePath会自动首尾相连ctx.closePath();ctx.stroke();// 实心ctx.beginPath();ctx.moveTo(175, 50);ctx.lineTo(200, 75);ctx.lineTo(200, 25);ctx.closePath();ctx.fill();}}

在这里插入图片描述

可以很明显的看到,没有closePath他是没有闭合的。有closePath哪怕没有回到起点,他也会自动的首尾相连。

上述的四个函数都可以在这个demo中学习到。

画一个圆弧

绘制圆弧或者圆,我们使用arc()方法。当然可以使用arcTo(),不过这个不太好理解,也不常用就不介绍了,着重介绍arc()

arc(x, y, radius, startAngle, endAngle, anticlockwise)

画一个以(x,y)为圆心的以 radius 为半径的圆弧(圆),从 startAngle 开始到 endAngle 结束,按照 anticlockwise 给定的方向(默认为顺时针)来生成。

  • x -> 圆弧中心(圆心)的 x 轴坐标。
  • y -> 圆弧中心(圆心)的 y 轴坐标。
  • radius -> 圆弧的半径。
  • startAngle -> 圆弧的起始点,x 轴方向开始计算,单位以弧度表示。
  • endAngle -> 圆弧的终点,单位以弧度表示。
  • anticlockwise -> 默认为 false可选的Boolean值,如果为 true,逆时针绘制圆弧,反之,顺时针绘制。

arc() 函数中表示角的单位是弧度,不是角度。角度与弧度的 js 表达式:

弧度=(Math.PI/180)*角度。

具体的起点,终点坐标可以看下图

在这里插入图片描述

具体看一个demo

function draw() {const canvas = document.getElementById("canvas");if (canvas.getContext) {const ctx = canvas.getContext("2d");const ratio = window.devicePixelRatio || 1hdCanvas(canvas, ratio)// 放大倍数ctx.scale(ratio, ratio);ctx.beginPath();ctx.arc(100, 50, 20, 0.5 * Math.PI, 2 * Math.PI);ctx.stroke();/*** 不加这句话,上个圆的终点会与当前这个圆的期待你相连, 不信可以试试* 也可以用 moveTo 到圆的起点, 这里偷懒了, 不太规范*/ctx.beginPath();ctx.arc(200, 50, 20, 0.5 * Math.PI, 4 * Math.PI);ctx.stroke();}
}

在这里插入图片描述

我们可以发现,起点与终点之间的值超过了2就会完整的一个圆出现。

为什么是2?

九漏鱼??????????

圆的周长等于什么? 2 * π * R

自我学习

马上画一个笑脸。

在这里插入图片描述

ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // 绘制
ctx.moveTo(110, 75);
ctx.arc(75, 75, 35, 0, Math.PI, false);   // 口 (顺时针)
ctx.moveTo(65, 65);
ctx.arc(60, 65, 5, 0, Math.PI * 2, true);  // 左眼
ctx.moveTo(95, 65);
ctx.arc(90, 65, 5, 0, Math.PI * 2, true);  // 右眼
ctx.stroke();

源码

二次贝塞尔曲线和三次贝塞尔曲线

  • quadraticCurveTo(cp1x, cp1y, x, y)

​ 绘制二次贝塞尔曲线,cp1x,cp1y 为一个控制点,x,y 为结束点。

  • bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

​ 绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。

这里就不展开了。这里面很复杂,课后学习!!!

色彩与样式

// 这些 fillStyle 的值均为 '橙色'
ctx.fillStyle = "orange";
ctx.fillStyle = "#FFA500";
ctx.fillStyle = "rgb(255,165,0)";
ctx.fillStyle = "rgba(255,165,0,1)";

色彩

  • fillStyle = color -> 设置图形的填充颜色。
  • strokeStyle = color -> 设置图形轮廓的颜色。

color指上述的各种实现方式

透明度

ctx.globalAlpha = 0.5;

这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0(完全透明)到 1.0(完全不透明),默认是 1.0。

因为 strokeStylefillStyle 属性接受符合 CSS 3 规范的颜色值,那我们可以用下面的写法来设置具有透明度的颜色。

// 指定透明颜色,用于描边和填充样式
ctx.strokeStyle = "rgba(255,0,0,0.5)";
ctx.fillStyle = "rgba(255,0,0,0.5)";

线型

可以通过一系列属性来设置线的样式。

  • lineWidth = value

    设置线条宽度。

  • lineCap = type

    设置线条末端样式。

    它可以为下面的三种的其中之一:buttroundsquare。默认是 butt

  • lineJoin = type

    设定线条与线条间接合处的样式。

    它可以是这三种之一:round, bevelmiter。默认是 miter

  • miterLimit = value

    限制当两条线相交时交接处最大长度;所谓交接处长度(斜接长度)是指线条交接处内角顶点到外角顶点的长度。

  • getLineDash()

    返回一个包含当前虚线样式,长度为非负偶数的数组。

  • setLineDash(segments)

    设置当前虚线样式。

  • lineDashOffset = value

    设置虚线样式的起始偏移量。

通过以下的样例可能会更加容易理解。

把所有的属性都搞一遍。

在这里插入图片描述

源码

渐变

这个和CSS的一样,就不展开了

  • createLinearGradient(x1, y1, x2, y2)

    createLinearGradient 方法接受 4 个参数,表示渐变的起点 (x1,y1) 与终点 (x2,y2)。

  • createRadialGradient(x1, y1, r1, x2, y2, r2)

    createRadialGradient 方法接受 6 个参数,前三个定义一个以 (x1,y1) 为原点,半径为 r1 的圆,后三个参数则定义另一个以 (x2,y2) 为原点,半径为 r2 的圆。

var lineargradient = ctx.createLinearGradient(0,0,150,150);
var radialgradient = ctx.createRadialGradient(75,75,0,75,75,100);

Copy to Clipboard

创建出 canvasGradient 对象后,我们就可以用 addColorStop 方法给它上色了。

  • gradient.addColorStop(position, color)

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

图案样式

这个也和CSS的一样,也不展开了

createPattern(image, type)

该方法接受两个参数。Image 可以是一个 Image 对象的引用,或者另一个 canvas 对象。Type 必须是下面的字符串值之一:repeatrepeat-xrepeat-yno-repeat

阴影

  • shadowOffsetX = float

    shadowOffsetXshadowOffsetY 用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 0

  • shadowOffsetY = float

    shadowOffsetX 和 shadowOffsetY 用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 0

  • shadowBlur = float

    shadowBlur 用于设定阴影的模糊程度,其数值并不跟像素数量挂钩,也不受变换矩阵的影响,默认为 0

  • shadowColor = color

    shadowColor 是标准的 CSS 颜色值,用于设定阴影颜色效果,默认是全透明的黑色。

填充规则

当我们用到 fill(或者 clipisPointinPath )你可以选择一个填充规则,该填充规则根据某处在路径的外面或者里面来决定该处是否被填充,这对于自己与自己路径相交或者路径被嵌套的时候是有用的。

function draw() {var ctx = document.getElementById('canvas').getContext('2d');ctx.beginPath();ctx.arc(50, 50, 30, 0, Math.PI*2, true);ctx.arc(50, 50, 15, 0, Math.PI*2, true);ctx.fill("evenodd");
}

这个可以大家自己试试是什么样子的。

文本

文本绘制也算是蛮重要的一章,可以绘制各式各样的图案,否则的话,按照path去一一绘画,会耗费大量的时间

绘制文本

fillText(text, x, y [, maxWidth\])

在指定的 (x,y) 位置绘制指定的实心文本,绘制的最大宽度是可选的。

strokeText(text, x, y [, maxWidth\])

在指定的 (x,y) 位置绘制指定的空心文本,绘制的最大宽度是可选的。

有样式的文本

  • font = '10px sans-serif'
    • 当前我们用来绘制文本的样式。这个字符串使用和 CSS font属性相同的语法。默认的字体是 10px sans-serif
  • textAlign = 'value'
    • 文本对齐选项。可选的值包括:start, end, left, right or center. 默认值是 start
  • textBaseline = 'value'
    • 基线对齐选项。可选的值包括:top, hanging, middle, alphabetic, ideographic, bottom。默认值是 alphabetic
  • direction = 'value'
    • 文本方向。可能的值包括:ltr, rtl, inherit。默认值是 inherit

如果你之前使用过 CSS,那么这些选项你会很熟悉。

预测文本宽度

当你需要获得更多的文本细节时,下面的方法可以给你测量文本的方法。

  • measureText()

    将返回一个 TextMetrics对象的宽度、所在像素,这些体现文本特性的属性。

下面的代码段将展示如何测量文本来获得它的宽度:

function draw() {var ctx = document.getElementById('canvas').getContext('2d');var text = ctx.measureText("foo"); // TextMetrics objecttext.width; // 16;
}

在这里插入图片描述

上图源码

图像

canvas中还有一项强大的功能就是他的图像处理能力,并不局限于静态图片,可以做到动态图片,甚至是游戏画面等等。这个图片的来源也不至于常见的JEPG、PNG、GIF等,甚至可以是video的某一帧,甚至是其他的canvas作为图片源。

使用图片大致可以分为两步

  • 获得一个HTML对象(可以是URL,图片,视频,其他canvas)
  • 使用drawImage()函数将获取的内容绘制在canvas上

图片源

  1. HTMLImageElement ->
  2. HTMLVideoElement ->
  3. HTMLCanvasElement ->
  4. ImageBitmap -> 位图资源,这里就不展开介绍了。因为浏览器的支持不是很好。

HTMLImageElement

第一种就是直接拿到

第二种是自己创建一个元素,使用 new Image()document.createElement("img") 方法

const c = document.getElementById("canvas-1");
const ctx = c.getContext("2d");
const img = document.getElementById("img1");
const img2 = new Image();
img2.onload = function() {ctx.drawImage(this, 10, 10);
}
img2.src = "https://www.twle.cn/static/i/meimei_160x160.png";
ctx.drawImage(img, 10, 10);

HTMLVideoElement

drawImage里的video并不是拿到什么数据流,而且拿的那一瞬间的一帧图案。

这里给一个简单的小demo后面也有源码。大家可以运行一下自己试试看。

在这里插入图片描述

在线代码: https://codesandbox.io/s/heuristic-feather-qnwdcs?file=/index.html

HTMLCanvasElement

这里很简单。就是传入别的canvascontext就好。直接看个例子就可以悟道了。

const canvas1  = document.createElement("canvas1");
const ctx1 = canvas1.getContext("2d");
ctx1.fillText("canvas")const canvas2  = document.createElement("canvas1");
const ctx2 = canvas1.getContext("2d");
canvas2.drawImage(canvas1, 10, 10);

就这么简单,直接将canvas1作为drawImage的第一个参数就行

drwaImage的变化

drawImage 方法的又一变种是增加了两个用于控制图像在 canvas 中缩放的参数。

  • drawImage(image, x, y, width, height)

    这个方法多了 2 个参数:widthheight,这两个参数用来控制 当向 canvas 画入时应该缩放的大小

drawImage 方法的第三个也是最后一个变种有 8 个新参数,用于控制做切片显示的。

  • drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

    第一个参数和其他的是相同的,都是一个图像或者另一个 canvas 的引用。其他 8 个参数最好是参照右边的图解,前 4 个是定义图像源的切片位置和大小,后 4 个则是定义切片的目标显示位置和大小。

状态恢复与保存

在了解变形之前,我先介绍两个在你开始绘制复杂图形时必不可少的方法。

  • save()

    保存画布 (canvas) 的所有状态

  • restore()

    save 和 restore 方法是用来保存和恢复 canvas 状态的,都没有参数。Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。

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

  • 当前应用的变形(即移动,旋转和缩放,见下)

  • 以及下面这些属性:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled

你可以调用任意多次 save方法。每一次调用 restore 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。

我们尝试用这个连续矩形的例子来描述 canvas 的状态栈是如何工作的。

第一步是用默认设置画一个大四方形,然后保存一下状态。改变填充颜色画第二个小一点的蓝色四方形,然后再保存一下状态。再次改变填充颜色绘制更小一点的半透明的白色四方形。

到目前为止所做的动作和前面章节的都很类似。不过一旦我们调用 restore,状态栈中最后的状态会弹出,并恢复所有设置。如果不是之前用 save 保存了状态,那么我们就需要手动改变设置来回到前一个状态,这个对于两三个属性的时候还是适用的,一旦多了,我们的代码将会猛涨。

当第二次调用 restore 时,已经恢复到最初的状态,因此最后是再一次绘制出一个黑色的四方形。

ctx.fillRect(0, 0, 150, 150);   // 使用默认设置绘制一个矩形
ctx.save();                  // 保存默认状态ctx.fillStyle = '#09F'       // 在原有配置基础上对颜色做改变
ctx.fillRect(15, 15, 120, 120); // 使用新的设置绘制一个矩形ctx.save();                  // 保存当前状态
ctx.fillStyle = '#FFF'       // 再次改变颜色配置
ctx.globalAlpha = 0.5;
ctx.fillRect(30, 30, 90, 90);   // 使用新的配置绘制一个矩形ctx.restore();               // 重新加载之前的颜色状态
ctx.fillRect(45, 45, 60, 60);   // 使用上一次的配置绘制一个矩形ctx.restore();               // 加载默认颜色配置
ctx.fillRect(60, 60, 30, 30);   // 使用加载的配置绘制一个矩形

在这里插入图片描述

源码

变形

这里面的都和css的一样,就不多介绍了。

  • translate(x, y)

    x *是左右偏移量,*y 是上下偏移量,

  • rotate(angle)

    这个方法只接受一个参数:旋转的角度 (angle),它是顺时针方向的,以弧度为单位的值。

​ 旋转的中心点始终是 canvas 的原点,如果要改变它,我们需要用到 translate方法。

  • scale(x, y)

    scale方法可以缩放画布的水平和垂直的单位。两个参数都是实数,可以为负数,x 为水平缩放因子,y 为垂直缩放因子,如果比 1 小,会缩小图形,如果比 1 大会放大图形。默认值为 1,为实际大小。

  • transform(a, b, c, d, e, f)

​ 这个方法是将当前的变形矩阵乘上一个基于自身参数的矩阵

​ 这个函数的参数各自代表如下:

  1. a (m11) 水平方向的缩放
  2. b(m12)竖直方向的倾斜偏移
  3. c(m21)水平方向的倾斜偏移
  4. d(m22)竖直方向的缩放
  5. e(dx)水平方向的移动
  6. f(dy)竖直方向的移动
  • setTransform(a, b, c, d, e, f)

    这个方法会将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 transform方法。如果任意一个参数是无限大,那么变形矩阵也必须被标记为无限大,否则会抛出异常。从根本上来说,该方法是取消了当前变形,然后设置为指定的变形,一步完成。

  • resetTransform()

    重置当前变形为单位矩阵,它和调用以下语句是一样的:ctx.setTransform(1, 0, 0, 1, 0, 0);

在这里插入图片描述

源码

图像混合

globalCompositeOperation = type

type有26种。

[ 'source-over','source-in','source-out','source-atop','destination-over','destination-in','destination-out','destination-atop','lighter', 'copy','xor', 'multiply', 'screen', 'overlay', 'darken','lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light','difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity'
]

在这里插入图片描述

源码

基础动画

我们知道canvas其实就是一个画布,我们在上面作画。等我们看到的时候已经作画完毕。那要怎么制作动画呢?如果需要移动它,我们不得不对所有东西(包括之前的)进行重绘。重绘是相当费时的,而且性能很依赖于电脑的速度。

我们可以制作很多帧,每一帧都是定格。已快速的速度播放,比如小时候看的定格动画。看的影视剧其实也可以拆分为60个定格图片。打的游戏FPS多少就是拆成多少帧"canvas"

那这样的话是不是就有思路啦?

有两种方法可以实现这样的动画操控。首先可以通过 setIntervalsetTimeout 方法来控制在设定的时间点上执行重绘。

setInterval(() => {ctx.arc(x, y, 50, 0, Math.PI * 2, true);ctx.stroke();if(x >= 300) {x = 70} else {x += 1}
}, 1000 / 60)

在这里插入图片描述

好像差点意思,不是我们想要的那个圆圈的活动啊。虽然确实动起来了!是不是我们在画新的圆之前先清除一下?

代码稍作修改

const timer = setInterval(() => {ctx.clearRect(0,0,450,150)ctx.translate(x, y)ctx.arc(70, 80, 50, 0, Math.PI * 2, true);ctx.stroke();if(x >= 300) {x = 1} else {x += 1}}, 1000 / 10)

在这里插入图片描述

之前还没发现,怎么中间还有一条线,而且这个间距明明都是 1啊,为什么越来越大,而且这个并没有清除成功啊!

  1. 中间的线应该是因为我们是连续作画,没有closepath导致的拖拽。
  2. 间距应该是因为这个translate每次都在之前的基础继续叠加,变成指数相关了。需要save一下状态
  3. 清除失败应该也是因为没有save清除之后的状态。

再改一下!!!!

const timer = setInterval(() => {ctx.save()ctx.beginPath();ctx.clearRect(0,0,450,150)ctx.translate(x, y)ctx.arc(70, 80, 50, 0, Math.PI * 2, true);ctx.stroke();ctx.closePath();ctx.restore();if(x >= 300) {x = 1} else {x += 1}}, 1000 / 60)

在这里插入图片描述

ohhhhhhhhhhh!成型!!!

总结

  1. 重绘之前要清空、或者保留状态。
  2. 画好一个东西之后要closePath

强化训练

做一个时钟~

在这里插入图片描述

我做的源码~

里面几乎用到了之前学到的所有知识。还是很值得练习的,最好是自己手写一份,我的只能算参考不算标准答案。

拖影动画

拖影效果的原理就是将之前的清除函数clearRect换成带透明度的一个蒙版覆盖上去

ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.fillRect(0,0, 450, 150);
// ctx.clearRect(0, 0, 450, 150)

直接替换一下之前做的时钟。

在这里插入图片描述

源码

事件

键盘事件

属性描述
onkeydown当按下按键时运行脚本
onkeypress当按下并松开按键时运行脚本
onkeyup当松开按键时运行脚本

鼠标事件

通过鼠标触发事件, 类似用户的行为

属性描述
onclick当单击鼠标时运行脚本
ondblclick当双击鼠标时运行脚本
ondrag当拖动元素时运行脚本
ondragend当拖动操作结束时运行脚本
ondragenter当元素被拖动至有效的拖放目标时运行脚本
ondragleave当元素离开有效拖放目标时运行脚本
ondragover当元素被拖动至有效拖放目标上方时运行脚本
ondragstart当拖动操作开始时运行脚本
ondrop当被拖动元素正在被拖放时运行脚本
onmousedown当按下鼠标按钮时运行脚本
onmousemove当鼠标指针移动时运行脚本
onmouseout当鼠标指针移出元素时运行脚本
onmouseover当鼠标指针移至元素之上时运行脚本
onmouseup当松开鼠标按钮时运行脚本
onmousewheel当转动鼠标滚轮时运行脚本
onscroll当滚动元素的滚动条时运行脚本

实践

c.addEventListener('mouseover', function(e){timer = setInterval(draw, 1000 / 60);
});c.addEventListener('mouseout', function(e){clearInterval(timer);
});

像素操作

之前学习过对于图像的获取。现在要学习的是对于图像具体细节的获取,某个像素点的色彩。对于图像的操作,比如图像平滑操作以及如何从canvas画布中保存图像。

ImageData 对象

ImageData 对象中存储着 canvas 对象真实的像素数据

它提供了以下属性

属性说明
ImageData.data只读,Uint8ClampedArray 类型的一维数组,包含着 RGBA 格式的整型数据,范围在 0255 之间( 包括 255 )顺序的数据,
ImageData.height只读,无符号长整型(unsigned long),图片高度,单位是像素
ImageData.width只读,无符号长整型(unsigned long),图片宽度,单位是像素

Uint8ClampedArray

Uint8ClampedArray 是一个 高度 × 宽度 × 4 bytes 的一维数组

这是什么意思呢 ?

直接上图

在这里插入图片描述

可以看到他每四个单位里面就会放好一个点位的RGBA对应的数值。那我们可以做些什么呢?

很简单!取色器!

取色器

结合之前学到的事件,配置鼠标事件,将鼠标当前的颜色输出展示出来。

const canvas = document.getElementById("canvas");const div = document.getElementById("div");
const span = document.getElementById("span");if (canvas.getContext) {const ctx = canvas.getContext("2d");const img = new Image()img.src = './img/colorPicker.jpeg'img.onload = function() {ctx.drawImage(img, 0, 0);img.style.display = 'none';};function pick(event) {const x = event.offsetX;const y = event.offsetY;const pixel = ctx.getImageData(x, y, 1, 1);const data = pixel.data;const rgba = `rgba(${data[0]}, ${data[1]}, ${data[2]}, ${data[3] / 255})`;div.style.background = rgba;span.textContent = rgba}canvas.addEventListener('mousemove', pick)
}

在这里插入图片描述

既然可以取到颜色那么,对于颜色的修改就可以直接赋值操作了。比如加滤镜之类的,这个需要上网找特定的颜色修改公式了。不展开啦。

保存

HTMLCanvasElement 提供一个 toDataURL() 方法,此方法在保存图片的时候非常有用。它返回一个包含被类型参数规定的图像表现格式的数据链接。返回的图片分辨率是 96 dpi。

  • canvas.toDataURL('image/png')

    默认设定。创建一个 PNG 图片。

  • canvas.toDataURL('image/jpeg', quality)

    创建一个 JPG 图片。你可以有选择地提供从 0 到 1 的品质量,1 表示最好品质,0 基本不被辨析但有比较小的文件大小。

当你从画布中生成了一个数据链接,例如,你可以将它用于任何``元素,或者将它放在一个有 download 属性的超链接里用于保存到本地。

你也可以从画布中创建一个Blob对像。

  • canvas.toBlob(callback, type, encoderOptions)

    这个创建了一个在画布中的代表图片的 Blob 对像。

const canvas = document.getElementById("canvas");
// 创建一个 a 标签,并设置 href 和 download 属性
const el = document.createElement('a');
// 设置 href 为图片经过 base64 编码后的字符串,默认为 png 格式
el.href = canvas.toDataURL();
el.download = '文件名称';// 创建一个点击事件并对 a 标签进行触发
const event = new MouseEvent('click');
el.dispatchEvent(event);

结语

至此canvas基本上算是小成了

有问题欢迎留言交流

所有的源码点我

相关内容

热门资讯

AGI的不归之途 AGI,即通用人工智能,正踏上一条不归之途。它犹如一艘在未知海域航行的巨轮,不断探索着智慧的边界。一...
欧盟起草对俄第18轮制裁措施 ... 来源:新华网新华社布鲁塞尔6月2日电(记者张兆卿)欧盟委员会2日发表声明说,欧盟正制定针对俄罗斯的第...
原创 心... 在现代商业环境中,烘焙行业作为食品产业的一个重要分支,正快速发展,面包作为其核心产品,备受瞩目。如今...
首次披露:五台山王黎明,已被查... 政事儿微信公号消息,据山西省纪委监委6月2日消息:日前,经山西省委批准,山西省纪委监委对五台山风景名...
深夜突发!关税升级,特朗普似乎... 当地时间6月2日,美股三大指数集体收涨,道指涨0.08%,纳指涨0.67%,标普500指数涨0.41...
年内创业板首批IPO获受理 创... 5月30日,陕西旅游、三瑞智能、宏明电子、大亚股份4家公司IPO获得受理,创下2025年以来单日受理...
国金证券:创新药密集获批 后续... 智通财经APP获悉,国金证券发布研报称,国家药监局批准11 款全新创新药的上市申请,另外还有2款创新...
美国房地产经纪公司Redfin... 观点网讯:6月2日,美国房地产经纪公司Redfin最新统计数据显示,美国待售房屋总价值高达6980亿...
移出经营异常名录不留痕 监管有... 汪昌莲近日,市场监管总局印发《关于贯彻落实有关事项的通知》,对已经移出经营异常名录的经营主体,不再公...
美股三大指数飘红,白银期货飙涨...   中新经纬6月3日电 美股三大指数低开,盘中走势转强,集体收红。道指收涨0.08%,纳指涨0.67...
特朗普升级贸易战 欧盟警告:可... 财联社6月3日讯(编辑 夏军雄)当地时间周一(6月2日),欧盟警告称,如果美国总统特朗普兑现其最新的...
每经实探|网红家装企业“住范儿... “(端午节)放假前,每天有百把号人来找(住范儿)呢,欠了不少钱。”6月1日下午,《每日经济新闻》记者...
BD交易大爆发,今年总额已超4... 一笔笔BD(business development,业务发展)交易订单让中国创新药又香了起来。据澎...
美股零日期权大热!特朗普交易再... 刚过去的5月,在特朗普政府推迟贸易谈判期限,并与多个主要贸易伙伴展开积极对话后,市场风险偏好有所改观...
海昌海洋公园即将易主?祥源集团... 6月2日,海昌海洋公园控股有限公司(海昌海洋公园,02255.HK)发布公告,由祥源集团间接全资控制...
鲍威尔出席美联储国际金融司活动... 美联储主席鲍威尔(Jerome Powell)周一在美联储国际金融司(IF)成立75周年活动上发表讲...
安联El Erian:美联储进... 知名投资管理公司安联(Allianz)首席经济顾问Mohamed El-Erian近日在《金融时报》...
国产豪车崩盘预警?亏损18亿,... 端午时节,华为发布了尊界S800,价格70.1-101.8W。一石激起千层浪,这个价格引起了巨大的争...
见证历史!新一轮货币战争在路上... 美元信誉持续受损。无论是沙特、还是俄罗斯,越来越多的国家对美国的36万亿美元债务表现出担忧。现在,中...
从一面之恩到千亿帝国CEO,安... 近日,吉利汽车管理层大调整引发行业聚焦。在吉利一季度财报发布的当天,吉利控股集团宣布重大人事调整:极...
智岩科技启动上市辅导 今年10...   近期,深圳智岩科技股份有限公司(以下简称“智岩科技”)正式启动上市辅导,辅导机构为中金公司。智岩...
银发跑者越跑越快,还最能“买买... 文/段修健近日,国家体育总局体科所中国体育经济研究中心发布《中国路跑人群消费与赛事经济发展趋势与特征...
【美股】美股走“V”全线收涨 ... 6月2日,美股走V全线收涨。道指涨0.08%,报42305.48点,收复早盘约1%跌幅并录得三连涨;...
涉嫌虚假记载细节曝光,锦富技术... 锦富技术5月30日晚公告称,公司当日收到中国证监会下发的《行政处罚事先告知书》。锦富技术披露的202...
6.3股市早8点丨三天假发生了... 三天假发生了啥事?股市早8点 老沙自媒体2025年6月3日(周二)每日大道正道消息▊美股小涨北京时间...
绿通科技:拟现金收购大摩半导体... 【绿通科技筹划收购大摩半导体不低于51%股权】6月2日晚间,绿通科技公告,筹划现金收购江苏大摩半导体...
股市必读:爱博医疗(68805... 截至2025年5月30日收盘,爱博医疗(688050)报收于71.41元,下跌1.76%,换手率1....
销售会“提问”,再冷淡的客户,... 回复“9”限时领《9套销售话术资料包》 作者:Dora 在销售咨询过程中,“高效提问”不仅是获取客户...
原创 德... 2025年2月24日,恰逢俄乌冲突爆发三周年,德国也于前一天举行了新一轮的议会选举。在过去的三年中,...
明天沪主板新股海阳科技申购!聚... 明天,沪主板将迎来一家新股申购! 格隆汇获悉,海阳科技(603382)于6月3日申购,发行价格为11...