【canvas】网易云音乐鲸云动效『水晶音波』的简单实现

最近闲来无事,打开网易云音乐,发现还有鲸云音效这种东西,嗯?『水晶音波』,挺炫。嗯?黑胶VIP专享?(其实我已经是黑胶VIP)好像实现起来也不很复杂呀,所以花了一下午,实现了一个简单版本。

这是网易云音乐的截图,结尾放我自己实现的效果
在这里插入图片描述
先明确一点,简单实现,所以没搞懂的,不想做的,都省略不做了。




HTML

简单嘛~
不需要太多元素,简简单单才是真

<div class="debut">
  <!-- 背景部分 -->
  <canvas class="music-cover-background" id="background">your brower does not support canvas</canvas>
  
  <!-- 前景部分 -->
  <div class="music-cover">
    <img src="images/1753378458.jpg" class="music-cover-image"></img>
  </div>
</div>


CSS

开局找个地,然后画个圈

.debut {
  position: absolute;
  width: 100%;
  height: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.music-cover {
  width: 23.75rem;  /* 380px */
  height: 23.75rem; /* 380px */
  box-sizing: border-box;
  border: .125rem solid #B3B3B3; /* 2px solid #B3B3B3 */
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

使用flex布局实现居中


圈中贴个图,让它转起来

.music-cover-image {
  width: 21.25rem; /* 340px */
  height: 21.25rem; /* 340px */
  border: none;
  border-radius: 50%;
  animation: rotate infinite linear 25s;
}

@keyframes rotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

动画时间25s是我用秒表测的


图后画阴影,颜色简单选

.music-cover::before {
  content: "";
  position: absolute;
  width: 21.25rem; /* 340px */
  height: 21.25rem; /* 340px */
  border-radius: 50%;
  filter: blur(1.875rem); /* 30px */
  background-image: radial-gradient(white, silver);
}

使用伪元素就够了


一通操作之后,“唱片”就实现了,下面是效果图:

嗯,还行,接下来才是canvas画背景部分,也就是三角形往外飘嘛~
不难。



canvas

画布定个位

.music-cover-background {
  position: absolute;
}

position: absolute后,会因为父元素.debut采用flex布局而居中


紧接定大小

const canvas = document.getElementById('background');
canvas.width = canvas.height = Math.ceil(canvas.parentNode.lastElementChild.offsetWidth * 1.68421);

堆个三角形

const PI2 = 2 * Math.PI;
class Triangle {
  constructor(context, speed, pole, range) {
    this.ctx = context;
    this.pole = pole;
    this.range = range;
    this.speed = speed;
    this.points = [[0, 0], [0, 0], [0, 0]];
    this.__restart();
  }

  __restart() {
    this.angle = Math.random() * PI2; // 随机生成一个移动方向
    this.speedX = Math.cos(this.angle) * this.speed;
    this.speedY = Math.sin(this.angle) * this.speed;
    this.opacity = 1;

    const dist = Math.random() * 150; // 为了让三角形生成错落有致,所以让三角形从距离pole点的一个随机距离dist出发
    const distX = Math.cos(this.angle) * dist;
    const distY = Math.sin(this.angle) * dist;
    const θ = Math.random() * PI2; // 将三角形随机旋转一个θ°
    const x2 = Math.random() * 10;
    const y2 = 20 + Math.random() * 20;
    const x3 = 10 + Math.random() * 15;
    const y3 = 12 + Math.random() * 6;
    this.points[0][0] = Math.floor(this.pole[0] + distX);
    this.points[0][1] = Math.floor(this.pole[1] + distY);
    this.points[1][0] = Math.floor(this.pole[0] + distX + (x2 * Math.cos(θ) - y2 * Math.sin(θ)));
    this.points[1][1] = Math.floor(this.pole[1] + distY + (y2 * Math.cos(θ) + x2 * Math.sin(θ)));
    this.points[2][0] = Math.floor(this.pole[0] + distX + (x3 * Math.cos(θ) - y3 * Math.sin(θ)));
    this.points[2][1] = Math.floor(this.pole[1] + distY + (y3 * Math.cos(θ) + x3 * Math.sin(θ)));
  }

  __distance() {
    const dx = this.points[0][0] - this.pole[0];
    const dy = this.points[0][1] - this.pole[1];
    return Math.floor(Math.sqrt(dx * dx + dy * dy));
  }

  __lerp(src, dst, coeff) {
    return src + (dst - src) * coeff;
  }

  __update() {
    const dist = this.__distance();
    if (dist - this.range > 0.0001)
      this.__restart();
    else {
      this.points.forEach((point, index) => {
        this.points[index][0] = point[0] + this.speedX;
        this.points[index][1] = point[1] + this.speedY;
      });
      this.opacity = this.__lerp(1, 0, dist / this.range);
    }
  }

  render() {
    this.__update();
    this.ctx.lineWidth = 2;
    this.ctx.lineJoin = "miter";
    this.ctx.strokeStyle = `rgba(179, 179, 179, ${this.opacity})`;
    this.ctx.beginPath();
    this.ctx.moveTo(this.points[0][0], this.points[0][1]);
    this.ctx.lineTo(this.points[1][0], this.points[1][1]);
    this.ctx.lineTo(this.points[2][0], this.points[2][1]);
    this.ctx.closePath();
    this.ctx.stroke();
    this.ctx.fillStyle = 'rgba(67, 67, 67, .2)';
    this.ctx.fill();
  }
}

定义个“场景”

class Scene {
  constructor(canvas) {
    this.cvs = canvas;
    this.ctx = canvas.getContext('2d');
    this.triangleSet = [];
    this.triangleNum = 25; // 三角形个数
    const realm = this.cvs.width / 2; // 画布中心
    for (let i = 0; i < this.triangleNum; ++i)
      this.triangleSet[i] = new Triangle(this.ctx, 1.5, [realm, realm], realm);
  }

  render() {
    this.ctx.clearRect(0, 0, this.cvs.width, this.cvs.height);  // 及时清除画布
    this.triangleSet.forEach(triangle => triangle.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();

完成,下面是最终效果
源码链接在这:github
在这里插入图片描述
在线演示:codepen

codepen上的代码会有些不同,因为不想引用图片,所以用css简单画了一个唱片,效果如下
在这里插入图片描述


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

  • 51
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
实现 Android 音乐播放器专辑图片旋转,可以使用自定义 View 来实现。以下是实现类似网易云音乐旋转专辑 View 的步骤: 1. 首先创建一个自定义 View,继承自 ImageView。 2. 在自定义 View 中添加一个属性 mRotateDegree 表示当前旋转的角度。 3. 在 onDraw 方法中,使用 Canvas 对象绘制出图片。 4. 在 onDraw 方法中,使用 Matrix 对象对图片进行旋转操作。 5. 在 onDraw 方法中,使用 ValueAnimator 对象控制旋转的速度和方向。 6. 在自定义 View 中添加一个方法 startRotate(),用于开始旋转。 7. 在自定义 View 中添加一个方法 stopRotate(),用于停止旋转。 以下是示例代码: ```java public class RotateImageView extends ImageView { private float mRotateDegree = 0; private ValueAnimator mRotateAnimator; public RotateImageView(Context context) { super(context); } public RotateImageView(Context context, AttributeSet attrs) { super(context, attrs); } public RotateImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { Matrix matrix = new Matrix(); matrix.postRotate(mRotateDegree, getWidth() / 2f, getHeight() / 2f); canvas.setMatrix(matrix); super.onDraw(canvas); } public void startRotate() { if (mRotateAnimator != null && mRotateAnimator.isRunning()) { return; } mRotateAnimator = ValueAnimator.ofFloat(0f, 360f); mRotateAnimator.setDuration(10000); mRotateAnimator.setRepeatCount(ValueAnimator.INFINITE); mRotateAnimator.setInterpolator(new LinearInterpolator()); mRotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mRotateDegree = (float) animation.getAnimatedValue(); invalidate(); } }); mRotateAnimator.start(); } public void stopRotate() { if (mRotateAnimator != null) { mRotateAnimator.cancel(); } } } ``` 在使用时,只需要将 RotateImageView 添加到布局中,然后调用 startRotate() 方法开始旋转,调用 stopRotate() 方法停止旋转即可。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值