【炫酷!在你的网页上来场烟花秀吧!】(附源码)
技术揭秘 在HTML5中,<canvas>元素为我们提供了一个强大的绘图平台,允许我们通过JavaScript进行绘画。烟花表演,本质上就是这种绘图技术的运用。以下是实现烟花效果的简要步骤: 初始化画布:设置画布尺寸,确保画布能够适应不同的屏幕大小。 定义烟花行为:通过编写JavaScript函数来定义烟花的运动轨迹、颜色和消失方式。 绘制烟花:使用路径(Path)和填充(fill)命令在画布上绘制圆形,模拟烟花的爆炸效果。 动画循环:通过requestAnimationFrame实现动画循环,不断地更新和重绘烟花的位置和状态。
【炫酷!在你的网页上来场烟花秀吧!】(附源码)
1、创建一个 index.html 复制以下代码 - <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>烟花</title>
- </head>
- <style>
- body {
- margin: 0;
- padding: 0;
- overflow: hidden;
- }
-
- .canvasBox {
- width: 100%;
- height: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- canvas {
- border: 1px solid;
- background-color: #000;
- }
- </style>
-
- <body>
- <div class="canvasBox">
- <canvas id="canvas"></canvas>
- </div>
- </body>
-
- </html>
- <script src="./index.js"></script>
- <script>
- const canvas = document.getElementById('canvas')
- const canvasWidth = document.documentElement.clientWidth || document.body.clientWidth
- const canvasHeight = document.documentElement.clientHeight || document.body.clientHeight
- const ratio = Math.max(window.devicePixelRatio, 2)
- canvas.width = canvasWidth * ratio
- canvas.height = canvasHeight * ratio
- canvas.style.width = canvasWidth + 'px'
- canvas.style.height = canvasHeight + 'px'
-
- const ctx = canvas.getContext('2d')
- ctx.scale(ratio, ratio)
-
- const getRandom = (min, max) => {
- return Math.random() * (max - min) + min
- }
-
- const drawCircle = ({ opacity = 1, x, y, radius, color }) => {
- ctx.save()
- ctx.globalAlpha = opacity
- ctx.beginPath()
- ctx.arc(x, y, radius, 0, Math.PI * 2)
- ctx.fillStyle = color
- ctx.fill()
- ctx.restore()
- }
- const deleteFromList = (list, target) => {
- const index = list.findIndex(item => {
- return item === target
- })
- list.splice(index, 1)
- }
- // 动画循环
- // 烟花列表
- const fireworkList = []
- const draw = () => {
- // 使用半透明清空画布,形成拖尾效果
- ctx.fillStyle = 'rgba(0,0,0,0.3)'
- ctx.fillRect(0, 0, canvasWidth, canvasHeight)
-
- ctx.save()
-
- // 修改坐标系
- ctx.translate(0, canvasHeight)
- ctx.scale(1, -1)
-
- const list = [...fireworkList]
- list.forEach(firework => {
- firework.update()
- if (firework.isEnd()) {
- deleteFromList(fireworkList, firework)
- }
- })
-
- ctx.restore()
-
- requestAnimationFrame(draw)
- }
- draw()
-
- // 烟花颜色列表
- const createFireworkColor = () => {
- const colorList = [
- '#ff0043',
- '#14fc56',
- '#1e7fff',
- '#e60aff',
- '#ffbf36',
- '#ffffff'
- ]
- return colorList[Math.floor(Math.random() * colorList.length)]
- }
-
- // 发射烟花
- canvas.addEventListener('click', () => {
- const firework = new Firework(
-
- {
- color: createFireworkColor()
- })
- fireworkList.push(firework)
- firework.launch()
- })
-
- </script>
复制代码2、创建一个 index.js 复制以下代码 - // 爆炸碎片类
- class ExplosiveDebris {
- constructor(opt) {
- this.firework = opt.firework
- this.x = opt.x
- this.y = opt.y
- this.color = Math.random() > 0.2 ? opt.color : '#fff'
- this.radius = opt.radius || 2
- this.angle = getRandom(0, 2 * Math.PI)
- this.speed = opt.speed || getRandom(0.1, 4)
- this.vx = Math.cos(this.angle) * this.speed
- this.vy = Math.sin(this.angle) * this.speed
- this.g = opt.g || 0.98
- this.time = getRandom(0.5, 1)
- this.startTime = 0
- // 痕迹碎片数量
- this.debrisCount = opt.debrisCount || 3
- // 是否要进行二次爆炸
- this.secondBurst = opt.secondBurst || false
- }
-
- start() {
- this.startTime = Date.now()
- }
-
- update() {
- const duration = (Date.now() - this.startTime) / 1000
- const vy = this.vy - this.g * duration
- this.x += this.vx
- this.y += vy
- const progress = duration / this.time
- let opacity = progress > 0.7 ? 1 - 1 * progress : 1
- if (opacity < 0) opacity = 0
- drawCircle({
- x: this.x,
- y: this.y,
- color: this.color,
- radius: this.radius,
- opacity: opacity
- })
- // 添加痕迹碎片
- if (this.debrisCount > 0 && Math.random() > 0.8) {
- this.debrisCount--
- this.firework.addDebris({
- x: this.x + getRandom(-2, 2),
- y: this.y + getRandom(-2, 2),
- color: this.color,
- radius: 0.5,
- g: 0.1
- })
- }
- return {
- x: this.x,
- y: this.y,
- isEnd: progress >= 1
- }
- }
- }
-
-
- // 爆炸器类
- class Explosive {
- constructor(opt) {
- this.firework = opt.firework
- this.x = opt.x
- this.y = opt.y
- this.color = opt.color
- // 爆炸碎片列表
- this.debrisList = []
- // 爆炸碎片数量
- this.debrisNum = opt.debrisNum || getRandom(50, 400)
- // 是否要二次爆炸
- this.secondBurst = opt.secondBurst || this.debrisNum <= 100
- //是否是第一次爆炸
- this.isFirstBurst = true
- }
-
- start(debrisNum, opt = {}) {
- const num = debrisNum || this.debrisNum
- opt.x = opt.x || this.x
- opt.y = opt.y || this.y
- opt.secondBurst = this.secondBurst && this.isFirstBurst
- for (let i = 0; i < num; i++) {
- const explosiveDebris = new ExplosiveDebris({
- firework: this.firework,
- color: this.color,
- ...opt
- })
- explosiveDebris.start()
- this.debrisList.push(explosiveDebris)
- }
- this.isFirstBurst = false
- }
-
- update() {
- const list = [...this.debrisList]
- list.forEach(debris => {
- const res = debris.update()
- if (res.isEnd) {
- deleteFromList(this.debrisList, debris)
- // 二次爆炸
- if (debris.secondBurst) {
- this.start(5, {
- x: res.x,
- y: res.y,
- speed: 1
- })
- }
- }
- })
- return {
- isEnd: list.length <= 0
- }
- }
- }
-
- // 痕迹碎片类
- class Debris {
- constructor(opt = {}) {
- // 颜色
- this.color = opt.color || '#fff'
- // 透明度
- this.opacity = getRandom(0.1, 0.5)
- // 半径
- this.radius = opt.radius || 1
- // 存在时间
- this.time = getRandom(0.5, 1)
- // 重力,px/s2
- this.g = opt.g || 0.98
- // 位置
- this.x = opt.x
- this.y = opt.y
- // 创建的时间
- this.startTime = 0
- }
-
- start() {
- this.startTime = Date.now()
- }
-
- update() {
- const duration = (Date.now() - this.startTime) / 1000
- this.y -= this.g * duration
- drawCircle({
- opacity: this.opacity,
- x: this.x,
- y: this.y,
- radius: this.radius,
- color: this.color
- })
- return {
- x: this.x,
- y: this.y,
- isEnd: duration > this.time
- }
- }
- }
-
-
- // 发射器类
- class Launcher {
- constructor(opt = {}) {
- // 烟花实例
- this.firework = opt.firework
- // 颜色
- this.color = opt.color
- // 初始位置
- this.x = opt.x || canvasWidth * getRandom(0.2, 0.8)
- this.y = opt.y || 0
- // 目标位置
- this.ty = canvasHeight * getRandom(0.6, 0.8)
- // 半径
- this.radius = opt.radius || getRandom(2, 5)
- // 发射的持续时间
- this.duration = opt.duration || getRandom(2000, 3500)
- // 发射时的时间
- this.startTime = 0
- }
-
- start() {
- this.startTime = Date.now()
- }
-
- easeOutCubic(t, b, c, d) {
- return c * ((t = t / d - 1) * t * t + 1) + b
- }
-
- update() {
- const x = this.x
- let y = this.easeOutCubic(
- Date.now() - this.startTime,
- this.y,
- this.ty - this.y,
- this.duration
- )
- y = Math.min(y, this.ty)
- // 透明度变小
- let opacity = 1 - 1 * (y / this.ty)
- if (opacity < 0) opacity = 0
- this.draw(x, y, opacity)
- // 添加痕迹碎片
- if (Math.random() > 0.7 && opacity >= 0.1) {
- this.firework.addDebris({
- x: x + getRandom(-2, 2), // x坐标添加一段随机量
- y
- })
- }
- return {
- x,
- y,
- isEnd: y >= this.ty //返回true代表发射结束
- }
- }
- draw(x, y, opacity) {
- // 外圆,烟花的颜色
- drawCircle({
- opacity: opacity,
- x: x,
- y: y,
- radius: this.radius,
- color: this.color
- })
- // 内圆,白色
- drawCircle({
- opacity: opacity,
- x: x,
- y: y,
- radius: this.radius / 2,
- color: '#fff'
- })
- }
- }
-
- // 烟花类
- class Firework {
- constructor(opt = {}) {
- // 颜色
- this.color = opt.color || tinycolor.random().toHexString()
- // 发射器
- this.launcher = null
- // 爆炸器
- this.explosive = null
- // 烟花状态:waiting(等待发射)、launching(发射中)、bursting(爆炸中)、end(烟花结束)
- this.status = 'waiting'
- // 痕迹碎片列表
- this.debrisList = []
- }
-
- // 发射
- launch() {
- this.launcher = new Launcher({
- firework: this,
- color: this.color
- })
- this.launcher.start()
- this.status = 'launching'
- }
-
- // 爆炸
- burst({ x, y }) {
- this.explosive = new Explosive({
- firework: this,
- x,
- y,
- color: this.color
- })
- this.explosive.start()
- }
-
- // 更新
- update() {
- if (this.status === 'launching') {
- const res = this.launcher.update()
- if (res.isEnd) {
- this.status = 'bursting'
- this.burst(res)
- }
- } else if (this.status === 'bursting') {
- const res = this.explosive.update()
- if (res.isEnd) {
- this.status = 'end'
- }
- }
- // 更新痕迹碎片
- this.updateDebris()
- }
-
- // 添加痕迹碎片
- addDebris(opt = {}) {
- const debris = new Debris({
- ...opt,
- color: opt.color || this.color
- })
- debris.start()
- this.debrisList.push(debris)
- }
-
- // 更新痕迹碎片
- updateDebris() {
- const list = [...this.debrisList]
- list.forEach(debris => {
- const res = debris.update()
- if (res.isEnd) {
- deleteFromList(this.debrisList, debris)
- }
- })
- }
-
- isEnd() {
- return this.status === 'end'
- }
- }
复制代码3、给自己放个烟花秀吧 创建一个文件夹,将以上两个文件 index.html & index.js 放到创建的文件夹中 在电脑端双击打开 index.html,即可在浏览器中打开页面,点击屏幕给自己放个烟花秀吧 !!!
|