我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!
根据步骤来,一步步教你实现一个简单的九宫格抽奖动画
创建项目,引入vue
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
</div>
</body>
<script type="text/javascript">
const app = {
data() {
return {}
},
mounted(){
},
methods:{}
}
Vue.createApp(app).mount('#app')
</script>
</html>
静态页面
先画底盘
主要通过两个div叠加
<head>
+ <style>
+ * {
+ box-sizing: border-box;
+ padding: 0;
+ margin: 0;
+ }
+ .grid-box{
+ width: 400px;
+ height: 400px;
+ border-radius: 16px;
+ background-color: #617df2;
+ margin: 100px auto;
+ padding: 15px;
+ }
+ .grid-box__inner{
+ width: 100%;
+ height: 100%;
+ border-radius: 12px;
+ background-color: #869cfa;
+ }
+ </style>
</head>
<body>
<div id="app">
+ <div class="grid-box">
+ <div class="grid-box__inner">
+ </div>
+ </div>
</div>
</body>
画奖项
通过 v-for
遍历奖项数组,利用flex
布局进行排版
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@next"></script>
<style>
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
.grid-box{
width: 400px;
height: 400px;
border-radius: 16px;
background-color: #617df2;
margin: 100px auto;
padding: 15px;
}
.grid-box__inner{
width: 100%;
height: 100%;
border-radius: 12px;
background-color: #869cfa;
+ display: flex;
+ flex-flow: row wrap;
+ align-content:flex-start
}
+ .grid-box__item{
+ width: 103px;
+ height: 103px;
+ margin-top: 15px;
+ margin-left: 15px;
+ background-color: #b8c5f2;
+ border-radius: 8px;
+ color: #fff;
+ display: flex;
+ justify-content:center;
+ align-items:center;
+ }
+ .grid-box__item.start-btn{
+ cursor:pointer;
+ }
</style>
</head>
<body>
<div id="app">
<div class="grid-box">
<div class="grid-box__inner">
+ <div class="grid-box__item prize" v-for="(prize,i) in list" :key="'prize'+i">
+ {{ prize.label }}
+ </div>
+ <div class="grid-box__item start-btn">
+ 开始
+ </div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
const app = {
data() {
return {
+ list: [
+ { label: '一等奖' },
+ { label: '二等奖' },
+ { label: '三等奖' },
+ { label: '安慰奖' },
+ { label: '谢谢参与' },
+ { label: '安慰奖' },
+ { label: '谢谢参与' },
+ { label: '三等奖' },
]
}
},
mounted(){
},
methods:{}
}
Vue.createApp(app).mount('#app')
</script>
</html>
注意:此时奖项的顺序是按每行从头开始排的,开始按钮也不再最中间。因此进行一下小修改
画奖项(修改版)
先让所有奖项居中
移除 flex
布局,利用 position:absolute
实现居中
.grid-box__inner{
width: 100%;
height: 100%;
border-radius: 12px;
background-color: #869cfa;
+ position: relative;
- display: flex;
- flex-flow: row wrap;
- align-content:flex-start
}
.grid-box__item{
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ margin-left: -51.5px;
+ margin-top: -51.5px;
width: 103px;
height: 103px;
- margin-top: 15px;
- margin-left: 15px;
background-color: #b8c5f2;
border-radius: 8px;
color: #fff;
display: flex;
justify-content:center;
align-items:center;
}
对每个奖项进行位移
通过transform:translate(x,y)
来进行位移 位移距离为 一个奖项的宽高+间距
<body>
<div id="app">
<div class="grid-box">
<div class="grid-box__inner">
- <div class="grid-box__item prize" v-for="(prize,i) in list" :key="'prize'+i">
+ <div class="grid-box__item prize" v-for="(prize,i) in list" :style="getGridItemStyle(i)" :key="'prize'+i">
{{ prize.label }}
</div>
<div class="grid-box__item start-btn">
开始
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
const app = {
methods:{
+ getGridItemStyle(index){
+ const gutter = 15 // 间距
+ const width_height = 103 // 宽度 | 高度
+ const margin = gutter + width_height // 偏移量
+ const map = {
+ 0:`translate(-${margin}px,-${margin}px)`,
+ 1:`translate( 0px,-${margin}px)`,
+ 2:`translate( ${margin}px,-${margin}px)`,
+ 3:`translate( ${margin}px,0px)`,
+ 4:`translate(${margin}px,${margin}px)`,
+ 5:`translate(0px,${margin}px)`,
+ 6:`translate(-${margin}px,${margin}px)`,
+ 7:`translate(-${margin}px,0px)`,
+ }
+ return {
+ transform:map[index] || ''
+ }
}
}
}
</script>
选中样式
通过切换class
来实现选中样式
<style>
+ .grid-box__item.active{
+ background-color: #7074f6;
+ }
</style>
<body>
<div id="app">
<div class="grid-box">
<div class="grid-box__inner">
- <div class="grid-box__item prize" v-for="(prize,i) in list" :style="getGridItemStyle(i)" :key="'prize'+i">
+ <div class="grid-box__item prize" :class="{active:curIndex === i}" v-for="(prize,i) in list" :style="getGridItemStyle(i)" :key="'prize'+i">
{{ prize.label }}
</div>
<div class="grid-box__item start-btn">
开始
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
const app = {
data() {
return {
+ curIndex:0,
}
},
}
</script>
旋转动画
思路:通过定时器setTimeout
改变curIndex
的值来实现动画
绑定点击事件
- <div class="grid-box__item start-btn">
+ <div class="grid-box__item start-btn" @click="handleClickStartBtn">
开始
</div>
<script type="text/javascript">
const app = {
data() {
return {
+ isTurning:false,
}
},
methods:{
+ handleClickStartBtn(){
+ if(this.isTurning) return
+ this.turn()
+ },
+ turn(){}
}
}
</script>
模拟中奖
<script type="text/javascript">
const app = {
methods:{
+ getWinningIndex(){
+ return parseInt(Math.random() * 8, 10)
+ },
}
}
</script>
抽奖动画
<script type="text/javascript">
const app = {
methods:{
turn(){
+ const winningIndex = this.getWinningIndex()
+ let index = 0
+ this.curIndex = 0
+ this.isTurning = true
+ let setTime = ()=>{
+ setTimeout(()=>{
+ // 至少转3圈
+ if(this.curIndex==winningIndex && parseInt(index/8)===3){
+ console.log('中奖了,奖品为:'+ this.list[this.curIndex].label)
+ this.isTurning = false
+ }else{
+ index++
+ this.curIndex = index%8
+ setTime()
+ }
+ },200)
+ }
+ setTime()
},
}
}
</script>
抽奖动画(变速)
累计动画跳动的次数
,通过判断当前跳动次数
和 到达指定奖项需要跳动的总次数
,当差值
小于指定次数
时开始减速。通过动态设置 setTimeout 的 time 来实现减速
<script type="text/javascript">
const app = {
methods:{
turn(){
const winningIndex = this.getWinningIndex()
this.curIndex = 0 // 重置curIndex
this.isTurning = true
const lastSpeed = 600 // 最终速度( 速度 ==> setTimeout 的 延迟时间)
const startSpeed = 80 // 初始速度
const count = 4 // 转的圈数
const speedDownNum = 15 // 倒数第 speedDownNum 次开始减速
const speed = (lastSpeed - startSpeed) / speedDownNum // 平均每次减速的量
let index = 0 // 变换的次数
let time = startSpeed // setTimeout 的 延迟时间
// 通过动态修改 setTimeout 的延迟时间 来达到变换速度的效果
let setTime = ()=> {
setTimeout(() => {
// 判断是否开始减速
if (index >= count * 8 + winningIndex - speedDownNum) {
time += speed
}
index++
this.curIndex = index % 8
// 当 index 小于 总数时 设置下一次 timeout
if (index < count * 8 + winningIndex) {
setTime()
} else {
this.isTurning = false
console.log('中奖了,奖品为:'+ this.list[this.curIndex].label)
}
}, time)
}
setTime()
},
}
}
</script>
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@next"></script>
<style>
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
.grid-box{
width: 400px;
height: 400px;
border-radius: 16px;
background-color: #617df2;
margin: 100px auto;
padding: 15px;
}
.grid-box__inner{
width: 100%;
height: 100%;
border-radius: 12px;
background-color: #869cfa;
position: relative;
}
.grid-box__item{
position: absolute;
left: 50%;
top: 50%;
margin-left: -51.5px;
margin-top: -51.5px;
width: 103px;
height: 103px;
background-color: #b8c5f2;
border-radius: 8px;
color: #fff;
display: flex;
justify-content:center;
align-items:center;
}
.grid-box__item.active{
background-color: #7074f6;
}
.grid-box__item.start-btn{
cursor:pointer;
}
</style>
</head>
<body>
<div id="app">
<div class="grid-box">
<div class="grid-box__inner">
<div class="grid-box__item prize" :class="{active:curIndex === i}" v-for="(prize,i) in list" :style="getGridItemStyle(i)" :key="'prize'+i">
{{ prize.label }}
</div>
<div class="grid-box__item start-btn" @click="handleClickStartBtn">
开始
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
const app = {
data() {
return {
list: [
{ label: '一等奖' },
{ label: '二等奖' },
{ label: '三等奖' },
{ label: '安慰奖' },
{ label: '谢谢参与' },
{ label: '安慰奖' },
{ label: '谢谢参与' },
{ label: '三等奖' },
],
curIndex:null,
isTurning:false
}
},
mounted(){
},
methods:{
getGridItemStyle(index){
const gutter = 15 // 间距
const width_height = 103 // 宽度 | 高度
const margin = gutter + width_height
const map = {
0:`translate(-${margin}px,-${margin}px)`,
1:`translate( 0px,-${margin}px)`,
2:`translate( ${margin}px,-${margin}px)`,
3:`translate( ${margin}px,0px)`,
4:`translate(${margin}px,${margin}px)`,
5:`translate(0px,${margin}px)`,
6:`translate(-${margin}px,${margin}px)`,
7:`translate(-${margin}px,0px)`,
}
return {
transform:map[index] || ''
}
},
handleClickStartBtn(){
if(this.isTurning) return
this.turn()
},
turn(){
const winningIndex = this.getWinningIndex()
this.curIndex = 0
this.isTurning = true
const lastSpeed = 600 // 最终速度( 速度 ==> setTimeout 的 延迟时间)
const startSpeed = 80 // 初始速度
const count = 4 // 转的圈数
const speedDownNum = 15 // 倒数第 speedDownNum 次开始减速
const speed = (lastSpeed - startSpeed) / speedDownNum // 平均每次减速的量
let index = 0 // 变换的次数
let time = startSpeed // setTimeout 的 延迟时间
// 通过动态修改 setTimeout 的延迟时间 来达到变换速度的效果
let setTime = ()=> {
setTimeout(() => {
// 判断是否开始减速
if (index >= count * 8 + winningIndex - speedDownNum) {
time += speed
}
index++
this.curIndex = index % 8
// 当 index 小于 总数时 设置下一次 timeout
if (index < count * 8 + winningIndex) {
setTime()
} else {
this.isTurning = false
console.log('中奖了,奖品为:'+ this.list[this.curIndex].label)
}
}, time)
}
setTime()
},
getWinningIndex(){
return parseInt(Math.random() * 8, 10)
},
}
}
Vue.createApp(app).mount('#app')
</script>
</html>