一直以为网易云音乐的用户体验是很不错的,很早就注意到了里面的鲸鱼音效,以下图,就是一个环形的跟着音乐节拍跳动的特效。css
gif动图可能效果不太理想,能够直接在手机上体验
身为前端凭着本能的好奇心和探索心固然会研究一番,如何在页面上实现该效果?html
其实这类动效原理并不复杂,你须要一堆数据来表述每一块的高度,而后经过某种方式,让前台渲染可见便可。前端
如何获取音乐实时的节拍数据呢,这里用到了AudioContext
web
AudioContext
接口表示由音频模块链接而成的音频处理图,每一个模块对应一个AudioNode
。AudioContext
能够控制它所包含的节点的建立,以及音频处理、解码操做的执行。作任何事情以前都要先建立AudioContext
对象,由于一切都发生在这个环境之中。
这一段是从https://developer.mozilla.org/zh-CN/docs/Web/API/AudioContext摘录下来的,里面有不少方法,详细能够看具体文档,这里只介绍咱们下面用到的其中几个canvas
AudioContext
的createAnalyser()
方法能建立一个AnalyserNode,能够用来获取音频时间和频率数据,以及实现数据可视化。api
var audioCtx = new AudioContext(); var analyser = audioCtx.createAnalyser();
这里返回的是一个AnalyserNode
对象。数组
AnalyserNode
赋予了节点能够提供实时频率及时间域分析的信息。它使一个 AudioNode
经过音频流不作修改的从输入到输出, 但容许你获取生成的数据, 处理它并建立音频可视化。ide
AnalyserNode
还有不少属性oop
AnalyserNode
接口的 fftSize
属性的值是一个无符号长整型的值, 表示(信号)样本的窗口大小。当执行快速傅里叶变换(Fast Fourier Transfor (FFT))时,这些(信号)样本被用来获取频域数据。布局
fftSize
属性的值必须是从32到32768范围内的2的非零幂; 其默认值为2048。
frequencyBinCount
的值固定为 AnalyserNode
接口中fftSize
值的一半. 该属性一般用于可视化的数据值的数量.
AudioContext
的 createMediaElementSource()
方法用于建立一个新的 MediaElementAudioSourceNode
对象,输入某个存在的 HTML <audio>
or <video>
元素, 对应的音频便可被播放或者修改。
var audioCtx = new AudioContext(); var source = audioCtx.createMediaStreamSource(stream);
上面不少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
,效果是出来了,但体验差了一大截。
若是喜欢的文章的话,能够点赞并收藏,多多关注个人博客