基于JavaScript實(shí)現(xiàn)簡(jiǎn)單的音樂(lè)音譜圖效果
我們經(jīng)常看到在聽(tīng)樂(lè)音的時(shí)候,會(huì)有音譜圖隨著音樂(lè)的節(jié)奏不斷變化給人視覺(jué)上的享受,那么我們通過(guò)js來(lái)實(shí)現(xiàn)以下這個(gè)效果,下面是簡(jiǎn)單的效果圖
首先我們需要有一個(gè)繪制音頻的函數(shù)
function draw() { // 請(qǐng)求下一幀動(dòng)畫(huà) animationId = requestAnimationFrame(draw); // 獲取音頻頻譜數(shù)據(jù) analyser.getByteFrequencyData(dataArray); // 清空畫(huà)布 ctx.fillStyle = 'black'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 計(jì)算每個(gè)頻譜條的寬度 var barWidth = (canvas.width / bufferLength) * 2.5; var barHeight; var x = 0; // 遍歷頻譜數(shù)據(jù)數(shù)組,繪制頻譜條 for (var i = 0; i < bufferLength; i++) { // 計(jì)算頻譜條的高度 barHeight = dataArray[i] / 255 * canvas.height; // 根據(jù)頻譜條的索引值計(jì)算顏色(彩虹色) var hue = i / bufferLength * 360; ctx.fillStyle = 'hsl(' + hue + ', 100%, 50%)'; // 繪制頻譜條矩形 ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight); // 更新下一個(gè)頻譜條的起始位置 x += barWidth + 1; } }
這個(gè)函數(shù)是用于繪制頻譜圖的核心部分。它使用requestAnimationFrame()
方法來(lái)請(qǐng)求下一幀動(dòng)畫(huà),并將自身作為回調(diào)函數(shù)。這樣可以不斷更新頻譜圖。
在函數(shù)內(nèi)部,analyser.getByteFrequencyData(dataArray)
用于獲取當(dāng)前的音頻頻譜數(shù)據(jù),將數(shù)據(jù)存儲(chǔ)在dataArray
數(shù)組中。
然后,畫(huà)布被清空,使用黑色填充整個(gè)畫(huà)布。
接下來(lái),通過(guò)計(jì)算每個(gè)頻譜條的寬度,以及根據(jù)頻譜數(shù)據(jù)計(jì)算每個(gè)頻譜條的高度,來(lái)確定頻譜條的繪制參數(shù)。
然后,使用彩虹色調(diào)的漸變來(lái)設(shè)置頻譜條的顏色,顏色的HSL值根據(jù)頻譜條的索引值計(jì)算。
最后,在畫(huà)布上繪制每個(gè)頻譜條的矩形,每個(gè)矩形之間留有間距。
通過(guò)不斷調(diào)用requestAnimationFrame()
方法并在每一幀更新頻譜圖,可以實(shí)現(xiàn)連續(xù)的動(dòng)畫(huà)效果。
接下來(lái)我們需要分析一下音頻
document.getElementById('playButton').addEventListener('click', function() { if (!audioContext) { audioContext = new (window.AudioContext || window.webkitAudioContext)(); analyser = audioContext.createAnalyser(); analyser.fftSize = 2048; audioElement = document.createElement('audio'); audioElement.src = '1.mp3'; audioElement.controls = true; audioElement.style.display = 'none'; document.body.appendChild(audioElement); var source = audioContext.createMediaElementSource(audioElement); source.connect(analyser); analyser.connect(audioContext.destination); bufferLength = analyser.frequencyBinCount; dataArray = new Uint8Array(bufferLength); } audioElement.play(); draw(); }); document.getElementById('pauseButton').addEventListener('click', function() { audioElement.pause(); cancelAnimationFrame(animationId); });
當(dāng)用戶點(diǎn)擊"播放"按鈕時(shí),創(chuàng)建一個(gè)新的AudioContext
對(duì)象用于處理音頻,創(chuàng)建一個(gè)AnalyserNode
對(duì)象用于分析音頻頻譜。然后創(chuàng)建一個(gè)audio
元素并將其設(shè)置為要播放的音頻文件。將audio
元素連接到AnalyserNode
,將AnalyserNode
連接到AudioContext
的目標(biāo)(通常是揚(yáng)聲器)。設(shè)置頻率分析器的參數(shù),包括FFT大小。
當(dāng)用戶點(diǎn)擊"播放"按鈕時(shí),音頻開(kāi)始播放,并且在draw()
函數(shù)中的requestAnimationFrame(draw)
中調(diào)用的循環(huán)中,更新頻譜數(shù)據(jù)并繪制頻譜圖。首先,使用analyser.getByteFrequencyData(dataArray)
獲取音頻頻譜數(shù)據(jù)。然后,通過(guò)遍歷數(shù)據(jù)數(shù)組,計(jì)算每個(gè)頻譜條的高度,并根據(jù)頻譜條的位置在畫(huà)布上繪制矩形。顏色根據(jù)頻譜條的索引值計(jì)算,使得頻譜圖呈現(xiàn)彩虹色的效果。
當(dāng)用戶點(diǎn)擊"暫停"按鈕時(shí),音頻暫停播放,并調(diào)用cancelAnimationFrame(animationId)
來(lái)停止繪制頻譜圖。
請(qǐng)確保將audioElement.src
中的路徑替換為你要播放的實(shí)際音頻文件的路徑。
當(dāng)然修改draw函數(shù)可以得到其他的音頻圖,比如波形圖
具體的draw代碼如下
這個(gè)函數(shù)主要用于繪制音頻的時(shí)域波形圖。它也使用了requestAnimationFrame()
方法來(lái)請(qǐng)求下一幀動(dòng)畫(huà),并將自身作為回調(diào)函數(shù)。
在函數(shù)內(nèi)部,analyser.getByteTimeDomainData(dataArray)
用于獲取當(dāng)前的音頻時(shí)域數(shù)據(jù),將數(shù)據(jù)存儲(chǔ)在dataArray
數(shù)組中。
然后,畫(huà)布被清空,并將背景顏色設(shè)置為lime。
接下來(lái),設(shè)置線條的寬度和顏色。
然后,開(kāi)始繪制路徑。
通過(guò)計(jì)算每個(gè)數(shù)據(jù)片段的寬度,以及根據(jù)時(shí)域數(shù)據(jù)計(jì)算每個(gè)點(diǎn)的縱坐標(biāo),確定波形圖的繪制參數(shù)。
然后,根據(jù)波形點(diǎn)的位置,使用moveTo()
方法將繪制路徑移動(dòng)到第一個(gè)點(diǎn)的位置,并使用lineTo()
方法連接到下一個(gè)點(diǎn)的位置。這樣就形成了一條完整的波形路徑。
在遍歷完所有的數(shù)據(jù)點(diǎn)后,使用lineTo()
方法將最后一個(gè)點(diǎn)連接到畫(huà)布的右側(cè)中點(diǎn),以形成閉合路徑。
最后,使用stroke()
方法繪制路徑。
通過(guò)不斷調(diào)用requestAnimationFrame()
方法并在每一幀更新波形圖,可以實(shí)現(xiàn)連續(xù)的動(dòng)畫(huà)效果。
function draw() { // 請(qǐng)求下一幀動(dòng)畫(huà) animationId = requestAnimationFrame(draw); // 獲取音頻時(shí)域數(shù)據(jù) analyser.getByteTimeDomainData(dataArray); // 清空畫(huà)布并設(shè)置背景顏色為lime ctx.fillStyle = 'lime'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 設(shè)置線條寬度和顏色 ctx.lineWidth = 2; ctx.strokeStyle = 'black'; // 開(kāi)始繪制路徑 ctx.beginPath(); // 計(jì)算每個(gè)數(shù)據(jù)片段的寬度 var sliceWidth = canvas.width * 1.0 / bufferLength; var x = 0; // 遍歷時(shí)域數(shù)據(jù)數(shù)組,繪制波形 for (var i = 0; i < bufferLength; i++) { // 將數(shù)據(jù)歸一化到范圍[-1, 1] var v = dataArray[i] / 128.0; // 計(jì)算波形點(diǎn)的縱坐標(biāo) var y = v * canvas.height / 2; if (i === 0) { // 移動(dòng)到第一個(gè)點(diǎn)的位置 ctx.moveTo(x, y); } else { // 連接到下一個(gè)點(diǎn)的位置 ctx.lineTo(x, y); } // 更新下一個(gè)點(diǎn)的橫坐標(biāo) x += sliceWidth; } // 連接最后一個(gè)點(diǎn)到畫(huà)布右側(cè)中點(diǎn),形成閉合路徑 ctx.lineTo(canvas.width, canvas.height / 2); // 繪制路徑 ctx.stroke(); }
到此這篇關(guān)于基于JavaScript實(shí)現(xiàn)簡(jiǎn)單的音樂(lè)音譜圖效果的文章就介紹到這了,更多相關(guān)JavaScript音譜圖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript 判斷數(shù)據(jù)類型的4種方法
這篇文章主要介紹了JavaScript 判斷數(shù)據(jù)類型的4種方法,幫助大家更好的理解和學(xué)習(xí)JavaScript,感興趣的朋友可以了解下2020-09-09javascript 自動(dòng)標(biāo)記來(lái)自搜索結(jié)果頁(yè)的關(guān)鍵字
使用javascript自動(dòng)標(biāo)記來(lái)自搜索結(jié)果頁(yè)的關(guān)鍵字的實(shí)現(xiàn)代碼。2010-01-01基于javascript實(shí)現(xiàn)彩票隨機(jī)數(shù)生成(簡(jiǎn)單版)
這篇文章主要介紹了基于javascript實(shí)現(xiàn)彩票隨機(jī)數(shù)生成的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-01-01實(shí)現(xiàn)表格中行點(diǎn)擊時(shí)的漸擴(kuò)效果!
實(shí)現(xiàn)表格中行點(diǎn)擊時(shí)的漸擴(kuò)效果!...2006-12-12js實(shí)現(xiàn)可旋轉(zhuǎn)的立方體模型
這里給大家分享的是通過(guò)js腳本來(lái)控制頁(yè)面中的正方體轉(zhuǎn)動(dòng)特效,用戶可以點(diǎn)擊按鈕向右轉(zhuǎn)動(dòng),也可以向下轉(zhuǎn)動(dòng),結(jié)合自己的需求控制即可。效果非常棒,這里推薦給大家2016-10-10javascript 通用loading動(dòng)畫(huà)效果實(shí)例代碼
這篇文章主要介紹了javascript 通用loading動(dòng)畫(huà)效果實(shí)例代碼,有需要的朋友可以參考一下2014-01-01使用Javascript監(jiān)控前端相關(guān)數(shù)據(jù)的代碼
本篇文章詳細(xì)的介紹了使用Javascript監(jiān)控前端相關(guān)數(shù)據(jù),可以及時(shí)的監(jiān)控前端的錯(cuò)誤,加載時(shí)間等,有需要的可以了解一下。2016-10-10前端JavaScript中l(wèi)ocation.reload刷新頁(yè)面用法詳解
這篇文章主要介紹了前端JavaScript中l(wèi)ocation.reload刷新頁(yè)面用法的相關(guān)資料,location.reload()是JavaScript中用于重新加載當(dāng)前頁(yè)面的方法,它可以接受一個(gè)布爾參數(shù),以決定是否忽略緩存,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-02-02JavaScript實(shí)現(xiàn)鼠標(biāo)控制自由移動(dòng)的窗口
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)鼠標(biāo)控制自由移動(dòng)的窗口,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06