【canvas】网易云音乐鲸云动效『孤独星球』的简单实现

书接上回:【canvas】网易云音乐鲸云特效『水晶音波』的简单实现

今天,我来实现一下『孤独星球』这一动效。

这是网易云音乐的截图,结尾放我自己实现的效果
在这里插入图片描述

上回已经开好“局”了,所以这里就不必重复了,直接讲如何用canvas画背景的涟漪效果。




canvas

这回我细讲思路。
先定义一个空心圆,这空心圆上有个小球
所以要有这么几个关键变量要有,

变量
空心圆圆心位置、变大的速度、半径、渐变的范围(即圆最多有多大)
小球位置、移动的速度、半径.

空心圆会变大,小球会移动,那么实现方式就简单了,我延续上次定义三角形的方式,这样定义

class Circle {
  constructor(context, speed, pole, radius, range) {
    this.ctx = context;
    this.speed = speed;
    this.pole = pole;
    this.radius = radius;
    this.range = range;
    this.__restart();
  }

  __restart() {
    this.r = this.r ? this.radius[0] : this.radius[1];
    this.ballRadius = Math.floor(4 + Math.random() * 3);
    this.ballAngle = Math.random() * PI2; // 小球的位置用角度表示,随机产生
    this.ballPoint || (this.ballPoint = [0, 0]);
    this.ballPoint[0] = Math.cos(this.ballAngle) * this.r; // 根据空心圆的半径计算小球的坐标
    this.ballPoint[1] = Math.sin(this.ballAngle) * this.r;
  }

  __lerp(src, dst, coeff) {
    return src + (dst - src) * coeff; // 线性函数,小学数学
  }

  __update() {
    if (this.r - this.range > 0.0001)  // 空心圆超过一定范围就会重新开始
      this.__restart();
    else {
      this.r += this.speed;
      this.ballAngle += .01;
      this.ballPoint[0] = Math.cos(this.ballAngle) * this.r;
      this.ballPoint[1] = Math.sin(this.ballAngle) * this.r;
      this.opacity = this.__lerp(1, 0, this.r / this.range);
    }
  }

  render() {
    this.__update();
    // 绘制空心圆
    this.ctx.lineWidth = 2;
    this.ctx.strokeStyle = `rgba(179, 179, 179, ${this.opacity})`;
    this.ctx.beginPath();
    this.ctx.arc(this.pole[0], this.pole[1], this.r, 0, PI2);
    this.ctx.stroke();
    // 绘制小球
    this.ctx.strokeStyle = `rgba(179, 179, 179, 0)`;
    this.ctx.fillStyle = `rgba(179, 179, 179, ${this.opacity})`;
    this.ctx.beginPath();
    this.ctx.arc(this.pole[0] + this.ballPoint[0], this.pole[1] + this.ballPoint[1], this.ballRadius, 0, PI2);
    this.ctx.stroke();
    this.ctx.fill();
  }
}

在上述代码中,会有几个值得细品的地方:

  1. 为什么这样计算小球的[x, y]位置写成这样?
this.ballPoint || (this.ballPoint = [0, 0]);
this.ballPoint[0] = Math.cos(this.ballAngle) * this.r;
this.ballPoint[1] = Math.sin(this.ballAngle) * this.r;

  而不这样

this.ballPoint = [Math.cos(this.ballAngle) * this.r, Math.sin(this.ballAngle) * this.r];

这两种方法不会差太多,但我是个强迫症,后者会反复创建新的数组赋值给this.ballPoint,我认为这是不好的,数组只需要创建一次,每次修改数组中的值就可以了。

  1. 为什么构造这个圆时,我传入的半径radius是一个数组?

其实这是为了实现错落有致。在后面的“场景”定义中会进一步解答这个问题。

紧接是定义“场景”。

class Scene {
  constructor(canvas) {
    this.cvs = canvas;
    this.ctx = canvas.getContext('2d');
    const slit = 50;
    const pole = this.cvs.width / 2;
    this.circleSet = [];
    this.circleNum = Math.floor(pole / slit);
    const range = this.circleNum * slit;
    for (let i = 1; i < this.circleNum; ++i)
      this.circleSet.push(new Circle(this.ctx, 1, [pole, pole], [slit, slit * i], range));
  }

  render() {
    this.ctx.clearRect(0, 0, this.cvs.width, this.cvs.height);
    this.circleSet.forEach(circle => circle.render());
  }

  run() {
    if (!this.timer) {
      this.timer = setInterval(this.render.bind(this), 25);
    }
  }

  stop() {
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = 0;
    }
  }
}

// 跑场景
const canvas = document.getElementById('background');
canvas.width = canvas.height = Math.ceil(canvas.parentNode.lastElementChild.offsetWidth * 1.68421);
const scene = new Scene(canvas);
scene.run();

其中

    const slit = 50;
    const pole = this.cvs.width / 2;
    this.circleSet = [];
    this.circleNum = Math.floor(pole / slit);
    const range = this.circleNum * slit;
    for (let i = 1; i < this.circleNum; ++i)
      this.circleSet.push(new Circle(this.ctx, 1, [pole, pole], [slit, slit * i], range));

这部分是原理,我画了一个简易的示意图表示
在这里插入图片描述
当某个空心圆的半径超过range,那就会到因Circle.__update()方法而回到示意图中start位置。以此实现周期。

最终效果

源码链接在这:github
在线演示:codepen
在这里插入图片描述


下篇 —— 网易云音乐鲸云动效『迷幻水波』的原理:【canvas】三阶贝塞尔曲线拟合圆

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值