水波圖實現原理

在項目中本身使用 Canvas 實現了一下水波圖,在這裏給你們分享一下它的實現原理。 一開始看到波浪,可能不知道從何入手,咱們來看看波浪的特徵就會有靈感了。git

沒錯,有人確定會想到,就是 正餘弦曲線!對於波陡很小的波動,通常選擇正弦或餘弦的曲線來表示波形,這是最簡單而又最接近實際波形的表述。這裏我選擇了正弦曲線來實現。github

在講實現思路以前,咱們來回憶一下正弦曲線的基礎。canvas

正弦曲線

正弦曲線公式:y = A sin(Bx + C) + D瀏覽器

振幅是 A,A 值越大,曲線更陡峭:動畫

週期是 2π/B,B 值大於 1 時,B 值越大,週期越短,B 值小於 1 大於 0 時,週期變長:ui

相移是 −C/B,在 B 不變的狀況,C 爲正值時,曲線向左移動,C 爲負值時,曲線向右移動:this

垂直位移是 D,控制曲線上下移動:spa

實現思路

瞭解了正弦曲線的一些屬性,咱們能夠把這些屬性來控制波浪,3d

  • 振幅:控制波浪的高度
  • 週期:控制波浪的寬度
  • 相移:控制波浪的水平移動
  • 垂直位移:控制水位的高度

動畫效果的實現主要是利用相移,經過不斷水平移動曲線,產出波浪移動的感受,而後能夠繪製多條曲線,曲線之間經過控制屬性(高度、寬度、移動速度),產生視覺差,就會有波浪起伏的感受了。code

有了這些思路,咱們來慢慢實現。

曲線繪製

初始化,定義 Canvas 的寬高:

componentDidMount() {
  const self = this;
  const canvas = this.refs.canvas;
  canvas.height = 500;
  canvas.width = 500;
  this.canvas = canvas;
  this.canvasWidth = canvas.width;
  this.canvasHeight = canvas.height;
  const ctx = canvas.getContext('2d');
  this.drawSin(ctx);
}
render() {
  return (
    <div className="content page"> <canvas ref="canvas"></canvas> </div>
  );
}
複製代碼

根據定義波浪的參數配置,經過公式: y = 波浪高度 * sin(x * 波浪寬度 + 水平位移),來繪製正弦曲線:

drawSin(ctx) {
  const points = [];
  const canvasWidth = this.canvasWidth;
  const canvasHeight = this.canvasHeight;
  const startX = 0;
  const waveWidth = 0.05; // 波浪寬度,數越小越寬
  const waveHeight = 20; // 波浪高度,數越大越高
  const xOffset = 0; // 水平位移

  ctx.beginPath();
  for (let x = startX; x < startX + canvasWidth; x += 20 / canvasWidth) {
    const y = waveHeight * Math.sin((startX + x) * waveWidth + xOffset);
    points.push([x, (canvasHeight / 2) + y]);
    ctx.lineTo(x, (canvasHeight / 2) + y);
  }
  ctx.lineTo(canvasWidth, canvasHeight);
  ctx.lineTo(startX, canvasHeight);
  ctx.lineTo(points[0][0], points[0][1]);
  ctx.stroke();
}
複製代碼

曲線繪製完,這時曲線是靜態的,如何讓它動起來?前面思路提到,能夠經過不斷改變水平偏移(xOffset),讓曲線水平移動,便可產生動態的效果。

componentDidMount() {
  ...
  this.xOffset = 0;  // 初始偏移
  this.speed = 0.1;  // 偏移速度
  requestAnimationFrame(this.draw.bind(this, canvas));
}

draw() {
  const canvas = this.canvas;
  const ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // 曲線繪製
  this.drawSin(ctx, this.xOffset);
  this.xOffset += this.speed;
  requestAnimationFrame(this.draw.bind(this));
}

drawSin(ctx, xOffset = 0) {
  ...
}
複製代碼

球型繪製

如今咱們雛形已經出來了,曲線和動態效果已經實現,上面能夠當作是水裝在一個長方體上,若是讓水裝在一個球體上? 這裏咱們用到了 Canvas 的 clip() 方法來定義剪切區域,定義了剪切區域後,瀏覽器會將全部的繪圖操做都限制在本區域內執行,因此咱們能夠先畫一個圓,定義後面繪製的區域只能在這個圓的區域內,超出部分就不顯示,這樣就能造成浪在一個球體中的效果了。

draw() {
  ...
  if (!this.isDrawCircle) {
    this.drawCircle(ctx);
  }
  this.drawSin(ctx, this.xOffset);
  this.xOffset += this.speed;
  requestAnimationFrame(this.draw.bind(this));
}

drawCircle(ctx) {
  const r = this.canvasWidth / 2;
  const lineWidth = 5;
  const cR = r - (lineWidth);
  ctx.lineWidth = lineWidth;
  ctx.beginPath();
  ctx.arc(r, r, cR, 0, 2 * Math.PI);
  ctx.stroke();
  ctx.clip();
  this.isDrawCircle = true;
}
複製代碼

水位控制

是否是有點感受了,目前還差一點,就是控制水位,也就是映射到數據的百分比。前面若是有留意的話,會發現 正弦曲線 y 座標的計算,最後會加上 canvasHeight / 2 ,其實這裏就是設置水位了。 咱們來看看:y = A sin(Bx + C) + D,曲線的高度有 A 和 D 決定,A 控制波浪的高度,實際水位仍是由 D 來控制。 水位的高度,在可視化上含義就是數據的百分比,假設目前的百分比80%,水位的高度就 canvasHeight * 0.8,映射到座標系統 y 的座標就是 canvasHeight * (1 - 0.8)。(座標原點在左上角)。 在動畫效果上除了正弦曲線的水平移動,咱們加上水位上升的動效:

componentDidMount() {
  ...
  this.xOffset = 0;
  this.speed = 0.1;
  // 水位數值
  this.rangeValue = 0.6;
  // 初始水位
  this.nowRange = 0;
  requestAnimationFrame(this.draw.bind(this, canvas));
}

draw() {
  ...
  this.drawSin(ctx, this.xOffset, this.nowRange);
  this.xOffset += this.speed;
  if (this.nowRange < this.rangeValue) {
    this.nowRange += 0.01;
  }
  requestAnimationFrame(this.draw.bind(this));
}

drawCircle(ctx) {
  ...
}

drawSin(ctx, xOffset = 0, nowRange = 0) {
  ...
  for (let x = startX; x < startX + canvasWidth; x += 20 / canvasWidth) {
    const y = waveHeight * Math.sin((startX + x) * waveWidth + xOffset);
    points.push([x, (1 - nowRange) * canvasHeight + y]);
    ctx.lineTo(x, (1 - nowRange) * canvasHeight + y);
  }
  ...
}
複製代碼

最終效果

最後咱們加上顏色,再加多一條正弦曲線,就會有波浪起伏的感受了。

在上面球型繪製的時候,咱們用到剪切區域的方法來實現,有些人確定會想到,這時我不用圓去裁切,而是用其餘形狀,就能夠創造出水在各類容器的效果了。

源代碼:github.com/beyondxgb/w…

相關文章
相關標籤/搜索