Canvas入门教程

1. Canvas初识

1.1 Canvas基础
  1. 初始化canvas
<canvas id="canvas" width="600" height="400"><canvas>

<script>
	const canvas = document.getElementById('canvas')
	let ctx = canvas.getContext('2d')
	or
	let ctx = document.getElementById('canvas').getContext('2d')
	// ...
</script>
  1. 开始绘制到结束绘制 beginPath&closePath
ctx.beginPath();
// ....
ctx.closePath();
  1. 移动画笔 moveTo 可防止绘制一条连续的路径
ctx.moveTo(x, y);
1.2 Canvas填充与路径绘制
  1. 绘制图形路径 strokeRect
ctx.strokeRect(x, y, 矩形宽度, 矩形高度);
or
ctx.rect(x, y, 矩形宽度, 矩形高度)
ctx.stroke()
  1. 清除图像 clearRect
ctx.clearRect(x, y, 清除宽度(clientWidth), 清除高度(clientHeight));
  1. 填充图形 fillRect
ctx.fillRect(x, y, 矩形宽度, 矩形高度);
or
ctx.rect(x, y, 矩形宽度, 矩形高度)
ctx.fill()
1.3 Canvas绘制圆
  1. 绘制圆形 arc(圆心x, 圆心y, radius, 开始角度, 结束角度, 逆时针&顺时针)
// false = 顺时针,true = 逆时针,默认值是false
ctx.arc(300, 200, 50, 0, Math.PI / 2, true)
ctx.fill()
  1. 绘制圆弧 arcTo
// ctx.arcTo(x1, y1, x2, y2, radius)
ctx.arcTo(300, 200, 50, 0)
ctx.fill()
1.4 Canvas绘制折线线段
  1. 绘制直线 lineTo
ctx.lineTo(x, y);
1.5 贝塞尔曲线实现
  1. 二次贝塞曲线 quadraticCurveTo(控制点cpx, 控制点cpy, 终点x, 终点y)
    二次贝塞曲线图例

例:绘制气泡框

ctx.moveTo(200, 300)
ctx.quadraicCurveTo(150, 300, 150, 200)
ctx.quadraicCurveTo(150, 100, 300, 100)
ctx.quadraicCurveTo(450, 100, 450, 200)
ctx.quadraicCurveTo(450, 300, 250, 300)

ctx.quadraicCurveTo(250, 350, 150, 350)
ctx.quadraicCurveTo(200, 350, 200, 300)

ctx.stroke()
  1. 三次贝塞曲线 bezierCurveTo(控制点cpx1, 控制点cpy1, 控制点cpx2, 控制点cpy2, 终点x, 终点y)
    三次贝塞曲线图例

例:绘制爱心

ctx.moveTo(300, 200)
ctx.bezierCurveTo(350, 150, 400, 240, 300, 280)

ctx.moveTo(300, 200)
ctx.bezierCurveTo(250, 150, 200, 240, 300, 280)

ctx.stroke()
1.6 封装路径 Path2D
  1. 通过 Path2D 封装路径,更好的开发
let heartPath = new Path2D()
heartPath.moveTo(300, 200)
heartPath.bezierCurveTo(350, 150, 400, 240, 300, 280)

heartPath.moveTo(300, 200)
heartPath.bezierCurveTo(250, 150, 200, 240, 300, 280)

ctx.stroke(heartPath)
  • SVG写法 可填路径
    • 位置 M x y
    • 水平 h
    • 垂直 v
    • 回到起点 z
let polyline = new Path2D('M10 10 h 80 v 80 h-80 z')
ctx.stroke(polyline)
1.7 颜色样式控制
  1. 设置画笔颜色 strokeStyle
ctx.strokeStyle = '#f00'
  1. 设置填充颜色 fillStyle
ctx.fillStyle = 'rgba(255, 0, 0)'
  1. 设置全局透明度 globalAlpha
ctx.globalAlpha = 0.5
1.8 线型渐变&径向渐变&圆锥渐变
  1. 线型渐变 createLinearGradient
let linearGradient = ctx.createLinearGradient(0, 0, 600, 400)
linearGradient.addColorStop(0, 'red')
linearGradient.addColorStop(0.3, 'deeppink')
linearGradient.addColorStop(1, 'blue')
ctx.fillStyle = linearGradient
ctx.fillRect(100, 200, 300, 300)
  1. 径向渐变 createRadialGradient(startX, startY, r0, endX, endY, r1)
let radialGradient = ctx.createRadialGradient(300, 200, 0, 300, 200, 100)
radialGradient.addColorStop(0, 'red')
radialGradient.addColorStop(0.3, 'deeppink')
radialGradient.addColorStop(1, 'blue')
ctx.fillStyle = radialGradient
ctx.fillRect(100, 200, 300, 300)
  1. 圆锥渐变 createConicGradient(角度, x, y)
let conicGradient = ctx.createConicGradient(0, 300, 200)
conicGradient.addColorStop(0, 'red')
conicGradient.addColorStop(1, 'blue')
ctx.fillStyle = conicGradient
ctx.fillRect(0, 0, 300, 200)
  1. 请求动画帧 requestAnimationFrame
let index = 0
let render = () => {
	ctx.clearRect(0, 0, 600, 400)
	index += 0.01
	if (index > 1) index = 0

	let linearGradient = ctx.createLinearGradient(0, 0, 600, 400)
	linearGradient.addColorStop(0, 'red')
	linearGradient.addColorStop(index, 'deeppink')
	linearGradient.addColorStop(1, 'blue')
	ctx.fillStyle = linearGradient
	ctx.fillRect(100, 200, 300, 300)
	requestAnimationFrame(render)
}

requestAnimationFrame(render)
1.9 pattern印章填充
  1. 印章 createPattern(img, 重复方式)
let img = new Image()
img.src = ''

img.onload = () => {
	// img 可以是image,也可以是canvas对象
	let pattern = ctx.createPattern(img, 'no-repeat')
	ctx.fillStyle = pattern
	ctx.fillRect(0, 0, 600, 400)
}

2. Canvas初阶

2.1 线段及虚线样式
  1. 线段 lineTo
ctx.moveTo(200, 150)
ctx.lineTo(300, 200)
ctx.lineTo(400, 150)
  • 线段宽度 lineWidth
  • 线条端点样式 lineCap
    • 平齐 butt
    • 半圆 round
    • 正方形 square
  • 连接处样式 lineJoin
    • 尖角 mitter
    • 圆滑 round
    • 折断 bevel
  • 斜截面限制 miterLimit
  1. 虚线 setLineDash([虚线长度, 空白长度])
  • 虚线偏移 lineDashOffset
let index = 0
let render = () => {
	ctx.clearRect(0, 0, 600, 400)
	index++
	if (index > 400) index = 0

	ctx.moveTo(150, 150)
	ctx.lineTo(300, 200)
	ctx.lineTo(450, 150)

	ctx.setLineDash([20, 30])
	ctx.lineDashOffset = index
	ctx.stroke()
	requestAnimationFrame(render)
}

render()
2.2 阴影
  1. 设置阴影
ctx.shadowOffsetX = 10
ctx.shadowOffsetY = 10
ctx.shadowBlur = 5
ctx.shadowColor = 'rgba(255, 100, 100, 1)'
2.3 图像和视频
  1. 图像绘制 drawImage
  • drawImage(img, x, y) 简单显示
  • drawImage(img, x, y, 缩放宽度, 缩放高度) 缩放
  • drawImage(img, 裁切位置x, 裁切位置y, 裁切宽度, 裁切高度, x, y, 缩放宽度, 缩放高度) 裁切
let img = new Image()
img.src = ''
img.onload = () => {
	// 1. drawImage(img, x, y)
	ctx.drawImage(img, 0, 0)
	// 2. drawImage(img, x, y, 缩放宽度, 缩放高度)
	ctx.drawImage(img, 0, 0, 600, 400)
}
  1. 视频绘制
let video = document.createElement('video')
video.src = ''
video.play()
let render = () => {
	ctx.drawImage(video, 0, 0, 600, 400)
	requestAnimationFrame(render)
}
render()
2.4 文字绘制
  1. 字体 font
ctx.font = '100px Microsoft YaHei'
  1. 填充渲染文字 fillText(text, x, y, 文字最大宽度)
ctx.fillText('txt', 300, 200, 100)
  1. 轮廓 stokeText(text, x, y, 文字最大宽度)
ctx.strokeText('txt', 300, 200)
  1. 文本对齐 textAlign
  2. 文本基线对齐 textBaseLine textBaseline top bottom alphabetic
  3. 文本方向 direction 反向:rtl
  4. 预测文本宽度 measureText
let txt = ctx.measureText('txt')
2.5 位移&缩放&旋转&变换

注: 位移&缩放&旋转的是坐标系

  1. 位移 translate(x, y)
ctx.translate(100, 100)
ctx.fillRect(0, 0, 50, 50)
  1. 缩放 scale(h, v)
ctx.scale(5, 2)
ctx.fillRect(0, 0, 50, 50)
  1. 旋转 rotate(deg)
ctx.rotate(Math.PI / 6)
ctx.fillRect(0, 0, 50, 50)
  1. 变形 transform(a, b, c, d, e, f)

[ a c e b d f 0 0 1 ] \left[ \begin{matrix} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{matrix} \right] ab0cd0ef1

  • a 和 b为水平坐标轴
  • c 和 d为垂直坐标轴
  • e 和 f为位移距离
ctx.transform(1, 0, 0, 1, 100, 100)
ctx.fillRect(0, 0, 50, 50)
2.6 合成图像

图层合成 [globalCompositeOperation](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation)

  • source-over 默认设置,并在现有画布上下文之上绘制新图形。
  • source-in 在新图形和目标画布重叠的地方绘制。其他的都是透明的。
  • source-out 在不与现有画布内容重叠的地方绘制新图形。
  • source-atop 新图形只在与现有画布内容重叠的地方绘制。
  • destination-over 在现有的画布内容后面绘制新的图形。
  • destination-in 现有的画布内容保持在新图形和现有画布内容重叠的位置。其他的都是透明的。
  • destination-out 现有内容保持在新图形不重叠的地方。
  • destination-atop 现有的画布只保留与新图形重叠的部分,新的图形是在画布内容后面绘制的。
  • lighter 两个重叠图形的颜色是通过颜色值相加来确定的。
  • copy 只显示新图形。
  • xor 图像中,那些重叠和正常绘制之外的其他地方是透明的。
  • multiply 将顶层像素与底层相应像素相乘,结果是一幅更黑暗的图片。
  • screen 像素被倒转,相乘,再倒转,结果是一幅更明亮的图片。
  • overlay multiply 和 screen 的结合,原本暗的地方更暗,原本亮的地方更亮。
  • darken 保留两个图层中最暗的像素。
  • lighten 保留两个图层中最亮的像素。
  • color-dodge 将底层除以顶层的反置。
  • color-burn 将反置的底层除以顶层,然后将结果反过来。
  • hard-light 屏幕相乘(A combination of multiply and screen)类似于叠加,但上下图层互换了
  • soft-light 用顶层减去底层或者相反来得到一个正值。
  • difference 一个柔和版本的强光(hard-light)。纯黑或纯白不会导致纯黑或纯白。
  • exclusion 和 difference 相似,但对比度较低。
  • hue 保留了底层的亮度(luma)和色度(chroma),同时采用了顶层的色调(hue)。
  • saturation 保留底层的亮度(luma)和色调(hue),同时采用顶层的色度(chroma)。
  • color 保留了底层的亮度(luma),同时采用了顶层的色调 (hue) 和色度 (chroma)。
  • luminosity 保持底层的色调(hue)和色度(chroma),同时采用顶层的亮度(luma)。
2.7 裁剪

裁剪路径 clip

let heartPath = new Path2D()
heartPath.moveTo(300, 200)
heartPath.bezierCurveTo(350, 150, 400, 240, 300, 280)

heartPath.moveTo(300, 200)
heartPath.bezierCurveTo(250, 150, 200, 240, 300, 280)

ctx.clip(heartPath)
ctx.stroke(heartPath)

let img = new Image()
img.src = ''
img.onload = () => {
	ctx.drawImage(img, 0, 0, 600, 400)
}
2.8 状态的保存&恢复
  1. 状态保存 save
ctx.fillStyle = 'red'
ctx.fillRect(0, 0, 100, 100)
ctx.save()
  1. 状态恢复 restore
ctx.fillStyle = 'red'
ctx.fillRect(0, 0, 100, 100)
ctx.save()

ctx.fillStyle = 'blue'
ctx.fillRect(100, 100, 100, 100)
ctx.save()

ctx.fillStyle = 'yellow'
ctx.fillRect(200, 200, 100, 100)

ctx.restore()
ctx.fillRect(300, 300, 100, 100) // color: blue

ctx.restore()
ctx.fillRect(400, 400, 100, 100) // color: red
2.9 像素操作
  1. 获取像素 getImageData
  2. 渲染像素 putImageData(imagedata, dx, dy, dirtyX<可选>, dirtyY<可选>, dirtyWidth<可选>, dirtyHeight<可选>)
let img = new Image()
img.src = ''
img.onload = () => {
	ctx.drawImage(img, 0, 0, 600, 400)
}

/* 将图层改成灰色 */
// 获取像素数据
let imageData = ctx.getImageData(0, 0, 600, 400)
// 循环修改数据
for (let i = 0; i < imageData.data.length; i += 4) {
	// 计算当前像素的平均值
	let avg = (imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2]) / 3
	imageData.data[i] = avg
	imageData.data[i + 1] = avg
	imageData.data[i + 2] = avg
	imageData.data[i + 3] = 255
}
// 将修改的数据重新渲染到画布上
ctx.putImageData(imageData, 0, 0)
2.10 封装绘制
  1. 封装体
class Heart {
  constructor(x, y) {
    this.x = x
    this.y = y
  }

  draw () {

    this.heartPath = new Path2D()
    // 起点
    this.heartPath.moveTo(this.x, this.y)
    this.heartPath.bezierCurveTo(this.x + 50, this.y - 50, this.x + 100, this.y + 40, this.x, this.y + 80)
    this.heartPath.moveTo(this.x, this.y)
    this.heartPath.bezierCurveTo(this.x - 50, this.y - 50, this.x - 100, this.y + 40, this.x, this.y + 80)

    ctx.save()
    ctx.fillStyle = `rgba(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, 0.5)`
    ctx.fill(this.heartPath)
    // ctx.translate(this.x, this.y)
    ctx.scale(0.5, 0.5)
    // ctx.stroke(heartPath)
    ctx.restore()
  }
}

const render = () => {
  ctx.clearRect(0, 0, 600, 400)
  let heart = new Heart(300, 200)
  heart.draw()
  requestAnimationFrame(render)
}

render()

3. Canvas实例

3.1 时钟绘制
<canvas id="canvas" width="800" height="600"></canvas>
let ctx = document.getElementById('canvas').getContext('2d')

const render = () => {
	ctx.clearRect(0, 0, 800, 600)
	ctx.save()
	ctx.translate(400, 300)
	ctx.rotate(-Math.PI / 2)
	ctx.lineCap = 'round'

	ctx.save()
	for (let i = 0; i < 12; i++) {
		ctx.beginPath()
		ctx.moveTo(170, 0)
		ctx.lineTo(190, 0)
		ctx.lineWidth = 8
		ctx.strokeStyle = 'gray'
		ctx.stroke()
		ctx.closePath()
		ctx.rotate(2 * Math.PI / 12)
	}
	ctx.restore()

	ctx.save()
	for (let i = 0; i < 60; i++) {
		ctx.beginPath()
		ctx.moveTo(180, 0)
		ctx.lineTo(190, 0)
		ctx.lineWidth = 2
		ctx.strokeStyle = 'gray'
		ctx.stroke()
		ctx.closePath()
		ctx.rotate(2 * Math.PI / 60)
	}
	ctx.restore()

	// 获取当前时间
	let time = new Date()

	// 绘制秒针
	ctx.save()
	let second = time.getSeconds()
	ctx.rotate(2 * Math.PI / 60 * second)
	ctx.beginPath()
	ctx.moveTo(-30, 0)
	ctx.lineTo(190, 0)
	ctx.lineWidth = 2
	ctx.strokeStyle = 'red'
	ctx.stroke()
	ctx.closePath()
	ctx.restore()

	// 绘制分针
	ctx.save()
	let minute = time.getMinutes()
	ctx.rotate(2 * Math.PI / 60 * minute + 2 * Math.PI / 60 / 60 * second)
	ctx.beginPath()
	ctx.moveTo(-20, 0)
	ctx.lineTo(130, 0)
	ctx.lineWidth = 4
	ctx.strokeStyle = '#888'
	ctx.stroke()
	ctx.closePath()
	ctx.restore()

	// 绘制时针
	ctx.save()
	let hour = time.getHours() >= 12 ? time.getHours() - 12 : time.getHours()
	ctx.rotate(2 * Math.PI / 12 * hour + 2 * Math.PI / 12 / 60 * minute + 2 * Math.PI / 12 / 60 / 60 * second)
	ctx.beginPath()
	ctx.moveTo(-15, 0)
	ctx.lineTo(110, 0)
	ctx.lineWidth = 8
	ctx.strokeStyle = '#333'
	ctx.stroke()
	ctx.closePath()
	ctx.restore()

	ctx.restore()
	requestAnimationFrame(render)
}
render()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值