Vue3 & Typescript组件:红包雨

效果:

  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一个红包雨组件就完成啦

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345