一個靈活的,可配置的波浪動畫外掛

demonQ發表於2019-02-14

先看效果

效果展示

github

起因

春節放假前的第二天上午,看到設計稿上的圖(如下),於是發生了下面的對話

一個靈活的,可配置的波浪動畫外掛

-我:把這個圖傳一下吧。

-UI:好的,你是要 gif 吧。

-我:啥?這個是個動畫?

-UI:啥?這不是動畫?

-我:打擾了。

於是在剩下的一天裡,我擼出了這個波浪動畫

開始準備

  1. 想想需求

首先我們能想到的是,波浪的數量一定是可定製的,其次是顏色,速度,透明度,高度等等。這些可定製的引數應當在函式例項化時傳入。所以我們先定製一個介面(包括了後來在編寫時我認為需要的)。

interface Options {
  number: number
  smooth: number
  velocity: number
  height: number
  colors: Array<{ hex: string, rgba: string }>
  opacity: number
  border: {
    show: boolean,
    width: number,
    color: string[]
  }
  position: 'top' | 'bottom' | 'left' | 'right'
}
複製程式碼

不瞭解 typescript 的同學可以意會,key 指的傳入變數的名稱,value 為變數的型別規範

  1. 設計介面

我們先預先想下可能發生的場景:

  1. 動畫需要指令化的開始或暫停
  2. 可以通過方法實時設定或更改引數,並反映在動畫上
  3. 在動畫的容器縮放時,畫布應當能重新設定

所以我們暫時值規定四個型別為 public 介面 animatepausesetOptionsreset

interface Core {
  animate: () => void
  pause: () => void
  setOptions: (options: Object) => void
  reset: () => void
}
複製程式碼

開始寫動畫

準備工作基本完成,現在開始編寫核心動畫。

  1. 準備一個 canvas

基本操作,不多介紹。

<body>
  <canvas id="canvas"></canvas>
</body>
複製程式碼
const canvas = document.getElementById('canvas')  
const ctx = canvas.getContext('2d')
canvas.width = document.body.offsetWidth  
canvas.height = document.body.offsetHeight
複製程式碼
  1. 先畫一池水

畫一個矩形,然後填充顏色。

ctx.fillStyle = "rgba(255,118,87,.6)"
ctx.beginPath()
ctx.moveTo(0, canvas.height/2)
ctx.lineTo(canvas.width, canvas.height/2)
ctx.lineTo(canvas.width, canvas.height)
ctx.lineTo(0, canvas.height)
ctx.lineTo(0, canvas.height/2)
ctx.closePath()
ctx.fill()
複製程式碼
一個靈活的,可配置的波浪動畫外掛
  1. 讓水動起來

要讓水池的水呈週期性的上漲和跌落,很容易想到藉助正弦或者餘弦函式來完成,在每一幀的渲染中將 step 增加 1 度,規定 50 為變化值,利用正弦函式將變化量作用於左右兩個頂點上即可。

  let step = 0
  
  function loop(){
    // 清空canvas
    ctx.clearRect(0,0,canvas.width,canvas.height)
    ctx.clearRect(0,0,canvas.width,canvas.height)
    ctx.fillStyle = "rgba(255,118,87,.6)"
    step++
    
    const angle = step * Math.PI / 180
    const deltaHeight   = Math.sin(angle) * 50
    
    ctx.beginPath()
    ctx.moveTo(0, canvas.height/2+deltaHeight)
    ctx.lineTo(canvas.width, canvas.height/2+deltaHeight)
    ctx.lineTo(canvas.width, canvas.height)
    ctx.lineTo(0, canvas.height)
    ctx.lineTo(0, canvas.height/2+deltaHeight)
    ctx.closePath()
    ctx.fill()

    requestAnimationFrame(loop)
  }
  
  loop()
複製程式碼
一個靈活的,可配置的波浪動畫外掛
  1. 讓水晃起來

使左右頂點不同步即可讓水面晃動起來,所以我們將左頂點的取值使用餘弦函式即可。

  let step = 0

  function loop() {
    //清空canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    ctx.fillStyle = "rgba(255,118,87,.6)"
    step++
    
    const angle = step * Math.PI / 180
    const deltaHeight = Math.sin(angle) * 50
    const deltaHeightRight = Math.cos(angle) * 50
    
    ctx.beginPath()
    ctx.moveTo(0, canvas.height / 2 + deltaHeight)
    ctx.lineTo(canvas.width, canvas.height / 2 + deltaHeightRight)
    ctx.lineTo(canvas.width, canvas.height)
    ctx.lineTo(0, canvas.height)
    ctx.lineTo(0, canvas.height / 2 + deltaHeight)
    ctx.closePath()
    ctx.fill()

    requestAnimationFrame(loop)
  }

  loop()
複製程式碼
一個靈活的,可配置的波浪動畫外掛
  1. 製造波浪

藉助貝塞爾曲線將矩形的一邊變為波浪。

一個靈活的,可配置的波浪動畫外掛

在 canvas 繪製中,我們藉助 bezierCurveTo(cpX1, cpY1, cpX2, cpY2, x, y) 方法繪製貝塞爾曲線,由上圖可見(歪脖 45 度檢視更直觀),兩個控制點的橫座標應當設為矩形寬的中點,縱座標跟隨峰值變化即可。

  let step = 0

  function loop() {
    //清空canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    ctx.fillStyle = "rgba(255,118,87,.6)"
    step++
    
    const angle = step * Math.PI / 180
    const deltaHeight = Math.sin(angle) * 50
    const deltaHeightRight = Math.cos(angle) * 50
    
    ctx.beginPath()
    ctx.moveTo(0, canvas.height/2+deltaHeight)
    ctx.bezierCurveTo(canvas.width /2, canvas.height/2+deltaHeight-50, canvas.width / 2, canvas.height/2+deltaHeightRight-50, canvas.width, canvas.height/2+deltaHeightRight)
    ctx.lineTo(canvas.width, canvas.height)
    ctx.lineTo(0, canvas.height)
    ctx.lineTo(0, canvas.height/2+deltaHeight)
    ctx.closePath()
    ctx.fill()

    requestAnimationFrame(loop)
  }

  loop()
複製程式碼
一個靈活的,可配置的波浪動畫外掛
  1. 多個波浪

將上面的波浪寫成 for 迴圈多次渲染即可。

  let step = 0
  const lines = 3

  function loop() {
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    step++
    for (let i = 0; i < lines; i++) {
      ctx.fillStyle = 'rgba(255,118,87,.3)'
      var angle = (step + i * 180 / lines) * Math.PI / 180
      var deltaHeight = Math.sin(angle) * 50
      var deltaHeightRight = Math.cos(angle) * 50
      ctx.beginPath()
      ctx.moveTo(0, canvas.height / 2 + deltaHeight)
      ctx.bezierCurveTo(canvas.width / 2, canvas.height / 2 + deltaHeight - 50, canvas.width / 2, canvas.height / 2 + deltaHeightRight - 50, canvas.width, canvas.height / 2 + deltaHeightRight)
      ctx.lineTo(canvas.width, canvas.height)
      ctx.lineTo(0, canvas.height)
      ctx.lineTo(0, canvas.height / 2 + deltaHeight)
      ctx.closePath()
      ctx.fill()
    }

    requestAnimationFrame(loop)
  }

  loop()
複製程式碼
一個靈活的,可配置的波浪動畫外掛

結語

核心程式碼已經敘述完畢,其餘針對設定項的封裝就不再贅述了(基本是將上述的核心部分進行變數化設定)。

如果你也有這樣的需求,可以參考上述示例簡單使用,也可以使用我編寫的

最後的最後,歡迎 star ~

github

相關文章