前些日子,一个同事说喜欢 B 站上的线上自习室,打开视频就这么安安静静的一个人待着也很舒服,我也很喜欢这种状态。大约三四年前我有一个网页,背景是嘈杂的都市路口,小雨稀疏得下着,听着耳机里传来雨水哗啦啦的声音,我可以特别放空。恰逢端午的小雨,我把代码重新部署上线
实现思路
画面的主要功能是下雨,雨滴的绘制可以拆分成两个阶段,分别是落地前的渐变矩形和落地后的扩展椭圆形,设定了一个地面阈值,当雨滴的y坐标小于阈值时,雨滴的形状是矩形,每帧的回调中,y坐标递增,否则就是半径递增,核心逻辑:
move() {
if (this.yPosition < this.maxHeight) {
this.yPosition += this.yVelocity;
this.xPosition += this.xVelocity;
} else {
if (this.radius < 30) {
this.radius += this.radiusGrowth;
} else {
this.playing = false;
}
}
this.draw();
}
雨滴的形状也很简单都是基础形状,所以直接用graphic
绘制即可
draw() {
if (this.yPosition < this.maxHeight) {
this.context.beginPath();
this.context.fillStyle = RainController.RAIN_COLOR;
this.context.fillRect(this.xPosition, this.yPosition, RainController.RAIN_WIDTH, RainController.RAIN_HEIGHT);
} else {
this.context.beginPath();
this.context.strokeStyle = RainController.RAIN_COLOR;
this.context.ellipse(this.xPosition, this.yPosition, this.radius, this.radius * RainController.ELLIPSE_FACTOR, 0, 0, 2 * Math.PI);
this.context.stroke();
}
}
因为是原生的canvas,所以用 requestAnimationFrame
来控制雨滴的数量和更新雨滴的绘制,同时为了让雨势更自然,我还做了一个数量变化的滞后逻辑,这样用户在调整雨滴数量的时候就不会过于突兀,核心的控制逻辑详见:
step() {
this.clearCanvas();
this.updateRainDrops();
this.drawRainDrops();
window.requestAnimationFrame(this.step.bind(this));
}
clearCanvas() {
this.context.fillStyle = RainController.BACKGROUND_COLOR;
this.context.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
}
updateRainDrops() {
const currentTimestamp = Date.now();
if (currentTimestamp - this.previousTimestamp > RainController.UPDATE_INTERVAL) {
this.adjustRainDropCount();
this.previousTimestamp = currentTimestamp;
}
this.rainDrops = this.rainDrops.reduce((updatedRainDrops, rainDrop) => {
if (rainDrop.playing) {
rainDrop.move();
updatedRainDrops.push(rainDrop);
} else if (rainDrop.alive) {
rainDrop.initialize(this.canvasWidth, this.canvasHeight);
updatedRainDrops.push(rainDrop);
}
return updatedRainDrops;
}, []);
}
adjustRainDropCount() {
const rainGap = this.targetRainCount - this.rainDrops.length;
const adjustmentBatchSize = Math.abs(Math.floor(rainGap / 20)) + 1;
if (rainGap > 0) {
this.addRainDrops(adjustmentBatchSize);
} else if (rainGap < 0) {
this.removeRainDrops(adjustmentBatchSize);
}
}
addRainDrops(count) {
for (let i = 0; i < count; i++) {
const rainDrop = new Rain(this.context);
rainDrop.initialize(this.canvasWidth, this.canvasHeight);
rainDrop.alive = true;
this.rainDrops.push(rainDrop);
}
}
removeRainDrops(count) {
for (let i = 0; i < count * 3; i++) {
const randomIndex = Math.floor(Math.random() * this.rainDrops.length);
this.rainDrops[randomIndex].alive = false;
}
}
drawRainDrops() {
this.rainDrops.forEach(rainDrop => rainDrop.draw());
}
我还增加了日期和番茄时钟功能,番茄时钟支持多个倒计时数字的设置,为了让控制面板不干扰整个画面,我还特意做了控制面板的隐藏,只有当鼠标悬浮在屏幕右下角的时候才会唤起面板,同时面板上还可以设置声音的开关、雨势的大小、全屏开关
因为功能定义是个自用的屏保,所以 它适合在PC端 打开,而且受浏览器的限制还得点击一下才会播放声音,完整的代码我就不贴了,喜欢的朋友可以打开网站,右键选择保存网页即可获取所有代码
点击查看原文可直接跳转网页
更
多
精
彩
融球效果(shader) 颜色滤镜 水波扩散效果(shader)