You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免页面刷新重置值?实现猜歌应用每日换歌方案

实现每日固定更换歌曲的猜歌应用方案

一、Wordle的核心实现逻辑

Wordle靠**固定UTC时间窗口(每日0点切换)**来锁定当日谜题,核心逻辑是:

  • 提前将所有谜题按顺序存入列表/数据库
  • 计算当前日期与某个固定起始日的天数差,用这个差值作为索引从列表中取出当日谜题
  • 前端用localStorage记录用户当日的游戏状态,但谜题本身的选择完全基于日期计算,同一日期内所有用户看到的内容一致,刷新页面也不会改变

二、是否需要后端API支持?

分两种场景:

  • 纯前端方案:如果你的歌曲列表固定、不需要动态更新,完全可以在前端实现,无需后端。
  • 后端方案:如果需要动态维护歌曲库、或者想隐藏歌曲列表(防止用户提前查看),后端API是必要的——由后端根据日期返回当日歌曲,前端仅负责播放和交互。

结合你已经准备了Node/Express和MongoDB,推荐用后端方案,扩展性更强。

三、针对你的Remix项目的具体实现方案

方案1:纯前端快速落地

无需后端,基于日期计算固定的歌曲索引:

  1. 先写一个计算日期偏移量的工具函数:
// 计算当前UTC日期与自定义起始日的天数差
const getDayOffset = () => {
  const startDate = new Date('2024-01-01'); // 可自定义起始日期
  const today = new Date();
  today.setUTCHours(0, 0, 0, 0);
  startDate.setUTCHours(0, 0, 0, 0);
  const diffTime = today.getTime() - startDate.getTime();
  return Math.floor(diffTime / (1000 * 60 * 60 * 24));
};
  1. 修改你的useEffect逻辑,用日期偏移量替代随机数:
useEffect(() => {
  const dayOffset = getDayOffset();
  // 取模确保索引在歌曲列表范围内
  const songIndex = dayOffset % songList.length;
  randomSongIndex.current = songIndex;
  const targetSong = songList[songIndex];
  const audio = new Audio(targetSong);
  audioRef.current = audio;

  audio.addEventListener("loadedmetadata", () => {
    // 基于日期生成固定的起始播放位置,确保同一日播放起点一致
    const pseudoRandom = (seed) => {
      const x = Math.sin(seed) * 10000;
      return x - Math.floor(x);
    };
    const startTime = Math.floor(pseudoRandom(dayOffset) * (audio.duration - 6));
    startTimeRef.current = startTime;
  });

  return () => {
    audio.removeEventListener("loadedmetadata", () => {});
  };
}, []);

这样不管怎么刷新页面,同一UTC日期内都会加载同一首歌,播放起点也固定。

方案2:后端API实现(推荐)

利用你已有的Node/Express和MongoDB:

  1. 后端接口实现:
    • 在MongoDB中存储歌曲文档,包含url、title等字段
    • 编写GET /api/daily-song接口,根据日期返回当日歌曲:
app.get('/api/daily-song', async (req, res) => {
  const startDate = new Date('2024-01-01');
  const today = new Date();
  today.setUTCHours(0, 0, 0, 0);
  const diffTime = today.getTime() - startDate.getTime();
  const dayOffset = Math.floor(diffTime / (1000 * 60 * 60 * 24));
  
  const songCount = await Song.countDocuments();
  const targetIndex = dayOffset % songCount;
  const dailySong = await Song.findOne().skip(targetIndex);
  
  res.json(dailySong);
});
  1. 前端调用接口:
    • 在InputSection组件中通过useEffect获取当日歌曲:
useEffect(() => {
  const fetchDailySong = async () => {
    const res = await fetch('/api/daily-song');
    const song = await res.json();
    const audio = new Audio(song.url);
    audioRef.current = audio;

    audio.addEventListener("loadedmetadata", () => {
      const dayOffset = getDayOffset();
      const pseudoRandom = (seed) => {
        const x = Math.sin(seed) * 10000;
        return x - Math.floor(x);
      };
      const startTime = Math.floor(pseudoRandom(dayOffset) * (audio.duration - 6));
      startTimeRef.current = startTime;
    });
  };
  fetchDailySong();
}, []);

四、React钩子相关说明

没有专门的React内置钩子直接实现“每日固定内容”,核心思路是用日期作为唯一标识,结合useEffect在组件初始化时获取对应内容。另外可以用localStorage记录用户当日的猜题状态(比如是否已猜对),避免页面刷新后重复操作。

五、现有代码优化点

  • 当前代码用useRef存随机索引,组件初始化时的useEffect空依赖只会执行一次,但页面刷新会重新执行导致换歌,改成基于日期的索引就能解决。
  • 播放时长的setTimeout(1000)是固定1秒,建议改成基于需求的固定时长(比如6秒):setTimeout(() => { ... }, 6000),更符合猜歌场景。

内容的提问来源于stack exchange,提问作者Lemon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 07:53:20