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