vue 通過p5.js實現聲音視覺化 顏色跟隨歌曲圖片主色變化
阿新 • • 發佈:2021-01-19
注意:
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_()