彩带飘落效果
文章目录
- 彩带效果
- 适应场景
- HTML版本
- Vue3版本
彩带效果
彩带特效组件
适应场景
完成小结、版本升级等场景。提供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.innerWidthconst yPos = window.innerHeight * 0.8 + Math.random() * window.innerHeight * 0.2 // 更靠近底部发射// 角度设置 - 更多向上的角度,像烟花发射let angleif (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 = colorconfetti.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) * velocitylet yVelocity = Math.sin(angle) * velocityconst rotateVel = Math.random() * 0.2 - 0.1let rotation = Math.random() * 360// 时间跟踪(毫秒)let time = 0const initialBurstDuration = 500 // 初始高速喷射持续500毫秒let lastTimestamp = performance.now()// 动画函数function animate(timestamp) {// 计算时间差const deltaTime = timestamp - lastTimestamplastTimestamp = timestamptime += deltaTime// 应用物理效果if (time < initialBurstDuration) {// 初始爆发阶段 - 保持高速,稍微减速yVelocity *= 0.99xVelocity *= 0.99} else {// 自由落体阶段yVelocity += gravityxVelocity *= 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 += rotateVelconfetti.style.transform = 'rotate(' + rotation + 'deg)'// 超出屏幕移除彩带if (currentY < window.innerHeight + 100 &¤tX > -100 &¤tX < 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() {// 清除现有的confettiif (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 // 阻力系数// 创建confettifor (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 - sizeconst yPos = window.innerHeight * 0.8 + Math.random() * window.innerHeight * 0.2// 烟花般发射的角度let angleif (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 = colorconfetti.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) * velocitylet yVelocity = Math.sin(angle) * velocityconst rotateVel = Math.random() * 0.2 - 0.1let rotation = Math.random() * 360// 时间追踪let time = 0const initialBurstDuration = 500let lastTimestamp = performance.now()// 动画函数function animate(timestamp) {const deltaTime = (timestamp - lastTimestamp) / 1000 // 转换为秒lastTimestamp = timestamptime += 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'// 旋转confettirotation += rotateVel * deltaTime * 60confetti.style.transform = `rotate(${rotation}deg)`// 仅当confetti落到屏幕下方或超出两侧时移除if (currentY < window.innerHeight + 100 &¤tX > -100 &¤tX < 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>