AudioContext技術和音樂可視化(2)

Intro

轉載請註明來源,能夠在測試博客查看完成效果。javascript

本篇講述如何繪製動態的星空,其實關聯到頻域數據已經沒什麼懸念了。css

1、使用Canvas繪圖

1.1 位置和大小

繪製背景的第一要務即是把canvas元素放置在背景這一層次上,避免遮蓋其餘元素。html

對我而言,我的習慣用css來設置大小和位置,用html來肯定渲染順序而不是z-index。html5

下面是html代碼。java

<html>

<body>
  <canvas id="background-canvas"></canvas>
  <!-- other elements -->
</body>

</html>

下面是css代碼。canvas

#background-canvas {
  position: fixed;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
  background-color: black;
}

fixed確保拖動頁面不會令背景也跟隨移動。瀏覽器

其他部分我想應該沒什麼有疑問的地方。dom

1.2 CanvasContext2D

對於canvas元素的繪圖操做我想不少人應該接觸過。函數

以繪製圓形爲例,使用以下代碼。性能

const canvas = document.getElementById("background-canvas");
const ctx = canvas.getContext("2d");

ctx.fillStyle='#fff';
ctx.beginPath();
ctx.arc(100,100,50,0,Math.PI*2); // 參數分別爲座標x,y,半徑,起始弧度,結束弧度
ctx.fill();

這樣就畫完了一個實心圓。

須要注意,canvas的大小經過css設置可能致使畫面被拉伸變形模糊,因此最好的辦法是繪製前肯定一下canvas的大小。

此外須要注意的是,重置大小會致使畫面清空,用這種方式能夠替代fillRect或者clearRect,有的瀏覽器平臺更快但也有瀏覽器更慢。能夠查閱這篇博文來參考如何提高canvas繪圖性能。

fillStyle可使用css的顏色代碼,也就是說咱們能夠寫下諸如rgbahsla之類的顏色,這給咱們編寫代碼提供了不少方便。

1.3 繪製星星

星空是由星星組成的這顯然不用多說了,先來看如何繪製單個星星。

星星的繪製方法不少,貼圖雖然便利但顯然不夠靈活,咱們的星星是要隨節奏改變亮度和大小的,利用貼圖的話就只能在alpha值和drawImage縮放來處理了。雖然是一種不錯的辦法,不過這裏我使用了RadialGradient來控制繪圖。

PS:RadialGradient 的性能比較差,大量使用會致使明顯的性能降低,這是一個顯著下降繪製效率的地方。

那麼,咱們先畫一個圓(加點細節預警)。

const canvas = document.getElementById("background-canvas");
const ctx = canvas.getContext("2d");

// 確保不會變形
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;

// 參數分別爲起始座標x,y,半徑,結束座標x,y,半徑
const gradient = ctx.createRadialGradient(100, 100, 0, 100, 100, 50); 
gradient.addColorStop(0.025, "#fff"); // 中心的亮白色
gradient.addColorStop(0.1, "rgba(255, 255, 255, 0.9)"); // 核心光點和四周的分界線
gradient.addColorStop(0.25, "hsla(198, 66%, 75%, 0.9)"); // 核心亮點往四周發散的藍光
gradient.addColorStop(0.75, "hsla(198, 64%, 33%, 0.4)"); // 藍光邊緣
gradient.addColorStop(1, "hsla(198, 64%, 33%, 0)"); // 淡化直至透明
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(100, 100, 50, 0, Math.PI * 2);
ctx.fill();

能夠在codepen查看效果或直接編輯你的星(圈)星(圈)。

看上去還不錯?

讓咱們用代碼控制它的亮度和大小。

const canvas = document.getElementById("background-canvas");
const ctx = canvas.getContext("2d");

// 確保不會變形
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;

// 經過energy控制亮度和大小
let energy = 255;
let radius = 50;
let energyChangeRate = -1;

function draw() {
  requestAnimationFrame(draw); // 定時繪製,requestAnimationFrame比setTimeout更好。
  energy += energyChangeRate; // 見過呼吸燈吧?咱們讓它變亮~再變暗~反覆循環~
  if (energy <= 0 || energy >= 255) energyChangeRate = -energyChangeRate;
    
  // 計算出當前的大小
  const r = radius + energy * 0.1;

  // 清空屏幕
  ctx.fillStyle = "black";
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  // 參數分別爲起始座標x,y,半徑,結束座標x,y,半徑
  const gradient = ctx.createRadialGradient(100, 100, 0, 100, 100, r);
  gradient.addColorStop(0.025, "#fff"); // 中心的亮白色
  gradient.addColorStop(0.1, "rgba(255, 255, 255, 0.9)"); // 核心光點和四周的分界線
  gradient.addColorStop(0.25, `hsla(198, 66%, ${Math.min(75+energy*0.01,100)}%, 0.9)`); // 核心亮點往四周發散的藍光
  gradient.addColorStop(0.75, `hsla(198, 64%, ${Math.min(33+energy*0.01,100)}%, 0.4)`); // 藍光邊緣
  gradient.addColorStop(1, "hsla(198, 64%, 33%, 0)"); // 淡化直至透明
  ctx.fillStyle = gradient;
  ctx.beginPath();
  ctx.arc(100, 100, r, 0, Math.PI * 2);
  ctx.fill();
}
draw();

能夠在codepen查看並編輯效果。

1.4 封裝星星

一般來講粒子系統不大會把單個粒子封裝成類,由於函數調用的開銷仍是蠻大的。。。

不過在這裏咱們這裏就先這樣了,方便理解和閱讀。渲染的瓶頸解決以前,粒子函數調用這點開銷根本不是回事兒。

const canvas = document.getElementById("background-canvas");
const ctx = canvas.getContext("2d");

// 確保不會變形
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;

// 用javascript原生的class而不是prototype
class Star {
  constructor(x, y, radius, lightness) {
    this.radius = radius;
    this.x = x;
    this.y = y;
    this.lightness;
  }

  draw(ctx, energy) {
    // 計算出當前的大小
    const r = this.radius + energy * 0.1;

    // 參數分別爲起始座標x,y,半徑,結束座標x,y,半徑
    const gradient = ctx.createRadialGradient(
      this.x,
      this.y,
      0,
      this.x,
      this.y,
      r
    );
    gradient.addColorStop(0.025, "#fff"); // 中心的亮白色
    gradient.addColorStop(0.1, "rgba(255, 255, 255, 0.9)"); // 核心光點和四周的分界線
    gradient.addColorStop(
      0.25,
      `hsla(198, 66%, ${Math.min(75 + energy * 0.01, 100)}%, 0.9)`
    ); // 核心亮點往四周發散的藍光
    gradient.addColorStop(
      0.75,
      `hsla(198, 64%, ${Math.min(33 + energy * 0.01, 100)}%, 0.4)`
    ); // 藍光邊緣
    gradient.addColorStop(1, "hsla(198, 64%, 33%, 0)"); // 淡化直至透明
    ctx.fillStyle = gradient;
    ctx.beginPath();
    ctx.arc(this.x, this.y, r, 0, Math.PI * 2);
    ctx.fill();
  }
}

const star = new Star(100, 100, 50);

let energy = 255;
let energyChangeRate = -1;

// 渲染函數來循環渲染!
function render() {
  requestAnimationFrame(render);
  energy += energyChangeRate;
  if (energy <= 0 || energy >= 255) energyChangeRate = -energyChangeRate;
  // 清空屏幕
  ctx.fillStyle = "black";
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  star.draw(ctx, energy);
}

// 開始渲染動畫!
render();

能夠在codepen查看代碼效果。

完成!

1.5 銀河

繪製銀河的核心在於隨機分佈的星星繞着同一中心點旋轉,分爲兩步來說,第一步是隨機分佈,這很簡單,用Math.random就行了。

// star 部分略

class Galaxy {
  constructor(canvas) {
    this.stars = [];
    this.canvas = canvas;
    this.ctx = canvas.getContext("2d");

    this.energy = 255;
    this.energyChangeRate = -2;
  }

  init(num) {
    for (let i = 0; i < num; i++) {
      this.stars.push(
        // 隨機生成必定數量的星星,初始化星星位置和大小。
        new Star(
          Math.random() * this.canvas.width,
          Math.random() * this.canvas.height,
          Math.random() * 10 + 1,
          Math.random() * 30 + 33
        )
      );
    }
  }

  render() {
    this.energy += this.energyChangeRate;
    if (this.energy <= 0 || this.energy >= 255)
      this.energyChangeRate = -this.energyChangeRate;

    // 清空屏幕
    this.ctx.fillStyle = "black";
    this.ctx.fillRect(0, 0, canvas.width, canvas.height);

    for (const star of this.stars) {
      star.draw(this.ctx, this.energy);
    }
  }
}

const canvas = document.getElementById("background-canvas");
// 確保不會變形
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
const galaxy = new Galaxy(canvas);
galaxy.init(50);

function render() {
  requestAnimationFrame(render);
  galaxy.render();
}

render();

能夠在codepen查看效果和完整代碼。

1.6 旋轉起來!

【加點細節預警】

接下來咱們爲星星準備軌道參數,讓它們動起來!

首先修改Star類,加入幾個字段。

class Star {
  constructor(x, y, radius, lightness, orbit, speed, t) {
    this.radius = radius;
    this.x = x;
    this.y = y;
    this.lightness;
    this.orbit = orbit; // 軌道
    this.speed = speed; // 運動速度
    this.t = t; // 三角函數x軸參數,用 sin/cos 組合計算位置
  }
    // 下略
}

修改初始化代碼。

// 前略  
  init(num) {
    const longerAxis = Math.max(this.canvas.width, this.canvas.height);
    const diameter = Math.round(
      Math.sqrt(longerAxis * longerAxis + longerAxis * longerAxis)
    );
    const maxOrbit = diameter / 2;

    for (let i = 0; i < num; i++) {
      this.stars.push(
        // 隨機生成必定數量的星星,初始化星星位置和大小。
        new Star(
          Math.random() * this.canvas.width,
          Math.random() * this.canvas.height,
          Math.random() * 10 + 1,
          Math.random() * 30 + 33,
          Math.random() * maxOrbit, // 隨機軌道
          Math.random() / 1000, // 隨機速度
          Math.random() * 100 // 隨機位置
        )
      );
    }
  // 後略

而後在Galaxy里加入控制移動的代碼。

move() {
    for (const star of this.stars) {
      console.log(star.orbit)
      star.x = this.canvas.width/2+ Math.cos(star.t) * star.orbit;
      star.y = this.canvas.height/2+ Math.sin(star.t) * star.orbit/2;
      star.t += star.speed;
    }

而後每一幀進行移動!

function render() {
  requestAnimationFrame(render);
  galaxy.render();
  galaxy.move(); // 動起來!
}

大功告成!

codepen查看完整源碼!

1.7 待續

PS:不保證粘貼的代碼都能跑,反正codepen上是都能的。

相關文章
相關標籤/搜索