前端之学习 canvas 的基础语法
前言
今天,学习了 canvas 的一些语法知识和使用,在这里分享给大家一起学习交流 首先,我们应该了解 canvas 是什么,有什么用,文章最后,我们将使用 canvas 绘制一个刮刮卡的中奖特效!
什么是 canvas
定义:是 HTML5 提供的一种新标签, ie9 才开始支持的,Canvas 是一个矩形区域的画布,可以用 JS 控制每一个像素在上面绘画。canvas 标签使用 JavaScript 在网页上绘制图像,本身不具备绘图功能。canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。
vscode 代码提示
在了解 canvas 之后,大家一定迫不及待地想加入 canvas 的学习之路吧。
- 首先,我们使用 vscode 新建一个 html 文件,
并在
body
标签中使用 canvas 标签,给定一定的大小,这样,canvas 画布就建好了
<canvas class="canvas" width="600" height="400"></canvas>
- 在
script
标签中,开始使用我们神奇的画笔,工欲善其事必先利其器,开始之前,我们使用/** @type {HTMLCanvasElement} */
,vscode 会给出 canvas 相关 api 的智能提示,让我们更顺畅的完成我们的代码
// 获取画布
/** @type {HTMLCanvasElement} */
let canvas = document.querySelector('.canvas')
// 获取画布上下文对象
let ctx = canvas.getContext('2d')
绘制二次贝塞尔曲线
首先,我们使用贝塞尔曲线绘制一个聊天气泡
在 canvas 中,一般相邻两个值为一组对象,代表 x 坐标和 y 坐标,而浏览器左上角即为原点位置
比如:
ctx.moveTo(200, 300)
中 200 和 300 分别代表 x 轴和 y 轴坐标,即表示一个点
ctx.quadraticCurveTo(150, 300, 150, 200)
中两两为一组,即代表了两个点的坐标
二次贝塞尔曲线通过和起始点的三个点既可以画出一条圆滑的曲线,如下图:
// 画笔移动到指定位置
ctx.moveTo(200, 300)
// 开始画笔
ctx.beginPath()
// 绘制多条贝塞尔曲线
ctx.quadraticCurveTo(150, 300, 150, 200)
ctx.quadraticCurveTo(150, 100, 300, 100)
ctx.quadraticCurveTo(450, 100, 450, 200)
ctx.quadraticCurveTo(450, 300, 250, 300)
ctx.quadraticCurveTo(250, 350, 150, 350)
ctx.quadraticCurveTo(200, 350, 200, 300)
// 填充颜色
ctx.stroke()
// 结束画笔
ctx.closePath()
这样,一个聊天气泡框就画好了,后面我们还会学习如何修改颜色或填充颜色
绘制三次贝塞尔曲线
三次贝塞尔曲线和二次贝塞尔曲线类似,不同的是每次使用四个点完成一条曲线的绘画,使用的方法名也不同 接下来,我们使用三次贝塞尔曲线绘画一个爱心
一段三次贝塞尔曲线如下:
//开始画笔
ctx.beginPath()
//三次贝塞尔曲线画一个爱心
ctx.moveTo(300, 200)
ctx.bezierCurveTo(350, 150, 500, 200, 300, 350)
//开始画笔
ctx.beginPath()
//三次贝塞尔曲线画一个爱心
ctx.moveTo(300, 200)
ctx.bezierCurveTo(350, 150, 500, 200, 300, 350)
ctx.bezierCurveTo(100, 200, 250, 150, 300, 200)
ctx.stroke()
// 结束画笔
ctx.closePath()
绘制文字
-
font 设置文字大小和字体
//设置文字大小和字体 ctx.font = "30px Microsoft Yahei"
-
strokeStyle 设置文字颜色
// 设置文字颜色 ctx.strokeStyle = "#ffcc2a"
-
fillText 设置文字内容和位置
// 设置文字内容和位置 ctx.fillText("Hello World", 100, 100)
-
textAlign 设置文字对齐方式
//设置文字对齐方式 start left center right end ctx.textAlign = "start"
-
textBaseline 文本基线对齐
//文本基线对齐 start top middle bottom end ctx.textBaseline = "middle"
-
direction 文字方向
//文字方向 rtl ltr ctx.direction = "rtl"
-
strokeText 设置文字轮廓
//设置文字轮廓 ctx.strokeText("Hello World", 300, 100)
-
预测量文字宽度
/*
预测量文字宽度
actualBoundingBoxAscent 实际文字上边距
actualBoundingBoxDescent 实际文字下边距
actualBoundingBoxLeft 实际文字左边距
actualBoundingBoxRight 实际文字右边距
fontBoundingBoxAscent 字体上边距
fontBoundingBoxDescent 字体下边距
width 文字宽度
*/
let result = ctx.measureText("Hello World")
console.log(result)
绘制图片
绘制图片有三种方式:
- 第一种:默认绘制图片的长宽
let img = new Image() img.src = "./imgs/girl.webp" img.onload = () => { // 第一种绘制图片方式 (默认绘制图片的长宽) ctx.drawImage(img, 0, 0) }
- 第二种绘制图片方式: 第一组坐标参数:绘制起点位置,第二组坐标参数:绘制长宽
let img = new Image()
img.src = "./imgs/girl.webp"
img.onload = () => {
//第二种绘制图片方式 (第一组:绘制起点位置,第二组:绘制长宽)
ctx.drawImage(img, 0, 0, 200, 400)
}
- 第三种绘制图片方式 第一组坐标参数:裁剪起点位置,第二组坐标参数:裁剪长宽,第三组坐标参数:绘制起点位置,第四组坐标参数:绘制长宽
let img = new Image()
img.src = "./imgs/girl.webp"
img.onload = () => {
//第三种绘制图片方式(第一组:裁剪起点位置,第二组:裁剪长宽,第三组:绘制起点位置,第四组:绘制长宽)
ctx.drawImage(img, 50, 50, 300, 150, 0, 0, 200, 200)
}
绘制视频
在 body
标签中添加 video
标签和 button
按钮控制视频播放
<video class="video" src="./video/video.mp4" controls></video>
<button class="btn">播放/暂停</button>
//获取视频对象
let video = document.querySelector('.video')
//获取按钮
let btn = document.querySelector('.btn')
btn.onclick = () => {
//播放或暂停视频
video.paused ? video.play() : video.pause()
render()
}
function render() {
//画出视频图像
ctx.drawImage(video, 0, 0, 600, 400)
//requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。循环调用 render,就可以实现播放视频的效果
requestAnimationFrame(render)
}
位移变换
首先,给 canvas 添加 border 样式,并且绘制一个矩形
<style>
.canvas {
border: 1px solid #000;
}
</style>
<script lang="js">
/** @type {HTMLCanvasElement} */
let canvas = document.querySelector('.canvas')
//获取画笔上下文对象
let ctx = canvas.getContext('2d')
ctx.fillRect(0, 0, 100, 50)
</script>
canvas 的位移变化都是坐标系的改变,即浏览器左上角原点形成的坐标系的变化
//坐标系位移
ctx.translate(100, 100)
// 缩放
ctx.scale(2, 2)
// 旋转
ctx.rotate(Math.PI / 4)
通过矩阵的方式位移变换
//平移 1,0,0,1分别代表x轴和y轴的缩放比例,200,200分别代表x轴和y轴的位移
ctx.transform(1, 0, 0, 1, 200, 200)
//旋转 transform(Math.cos(Math.PI / 4), Math.sin(Math.PI / 4), -Math.sin(Math.PI / 4), Math.cos(Math.PI / 4), 0, 0)
ctx.transform(1, 1, -1, 1, 0, 0)
//缩放 2,2分别代表x轴和y轴的缩放比例
ctx.transform(2, 0, 0, 2, 0, 0)
线段和虚线样式设置
绘制线段
ctx.moveTo(200, 200)
// 绘制直线
ctx.lineTo(350, 250)
// 绘制直线
ctx.lineTo(440, 200)
绘制虚线
绘制虚线先绘制线段,再设置让线段分割成为虚线
ctx.moveTo(200, 200)
// 绘制直线
ctx.lineTo(350, 250)
// 绘制直线
ctx.lineTo(440, 200)
//虚线样式
ctx.setLineDash([20, 20])
//虚线偏移量
ctx.lineDashOffset = index
使用 render 方法让虚线移动
let index = 0
//线段移动
function render() {
//清除上次绘画的矩形
ctx.clearRect(0, 0, 600, 400)
index++
if (index > 400) {
index = 0
}
ctx.moveTo(200, 200)
// 绘制直线
ctx.lineTo(350, 250)
// 绘制直线
ctx.lineTo(440, 200)
// 设置线条样式
ctx.lineWidth = 1
// 设置线条端点样式,round为圆形,square为方形,butt为直角
ctx.lineCap = 'butt'
//设置线段连接处样式,round为圆形,bevel为斜角,mitter为直角
ctx.linejoin = 'mitter'
// 设置线段连接处最大长度
ctx.miterLimit = 6
//虚线样式
ctx.setLineDash([20, 20])
//虚线偏移量
ctx.lineDashOffset = index
ctx.stroke()
//循环调用,实现动画效果
requestAnimationFrame(render)
}
render()
阴影
阴影属性和 css 的阴影类似
- shadowOffsetX: 阴影的水平偏移量
- shadowOffsetY: 阴影的垂直偏移量
- shadowBlur: 阴影的模糊程度
- shadowColor: 阴影的颜色
// path2D 对象 用于存储路径
let heartPath = new Path2D()
//二次贝塞曲线画一个爱心
heartPath.moveTo(300, 200)
heartPath.bezierCurveTo(350, 150, 500, 200, 300, 350)
heartPath.bezierCurveTo(100, 200, 250, 150, 300, 200)
// shadowOffsetX: 阴影的水平偏移量 shadowOffsetY: 阴影的垂直偏移量 shadowBlur: 阴影的模糊程度 shadowColor: 阴影的颜色
ctx.shadowOffsetX = 10
ctx.shadowOffsetY = 10
ctx.shadowBlur = 5
ctx.shadowColor = 'rgba(255, 200, 200, 1)'
ctx.stroke(heartPath)
裁剪图片
绘制图片后,使用 ctx.clip()则可对图片进行裁剪 如下:原本图片被爱心形状裁剪
// path2D对象 用于创建路径
let heart = new Path2D()
//二次贝塞曲线画一个爱心
heart.moveTo(300, 200)
heart.bezierCurveTo(350, 150, 500, 200, 300, 350)
heart.bezierCurveTo(100, 200, 250, 150, 300, 200)
//clip() 方法从原始画布中剪切任意形状和尺寸的区域
ctx.clip(heart)
let img = new Image()
img.src = "./imgs/girl.webp"
img.onload = () => {
//第三种绘制图片方式(第一组:裁剪起点位置,第二组:裁剪长宽,第三组:绘制起点位置,第四组:绘制长宽)
ctx.drawImage(img, 80, 70, 250, 250, 200, 180, 200, 300)
ctx.lineWidth = 5
ctx.strokeStyle = "red"
ctx.stroke(heart)
}
ctx.stroke(heart)
合成图像
使用合成图像,做一个刮刮卡抽奖效果
- 首先画布 canvas 样式和刮刮卡都定位到左上角,使用 z-index 使画布居于抽奖文字上层
- 绘制刮刮卡图片,遮盖文字
- 鼠标按下时,开启绘画,globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上,由于属性很多,具体参考canvas 中的合成------globalCompositeOperation 一文 ,这里使用**
destination-out
** 从画布中删除刮刮卡图像,画笔绘画时绘画弧形模仿橡皮差效果 - 鼠标抬起时,停止绘画
<style>
* {
margin: 0;
padding: 0;
}
.ggk {
width: 600px;
height: 400px;
line-height: 400px;
text-align: center;
font-weight: 900;
overflow: hidden;
font-size: 30px;
position: absolute;
left: 0;
top: 0;
z-index: -1;
}
.canvas {
border: 1px solid #000;
position: absolute;
left: 0;
top: 0;
z-index: 1;
}
</style>
<body>
<div class="ggk">谢谢惠顾</div>
<canvas class="canvas" width="600" height="400"></canvas>
</body>
//绘制刮刮卡图片
let img = new Image()
img.src = './imgs/ggk.webp'
img.onload = () => {
ctx.drawImage(img, 0, 0, 600, 400)
}
// isDraw 用来判断是否在画布上绘制
let isDraw = false
canvas.onmousedown = (e) => {
isDraw = true
}
canvas.onmouseup = (e) => {
isDraw = false
}
// onmousemove 在鼠标指针移动时,不断绘画
canvas.onmousemove = (e) => {
if (isDraw) {
// 获取鼠标指针的坐标
let x = e.pageX
let y = e.pageY
// globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上
// destination-out 从目标图像中删除源图像
ctx.globalCompositeOperation = 'destination-out'
// arc() 方法使用一个中心点和半径,为画布的当前子路径添加一条弧。
ctx.arc(x, y, 20, 0, Math.PI * 2)
ctx.fill()
}
}
let random = Math.random()
if (random < 0.5) {
let ggk = document.querySelector('.ggk')
ggk.innerHTML = '恭喜你中奖了'
}
刮刮卡刮奖前
刮刮卡刮奖后
原创不易,觉得文章还不错或者感兴趣可以点赞或者收藏一下哦!欢迎留言评论和关注一下!