1. 程式人生 > 其它 >vue 通過p5.js實現聲音視覺化 顏色跟隨歌曲圖片主色變化

vue 通過p5.js實現聲音視覺化 顏色跟隨歌曲圖片主色變化

技術標籤:vuejsvue.js前端視覺化

注意:

1. 最好先在https://editor.p5js.org/上寫好想要的效果再往vue上移,不過直接在vue上寫也行;

2. 關於p5js: 先在global mode下寫再轉化成instance mode 比較簡單,但直接在instance mode下寫也行;

關於這兩種mode:https://github.com/processing/p5.js/wiki/Global-and-instance-mode

3. 關於引入p5js:用npm會有問題,我是用的cdn, 既要引入p5.js也要引入p5.sound.js

4. http協議下不行哦,要https或者localhost才行

最終程式碼:

/* eslint-disable */
//p5js程式碼,看起來和一般的不一樣因為是 instance mode 
const s = sketch => {
  //要取得多少個不同頻率的音量資料
  const ORI_NUM = 64
  //要顯示多少個不同頻率的音量資料
  const NUM = 48
  //圖案的最長和最短
  const MIN_LENGTH = 112.5
  const MAX_LENGTH = 185
  //那個一條一條的圖案的寬高
  const WID = 3
  const HIG = 70
  //最後的顏色再變得亮一點,有多亮
  const WHITE = 20
  //圖案裡,條的顏色預設值rgba
  let color1 = [255, 255, 255, 180]
  //圖案裡,面的顏色預設值rgba
  let color2 = [255, 255, 255, 150]
  //儲存fft物件
  let fft
  sketch.setup = () => {
    //取得圖片dom並載入
    let imgDom = document.querySelector('.middle img')
    sketch.loadImage(imgDom.src, afterLoadImg)
    //要把canvas放到哪個div裡面,這個div的寬高要在css裡設定好
    let div = document.querySelector('#p5sketch')
    //建立畫布
    sketch.createCanvas(div.offsetWidth, div.offsetHeight)
    //取得AudioContext
    sketch.audioContext_ = sketch.getAudioContext()
    //取得聲音dom,注意vue專案裡只能有一個audio tag,切換歌曲時只變src
    let el = document.querySelector('audio')
    //根據聲音dom建立web audio API source node, 並且連線主輸出
    const source = sketch.audioContext_.createMediaElementSource(el)
    source.connect(p5.soundOut)
    //0.8是變化程度,越小圖案越抖
    fft = new p5.FFT(0.8, ORI_NUM)
    fft.setInput(source)
    sketch.angleMode(sketch.DEGREES)
  }
  //重新載入img
  sketch.reloadImg_ = () => {
    let imgDom = document.querySelector('.middle img')
    sketch.loadImage(imgDom.src, afterLoadImg)
  }
  sketch.draw = () => {
    sketch.clear()
    //取得不同頻率下的音量
    let spectrum = fft.analyze()
    sketch.noStroke()
    sketch.fill(
      color2[0] + WHITE,
      color2[1] + WHITE,
      color2[2] + WHITE,
      color2[3]
    )
    sketch.translate(sketch.width / 2, sketch.height / 2)
    //畫那個面面
    sketch.beginShape()
    for (let i = 0; i < NUM; i++) {
      let r = sketch.map(spectrum[i], 0, 255, MIN_LENGTH, MAX_LENGTH)
      let angle = sketch.map(i, 0, NUM - 1, -120, 60)
      //畫那個條條,先畫半個,因為要對稱
      sketch.push()
      sketch.fill(
        color1[0] + WHITE,
        color1[1] + WHITE,
        color1[2] + WHITE,
        color1[3]
      )
      sketch.rotate(angle - 30)
      sketch.rect(-WID / 2, r - HIG, WID, HIG, WID)
      sketch.pop()
      //面面的點
      let x = r * sketch.cos(angle)
      let y = r * sketch.sin(angle)
      sketch.curveVertex(x, y)
    }
    for (let i = NUM - 1; i >= 0; i--) {
      let r = sketch.map(spectrum[i], 0, 255, MIN_LENGTH, MAX_LENGTH)
      let angle = sketch.map(i, NUM - 1, 0, 60, 240)
      //另半個條條
      sketch.push()
      sketch.fill(
        color1[0] + WHITE,
        color1[1] + WHITE,
        color1[2] + WHITE,
        color1[3]
      )
      sketch.rotate(angle - 30)
      sketch.rect(-WID / 2, r - HIG, WID, HIG, WID)
      sketch.pop()
      //還是面面的點
      let x = r * sketch.cos(angle)
      let y = r * sketch.sin(angle)
      sketch.curveVertex(x, y)
    }
    sketch.endShape(sketch.CLOSE)

    // sketch.fill(255, 255, 255)
    // sketch.ellipse(0, 0, 225)
  }
  //這個方法是用來處理谷歌瀏覽器不讓自動播放的政策的,使用者沒給手勢AudioContext用不了
  sketch.resumeContext_ = () => {
    sketch.audioContext_.resume()
  }
  function afterLoadImg(img) {
    img.loadPixels()
    //取得圖片的兩個主色調
    let twoColors = getTwoColors(img)
    //如果太黑了就調亮點,不然在黑色背景上看不出來
    for (const col of twoColors) {
      if (col.r < 40 && (col.g < 40) & (col.b < 40)) {
        col.r += 40
        col.g += 40
        col.b += 40
      }
    }
    //把計算出來的顏色賦值給面面和條條
    color1[0] = twoColors[1].r
    color1[1] = twoColors[1].g
    color1[2] = twoColors[1].b
    color2[0] = twoColors[0].r
    color2[1] = twoColors[0].g
    color2[2] = twoColors[0].b
  }
  function getTwoColors(img) {
    //把圖片先整成10*10的,這樣計算量小一些
    img.resize(10, 10)
    //迴圈這100個畫素,把他們根據七種顏色分類
    let classifiedPix = {
      color0: [],
      color1: [],
      color2: [],
      color3: [],
      color4: [],
      color5: [],
      color6: []
    }
    for (let i = 0; i < 400; i += 4) {
      let r = img.pixels[i]
      let g = img.pixels[i + 1]
      let b = img.pixels[i + 2]
      let hue = getHue(r, g, b)
      classifiedPix['color' + sketch.round(hue / 60)].push(r, g, b)
    }
    //選出最多的兩種顏色類
    let color1sts = []
    let color2nds = []
    for (let cor in classifiedPix) {
      if (classifiedPix[cor].length > color1sts.length) {
        color1sts = classifiedPix[cor]
        continue
      } else if (classifiedPix[cor].length > color2nds.length) {
        color2nds = classifiedPix[cor]
      }
    }
    //計算這兩種顏色類的具體顏色
    let color1st = { r: 0, g: 0, b: 0 }
    let color2nd = { r: 0, g: 0, b: 0 }
    for (let i = 0; i < color1sts.length; i += 3) {
      let r = color1sts[i] / (color1sts.length / 3)
      let g = color1sts[i + 1] / (color1sts.length / 3)
      let b = color1sts[i + 2] / (color1sts.length / 3)
      color1st.r += r
      color1st.g += g
      color1st.b += b
    }
    for (let i = 0; i < color2nds.length; i += 3) {
      let r = color2nds[i] / (color2nds.length / 3)
      let g = color2nds[i + 1] / (color2nds.length / 3)
      let b = color2nds[i + 2] / (color2nds.length / 3)
      color2nd.r += r
      color2nd.g += g
      color2nd.b += b
    }
    return [color1st, color2nd]
  }
  //根據rgb的數值取得色調,也就是hsv裡的h
  function getHue(red, green, blue) {
    let min = Math.min(Math.min(red, green), blue)
    let max = Math.max(Math.max(red, green), blue)

    if (min == max) {
      return 0
    }
    let hue
    if (max == red) {
      hue = (green - blue) / (max - min)
    } else if (max == green) {
      hue = 2 + (blue - red) / (max - min)
    } else {
      hue = 4 + (red - green) / (max - min)
    }
    hue = hue * 60
    if (hue < 0) hue += 360
    return Math.round(hue)
  }
}

export const audioVis = () => {
  //p5sketch是要把canvas放在那個div裡的id
  return new p5(s, 'p5sketch')
}

在vue中使用:

需要watch當前歌曲的變化,一變化就呼叫下面

if (!this.myp5js) {
    this.myp5js = audioVis()
}
//更新圖案顏色
this.myp5js.reloadImg_()

當點選播放按鈕時呼叫下面, 呼叫多次沒事,但其實只需要在第一首歌的時候呼叫一次把audioContext啟用就行了

this.myp5js.resumeContext_()