Js 音频律动

2023-06-16 16:23:47 浏览数 (1)

这段时间在独立写音乐项目,在学习过程中接触到了JS的音频律动,于是找到了以下项目

以上是效果图

下面分享代码:

HTML结构

代码语言:javascript复制
      Document

CSS样式

代码语言:javascript复制
* {
  margin: 0;
  padding: 0;
}

body {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden;
  background: url(./test.jpg) center;
  background-size: cover;
  backdrop-filter: blur(50px) grayscale(50%);
}

.music-box {
  position: relative;
  width: 400px;
  height: 400px;
  display: flex;
  justify-content: center;
  align-items: center;
}

.my-canvas {
  position: absolute;
  top: 0;
}

.my-music-btn {
  position: relative;
  width: 250px;
  height: 250px;
  background: url(./test.jpg);
  background-size: cover;
  border-radius: 50%;
  border: none;
  outline: none;
  animation: music-btn-anim 20s infinite linear;
}

.my-music-btn.rotate {
  animation-play-state: paused;
}

@keyframes music-btn-anim {
  from {
      transform: rotate(0deg);
  }

  to {
      transform: rotate(360deg);
  }
}

JS

{tabs-pane label="代码部分"}

代码语言:javascript复制
// 音乐播放器
class MusicPlayer {
  constructor(data = {
      musicSrc: "./test.mp3",
      // musicImgSrc: "./music.jpg",
      effectColor: "#FFFFFF"
  }) {
      this._requestID = null;
      // 特效单体
      this._effectEntity = new Entity();
      this._effectEntity.addComp(new MusicBtnSingleComp({
          callback: () => {
              this._effectEntity.getComp("MusicBtnSingleComp").isRotate = !this._effectEntity.getComp("MusicBtnSingleComp").isRotate;
              !this._effectEntity.getComp("MusicSingleComp").isReady &&(this._effectEntity.getComp("MusicSingleComp").isReady = true);
              this._effectEntity.getComp("MusicSingleComp").isPlay = !this._effectEntity.getComp("MusicSingleComp").isPlay;

              if(!this._effectEntity.getComp("MusicSingleComp").isPlay) {
                  cancelAnimationFrame(this._requestID);
              } else {
                  this._requestID = requestAnimationFrame(this._renderFrame.bind(this));
              }
          }
      }));
      this._effectEntity.addComp(new MusicSingleComp({
          musicSrc: data.musicSrc
      }));
      this._effectEntity.addComp(new MusicEffectSingleComp({
          effectColor: data.effectColor
      }))
  }

  _renderFrame() {
      this._requestID = requestAnimationFrame(this._renderFrame.bind(this));

      this._effectEntity.getComp("MusicEffectSingleComp").byteFrequencyDate = this._effectEntity.getComp("MusicSingleComp").byteFrequencyDate;
  }
}

// 单体
class Entity {
  constructor() {
      this._compMap = new Map();
  }

  addComp(comp) {
      this._compMap.set(comp.name, comp);
  }

  getComp(compName) {
      return this._compMap.get(compName);
  }
}

// 音乐按钮
class MusicBtnSingleComp {
  constructor(data) {
      this.name = "MusicBtnSingleComp";
      this._isRotate = false;
      this._musicBtnDom = document.querySelector(".my-music-btn");
      this._musicBtnDom.addEventListener("click", data.callback);
  }

  set isRotate(value) {
      if (value) {
          this._musicBtnDom.classList.remove("rotate");
      } else {
          this._musicBtnDom.classList.add("rotate");
      }
      this._isRotate = value;
  }

  get isRotate() {
      return this._isRotate;
  }
}

// 音乐
class MusicSingleComp {
  constructor(data) {
      this.name = "MusicSingleComp";
      this._fftSize = 512;

      this._myAudioDom = document.createElement("audio");
      this._myAudioDom.src = data.musicSrc;
      this._myAudioDom.loop = true;

      this._isReady = false;
      this._isPlay = false;
      this._analyser = null;
      this._dataArray = [];
  }

  set isReady(value) {
      if (value) {
          const ctx = new window.AudioContext();
          this._analyser = ctx.createAnalyser();
          this._analyser.fftSize = this._fftSize;
          const source = ctx.createMediaElementSource(this._myAudioDom);
          source.connect(this._analyser);
          this._analyser.connect(ctx.destination);
          const bufferLength = this._analyser.frequencyBinCount;
          this._dataArray = new Uint8Array(bufferLength);
      }
      this._isReady = value;
  }

  get isReady() {
      return this._isReady;
  }

  set isPlay(value) {
      if (value) {
          this._myAudioDom.play();
      } else {
          this._myAudioDom.pause();
      }
      this._isPlay = value;
  }

  get isPlay() {
      return this._isPlay;
  }

  get byteFrequencyDate() {
      this._analyser.getByteFrequencyData(this._dataArray);
      return this._dataArray.slice(0, 120);
  }
}

// 音乐特效
class MusicEffectSingleComp {
  constructor(data) {
      this.name = "MusicEffectSingleComp";
      this._effectColor = data.effectColor;
      this._canvasDom = document.querySelector(".my-canvas");
      this._canvasDom.width = 400;
      this._canvasDom.height = 400;
      this._ctx = this._canvasDom.getContext("2d");
      this._byteFrequencyData;
      this._randomData = Uint8Array.from(new Uint8Array(120), (v,k) => k);
      this._randomData.sort(() => Math.random() - 0.5);
      this.byteFrequencyDate = new Uint8Array(120).fill(0);
  }

  set byteFrequencyDate(value) {
      this._byteFrequencyData = value;
      const bData = [];
      this._randomData.forEach(value => {
          bData.push(this._byteFrequencyData[value]);
      })

      const angle = Math.PI * 2 / bData.length;
      this._ctx.clearRect(0, 0, this._canvasDom.width, this._canvasDom.height);
      this._ctx.fillStyle = this._effectColor;
      this._ctx.save();
      this._ctx.translate(this._canvasDom.width / 2, this._canvasDom.height / 2);
      bData.forEach((value, index) => {
          this._ctx.save();
          this._ctx.rotate(angle * index);
          this._ctx.beginPath();
          const h = value / 256 * 60;
          this._ctx.roundRect(-4, 140, 4, (h < 4) ? 4 : h, 4);
          // 若上行的 roundRect 存在兼容性问题可以更换为下面注释的代码
          // this._ctx.fillRect(-4, 140,  4, (h < 4) ? 4 : h);
          this._ctx.fill();
          this._ctx.restore();
      });
      this._ctx.restore();
  }
}

new MusicPlayer();

{/tabs-pane} {tabs-pane label="代码解释"} 这是一个使用 JavaScript 原生 API 实现的音乐播放器,包含音乐按钮、音乐、音乐特效三个部分。其中:

  • MusicPlayer:音乐播放器类,通过构造函数创建音乐播放器实例,同时包含特效单体(EffectEntity)。
  • Entity:单体类,通过 addComp 和 getComp 方法向特效单体中添加和获取组件。
  • MusicBtnSingleComp:音乐按钮组件,包含事件监听、旋转特效等。
  • MusicSingleComp:音乐组件,负责音乐的加载、配置播放参数和获取音频频谱数据。
  • MusicEffectSingleComp:音乐特效组件,通过获取音频频谱数据,实现了可视化的音乐特效。

其中 MusicEffectSingleComp 中的 _ctx.roundRect 方法,可能是用户自定义的实现;如果有兼容性问题,可以更换到代码注释处的相应代码。 {/tabs-pane}

使用时仅需将图片和音频放于项目根目录并重命名为test.jpgtest.mp3即可

0 人点赞