效果:
- 点击按钮下红包雨
- 每个红包就是这么晃呀晃
- 速度可调节、红包雨图片可调节、选中红包后换的图片可调节。。
效果图:
思路:
- 给所有的红包设置一个容器,v-if先为false
- 使用页面中的开关打开时,组件容器v-if为true
- 设置定时器创建元素img,给img添加className(该className提前设置样式和固定定位)
- 执行每个小红包的设置函数:红包生成的频率通过定时器中的frequency控制、位置通过固定定位的left和top的设置来控制。。目前只有这俩,想设置别的再将变量抽离出即可
- 每个红包的位置出容器后,将自己的定时器销毁,自己的元素从容器中移除
- 所有的红包消失后 或 跳转到别的页面,销毁所有定时器
- 点击每个红包后需要将事件及元素传递出来,页面中应该会用到
- 操作完毕元素后,将容器出现的变量设置为false
点:
- 设置一个数组防止每个小红包的定时器,用来将来销毁
- 红包的点击事件利用事件委托加在了容器上,又通过emit将元素的值传递给了使用页面
- 得等所有的红包消失后才能让该容器消失,所以使用了Promise.all方法
组件代码:
<script lang='ts' setup>
const { proxy }: any = getCurrentInstance();
const emit = defineEmits(['update:rain', 'sendPack']);
const props = defineProps({
rain: {
required: true,
type: Boolean,
default: false
},
// 持续多久:30s
continue: {
type: Number,
default: 30000
},
// 红包频率:0.5s
frequency: {
type: Number,
default: 500
},
// 下雨时的红包src
rainImgSrc: {
type: String,
default: 'https://img2.baidu.com/it/u=3312370966,862425551&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1683997200&t=14f3f89e4313a437460360bbab975013'
},
// 选中后的src
selectedSrc: {
type: String,
default: 'https://img1.baidu.com/it/u=2757163103,4075712024&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1684256400&t=9b770ad2ebc148391718a0c636cd080b'
},
})
const moneyConShow = computed({
get() {
if (props.rain) nextTick(() => startRain());
return props.rain;
},
set() {
emit('update:rain', false);
}
})
// #region common
// 保存所有红包定时器
const allPackTimer = ref([]);
// 销毁定时器
const removeTimer = (timer: number, type: string) => {
if (timer) {
type === 'Interval' ? clearInterval(timer) : clearTimeout(timer);
timer = 0;
}
}
const removeAllTimer = () => {
removeTimer(rainTimer.value, 'Interval');
removeTimer(closeRainTimer.value, 'Timeout');
for (let i = 0; i < allPackTimer.value.length; i++) {
const timer = allPackTimer.value[i].timer;
if (timer) {
removeTimer(timer, 'Interval')
allPackTimer.value.splice(i, 1) // 得清空储存定时器的数组
i--;
};
}
}
// #endregion
const screenHeight = shallowRef(0);
// 容器
const refMoneyCon = ref();
const moneyConWidth = shallowRef(0);
// 开始下雨定时器
const rainTimer = ref(0);
// 关闭下雨的定时器
const closeRainTimer = shallowRef(0);
const packClickFun = (e: any) => {
const packMessage = e.target.getAttribute('message');
// console.log(packMessage, '---------- 红包唯一值,别删,要放toast')
const currentPackEl = e.target;
// remove current timer
const currentPackTimer = allPackTimer.value.find(item => item.id === packMessage);
removeTimer(currentPackTimer.timer, 'Interval');
currentPackEl.style.transform = 'rotate(0deg) scale(1.4)';
currentPackEl.src = props.selectedSrc;
currentPackEl.style.transition = 'all .2s ease-in-out';
currentPackEl.style.zIndex = '99';
emit('sendPack', currentPackEl);
}
const startRain = () => {
// 取值
const containerEl = refMoneyCon.value;
moneyConWidth.value = containerEl.clientWidth;
screenHeight.value = document.documentElement.clientHeight || document.body.clientHeight;
// 每个红包的点击
containerEl.addEventListener('click', packClickFun);
let promiseTimerArr = [];
rainTimer.value = window.setInterval(() => {
// message是自定义属性,红包的唯一值
promiseTimerArr.push(makePack({ containerEl, message: Math.random() * 10 + new Date().getTime().toString() }));
}, props.frequency)
// 从此刻开始,continue后结束:销毁下雨定时器、销毁自己
closeRainTimer.value = window.setTimeout(() => {
removeTimer(rainTimer.value, 'Interval');
removeTimer(closeRainTimer.value, 'Timeout');
// 所有红包的定时器都执行完毕后,容器消失,销毁页面click事件监听
Promise.all(promiseTimerArr).then(() => {
moneyConShow.value = false;
containerEl.removeEventListener('click', packClickFun);
})
}, props.continue);
}
const packHeight = shallowRef(0);
interface PackType {
containerEl: Element
message: string
}
const makePack = ({
containerEl,
message
}: PackType) => {
return new Promise((resolve) => {
// create & init
const packEl = document.createElement('img');
// 自定义属性:给每个红包配置唯一值
let msg = document.createAttribute('message');
msg.nodeValue = message;
packEl.attributes.setNamedItem(msg);
packEl.src = props.rainImgSrc;
packEl.className = 'image-normal';
packEl.style.top = '-400px';
packHeight.value = packEl.clientHeight;
containerEl.appendChild(packEl);
makeRain(packEl).then((arr) => {
resolve(arr)
});
})
}
// 红包随机参数:速度、left;top:定时器、speed
const makeRain = (el: HTMLImageElement) => {
return new Promise((resolve) => {
let packTimer: number = 0;
const left = proxy.$utils.getRandom({ min: 0, max: refMoneyCon.value.clientWidth })
const speed = proxy.$utils.getRandom({ min: 1, max: 2 })
// 初始值
el.style.left = left + 'px';
el.style.top = -el.clientHeight + 'px';
let deg: number = 0;
// left和deg的方向变量
let leftRight = left >= refMoneyCon.value.clientWidth / 2;
let clockWise = speed >= 3.5;
packTimer = window.setInterval(() => {
// 水平方向
if (leftRight) {
el.style.left = el.offsetLeft + speed + 'px';
if (el.offsetLeft >= moneyConWidth.value - el.clientWidth) leftRight = false;
} else {
el.style.left = el.offsetLeft - speed + 'px';
if (el.offsetLeft <= 0) leftRight = true;
}
// 角度
if (clockWise) {
deg += 2;
if (deg >= 42) clockWise = false;
} else {
deg -= 2;
if (deg <= -42) clockWise = true;
}
el.style.transform = 'rotate(' + deg + 'deg)'
// 垂直方向
el.style.top = el.offsetTop + speed + 'px';
if (el.offsetTop >= screenHeight.value) {
// removeEl
clearInterval(packTimer);
packTimer = 0;
const currentTimerIndex = allPackTimer.value.findIndex(item => item.timer === packTimer);
allPackTimer.value.splice(currentTimerIndex, 1);
refMoneyCon.value.removeChild(el);
resolve(allPackTimer.value);
};
}, 20);
// 保存该定时器
allPackTimer.value.push({
timer: packTimer,
id: el.getAttribute('message')
});
})
}
onBeforeUnmount(() => {
removeAllTimer();
})
</script>
<template>
<div v-if="moneyConShow" ref="refMoneyCon">
</div>
</template>
<style lang="scss" scoped>
:deep().image-normal {
width: 60px;
position: fixed;
border-radius: 4px;
}
</style>
使用页面代码:
<template>
<money-rain v-model:rain="moneyShow" @sendPack="handlePack"></money-rain>
</template>
<script setup lang="ts">
const moneyShow = ref(false);
const rainMoney = () => {
moneyShow.value = true;
}
const handlePack = (e: any) => {
console.group('e --------------------', e)
setTimeout(() => {
alert('走喽~~')
moneyShow.value = false;
}, 2000)
}
</script>
~~tada一个红包雨组件就完成啦
实不相瞒。。。能用是一定能用,有没有坑就不知道了。。麻烦看到坑的您记得告我一下子,我改!!