vue 音频可视化

简介: ​书接上文 vue实现歌词滚动效果,既然有了歌词滚动那么今天给大家来个绝活——音频可视化。伴随着各种新技术的更新迭代,往后大家或多或少都会接触到这个技能,因为在未来前端可视化必定会占有极其重要的地位,可视化应用场景应用模式也是千变万化,我将以最直观最简单的例子作为切入点,来带大家了解探究音频可视化到底是怎么实现的。​

前言

书接上文 [vue实现歌词滚动效果],既然有了歌词滚动那么今天给大家来个绝活——音频可视化。伴随着各种新技术的更新迭代,往后大家或多或少都会接触到这个技能,因为在未来前端可视化必定会占有极其重要的地位,可视化应用场景应用模式也是千变万化,我将以最直观最简单的例子作为切入点,来带大家了解探究音频可视化到底是怎么实现的。

一、音频可视化是什么?
​音频可视化,是指一种以视觉为核心,以音乐为载体,以大众为诉求对象,借助多种新媒体技术等传播媒介,通过画面、影像来诠释音乐内容的、视听结合的大众化传播方式。  它能为理解、分析和比较音乐艺术作品形态的表现力和内外部结构提供的一种直观视觉呈现的技术。

二、实现思路
1,创建音频上下文,音频源节点

2,创建分析器节点对音频进行分析,连接音频源节点和分析器,随着音乐进度变化,分析器不断的分析出结果

3,连接分析器反馈分析结果到输出设备,由输出设备渲染到canvas标签

至于分析器简单来说就是可以把音频时域图通过频谱分析转换为频谱图,这个转换的过程叫做频谱分析,频谱分析的方式有很多,最常见的是快速傅里叶变换,有兴趣的可以研究一下频谱分析(之前写的一些小dome),内部逻辑大家就没必要深究啦 !

三、实现代码
1.初始化canvas
代码如下(示例)



initCancas() { 
  let canvas = document.getElementById("myCanvas");
  let ctx = canvas.getContext("2d"); 
  ctx.fillRect(10, 10, 280, 130);
},

2,当音频播放时 初始化分析器
根据MDN的文档,AudioContext是一个专门用于音频处理的接口,并且工作原理是将AudioContext创建出来的各种节点(AudioNode)相互连接,音频数据流经这些节点并作出相应处理。

AudioContext() 构造方法创建了一个新的 AudioContext 对象 它代表了一个由音频模块链接而成的音频处理图,每一个模块由 AudioNode 表示。

AudioContext的createAnalyser()方法能创建一个AnalyserNode,可以用来获取音频时间和频率数据,以及实现数据可视化。
f9439d6e5dfe4f2dac92845641a56783.png
f9439d6e5dfe4f2dac92845641a56783.png
e73612501df34ccc98149901017d8bec.png
5fa25bf25e1246c68a9a7e112f190151.png

onPlay() {

  if (this.isInit) {
    return;
  }
  let audioEle = document.getElementById("myAudio");
  //初始化
  var audCtx = new AudioContext(); //创建音频上下文
  this.analyser = audCtx.createAnalyser();
  const source = audCtx.createMediaElementSource(audioEle); //创建音频源节点
  this.analyser.fftSize = 512; //一个无符号长整形 (unsigned long) 的值,代表了快速傅里叶变换(分析器)的窗口大小
  //创建数组,用于接受节点分析器分析的数据
  this.dataArry = new Uint8Array(this.analyser.frequencyBinCount); //这里并不是声明一个普通数组,而是需要声明一个无符号的八位整数,刚好是一个字节。并且数组长度需要刚好等于频谱图横坐标长度
  source.connect(this.analyser);
  this.analyser.connect(audCtx.destination);

  this.isInit = true;
},

  

 

3.绘制分析器分析的结果数据
代码如下(示例):

//把分析器分析的数据不断绘制到canvas
draw() {
  let cvs = document.getElementById("myCanvas");
  let ctx = cvs.getContext("2d");
  requestAnimationFrame(this.draw);
  //清空画布
  const { width, height } = cvs;
  ctx.clearRect(0, 0, width, height);
  //判断分析器是否初始化
  if (!this.isInit) {
    return;
  }
  //分析器节点分析出数据到数组中
  this.analyser.getByteFrequencyData(this.dataArry); //让分析器节点分析当前音频源数据,把分析结果添加到数组
  const len = this.dataArry.length;
  const barWidth = width / len / 2;
  ctx.fillStyle = "#41b883";
  for (let i = 0; i < len; i++) {
    const data = this.dataArry[i]; //<256
    const barHeight = (data / 255) * height;
    const x1 = i * barWidth + width / 2;
    const x2 = width / 2 - (i + 1) * barWidth;
    const y = height - barHeight;
    ctx.fillRect(x1, y, barWidth, barHeight);
    ctx.fillRect(x2, y, barWidth, barHeight);
  }
},

window.requestAnimationFrame() 告诉浏览器,你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

备注: 若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用 window.requestAnimationFrame(callback)

兼容说明
由于是用的是实验API,此功能某些浏览器尚在开发中,请参考浏览器兼容性表格以得到在不同浏览器中适合使用的前缀。由于该功能对应的标准文档可能被重新修订,所以在未来版本的浏览器中该功能的语法和行为可能随之改变。
3ca95d3819b844428d17407f093a0fcf.png

 歌词滚动+音频可视化全部代码

{{ dataWords }}
<canvas
  id="myCanvas"
  style="width: 600px; height: 100px"
>
</canvas>


return {
  isInit: false,
  className: "text load",
  lrcTime: "",
  music,
  currenttime: "",
  LRC: `[00:00.000] 作词 : 周耀辉/李焯雄

[00:01.000] 作曲 : 林健华
[00:02.000] 编曲 : 林健华
[00:15.000]忽然之间
[00:18.000]天昏地暗
[00:21.000]世界可以忽然什么都没有
[00:28.000]我想起了你
[00:32.000]再想到自己
[00:35.000]我为什么总在非常脆弱的时候
[00:40.000]怀念你
[00:43.000]''
[00:44.000]我明白太放不开你的爱
[00:49.000]太熟悉你的关怀分不开
[00:54.000]想你算是安慰还是悲哀
[00:58.000]而现在就算时针都停摆
[01:03.000]就算生命像尘埃分不开
[01:08.000]我们也许反而更相信爱
[01:24.000]''
[01:25.000]如果这天地
[01:29.000]最终会消失
[01:32.000]不想一路走来珍惜的回忆
[01:38.000]没有你
[01:40.000]''
[01:41.000]我明白太放不开你的爱
[01:46.000]太熟悉你的关怀分不开
[01:51.000]想你算是安慰还是悲哀
[01:55.000]而现在就算时针都停摆
[02:00.000]就算生命像尘埃分不开
[02:05.000]我们也许反而更相信爱
[02:34.000]''
[02:35.000]我明白太放不开你的爱
[02:40.000]太熟悉你的关怀分不开
[02:45.000]想你算是安慰还是悲哀
[02:49.000]而现在就算时针都停摆
[02:54.000]就算生命像尘埃分不开
[03:00.000]我们也许反而更相信爱`,

  lrcData: "",
  dataWords: "",

  analyser: {},
  dataArry: [],
};

},
name: "HelloWorld",
methods: {

// 当音频播放
onPlay() {
  if (this.isInit) {
    return;
  }
  let audioEle = document.getElementById("myAudio");
  //初始化
  var audCtx = new AudioContext(); //创建音频上下文
  this.analyser = audCtx.createAnalyser();
  const source = audCtx.createMediaElementSource(audioEle); //创建音频源节点
  this.analyser.fftSize = 512; //一个无符号长整形 (unsigned long) 的值,代表了快速傅里叶变换(分析器)的窗口大小
  //创建数组,用于接受节点分析器分析的数据
  this.dataArry = new Uint8Array(this.analyser.frequencyBinCount); //这里并不是声明一个普通数组,而是需要声明一个无符号的八位整数,刚好是一个字节。并且数组长度需要刚好等于频谱图横坐标长度
  source.connect(this.analyser);
  this.analyser.connect(audCtx.destination);

  this.isInit = true;
},
//把分析器分析的数据不断绘制到canvas
draw() {
  let cvs = document.getElementById("myCanvas");
  let ctx = cvs.getContext("2d");
  requestAnimationFrame(this.draw);
  //清空画布
  const { width, height } = cvs;
  ctx.clearRect(0, 0, width, height);
  //判断分析器是否初始化
  if (!this.isInit) {
    return;
  }
  //分析器节点分析出数据到数组中
  this.analyser.getByteFrequencyData(this.dataArry); //让分析器节点分析当前音频源数据,把分析结果添加到数组
  const len = this.dataArry.length;
  const barWidth = width / len / 2;
  ctx.fillStyle = "#41b883";
  for (let i = 0; i < len; i++) {
    const data = this.dataArry[i]; //<256
    const barHeight = (data / 255) * height;
    const x1 = i * barWidth + width / 2;
    const x2 = width / 2 - (i + 1) * barWidth;
    const y = height - barHeight;
    ctx.fillRect(x1, y, barWidth, barHeight);
    ctx.fillRect(x2, y, barWidth, barHeight);
  }
},
initCancas() {
  let canvas = document.getElementById("myCanvas");
  let ctx = canvas.getContext("2d");
  // ctx.fillStyle = "#000";
  ctx.fillRect(10, 10, 280, 100);
},
//歌词数据转化为数组
formatLrc() {
  var strLrc = this.LRC.split("\n");
  let arr = [];
  for (var i = 0; i < strLrc.length; i++) {
    var str = strLrc[i];
    var parts = str.split("]");
    var timeStr = parts[0].substring(1);
    var obj = {
      time: this.formatTime(timeStr),
      words: parts[1],
    };
    arr.push(obj);
  }
  this.lrcData = arr;
},
//时间转换(秒)
formatTime(time) {
  var parts = time.split(":"); //[03:00.000]==>[03,00.00]
  return +parts[0] * 60 + +parts[1]; //计算秒
},
audioTime(e) {
  var time = e.target.currentTime; //当前播放器时间
  for (var i = 0; i < this.lrcData.length; i++) {
    if (time < this.lrcData[i].time) {
      //循环歌词数组,当播放器当前时间第一次小于歌词时间时当前数组下标减一即为当前时间数组所对应歌词下标
      this.lrcTime = this.lrcData[i].time - this.lrcData[i - 1].time;
      this.dataWords = this.lrcData[i - 1].words;
      return i - 1;
    }
  }
},

},
watch: {

dataWords() {
  this.className = "text";
  setTimeout(() => {
    this.className = "text load";
  }, 40);
},

},
mounted() {

this.formatLrc();
this.initCancas();
this.draw();

},
};

background-size: 0 100%;

}
100% {

background-size: 100% 100%;

}
}
.text {
background: #7e7e7e -webkit-linear-gradient(left, #76ca16, #0fa046) no-repeat 0

0;

-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-size: 0 100%;
}
.load {
background-size: 100% 100%;
animation: scan linear;
}

总结
以上就是今天要讲的内容,本文仅仅简单介绍了音频可视化的实现,而MDN为我们提供了大量新的API,能实现的东西远不局限于音频可视化,大家可以发挥想象力和创造力,去实现更华丽的功能。

目录
相关文章
|
7天前
|
数据采集 监控 JavaScript
在 Vue 项目中使用预渲染技术
【10月更文挑战第23天】在 Vue 项目中使用预渲染技术是提升 SEO 效果的有效途径之一。通过选择合适的预渲染工具,正确配置和运行预渲染操作,结合其他 SEO 策略,可以实现更好的搜索引擎优化效果。同时,需要不断地监控和优化预渲染效果,以适应不断变化的搜索引擎环境和用户需求。
|
7天前
|
缓存 JavaScript 搜索推荐
Vue SSR(服务端渲染)预渲染的工作原理
【10月更文挑战第23天】Vue SSR 预渲染通过一系列复杂的步骤和机制,实现了在服务器端生成静态 HTML 页面的目标。它为提升 Vue 应用的性能、SEO 效果以及用户体验提供了有力的支持。随着技术的不断发展,Vue SSR 预渲染技术也将不断完善和创新,以适应不断变化的互联网环境和用户需求。
27 9
|
6天前
|
缓存 JavaScript UED
Vue 中实现组件的懒加载
【10月更文挑战第23天】组件的懒加载是 Vue 应用中提高性能的重要手段之一。通过合理运用动态导入、路由配置等方式,可以实现组件的按需加载,减少资源浪费,提高应用的响应速度和用户体验。在实际应用中,需要根据具体情况选择合适的懒加载方式,并结合性能优化的其他措施,以打造更高效、更优质的 Vue 应用。
|
5天前
|
JavaScript
如何在 Vue 中使用具名插槽
【10月更文挑战第25天】通过使用具名插槽,你可以更好地组织和定制组件的模板结构,使组件更具灵活性和可复用性。同时,具名插槽也有助于提高代码的可读性和可维护性。
13 2
|
5天前
|
JavaScript
Vue 中的插槽
【10月更文挑战第25天】插槽的使用可以大大提高组件的复用性和灵活性,使你能够根据具体需求在组件中插入不同的内容,同时保持组件的结构和样式的一致性。
11 2
|
5天前
|
前端开发 JavaScript 容器
在 vite+vue 中使用@originjs/vite-plugin-federation 模块联邦
【10月更文挑战第25天】模块联邦是一种强大的技术,它允许将不同的微前端模块组合在一起,形成一个统一的应用。在 vite+vue 项目中,使用@originjs/vite-plugin-federation 模块联邦可以实现高效的模块共享和组合。通过本文的介绍,相信你已经了解了如何在 vite+vue 项目中使用@originjs/vite-plugin-federation 模块联邦,包括安装、配置和使用等方面。在实际开发中,你可以根据自己的需求和项目的特点,灵活地使用模块联邦,提高项目的可维护性和扩展性。
|
6天前
|
JavaScript 前端开发 UED
vue 提高 tree shaking 的效果
【10月更文挑战第23天】提高 Vue 中 Tree shaking 的效果需要综合考虑多个因素,包括模块的导出和引用方式、打包工具配置、代码结构等。通过不断地优化和调整,可以最大限度地发挥 Tree shaking 的优势,为 Vue 项目带来更好的性能和用户体验。
|
6天前
|
缓存 JavaScript UED
Vue 中异步加载模块的方式
【10月更文挑战第23天】这些异步加载模块的方式各有特点和适用场景,可以根据项目的需求和架构选择合适的方法来实现模块的异步加载,以提高应用的性能和用户体验
|
6天前
|
JavaScript 测试技术 UED
解决 Vue 项目中 Tree shaking 无法去除某些模块
【10月更文挑战第23天】解决 Vue 项目中 Tree shaking 无法去除某些模块的问题需要综合考虑多种因素,通过仔细分析、排查和优化,逐步提高 Tree shaking 的效果,为项目带来更好的性能和用户体验。同时,持续关注和学习相关技术的发展,不断探索新的解决方案,以适应不断变化的项目需求。
|
7天前
|
JavaScript 搜索推荐 前端开发
Vue SSR 预渲染的广泛应用场景及其优势
【10月更文挑战第23天】Vue SSR 预渲染技术在众多领域都有着广泛的应用价值,可以显著提升网站的性能、用户体验和搜索引擎优化效果。随着技术的不断发展和完善,其应用场景还将不断拓展和深化
20 2