我用canvas帶你看一場流星雨

2022-10-17 12:00:48

前言

最近總是夢見一些小時候的故事,印象最深刻的就是夏天坐在屋頂上,看著滿天的繁星,一顆,兩顆,三顆...不由自主地開始了數星星的過程。不經意間,一顆流星劃過夜間,雖然只是轉瞬即逝,但它似乎比夜空中的其它繁星更吸引著我。聽老人說,看見流星的時候許願,願望是可以實現的,此時早已把數星星拋之腦後,開始期待著下一顆流星的出現。但是那天晚上,流星再也沒有出現,這也成了自己小時候的一個遺憾。

今天,我決定用canvas為大家帶來一場流星雨視覺盛宴。

如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖 第一時間獲取最新文章~

需求分析

首先我們需要的元素有:夜空、滿天繁星、流星雨。

滿天繁星: 這個其實就是畫上一個個點,然後不斷的通過顏色交替,營造出一種星星閃爍的意境。

流星雨: 流星處於他自己的運動軌跡之中,當前的位置最亮,輪廓最清晰,而之前劃過的地方離當前位置軌跡距離越遠就越暗淡越模糊,其實它就是一個漸變的過程,恰巧canvas有方法可以建立一個沿引數座標指定的直線的漸變。然後讓它從右上向左下移動,這樣就能營造一種流星雨的效果,同時實現動畫的迴圈。

OK,需求分析結束,準備動手開幹~

實現過程

1.繪製滿天繁星

//建立一個星星物件
class Star {
  constructor() {
    this.x = windowWidth * Math.random(); //橫座標
    this.y = 5000 * Math.random(); //縱座標
    this.text = "."; //文字
    this.color = "white"; //顏色
  }
  //初始化
  init() {
    this.getColor();
  }
  //繪製
  draw() {
    context.fillStyle = this.color;
    context.fillText(this.text, this.x, this.y);
  }
}

//畫星星
for (let i = 0; i < starCount; i++) {
  let star = new Star();
  star.init();
  star.draw();
  arr.push(star);
}

來看下此時的效果:

夜空中的滿天繁星現在是有了,但是缺乏一點意境,我們得想辦法讓這些繁星都閃爍起來。

2.滿天繁星閃起來

//建立一個星星物件
class Star {
  constructor() {
    this.x = windowWidth * Math.random(); //橫座標
    this.y = 5000 * Math.random(); //縱座標
    this.text = "."; //文字
    this.color = "white"; //顏色
  }
	// 獲取隨機顏色
  getColor() {
    let _r = Math.random();
    if (_r < 0.5) {
      this.color = "#333";
    } else {
      this.color = "white";
    }
  }

  //初始化
  init() {
    this.getColor();
  }
  //繪製
  draw() {
    context.fillStyle = this.color;
    context.fillText(this.text, this.x, this.y);
  }
}

//畫星星
for (let i = 0; i < starCount; i++) {
  let star = new Star();
  star.init();
  star.draw();
  arr.push(star);
}

//繁星閃起來
let t1
function playStars() {
  for (let n = 0; n < starCount; n++) {
    arr[n].getColor();
    arr[n].draw();
  }
  t1 = requestAnimationFrame(playStars);
}

繁星閃爍的元素就在於這個getColor方法,通過不斷地切換星星的顏色,來達到星星閃爍的效果。

再來看看這時的效果:

此刻的自己就可以開始數星星了,一顆,兩顆,三顆...

3.繪製流星

簡單點理解,流星其實就是一條漸變的線段,當前的位置最亮,輪廓最清晰,而之前劃過的地方離當前位置軌跡距離越遠就越暗淡越模糊。

這裡的關鍵API是createLinearGradient,用於建立一個沿引數座標指定的直線的漸變。

語法:

CanvasGradient ctx.createLinearGradient(x0, y0, x1, y1);

  • x0:起點的 x 軸座標。
  • y0:起點的 y 軸座標。
  • x1:終點的 x 軸座標。
  • y1:終點的 y 軸座標。

使用createLinearGradient() 方法初始化一個線性漸變。在這個線性漸變中新增三種顏色,達到一種漸變的效果來模擬出流星劃過夜空的狀態。

/**繪製流星**/
  draw() {
    //繪製一個流星的函數
    context.save();
    context.beginPath();
    context.lineWidth = 1; //寬度
    context.globalAlpha = this.alpha; //設定透明度
    //建立橫向漸變顏色,起點座標至終點座標
    let line = context.createLinearGradient(
      this.x,
      this.y,
      this.x + this.width,
      this.y - this.height
    );
    //分段設定顏色
    line.addColorStop(0, "white");
    line.addColorStop(0.3, this.color1);
    line.addColorStop(0.6, this.color2);
    context.strokeStyle = line;
    //起點
    context.moveTo(this.x, this.y);
    //終點
    context.lineTo(this.x + this.width, this.y - this.height);
    context.closePath();
    context.stroke();
    context.restore();
  }

現在我們來看一看當年的那個流星:

4.流星劃過夜空

流星有了,現在我們得想辦法讓它動起來。這裡其實就是通過不斷地計算位置來達到流星動起來的效果。

move() {
    //清空流星畫素
    let x = this.x + this.width - this.offset_x;
    let y = this.y - this.height;
    context.clearRect(x - 3, y - 3, this.offset_x + 5, this.offset_y + 5);
    //重新計算位置,往左下移動
    this.countPos();
    //透明度增加
    this.alpha -= 0.002;
    //重繪
    this.draw();
  }

現在,我們就可以看到當年的那顆流星了,是不是很激動。稍安勿躁,為了彌補當年的遺憾,這裡決定來一場從未真實見過的流星雨。

5.流星雨

寫到這裡,實現流星雨其實就很簡單了,我們只需要再多生成一些流星,為它們各自分配不同的座標即可。

let t2
// 建立流星雨物件
class MeteorRain {
  constructor() {
    this.x = -1;
    this.y = -1;
    this.length = -1; //長度
    this.angle = 30; //傾斜角度
    this.width = -1; //寬度
    this.height = -1; //高度
    this.speed = 1; //速度
    this.offset_x = -1; //橫軸移動偏移量
    this.offset_y = -1; //縱軸移動偏移量
    this.alpha = 1; //透明度
    this.color1 = ""; //流星的色彩
    this.color2 = ""; //流星的色彩
  }
  init() {
    //初始化
    this.getPos();
    this.alpha = 1; //透明度
    this.getRandomColor();
    //最小長度,最大長度
    let x = Math.random() * 80 + 150;
    this.length = Math.ceil(x);
    x = Math.random() + 0.5;
    this.speed = Math.ceil(x); //流星的速度
    let cos = Math.cos((this.angle * 3.14) / 180);
    let sin = Math.sin((this.angle * 3.14) / 180);
    this.width = this.length * cos;
    this.height = this.length * sin;
    this.offset_x = this.speed * cos;
    this.offset_y = this.speed * sin;
  }
  /**獲取隨機顏色函數**/
  getRandomColor() {
    let a = Math.ceil(255 - 240 * Math.random());
    //中段顏色
    this.color1 = "rgba(" + a + "," + a + "," + a + ",1)";
    //結束顏色
    this.color2 = "black";
  }
  /**重新計算流星座標的函數**/
  countPos() {
    //
    //往左下移動,x減少,y增加
    this.x = this.x - this.offset_x;
    this.y = this.y + this.offset_y;
  }
  /**獲取隨機座標的函數**/
  getPos() {
    //
    //橫座標
    this.x = Math.random() * window.innerWidth; //視窗高度
    //縱座標
    this.y = Math.random() * window.innerHeight; //視窗寬度
  }
  /**繪製流星**/
  draw() {
    //繪製一個流星的函數
    context.save();
    context.beginPath();
    context.lineWidth = 1; //寬度
    context.globalAlpha = this.alpha; //設定透明度
    //建立橫向漸變顏色,起點座標至終點座標
    let line = context.createLinearGradient(
      this.x,
      this.y,
      this.x + this.width,
      this.y - this.height
    );
    //分段設定顏色
    line.addColorStop(0, "white");
    line.addColorStop(0.3, this.color1);
    line.addColorStop(0.6, this.color2);
    context.strokeStyle = line;
    //起點
    context.moveTo(this.x, this.y);
    //終點
    context.lineTo(this.x + this.width, this.y - this.height);
    context.closePath();
    context.stroke();
    context.restore();
  }
  move() {
    //清空流星畫素
    let x = this.x + this.width - this.offset_x;
    let y = this.y - this.height;
    context.clearRect(x - 3, y - 3, this.offset_x + 5, this.offset_y + 5);
    //重新計算位置,往左下移動
    this.countPos();
    //透明度增加
    this.alpha -= 0.002;
    //重繪
    this.draw();
  }
}

//繪製流星
function playRains() {
  for (let n = 0; n < rainCount; n++) {
    // console.log(rains, "--");
    let rain = rains[n];
    rain.move(); //移動
    if (rain.y > window.innerHeight) {
      //超出界限後重來
      context.clearRect(rain.x, rain.y - rain.height, rain.width, rain.height);
      rains[n] = new MeteorRain();
      rains[n].init();
    }
  }
  t2 = requestAnimationFrame(playRains);
}

6.merge視覺盛宴

流星極短暫的星星是也,它不像恆星和行星那般耀眼,卻用短暫的生命,劃破夜空,用瞬間瀟灑的弧線留住美麗的光輝。曇花和流星瞬間之美令我無法忘懷,大自然神奇的造物者給了我們許多的美,不論瞬間的還是永恆的,我們都要用真心去欣賞去品味。

通過合併前面五個步驟,我們就能夠一睹這流星剎那間的交錯,而後瞬間就穿透為永恆,只是一刻用生命幻化的美。

最後

今天的視覺盛宴就到這裡了,想要檢視原始碼的同學公眾號回覆流星雨

原文首發地址點這裡,歡迎大家關注公眾號 「前端南玖」,如果你想進前端交流群一起學習,請點這裡

我是南玖,我們下期見!!!