独乐乐不如众乐乐,我刮出了优弧,掘友们刮出了什么?

前言

  大家好,我是沐浴在曙光下的贰货道士。最近看了德育处主任的canvas从入门到放弃,心有所感,整理了第65点—canvas注意点补充灵魂拷问——canvas模糊问题处理 。最近不是很忙,决定利用canvas整一个刮刮乐玩玩。后期如果有空(懂得都懂,看破不说破就好),可能会整一个坦克大战看看。有喜欢本文的朋友,欢迎一键三连哦~

效果展示

核心思路及关键知识点分析

无刮卡效果的实现

  如果仅仅需要点击抽奖按钮后,在灰色画布上生成结果。我们可以给包裹文本domcanvas domdiv设置相对定位,给初始值为空的文本dom设置绝对定位,让它脱离文档流, 浮动在div上方。在点击抽奖按钮时,利用洗牌算法,生成一个随机结果,展示在画布上即可。但是因为现在需要有刮卡的效果,就要使用到canvas技术。

分析刮刮乐中存在的dom事件

   在canvas上从按下鼠标到松开鼠标的这一过程中,鼠标有在canvas上移动才会触发刮刮乐效果。在每次点击按钮之前,我们都需要声明一个初始值为falsemoveOnMouseDown变量。

  在canvas上,一个完整的刮刮乐游戏应具备的dom事件包括:

  • canvasonmousedown事件属性:将moveOnMouseDown变量置为true,鼠标开始在canvas上移动
  • canvasonmousemove事件属性:当moveOnMouseDown变量的值为true时,触发canvas上的画笔移动算法(会在后文讲述),从而来模拟刮刮乐的效果
  • canvasonmouseup事件属性:将moveOnMouseDown变量置为false,鼠标结束了在canvas上的移动

  除此之外,为防止某些误操作。我们还可以在mounted阶段,为document添加selectstart事件,阻止用户选择文本。而在beforeDestroy阶段,移除selectstart事件和canvas上的所有监听事件。

globalCompositeOperation的应用

  在canvas上下文中,有一个至关重要的api,名为globalCompositeOperation。它确定了新绘制的图形或图像,与画布上现有内容的组合方式,影响了新绘制操作的像。而我们今天的主角是:

image.png

image.png

  假定新绘制的图形为new, 已绘制的图形为old。通过这两张图,我们不难发现:

  • source-overnew会完全覆盖old,两者不会产生任何混合效果。因此,可以用于画布的重置
  • destination-outnew会擦除oldnew部分对应的画布信息。即canvas只保留newold不重叠的上下文信息,newold重叠的上下文信息会被擦除

  在明白globalCompositeOperation的这些特性后,我们很快就能想到页面的布局:

  • 使用文本dom获取抽奖结果,使用canvas构造刮奖样式
  • 使用一个具有相对定位特性的div元素,包裹具有绝对定位特性的文本domcanvas需要注意书写顺序,canvas元素需要在文本dom下方。 只有这样,这两个dom元素才会脱离文档流,并重叠在一起,且canvas元素会悬浮在文本dom上方。那么,这个div整体就构成了old, canvas上画笔移动的区域就构成了new
  • 确定好页面结构后,为了能在每次点击按钮后,重新开启刮刮乐之旅。globalCompositeOperation的值需要重置为source-over,同时初始化画布的样式
  • 而鼠标在canvas上移动的过程,也是new生成的过程。我们需要擦除canvasnew区域对应的蒙层,下层文本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()
}

洗牌算法

  图片来源—Ethan_Zhou洗牌算法

image.png

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('今晚加班')
}

结语

往期精彩推荐(强势引流):

  面试不面试,你都必须得掌握的vue知识

  无论如何,你都必须得掌握的JS知识(续)

  无论如何,你都必须得掌握的JS知识

  我的css世界

  什么?都2022年了,你还在一遍又一遍重复写form表单?

  大概就这样吧, 有兴趣的掘友们可以去试试~ 更多精彩文章正在努力创作中,敬请期待哦

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY3eUTQo' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片