今天圣诞节,先预祝你们节日快乐.既然是圣诞节,那咱们就来学点有意思的,用几十行代码来实现一个高性能的抽奖小游戏.也基于此,来巩固咱们的javascript基础,以及前端一些基本算法的应用.javascript
因为目前已有不少方案能够实现九宫格抽奖动画,好比使用动态active实现边框动画,用随机算法和定时器设置在何处中止等等. 为了进一步提升性能,本文介绍的方法,将使用坐标法,将操做dom的成本下降,彻底由js实现滑块的路径的计算,滑块元素采用绝对定位,让其脱离文档流,避免其余元素的重绘等等,最后点击按钮咱们会使用防抖函数来避免频繁执行函数,形成没必要要的性能损失.css
为了让你们更加熟悉dom结构,这里我就不用js动态生成了.以下html结构: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;此时九宫格布局就实现了. 滑块采用绝对定位,至于具体如何去沿着环形轨道运动,请继续看下文介绍.vue
/** * 生成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维环形坐标集.java
由抽奖动画分析可知,咱们滑块运动的轨迹,其实就是环形坐标集合,因此咱们只要让滑块的顶点(默认左上角)沿着环形坐标集合一步步变化就行了.node
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基础知识好比闭包.webpack
加速度运动其实很简单,好比每转过一圈将setTimeout的延迟时间改变便可.代码以下:css3
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)
}
复制代码
随机中止这块主要是用了Math.random这个API, 咱们在最后一圈的时候, 根据随机返回的数值来决定什么时候中止,这里咱们在函数内部实现随机数值,完整代码以下:git
/** * 环形随机轨道运动函数 * @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)
}
复制代码
防抖函数实现:
// 防抖函数,避免频繁点击执行屡次函数
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上,欢迎交流学习~
若是想了解更多H5游戏, webpack,node,gulp,css3,javascript,nodeJS,canvas数据可视化等前端知识和实战,欢迎在公众号《趣谈前端》加入咱们一块儿学习讨论,共同探索前端的边界。