最近闲来无事,打开网易云音乐,发现还有鲸云音效这种东西,嗯?『水晶音波』,挺炫。嗯?黑胶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
简单画了一个唱片,效果如下