バックグラウンド
「手にハンマーを持っていると、世界中の釘が見える」ということわざがあります。前回の記事での最初の npm パッケージの開発リスト項目の高さが固定された仮想リスト コンポーネントを開発しました。その開発コンポーネントは中毒になりました。最近のプロジェクトでは、9マスのターンテーブル抽選アニメーションを実現する予定です。この機能は再利用できるようです。コンポーネント化することも意図されています。前回の開発コンポーネントで検討した開発パラダイムにより、今回の開発コンポーネントには開発コンポーネント以外の問題の自由が少なくなります。さて、今日の本題に入ります。
エフェクトデモ
まずは最終的な演出を見てみましょう。抽選ボタンをクリックすると抽選が回転し始めます。抽選のアニメーションは低速から高速に変化し、その後一定速度を維持します。停止すると低速になります。現実世界のオブジェクトの回転効果。ちなみに、この記事の抽選機能は、vue3 + vite + ts
開発手法を採用して開発しているため、他の技術スタックを使用している場合は、使用するために修正が必要です。
実装のアイデア
九公格宝くじ抽選マーキー アニメーションは、九公格宝くじ抽選機能の中核です。以下の図に示すように: 通常、九公歌マーキー アニメーションは時計回りに回転し、回転順序は [1、2、3、6、9、8、7、4] です。どのグリッドに目を向けると、このグリッドにハイライト スタイルを追加すると、ちらつき効果があるように見えます。また、回転速度も変化します。最初は初速度から最高速度まで加速し、その後は一定速度で回転を続けますが、ウィニンググリッドに到達したら速度を下げます。マーキーの回転速度を制御するにはどうすればよいですか? 現在位置に到達するまでの歩数と、曲がる必要がある総歩数に基づいて判断できます。たとえば、走行の最初の 3 分の 1 ではタイマーを加速させ、走行の最後の 6 分の 1 ではタイマーを加速させ、走行の途中では一定の速度を維持し、タイミング値を次のように設定します。タイマーを固定値に設定します。
自分でやれ
固定ページを描画する
インターネットから見つけた10元即時割引金の写真、参加ありがとうございます、宝くじボタンの写真の3枚を使用して、9グリッドの素材データリストを生成します。画像全体ではなく 9 枚の画像を使用する理由は何ですか? 主な理由は、画像全体を使用すると、マーキーが通過するグリッドを設定するときにハイライト効果を実現するのが簡単ではないためです。
import { reactive } from 'vue';
import { NineGridLottery } from '@lib/core';
import RMBImg from '@/assets/10rmb.png';
import LotteryBtnImg from '@/assets/lottery-btn.png';
import ThankImg from '@/assets/thank.png';
// 奖品列表
const lotteryList = reactive([
{ name: '10元立减金', pic: RMBImg },
{ name: '谢谢参与', pic: ThankImg },
{ name: '10元立减金', pic: RMBImg },
{ name: '谢谢参与', pic: ThankImg },
{ name: '抽奖', pic: LotteryBtnImg },
{ name: '谢谢参与', pic: ThankImg },
{ name: '10元立减金', pic: RMBImg },
{ name: '谢谢参与', pic: ThankImg },
{ name: '10元立减金', pic: RMBImg },
]);
网页结构如下: index=4 时,这个位置是抽奖按钮,抽奖按钮一般交互比较多,所以用插槽的方式暴露给父组件,让父组件去定制。因为slot
上不能绑定事件,所以需要在外层加个div,把点击事件绑定在外层的div上。考虑到不同的抽奖活动外观展示都不一样,所以抽奖的样式要允许父组件自定义。这里把抽奖组件的样式类名前缀交给父组件去配置。
<template>
<div :class="`${props.classPrefix}-box`">
<div
v-for="(item, index) in props.lotteryList"
:class="[`${props.classPrefix}-item`, { active: state.curIndex === index }]"
>
<template v-if="index !== 4">
<img v-if="item.pic" :src="item.pic" class="pic" alt="" />
<!-- <p class='text'>{{ item.name }}</p> -->
</template>
<div v-else @click="start()">
<slot name="lotteryBtn" :itemData="item"> </slot>
</div>
</div>
</div>
</template>
九宫格抽奖组件的样式是在父组件定义的,九宫格抽奖容器采用flex布局,需要注意的是 flex-wrap
默认属性是不换行的,要将属性值设置成wrap
。另外下载的png图片不太标准,有色彩的部分并不在png图片背景的正中央,需要向左偏移1px, 看起来左右才对称。转动高亮的效果使用区别于静止状态的背景图和阴影效果实现。
<style lang="less">
.lottery-box {
width: 375px;
height: 375px;
background: #ea0019;
margin: 100px auto;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
align-items: center;
.lottery-item {
width: 125px;
height: 125px;
position: relative;
&.active {
box-shadow: 2px 2px 30px #ffe4c0;
background-color: #ffe4c0;
}
&:nth-of-type(5) {
cursor: pointer;
}
.pic {
width: 100%;
height: 100%;
position: absolute;
left: -1px;
}
.text {
width: 100%;
height: 20px;
background: rgba(0, 0, 0, 0.5);
color: #fff;
font-size: 12px;
text-align: center;
line-height: 20px;
position: absolute;
left: 0;
bottom: 0;
}
}
}
</style>
让抽奖转盘转起来
首先确定一下九宫格转盘的跑动顺序。顺时针转动时,每个格子的下标为:
// 跑动顺序
const lotterySort = [0, 1, 2, 5, 8, 7, 6, 3];
每跑一圈,需要跑八步。到达中奖格子位置跑马灯动画需要跑动的总步数=基本圈数*8+中奖格子在跑动数组中的位置
。中奖的id在未跑动前,要先调用后端接口,确定下来。
// 总执行步数
const totalSteps = computed(() => {
return props.baseCircles * 8 + lotterySort.indexOf(props.winId);
});
跑动时,需要动态设置跑动速率。每次跑动时,清除上一次的速率。每前进一步,重新创建定时器,调整速率。并对到达位置的格子进行高亮显示。当跑动的步数大于等于总步数时,给父组件发消息。
const timer: Ref<ReturnType<typeof setTimeout> | null> = ref(null);
const startRun = () => {
// 延时器的速度要动态调节
timer.value && clearTimeout(timer.value);
// console.log(`已走步数=${state.curStep}, 执行总步数=${totalSteps.value}`);
// 已走步数超过要执行总步数, 则停止
if (state.curStep >= totalSteps.value) {
state.isRunning = false;
return emit('end');
}
// 高亮抽奖格子序号
state.curIndex = lotterySort[state.curStep % 8];
// 速度调整
state.speed = calcSpeed(state.speed);
timer.value = setTimeout(() => {
state.curStep++;
startRun();
}, state.speed);
};
跑动速率的计算方法,前三分之一的路程,从初始速率加速到最快速率,然后保持最快速率,匀速跑动,到了总路程最后的六分之一时,开始减速,犹如刹车一样,慢慢停下来。
// 需要加速的前段步数
const frontSteps = Math.floor(props.baseCircles * 8 * (1 / 3));
// 需要减速的后段步数
const midSteps = Math.floor(totalSteps.value * (5 / 6));
// 计算速度
const calcSpeed = (speed: number) => {
// 最快最慢速度
const { fastSpeed, slowSpeed } = props;
// 前段加速,中段匀速,后段减速
if (state.curStep < frontSteps && speed > fastSpeed) {
speed = speed - Math.floor((props.initSpeed - fastSpeed) / frontSteps);
} else if (state.curStep > midSteps && speed < slowSpeed) {
speed = speed + Math.floor((slowSpeed - fastSpeed) / frontSteps / 5);
}
return speed;
};
使用方法
组件有 7 个配置参数:
组件属性名 | 含义 |
---|---|
lotteryList | 抽奖列表 |
winId | 中奖 id |
classPrefix | 九宫格容器和奖项的样式类名前缀 |
initSpeed | 初始转动速度 单位 ms (可选) |
baseCircles | 基本转动圈数 (可选) |
fastSpeed | 最快转动速度 单位 ms (可选) |
slowSpeed | 最慢转动速度 单位 ms (可选) |
@end | 转动结束事件 |
<template>
<NineGridLottery
:lotteryList="lotteryList"
:winId="options.winId"
:initSpeed="options.initSpeed"
:baseCircles="options.baseCircles"
@end="handleEnd"
classPrefix="lottery"
>
<template #lotteryBtn="{ itemData }">
<img :src="itemData.pic" class="pic" alt="" />
</template>
</NineGridLottery>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import { NineGridLottery } from '@lib/core';
import RMBImg from '@/assets/10rmb.png';
import LotteryBtnImg from '@/assets/lottery-btn.png';
import ThankImg from '@/assets/thank.png';
// 奖品列表
const lotteryList = reactive([
{ name: '10元立减金', pic: RMBImg },
{ name: '谢谢参与', pic: ThankImg },
{ name: '10元立减金', pic: RMBImg },
{ name: '谢谢参与', pic: ThankImg },
{ name: '抽奖', pic: LotteryBtnImg },
{ name: '谢谢参与', pic: ThankImg },
{ name: '10元立减金', pic: RMBImg },
{ name: '谢谢参与', pic: ThankImg },
{ name: '10元立减金', pic: RMBImg },
]);
// 后台配置的奖品数据
const options = reactive({
// 中奖id
winId: 6,
// 基本圈数
baseCircles: 4,
// 抽奖转动速度
initSpeed: 300,
fastSpeed: 100,
slowSpeed: 600,
});
const handleEnd = () => {
alert('恭喜你中奖了');
};
</script>
// 样式参照上文
<style lang="less"></style>
结语
これで、vue3+vite4+ts バージョンの九公歌回転抽選の主要な機能ポイントの実現は終了です。この記事の例の完全なコードを入手したい場合は、ここをクリックしてダウンロードしてください。さらに、このコンポーネントは npm 公式 Web サイトにもアップロードされており、ここをクリックして表示、インストール、使用できます。