利用AudioContext來實現網易雲音樂的鯨魚音效

一直以爲網易雲音樂的用戶體驗是很不錯的,很早就注意到了裏面的鯨魚音效,以下圖,就是一個環形的跟着音樂節拍跳動的特效。css

鯨魚音效

gif動圖可能效果不太理想,能夠直接在手機上體驗html

身爲前端憑着本能的好奇心和探索心固然會研究一番,如何在頁面上實現該效果?前端

1.AudioContext

其實這類動效原理並不複雜,你須要一堆數據來表述每一塊的高度,而後經過某種方式,讓前臺渲染可見便可。web

如何獲取音樂實時的節拍數據呢,這裏用到了AudioContextcanvas

AudioContext接口表示由音頻模塊鏈接而成的音頻處理圖,每一個模塊對應一個AudioNodeAudioContext能夠控制它所包含的節點的建立,以及音頻處理、解碼操做的執行。作任何事情以前都要先建立AudioContext對象,由於一切都發生在這個環境之中。api

這一段是從developer.mozilla.org/zh-CN/docs/…摘錄下來的,裏面有不少方法,詳細能夠看具體文檔,這裏只介紹咱們下面用到的其中幾個數組

1.1 AudioContext.createAnalyser()

AudioContextcreateAnalyser()方法能建立一個AnalyserNode,能夠用來獲取音頻時間和頻率數據,以及實現數據可視化。ide

var audioCtx = new AudioContext();
var analyser = audioCtx.createAnalyser();
複製代碼

這裏返回的是一個AnalyserNode對象。oop

AnalyserNode 賦予了節點能夠提供實時頻率及時間域分析的信息。它使一個 AudioNode 經過音頻流不作修改的從輸入到輸出, 但容許你獲取生成的數據, 處理它並建立音頻可視化。佈局

WebAudioFFT

AnalyserNode還有不少屬性

AnalyserNode.fftSize

AnalyserNode 接口的 fftSize 屬性的值是一個無符號長整型的值, 表示(信號)樣本的窗口大小。當執行快速傅里葉變換(Fast Fourier Transfor (FFT))時,這些(信號)樣本被用來獲取頻域數據。

fftSize 屬性的值必須是從32到32768範圍內的2的非零冪; 其默認值爲2048。

AnalyserNode.frequencyBinCount 只讀

frequencyBinCount 的值固定爲 AnalyserNode 接口中fftSize值的一半. 該屬性一般用於可視化的數據值的數量.

1.2 AudioContext.createMediaElementSource()

AudioContextcreateMediaElementSource() 方法用於建立一個新的 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的數組

20181121145740

這就是音頻的頻譜數據,這個長度跟上面設置的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 ;//填充
複製代碼

music01.gif

完整代碼能夠查看demo

環形的頻譜圖

若是上面的頻譜圖很清楚了的話,下面的環形也垂手可得了,主要用到了座標的旋轉

這裏注意的是在進行translaterotate操做時須要進行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();
複製代碼

很顯然,下面的方式更精簡

music02.gif

完整代碼能夠查看demo

小節

以上就實現了環形的頻譜圖,是否是愈來愈靠近網易雲音樂的鯨魚音效了呢,中間加一個自動旋轉的專輯封面就能夠了~

以前寫過幾篇都是關於css的文章,有人可能以爲是否是不會js啊,每天搗鼓css,其實並非這樣的,各自有各自的職責範圍,像界面UI之類的,原本就是樣式上的事情,不少人一看看上去以爲css實現不了,立刻就搬出js,效果是出來了,但體驗差了一大截。


若是喜歡的文章的話,能夠點贊並收藏,多多關注個人博客

相關文章
相關標籤/搜索