彩带飘落效果

彩带效果

彩带特效组件

适应场景

完成小结、版本升级等场景。提供HTM、Vue3版本。

HTML版本

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>彩带效果</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
        background-color: #f0f0f0;
        font-family: 'Arial', sans-serif;
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100vh;
      }

      #confetti-container {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        pointer-events: none;
        z-index: 10;
      }

      .message-container {
        background: rgba(255, 255, 255, 0.8);
        padding: 20px;
        border-radius: 8px;
        text-align: center;
        z-index: 20;
      }

      .message {
        font-size: 2rem;
        font-weight: bold;
        margin-bottom: 20px;
      }

      button {
        padding: 12px 24px;
        background-color: #4caf50;
        color: white;
        border: none;
        border-radius: 4px;
        font-size: 18px;
        cursor: pointer;
      }

      button:hover {
        background-color: #45a049;
      }

      .confetti {
        position: absolute;
        width: 10px;
        height: 20px;
      }
    </style>
  </head>
  <body>
    <div id="confetti-container"></div>

    <div class="message-container">
      <div class="message">🎉 新功能上线啦!</div>
      <button id="confetti-button">再来一次</button>
    </div>

    <script>
      // 容器
      document.getElementById('confetti-button').onclick = createConfetti

      // 页面加载后自动触发一次彩带效果
      window.onload = function () {
        setTimeout(createConfetti, 500)
      }

      function createConfetti() {
        // 清除现有彩带
        const container = document.getElementById('confetti-container')
        container.innerHTML = ''

        // 彩带颜色
        const colors = [
          '#f44336',
          '#e91e63',
          '#9c27b0',
          '#673ab7',
          '#3f51b5',
          '#2196f3',
          '#03a9f4',
          '#00bcd4',
          '#009688',
          '#4CAF50',
          '#8BC34A',
          '#CDDC39',
          '#FFEB3B',
          '#FFC107',
          '#FF9800',
          '#FF5722',
        ]

        // 物理参数 - 烟花效果
        const gravity = 0.25 // 重力加速度 - 增强
        const initialVelocity = 20 // 基础初始速度 - 显著提高
        const velocityVariation = 8 // 速度变化幅度
        const dragCoefficient = 0.98 // 阻力系数 - 稍微增加以模拟空气阻力

        // 创建彩带 - 增加数量以创造更密集的效果
        for (let i = 0; i < 200; i++) {
          setTimeout(function () {
            const confetti = document.createElement('div')
            confetti.className = 'confetti'

            // 随机彩带特性
            const color = colors[Math.floor(Math.random() * colors.length)]
            const shape =
              Math.random() < 0.33 ? 'circle' : Math.random() < 0.66 ? 'rectangle' : 'triangle'
            const size = Math.random() * 10 + 5

            // 从两侧喷出 - 随机选择左侧或右侧
            const side = Math.random() < 0.5 ? 'left' : 'right'
            const xPos = side === 'left' ? 0 : window.innerWidth
            const yPos = window.innerHeight * 0.8 + Math.random() * window.innerHeight * 0.2 // 更靠近底部发射

            // 角度设置 - 更多向上的角度,像烟花发射
            let angle
            if (side === 'left') {
              angle = -Math.PI / 2 + (Math.random() * Math.PI) / 4 // -90度到-45度(强烈向上偏右)
            } else {
              angle = (Math.PI * 3) / 2 - (Math.random() * Math.PI) / 4 // 225度到270度(强烈向上偏左)
            }

            // 初始速度 - 更高的初速度模拟烟花发射
            const velocity = initialVelocity + Math.random() * velocityVariation

            // 设置初始位置和样式
            confetti.style.left = xPos + 'px'
            confetti.style.top = yPos + 'px'
            confetti.style.width = size + 'px'
            confetti.style.height = size + 'px'
            confetti.style.backgroundColor = color
            confetti.style.transform = 'rotate(' + Math.random() * 360 + 'deg)'

            // 设置不同形状
            if (shape === 'circle') {
              confetti.style.borderRadius = '50%'
            } else if (shape === 'triangle') {
              confetti.style.width = '0'
              confetti.style.height = '0'
              confetti.style.backgroundColor = 'transparent'
              confetti.style.borderLeft = size / 2 + 'px solid transparent'
              confetti.style.borderRight = size / 2 + 'px solid transparent'
              confetti.style.borderBottom = size + 'px solid ' + color
            }

            container.appendChild(confetti)

            // 动画参数
            let xVelocity = Math.cos(angle) * velocity
            let yVelocity = Math.sin(angle) * velocity
            const rotateVel = Math.random() * 0.2 - 0.1
            let rotation = Math.random() * 360

            // 时间跟踪(毫秒)
            let time = 0
            const initialBurstDuration = 500 // 初始高速喷射持续500毫秒
            let lastTimestamp = performance.now()

            // 动画函数
            function animate(timestamp) {
              // 计算时间差
              const deltaTime = timestamp - lastTimestamp
              lastTimestamp = timestamp
              time += deltaTime

              // 应用物理效果
              if (time < initialBurstDuration) {
                // 初始爆发阶段 - 保持高速,稍微减速
                yVelocity *= 0.99
                xVelocity *= 0.99
              } else {
                // 自由落体阶段
                yVelocity += gravity
                xVelocity *= dragCoefficient
              }

              // 更新位置
              const currentX = parseFloat(confetti.style.left)
              const currentY = parseFloat(confetti.style.top)
              confetti.style.left = currentX + xVelocity + 'px'
              confetti.style.top = currentY + yVelocity + 'px'

              // 旋转彩带
              rotation += rotateVel
              confetti.style.transform = 'rotate(' + rotation + 'deg)'

              // 超出屏幕移除彩带
              if (
                currentY < window.innerHeight + 100 &&
                currentX > -100 &&
                currentX < window.innerWidth + 100
              ) {
                requestAnimationFrame(animate)
              } else {
                confetti.remove()
              }
            }

            // 启动动画
            requestAnimationFrame(animate)
          }, Math.random() * 800) // 缩短发射间隔,使效果更集中
        }
      }
    </script>
  </body>
</html>

Vue3版本

<template>
  <div class="confetti-container" ref="confettiContainer"></div>
  <div class="message-container">
    <div class="message">🎉 新功能上线啦!</div>
    <button @click="createConfetti">再来一次</button>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'

// confetti容器的引用
const confettiContainer = ref(null)

// 组件挂载时触发confetti效果
onMounted(() => {
  setTimeout(createConfetti, 500)
})

function createConfetti() {
  // 清除现有的confetti
  if (confettiContainer.value) {
    confettiContainer.value.innerHTML = ''
  }

  // confetti的颜色数组
  const colors = [
    '#f44336',
    '#e91e63',
    '#9c27b0',
    '#673ab7',
    '#3f51b5',
    '#2196f3',
    '#03a9f4',
    '#00bcd4',
    '#009688',
    '#4CAF50',
    '#8BC34A',
    '#CDDC39',
    '#FFEB3B',
    '#FFC107',
    '#FF9800',
    '#FF5722',
  ]

  // 物理参数
  const gravity = 0.25 // 重力
  const initialVelocity = 20 // 初始速度
  const velocityVariation = 8 // 速度变化范围
  const dragCoefficient = 0.98 // 阻力系数

  // 创建confetti
  for (let i = 0; i < 200; i++) {
    setTimeout(() => {
      const confetti = document.createElement('div')
      confetti.className = 'confetti'

      // 随机confetti属性
      const color = colors[Math.floor(Math.random() * colors.length)]
      const shape =
        Math.random() < 0.33 ? 'circle' : Math.random() < 0.66 ? 'rectangle' : 'triangle'
      const size = Math.random() * 10 + 5

      // 从两侧发射
      const side = Math.random() < 0.5 ? 'left' : 'right'
      const xPos = side === 'left' ? 0 : window.innerWidth - size
      const yPos = window.innerHeight * 0.8 + Math.random() * window.innerHeight * 0.2

      // 烟花般发射的角度
      let angle
      if (side === 'left') {
        angle = -Math.PI / 2 + (Math.random() * Math.PI) / 4 // -90° 到 -45°
      } else {
        angle = (Math.PI * 3) / 2 - (Math.random() * Math.PI) / 4 // 225° 到 270°
      }

      // 初始速度
      const velocity = initialVelocity + Math.random() * velocityVariation

      // 设置初始位置和样式
      confetti.style.position = 'absolute'
      confetti.style.left = xPos + 'px'
      confetti.style.top = yPos + 'px'
      confetti.style.width = size + 'px'
      confetti.style.height = size + 'px'
      confetti.style.backgroundColor = color
      confetti.style.transform = `rotate(${Math.random() * 360}deg)`

      // 设置形状样式
      if (shape === 'circle') {
        confetti.style.borderRadius = '50%'
      } else if (shape === 'triangle') {
        confetti.style.width = '0'
        confetti.style.height = '0'
        confetti.style.backgroundColor = 'transparent'
        confetti.style.borderLeft = `${size / 2}px solid transparent`
        confetti.style.borderRight = `${size / 2}px solid transparent`
        confetti.style.borderBottom = `${size}px solid ${color}`
      }

      confettiContainer.value.appendChild(confetti)

      // 动画参数
      let xVelocity = Math.cos(angle) * velocity
      let yVelocity = Math.sin(angle) * velocity
      const rotateVel = Math.random() * 0.2 - 0.1
      let rotation = Math.random() * 360

      // 时间追踪
      let time = 0
      const initialBurstDuration = 500
      let lastTimestamp = performance.now()

      // 动画函数
      function animate(timestamp) {
        const deltaTime = (timestamp - lastTimestamp) / 1000 // 转换为秒
        lastTimestamp = timestamp
        time += deltaTime * 1000 // 以毫秒为单位追踪时间

        // 应用物理效果
        if (time < initialBurstDuration) {
          // 初始爆发阶段
          yVelocity *= Math.pow(0.99, deltaTime * 120) // 帧率调整后的阻力
          xVelocity *= Math.pow(0.99, deltaTime * 120)
        } else {
          // 自由落体阶段
          yVelocity += gravity * deltaTime * 60 // 应用重力
          xVelocity *= Math.pow(dragCoefficient, deltaTime * 60) // 应用阻力
        }

        // 更新位置
        const currentX = parseFloat(confetti.style.left || '0')
        const currentY = parseFloat(confetti.style.top || '0')
        confetti.style.left = currentX + xVelocity * deltaTime * 60 + 'px'
        confetti.style.top = currentY + yVelocity * deltaTime * 60 + 'px'

        // 旋转confetti
        rotation += rotateVel * deltaTime * 60
        confetti.style.transform = `rotate(${rotation}deg)`

        // 仅当confetti落到屏幕下方或超出两侧时移除
        if (
          currentY < window.innerHeight + 100 &&
          currentX > -100 &&
          currentX < window.innerWidth + 100
        ) {
          requestAnimationFrame(animate)
        } else if (currentY >= window.innerHeight + 100) {
          confetti.remove() // 仅当落到下方时移除
        } else {
          requestAnimationFrame(animate) // 如果在上方或两侧则继续
        }
      }

      requestAnimationFrame(animate)
    }, Math.random() * 800)
  }
}
</script>

<style scoped>
:deep(html),
:deep(body) {
  margin: 0;
  overflow: hidden;
  background-color: #f0f0f0;
  font-family: 'Arial', sans-serif;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  width: 100vw;
}

.confetti-container {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  z-index: 10;
}

.message-container {
  /* background: rgba(255, 255, 255, 0.8); */
  padding: 20px;
  border-radius: 8px;
  text-align: center;
  z-index: 20;
  padding-top: 17%;
}

.message {
  font-size: 2rem;
  font-weight: bold;
  margin-bottom: 20px;
  color: #fff;
}

button {
  padding: 12px 24px;
  background-color: #4caf50;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 18px;
  cursor: pointer;
}

button:hover {
  background-color: #45a049;
}

.confetti {
  position: absolute;
  width: 10px;
  height: 20px;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@才华有限公司

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值