最【通俗易懂】的 canvas 入门教程(多图预警)
最【通俗易懂】的 canvas 入门教程(多图预警)
canvas画布的宽度与高度
canvas
标签只有两个常用的标签属性:width 和 height。
当没有设置宽度和高度的时候,canvas
会初始化成宽300px,高150px的画布。
使用 html 属性设置 width,height 时,只影响画布本身,不影响画布内容。
通过 css 样式指定 canvas
的 width,height 时,不但影响画布本身的宽高,还会使画布中的内容等比例缩放(缩放参照画布默认的尺寸)。
<!-- 不要通过样式指定宽高 -->
<canvas id="canvas" width="400" height="200" style="border: 1px solid"></canvas>
canvas画笔
canvas
标签身上有一个 getContext() 方法。
这个方法是用来获取渲染上下文和它绘画的功能。
const canvas = document.getElementById('canvas')// 检查浏览器是否支持canvas
if (canvas.getContext) {// canvas相关的操作基本都在ctx上面进行const ctx = canvas.getContext('2d')
}
canvas绘制矩形
canvas
标签只支持一种原生的图像绘制,那就是矩形。
其他图像的绘制至少都需要生成一条路径(后续会介绍路径)。
矩形包含 fillRect(填充矩形)和 strokeRect (边框矩形)两种,这两种绘制矩形的方法不会生成路径。
ctx.fillRect(x, y, width, height)
ctx.strokeRect(x, y, width, height)
两种矩形的参数都是一样的:
- x:x轴偏移量。
- y:y轴偏移量。
- width:矩形宽度。
- height:矩形高度。
注意:画布左上方 x 轴,y 轴的偏移量为 (0, 0),所以这里可以看作为原点。
// 填充矩形
ctx.fillRect(50, 50, 100, 100)// 边框矩形
ctx.strokeRect(200, 50, 100, 100)
strokeRect边框矩形渲染问题
按理来说,strokeRect 渲染时的默认边框应该是 1px。
但是 canvas
在渲染矩形边框时,边框宽度是平均分在偏移位置两侧的。
举例说明:
// 边框会渲染在 49.5-50.5 之间,浏览器是不会让一个像素只显示一半的,只会全部显示。相当于边框会渲染在 49-51 之间,也就是 2px
ctx.strokeRect(50, 50, 100, 100)// 将偏移量多移动 0.5,边框会渲染在 200-201 和 50-51 之间,也就是1px
ctx.strokeRect(200.5, 50.5, 100, 100)
canvas清除区域
ctx.clearRect(x, y, width, height):可以清除 canvas
画布上指定的区域,让清除部分完全透明。
ctx.fillRect(50, 50, 100, 100)// 以 (100, 100) 为偏移量,清除一个 50 * 50 的区域
ctx.clearRect(100, 100, 50, 50)
canvas样式和颜色
在绘制图像之前,我们可以先设置图像的样式或者颜色。
ctx.fillStyle:设置图像的填充颜色。(默认黑色)
ctx.strokeStyle:设置图像轮廓的颜色。(默认黑色)
ctx.lineWidth:设置当前绘线的粗细,属性值必须为正数(默认值 1,0、负数、Infinity 和 NaN 会被忽略)。
ctx.lineJoin:设定线条与线条间结合的样式。(默认miter)
- round:圆角。
- bevel:斜角。
- miter:直角。
// 填充样式红色
ctx.fillStyle = 'red'// 边框样式蓝色
ctx.strokeStyle = 'blue'// 绘线粗细 10px
ctx.lineWidth = 10// 绘线交接呈现圆角
ctx.lineJoin = 'round'
ctx.fillRect(50, 50, 100, 100)
ctx.strokeRect(200, 50, 100, 100)
canvas路径
路径绘制矩形
ctx.stroke():通过线条来绘制图像轮廓,不会自动调用 closePath()。
ctx.fill():通过填充路径的内容区域生成实心的图像,自动调用closePath()。
ctx.rect(x, y, width, height):绘制一个偏移量 (x, y),宽 width,高 height 的矩形。
当该方法执行的时候,moveTo() 方法自动设置起点坐标为 (x, y)。
该方法执行完毕的时候,画布上不会呈现图像,相当于只是形成了路径列表,要调用 fill() 或 stroke() 方法才会呈现在画布中。
// 绘制路径不显示
ctx.rect(50, 50, 100, 100)
// 填充显示
ctx.fill()ctx.rect(200, 50, 100, 100)
// 路径连接显示
ctx.stroke()
重置路径和闭合路径
ctx.beginPath():新建一条路径,生成之后,图像绘制命令被指向到路径上准备生成路径。
本质上,路径是由多个子路径构成,这些子路径都是在一个路径列表中,每次调用 beginPath,路径列表都会清空重置。
通常我们在绘制图像之前,都会调用该方法。
ctx.moveTo(x, y):将画笔移动到指定的坐标轴上。(设置起点)
ctx.lineTo(x, y):绘制一条从当前位置到指定坐标轴位置的直线。
ctx.closePath():闭合路径,图像绘制命令又重新指向到上下文中。
使用 fill() 绘制图像或图像路径已经闭合不需要使用此方法。
通常使用 stroke() 绘制图像的时候才使用此方法。
// 自动填充路径
ctx.beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(100, 50)
ctx.lineTo(100, 100)
ctx.fill()// 路径不会自动闭合
ctx.beginPath()
ctx.moveTo(150, 50)
ctx.lineTo(200, 50)
ctx.lineTo(200, 100)
ctx.stroke()// 手动闭合路径
ctx.beginPath()
ctx.moveTo(250, 50)
ctx.lineTo(300, 50)
ctx.lineTo(300, 100)
ctx.closePath()
ctx.stroke()
路径绘线样式
ctx.lineCap:绘制每一条线段末端的样式属性。
- butt:线段末端以方形结束。(默认值)
- round:线段末端以圆形结束。
- square:线段末端以方形结束,但是增加了一个宽度和线段相同,高度是线段宽度一半的矩形区域。
// 绘线末端圆形显示
ctx.lineCap = 'round'
ctx.lineWidth = 10
ctx.moveTo(50, 50)
ctx.lineTo(100, 50)
ctx.lineTo(100, 100)
ctx.stroke()
canvas状态
ctx.save():将当前状态放入栈中,保持 canvas
全部状态的方法。
保存到栈中的绘制状态由下面部分组成。
- 当前的变换矩阵。
- 当前的剪切区域。
- 当前的虚线列表。
- 绘制图像的样式(strokeStyle / fillStyle / lineWidth / lineJoin / lineCap …)。
ctx.restore():通过在绘图状态栈中弹出顶端的状态,将 canvas
恢复到最近的保存状态的方法,如果没有保存状态,此方法不做任何改变。
通常我们在绘制图像进行的操作,都会放在 save() 和 restore() 方法之间,避免当前绘制图像设置的状态,影响到后续图像的绘制效果。
// 画布默认状态放入栈中
ctx.save()
// 当前填充样式设置为红色
ctx.fillStyle = 'red'
// 绘制矩形,显示效果为红色
ctx.fillRect(50, 50, 100, 100)
// 弹出栈中顶端状态,这个时候红色的填充样式会被弹出的状态覆盖,变为黑色
ctx.restore()
// 绘制矩形,显示效果为黑色
ctx.fillRect(200, 50, 100, 100)
canvas圆形 / 圆弧
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise):画一个以(x, y)坐标为圆心,radius 为半径的圆弧或圆,从 startAngle 开始,到 endAngle 结束。
参数如下:
- x:圆心在画布 x 轴上的偏移量。
- y:圆心在画布 y 轴上的偏移量。
- radius:绘制圆的半径。
- startAngle:圆弧的起始点,x 轴方向开始计算,单位以弧度表示。
- endAngle:圆弧的终点,单位以弧度表示。
- anticlockwise:布尔值。true 表示逆时针,false 表示顺时针。(默认值)
// 绘制一个在(100, 100)坐标为圆心,半径50的圆
ctx.arc(100, 100, 50, 0, 2 * Math.PI, false)
// 绘制边框圆形
ctx.stroke()
ctx.arcTo(x1, y1, x2, y2, radius):根据设置的两个控制点和半径画一段圆弧。
注意:
必须存在一个开始坐标点 ctx.moveTo(x, y),三点才能构成圆弧,半径为 radius 的圆向夹角里面填充。
绘制的圆弧一定经过起点,但不一定经过 (x1, y1) 和 (x2, y2),这两个坐标只是用来控制方向的。
// 将3个点连接起来
ctx.beginPath()
ctx.moveTo(20, 20)
ctx.lineTo(200, 0)
ctx.lineTo(100, 100)
ctx.stroke()// 观察圆弧在3点之间的位置
ctx.beginPath()
ctx.moveTo(20, 20)
ctx.arcTo(200, 0, 100, 100, 20)
ctx.stroke()
canvas贝塞尔曲线
二次贝塞尔
ctx.quadraticCurveTo(cpx, cpy, x, y):绘制二次贝塞尔曲线。
参数:
- cpx:控制点的 x 轴坐标。
- cpy:控制点的 y 轴坐标。
- x:终点的 x 轴坐标。
- y:终点的 y 轴坐标。
注意:
必须要设置起点 moveTo(x, y)。
二次贝塞尔曲线一定经过起点和终点。
// 将3个点连接起来
ctx.beginPath()
ctx.moveTo(20, 20)
ctx.lineTo(200, 0)
ctx.lineTo(100, 100)
ctx.stroke()// 观察二次贝塞尔曲线在3点之间的位置
ctx.beginPath()
ctx.moveTo(20, 20)
ctx.quadraticCurveTo(200, 0, 100, 100, 20)
ctx.stroke()
三次贝塞尔
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y):绘制三次贝塞尔曲线。
参数只是在二次贝塞尔的基础上多增加了一个控制点 (cp2x, cp2y)。
同样要设置起点 moveTo(x, y),也一定经过起点和终点。
// 将4个点连接起来
ctx.beginPath()
ctx.moveTo(20, 20)
ctx.lineTo(200, 0)
ctx.lineTo(100, 100)
ctx.lineTo(200, 100)
ctx.stroke()// 观察三次贝塞尔曲线在4点之间的位置
ctx.beginPath()
ctx.moveTo(20, 20)
ctx.bezierCurveTo(200, 0, 100, 100, 200, 100, 20)
ctx.stroke()
canvas变换
平移变换
ctx.translate(x, y):对当前 canvas
画布进行平移变换。
参数:
- x:水平方向的移动距离。
- y:垂直方向的移动距离。
注意:在 canvas
中 translate 是累加的。
// x 轴和 y 轴都平移 50px
ctx.translate(50, 50)
ctx.fillRect(0, 0, 100, 100)
旋转变换
ctx.rotate(angle):将当前 canvas
画布对照原点顺时针旋转。
参数:
- angle:顺时针旋转的弧度(degree * Math.PI / 180)
旋转的中心始终是 canvas
的原点 (0, 0),x 轴顺时针旋转,如果要改变中心点,我们可以通过 translate() 方法移动 canvas
。
注意:在 canvas
中 rotate 是累加的。
ctx.translate(50, 50)
// 顺时针旋转 45 度
ctx.rotate(45 * Math.PI / 180)
ctx.fillRect(0, 0, 100, 100)
伸缩变换
ctx.scale(x, y):将当前 canvas
画布的 x 轴和 y 轴进行伸缩变换。
参数:
- x:水平方向的缩放因子。
- y:垂直方向的缩放因子。
注意:
在
canvas
中 scale 是累加的。缩放因子为 1 时大小不变,值为负数按照 x 轴或 y 轴进行翻转(翻转上下文)。
默认的,在
canvas
中一个单位实际上就是一个像素。例如,如果我们将 0.5 作为缩放因子,最终的单位会变成 0.5 像素,并且形状的尺寸会变成原来的一半。相似的方式,我们将 2.0 作为缩放因子,将会增大单位尺寸变成两个像素。形状的尺寸将会变成原来的两倍。
图像伸缩:
ctx.translate(50, 50)
// x 轴放大 2 倍,y 轴不变
ctx.scale(2, 1)
ctx.fillRect(0, 0, 100, 100)
文字翻转:
// x 轴放大 2 倍,并翻转,y 轴不变
ctx.scale(-2, 1)
ctx.font = '48px serif'
ctx.fillText('canvas', -200, 100)
canvas背景
图片背景
ctx.drawImage(img, sx, sy, swidth, sheight, x, y, width, height):在画布上绘制图片。
参数:
- img:图像源对象(规定使用的图像、画布或视频等)。
- sx:可选,开始剪切的x坐标位置。
- sy:可选,开始剪切的y坐标位置。
- swidth:可选,被剪切图像的宽度。
- sheight:可选,被剪切图像的高度。
- x:在画布上放置图像的x坐标位置。
- y:在画布上放置图像的y坐标位置。
- width:可选,要使用的图像的宽度(伸展或缩小图像)。
- height:可选,要使用的图像的高度(伸展或缩小图像)。
注意:必须要等图片加载完才能操作,参数必须如下 3 种方式(3, 5, 9)。
演示模板:
<img id="img" src="./img/react.png">
<canvas id="canvas" width="400" height="200" style="border: 1px solid"></canvas>
- ctx.drawImage(img, x, y):在画布上定位图像。
const img = new Image()
img.src = './img/react.png'
img.onload = () => {// 将图片绘制到画布上ctx.drawImage(img, 0, 0)
}
- ctx.drawImage(img, x, y, width, height):在画布上定位图像,并规定图像的宽度和高度。
ctx.drawImage(img, 0, 0, 100, 100)
- ctx.drawImage(img, sx, sy, swidth, sheight, x, y, width, height):剪切图像,并在画布上定位被剪切的部分。
ctx.drawImage(img, 0, 0, 100, 100, 0, 0, 100, 100)
设置背景
ctx.createPattern(image, repetition):创建一个用于图像绘制使用的样式。
参数:
- image:图像源对象(规定使用的图像、画布或视频等)。
- repetition:重复图像的方式,值只能是 repeat | repeat-x | repeat-y | no-repeat。
repetition 如果为空字符串 (’’) 或 null (但不是 undefined ),repetition将被当作 repeat。
// 创建背景样式
const pat = ctx.createPattern(img, 'repeat')
ctx.fillStyle = pat
ctx.fillRect(0, 0, 300, 100)
canvas渐变
线性渐变
ctx.createLinearGradient(x1, y1, x2, y2):从 (x1, y1) 到 (x2, y2) 进行渐变。
该方法返回一个 CanvasGradient 对象。
使用 CanvasGradient 身上的 addColorStop(position, color) 设置渐变颜色。
参数:
- position:介于 0-1 之间的值,表示渐变中开始与结束之间的位置。
- color:在position位置显示的css颜色值。
// 从 (0, 0) 坐标点到 (300, 0) 坐标点进行渐变
const line = ctx.createLinearGradient(0, 0, 300, 0)
// 渐变顺序 红 --> 蓝 --> 绿
line.addColorStop(0, 'red')
line.addColorStop(.5, 'blue')
line.addColorStop(1, 'green')
// 图像填充颜色设置为渐变色
ctx.fillStyle = line
ctx.fillRect(0, 0, 300, 100)
径向渐变
ctx.createRadialGradient(x1, y1, r1, x2, y2, r2):从 (x1, y1) 为圆心,半径为 r1 的圆,向 (x2, y2) 为圆心,半径为 r2 的圆进行径向渐变。
使用方法跟上述的 createLinearGradient 一样。
// 以 (200, 100) 为圆心 50 为半径,向 100 为半径的圆渐变
const grad = ctx.createRadialGradient(200, 100, 50, 200, 100, 100)
// 渐变顺序 红 --> 蓝 --> 绿
grad.addColorStop(0, 'red')
grad.addColorStop(.5, 'blue')
grad.addColorStop(1, 'green')
// 图像填充颜色设置为渐变色
ctx.fillStyle = grad
ctx.fillRect(0, 0, 400, 200)
canvas文本相关
注意:文本相对于画布的偏移量,都是参照文本的基线进行偏移的。
渲染文本
canvas
中提供了两种方法渲染文本,如下:
ctx.fillText(text, x, y, [maxWidth]):在 (x, y) 填充指定的文本 (text)。
ctx.strokeText(text, x, y, [maxWidth]):在 (x, y) 绘制文本边框 (text)。
参数:
- text:文本内容。
- x, y:偏移量。
- maxWidth:字体绘制的最大宽度,绘制字体宽度超出最大宽度会水平自适应。
// 边框文本
ctx.strokeText('天天好心情', 50, 50)
// 填充文本
ctx.fillText('天天好心情', 50, 100)
// 填充文本现在宽度
ctx.fillText('天天好心情', 50, 150, 30)
文本样式
设置字体
ctx.font:font 属性指定时,必须要有大小和字体,缺一不可。
默认字体 10px sans-serif。
// 字体大小设置为 30px
ctx.font = '30px sans-serif'
ctx.strokeText('天天好心情', 50, 50)
文本对齐方式
ctx.textAlign:设置文本的对齐方式,值如下:
start:文本对齐界线开始的地方。(默认值)
end:文本对齐界线结束的地方。
left:文本左对齐。
right:文本右对齐。
center:文本居中对齐。
这里的 textAlign = ‘center’ 比较特殊。textAlign 的值为 center 时候文本的居中是基于你在 (fillText / strokeText) 的时候所给的x的值,也就是说文本一半在 x 的左边,一半在 x 的右边(可以理解为计算 x 的位置时从默认文字的左端,改为文字的中心,因此你只需要考虑 x 的位置即可)。所以,如果你想让文本在整个
canvas
居中,就需要将 (fillText / strokeText) 的x值设置成canvas
的宽度的一半。
ctx.font = '30px sans-serif'
// 文本居中对齐
ctx.textAlign = 'center'
ctx.strokeText('天天好心情', 50, 50)
文本基线对齐方式
文本相对于画布的偏移量,都是参照文本的基线进行偏移的,设置基线在文本中的位置,可以调整文本在 canvas
画布中的位置。
ctx.textBaseline:描绘绘制文本时,当前文本基线的属性,值如下:
alphabetic:文本基线是标准的字母基线。(默认值)
top:文本基线在文本块的顶部。
middle: 文本基线在文本块的中间。
bottom:文本基线在文本块的底部。
hanging:文本基线是悬挂基线。
ideographic:文本基线是表意字基线。
如果字符本身超出了 alphabetic 基线,那么 ideographic 基线位置在字符本身的底部。
ctx.font = '30px sans-serif'
ctx.textAlign = 'center'
// 基线设置在文本的顶部
ctx.textBaseline = 'top'
ctx.strokeText('天天好心情', 50, 50)
文本在canvas中垂直居中显示
通过之前的两种对齐方式,我们可以使文本在 canvas
画布中垂直居中显示。
ctx.font = '30px sans-serif'
// x 轴偏移画布一半,水平按文本中间对齐
ctx.textAlign = 'center'
// y 轴偏移画布一半,基线设置在文本的中心位置
ctx.textBaseline = 'middle'
ctx.fillText('天天好心情', canvas.width / 2, canvas.height / 2)
获取文本信息
ctx.measureText(text):返回一个 TextMetrics 对象,包含关于文本尺寸的信息(一般都用来获取文本的宽度)。
参数:
- text:文本内容。
ctx.font = '100px serif'
// 返回当前文本的相关信息
const textInfo = ctx.measureText('天天好心情')
console.log(textInfo)
字体大下设置为 100px,一共 5 个字,所以宽度是 500px。
canvas阴影
设置 canvas
图像或文字阴影需要如下属性:
- ctx.shadowOffsetX:图像 x 轴延伸距离。(默认值 0)
- ctx.shadowOffsetY:图像 y 轴延伸距离。(默认值 0)
- ctx.shadowBlur:用来设定阴影的模糊程度,其数值并不跟像素数量挂钩,也不受变换矩阵的影响。(默认值 0)
- ctx.shadowColor:必须是标准的CSS颜色值,用于设定阴影颜色效果。(默认是全透明的黑色)
// 图像阴影向左移动 10px
ctx.shadowOffsetX = 10
// 图像阴影向下移动 10px
ctx.shadowOffsetY = 10
// 模糊程度为 10
ctx.shadowBlur = 10
// 阴影颜色为红色
ctx.shadowColor = 'red'
ctx.fillRect(50, 50, 100, 100)
canvas像素相关
获取区域内像素信息
ctx.getImageData(x, y, width, height):返回一个 ImageData 对象,用来描述 canvas
区域隐含的像素数据,这个区域通过矩形表示,起始点是 (x, y),宽为 width,高为 height。
imageData 对象中存储着 canvas
对象真实的像素数据,它包含以下几个只读属性:
width:图片的宽度,单位是像素。
height:图片的高度,单位是像素。
data:Uint8ClampedArray 类型的一维数组,包含着 RGBA 格式的整型数组,范围在 0-255 之间 (包括255)。按图像从左到右,从下到下记录像素的。
const img = new Image()
img.src = './img/react.png'
img.onload = () => {ctx.drawImage(img, 0, 0, 100, 100)// 获取绘制图像区域内的相关信息const imgData = ctx.getImageData(0, 0, 100, 100)console.log(imgData)
}
图像的长和宽都是 100,所以一共有 10000 个像素点 (100 * 100),因为一个像素点对应一个 RGBA 值,一个 RGBA 值由 4 个数值构成,所以 data 数组的长度为 40000。
该数组每 4 个值构成一个像素点。
对画布进行像素数据的写入
ctx.putImageData(imagedata, dx, dy)
ctx.putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
将数据从已有的 ImageData 对象绘制到位图的方法。 如果提供了一个绘制过的矩形,则只绘制该矩形的像素。此方法不受画布转换矩阵的影响。
参数:
- imagedata:包含像素值的数组对象。
- dx:源图像数据在目标画布中的位置偏移量(x 轴方向的偏移量)。
- dy:源图像数据在目标画布中的位置偏移量(y 轴方向的偏移量)。
- dirtyX:可选,在源图像数据中,矩形区域左上角的位置。默认是整个图像数据的左上角(x 坐标)。
- dirtyY:可选,在源图像数据中,矩形区域左上角的位置。默认是整个图像数据的左上角(y 坐标)。
- dirtyWidth:可选,在源图像数据中,矩形区域的宽度。默认是图像数据的宽度。
- dirtyHeight:可选,在源图像数据中,矩形区域的高度。默认是图像数据的高度。
const img = new Image()
img.src = './img/react.png'
img.onload = () => {ctx.drawImage(img, 0, 0, 100, 100)const imgData = ctx.getImageData(0, 0, 100, 100)// 将 ImageData 对象重新写入到画布中ctx.putImageData(imgData, 200, 100, 0, 0, 50, 50)
}
创建imageData对象
ctx.createImageData(width, height)
ctx.createImageData(imagedata)
创建一个新的、空的、指定大小的imageData 对象,所有像素在新对象中都是透明的。(data 数组中的值都是 0)
参数:
- width:imageData 新对象的宽度。
- height:imageData 新对象的高度。
- imagedata:从现有的 ImageData 对象中,复制一个和其宽度和高度相同的对象。图像自身不允许被复制。(data 数组中的值都是 0)
const img = new Image()
img.src = './img/react.png'
img.onload = () => {ctx.drawImage(img, 0, 0, 100, 100)const imgData = ctx.getImageData(0, 0, 100, 100)// 根据老的 ImageData 对象,创建一个新的 ImageData 对象const imgDataByData = ctx.createImageData(imgData)console.log('imgDta创建', imgDataByData)// 根据指定的宽高,创建一个新的 ImageData 对象const imgDataByRect = ctx.createImageData(10, 10)console.log('宽高创建', imgDataByRect)
}
像素扩展
获取某个坐标的像素
通过 getImageData 方法我们可以获取到整个 canvas
画布的像素信息。已知坐标 (x, y),通过 (y * canvas.width + x) 可以获取这是第几个像素。因为一个像素包含 RGBA 4 个数值,所以最后获取到的像素为 (y * canvas.width + x) * 4,加上紧跟的后 3 位所对应的数值构成的 RGBA 值。
// 获取 imageData 中某个坐标的像素
function getPixelInfo(imageData, x, y) {const { width, data } = imageDataconst pixel = []const index = (y * width + x) * 4pixel[0] = data[index]pixel[1] = data[index + 1]pixel[2] = data[index + 2]pixel[3] = data[index + 3]return pixel
}
测试一下:
ctx.fillStyle = 'red'
ctx.fillRect(10, 10, 1, 1)
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height)
// 获取 (10, 10) 这个坐标点的像素信息
const pixel = getPixelInfo(imgData, 10, 10)
console.log(pixel)
设置某个坐标的像素
基本原理跟获取坐标像素一样,我们可以传入一个 RGBA 值来设置某些坐标的像素。
// 设置 imageData 中某个坐标的像素
function setPixelInfo(imageData, x, y, rgba) {const { width, data } = imageDataconst index = (y * width + x) * 4data[index] = rgba[0]data[index + 1] = rgba[1]data[index + 2] = rgba[2]data[index + 3] = rgba[3]
}
测试一下:
// 创建一个10 * 10 imageData 对象
const imgData = ctx.createImageData(10, 10)
// RGBA 红色
const rgba = [255, 0, 0, 255]
for (let y = 0; y < 10; y++) {for (let x = 0; x < 10; x++) {// 将每个像素点都设置为红色setPixelInfo(imgData, x, y, rgba)}
}
// 将 imageData 对象写入到 (100, 100) 开始的位置
ctx.putImageData(imgData, 100, 100)
马赛克小练习
现在我们既能获取到坐标位置的像素,也能设置坐标位置的像素。基于这两点我们来实现一个马赛克的效果。
基本思路:
假如现在有一个 100 * 100 的图像,将一个像素点看做一个 1 * 1 的小方块,里面就只有一个 RGBA 值,一共有 100 * 100 = 10000 个小方块,10000 个像素点。
将这个小方块变大,包含 2 * 2 个像素点,一共有 50 * 50 = 2500 个小方块。
现在一个小方块里面包含 4 个像素点,可能存在多个 RGBA 值,随机取出其中的一个 RGBA 值,将这个小方块中的 4 个像素点的 RGNA 值,都设置成随机取出的这个 RGBA 值。
现在每个小方块虽然包含 4 个像素点,但是这 4 个像素点的 RGBA 值都一样,也可以看作现在这个图像只有 2500 个像素点,像素点变少了,图像就模糊了,马赛克效果就形成了。
/*设置马赛克像素imgData:imageData对象size:马赛克程度,值越大越模糊(小方块宽高)
*/
function setMosaicPixel(imgData, size) {const { width, height } = imgData// 小方块变大,所以宽高要除以 sizefor (let y = 0; y < height / size; y++) {for (let x = 0; x < width / size; x++) {// 随机获取小方块中的 (x, y) 坐标const randomX = x * size + Math.floor(Math.random() * size)const randomY = y * size + Math.floor(Math.random() * size)// 获取这个坐标像素点的 RGBA 值const randomPixel = getPixelInfo(imgData, randomX, randomY)// 将这个小方块中的像素点都应用这个 RGBA 值for (let MosaicX = 0; MosaicX < size; MosaicX++) {for (let MosaicY = 0; MosaicY < size; MosaicY++) {setPixelInfo(imgData, x * size + MosaicX, y * size + MosaicY, randomPixel)}}}}
}
测试一下:
const img = new Image()
img.src = './img/react.png'
img.onload = () => {// 绘制图像ctx.drawImage(img, 0, 0, 200, 100)// 获取图像的信息const imgData = ctx.getImageData(0, 0, 200, 100)// 将图像马赛克化setMosaicPixel(imgData, 2)// 最后重新写入到画布中ctx.putImageData(imgData, 0, 0)
}
canvas透明度
ctx.globalAlpha:这个属性影响到 canvas
里所有图像的透明度,有效的值范围是 0(完全透明)到 1(完全不透明),默认是 1。
// 画布全局透明度设置为 0.2
ctx.globalAlpha = .2
ctx.fillStyle = 'red'
ctx.fillRect(50, 50, 50, 50)
ctx.fillStyle = 'blue'
ctx.fillRect(150, 50, 50, 50)
canvas图像合成设置
ctx.globalCompositeOperation:设置或返回如何将一个源(新的 source)图像绘制到目标(已有的 destination)的图像上。
可选值如下:
属性值 | 描述 |
---|---|
source-over | 源在上面,新的图像层级比较高。(默认值) |
source-in | 只留下源与目标的重叠部分。(源的那一部分) |
source-out | 只留下源超过目标的部分。 |
source-atop | 砍掉源溢出的部分。 |
destination-over | 目标在上面,旧的图像层级比较高。 |
destination-in | 只留下源与目标的重叠部分。(目标的那一部分) |
destination-out | 只留下目标超过源的部分。 |
destination-atop | 砍掉目标溢出的部分。 |
lighter | 显示源图像 + 目标图像。(重叠图形的颜色是通过颜色值相加来确定的) |
copy | 显示源图像,忽略目标图像。 |
xor | 那些重叠和正常绘制之外的其他地方是透明的。 |
// 设置图像重叠的地方不显示
ctx.globalCompositeOperation = 'xor'
ctx.fillStyle = 'red'
ctx.fillRect(50, 50, 50, 50)
ctx.fillStyle = 'blue'
ctx.fillRect(75, 75, 50, 50)
刮刮卡小练习
通过 globalCompositeOperation 属性设置,可以实现一个简单的刮刮卡效果。
基本思路:
将
canvas
画布填充一个颜色,和最后要显示的图片宽高设为一致,通过定位将画布覆盖在图片上方给
canvas
元素绑定鼠标按下事件,获取当前鼠标按下位置的 offsetX 和 offsetY,相当于在画布中的偏移量,设置起点 moveTo(offsetX, offsetY)。给
canvas
元素绑定鼠标移动事件,鼠标按下移动时,实时获取鼠标相对于画布的偏移量,设置 lineTo(offsetX, offsetY),并将这些路径连接起来。设置 ctx.globalCompositeOperation = ‘destination-out’,只留下不是鼠标移动绘制形成的区域,重叠的区域将会透明显示,最下方的图片就能看见。
给
canvas
元素绑定鼠标松开事件,清除canvas
身上的鼠标移动事件。
<!-- 结构设置 -->
<div style="width: 400px;height: 200px;position: relative"><canvas id="canvas" width="400" height="200" style="border: 1px solid;position: absolute"></canvas><img id="img" src="./img/react.png" style="width: 100%;height: 100%">
</div>
// 获取 canvas 元素的宽高
const { width, height } = canvas
// 将整个画布填充为灰色
ctx.fillStyle = 'gray'
ctx.fillRect(0, 0, width, height)
// 只留下目标超过源的部分
ctx.globalCompositeOperation = 'destination-out'
// 刮卡的粗细设置为 20px
ctx.lineWidth = 20
// 将绘线的路径连接处和两端都设置为圆形,这样比较好看
ctx.lineJoin = 'round'
ctx.lineCap = 'round'
// 为 canvas 元素绑定鼠标按下事件
canvas.onmousedown = function(event) {// 获取当前鼠标在画布中的偏移量const { offsetX, offsetY } = event// 设置起点ctx.moveTo(offsetX, offsetY)// 为 canvas 元素绑定鼠标移动事件canvas.onmousemove = function(event) {// 实时获取鼠标的偏移量const { offsetX, offsetY } = event// 设置路径点ctx.lineTo(offsetX, offsetY)// 将子路径连接起来绘制显示ctx.stroke()}// 为 canvas 元素绑定鼠标离开事件canvas.onmouseup = () => {// 清除 canvas 元素身上的鼠标移动事件canvas.onmousemove = null}
}
canvas将画布导出为图像
canvas.toDataURL(type, encoderOptions)。
通过 canvas
身上的 toDataURL 方法,返回一个包含画布内容的 base64 格式的 data url。
参数:
- type:图片格式,默认为 image/png。
- encoderOptions:在指定图片格式为 image/jpeg 或 image/webp 的情况下,可以从 0-1 之间选择图片的质量。如果超出取值范围,将会使用默认值0.92。其他参数会被忽略。
ctx.fillStyle = 'red'
ctx.fillRect(50, 50, 100, 100)
const dataUrl = canvas.toDataURL()
// data:image/png;base64,iVBORw0KGgoAAAANSUh....
console.log(dataUrl)
地址栏输入 data url:
canvas事件操作
canvas
中几乎没有提供任何事件操作的方法,但是我们可以通过 isPointInPath 方法,判断当前坐标是否在路径列表中,从而进行一些事件操作。
ctx.isPointInPath(x, y):判断在当前路径中是否包含检测点 (x, y),返回 true / false。
特别注意:
ctx.beginPath() 之前的路径检测不到,因为它会清空路径列表。
fillRect 和 strokeRect 这两个直接绘制矩形的方法不会生成路径,它们包含的坐标点 isPointInPath 检查不到。
// 绘制一个路径矩形
ctx.rect(50, 50, 100, 100)
ctx.fill()// 给 canvas 绑定点击事件
canvas.onclick = event => {// 鼠标点击位置距离 canvas 的 offsetX 和 offsetY,就相当于在 canvas 中的坐标位置const { offsetX, offsetY } = eventconst isInPath = ctx.isPointInPath(offsetX, offsetY)console.log(`当前点击坐标:(${offsetX}, ${offsetY})`, `是否在路径列表中:${isInPath}`)
}
最【通俗易懂】的 canvas 入门教程(多图预警)相关推荐
- 平凡的世界电子书pdf下载_零基础彩铅画入门教程步骤图及全套PDF电子书教程下载!...
零基础彩铅画入门教程步骤图及全套PDF电子书教程下载! 很多小伙伴喜欢看彩铅画也想尝试自己画一下,不过大家担心没有绘画基础能画好彩铅画吗.在这里小编要告诉大家没有绘画基础也可以学好画画的,今天小编给大 ...
- 视频教程-通俗易懂的SVN入门教程(含配套资料)-Java
通俗易懂的SVN入门教程(含配套资料) 张长志技术全才.擅长领域:区块链.大数据.Java等.10余年软件研发及企业培训经验,曾为多家大型企业提供企业内训如中石化,中国联通,中国移动等知名企业.拥有丰 ...
- Canvas入门教程
Canvas入门教程 1. Canvas初识 1.1 Canvas基础 1.2 Canvas填充与路径绘制 1.3 Canvas绘制圆 1.4 Canvas绘制折线线段 1.5 贝塞尔曲线实现 1.6 ...
- canvas学习笔记(下篇) -- canvas入门教程--保存状态/变形/旋转/缩放/矩阵变换/综合案例(星空/时钟/小球)...
[下篇] -- 建议学习时间4小时 课程共(上中下)三篇 此笔记是我初次接触canvas的时候的学习笔记,这次特意整理为博客供大家入门学习,几乎涵盖了canvas所有的基础知识,并且有众多练习案例, ...
- Render Hell —— 史上最通俗易懂的GPU入门教程(四)
声明:文本非原创,只是翻译,原文链接如下: https://simonschreibt.de/gat/renderhell-book4/ Render Hell – Book IV 这下越来越有趣了! ...
- 超通俗易懂的Servlet入门教程
不怕千万人阻挡,就怕自己投降. 文章目录 01.Servlet快速入门 02.Servlet3.0注解配置 03.GenericServlet&HttpServlet(Serlvet的体系结构 ...
- Render Hell —— 史上最通俗易懂的GPU入门教程(三)
声明:文本非原创,只是翻译,原文链接如下: https://simonschreibt.de/gat/renderhell-book3/ Render Hell – Book III 本文是 &quo ...
- Render Hell —— 史上最通俗易懂的GPU入门教程(二)
原文 Render Hell – Book II | Simon schreibt. Pipeline 详解 关于本篇文章,我收到的大多数积极反馈是:非常漂亮的演示说明,但是你的 Pipeline 已 ...
- 通俗易懂的傅里叶变换讲解(多图预警)
这学期学习图像处理时遇到了傅里叶变换,他的名字早就耳熟能详,但从来没有搞懂其中真正的含义,因为没有系统的学过复变函数.借此机会学习,在网上找到了一篇通俗易懂的博客,其中没有许多公式解读,读后对傅里叶变 ...
最新文章
- 数据分析索引总结(下)Pandas索引技巧
- uva 10801 Lift Hopping
- Tex 表格注释实例
- 春风吹用计算机的谱子,方大同《春风吹》简谱
- 如何将Emacs添加到右键菜单并显示为“烤肉”
- Confluence 6 使用 Decorator 宏
- 在线工具:找到神器,助你轻松应对各种职场难题
- 关于 签名验证失败 的问题
- 电容屏物体识别_电容屏物体识别技术简介
- linux ps命令是什么,linux中的ps命令的详细解释
- vue网页打印后事件失效
- 五年程序员一般多少工资?网友:能活下来我都觉得是庆幸的!
- 四苯基卟啉(TPP);硝基卟啉(TPP-NOz);锌卟啉(TPP-Zn)的合成路线/实物图/结构式-齐岳供应
- JS封装小写字母转大写
- 成品、预售、老店翻新、教练:邀约话术(绝干干货)-健身房管理系统结捷径系统
- Python爬虫实战 | (21) Scrapy+Selenium爬取新浪滚动新闻
- Oralce性能优化-绑定变量窥视
- KettleError connecting to database: (using class org.gjt.mm.mysql.Driver)Communications link failure
- 以太坊手续费详细分析
- Unity VR开发教程 OpenXR+XR Interaction Toolkit 2.1.1 (四) 传送