使用三角函数构建水波图

「这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

原理

水波图动画我们已经见过很多次了,一开始看到波浪,可能不知道从何入手,我们来看看波浪的特征就会有灵感了

其实就是借助三角函数中的正余弦曲线

三角函数

正弦函数:y = A sin(Bx + C) + D,正弦函数可以构建出一个有规律的正弦曲线

通过修改A、B、C、D 几个参数我们能得到不一样结果。

A 控制振幅,A 值越大,曲线更陡峭: B 控制着周期,B 值大于 1 时,B越大周期越小,B 值小于 1 大于 0 时,周期变长:


C 控制曲线左移或者右移,小于0时曲线向右移动,大于0时曲线向左移动
D 控制曲线的上下移动

了解了这些基础概念后我们就有了一个概念:

  • 振幅:控制波浪的高度
  • 周期:控制波浪的宽度
  • 相移:控制波浪的水平移动
  • 垂直位移:控制水位的高度

实现

首先我们画出一条正弦曲线

function draw (ctx) {
  const canvasWidth = 300;
  const canvasHeight = 300;
  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);
    // 通过公式: y = 波浪高度 * sin(x * 波浪宽度 + 水平位移)
    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();
}
draw(ctx)
复制代码

现在我们就画好了一条静止的波浪和容器,那如何能让它动起来呢,上面我们已经说过了,xOffset 是控制他的水平位移的,所以只要动态且有规律的改变xOffset的值,就能够让波浪动起来

function drawSin (ctx, xOffset) {
  ...
}
function draw() {
  var xOffset = 0; // 水平位移
  var speed = .1;
  const canvas = document.getElementById('mycanvas');
  const ctx = canvas.getContext('2d');
  function start() {
    ctx.beginPath();
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    xOffset += speed;
    // 曲线绘制
    drawSin(ctx, xOffset);
    requestAnimationFrame(() => {
      start()
    });
  }
  start();
}
draw()
复制代码

水位控制

现在我们想做一个波浪升高的动画,** A sin(Bx + C) + D** 那么我们只要动态控制 D 的大小就可以了,水位最低时,D 等于canvas的高度,也就是在canvas容器的最底部,所以得到一个很简单的公示(1-nowRange)*canvasHeight nowRange 范围时0-1,代表整个容器的比率。我们需要控制水位到canvas容器一半的位置,那么只要使nowRange为0.5就可以了。

function drawSin (ctx, xOffset = 0, nowRange = 0) {
  const points = [];
  const canvasWidth = 500;
  const canvasHeight = 500;
  const startX = 0;
  const waveWidth = 0.05; // 波浪宽度,数越小越宽
  const waveHeight = 20; // 波浪高度,数越大越高

  ctx.beginPath();
  for (let x = startX; x < startX + canvasWidth; x += 20 / canvasWidth) {
   // 计算水位值
    const D = (1 - nowRange) * canvasHeight;
    const y = waveHeight * Math.sin((startX + x) * waveWidth + xOffset) + D;
    points.push([x,  y]);
    ctx.lineTo(x, y);
  }
  ctx.lineTo(canvasWidth, canvasHeight);
  ctx.lineTo(startX, canvasHeight);
  ctx.lineTo(points[0][0], points[0][1]);
  ctx.stroke();
}

function draw() {
  var xOffset = 0; // 水平位移
  var speed = .1;
  var nowRange = 0;
  const canvas = document.getElementById('mycanvas');
  const ctx = canvas.getContext('2d');
  canvas.height = 500;
  canvas.width = 500;
  function start() {
    ctx.beginPath();
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    xOffset += speed;
    // 计算nowRange,最高为0.5
    if (nowRange < 0.5) {
      nowRange += 0.01;
    }
    // 曲线绘制
    drawSin(ctx, xOffset, nowRange);
    requestAnimationFrame(() => {
      start()
    });
  }
  start();
}
draw()
复制代码

最后加上颜色就可以了,并且可以使用 ctx.clip 裁剪方法,为它设置容器


class Wave {
  constructor({
    canvasWidth, // 轴长
    canvasHeight, // 轴高
    waveWidth = 0.055, // 波浪宽度,数越小越宽
    waveHeight = 6, // 波浪高度,数越大越高
    xOffset = 0,
    speed = 0.04,
    colors = ['#DBB77A', '#BF8F3B'], // 波浪颜色
  } = {}) {
    this.points = [];
    this.startX = 0;
    this.canvasWidth = canvasWidth;
    this.canvasHeight = canvasHeight;
    this.waveWidth = waveWidth;
    this.waveHeight = waveHeight;
    this.xOffset = xOffset;
    this.speed = speed;
    this.colors = colors;
  }
  getChartColor(ctx) {
    const radius = this.canvasWidth / 2;
    const grd = ctx.createLinearGradient(radius, radius, radius, this.canvasHeight);
    grd.addColorStop(0, this.colors[0]);
    grd.addColorStop(1, this.colors[1]);
    return grd;
  }
  draw(ctx) {
    ctx.save();
    const points = this.points;
    ctx.beginPath();
    for (let i = 0; i < points.length; i += 1) {
      const point = points[i];
      ctx.lineTo(point[0], point[1]);
    }
    ctx.lineTo(this.canvasWidth, this.canvasHeight);
    ctx.lineTo(this.startX, this.canvasHeight);
    ctx.lineTo(points[0][0], points[0][1]);
    ctx.fillStyle = this.getChartColor(ctx);
    ctx.fill();
    ctx.restore();
  }
  setParams({
    nowRange,
  } = {}) {
    this.points = [];
    const {
      startX, waveHeight, waveWidth, canvasWidth, canvasHeight, xOffset,
    } = this;
    for (let x = startX; x < startX + canvasWidth; x += 20 / canvasWidth) {
      const y = Math.sin(((startX + x) * waveWidth) + xOffset);
      const dY = canvasHeight * (1 - (nowRange / 100));
      this.points.push([x, dY + (y * waveHeight)]);
    }
    this.xOffset += this.speed;
  }
}

class drawClass {
  init() {
    const canvas = document.getElementById('mycanvas');
    canvas.height = 500;
    canvas.width = 500;
    this.canvas = canvas;
    this.canvasWidth = canvas.width;
    this.canvasHeight = canvas.height;
    this.radius = this.canvasWidth / 2;
    this.nowRange = 0;
    this.rangeValue = 60;
    this.wave1 = new Wave({
      canvasWidth: this.canvasWidth, // 轴长
      canvasHeight: this.canvasHeight, // 轴高
      waveWidth: 0.05, // 波浪宽度,数越小越宽
      waveHeight: 4, // 波浪高度,数越大越高
      colors: ['rgba(38, 116, 233, .5)', 'rgba(8, 96, 228, 1)'], // 波浪颜色
      xOffset: 0, // 初始偏移
      speed: 0.04, // 速度
    });
    this.wave2 = new Wave({
      canvasWidth: this.canvasWidth, // 轴长
      canvasHeight: this.canvasHeight, // 轴高
      waveWidth: 0.04, // 波浪宽度,数越小越宽
      waveHeight: 3, // 波浪高度,数越大越高
      colors: ['rgba(38, 116, 233, .5)', 'rgba(8, 96, 228, .5)'], // 波浪颜色
      xOffset: 2, // 初始偏移
      speed: 0.02, // 速度
    });
    const start = () => {
      const ctx = this.canvas.getContext('2d');
      ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
      if (this.drawContainer) {
        this.drawContainer(ctx);
      }
      if (this.nowRange <= this.rangeValue) {
        this.nowRange += 1;
      }
      if (this.nowRange > this.rangeValue) {
        this.nowRange -= 1;
      }
      this.wave2.setParams({ nowRange: this.nowRange });
      this.wave2.draw(ctx);
      this.wave1.setParams({ nowRange: this.nowRange });
      this.wave1.draw(ctx);
      window.requestAnimationFrame(start);
    }
    start();
  }

  drawContainer(ctx) {
    const r = this.canvasWidth / 2;
    const lineWidth = 4;
    const cR = r - (lineWidth);
    ctx.lineWidth = lineWidth;
    ctx.beginPath();
    ctx.arc(r, r, cR, 0, 2 * Math.PI);
    ctx.strokeStyle = 'rgba(186, 165, 130, 0.3)';
    ctx.stroke();
    ctx.clip();
  }
}
var draw = new drawClass();
draw.init()
复制代码

Kapture 2021-11-20 at 08.36.14.gif

猜你喜欢

转载自juejin.im/post/7032442402409283591