九宫格抽奖的两种实现方案
九宫格抽奖,实现跑马灯的效果,要求启动后要有加速、匀速和减速的过程。

- 根据设置的顺序运动。 要有速度变换的过程
- 运动过程中当前格子要有操作,比如边框、高亮。
第一种方案 setTimeout
通过修改 setTimeout 的第二个参数,控制每次运动的时间间隔,从而达到加减速的效果。
- 采用环形链表的结构来描述抽奖运动的整个过程,这样好处是只需要每次移动向下一个就好
/**
*
* @param {*} DataArr 奖品数组
* @param {*} count 圈数
* @param {*} id 中奖id
* @param {*} cb 每走一格的回调
* @param {*} end 结束回调
* @returns
*/
function start(DataArr, count, id, cb, end) {
console.log("中奖id", id);
let i = 1; // 初始值为1 代表当前在第一个
let time,
current = 0;
// 最大速度 150
let timeout = 300; // 走一格所花的时间,时间越大,速度越慢。
for (let m = 0; m < DataArr.length; m++) {
// 找到圈数之后要走的步数
if (DataArr[m] == id) {
break;
}
}
// 创建链表
let data = Array(DataArr.length)
.fill(0)
.map((e, i) => ({ order: i }));
for (let i = 0; i < DataArr.length; i++) {
// 格式化奖品 环形链表
data[i].next = i < DataArr.length - 1 ? data[i + 1] : data[0];
}
// 定位到抽中的奖品
let n = 0;
let tem = data[0];
while (true) {
console.log("抽中的奖品", id);
if (tem.order === id) break;
tem = tem.next;
n++;
if (n === data.length) {
console.error(`奖品${id}不存在`);
return;
}
}
let currentObj = data[0];
let allcount = DataArr.length * count + n; // 计算需要走多少格
cb(currentObj); // 初始化位置
function step() {
// 结束条件
if (i > data.length * count && currentObj.order == id) {
clearInterval(time);
time = null;
end();
return;
}
// 加速
if (i < allcount / count) timeout = timeout - (160 / allcount) * count;
// 减速
if (i > ((count - 1) * allcount) / count)
timeout = timeout + (500 / allcount) * count;
if (i == allcount - 2) timeout = 600;
if (i == allcount - 1) timeout = 1000;
currentObj = currentObj.next; // 获取当前停留格子的数据,
cb(currentObj);
i++;
setTimeout(step, timeout);
}
time = setTimeout(step, timeout);
}
export default start;第二种方案 requestAnimationFrame
requestAnimationFrame 方法告诉浏览器您希望执行动画,并请求浏览器在下一次重新绘制之前调用指定的函数来更新动画。
它返回一个 id,标示当前的回调。用法与 settimeout 类似可以通过cancelAnimationFrame 用于取消这个函数的执行。
基本实现思路跟第一种方案类似,在于把每一步区分成多个帧,相隔一定的帧数走一格。 通过控制每走一格所相隔的帧数,从而达到视觉上加速、减速的效果。
class LuckCtrl {
private data: Record<string, any>[];
private maxSpeed: number;
private cycleNumber: number;
private minSpeed: number;
Raf?: number;
/**
*
* @param DataArr
* @param cycleNumber
* @param minSpeed
*/
constructor(
DataArr: number[],
cycleNumber: number = 7,
minSpeed: number = 10
) {
this.data = Array(DataArr.length)
.fill(0)
.map((e, i) => ({ order: i }));
this.maxSpeed = 4; // 最大速度
this.cycleNumber = cycleNumber; // 圈数
this.minSpeed = minSpeed; // 最小速度
for (let i = 0; i < DataArr.length; i++) {
// 格式化奖品 环形链表
this.data[i].next =
i < DataArr.length - 1 ? this.data[i + 1] : this.data[0];
}
}
/**
* 运动开始
* @param id 抽中的奖品id
* @param running 运动中回调
* @param runend 运动结束回调
* @returns
*/
run(
id: number,
running: (k: Record<string, any>) => void,
runend: () => void
) {
let counter = 0, // 记速器
current = 0, // 调速器 控制区内间速度
currentObj = this.data[0];
let n = 0;
let tem = this.data[0];
while (true) {
// 定位到抽中的奖品
console.log("抽中的奖品", id);
if (tem.order === id) break;
tem = tem.next;
n++;
if (n === this.data.length) {
console.error(`奖品${id}不存在`);
return;
}
}
let allCount = this.cycleNumber * this.data.length + n; // 总运动区间
let addSpace = this.minSpeed - this.maxSpeed; // 加速区间
let reduceSpace = allCount - addSpace; // 减速区间
/**
* raf 每次执行回调
* @returns
*/
const step = () => {
// 加速
if (counter < addSpace) {
if (current < Math.pow(this.minSpeed - counter, 2)) {
current += this.minSpeed / 2;
} else {
current = 0;
counter++;
currentObj = currentObj.next;
running(currentObj);
console.log("加速阶段");
}
}
// 匀速
if (counter >= addSpace && counter < reduceSpace) {
if (current < this.maxSpeed) current++;
else {
// 计数清零
current = 0;
counter++;
currentObj = currentObj.next;
running(currentObj);
console.log("匀速阶段");
}
}
// 减速
if (counter >= reduceSpace && counter < allCount) {
if (Math.sqrt(current) <= this.minSpeed - (allCount - counter)) {
current += 2;
} else {
current = 0;
counter++;
currentObj = currentObj.next;
running(currentObj);
console.log("减速阶段");
}
}
// 停止
if (counter >= allCount) {
runend();
console.log("停止");
this.offRaf(); // 关闭raf
return;
}
this.Raf = requestAnimationFrame(step);
};
running(currentObj); // 初始化抽奖位置
console.log("抽奖开始");
this.Raf = requestAnimationFrame(step);
}
offRaf() {
console.log("关闭raf");
this?.Raf && cancelAnimationFrame(this.Raf);
}
}