最近作者遇到了一个红包雨的需求。就是 我们 平常 经常见到的 一堆红包从上往下开始落。然后用户们就哒哒哒哒哒的点😂
今天作者分享一个自己写的demo,个人认为还有很大的进步空间和改良的地方。如果大家有更好的方案,希望不吝赐教。🌹
OK 首先思考红包雨实现的几个关键点:
1. 从上往下落。这里作者用 css 的 animation 实现。
2. 红包的位置。因为我们需要让红包散点的铺开,之间有些距离。这样才能有更好的视觉效果。作者用 css 的定位。 父容器 用 relative,红包用 absolute。这样我们就 只用 调整 top 和 left 两个参数即可。
好,实操开始!!!启动!
首先作者是这么写的文件结构。
luckyBagRain ----红包雨
然后将红包封成了单独的组件,也就是 components 下的 luckyBag.vue
index.vue 是我们的父容器页面。
utils 文件夹下的 creatLuckyBag.js 是创建红包的工具方法。因为作者想通过函数调用的方式调用组件,创建红包(使用vue 的 h 和 render 函数,类似于element-plus 的 message 组件)。而不是在父组件里面通过 v-for 去循环。
源码如下:
index.vue
<script setup>
import { ref, onMounted } from 'vue'
import { createLuckyBag } from './utils/createLuckyBag.js'
let timer
onMounted(() => {
// 开始执行掉落红包
startDown(10, 3000)
})
function startDown(count, duration = 3000) {
// timer 用于之后删除定时器
timer = setInterval(() => {
for (let index = 0; index < count; index++) {
createLuckyBag()
}
}, duration)
}
</script>
<template>
<div class="luckyBagRain">
</div>
</template>
<style lang="scss" scoped>
.luckyBagRain {
position: relative;
height: 100vh;
box-sizing: border-box;
background-color: rgba($color: #000000, $alpha: 0.8);
overflow: hidden;
}
</style>
luckyBag.vue
<script setup>
import { ref, onMounted } from 'vue'
//销毁组件方法
const props = defineProps({
onDestroy: Function
})
const visible = ref(true)
const speed = ref({})
onMounted(() => {
// 随机数 生成红包 top 和 left 的值。
let x = Math.random() * window.innerWidth
let y = Math.random() * 1000 * Math.random()
// 随机时间,控制红包不同的下落速度
let duration = Math.random() * 5 + 8
//如果红包超出边框,则调整
x = x > window.innerWidth * 0.8 ? x - window.innerWidth / 5 : x
speed.value = {
'animation-duration': duration + 's',
'left': x + 'px',
'top': y + 'px'
}
// 比动画结束前 1s 销毁组件
setTimeout(() => {
fade()
}, (duration - 1) * 1000)
})
//点击之后配合transition 组件实现fade淡出效果,并销毁组件,后续和后端的交互也可在此处
function fade() {
visible.value = false
setTimeout(() => {
props.onDestroy()
}, 1000)
}
</script>
<template>
<transition name="fade">
<div :style="speed" @click="fade" v-if="visible" class="lucky-bag"></div>
</transition>
</template>
<style lang="scss" scoped>
.lucky-bag {
position: absolute;
width: 60px;
height: 80px;
background: url("@/assets/img/activity/luckyBag.png") no-repeat;
background-size: 100% 100%;
animation: move linear;
}
.fade-enter-active,
.fade-leave-active {
transition: all 0.4s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
@keyframes move {
0% {
transform: translateY(-1000px);
}
100% {
transform: translateY(100vh);
}
}
</style>
creatLuckyBag.js
import { createVNode, render } from 'vue';
import LuckyBag from '../components/luckyBag.vue';
function createLuckyBag() {
const container = document.createElement('div');
const targetDom = document.getElementsByClassName('luckyBagRain')[0];
const vm = createVNode(LuckyBag, {})
vm.props.onDestroy = () => {
render(null, container)
}
render(vm, container);
targetDom.appendChild(container.firstElementChild);
}
export { createLuckyBag }
效果展示:
如果你在实现过程中遇到了任何问题,或者对本文有任何建议,欢迎在评论区留言交流。