壁纸、主题和播放器是怎么做的
这个站上「换壁纸」「切深浅色」「放歌」这三件事,都不需要服务器。它们的共同点是:状态只属于当前访客,存本地就够了。
壁纸:CSS 渐变而不是图片
四张壁纸全部是 CSS 渐变,没有一个图片文件。这样做有几个好处:任意分辨率都清晰、整站体积小、不会有版权问题。
壁纸定义在 src/data/site.ts 里:
{
id: 'aurora',
name: '极光',
dark: "radial-gradient(1200px 600px at 12% 8%, rgba(56,189,248,.30), transparent 60%), linear-gradient(160deg, #04070f, #0f2236)",
light: "radial-gradient(1200px 600px at 12% 8%, rgba(56,189,248,.45), transparent 60%), linear-gradient(160deg, #eef6ff, #e8f1ff)",
}
布局文件会把它编译成一组 CSS 规则:
:root[data-wallpaper='aurora'][data-mode='dark'] #bg { background-image: ... }
:root[data-wallpaper='aurora'][data-mode='light'] #bg { background-image: ... }
于是切换壁纸只需要改 <html> 上的一个属性,浏览器自己会做过渡。
想换成自己的照片,把图片放进 public/wallpapers/,然后把 light / dark 改成 url('/wallpapers/photo.jpg') center / cover no-repeat 就行。
不让页面闪一下
如果等 JavaScript 加载完再读取偏好,用户会先看到默认壁纸闪一下再变。解决办法是把读取逻辑写成 <head> 里的内联脚本——它在浏览器画第一帧之前就同步执行完了:
<script is:inline>
(function () {
var root = document.documentElement;
var saved = JSON.parse(localStorage.getItem('site-prefs') || '{}');
if (saved.mode) root.dataset.mode = saved.mode;
if (saved.wallpaper) root.dataset.wallpaper = saved.wallpaper;
})();
</script>
is:inline 是 Astro 的指令,表示「不要打包这个脚本,原样输出到 HTML 里」。这是它和默认 <script> 的关键区别。
播放器:原生 audio 就够
播放器没有用任何第三方库,核心就是 new Audio() 那一套:play()、pause()、timeupdate 事件、currentTime 赋值。
几个必须处理的细节:
- 不要自动播放。 浏览器会拦截没有用户交互的
play(),而且突然响起来也很讨厌。播放器只在用户点击后才出声。 - 音量和进度要存。 存进
localStorage,下次打开还是你调好的样子。 duration要等元数据。 在loadedmetadata事件之前读audio.duration会得到NaN。
audio.addEventListener('loadedmetadata', () => {
time.textContent = `0:00 / ${fmt(audio.duration)}`;
});
audio.addEventListener('timeupdate', () => {
if (scrubbing) return; // 用户正在拖进度条时不要抢控制权
seek.value = String(Math.round((audio.currentTime / audio.duration) * 1000));
});
音频文件放哪里
小文件可以直接丢进 public/music/。但一首 320kbps 的歌大约 8 到 10MB,攒上几十首,Git 仓库就会变得又大又慢——GitHub 还限制单个文件不能超过 100MB。
更合适的做法是把音频放到 Cloudflare R2:免费 10GB 存储,而且不收流量费。上传后拿到公开链接,直接填进 playlist 的 src 就行。
最后提醒一句:放别人的商业音乐是有版权风险的,自己录的、原创的,或者明确标注 CC 授权的才安全。