本篇文章給大家介紹如何用前端代碼實(shí)現(xiàn)一個(gè)煙花綻放的絢爛效果,其實(shí)主要就是用前端三劍客來實(shí)現(xiàn),也就是HTML+CSS+JS,下面一起來看一下,作者會(huì)解說相應(yīng)的代碼,希望對需要的朋友有所幫助。(ps:之所以有這篇文章是由于作者所在地區(qū)禁止放煙花…哈哈)
↑↑↑↑↑↑ 效果圖鎮(zhèn)樓 ↑↑↑↑↑↑
序
不知道是在什么時(shí)候,濟(jì)南就開始都在傳:“今年不再限制放煙花啦!”。一些集市上也開始有了售賣煙花的攤子
大家都很興奮,很多小伙伴開始購買煙花。特別是今年特別火的 “加特林 ?”
但是大家興奮勁還沒過呢,隨著官方 一紙禁令,讓咱們知道了:
2023 過春年
煙花依然了無緣
讓我們這些屯了煙花的可咋辦啊 ??????
不過身為程序猿的我們,能就這么認(rèn)栽了?
我們可是要用代碼改變世界的人啊~~~~
所以我辛苦閉關(guān)九九八十一秒(開玩笑~我寫了好幾天),終于把煙花放到了瀏覽器上,看著效果我是“內(nèi)牛滿面(淚流滿面)”??!
此時(shí),我真想仰天長嘯,大聲唱道:
2023 過春年,
煙花依然了無緣;
這能難倒程序猿?
一鍵三連過大年!
代碼
下面開始上菜(代碼)咯~~~
咱們整個(gè)的代碼一共分為三個(gè)部分:
-
html:用來構(gòu)建項(xiàng)目結(jié)構(gòu)
-
css:處理文字樣式
-
js(核心):處理煙花邏輯
那么下面,咱們就針對這三個(gè)部分,分別進(jìn)行處理:
1. html
整個(gè) html
分為兩部分:
- 文字結(jié)構(gòu):用來處理右下角的文本
2. canvas 畫板:作為煙花渲染區(qū)
1. 文字結(jié)構(gòu)
文字結(jié)構(gòu)整體的內(nèi)容處理會(huì)比較簡單,通過div
包裹“文本標(biāo)簽”即可:
<!-- 文字修改區(qū) --> <div class="title"> <h2>LGD_Sunday 祝大家:</h2> <h1>2023 新年快樂?</h1> </div>
2. canvas
canvas 作為 web
為我們提供的畫板工具,可以幫助咱們繪制各種各樣的圖形。
那么對于咱們本次的煙花繪制而言,同樣需要借助canvas
的能力。
所以在html
區(qū)域部分,我們必須提供一個(gè)canvas
繪制區(qū):
<!-- 煙花渲染區(qū) --> <canvas></canvas>
html 區(qū)域總結(jié)
當(dāng)咱們完成基本的html
繪制之后,運(yùn)行代碼到瀏覽器,效果應(yīng)該是這個(gè)樣子的:
啥都沒有對吧,別著急,下面咱們?nèi)ヌ幚?code>css部分。
2. css
css
處理的核心目的,是為了幫助咱們繪制文字區(qū)域的樣式(沒錯(cuò),與煙花無關(guān))。
所以,整個(gè)css
區(qū)域繪制會(huì)比較簡單
咱們直接來看代碼:
html, body { padding: 0px; margin: 0px; background: #222; font-family: 'Karla', sans-serif; color: #fff; height: 100vh; overflow: hidden; } .title { z-index: 1000; position: fixed; bottom: 12px; right: 12px; // 此處修改了字體 font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif; border: 2px solid #fff; padding: 7.5px 15px; background: rgba(0, 0, 0, 0.5); border-radius: 3px; overflow: hidden; } h1 { text-align: right; font-size: 46px; } h2 { font-size: 36px; } canvas { width: 100%; height: 100%; }
繪制了css
之后,頁面效果應(yīng)該是這個(gè)樣子的:
3. 核心區(qū)域:JavaScript
接下來就讓咱們進(jìn)入煙花繪制,最核心的部分JavaScript
的處理,在這部分咱們要利用canvas
的繪制能力,構(gòu)建出煙花效果。
整個(gè)js
的內(nèi)容,我們把它分為四大部分:
- 煙花類 Firework:這里咱們會(huì)通過
function
構(gòu)建構(gòu)造函數(shù),創(chuàng)建出自底向上的煙花升起效果。
2. 火花類 Spark:煙花上升到一定位置,會(huì)“綻放“,綻放之后變?yōu)椤被鸹ā?,火花類就是處理綻放之后的火花效果的。
-
渲染函數(shù) render:想要完成整個(gè)綻放,需要對頁面進(jìn)行不斷地重繪,否則”動(dòng)畫“會(huì)卡在一個(gè)點(diǎn)不動(dòng)。所以此時(shí)就需要借助到渲染函數(shù),咱們把它叫做
render
(vue 代碼對我影響頗深?。?/p> -
工具函數(shù):工具函數(shù)主要包含兩個(gè),咱們分開去說:
- 持續(xù)繪制函數(shù) drawCircle:該函數(shù)可以幫助我們,在每次重新渲染時(shí),進(jìn)行煙花和火花的繪制
- 隨機(jī)色值函數(shù) randomColor:該函數(shù)可以幫助我們,得到一個(gè)隨機(jī)的煙花色值,用來生成新的煙花
那么明確好大致分類之后,接下來咱們就一步一步的進(jìn)行實(shí)現(xiàn)。
但是大家要注意:為了保證邏輯的通暢性,咱們需要從工具函數(shù)開始進(jìn)行繪制。
1. 工具函數(shù)
在剛才咱們說過,工具函數(shù)主要包含兩個(gè),那么首先先來看第一個(gè)工具函數(shù)drawCircle
,它的主要作用是:在每次重新渲染時(shí),進(jìn)行煙花和火花的繪制
// 獲取 canvas 上下文,并指定寬高 let ctx = document.querySelector('canvas').getContext('2d') ctx.canvas.width = window.innerWidth ctx.canvas.height = window.innerHeight /** * 持續(xù)繪制 */ function drawCircle(x, y, radius, color) { color = color ctx.fillStyle = color ctx.fillRect(x - radius / 2, y - radius / 2, radius, radius) }
在上面的代碼中:
- 首先:我們拿到了
ctx
,也就是CanvasRenderingContext2D
二維渲染上下文 ,利用它可以進(jìn)行繪制渲染 - 然后:把
canvas
的寬高指定為頁面寬高 - 最后:構(gòu)建了
drawCircle
函數(shù),進(jìn)行持續(xù)繪制。它接收四個(gè)參數(shù):x
:繪制的 x 坐標(biāo)y
:繪制的 y 坐標(biāo)radius
:點(diǎn)的直徑(寬高)color
:色值
那么此時(shí),只要觸發(fā) drawCircle
函數(shù),就可以進(jìn)行持續(xù)繪制。
工具函數(shù)繪制完成之后,下面我們來看第二個(gè)函數(shù):隨機(jī)色值 randomColor:
/** * 生成隨機(jī)色值 */ function randomColor() { const r = Math.floor(Math.random() * 255) const g = Math.floor(Math.random() * 255) const b = Math.floor(Math.random() * 255) return `rgb(${r},${g},$)` }
randomColor
函數(shù),主要利用Math.random
生成了一個(gè) 0-255
的隨機(jī)數(shù),三個(gè)隨機(jī)數(shù)共同組成了rgb
色值。
2. 煙花類 Firework
有了工具函數(shù)之后,下面咱們就可以處理煙花類 Firework
。
煙花類本質(zhì)上是:自底向上的,煙花升起效果。 其核心是:生成可以被 drawCircle 繪制的對象
所以它內(nèi)部必然包含:坐標(biāo)、綻放點(diǎn)、色彩 等屬性:
/** * 煙花構(gòu)造 */ function Firework(x, y) { // 初始點(diǎn) this.x = x || Math.random() * ctx.canvas.width this.y = y || ctx.canvas.height // 綻放點(diǎn) this.burstLocation = (Math.random() * ctx.canvas.height) / 2 // 綻放是否已完畢 this.over = false // 煙花色 this.color = randomColor() }
在上面的代碼中:
x、y
:表示煙花升起的坐標(biāo)。其中y
中默認(rèn)為瀏覽器底部,x
則可以隨機(jī)burstLocation
:表示定義的綻放點(diǎn)。通常小于屏幕高度的一半,即:在屏幕上半部分”綻放“over
:表示當(dāng)前實(shí)例對象是否已經(jīng)綻放完畢了。完畢的實(shí)例將不再處理color
:隨機(jī)得到的煙花色
僅有屬性還不夠,因?yàn)闊熁ㄟ€需要 ”動(dòng)起來“,所以咱們還需要為它賦值三個(gè)方法,以幫助它進(jìn)行移動(dòng)、持續(xù)繪制、綻放:
// 初始綻放數(shù) const OVERLAP_NUM = 66 // 刷新速度 ms const TIME_STEP = 16 // 煙花移動(dòng)的速度與方向控制 const WALK = 0.2 // 火花數(shù)組 let sparks = [] // 煙花數(shù)組 let fireworks = [] /** * 煙花構(gòu)造 */ function Firework(x, y) { ... /** * 移動(dòng)的方法 */ this.move = function () { // 橫向偏移 this.x += WALK // 上升與綻放 if (this.y > this.burstLocation) { this.y -= 1 } else { this.burst() } } /** * 持續(xù)繪制 */ this.draw = function () { drawCircle(this.x, this.y, 1.5, this.color) } /** * 綻放方法 */ this.burst = function () { // 標(biāo)記綻放完畢 this.over = true // 碎裂煙花數(shù) let i = Math.floor(Math.random() * 150) + 10 // 構(gòu)建碎裂對象 while (i--) { sparks.push(new Spark(this.x, this.y, this.color)) } } }
在上面的代碼中,咱們一共構(gòu)建了三個(gè)方法:
move
:用來處理煙花上升。在上升的過程中,可以通過this.x += WALK
來進(jìn)行橫向的”微調(diào)“,這樣會(huì)更加漂亮draw
:用來進(jìn)行持續(xù)繪制。主要借助了drawCircle
完成burst
:處理綻放。當(dāng)煙花上升到指定位置時(shí),就需要進(jìn)行綻放處理。所有的綻放過程,將交由Spark
類處理。
3. 火花類 Spark
當(dāng)煙花逐漸上升到一定位置之后,則需要進(jìn)行綻放。
而所謂的綻放就是:煙花爆炸之后迸現(xiàn)出的火花。這一塊的過程,咱們將通過Spark
進(jìn)行處理。
煙花綻放時(shí),將迸現(xiàn)出大量的火花,其中每一個(gè)火花,都是一個(gè)Spark
實(shí)例。
所以針對于Spark
而言,它代表的是:單個(gè)火花,從出現(xiàn)到消亡的過程。
那么對于 Spark
它內(nèi)部的代碼來說,總體也是分為:屬性、方法 兩部分。
首先咱們先來看屬性:
/** * 火花構(gòu)造 */ function Spark(x, y, color) { // 標(biāo)記綻放點(diǎn)位置與色值 this.x = x this.y = y this.color = color // 位置 this.dir = Math.random() * (Math.PI * 2) // 執(zhí)行完畢 this.over = false // 火花崩裂速度 this.speed = Math.random() * 3 + 3 // 火花下墜的速度 this.gravity = Math.random() + 0.1 // 火花消失的速度 this.countdown = this.speed * 10 }
對于以上代碼來說:
x、y、color
:表示綻放的位置與色值dir
:表示綻放后的位置over
:表示綻放完成speed
:火花崩裂之后的運(yùn)行速度gravity
:火花開始下墜時(shí)的速度,它在計(jì)算時(shí)會(huì)進(jìn)行遞增(加速)countdown
:消失的倒計(jì)時(shí)
有了屬性之后,下面咱們需要通過兩個(gè)方法,來保證Spark
的移動(dòng)和繪制:
/** * 火花構(gòu)造 */ function Spark(x, y, color) { ... /** * 火花移動(dòng)方法 */ this.move = function () { // 倒計(jì)時(shí)處理 this.countdown-- if (this.countdown < 0) { this.over = true } // 速度遞減 if (this.speed > 0) { this.speed -= 0.1 } if (this.speed < 0) { return } // x、y 坐標(biāo)位置 this.x += Math.cos(this.dir + WALK) * this.speed this.y += Math.sin(this.dir + WALK) * this.speed this.y += this.gravity // 下墜速度加快 this.gravity += 0.05 } /** * 繪制 */ this.draw = function () { drawCircle(this.x, this.y, 3, this.color) } }
其中:
move
:代表移動(dòng)的過程。在這里咱們利用Math.cos 和 Math.sin 計(jì)算了坐標(biāo)位置,并且通過this.gravity += 0.05
增加了煙花下墜的速度。draw
:代表持續(xù)繪制。同樣需要利用drawCircle
方法。
4. 渲染函數(shù) render
那么最后,咱們就可以來構(gòu)建render
函數(shù)。
render
函數(shù)的核心作用是:保證煙花的不斷渲染。要達(dá)到這個(gè)目的,咱們就必須要保證render 函數(shù)不斷重復(fù)執(zhí)行
想要讓render
重復(fù)執(zhí)行其實(shí)有兩種方式:
1.window.requestAnimationFrame:該方法可以保證高性能的持續(xù)重繪。但是 在高刷屏幕下會(huì)導(dǎo)致 ”速率過快“2.window.setTimeout:該方法可以通過delay
控制速率,所以在當(dāng)前場景中比較推薦。
/** * 渲染函數(shù) */ function render() { // 夜幕背景色與區(qū)域 ctx.fillStyle = 'rgba(0, 0, 0, 0.1)' ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height) // 煙花上升 for (let firework of fireworks) { if (firework.over) { continue } firework.move() firework.draw() } // 火花下墜 for (let spark of sparks) { if (spark.over) { continue } spark.move() spark.draw() } // 通過隨機(jī)數(shù)來控制煙花產(chǎn)生速度 if (Math.random() < 0.05) { fireworks.push(new Firework()) } // 重復(fù)渲染 setTimeout(render, TIME_STEP) }
5. 完整代碼
因?yàn)檎椎?code>js代碼比較多,所以咱們在最后,把整個(gè)的js
代碼給大家貼出來(因?yàn)槲也幌嘈拍銈儠?huì)一步一步跟著學(xué) ??????),以方便大家隨取隨用(我是不是很周到??????)
// 獲取 canvas 上下文,并指定寬高 let ctx = document.querySelector('canvas').getContext('2d') ctx.canvas.width = window.innerWidth ctx.canvas.height = window.innerHeight // 初始綻放數(shù) const OVERLAP_NUM = 66 // 刷新速度 ms const TIME_STEP = 16 // 煙花移動(dòng)的速度與方向控制 const WALK = 0.2 // 火花數(shù)組 let sparks = [] // 煙花數(shù)組 let fireworks = [] // 初始爆炸的填充邏輯 for (let i = 0; i < OVERLAP_NUM; i++) { // 填充 fireworks.push( // 構(gòu)建隨機(jī)位置 new Firework( Math.random() * window.innerWidth, Math.random() * window.innerHeight ) ) } /** * 渲染函數(shù) */ function render() { // 夜幕背景色與區(qū)域 ctx.fillStyle = 'rgba(0, 0, 0, 0.1)' ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height) // 煙花上升 for (let firework of fireworks) { if (firework.over) { continue } firework.move() firework.draw() } // 火花下墜 for (let spark of sparks) { if (spark.over) { continue } spark.move() spark.draw() } // 通過隨機(jī)數(shù)來控制煙花產(chǎn)生速度 if (Math.random() < 0.05) { fireworks.push(new Firework()) } // 重復(fù)渲染 setTimeout(render, TIME_STEP) } /** * 火花構(gòu)造 */ function Spark(x, y, color) { // 標(biāo)記綻放點(diǎn)位置與色值 this.x = x this.y = y this.color = color // 位置 this.dir = Math.random() * (Math.PI * 2) // 執(zhí)行完畢 this.over = false // 火花崩裂速度 this.speed = Math.random() * 3 + 3 // 火花下墜的速度 this.gravity = Math.random() + 0.1 // 火花消失的速度 this.countdown = this.speed * 10 /** * 火花移動(dòng)方法 */ this.move = function () { // 倒計(jì)時(shí)處理 this.countdown-- if (this.countdown < 0) { this.over = true } // 速度遞減 if (this.speed > 0) { this.speed -= 0.1 } if (this.speed < 0) { return } // x、y 坐標(biāo)位置 this.x += Math.cos(this.dir + WALK) * this.speed this.y += Math.sin(this.dir + WALK) * this.speed this.y += this.gravity // 下墜速度加快 this.gravity += 0.05 } /** * 繪制 */ this.draw = function () { drawCircle(this.x, this.y, 3, this.color) } } /** * 煙花構(gòu)造 */ function Firework(x, y) { // 初始點(diǎn) this.x = x || Math.random() * ctx.canvas.width this.y = y || ctx.canvas.height // 綻放點(diǎn) this.burstLocation = (Math.random() * ctx.canvas.height) / 2 // 綻放是否已完畢 this.over = false // 煙花色 this.color = randomColor() /** * 移動(dòng)的方法 */ this.move = function () { // 橫向偏移 this.x += WALK // 上升與綻放 if (this.y > this.burstLocation) { this.y -= 1 } else { this.burst() } } /** * 持續(xù)繪制 */ this.draw = function () { drawCircle(this.x, this.y, 1.5, this.color) } /** * 綻放方法 */ this.burst = function () { // 標(biāo)記綻放完畢 this.over = true // 碎裂煙花數(shù) let i = Math.floor(Math.random() * 150) + 10 // 構(gòu)建碎裂對象 while (i--) { sparks.push(new Spark(this.x, this.y, this.color)) } } } /** * 持續(xù)繪制 */ function drawCircle(x, y, radius, color) { color = color ctx.fillStyle = color ctx.fillRect(x - radius / 2, y - radius / 2, radius, radius) } /** * 生成隨機(jī)色值 */ function randomColor() { const r = Math.floor(Math.random() * 255) const g = Math.floor(Math.random() * 255) const b = Math.floor(Math.random() * 255) return `rgb(${r},${g},$)` } // 開始 render()
總結(jié)
三年抗疫,咱們共同經(jīng)歷了封控、裁員、降薪等一系列讓人感到”始料未及“的事情。
我甚至一度以為將來會(huì)變成”封控常態(tài)化“、”裁員常態(tài)化“、”降薪常態(tài)化“。
但是在新的2023
年到來之前,所有的一切都已經(jīng)變成了過去式。
讓我們用一場煙花告別過去,迎接未來!
2023 年將會(huì)是一個(gè)好的年度,大家一起加油!
在這里:Sunday 祝大家:新年快樂,兔年大吉!
聲明:本文轉(zhuǎn)載于:juejin,如有侵犯,請聯(lián)系admin@php.cn刪除
- 上一篇:程序員在Boss上投簡歷要注意了!
- 下一篇:沒有了