主页 > 其他  > 

vue3开发打年兽功能

vue3开发打年兽功能

1.效果

WeChat_20250217192041

2.代码

2.1 index.vue

<template> <div class="pages"> <TopNavigationY leftTitle="打年兽" ruleIconColor="#fff" backgroundImage="" svgIpcn="backIcon4" gradientBackgroundColor="rgba(128, 76, 104, 0.6)" topNavHeight="56px" howToPlay="newYearEvent" :backdropFilter="true" ruleIcon="ruleFFF" > <template v-slot:top_l_right> <SvgIcon class="custimage" width="34px" height="34px" name="customerIconBW" @click="openService()" /> </template> </TopNavigationY> <div class="nianBeastBox"> <transition-group name="fade"> <animation v-for="item in JSONanimations" :key="item.id" :JSONanimations="item.anim" v-show="item.id == animationId && animationId != -1" class="nianBeastImg" ></animation> <img v-show="animationId == -1" class="death" :src="useImageUrl('newYearEvent', `criticalStrike`, 'develop')" alt="" /> </transition-group> <shell ref="shellRef" class="shell" /> <div class="nianBeast"> <!-- 动态渲染伤害值 --> <div v-for="item in data.elementsHtml" :key="item.id" class="damageValueNum" v-show="data.elementsHtml" > <div class="tex" v-show="!item.status" > <div class="text1">-</div> <div class="text2">-</div> <div class="text3">-</div> </div> <img v-show="item.status" class="img" :src="useImageUrl('newYearEvent', `criticalStrike`, 'develop')" alt="" /> <div class="num"> <div class="text1">{{ item.num }}</div> <div class="text2">{{ item.num }}</div> <div class="text3">{{ item.num }}</div> </div> </div> <img v-show="data.lastStrikesImgShow" class="lastStrike" :src="useImageUrl('newYearEvent', `lastStrikes`, 'develop')" alt="" /> <div class="Progress"> <van-progress :percentage="data.progressNum" :show-pivot="false" /> <div class="progress_pivot" :style="{ left: data.progressNum + '%' }" ></div> </div> <div class="countdown"> 剩余时间: <van-count-down :time="data.countdown" @finish="finishCountdown" /> </div> </div> <div class="cannon"> <img class="cannonLeft" :src="useImageUrl('newYearEvent', `barrel1`, 'develop')" alt="" /> <img class="view" :src="useImageUrl('newYearEvent', `view`, 'develop')" alt="" @click="viewYearBeast()" /> <img class="cannonRight" :src="useImageUrl('newYearEvent', `barrel2`, 'develop')" alt="" /> </div> <div class="shellBox"> <div class="shellList"> <div v-for="item in 4" :key="item" @click="selectProjectile(item)" :style="{ opacity: data.shellFrameIndex === item ? '1' : '0.6' }" > <div class="shellItem"> <img :src="useImageUrl('newYearEvent', `shell${item}`, 'develop')" alt="" /> </div> <div class="num">x 111</div> </div> </div> <van-stepper v-model="data.shellNum" :integer="true" /> </div> <div class="strikeYearBeast"> <img class="ranking" @click="goToRanking" :src="useImageUrl('newYearEvent', `ranking`, 'develop')" alt="" /> <div class="strikeYearBeastBtn" @click="clickYearBeastBtn" ></div> <div class="accumulate">{{ convertToWan(data.accumulatedDamage) }}</div> </div> </div> </div> <!-- 年兽弹窗 --> <van-overlay :show="data.nianBeastPop" z-index="1000" > <div class="popCentent"> <div class="cententBox"> <div class="cententItem" v-for="item in data.nianBeastList" :key="item.id" > <div class="time">开始时间:{{ item.startTime }}</div> <div class="time">结束时间:{{ item.endTime }}</div> <img class="img" src="@/assets/image/newYearEvent/nianBeast.png" alt="" /> <img class="btn" :src="useImageUrl('newYearEvent', `btn${item.status}`, 'develop')" alt="" /> </div> </div> <div class="cancelPop" @click="cancelYearBeast" ></div> </div> </van-overlay> <!-- 最后一击弹窗 --> <van-overlay :show="data.lastStrikeShow" z-index="1000" > <div class="lastStrikeCentent"> <div class="box"> <text-show :direction="'center'" :text="data.jewelry.name" text-id="444" > <div class="text textEllipsis">{{ data.jewelry.name }}</div> </text-show> <img class="img" src=" img.zbt /e/steam/item/730/UDkwIHwgQXNpaW1vdiAoRmFjdG9yeSBOZXcp.png" alt="" /> <div class="price"> <img class="coinImg" :src="useImageUrl('base', 'conch')" alt="" /> {{ data.jewelry.price }} </div> </div> <div class="cancelPop" @click="cancelYearBeast" ></div> </div> </van-overlay> </template> <script setup lang="ts"> import { onMounted, onUnmounted, reactive, ref, watch } from "vue" import { getTextByCode, viewTextByCode } from "@/api/base" import { showIntroduce } from "@/utils/Introduce" import { navigateTo } from "@/utils/route" // import { useImageUrl,joinImgPrefix } from "@/utils/index" import { useImageUrl, convertToWan } from "@/utils/index" import animation from "@/components/animation/index.vue" import nianBeast1 from "@/assets/json/nianBeast1.json" import nianBeast2 from "@/assets/json/nianBeast2.json" import shell from "./animation/shell.vue" import { openService } from "@/utils/userStore" import config from "@/config" const data = reactive({ countdown: 5000000, // 年兽倒计时 shellNum: 1, // 炮弹数量 progressNum: 50, // 进度条 elementsHtml: [], shellFrameIndex: 1, nianBeastPop: false, // 年兽弹窗 nianBeastList: [ { id: 1, startTime: "2025.12.26 12:52:24", endTime: "2025.12.26 12:52:24", status: 2 }, { id: 2, startTime: "2025.12.26 12:52:24", endTime: "2025.12.26 12:52:24", status: 1 }, { id: 3, startTime: "2025.12.26 12:52:24", endTime: "2025.12.26 12:52:24", status: 3 }, { id: 1, startTime: "2025.12.26 12:52:24", endTime: "2025.12.26 12:52:24", status: 2 } ], lastStrikeShow: false, // 最后一击弹窗 lastStrikesImgShow: false, // 最后一击图片 jewelry: { price: 11111, name: "额温额温额温额温额温额温额温额温额温额温" }, accumulatedDamage: "2222222" // 累计伤害 }) // 查看年兽弹窗 const viewYearBeast = () => { data.nianBeastPop = true console.log(`output-11111`, 11111) } // 关闭查看年兽弹窗 const cancelYearBeast = () => { data.nianBeastPop = false data.lastStrikeShow = false } // 选择炮弹 const selectProjectile = val => { data.shellFrameIndex = val data.shellNum = 1 } // 去排名 const goToRanking = () => { navigateTo({ name: "newYearRanking" }) } // 打年兽 const shellRef = ref(null) const clickYearBeastBtn = () => { shellRef.value.createAnimation() barrelAnimation() shellDamageValue() } // 炮弹动画 const barrelAnimation = () => { const animateElement = (element, scale, origin, duration) => { // 设置动画样式 element.style.transform = `scaleX(${scale})` element.style.transition = `transform ${duration}ms ease` element.style.transformOrigin = origin // 动画复原 setTimeout(() => { element.style.transform = "scaleX(1)" }, duration) } // 获取左侧炮管并执行动画 const left = document.querySelector(".cannonLeft") animateElement(left, 0.8, "left center", 200) // 获取右侧炮管并执行动画 const right = document.querySelector(".cannonRight") animateElement(right, 0.8, "right center", 200) config.cannonAudio.currentTime = 0.2 // 设置从第 1 秒开始播放 config.cannonAudio.play() // 播放音效 } // 炮弹伤害值 const shellDamageValue = () => { const obj = { num: Math.floor(Math.random() * 10), status: true } data.elementsHtml.push(obj) } // 玩法规则是否第一次弹出 const showRule = async () => { const res = await getTextByCode("newYearEvent") if (res.data.show) { showIntroduce("newYearEvent") await viewTextByCode("newYearEvent") } } // 音效播放函数 const playSound = () => { config.newYearAudio.volume = 0.2 // 设置音量 config.newYearAudio.loop = true // 设置循环播放 // 播放音效 config.newYearAudio .play() .then(() => { console.log("音效播放成功") }) .catch(error => { console.error("音效播放失败:", error) }) } // 音效停止函数 const stopSound = () => { if (config.newYearAudio) { config.newYearAudio.pause() // 暂停播放 config.newYearAudio.currentTime = 0 // 重置播放时间 } } // 获取年兽动画 const JSONanimations = ref<any>([ { id: 1, anim: nianBeast1 }, { id: 2, anim: nianBeast2 }, { id: 3, anim: nianBeast2 }, { id: 4, anim: nianBeast2 } ]) const animationId = ref(1) const finishCountdown = () => { if (data.progressNum <= 0) return data.nianBeastList.map(item => { if (item.status == 1) { animationId.value = item.id } }) } watch( () => data.progressNum, val => { if (val <= 0) { animationId.value = -1 data.lastStrikesImgShow = true } }, { immediate: true } ) onMounted(async () => { await playSound() await showRule() }) onUnmounted(() => { stopSound() }) </script> <style lang="scss" scoped> :deep(.top_navigation) { position: fixed; .top_l { top: 56px !important; transform: translateY(-50%); margin-left: 8px; display: flex; align-items: center; .custimage { transform: translateY(1px); margin-left: 23px; } } } .pages { width: 750px; min-height: 1624px; background: #490205; overflow: hidden; .nianBeastBox { width: 750px; height: 1624px; background: url($yjnewYearEventBg) no-repeat bottom; background-size: 100%; position: relative; .fade-enter-active, .fade-leave-active { transition: opacity 0.5s ease; } .fade-enter, .fade-leave-to { opacity: 0; } .nianBeastImg { position: absolute; top: 10px; } .death { position: absolute; left: 50%; transform: translateX(-50%); top: 570px; width: 400px; height: 400px; } .nianBeast { width: 750px; height: 1050px; display: grid; place-items: center; position: absolute; @keyframes float { 0% { transform: translateY(0); opacity: 1; } 100% { transform: translateY(-30px); opacity: 0; } } .damageValueNum { font-size: 56px; font-weight: bold; display: flex; justify-content: center; align-items: center; position: absolute; font-family: YouSheBiaoTiHei; animation: float 1.7s ease forwards; top: 500px; left: 50%; transform: translateY(-50%); .tex { position: absolute; left: -50px; } .img { width: 222px; height: 107px; margin-top: 60px; margin-right: 10px; margin-left: -150px; } .text1 { font-size: 56px; text-shadow: -2px -2px 0 #ffd700, 2px -2px 0 #ffd700, -2px 2px 0 #ffd700, 2px 2px 0 #ffd700; position: absolute; top: 80px; -webkit-text-stroke: 20px #ffd700; letter-spacing: 6px; } .text2 { font-size: 56px; text-shadow: -2px -2px 0 #8b0000, 2px -2px 0 #8b0000, -2px 2px 0 #8b0000, 2px 2px 0 #8b0000; -webkit-text-stroke: 16px #8b0000; position: absolute; top: 80px; letter-spacing: 5px; } .text3 { color: #fff; position: absolute; top: 80px; letter-spacing: 5px; } } .lastStrike { width: 600px; height: 216.846px; background: url($yjprogressHead) no-repeat center; background-size: 100%; position: absolute; top: 700px; animation: float 2.5s ease forwards; } .Progress { position: absolute; top: 820px; left: -40px; width: 312px; transform: rotate(270deg); .progress_pivot { width: 100px; height: 56px; transform: rotate(90deg) !important; background: url($yjprogressHead) no-repeat bottom; background-size: 100%; position: absolute; top: -16px; opacity: 1 !important; margin-left: -46px; filter: brightness(2); } :deep(.van-progress) { height: 32px; border-radius: 4px; opacity: 0.9; background: rgba(0, 0, 0, 0.5); box-shadow: 0px 4px 2px 0px rgba(0, 0, 0, 0.25) inset; display: flex; align-items: center; } :deep(.van-progress__portion) { border-radius: 2px; height: 24px; border-top: 4px solid #ffa45a; border-right: 4px solid #ffa45a; border-bottom: 4px solid #ffa45a; background: linear-gradient(180deg, #ffa45a 0%, #e24129 100%); margin-left: 3px; } } .countdown { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 320px; height: 60px; background: rgba(73, 2, 4, 0.8); display: flex; justify-content: center; align-items: center; color: #fff; font-family: "AP700"; font-size: 32px; .van-count-down { color: #fff; font-family: "AP700"; font-size: 32px; } } } } .cannon { width: 750px; height: 170px; margin-top: 50px; display: flex; justify-content: space-between; align-items: center; position: absolute; top: 1045px; z-index: 1; .view { width: 300px; height: 80px; } .cannonLeft { width: 148px; height: 170px; } .cannonRight { @extend .cannonLeft; } } .shellBox { position: absolute; top: 1255px; left: 50%; transform: translateX(-50%); text-align: center; .shellList { width: 460px; margin: 0 auto; display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 20px; .shellItem { width: 100px; height: 100px; background: url($yjnewYearlastshellFrame) no-repeat bottom; background-size: 100%; img { width: 73px; height: 67px; margin-left: 13px; margin-top: 15px; } } .num { color: #f5b142; text-align: center; font-family: "AP500"; font-size: 20px; margin-top: 4px; } } } .strikeYearBeast { display: flex; justify-content: center; position: absolute; top: 1485px; left: 50%; transform: translateX(-50%); .ranking { width: 102px; height: 102px; } .strikeYearBeastBtn { width: 360px; height: 106px; margin: 0 26px; background: url($yjnewYearstrikeYearBeastBtn) no-repeat bottom; background-size: 100%; &:active { background: url($yjnewYearstrikeYearBeastBtnA) no-repeat bottom; background-size: 100%; } } .accumulate { width: 142px; height: 102px; background: url($yjnewYearaccumulate) no-repeat bottom; background-size: 100%; color: #ffc337; text-align: center; font-family: "AP700"; font-size: 36px; } } } .popCentent { width: 688px; height: 903px; background: url($yjnewYearviewPop) no-repeat bottom; background-size: 100%; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); .cententBox { display: grid; /* 外层容器 */ grid-template-columns: repeat(2, 1fr); gap: 20px; padding: 0 74px; margin-top: 200px; .cententItem { color: #fff7c4; font-family: "AP500"; font-size: 14px; width: 254px; height: 254px; text-align: center; padding: 12px; .time { line-height: 20px; } .img { width: 139px; height: 139px; } .btn { width: 156px; height: 48px; } } } } .cancelPop { width: 50px; height: 50px; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); } .lastStrikeCentent { width: 688px; height: 898px; background: url($yjnewYearlastStrike) no-repeat bottom; background-size: 100%; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); .box { position: absolute; left: 50%; top: 270px; transform: translateX(-50%); } .text { width: 240px; color: #fad16c; font-family: "AP600"; font-size: 22px; text-align: center; padding: 0 10px; } .img { width: 241px; height: 159px; transform: rotate(20deg); margin-top: 40px; margin-left: -8px; } .price { @extend .text; display: flex; justify-content: center; margin-top: 33px; img { width: 26px; height: 26px; transform: translateY(-2px); } } } :deep(.van-stepper__minus) { background: rgba(0, 0, 0, 0); color: #fff; font-family: "AP700"; font-size: 32px; } :deep(.van-stepper__minus--disabled) { color: #ccc; /* 设置为变暗的颜色 */ cursor: not-allowed; /* 修改鼠标样式 */ opacity: 0.5; /* 设置透明度 */ } /* 覆盖减号图标 */ :deep(.van-stepper__minus:before) { width: 0px; height: 0px; content: "-"; transform: translateY(-15px); } :deep(.van-stepper__input) { width: 64px; height: 40px; border-radius: 2px; color: #fff; font-family: "AP700"; font-size: 28px; background: rgba(0, 0, 0, 0); border: 2px solid #fcc651; } :deep(.van-stepper__plus) { background: rgba(0, 0, 0, 0); color: #fff; font-family: "AP700"; font-size: 32px; transform: translate(-15px, -15px); } // /* 覆盖加号图标 */ :deep(.van-stepper__plus:before) { width: 0px; height: 0px; content: "+"; } :deep(.van-stepper__plus:after) { width: 0px; height: 0px; } </style>

2.2 newYearRanking.vue

<template> <div class="allPages"> <TopNavigationY title="排行榜" /> <img class="title" :src="useImageUrl('newYearEvent', `rankingTitle`, 'develop')" alt="" /> <swiper class="mySwiper" :slides-per-view="3" :space-between="10" > <swiper-slide :class="['titleItem', { active: item == activeIndex }]" v-for="item in 4" :key="item" @click="handleToggle(item)" > 年兽{{ item }}号 </swiper-slide> </swiper> <div class="list"> <div v-for="item in rankingInfo" :key="item.id" class="rankingInfo" > <img v-if="item.id <= 3" class="img" :src="useImageUrl('newYearEvent', `rank${item.id}`, 'develop')" alt="" /> <div v-if="item.id > 3" class="imgs" > {{ item.id }} </div> <img class="profilePicture" :src="useImageUrl('newYearEvent', `rank3`, 'develop')" alt="" /> <text-show :direction="'center'" :text="ProhibitedWords(item.name)" :text-id="item.id" > <div class="name textEllipsis">{{ ProhibitedWords(item.name) }}</div> </text-show> <div class="damageValue"> 伤害值: <img class="shellIcon" :src="useImageUrl('newYearEvent', `shellIcon`, 'develop')" alt="" /> x{{ convertToWan(item.damageValue) }} </div> </div> <touchGround class="touchGround" :total="pageData.total" :size="pageData.size" :currentpage="pageData.current" @touchGroundFun="touchGroundFun" ></touchGround> </div> <div class="personalInfo"> <div class="userName"> <img class="userIcon" :src="useImageUrl('newYearEvent', `shellIcon`, 'develop')" alt="" /> <text-show :direction="'center'" :text="myInfo.name" :text-id="3 + myInfo.ranking" > <div class="name textEllipsis">{{ myInfo.name }}</div> </text-show> </div> <div class="ranking">排名: {{ myInfo.ranking }}</div> <div class="damageValueC"> 伤害值: <img class="shellIcon" :src="useImageUrl('newYearEvent', `shellIcon`, 'develop')" alt="" /> {{ convertToWan(myInfo.damageValue) }} </div> </div> </div> </template> <script setup lang="ts"> import { ref } from "vue" import { useImageUrl,ProhibitedWords,convertToWan } from "@/utils/index" import "swiper/css" import { Swiper, SwiperSlide } from "swiper/vue" const rankingInfo = ref([ { id: 1, name: "111111", damageValue: "222" }, { id: 2, name: "小仙女", damageValue: "222" }, { id: 3, name: "111111", damageValue: "222" }, { id: 4, name: "111111", damageValue: "222" }, { id: 5, name: "11111 1", damageValue: "222" }, { id: 6, name: "111111", damageValue: "222" }, { id: 1, name: "111111", damageValue: "222" }, { id: 2, name: "111111", damageValue: "222" }, { id: 3, name: "111111", damageValue: "222" }, { id: 4, name: "111111", damageValue: "222" }, { id: 5, name: "11111 1", damageValue: "222" }, { id: 6, name: "111111", damageValue: "222" } ]) const pageData = ref({ current: 1, size: 15, total: 1 }) const myInfo = ref({ name: "eee", ranking: 111111, damageValue: "331133" }) const activeIndex = ref(1) // 选择年兽 const handleToggle = val => { activeIndex.value = val } const getlistData = async () => {} // 触底加载 const touchGroundFun = () => { pageData.value.size += 15 getlistData() } </script> <style lang="scss" scoped> .allPages { width: 750px; height: 1624px; background: url($yjnewYeartheChartsBg) no-repeat center; background-size: 100% 100%; .title { width: 570px; height: 137px; margin: 30px 90px 20px; } .mySwiper { height: 64px; margin: 0 26px 50px; .titleItem { width: 186px; height: 64px; background: url($yjnewYearcheckedState) no-repeat center; background-size: 100% 100%; color: #bc2811; text-align: center; font-family: "AP600"; font-size: 32px; line-height: 64px; &.active { background-image: url($yjnewYearcheckedStates); background-size: 100% 100%; color: #ffe7bb; } } } .list { height: 1100px; overflow: auto; padding-bottom: 50px; .rankingInfo { width: 694px; height: 120px; background: url($yjnewYearrankingBg) no-repeat center; background-size: 100% 100%; margin: 0 auto 24px; display: flex; align-items: center; .img { width: 64px; height: 64px; margin-left: 42px; } .imgs { @extend .img; background: url($yjnewYearrank4) no-repeat center; background-size: 100% 100%; color: #f1bc7a; text-align: center; font-family: "AP600"; font-size: 34px; line-height: 66px; } .profilePicture { width: 50px; height: 48px; border-radius: 50%; background: lightgray 50% / cover no-repeat; flex-shrink: 0; margin-left: 32px; } .text { color: #ffe7bb; font-family: "AP500"; font-size: 28px; } .name { @extend .text; width: 200px; margin: 0 18px; } .damageValue { @extend .text; float: right; margin-top: -10px; .shellIcon { width: 38px; height: 38px; transform: translateY(5px); } } } } .personalInfo { width: 750px; height: 108px; background: url($yjnewYearpersonalInfoBg) no-repeat center; background-size: 100% 100%; position: fixed; bottom: 0px; display: flex; align-items: center; .text { color: #fff7c4; font-family: "AP400"; font-size: 28px; } .userName { width: 210px; display: flex; align-items: center; justify-content: center; .userIcon { width: 50px; height: 48px; flex-shrink: 0; margin-right: 10px; } .name { max-width: 130px; transform: translateY(3px); } } .ranking { @extend .text; width: 180px; margin-left: 54px; transform: translateY(4px); } .damageValueC { @extend .text; transform: translateY(-4px); position: absolute; right: 30px; .shellIcon { width: 38px; height: 38px; transform: translateY(4px); } } } } </style>

2.3 animation/shell.vue 设置炮弹

<template> <div> <div v-for="(animationData, index) in animations" :key="index" class="animation-container" ></div> </div> </template> <script setup lang="ts"> import { ref, onBeforeUnmount } from "vue" import lottie from "lottie-web" import JSONanimations from "@/assets/json/shell.json" // 响应式数据,用于跟踪动画实例 const animations = ref<{ id: number; container: HTMLElement }[]>([]) const animationInstances = ref<any>([]) // 存储 Lottie 动画实例 // 创建动画实例 const createAnimation = () => { const container = document.createElement("div") container.className = "animation-container" document.body.appendChild(container) const animation = lottie.loadAnimation({ container, renderer: "svg", loop: true, autoplay: true, animationData: JSONanimations }) animationInstances.value.push(animation) animations.value.push({ id: animationInstances.value.length - 1, container }) // 设置定时器,3秒后删除动画 setTimeout(() => { removeAnimation(animation, container) }, 500) } // 移除动画实例和 DOM 元素 const removeAnimation = (animationToRemove, container) => { const index = animationInstances.value.indexOf(animationToRemove) if (index > -1) { animationToRemove.destroy() // 销毁动画实例 animationInstances.value.splice(index, 1) // 从数组中移除动画实例 animations.value.splice(index, 1) // 从响应式数据中移除 container.remove() // 从 DOM 中移除容器元素 } } // 在组件销毁前清理动画和 DOM 元素 onBeforeUnmount(() => { animationInstances.value.forEach((animation, index) => { animation.destroy() // 销毁动画实例 animations.value[index]?.container.remove() // 从 DOM 中移除容器元素 }) animationInstances.value.length = 0 // 清空实例数组 animations.value = [] // 清空响应式数据 }) // 暴露方法给父组件 defineExpose({ createAnimation }) </script> <style> .animation-container { width: 750px; height: 1435px; position: absolute; top: 0; } </style>

标签:

vue3开发打年兽功能由讯客互联其他栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“vue3开发打年兽功能