NarraLeaf

添加页面叠层(设置页面)

用 Layout、Page 与 useRouter 搭建设置页覆盖层,在不打断游戏流程的情况下显示

路由结构

页面叠层指在游戏主界面上方叠加显示的 UI(如设置、存档、图鉴等),不中断游戏流程。通过 LayoutPage 定义路由结构,使用 useRouter 进行导航,即可实现设置页等叠层

下面的设置页叠层通过快捷菜单打开和关闭

1. 定义 Layout 与 Page 结构

定义 settings 布局,并加入 generalaudio 等子页面。Player 已经创建根布局,因此直接把 PageLayout 放在 Player 内即可。Page name={null} 表示父布局的默认页面

import { GameProviders, Layout, Page, Player } from "narraleaf-react";

function AppLayout() {
  return (
    <GameProviders>
      <Player story={story} onReady={({ liveGame }) => liveGame.newGame()}>
        {/* 默认页面(游戏舞台)- 路径:/ */}
        <Page name={null}>
          <GameStage />
        </Page>

        {/* 设置叠层 - 路径:/settings、/settings/general 等 */}
        <Layout name="settings">
          <Page name={null}>
            <SettingsHome />
            {/* /settings - 设置入口或默认标签页 */}
          </Page>
          <Page name="general">
            <SettingsGeneral />
            {/* /settings/general - 通用设置 */}
          </Page>
          <Page name="audio">
            <SettingsAudio />
            {/* /settings/audio - 音频设置 */}
          </Page>
        </Layout>
      </Player>
    </GameProviders>
  );
}

不要再手动添加 RootLayout。在 Player 内嵌套第二个根布局会产生错误的路由边界

2. 实现设置页面组件

设置页作为叠层,通常使用半透明背景遮罩 + 居中面板,点击遮罩或关闭按钮可返回游戏

import { useRouter } from "narraleaf-react";

function SettingsGeneral() {
  const router = useRouter();

  return (
    <div
      className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
      onClick={() => router.back()}
      // 点击遮罩关闭
    >
      <div
        className="bg-gray-900 rounded-lg p-6 w-96 max-h-[80vh] overflow-y-auto"
        onClick={(e) => e.stopPropagation()}
        // 点击面板时阻止关闭
      >
        <h2 className="text-xl font-bold mb-4">通用设置</h2>
        {/* 设置表单内容 */}
        <button
          className="mt-4 px-4 py-2 bg-amber-500 rounded"
          onClick={() => router.back()}
        >
          返回
        </button>
      </div>
    </div>
  );
}

3. 从快捷菜单打开设置

快捷菜单 中调用 router.navigate("/settings") 打开设置叠层:

import { useRouter } from "narraleaf-react";

function QuickMenu() {
  const router = useRouter();

  const openSettings = () => router.navigate("/settings");
  // 导航到设置叠层

  return (
    <div className="fixed bottom-5 left-0 right-0 flex justify-center">
      <button onClick={openSettings}>设置</button>
    </div>
  );
}

4. 路由 API 速览

方法说明
router.navigate(path)导航到指定路径,如 "/settings/general"
router.back()返回上一页
router.forward()前进
router.clear().navigate(path)清空历史后导航(常用于返回主界面)
router.getCurrentPath()获取当前路径
router.replace(path)替换当前历史记录,不新增一条
router.canGoBack()检查 back() 是否可以返回

5. 添加进入/退出动画

使用 Motion 的 motion.div 包裹页面内容。LayoutRouter 会让退出页面保持挂载,直到退出动画完成:

import { motion } from "motion/react";

function SettingsGeneral() {
  const router = useRouter();

  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
      onClick={() => router.back()}
    >
      <motion.div
        initial={{ scale: 0.9, opacity: 0 }}
        animate={{ scale: 1, opacity: 1 }}
        exit={{ scale: 0.9, opacity: 0 }}
        className="bg-gray-900 rounded-lg p-6 w-96"
        onClick={(e) => e.stopPropagation()}
      >
        {/* ... */}
      </motion.div>
    </motion.div>
  );
}

参考

本页目录