canvas 绘图技术与图片处理
source link: https://gaohaoyang.github.io/2021/07/22/canvas-draw/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
本文将讲述一些 canvas 相关的绘图技术,其中包括:
- 绘图 API
绘图 API
相关 api 可参考,这里不展开详讲。
下面举几个示例
代码如下:
<canvas id="mainCanvas" style="background-color: #fff;" width="800" height="400"></canvas>
const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')
if (canvas) {
const context = canvas.getContext('2d')
if (context) {
const { offsetLeft, offsetTop } = canvas
let x
let y
const mouseMoveHandler = (e: MouseEvent) => {
x = e.pageX
y = e.pageY
x -= offsetLeft
y -= offsetTop
context.lineTo(x, y)
context.lineCap = 'round'
context.lineJoin = 'round'
context.stroke()
}
canvas.addEventListener('mousedown', (e) => {
context.beginPath()
context.moveTo(e.pageX - offsetLeft, e.pageY - offsetTop)
canvas.addEventListener('mousemove', mouseMoveHandler)
})
canvas.addEventListener('mouseup', () => {
canvas.removeEventListener('mousemove', mouseMoveHandler)
})
}
}
demo 链接 https://gaohaoyang.github.io/canvas-practice/09-mouse-draw/
源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/09-mouse-draw/index.ts
二次贝塞尔曲线
具体 api 可参考文档 CanvasRenderingContext2D.quadraticCurveTo()
ctx.quadraticCurveTo(cpx, cpy, x, y);
它需要2个点。第一个点是控制点,第二个点是终点。起始点是当前路径最新的点,当创建二次贝赛尔曲线之前,可以使用 moveTo()
方法进行改变。
接下来实现一个 demo,用鼠标位置作为控制点,控制这个二次贝塞尔曲线。
const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')
if (canvas) {
const context = canvas.getContext('2d')
const { offsetLeft, offsetTop } = canvas
const x0 = 300
const y0 = 100
const x1 = 600
const y1 = 300
if (context) {
canvas.addEventListener('mousemove', (e) => {
context.clearRect(0, 0, canvas.width, canvas.height)
const x = e.pageX - offsetLeft
const y = e.pageY - offsetTop
context.beginPath()
context.moveTo(x0, y0)
context.quadraticCurveTo(x, y, x1, y1)
context.stroke()
})
}
}
demo 链接 https://gaohaoyang.github.io/canvas-practice/10-quadratic/
源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/10-quadratic/index.ts
穿过控制点的二次贝塞尔曲线
const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')
if (canvas) {
const context = canvas.getContext('2d')
const { offsetLeft, offsetTop } = canvas
const x0 = 300
const y0 = 100
const x1 = 600
const y1 = 300
if (context) {
canvas.addEventListener('mousemove', (e) => {
context.clearRect(0, 0, canvas.width, canvas.height)
const x = e.pageX - offsetLeft
const y = e.pageY - offsetTop
const cpx = x * 2 - (x0 + x1) / 2
const cpy = y * 2 - (y0 + y1) / 2
context.beginPath()
context.moveTo(x0, y0)
context.quadraticCurveTo(cpx, cpy, x1, y1)
context.stroke()
})
}
}
const cpx = x * 2 - (x0 + x1) / 2
const cpy = y * 2 - (y0 + y1) / 2
demo 链接 https://gaohaoyang.github.io/canvas-practice/11-quadratic-through/
源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/11-quadratic-through/index.ts
绘制平滑的曲线,用多个点控制
import Ball from '../common/Ball'
const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')
if (canvas) {
const ctx = canvas.getContext('2d')
if (ctx) {
const points = []
const num = 4
for (let i = 0; i < num; i += 1) {
const ball = new Ball(2)
const x = Math.random() * canvas.width
const y = Math.random() * canvas.height
points.push({
x,
y,
})
ball.x = x
ball.y = y
ball.draw(ctx)
}
ctx.beginPath()
ctx.moveTo(points[0].x, points[0].y)
for (let i = 1; i < num - 2; i += 1) {
const xAv = (points[i].x + points[i + 1].x) / 2
const yAv = (points[i].y + points[i + 1].y) / 2
ctx.quadraticCurveTo(points[i].x, points[i].y, xAv, yAv)
}
ctx.quadraticCurveTo(points[num - 2].x, points[num - 2].y, points[num - 1].x, points[num - 1].y)
ctx.stroke()
}
}
xAv, yAv 围边设置为循环中当前点和后续点的x,y坐标的平均值,这样就能绘制一条平滑的曲线了
demo 链接 https://gaohaoyang.github.io/canvas-practice/12-multi-quadratic/
源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/12-multi-quadratic/index.ts
闭合的多条曲线
import Ball from '../common/Ball'
const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')
if (canvas) {
const ctx = canvas.getContext('2d')
if (ctx) {
const points = []
const num = 4
for (let i = 0; i < num; i += 1) {
const ball = new Ball(2)
const x = Math.random() * canvas.width
const y = Math.random() * canvas.height
points.push({
x,
y,
})
ball.x = x
ball.y = y
ball.draw(ctx)
}
const xAv1 = (points[0].x + points[num - 1].x) / 2
const yAv1 = (points[0].y + points[num - 1].y) / 2
ctx.beginPath()
ctx.moveTo(xAv1, yAv1)
for (let i = 0; i < num - 1; i += 1) {
const xAv = (points[i].x + points[i + 1].x) / 2
const yAv = (points[i].y + points[i + 1].y) / 2
ctx.quadraticCurveTo(points[i].x, points[i].y, xAv, yAv)
}
ctx.quadraticCurveTo(points[num - 1].x, points[num - 1].y, xAv1, yAv1)
ctx.stroke()
}
}
demo 链接 https://gaohaoyang.github.io/canvas-practice/13-multi-quadratic-close/
源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/13-multi-quadratic-close/index.ts
图形与填充色
一般来说绘图顺序如下:
- beginPath 开始绘制
- moveTo 移动起点
- lineStyle 线样式
- fillStyle 填充样式
- lineTo 或 quadraticCurveTo 等绘制曲线
- closePath 闭合
- fill 填充
- stroke 描边
ctx.createLinearGradient(x0, y0, x1, y1)
createLinearGradient() 方法需要指定四个参数,分别表示渐变线段的开始和结束点。
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)
arc()
是 Canvas 2D API 绘制圆弧路径的方法。 圆弧路径的圆心在 (x, y) 位置,半径为 r ,根据anticlockwise (默认为顺时针)指定的方向从 startAngle 开始绘制,到 endAngle 结束。
const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')
if (canvas) {
const ctx = canvas.getContext('2d')
if (ctx) {
ctx.beginPath()
const gradient = ctx.createLinearGradient(100, 100, 200, 200)
gradient.addColorStop(0, '#ff0000')
gradient.addColorStop(1, '#000000')
ctx.fillStyle = gradient
ctx.fillRect(100, 100, 100, 100)
const gradient2 = ctx.createLinearGradient(200, 200, 300, 300)
gradient2.addColorStop(0, '#ff0000')
gradient2.addColorStop(0.6, '#008880')
gradient2.addColorStop(1, '#000000')
ctx.fillStyle = gradient2
ctx.fillRect(200, 200, 100, 100)
const gradient3 = ctx.createRadialGradient(500, 200, 0, 500, 200, 100)
gradient3.addColorStop(0, '#000000')
gradient3.addColorStop(1, '#ff0000')
ctx.arc(500, 200, 100, 0, 2 * Math.PI)
ctx.fillStyle = gradient3
ctx.fill()
}
}
效果如下:
demo 链接 https://gaohaoyang.github.io/canvas-practice/14-gradient/
源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/14-gradient/index.ts
有些场景,可能需要在 canvas 内绘制一张图片,接下来我们看看绘制图片的方式
const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')
if (canvas) {
const ctx = canvas.getContext('2d')
if (ctx) {
const img = new Image()
img.src = 'https://gw.alicdn.com/imgextra/i2/O1CN01gR6ymq1dfV5RmYxYk_!!6000000003763-2-tps-658-411.png'
img.addEventListener('load', () => {
ctx.drawImage(img, 0, 0, 658, 329, 0, 0, 800, 400)
})
}
}
demo 链接 https://gaohaoyang.github.io/canvas-practice/15-image/
源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/15-image/index.ts
接下来我们尝试将上述demo中的所有绿色像素移除,看看是什么效果
const canvas: HTMLCanvasElement | null = document.querySelector('#mainCanvas')
if (canvas) {
const ctx = canvas.getContext('2d')
if (ctx) {
const img = new Image()
img.src = '../assets/1.png'
img.addEventListener('load', () => {
ctx.drawImage(img, 0, 0, 658, 329, 0, 0, canvas.width, canvas.height)
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
const pixels = imageData.data
for (let i = 0; i < pixels.length; i += 4) {
pixels[i + 1] = 0
}
ctx.putImageData(imageData, 0, 0)
})
}
}
效果如下:
demo 链接 https://gaohaoyang.github.io/canvas-practice/16-image-pixel/
源码链接 https://github.com/Gaohaoyang/canvas-practice/blob/main/src/16-image-pixel/index.ts
本文虽然没有涉及过多动画,但还是举例介绍了 canvas 2d 绘图的方式,并且介绍了如何加载图片与操作像素。这为后续的动画学习垫定了基础。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK