h5消除类游戏代码:手把手教你实现一个高性能的抽抽乐H5小游戏
h5消除类游戏代码:手把手教你实现一个高性能的抽抽乐H5小游戏function run(el path n = 1 i = 0 len = path.length) { setTimeout(() => { if(n > 0) { if(len <= i) { i = n === 1 ? len : 0 n-- } el.css('transform' `translate(${path[i][0]}px ${path[i][1]}px)`) run(el path n i len) } } 300) } 复制代码这样就能实现我们的滑块按照九宫格边框运动的动画了 当然以上函数只是基本的动画 还没有实现在
那我们就来学点有意思的 用几十行代码来实现一个高性能的抽奖小游戏.也基于此 来巩固我们的javascript基础 以及前端一些基本算法的应用.
效果展示 你将收获- 防抖函数的应用
- 用css实现九宫格布局
- 生成n维环形坐标的算法
- 如何实现环形随机轨道运动函数
- 实现加速度动画
- 性能分析与优化
由于目前已有很多方案可以实现九宫格抽奖动画 比如使用动态active实现边框动画 用随机算法和定时器设置在何处停止等等. 为了进一步提高性能 本文介绍的方法 将使用坐标法 将操作dom的成本降低 完全由js实现滑块的路径的计算 滑块元素采用绝对定位 让其脱离文档流 避免其他元素的重绘等等 最后点击按钮我们会使用防抖函数来避免频繁执行函数 造成不必要的性能损失.
1. 九宫格布局实现为了让大家更加熟悉dom结构 这里我就不用js动态生成了.如下html结构:
<div class="wrap">
<div class="title">圣诞抽抽乐</div>
<div class="box">
<div class="item">我爱你</div>
<div class="item">你爱我</div>
<div class="item">我不爱你</div>
<div class="item">你爱我</div>
<div class="item start">开始</div>
<div class="item">你爱我</div>
<div class="item">再见</div>
<div class="item">谢谢惠顾</div>
<div class="item">你爱我</div>
<div class="spin"></div>
</div>
</div>
复制代码
九宫格布局我们使用flex来实现 核心代码如下:
.box {
display: flex;
flex-wrap: wrap;
width: 300px;
height: 300px;
position: relative;
.item {
box-sizing: border-box;
width: 100px;
}
// 滑块
.spin {
box-sizing: border-box;
position: absolute;
left: 0;
top: 0;
display: inline-block;
width: 100px;
height: 100px;
background-color: rgba(0 0 0 .2);
}
}
复制代码
由上可知容器box采用flex布局 要想让flex子元素换行 我们这里要设置flex-wrap: wrap;此时九宫格布局就实现了. 滑块采用绝对定位 至于具体如何去沿着环形轨道运动 请继续看下文介绍.
2.生成n维环形坐标的算法由上图我们可以知道 一个九宫格的4条边 可以用以上8个坐标收尾连接起来 那么我们可以基于这个规律.来生成环形坐标集合.代码如下:
/**
* 生成n维环形坐标
* @param {number} n 维度
* @param {number} cell 单位坐标长度
*/
function generateCirclePath(n cell) {
let arr = []
for(let i=0; i< n; i ) {
arr.push([i*cell 0])
}
for(let i=0; i< n-1; i ) {
arr.push([(n-1)*cell (i 1)*cell])
}
for(let i=0; i< n-1; i ) {
arr.push([(n-i-2)*cell (n-1)*cell])
}
for(let i=0; i< n-2; i ) {
arr.push([0 (n-i-2)*cell])
}
return arr
}
复制代码
如果是单位坐标 那么cell为1 cell设计的目的就位为了和现实的元素相结合 我们可以手动设置单元格的宽度来实现不同大小的n维环形坐标集.
3.实现环形随机轨道运动函数由抽奖动画分析可知 我们滑块运动的轨迹 其实就是环形坐标集合 所以我们只要让滑块的顶点(默认左上角)沿着环形坐标集合一步步变化就好了.
function run(el path n = 1 i = 0 len = path.length) {
setTimeout(() => {
if(n > 0) {
if(len <= i) {
i = n === 1 ? len : 0
n--
}
el.css('transform' `translate(${path[i][0]}px ${path[i][1]}px)`)
run(el path n i len)
}
} 300)
}
复制代码
这样就能实现我们的滑块按照九宫格边框运动的动画了 当然以上函数只是基本的动画 还没有实现在随机位置停止 以及滑块的加速度运动 这块需要一定的技巧和js基础知识比如闭包.
3.1 加速度运动加速度运动其实很简单 比如每转过一圈将setTimeout的延迟时间改变即可.代码如下:
function run(el path n = 1 speed = 60 i = 0 len = path.length) {
setTimeout(() => {
if(n > 0) {
if(len <= i) {
i = n === 1 ? len : 0
n--
speed = (300 - speed) / n
}
el.css('transform' `translate(${path[i][0]}px ${path[i][1]}px)`)
run(el path n speed i len)
}
} speed)
}
复制代码
3.2 随机停止实现
随机停止这块主要是用了Math.random这个API 我们在最后一圈的时候 根据随机返回的数值来决定何时停止 这里我们在函数内部实现随机数值 完整代码如下:
/**
* 环形随机轨道运动函数
* @param {element} el 运动的dom元素
* @param {array} path 运动的环形坐标集合
* @param {number} speed 运动的初始速度
* @param {number} i 运动的初始位置
* @param {number} len 路径的长度
* @param {number} random 中奖坐标
*/
function run(el path n = 1 speed = 60 i = 0 len = path.length random = Math.floor(Math.random() * len)) {
setTimeout(() => {
if(n > 0) {
// 如果n为1 则设置中奖数值
if(n === 1) {
len = random
}
if(len <= i) {
i = n === 1 ? len : 0
n--
speed = (300 - speed) / n
}
el.css('transform' `translate(${path[i][0]}px ${path[i][1]}px)`)
run(el path n speed i len random)
}
} speed)
}
复制代码
4.实现点击开始的防抖函数以及应用
防抖函数实现:
// 防抖函数 避免频繁点击执行多次函数
function debounce(fn interval = 300) {
let timeout = null
return function () {
clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(this arguments)
} interval)
}
}
复制代码
那么我们点击时 代码应该长这样:
// 点击开始按钮 开始抽奖
$('.start').on('click' debounce(() => { run($('.spin') generateCirclePath(3 100) 3) }))
复制代码
延伸
在文章发布之后 有热心的小伙伴们提出了几个建议 综合如下:
- 抽奖动画结束后提供回调来通知页面以便处理其他逻辑
- 处理多次点击时 虽然加了防抖 但是用户在动画没结束时点击了开始按钮 又会执行动画导致动画越来越快 发生混乱.
综合以上问题 我在之前基础上做了进一步扩展 来解决以上提到的问题.
- 添加动画结束时回调:
/**
* 环形随机轨道运动函数
* @param {element} el 运动的dom元素
* @param {array} path 运动的环形坐标集合
* @param {func} cb 动画结束时回调
* @param {number} speed 运动的初始速度
* @param {number} i 运动的初始位置
* @param {number} len 路径的长度
* @param {number} random 中奖坐标
*/
function run(el path n = 1 cb speed = 60 i = 0 len = path.length random = Math.floor(Math.random() * len)) {
setTimeout(() => {
if(n > 0) {
// 如果n为1 则设置中奖数值
if(n === 1) {
len = random
}
if(len <= i) {
i = n === 1 ? len : 0
n--
speed = (300 - speed) / n
}
el.css('transform' `translate(${path[i][0]}px ${path[i][1]}px)`)
run(el path n cb speed i len random)
}else {
cb && cb()
}
} speed)
}
复制代码
- 处理多次点击时 虽然加了防抖 但是用户在动画没结束时点击了开始按钮 又会执行动画导致动画越来越快 发生混乱.
// 1. 点击开始按钮 开始抽奖
$('.start').on('click' debounce(() => {
// 点击开始后禁用点击
$('.start').css('pointer-events' 'none')
run($('.spin') generateCirclePath(3 100) 3 () => {
// 动画结束后开启按钮点击
$('.start').css('pointer-events' 'auto')
alert('抽奖结束')
})
}))
复制代码
谢谢各位认真的建议 继续优化吧.
总结该实现方式的好处是支持n维环形坐标的抽奖 基于坐标法的应用还有很多 尤其是游戏和图形领域 在实现过程中一定要考虑性能和可扩展性 这样我们就可以在不同场景使用同一套方法论 岂不乐哉?本文完整源码我会放在github上 欢迎交流学习~
github地址:https://github.com/MrXujiang?tab=repositories
欢迎在公众号《趣谈前端》加入我们一起学习讨论,共同探索前端的边界。