当前位置: 首页 > news >正文

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="https://img.zbt.com/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: "11111wwwwwwwww1",
    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: "11111wwwwwwwww1",
    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>

相关文章:

  • 【论文笔记】On Generative Agents in Recommendation
  • DeepSeek 本地部署方法介绍
  • 鸡兔同笼问题
  • 20.【线性代数】——坐标系中,平行四边形面积=矩阵的行列式
  • ES快照备份索引数据(已亲测)
  • 数据恢复-01-机械硬盘的物理与逻辑结构
  • 【C语言】第二期——运算符与表达式
  • PMBOK第7版整体架构全面详解
  • AI芯片:科技变革的核心驱动力
  • QT (四)模型/视图 QFileSystemModel,QStringListModel,QStandardItemModel
  • 【生产变更】- 集群中配置SCAN ip的不同端口应用
  • 2步破解官方sublime4最新版本 4192
  • 基于SpringBoot的小区运动中心预约管理系统
  • Redis可视化连接工具RedisDesktopManager的下载与安装
  • 前端:最简单封装nmp插件(组件)过程。(待完善)
  • Spring Bean 生命周期的执行流程
  • shell脚本备份PostgreSQL数据库和库下表
  • 信息安全管理(3):网络安全
  • 基于Matlab实现信道估计仿真(源码)
  • YOLOV8的学习记录(一) 环境配置和安装
  • 六省会共建交通枢纽集群,中部离经济“第五极”有多远?
  • 梅花奖在上海|舞剧《朱鹮》,剧里剧外都是生命的赞歌
  • 农行回应“病重老人被要求亲自取钱在银行去世”:全力配合公安机关调查
  • 蚊媒传染病、手足口病……上海疾控发布近期防病提示
  • 中哥两国元首共同见证签署《中华人民共和国政府与哥伦比亚共和国政府关于共同推进丝绸之路经济带和21世纪海上丝绸之路建设的合作规划》
  • 巴方:印度上周导弹袭击造成至少40名平民死亡