我一直对浩瀚的宇宙和星空怀有一种特殊的情感。那些闪烁的星星、划过的流星,总能让我内心沉静下来。一次偶然的念头促使我萌生了开发一款星空屏保的想法——能在电脑空闲时自动启动,带我遨游在静谧的星空下。后来,这个小项目逐渐丰满起来,过程中的种种设计抉择和技术难题也让我收获颇丰。


初衷与设计思考

项目的最初想法其实非常简单,我希望:

  • 画面上随机分布许多星星,大小不一且自然闪烁;
  • 偶尔有流星划过,带有真实的尾迹效果;
  • 能够优雅地响应用户操作,空闲时自动进入屏保,操作时退出。

但随着设计细节逐渐展开,我意识到实现这些功能比想象中要复杂得多。比如如何高效渲染上百颗星星?如何用有限资源模拟星星闪烁?怎样让流星划过时看上去更加自然?还有,如何用现代框架保证代码结构清晰可维护?

经过一番调研和思考,我决定基于 Vue 3 的组合式 API 搭配 HTML5 Canvas 来完成这个屏保。Vue 提供了响应式和组件化架构,Canvas 则能高效渲染复杂动画,TypeScript 让代码更安全易维护。


技术选型和项目架构

技术栈方面,我选用了:

  • Vue 3(组合式 API):模块化逻辑,方便管理动画和状态。
  • Vite:极速构建和热更新,极大提升开发效率。
  • HTML5 Canvas:负责所有星空与流星的渲染,避免 DOM 操作带来的性能瓶颈。
  • TypeScript:提升代码质量,减少低级错误。

整体项目结构如下:

src/
├── assets/          # 静态资源,如字体或图片
├── components/      # Vue 组件
│   ├── StarCanvas.vue      # 星空与流星渲染核心组件
│   ├── ChargingText.vue    # “正在充电中...”提示组件
├── composables/     # 组合式函数
│   ├── useStarfield.ts         # 星空动画逻辑封装
│   ├── useScreensaverTrigger.ts # 屏保触发逻辑
├── views/           # 页面视图
│   ├── HomeView.vue          # 首页视图
│   └── ScreensaverView.vue   # 屏保视图
├── router/          # 路由配置
├── App.vue          # 根组件
└── main.ts          # 入口

核心功能实现详解

星空渲染的逻辑

StarCanvas.vue 是项目的灵魂,它利用 Canvas 绘制星星和流星。

我定义了每颗星星的属性:

  • 位置(x、y,均为 0~1 的比例值,方便响应不同分辨率)
  • z 坐标,模拟深度,影响大小和亮度
  • 大小,0.5 至 3 像素不等
  • 闪烁周期,用于实现周期性透明度变化,模拟闪烁

星星的位置在 3D 空间中随机分布,但最终映射到二维画布上。

我用 requestAnimationFrame 实现循环动画:

const animate = () => {
  ctx.clearRect(0, 0, width.value, height.value);
  drawStars();
  drawMeteor();
  animationId = requestAnimationFrame(animate);
};

流星的设计

流星是随机出现的,带有尾迹,划过画布。它有:

  • 起始位置 (x, y)
  • 速度
  • 角度(用来确定划过方向)
  • 尾迹长度
  • 透明度逐渐变化

通过一个类来封装流星的属性与行为:

class Meteor {
  constructor() { this.reset(); }
  reset() {
    this.x = Math.random() * width.value;
    this.y = -20;
    this.speed = 2 + Math.random() * 5;
    this.length = 5 + Math.random() * 15;
    this.angle = Math.PI / 6 + Math.random() * Math.PI / 3;
    this.alpha = 0;
  }
  update() {
    this.x += Math.sin(this.angle) * this.speed;
    this.y += Math.cos(this.angle) * this.speed;
    this.alpha = Math.min(this.alpha + 0.01, 0.7);
    if (this.y > height.value + 20 || this.x < -20 || this.x > width.value + 20) this.reset();
  }
  draw(ctx) {
    ctx.strokeStyle = `rgba(255,255,255,${this.alpha})`;
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(this.x, this.y);
    ctx.lineTo(this.x - Math.sin(this.angle) * this.length, this.y - Math.cos(this.angle) * this.length);
    ctx.stroke();
  }
}

屏保自动触发机制

我用组合式函数封装了用户无操作 30 秒后自动进入屏保的逻辑:

export function useScreensaverTrigger(timeout = 30000) {
  const isActive = ref(false);
  let timer;
  const resetTimer = () => {
    clearTimeout(timer);
    isActive.value = false;
    timer = setTimeout(() => isActive.value = true, timeout);
  };
  onMounted(() => {
    resetTimer();
    window.addEventListener('mousemove', resetTimer);
    window.addEventListener('keydown', resetTimer);
    window.addEventListener('touchstart', resetTimer);
  });
  onUnmounted(() => {
    clearTimeout(timer);
    window.removeEventListener('mousemove', resetTimer);
    window.removeEventListener('keydown', resetTimer);
    window.removeEventListener('touchstart', resetTimer);
  });
  return { isActive };
}

这确保了屏保的自动启停与用户活动联动。


详细流程与逻辑图

为了理清动画和交互的流程,我绘制了一张整体流程图:

简单制作星空屏保_前端

这张流程图描述了星空屏保的运行机制,每一帧都会清理画布,然后重新绘制所有元素,同时根据用户操作管理屏保的启停。


关键代码块与知识点讲解

1. 三维视觉深度

每颗星星的 z 值控制其大小和亮度,让画面有层次感:

ctx.arc(x, y, star.size * star.z, 0, 2 * Math.PI);
ctx.fillStyle = `rgba(255, 255, 255, ${alpha * star.z})`;

这小小的乘法让远处星星更小更暗,近处星星更亮更大,极大丰富了视觉效果。

2. 闪烁的实现

通过正弦函数周期性调整透明度,模仿星星闪烁:

const alpha = 0.5 + Math.sin((Date.now() / 1000) * (2 * Math.PI) / star.blinkPeriod) * 0.5;

这个思路简单但有效,且易于参数调整。

3. 流星的尾迹绘制

流星尾迹由一条线段构成,透明度逐渐递减,显得自然:

ctx.strokeStyle = `rgba(255, 255, 255, ${this.alpha})`;
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x - Math.sin(this.angle) * this.length, this.y - Math.cos(this.angle) * this.length);
ctx.stroke();

4. 响应式 Canvas 大小调整

监听窗口变化,自动调整 Canvas 大小避免模糊或黑边:

window.addEventListener('resize', () => {
  canvas.width = container.clientWidth;
  canvas.height = container.clientHeight;
});

UI设计细节

项目的 UI 极简且符合现代设计规范。全黑背景,星星纯白渐变,流星光效柔和,整体沉稳安静。

首页简单清晰,提示用户:

  • 屏保会在 30 秒无操作后自动启动;
  • 任何鼠标或键盘操作会立即退出屏保。

屏保页面以全屏 Canvas 呈现,画布尺寸动态适配,保持高性能且画质细腻。

文字提示组件“正在充电中...”采用淡入淡出动画,字体简洁,位置居中底部,避免遮挡主画面。


如果你也对星空屏保或者Canvas动画感兴趣,希望我的分享能带给你一些灵感和帮助。也欢迎随时交流,我们一起探索数字艺术的无限可能。