利用AudioContext來實現網易雲音樂的鯨魚音效
一直覺得網易雲音樂的使用者體驗是很不錯的,很早就注意到了裡面的鯨魚音效,如下圖,就是一個環形的跟著音樂節拍跳動的特效。
gif動圖可能效果不太理想,可以直接在手機上體驗
身為前端憑著本能的好奇心和探索心當然會研究一番,如何在頁面上實現該效果?
1.AudioContext
其實這類動效原理並不複雜,你需要一堆資料來表述每一塊的高度,然後通過某種方式,讓前臺渲染可見即可。
如何獲取音樂實時的節拍資料呢,這裡用到了AudioContext
AudioContext
介面表示由音訊模組連線而成的音訊處理圖,每個模組對應一個AudioNode
。AudioContext
可以控制它所包含的節點的建立,以及音訊處理、解碼操作的執行。做任何事情之前都要先建立AudioContext
物件,因為一切都發生在這個環境之中。
1.1 AudioContext.createAnalyser()
AudioContext
的createAnalyser()
方法能建立一個AnalyserNode,可以用來獲取音訊時間和頻率資料,以及實現資料視覺化。
var audioCtx = new AudioContext();
var analyser = audioCtx.createAnalyser();
AnalyserNode
賦予了節點可以提供實時頻率及時間域分析的資訊。它使一個 AudioNode
通過音訊流不做修改的從輸入到輸出, 但允許你獲取生成的資料, 處理它並建立音訊視覺化。
AnalyserNode
還有很多屬性
AnalyserNode.fftSize
AnalyserNode
介面的 fftSize
屬性的值是一個無符號長整型的值, 表示(訊號)樣本的視窗大小。當執行快速傅立葉變換(Fast Fourier Transfor (FFT))時,這些(訊號)樣本被用來獲取頻域資料。
fftSize
屬性的值必須是從32到32768範圍內的2的非零冪; 其預設值為2048。
AnalyserNode.frequencyBinCount 只讀
frequencyBinCount
的值固定為 AnalyserNode
介面中fftSize
值的一半. 該屬性通常用於視覺化的資料值的數量.
1.2 AudioContext.createMediaElementSource()
AudioContext
的 createMediaElementSource()
方法用於建立一個新的 MediaElementAudioSourceNode
物件,輸入某個存在的 HTML <audio>
or <video>
元素, 對應的音訊即可被播放或者修改。
var audioCtx = new AudioContext();
var source = audioCtx.createMediaStreamSource(stream);
2.實現
上面很多api
可能剛開始看的時候會犯暈,不過沒事,下面一步一步寫成一個例子就明白了。
這裡我們採用canvas
來繪製頻譜圖,下面簡單寫一個佈局
<canvas id='canvas' width="600" height="600"></canvas>
<audio id="audio" controls autoplay loop></audio>
加點樣式
body{
background: black;
}
canvas,audio{
display: block;
margin: 0 auto;
}
下面來通過音訊來獲取頻譜資料
var audio = document.getElementById('audio');
audio.crossOrigin = 'anonymous';
audio.src='./406238.mp3';
var ctx = new AudioContext();
var analyser = ctx.createAnalyser();
var audioSrc = ctx.createMediaElementSource(audio);
audioSrc.connect(analyser);
analyser.connect(ctx.destination);
analyser.fftSize = 512;
var array = new Uint8Array(analyser.frequencyBinCount);
console.log(array)
列印一下這個array
,是一個長度為256的陣列
這就是音訊的頻譜資料,這個長度跟上面設定的analyser.fftSize
有關,是他的一半,也就是說,設定的越大,得到的資料越多,分析的也越準確。這裡只是繪製一些條形圖,並不需要預設的2048那麼大,所以這裡設定了512。
普通的頻譜圖
在此之前,我們先來實現一下常見的垂直頻譜圖,只需要用到ctx.fillRect
來繪製一個個的方塊就行了
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var cwidth = canvas.width;
var cheight = canvas.height - 2;
var meterWidth = 5; //方塊的寬度
var gap = 2; //方塊的間距
var minHeight = 2;
var meterNum = cwidth / (meterWidth + gap);//根據寬度和間距計算出可以放多少個方塊
ctx.fillStyle = 'rgba(255,255,255,.5)';//填充
function render() {
var array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(array);
var step = Math.round(array.length / meterNum);//從頻譜資料中每隔step均勻取出meterNum個數據
ctx.clearRect(0, 0, cwidth, cheight);
for (var i = 0; i < meterNum; i++) {
var value = array[i * step];
ctx.fillRect(i * (meterWidth+gap) , cheight - value + capHeight, meterWidth, cheight||minHeight); //繪製
}
requestAnimationFrame(render);
}
render();
如果需要漸變色的話,可以
var gradient = ctx.createLinearGradient(0, 0, 0, 300);
gradient.addColorStop(1, '#0f00f0');
gradient.addColorStop(0.5, '#ff0ff0');
gradient.addColorStop(0, '#f00f00');
ctx.fillStyle = gradient ;//填充
完整程式碼可以檢視demo
環形的頻譜圖
如果上面的頻譜圖很清楚了的話,下面的環形也輕而易舉了,主要用到了座標的旋轉
這裡注意的是在進行translate
和rotate
操作時需要進行ctx.save()
和ctx.restore()
,因為操作的是座標系,而不是元素本身,可以多嘗試一下
var PI = Math.PI;
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var cwidth = canvas.width;
var cheight = canvas.height;
var cr = 230;//環形半徑
var minHeight = 2;
var meterWidth = 5;
var meterNum = 180;//設定方塊的數量,考慮到閉環的關係
var gradient = ctx.createLinearGradient(0, -cr, 0, -cwidth/2);
gradient.addColorStop(0, '#0f0');
gradient.addColorStop(0.5, '#ff0');
gradient.addColorStop(1, '#f00');
ctx.fillStyle = gradient;
function render() {
var array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(array);
var step = Math.round(array.length / meterNum);
ctx.clearRect(0, 0, cwidth, cheight);
ctx.save();
ctx.translate(cwidth/2,cheight/2);
for (var i = 0; i < meterNum; i++) {
//ctx.save();
var value = array[i * step];
var meterHeight = value*(cheight/2 - cr)/256||minHeight;
ctx.rotate( 2*PI/meterNum );
ctx.fillRect( -meterWidth/2 , -cr- meterHeight , meterWidth, meterHeight);
//ctx.restore();
}
ctx.restore();
requestAnimationFrame(render);
}
render();
小tip
在進行旋轉操作時,如果你每次旋轉以後,都把座標系還原,那麼在迴圈的時候需要旋轉30,60,90...這樣
ctx.save();
ctx.rotate( 2*PI/meterNum*i );
ctx.restore();
如果你在每次旋轉以後,不還原座標系,那麼每次就是在上一次的基礎上繼續旋轉
//ctx.save();
ctx.rotate( 2*PI/meterNum );//不需要乘i
//ctx.restore();
很顯然,下面的方式更精簡
完整程式碼可以檢視demo
小節
以上就實現了環形的頻譜圖,是不是越來越靠近網易雲音樂的鯨魚音效了呢,中間加一個自動旋轉的專輯封面就可以了~
之前寫過幾篇都是關於css
的文章,有人可能覺得是不是不會js
啊,天天搗鼓css
,其實並不是這樣的,各自有各自的職責範圍,像介面UI之類的,本來就是樣式上的事情,很多人一看看上去覺得css
實現不了,馬上就搬出js
,效果是出來了,但體驗差了一大截。
如果喜歡的文章的話,可以點贊並收藏,多多關注我的部落格