关于九宫格抽奖的功能,想必大家都见过。外圈为奖品,中间是一个抽奖的按钮,接下来就讲解怎么实现九宫格的抽奖功能。
本demo使用技术:vue2/vue3,作用域插槽,定时器,递归自调用,注册全局组件
这里我就搭建个新vue3的项目,虽然是vue3,但也完全可以使用vue2的写法实现
创建项目删除初始化代码省略…
在这里我是写死的数据。首先我们在App组件里声明list数组,用来当做奖品。这里我们在list数组里写8个项,id从0到7,text可随意写(九宫格为什么写8个,是因为另一个是按钮,并不是奖品)
list: [
{
id: 0,
text: "腾讯100元",
},
{
id: 1,
text: "腾讯50元",
},
{
id: 2,
text: "腾讯10元",
},
{
id: 3,
text: "腾讯5元",
},
{
id: 4,
text: "腾讯一元",
},
{
id: 5,
text: "腾讯100积分",
},
{
id: 6,
text: "腾讯500元",
},
{
id: 7,
text: "腾讯1毛钱",
},
],
然后我们通过父子组件的传值,把list传给子组件。子组件使用props接收。代码省略
然后写个li标签,并使用v-for把每一项循环渲染出来。
九宫格的样式可随意写懒的可以直接复制,我这里cj_box类名写在了ul标签上
完整样式:
.cj_box {
width: 800px;
height: 800px;
margin: auto;
overflow: hidden;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
li {
width: 251px;
height: 251px;
background-color: #eee;
text-align: center;
line-height: 251px;
&:nth-child(5) {
cursor: pointer;
}
}
li.active {
background-color: rgb(233, 159, 159);
}
}
注意:我们这里给了九宫格的样式后,渲染li的顺序是从上往下,从左往右。然而正常的九宫格应该是顺时针围绕在外围一圈的,因为中间的按钮。 不一样的人,也可能会有不一样的思路
在这里我们对他进行更改下标即可,在计算属性里对list数组进行map处理,然后使用switch语句就能实现
let ary = this.list.map((item, index) => {
switch (index) {
case 3:
return {
...this.list[7],
index: 7,
};
case 4:
return {
...this.list[3],
index: 3,
};
case 5:
return {
...this.list[6],
index: 6,
};
case 6:
return {
...this.list[5],
index: 5,
};
case 7:
return {
...this.list[4],
index: 4,
};
default:
return {
...item,
index: index,
};
}
});
以上代码,阅读起来可能会有点麻烦。但仔细看也并不难,只是把正常下标,改成了九宫格形式的下标。
就比如第一个case的下标是3,那就是正常情况下的第二行第一个li;把他改成九宫格形式,那就是第八个li了,下标也自然是7了,以此类推。最后的default,也就是前三个li,他们不用改变
下面我们开始增加第五个li,点击按钮(计算属性完整代码如下)
computed: {
renderList() {
let ary = this.list.map((item, index) => {
switch (index) {
case 3:
return {
...this.list[7],
index: 7,
};
case 4:
return {
...this.list[3],
index: 3,
};
case 5:
return {
...this.list[6],
index: 6,
};
case 6:
return {
...this.list[5],
index: 5,
};
case 7:
return {
...this.list[4],
index: 4,
};
default:
return {
...item,
index: index,
};
}
});
return ary
.slice(0, 4)
.concat({ id: "btn", text: "开始抽奖" })
.concat(ary.slice(4));
},
},
最下面的 return ary 就是给数组增加一个标签,赋予id和text
接下来就给中间按钮添加点击事件了,不要迷惑
这里直接在循环的li标签里,写@click就行,然后把循环的每一项当做参数,传递过去。
这里循环遍历的不是list 而是renderList函数
<li
v-for="item in renderList"
:key="item.id"
@click="fn(item)"
>
{{ item.text }}
</li>
这样就把写了个点击事件,并传递了参数
接着就在methods里写代码了
注意看代码里的注释信息
fn(item) {
// 在return ary那里 我们添加的点击li,赋予的id是btn
// 下面代码当不等于btn的时候,那就意味着不是点击按钮,直接return
if (item.id !== "btn") return;
// 然后在date里,定义一个下标赋值为null,初始值设为null是控制抽奖转动,不点击按钮的时候,就不会有显示中奖的高亮
this.activeIndex = null;
// 封装一个抽奖的函数,并在fn函数里调用 ,参数为执行的时间参数
this.move(10);
},
注意看代码里的注释信息
// 参数time,表示传入的时间数,每10毫秒执行一次
move(time) {
this.timer = setTimeout(() => {
// 这里给空下标加1,每次执行累加 1
let n = this.activeIndex + 1;
// 把获取累加下标的值,对8进行取余,那么就会在下标为8里一直循环,永远不会出现下标为9的情况
this.activeIndex = n % 8;
// 这里是递归自调用一直循环,参数的设置代表了速度逐渐变慢,但并不会停下
this.move(time + time * 0.1);
// time时间值,
}, time);
}
表面上可以进行抽奖了,现在添加一下抽奖的样式
绑定下面的class类即可,根据下标变动,
<li
v-for="item in renderList"
:key="item.id"
:class="{ active: activeIndex === item.index }"
@click="fn(item)"
>
{{ item.text }}
</li>
现在是不是就有了表面效果了
下面给个判断,让他停下来,不然会一直抽奖下去
move(time) {
if (time > 800) {
} else {
this.timer = setTimeout(() => {
let n = this.activeIndex + 1;
this.activeIndex = n % 8;
this.move(time + time * 0.1);
}, time);
}
},
添加if判断,把代码放在else里,当时间参数不大于800时就继续执行,这样就能停下来了。但有个问题是,他永远都抽中了同一个奖品
更改:
date里声明变量,
data() {
return {
activeIndex: null,
cj: 5, // 控制中奖为第五个下标
};
},
这里的控制中奖,既可以设置为随机中奖,也可以设置为只种一个奖(就很棒!)我们这里的下标为五的奖品是100积分
在上面空的if为true代码块里,再写个判断
注意代码注释
move(time) {
if (time > 800) {
// 当中奖下标不等于我设置的奖品下标时,继续执行,
if (this.activeIndex !== this.cj) {
// 定时器
this.timer = setTimeout(() => {
let n = this.activeIndex + 1;
this.activeIndex = n % 8;
// 这里时间参数要小,这样才不会看出来倪端,不然猛的停下来,一看就太假。这样就是慢慢停到你想让他中奖的那个li上
this.move(time + time * 0.05);
}, time);
} else {
// 中奖下标等于我设置的奖品下标时,中奖了
console.log("中奖了", this.cj);
}
} else {
this.timer = setTimeout(() => {
let n = this.activeIndex + 1;
this.activeIndex = n % 8;
this.move(time + time * 0.1);
}, time);
}
},
太没人性了,下面我们写随机中奖
注意代码注释
fn(item) {
if (item.id !== "btn") return;
this.activeIndex = null;
// 控制随机中奖,先清空写死的中奖下标
this.cj = null
setTimeout(()=>{
// 使用Math.random 随机数 *8 然后赋值给我们的 this.cj 即可
this.cj = Math.floor(Math.random()*8)
console.log(this.cj);
},1000)
this.move(10);
},
这样就很银杏化了!!!
下面我们把item.text,写成作用域插槽
在App的子组件标签里,写上作用域插槽
<cj :list="list">
// 这里是奖品
<template #item='itemScope'>
<p>
{{itemScope.itemDate.text}}
</p>
</template>
// 这里是按钮
<template #btn>
<p>
用户的按钮
</p>
</template>
</cj>
然后在子组件里,更改 item.text
// li标签里,先注释掉 {{ item.text }}
// {{ item.text }}
// 写上slot 把item数据传递过去,接着进行v-if判断一下,不写v-if的话九宫格就全是奖品,没有按钮了
<slot name="item" :itemDate="item" v-if="item.id!=='btn'"></slot>
<slot name="btn" v-else></slot>
作用域插槽就完成了,下面我们改成vue3的写法
VUE3 (不多做详细解释),有过了解的,一看便懂
ps:vue3不需要data,因此没有this。使用的vue3内部的方法,都需要引入。函数和变量都写在一个函数里,都需要return出去。对于ref绑定的动态值做处理时,都需要写成 变量.value。
<template>
<ul class="cj_box">
<li
v-for="item in renderList"
:key="item.id"
:class="{ active: activeIndex === item.index }"
@click="fn(item)"
>
<slot name="item" :itemDate="item" v-if="item.id !== 'btn'"></slot>
<slot name="btn" v-else></slot>
</li>
</ul>
</template>
<script>
// 注册
import { computed, ref } from "vue";
export default {
name: "cj",
props: {
list: Array,
},
// setup(),传入子组件信息
setup(props) {
// 下标
let activeIndex = ref(null);
// 默认中奖
let cj = ref(5);
// 时间值
let timer = null;
// 点击开始抽奖
let fn = function (item) {
if (item.id !== "btn") return;
activeIndex.value = null;
// 控制随机中奖
cj.value = null;
setTimeout(() => {
cj.value = Math.floor(Math.random() * 8);
console.log(cj);
}, 1000);
// 调用抽奖函数
move(10);
};
// 抽奖函数
let move = function (time) {
if (time > 800) {
// 若非随机,指定奖品
if (activeIndex.value !== cj.value) {
timer = setTimeout(() => {
let n = activeIndex.value + 1;
activeIndex.value = n % 8;
move(time + time * 0.05);
}, time);
} else {
// 中奖了
console.log("中奖了", cj);
}
} else {
// 抽奖
timer = setTimeout(() => {
let n = activeIndex.value + 1;
activeIndex.value = n % 8;
move(time + time * 0.1);
}, time);
}
};
// 更改下标,改成九宫格形式
let renderList = computed(() => {
let ary =
// 对如果为空,做处理
props.list &&
props.list.map((item, index) => {
switch (index) {
case 3:
return {
...props.list[7],
index: 7,
};
case 4:
return {
...props.list[3],
index: 3,
};
case 5:
return {
...props.list[6],
index: 6,
};
case 6:
return {
...props.list[5],
index: 5,
};
case 7:
return {
...props.list[4],
index: 4,
};
default:
return {
...item,
index: index,
};
}
});
// 添加点击按钮
return ary
.slice(0, 4)
.concat({ id: "btn", text: "开始抽奖" })
.concat(ary.slice(4));
});
return {
activeIndex,
renderList,
cj,
fn,
move,
timer,
};
},
};
</script>
<style scoped lang="less">
.cj_box {
width: 800px;
height: 800px;
margin: auto;
overflow: hidden;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
li {
width: 251px;
height: 251px;
background-color: #eee;
text-align: center;
line-height: 251px;
&:nth-child(5) {
cursor: pointer;
}
}
li.active {
background-color: rgb(233, 159, 159);
}
}
</style>
补充:
可写个独立的js文件,引入vue组件并注册,然后把js文件,在mian.js里引入。使用时,直接组件名就行,不需要引入和注册
// 独立js文件名字为 cj.js cj3.vue组件为vue3的写法
import cj3 from './cj3.vue'
export default {
install(_Vue) {
console.log(_Vue);
_Vue.component('cj', cj3)
}
}
// main文件 引入 cj.js并注册
import { createApp } from 'vue'
import App from './App.vue'
import cj from './abc/cj.js'
createApp(App).use(cj).mount('#app')
如下就是在App组件里,使用通过cj.js,全局注册的cj.vue组件。直接使用即可
结束!
加油! Respect peace and love