前言
大家好,我是沐浴在曙光下的贰货道士。最近看了德育处主任的canvas从入门到放弃,心有所感,整理了第65点—canvas注意点补充 和 灵魂拷问——canvas模糊问题处理 。最近不是很忙,决定利用canvas
整一个刮刮乐玩玩。后期如果有空(懂得都懂,看破不说破就好
),可能会整一个坦克大战看看。有喜欢本文的朋友,欢迎一键三连哦~
效果展示
核心思路及关键知识点分析
无刮卡效果的实现
如果仅仅需要点击抽奖按钮后,在灰色画布上生成结果。我们可以给包裹文本dom
和canvas dom
的div
设置相对定位,给初始值为空的文本dom
设置绝对定位,让它脱离文档流, 浮动在div
上方。在点击抽奖按钮时,利用洗牌算法,生成一个随机结果,展示在画布上即可。但是因为现在需要有刮卡的效果,就要使用到canvas
技术。
分析刮刮乐中存在的dom
事件
在canvas
上从按下鼠标到松开鼠标的这一过程中,鼠标有在canvas
上移动才会触发刮刮乐效果。在每次点击按钮之前,我们都需要声明一个初始值为false
的moveOnMouseDown
变量。
在canvas
上,一个完整的刮刮乐游戏应具备的dom
事件包括:
canvas
的onmousedown
事件属性:将moveOnMouseDown
变量置为true
,鼠标开始在canvas
上移动canvas
的onmousemove
事件属性:当moveOnMouseDown
变量的值为true
时,触发canvas
上的画笔移动算法(会在后文讲述),从而来模拟刮刮乐的效果canvas
的onmouseup
事件属性:将moveOnMouseDown
变量置为false
,鼠标结束了在canvas
上的移动
除此之外,为防止某些误操作。我们还可以在mounted
阶段,为document
添加selectstart
事件,阻止用户选择文本。而在beforeDestroy
阶段,移除selectstart
事件和canvas
上的所有监听事件。
globalCompositeOperation
的应用
在canvas
上下文中,有一个至关重要的api
,名为globalCompositeOperation
。它确定了新绘制的图形或图像,与画布上现有内容的组合方式,影响了新绘制操作的像。而我们今天的主角是:
假定新绘制的图形为new
, 已绘制的图形为old
。通过这两张图,我们不难发现:
source-over
:new
会完全覆盖old
,两者不会产生任何混合效果。因此,可以用于画布的重置destination-out
:new
会擦除old
上new
部分对应的画布信息。即canvas
只保留new
和old
不重叠的上下文信息,new
和old
重叠的上下文信息会被擦除
在明白globalCompositeOperation
的这些特性后,我们很快就能想到页面的布局:
- 使用文本
dom
获取抽奖结果,使用canvas
构造刮奖样式 - 使用一个具有相对定位特性的
div
元素,包裹具有绝对定位特性的文本dom
和canvas
。需要注意书写顺序,canvas
元素需要在文本dom
下方。 只有这样,这两个dom
元素才会脱离文档流,并重叠在一起,且canvas
元素会悬浮在文本dom
上方。那么,这个div
整体就构成了old
,canvas
上画笔移动的区域就构成了new
- 确定好页面结构后,为了能在每次点击按钮后,重新开启刮刮乐之旅。
globalCompositeOperation
的值需要重置为source-over
,同时初始化画布的样式 - 而鼠标在
canvas
上移动的过程,也是new
生成的过程。我们需要擦除canvas
上new
区域对应的蒙层,下层文本dom
中的刮奖内容才会显示出来,此时globalCompositeOperation
的值就变为destination-out
画笔移动算法(canvas
擦除算法)
上文提及到,画笔移动的区域
为new
。只要我们将ctx.globalCompositeOperation
置为destination-out
,canvas
上对应new
的区域就会被擦除。而下层文本dom
对应区域会显露真容, 我们就能一览大奖的庐山真面目了。因此,与其说是canvas
的擦除算法,倒不如说是画笔的移动算法,即使用画笔在canvas
上多次绘制图形。
canvas
上可以绘制图形的种类有很多,包括直线
、折线
、矩形
、多边形
、圆形
、半圆
、弧线
等。那我们该如何确定图形的绘制种类呢?类似圆形
、半圆
、三角形
、菱形
和弧线
这几类,我们其实是可以直接舍弃的。 因为单独使用以上任意一种图形,在canvas
上绘制,是很难铺满整个canvas
的(即canvas
不会完全清空,总会剩余些零零碎碎的背景图层)。
考虑到市面上的刮刮乐效果,本文使用直线进行处理:
- 在
canvas
上画直线,需要有起点和终点。且线条必须具备一定的宽度,不然就是在刮痧 - 以 鼠标在
canvas
内部的clientX
与30的差,作为横向的起始坐标。虽然横向起点坐标的值小于0
时,在画布外绘制的直线,并不会显示在画布上。但如果为了代码的严谨性,在代码中也可以考虑这些极限情况。 纵向的起始坐标同理 - 以 鼠标在
canvas
内部的clientX
,作为横向的终点坐标。纵向的终点坐标同理 - 线条的长度和宽度,我们根据展示的效果微调就好
canvas.onmousemove = (e) => {
if (!moveOnMouseDown) return
const shiftX = e.offsetX - 30
const shiftY = e.offsetY - 30
const startX = shiftX < 0 ? 0 : shiftX
const startY = shiftY < 0 ? 0 : shiftY
`防止画笔污染`
ctx.beginPath()
ctx.moveTo(startX, startY)
ctx.lineTo(e.offsetX, e.offsetY)
ctx.stroke()
ctx.closePath()
}
洗牌算法
for (let i = prizeList.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
`加入分号,避免当前代码和上面的代码冲突,导致js无法识别,交换prizeList[i]和prizeList[j]的值`
;[prizeList[i], prizeList[j]] = [prizeList[j], prizeList[i]]
}
生成指定数组的N
种方法
`1. 采用new Array创建(最推荐)`
const arr = new Array(100).fill('今晚加班')
`2. 采用Array.from创建(推荐)`
`Array.from生成100个undefined值的数组`
const arr = Array.from({ length: 100 }, () => '今晚加班')
`3. 使用new Array和map结合的方式创建(相对推荐)`
`Array构造函数生成的数组,每一项都是空属性,无法被map循环`
`因此需要展开,成为具有100个undefined值的数组,再进行循环`
const arr = [...new Array(100)].map(item => '今晚加班')
`4. 使用Array.apply和map结合的方式创建(相对推荐)`
`Array.apply第二个参数接收一个类数组,返回一个包括100个undefined值的数组`
const arr = Array.apply(null, Array(100)).map(item => '今晚加班')
const arr = Array.apply(null, {length: 100}).map(item => '今晚加班')
`5. 采用for in循环(不推荐)`
const arr = []
for (let i = 0; i < 100; i++) {
arr.push('今晚加班')
}
结语
往期精彩推荐(强势引流):
大概就这样吧, 有兴趣的掘友们可以去试试~ 更多精彩文章正在努力创作中,敬请期待哦