canvas 鼠標點擊出現煙花效果

鼠標點擊出現煙花效果

主要想法:html

  • 鼠標點擊事件觸發圓點出如今鼠標處web

  • 圓點大小,顏色,數量隨機canvas

  • 有一個小白圈作爲軌跡,不斷改變半徑變大變淡消失dom

  • 圓點向外移動,自身變小,消失。隨着移動的距離遠,移動速度跟着變函數

須要處理的是,從鼠標點擊處能夠肯定圓點的x,y軸,可是動去哪,怎麼動是一個問題,
因此先肯定圓點的目標座標,再從圓點的原始座標(即鼠標點擊處)向目標座標移動,而且x,y 成比例 去移動纔不會在x,y移動距離不相等的狀況下 不平衡動畫

隨機生成圓點目標座標的函數代碼:this

let endpos = (x, y) => {
  let angle = random(0, 360) * Math.PI / 180,
    value = random(20, 150),
    radius = [-1, 1][random(0, 1)] * value;

  return {
    x: x + radius * Math.cos(angle),
    y: y + radius * Math.sin(angle)
  }
}

由於須要大量的隨機數,因此封裝一個生成隨機數函數spa

let random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

圓點的移動函數,咱們須要的移動是從原始座標向目標座標由快而慢。
生成比例分紅3段。那就是原始座標到第一段座標爲第一段,第一段座標到第二段座標爲第二段,第二段座標到目標座標爲第三段。若是當前座標是處於第一段內,則速度越快,以此類推code

如今兩種狀況,一種是目標座標大於原始座標,那麼,第一段就爲最接近原始座標的那一段(Math.max(current, ff) == current ? (Math.max(current, mm) == current ? s : m) : f),第二種狀況是目標座標小於原始座標,那麼就反過來,第一段爲離原始座標最遠的那一段。htm

//根據不一樣距離段設置前行的步伐,分爲3個階段,離出發點近的那一段爲速度最快,中間爲速度通常,最遠爲速度最慢
//區分目標點小於出發點的狀況
//ratio爲兩點之間的距離的行走比例,比例數值越大,行走越慢
  moveFun(start, end, current) {
    let s = random(26, 35),
      m = random(20, 25),
      f = random(15, 20),
      ff = start.x + ~~((end.x - start.x) / 3),
      mm = ff + ~~((end.x - start.x) / 3),
      ratio = end.x >= start.x ? (Math.max(current, ff) == current ? (Math.max(current, mm) == current ? s : m) : f) : (Math.max(current, ff) == current ? f : (Math.max(current, mm) == current ? m : s)),
      mp = {
        x: end.x - start.x,
        y: end.y - start.y
      };

    return {
      x: Math.abs(mp.x / ratio),
      y: Math.abs(mp.y / ratio)
    }
  }

每個小圓點作爲一個個體,自帶移動函數,和移動的目標座標。每次Animation時,圓點進行重繪更新最新的座標

小圓點的代碼:

class Circle {
  constructor(x, y) {
    this.r = random(9, 13)
    this.opos = {}
    this.x = this.opos.x = x
    this.y = this.opos.y = y

    this.colors = ['#FF1461', '#18FF92', '#5A87FF', '#FBF38C']
    this.color = this.colors[random(0, this.colors.length)];
    this.tpos = endpos(x, y)
  }

  creatCircle(ctx) {
    ctx.beginPath()
    ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI)
    ctx.closePath()
    ctx.fillStyle = this.color
    ctx.fill()
  }

  //根據不一樣距離段設置前行的步伐,分爲3個階段,離出發點近的那一段爲速度最快,中間爲速度通常,最遠爲速度最慢
  //區分目標點小於出發點的狀況
  //ratio爲兩點之間的距離的行走比例,比例數值越大,行走越慢
  moveFun(start, end, current) {
    let s = random(26, 35),
      m = random(20, 25),
      f = random(15, 20),
      ff = start.x + ~~((end.x - start.x) / 3),
      mm = ff + ~~((end.x - start.x) / 3),
      ratio = end.x >= start.x ? (Math.max(current, ff) == current ? (Math.max(current, mm) == current ? s : m) : f) : (Math.max(current, ff) == current ? f : (Math.max(current, mm) == current ? m : s)),
      mp = {
        x: end.x - start.x,
        y: end.y - start.y
      };

    return {
      x: Math.abs(mp.x / ratio),
      y: Math.abs(mp.y / ratio)
    }
  }

  //根據計算出來的移動值去移動
  //若是目標座標大於原始座標則向右移動,最大不能超過目標座標,反之向左移動最小不能小於目標座標
  move() {
    var movepos = this.moveFun(this.opos, this.tpos, this.x);

    this.x = (this.opos.x > this.tpos.x) ? Math.max(this.x - movepos.x, this.tpos.x) : Math.min(this.x + movepos.x, this.tpos.x)
    this.y = this.opos.y > this.tpos.y ? Math.max(this.y - movepos.y, this.tpos.y) : Math.min(this.y + movepos.y, this.tpos.y)
    this.r = Math.max(Math.abs((this.r - Math.random() / 1.2).toFixed(2)), 0)

  }
}

大圓則是從鼠標點擊處建立的一個圓,並不斷改變其半徑向外擴展,而且愈來愈透明,當它已經很大而且很透明時意味着咱們的動畫要結束了,因此以這個爲標準,中止動畫,而且清空屏幕

大圓代碼:

class BigCircle {
  constructor(x, y) {
    this.bR = random(16, 32)
    this.overR = random(60, 100)
    this.x = x
    this.y = y
    this.op = 1
  }

  creatBigCircle(ctx) {
    ctx.beginPath()
    ctx.arc(this.x, this.y, this.bR, 0, 2 * Math.PI)
    ctx.closePath()
    ctx.strokeStyle = 'rgba(128, 128, 128, ' + this.op + ')'
    ctx.stroke()
  }

  changeR() {
    this.bR = Math.min(this.bR += random(1, 4), this.overR);
    this.op = Math.max((this.op - Math.random() / 12).toFixed(2), 0)
  }

  //檢查是否運行完畢,以大圓爲標準清空屏幕
  complete() {
    return this.bR >= this.overR && this.op <= 0;
  }
}

所有代碼:

//canvas鼠標點擊煙花特效

let endpos = (x, y) => {
  let angle = random(0, 360) * Math.PI / 180,
    value = random(20, 150),
    radius = [-1, 1][random(0, 1)] * value;

  return {
    x: x + radius * Math.cos(angle),
    y: y + radius * Math.sin(angle)
  }
}

let random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

class Circle {
  constructor(x, y) {
    this.r = random(9, 13)
    this.opos = {}
    this.x = this.opos.x = x
    this.y = this.opos.y = y

    this.colors = ['#FF1461', '#18FF92', '#5A87FF', '#FBF38C']
    this.color = this.colors[random(0, this.colors.length)];
    this.tpos = endpos(x, y)
  }

  creatCircle(ctx) {
    ctx.beginPath()
    ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI)
    ctx.closePath()
    ctx.fillStyle = this.color
    ctx.fill()
  }

  //根據不一樣距離段設置前行的步伐,分爲3個階段,離出發點近的那一段爲速度最快,中間爲速度通常,最遠爲速度最慢
  //區分目標點小於出發點的狀況
  //ratio爲兩點之間的距離的行走比例,比例數值越大,行走越慢
  moveFun(start, end, current) {
      let s = random(26, 35),
        m = random(20, 25),
        f = random(15, 20),
        ff = start.x + ~~((end.x - start.x) / 3),
        mm = ff + ~~((end.x - start.x) / 3),
        ratio = end.x >= start.x ? (Math.max(current, ff) == current ? (Math.max(current, mm) == current ? s : m) : f) : (Math.max(current, ff) == current ? f : (Math.max(current, mm) == current ? m : s)),
        mp = {
          x: end.x - start.x,
          y: end.y - start.y
        };

      return {
        x: Math.abs(mp.x / ratio),
        y: Math.abs(mp.y / ratio)
      }
    }
    
    //根據計算出來的移動值去移動
    //若是目標座標大於原始座標則向右移動,最大不能超過目標座標,反之向左移動最小不能小於目標座標
  move() {
    var movepos = this.moveFun(this.opos, this.tpos, this.x);

    this.x = (this.opos.x > this.tpos.x) ? Math.max(this.x - movepos.x, this.tpos.x) : Math.min(this.x + movepos.x, this.tpos.x)
    this.y = this.opos.y > this.tpos.y ? Math.max(this.y - movepos.y, this.tpos.y) : Math.min(this.y + movepos.y, this.tpos.y)
    this.r = Math.max(Math.abs((this.r - Math.random() / 1.2).toFixed(2)), 0)

  }
}

class BigCircle {
  constructor(x, y) {
    this.bR = random(16, 32)
    this.overR = random(60, 100)
    this.x = x
    this.y = y
    this.op = 1
  }

  creatBigCircle(ctx) {
    ctx.beginPath()
    ctx.arc(this.x, this.y, this.bR, 0, 2 * Math.PI)
    ctx.closePath()
    ctx.strokeStyle = 'rgba(128, 128, 128, ' + this.op + ')'
    ctx.stroke()
  }

  changeR() {
    this.bR = Math.min(this.bR += random(1, 4), this.overR);
    this.op = Math.max((this.op - Math.random() / 12).toFixed(2), 0)
  }

  //檢查是否運行完畢,以大圓爲標準清空屏幕
  complete() {
    return this.bR >= this.overR && this.op <= 0;
  }
}

window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

window.clearRequestTimeout = window.cancelAnimationFrame || window.mozCancelRequestAnimationFrame || window.webkitCancelRequestAnimationFrame || window.msCancelRequestAnimationFrame;


let c = document.getElementById("fireworks"),
  w = c.width = c.offsetWidth,
  h = c.height = c.offsetHeight,

  ctx = c.getContext("2d"),
  nums = 40,
  circles = [],
  bCircle = null,
  animationId = false;

let int = function(x, y) {
  circles = []

  if (animationId) clearRequestTimeout(animationId)

  for (let i = nums; i-- > 0;) {
    circles.push(new Circle(x, y))
  }

  bCircle = new BigCircle(x, y)
  creat()
}

let creat = function() {
  ctx.clearRect(0, 0, w, h);

  circles.forEach(function(v) {
    v.move();
    v.creatCircle(ctx)
  })

  bCircle.changeR()
  bCircle.creatBigCircle(ctx)

  animationId = requestAnimationFrame(creat)

  if (bCircle.complete()) {
    //以大圓爲標準,清空屏幕中止動畫
    ctx.clearRect(0, 0, w, h);
    clearRequestTimeout(animationId)
  }
}

c.onclick = function(e) {
  e = e || window.event;
  int(e.clientX, e.clientY)
}

html爲

<canvas id="fireworks" width="500px" height="500px"></canvas>
相關文章
相關標籤/搜索