console
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>适合 Vue/小程序/Angular/React 的老虎机抽奖系统</title>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<style type="text/css">
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.slots-box {
padding: 20px 0;
overflow: hidden;
text-align: center;
}
.slot-box {
display: inline-block;
margin: 0 10px;
}
.slot-box-inner {
background: #eee;
width: 200px;
height: 200px;
overflow: hidden;
padding: 0 10px;
}
.slot-box-inner.move {
background: linear-gradient(to right, #0be881 0%, #4bcffa 50%, #0be881 100%);
background-size: 300%;
animation: sbg 1s infinite alternate;
}
.slot-items {
}
.slot-item {
margin: 10px 0;
height: 180px;
width: 180px;
text-align: center;
background: #f60;
color: white;
line-height: 180px;
}
.sele{
display: block;
margin: 20px auto;
width: 200px;
height: 48px;
border: 1px solid #eee;
color: #555;
outline: none;
background: #fff;
}
.btn {
display: block;
margin: 20px auto;
width: 200px;
height: 48px;
border-radius: 48px;
border: none;
color: #fff;
outline: none;
background: linear-gradient(to right, #f1c40f 0%, #e74c3c 100%);
}
@keyframes sbg {
0% {
background-position: 100%;
}
50% {
background-position: -100%;
}
100% {
background-position: 0;
}
}
</style>
</head>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.5.16/vue.min.js"></script>
<body>
<div id="app">
<div class="slots-box">
<div class="slot-box" v-for="(slot,i) in slots" >
<div class="slot-box-inner" :class="slotsOpts !== null ? 'move' : ''" >
<div class="slot-items" :style="'transform: translateY('+slot.trans+'px)'">
<div class="slot-item" v-for="opt in slot.items">{{ opt.name }}</div>
<div class="slot-item slot-item-copy" >{{ slot.items[0].name }}</div>
</div>
</div>
</div>
</div>
<button @click="slotsStart" class="btn">开始抽奖</button>
</div>
<script type="text/javascript">
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !requestAnimationFrame; ++x) {
requestAnimationFrame = vendors[x]+'RequestAnimationFrame';
cancelAnimationFrame =
vendors[x]+'CancelAnimationFrame' || vendors[x]+'CancelRequestAnimationFrame';
}
if (!requestAnimationFrame)
requestAnimationFrame = function(callback) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!cancelAnimationFrame)
cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
const nextRound = typeof window === 'object' && window.requestAnimationFrame ? window.requestAnimationFrame : requestAnimationFrame;
var solts = new Vue({
el: '#app',
data: function () {
return {
s: '',
slotsData: [
{name: '1鼠标',isActived: 0},
{name: '2键盘',isActived: 0},
{name: '3笔记本',isActived: 0}
],
slots: [
{
title: "组1",trans:0, items: []
},
{
title: "组2",trans:0, items: []
},
{
title: "组3",trans:0, items: []
}
],
slotsIndex: [-1,-1,-1],
slotsOpts: null,
slotsStartedAt: null,
animateDuration: 5
};
},
created() {
this.slotsResult();
},
methods: {
slotsResult() {
this.slots[0].items = this.slotsData.slice(0)
this.slots[1].items = this.slotsData.slice(0)
this.slots[2].items = this.slotsData.slice(0)
let sl = this.slots;
let si = [];
for (let i = 0; i < sl.length; i++) {
si.push(sl[i].items.findIndex(k => k.isActived === 1));
}
this.slotsIndex = si;
},
slotsStart: function () {
if (this.slotsOpts) {
return false;
}
let resultIndex = Math.floor(Math.random()*3);
for (let i = 0; i < this.slotsData.length; i++) {
this.slotsData[i].isActived = 0;
}
this.slotsData[resultIndex].isActived = 1;
this.slotsResult();
const itemHeight = 190;
this.slotsOpts = this.slots.map((data, i) => {
let choice = this.slotsIndex[i];
if (this.slotsIndex[0] === -1) {
choice = Math.floor(Math.random() * data.items.length);
}
const opts = {
finalPos: choice * itemHeight,
startOffset: 8000 + Math.random() * 500 + i * 1600,
height: data.items.length * itemHeight,
duration: this.animateDuration * 1000 + i * 1000,
isFinished: false
};
return opts;
});
nextRound(this.slotsAnimate);
},
slotsAnimate: function (timestamp) {
if (this.slotsStartedAt === null) {
this.slotsStartedAt = timestamp;
}
const timeDiff = timestamp - this.slotsStartedAt;
this.slotsOpts.forEach((opt,i) => {
if (opt.isFinished) {
return false;
}
const timeRemaining = Math.max(opt.duration - timeDiff, 0);
const power = 3;
const offset = (Math.pow(timeRemaining, power) / Math.pow(opt.duration, power)) * opt.startOffset;
const pos = -1 * Math.floor((offset + opt.finalPos) % opt.height);
if (timeDiff > opt.duration) {
opt.isFinished = true;
}
this.slots[i].trans = pos;
});
if (this.slotsOpts.every(o => o.isFinished)) {
this.slotsOpts = null;
this.slotsStartedAt = null;
alert("恭喜获得:"+this.slotsData[this.slotsIndex[0]].name)
} else {
nextRound(this.slotsAnimate);
}
}
}
});
</script>
</body>
</html>