效果预览
纯前端红包雨功能
使用了vant,postcss-px-to-viewport,lodash
项目结构
reset.css
html,
body {
height: 100%;
/* 文字风格 Sans-serif 各笔画粗细相同,Serif 笔画粗细不同,monospace 等宽体,cursive草书,fantasy梦幻 */
font-family: 'Microsoft YaHei', sans-serif, 'Helvetica Neue', Helvetica, Arial,
'黑体', '宋体', Arial;
-webkit-tap-highlight-color: transparent;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font-size: 14px;
color: #333;
}
/* 重置各标签的默认样式 */
a,
body,
center,
cite,
code,
dd,
del,
div,
dl,
dt,
em,
fieldset,
figcaption,
figure,
footer,
form,
h1,
h2,
h3,
h4,
h5,
h6,
header,
hr,
html,
img,
input,
label,
legend,
li,
mark,
ol,
p,
section,
span,
textarea,
time,
td,
th,
ul {
margin: 0;
border: 0;
padding: 0;
font-style: normal;
box-sizing: border-box;
/* 自动换行 */
word-wrap: break-word;
/* 强制英文单词断行 */
word-break: break-all;
}
/* 设置标签为块级分类 */
article,
aside,
details,
fieldset,
figcaption,
figure,
footer,
header,
main,
nav,
section {
display: block;
}
/* 去除input标签的默认样式 */
button,
input,
textarea {
-webkit-appearance: none;
font-family: 'Microsoft YaHei', sans-serif, 'Helvetica Neue', Helvetica, Arial,
'黑体', '宋体', Arial;
border: 0;
margin: 0;
padding: 0;
font-size: 1em;
line-height: 1em;
outline: none;
background-color: transparent;
}
/* 禁止多文本框手动拖动大小 */
textarea {
resize: none;
-webkit-appearance: none;
}
/* 去掉按下的阴影盒子 */
input,
textarea,
a {
-webkit-tap-highlight-color: transparent;
}
/* 清除a标签下划线 */
a,
a:visited {
text-decoration: none;
}
a:focus,
a:active,
a:hover {
outline: none;
}
/* 清除列表前面的点 */
ol,
li,
ul {
list-style: none;
}
/* 清除IE下图片的边框 */
img {
border-style: none;
font-size: 0;
}
/* 解决chrome浏览器默认黄色背景问题 */
input:-webkit-autofill,
textarea:-webkit-autofill,
select:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px #fff inset;
}
/* 设置默认滚动条样式 */
::-webkit-input-placeholder {
color: #afbdcc;
}
:-moz-placeholder {
color: #afbdcc;
}
::-moz-placeholder {
color: #afbdcc;
}
:-ms-input-placeholder {
color: #afbdcc;
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background-color: #f5f5f5;
}
::-webkit-scrollbar-track-piece {
background-color: #f5f5f5;
border-radius: 6px;
}
::-webkit-scrollbar-thumb {
background-color: #cccccc;
border-radius: 6px;
}
::-webkit-scrollbar-corner {
background-color: #f5f5f5;
}
::-webkit-resizer {
background-repeat: no-repeat;
background-position: bottom right;
}
RedPacket.js
// @ts-ignore
import RedPacketImgUrl from "../assets/image/RedPacket.png";
// @ts-ignore
import giftImgUrl from "../assets/image/liwu.png";
import { sample, random } from "lodash";
export default class RedPacket {
constructor(options) {
//类型 奇数:红包 偶数:礼物
this.type = options.type || 1;
//红包图片地址
this.url = options.url || RedPacketImgUrl;
// 礼物图片地址
this.giftUrl = options.giftUrl || giftImgUrl;
//红包宽度
this.width = options.width || "20vw";
//红包高度
this.height = options.height || "auto";
//红包的轨道
this.track = options.track || sample([0, 10, 20, 30, 40, 50, 60, 70, 80]);
//红包旋转角度
this.angle = options.angle || random(-360, 360);
// 红包掉落速度
this.speed = options.speed || random(3.1, 6.1);
//红包回调,
this.callback = options.callback;
//红包放入那个容器
this.parent = options.parent || document.body;
//创建红包
this.create();
}
//创建方法
create() {
//创建一个红包dom元素
const img = document.createElement("img");
//图片地址
if (this.type % 2 === 0) {
//偶数:礼物
img.src = this.giftUrl;
} else {
//奇数:红包
img.src = this.url;
}
//图片宽度
img.style.width = this.width;
//图片高度
img.style.height = this.height;
//开启定位
img.style.position = "absolute";
//红包轨道
img.style.left = this.track + "vw";
img.animate(
[
{ transform: "translateY(-20vh) rotate(0)" },
{ transform: `translateY(110vh) rotate(${this.angle}deg)` },
],
{
duration: this.speed * 1000,
fill: "forwards",
easing: "cubic-bezier(0.42, 0, 0.58, 1)", // 使用贝塞尔函数
}
);
// 图片的回调
img.ontouchstart = this.destroy.bind(this, img);
//放入容器
this.parent.appendChild(img);
setTimeout(() => {
// @ts-ignore
img.remove();
}, this.speed * 1000);
}
//图片销毁方法
destroy(currentImg) {
// @ts-ignore
currentImg.remove();
if (this.type % 2 === 0) {
//偶数:礼物
this.callback(2);
} else {
//奇数:红包
this.callback(1);
}
}
}
finally.vue
<template>
<van-overlay :show="isShow">
<div class="wrapper">
<div class="boxs">
<div class="one">最终奖励:</div>
<div class="two">抢到红包个数: {{ redPacketNumber }}个</div>
<div class="three">抢到礼物个数: {{ giftNumber }}个</div>
</div>
</div>
</van-overlay>
</template>
<script>
export default {
name: "finally",
data() {
return {
isShow: false,
redPacketNumber: 0, //红包个数
giftNumber: 0, //礼物个数
};
},
methods: {
show(val1, val2) {
this.redPacketNumber = val1;
this.giftNumber = val2;
this.isShow = true;
},
},
};
</script>
<style scoped lang="scss">
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.boxs {
text-align: center;
color: orange;
font-family: serif;
font-size: 26px;
.one {
font-size: 32px;
margin-bottom: 20px;
color: #fe3431;
}
.two {
margin-bottom: 10px;
}
}
</style>
meng.vue
<template>
<div>
<van-overlay :show="isShow">
<div class="wrapper">
<div>
<div class="title">红包雨即将开始</div>
<van-count-down
ref="countDown"
:time="times"
:auto-start="false"
@finish="finishs"
>
<template #default="timeData">
<h1 class="block">{{ timeData.seconds }}</h1>
</template>
</van-count-down>
</div>
</div>
</van-overlay>
</div>
</template>
<script>
export default {
name: "meng",
props: ["onFinish"],
data() {
return {
isShow: false,
times: 3500,
};
},
methods: {
show(time = 3500) {
this.isShow = true;
this.times = time;
this.$refs.countDown.start();
},
finishs() {
this.isShow = false;
this.onFinish(111);
},
},
};
</script>
<style scoped lang="scss">
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.block {
font-size: 60px;
font-weight: normal;
font-family: serif;
color: orange;
text-align: center;
}
.title {
position: fixed;
width: 100%;
top: 20%;
left: 0;
font-size: 30px;
font-weight: normal;
font-family: serif;
color: #fe3431;
text-align: center;
}
</style>
App.vue
<template>
<div id="app">
<meng ref="meng" :onFinish="onFinishs"></meng>
<jiangli ref="jiangli"></jiangli>
<div class="box" ref="RedPacketBox"></div>
<div v-if="boxShow" class="boxs">
<div>
<div class="imgs" @click="openCountdown">
<img src="./assets/image/RedPacket.png" alt="" />
</div>
</div>
</div>
</div>
</template>
<script>
import meng from "./components/meng.vue";
import jiangli from "./components/finally.vue";
import RedPacket from "./class/RedPacket";
import { random } from "lodash";
export default {
name: "App",
components: {
meng,
jiangli,
},
data() {
return {
boxShow: true,
timer: null,
redPacketNumber: 0, //红包个数
giftNumber: 0, //礼物个数
};
},
mounted() {},
methods: {
//开启倒计时
openCountdown() {
this.boxShow = false;
this.$refs.meng.show(3500);
},
//倒计时结束
onFinishs() {
this.setRedPacket(200, 20000);
},
//红包雨结束
redPacketEnd() {
this.$refs.jiangli.show(this.redPacketNumber, this.giftNumber);
},
//点击红包的回调
clickRedPacket(val) {
if (val % 2 === 0) {
//偶数:礼物
this.giftNumber++;
} else {
//奇数:红包
this.redPacketNumber++;
}
},
setRedPacket(speed, time) {
const that = this;
//下雨
this.timer = setInterval(() => {
new RedPacket({
parent: this.$refs.RedPacketBox,
type: random(0, 10),
callback: this.clickRedPacket,
});
}, speed);
//停止
setTimeout(() => {
clearInterval(this.timer);
that.redPacketEnd();
}, time);
},
},
};
</script>
<style lang="scss" scoped>
.box {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: linear-gradient(180deg, rgb(230, 190, 117), orange);
overflow: hidden;
}
.boxs {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: linear-gradient(180deg, rgb(230, 190, 117), orange);
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
.imgs {
width: 100px;
margin: 0 auto;
margin-bottom: 10px;
animation: shake 0.5s infinite;
img {
width: 100%;
height: auto;
}
}
}
@keyframes shake {
0% {
transform: rotate(0);
}
25% {
transform: rotate(-10deg);
}
50% {
transform: rotate(10deg);
}
75% {
transform: rotate(-10deg);
}
100% {
transform: rotate(0);
}
}
</style>
素材图(可以私信我要)
