「遥远未来的故事」的第一版背景,其实只有一片纯黑。
后来我觉得,一个关于星海的站点,至少应该让人听见一点夜的声音。于是我关掉 Canvas,试着只用手写 CSS 完成它。

一、把星星交给 box-shadow
如果每一颗星都是一个 DOM 节点,页面很快就会变得沉重。更轻的做法,是让一个 1px 的小圆点,用多重 box-shadow 复制出整片星野:
.stars {
width: 1px;
height: 1px;
border-radius: 50%;
background: #fff;
box-shadow:
12vw 18vh 0 rgba(255, 255, 255, .8),
28vw 42vh 0 rgba(255, 255, 255, .5),
43vw 12vh 0 rgba(255, 255, 255, .7),
67vw 63vh 0 rgba(255, 255, 255, .4);
animation: twinkle 7s ease-in-out infinite alternate;
}
坐标写成 vw / vh,星星就会跟着视口一起呼吸。再叠加一点点模糊,它们看起来就不再像坐标,而像光。
二、让光有层次
只有白色星点还不够。我会再叠两层:
- 一层很淡的径向渐变,当作银河;
- 两个大范围的模糊光斑,缓慢漂移,当作极光。
.aurora {
position: absolute;
border-radius: 50%;
filter: blur(90px);
opacity: .3;
animation: drift 26s ease-in-out infinite alternate;
}
关键是「慢」。背景动效一旦快起来,就会从星空变成故障现场。26 秒、40 秒,甚至更久,反而更接近夜晚。
三、给流星留一条路
流星不是随机出现的。它需要一条足够长、足够斜的轨迹,以及一个不会一直重复的节奏:
@keyframes shooting {
0% { transform: translate3d(0, 0, 0); opacity: 0; }
8% { opacity: 1; }
35% { transform: translate3d(-36vw, 28vh, 0); opacity: 0; }
100% { transform: translate3d(-36vw, 28vh, 0); opacity: 0; }
}
两枚流星使用不同的延迟与时长,于是它们不会一起出现。偶尔抬头看见一条光划过去,刚刚好。
四、最后的克制
这套背景的全部代码加起来不到一百行,没有图片请求,也没有额外的 JS 循环。
也是写到后来才明白:氛围不是把效果堆满,而是知道哪里应该留黑。
星星、极光、流星都只是背景。真正要发光的,是读故事的人停下来的那几秒。
这一篇读完了。愿你在某个遥远未来,与想见的人重逢。