美文网首页
Vue3 & Typescript组件:红包雨

Vue3 & Typescript组件:红包雨

作者: 牛会骑自行车 | 来源:发表于2023-05-14 12:16 被阅读0次

效果:

  1. 点击按钮下红包雨
  2. 每个红包就是这么晃呀晃
  3. 速度可调节、红包雨图片可调节、选中红包后换的图片可调节。。

效果图:

思路:

  • 给所有的红包设置一个容器,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一个红包雨组件就完成啦

实不相瞒。。。能用是一定能用,有没有坑就不知道了。。麻烦看到坑的您记得告我一下子,我改!!

相关文章

网友评论

      本文标题:Vue3 & Typescript组件:红包雨

      本文链接:https://www.haomeiwen.com/subject/dvkpsdtx.html