Web 模态框
编辑
学习如何在您的网络应用中使用 Expo Router 实现和自定义模态框的行为。
important 网页模态框是SDK 54及更高版本中的实验性功能。要使用此功能,您必须在项目中设置环境变量 EXPO_UNSTABLE_WEB_MODAL=1。
现代网络应用需要一种灵活的模态体验,以适应不同的内容大小和用户交互。Expo Router 提供了多种模态呈现模式,以满足现代网络体验的需求。这些模式利用 presentation 和 modal、formSheet、transparentModal 或 containedTransparentModal 在不同的屏幕宽度下呈现模态,并使用 webModalStyle 提供可自定义的样式属性。
Get started
要使用新的网页模态功能,您必须为开发和 导出 构建设置EXPO_UNSTABLE_WEB_MODAL=1环境变量。您可以通过将其添加到项目根目录的 .env 文件中,或通过在命令前加上前缀来实现,例如:EXPO_UNSTABLE_WEB_MODAL=1 npx expo start。
在 Expo Router 中,模态框是通过带有特定选项的 Stack.Screen 组件配置的。这要求模态屏幕被添加到您应用的 Stack 布局文件中。
考虑以下导航树,它包含在布局文件中定义的堆栈导航器、访问模态的主页和模态屏幕组件:
app_layout.tsxindex.tsxmodal.tsx在布局文件 (app/_layout.tsx) 中,模态屏幕组件被添加到 Stack 导航器中:
import { Stack } from 'expo-router'; export const unstable_settings = { anchor: 'index', }; export default function Layout() { return ( <Stack> <Stack.Screen name="index" /> <Stack.Screen name="modal" options={{ presentation: 'modal', // 启用模态行为 sheetAllowedDetents: [0.5, 1], // 针对小于 786px 的屏幕的捕捉位置数组。 }} /> </Stack> ); }
modal.tsx 用于显示模态框的内容:
import { Text, View } from 'react-native'; export default function Modal() { return <View style={{ flex: 1, padding: 16 }}>{/* 模态内容在这里 */}</View>; }
现在,要从 index.tsx 打开模态框,您可以在您的索引路由中使用 router.push('/modal'):
import { router } from 'expo-router'; import { Pressable, Text, View, StyleSheet } from 'react-native'; export default function Home() { return ( <View style={styles.container}> <Text style={styles.title}>Home Screen</Text> <Pressable onPress={() => router.push('/modal')} style={styles.button}> <Text style={styles.buttonText}>Open Modal</Text> </Pressable> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20, }, button: { backgroundColor: '#007AFF', padding: 16, borderRadius: 8, }, buttonText: { color: 'white', fontSize: 16, fontWeight: '600', }, });
以下是上述示例的结果:
锚点和嵌套堆栈
在使用堆栈或嵌套堆栈导航器时,模态框需要正确锚定,以确保正确的导航行为,尤其是在深度链接到模态路由时。如果没有锚点,模态框背后的屏幕将被擦除,失去导航上下文。
锚点_ 是模态框的基础。在复杂的应用中,当您有嵌套堆栈时,锚点必须为嵌套堆栈定义,其值成为堆栈的初始路由。
您可以通过从堆栈的布局文件导出 unable_settings 来配置锚点:
export const unstable_settings = { anchor: 'index', // 锚定到索引路由 };
在上述示例中,anchor: 'index' 告诉 Expo Router 在呈现模态时应在后台保持指定的锚点路由。
模态框呈现样式
模态框在您的网络应用中如何出现的差异在于屏幕较大(例如,台式机)时的呈现与在移动设备上运行时保持表单行为的不同,具体取决于配置选项。以下是可用于配置网络模态外观的选项,可以传递给 Stack.Screen 的 options 对象。
| 选项 | 类型 | 描述 |
|---|---|---|
presentation | 'modal'、'formSheet'、'transparentModal'、'containedTransparentModal' | 模态呈现样式。在宽度超过 768px 的屏幕上,所有样式显示为居中覆盖(例如,灯箱)。 在宽度小于 768px 的屏幕上,formSheet 用于显示为底部表单。当设置为 transparentModal 时,显示为没有完全遮挡背景内容的覆盖。捕捉位置和表单抓取器属性不会应用。该呈现方式在构建您自己的自定义模态时非常有用。 类似于 transparentModal,当设置为 containedTransparentModal 时,显示为没有完全遮挡背景内容的覆盖。捕捉位置和其他属性不会应用。该呈现方式在构建您自己的自定义模态时非常有用。 |
sheetAllowedDetents | number[]、'fitToContents' | 捕捉位置的百分比(0.0-1.0)或自动适应。仅适用于宽度小于 768px 的屏幕。 |
sheetGrabberVisible | boolean | **在 iOS 上,**显示/隐藏表单顶部的拖动手柄。在 Android 和网络上不支持。我们建议使用自定义表单头组件,以便在所有平台上模仿抓取器。 |
sheetCornerRadius | number | 表单的角半径(以像素为单位)。 |
webModalStyle | WebModalStyle | 特殊属性,允许网络特定的样式选项,以微调模态外观。 |
使用 webModalStyle 自定义模态样式
注意:webModalStyle属性仅适用于网络平台。在移动设备上,模态将自动适应以使用类似表单的行为以进行触摸交互。
您可以使用 webModalStyle 自定义网络上模态的尺寸和外观。它提供以下属性以供进一步自定义:
| 属性 | 类型 | 描述 | 默认 |
|---|---|---|---|
width | number string | 重写模态的宽度(像素或百分比)。仅适用于在桌面上的网络平台。 | 83vw |
height | number string | 重写模态的高度(像素或百分比)。仅适用于在桌面上的网络平台。 | 79vh |
minHeight | number string | 桌面模态的最小高度(像素或百分比)。覆盖默认的 iOS 26 尺寸。 | min(586px, 79vh) |
minWidth | number string | 桌面模态的最小宽度(像素或百分比)。覆盖默认的 iOS 26 尺寸。 | min(936px, 83vw) |
border | string | 重写桌面模态的边框(任何有效的 CSS 边框值,例如 '1px solid #ccc' 或 'none')。 | 无 |
overlayBackground | string | 重写覆盖背景颜色(任何有效的 CSS 颜色或 rgba/hsla 值)。 | 半透明黑色 |
shadow | string | 重写模态阴影过滤器(任何有效的 CSS 过滤器值,例如 'drop-shadow(0 4px 8px rgba(0,0,0,0.1))' 或 'none')。 | 投影阴影过滤器 |
自定义 CSS 属性
Expo Router 使用自定义 CSS 属性为模态样式设置,您可以使用 webModalStyle 全局覆盖这些属性。这些变量提供了对模态外观的细粒度控制。
宽度和高度尺寸变量
/* 默认模态宽度(桌面上为 83vw,遵循 iOS 26 规格) */ --expo-router-modal-width: 83vw; /* 最大模态宽度(最大 936px,默认 83vw,遵循 iOS 26) */ --expo-router-modal-max-width: min(936px, 83vw); /* 最小模态宽度(默认自动) */ --expo-router-modal-min-width: auto; /* 默认模态高度(79vh,遵循 iOS 26 规格) */ --expo-router-modal-height: 79vh; /* 最小模态高度(最大 586px,默认 79vh,遵循 iOS 26) */ --expo-router-modal-min-height: min(586px, 79vh);
边框和覆盖样式变量
/* 模态边框(默认无) */ --expo-router-modal-border: none; /* 模态角半径(默认 24px,遵循 iOS 26) */ --expo-router-modal-border-radius: 24px; /* 模态阴影过滤器(默认投影阴影) */ --expo-router-modal-shadow: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1)); /* 覆盖背景颜色(默认 25% 黑色) */ --expo-router-modal-overlay-background: rgba(0, 0, 0, 0.25);
如何将 webModalStyle 映射到 CSS 变量
当您使用 webModalStyle 重写任何尺寸变量时,Expo Router 会自动将这些 CSS 变量设置为您提供的值:
// 此 webModalStyle 配置 webModalStyle: { width: 800, height: 600, border: '2px solid blue', overlayBackground: 'rgba(0, 0, 0, 0.7)', shadow: 'drop-shadow(0 8px 16px rgba(0,0,0,0.2))', } // ...自动设置这些 CSS 变量: // --expo-router-modal-width: 800px // --expo-router-modal-height: 600px // --expo-router-modal-border: 2px solid blue // --expo-router-modal-overlay-background: rgba(0, 0, 0, 0.7) // --expo-router-modal-shadow: drop-shadow(0 8px 16px rgba(0,0,0,0.2))
常见示例
全屏模态框示例
要创建一个覆盖最大空间的全屏模态框,您可以在模态路由的 Stack.Screen 选项中使用 webModalStyle 属性:
import { Stack } from 'expo-router'; export const unstable_settings = { anchor: 'index', }; export default function Layout() { return ( <Stack> <Stack.Screen name="index" /> <Stack.Screen name="modal" options={{ presentation: 'modal', webModalStyle: { width: '95vw', height: '95vh', border: 'none', }, }} /> </Stack> ); }
以下是上述示例的结果:
在移动设备上运行您的网络应用时,您可以将 sheetAllowedDetents 设置为 fitToContents 或自定义值,以避免显示全屏模态框:
import { Stack } from 'expo-router'; export const unstable_settings = { anchor: 'index', }; export default function Layout() { return ( <Stack> <Stack.Screen name="index" /> <Stack.Screen name="modal" options={{ presentation: 'modal', webModalStyle: { width: '95vw', height: '95vh', border: 'none', }, sheetAllowedDetents: 'fitToContents', }} /> </Stack> ); }
模态框在移动设备上显示为表单:
紧凑模态框示例
对于较小的交互,您可以创建一个适应其内容的紧凑模态框:
import { Stack } from 'expo-router'; export const unstable_settings = { anchor: 'index', }; export default function Layout() { return ( <Stack> <Stack.Screen name="index" /> <Stack.Screen name="modal" options={{ presentation: 'modal', webModalStyle: { width: 400, height: 'auto', minHeight: 200, border: '1px solid #e5e7eb', overlayBackground: 'rgba(0, 0, 0, 0.3)', }, sheetCornerRadius: 12, sheetAllowedDetents: 'fitToContents', }} /> </Stack> ); }
以下是上述示例的结果:
透明模态框示例
当您想要显示一个应该保持底层屏幕可视上下文的覆盖时,可以将 presentation 选项设置为 transparentModal:
import { Stack } from 'expo-router'; export const unstable_settings = { anchor: 'index', }; export default function Layout() { return ( <Stack> <Stack.Screen name="index" /> <Stack.Screen name="modal" options={{ presentation: 'transparentModal', }} /> </Stack> ); }
以下是上述示例的结果:
角半径示例
您可以使用 sheetCornerRadius 自定义角半径:
import { Stack } from 'expo-router'; export const unstable_settings = { anchor: 'index', }; export default function Layout() { return ( <Stack> <Stack.Screen name="index" /> <Stack.Screen name="modal" options={{ presentation: 'formSheet', sheetAllowedDetents: [0.4], sheetCornerRadius: 32, }} /> </Stack> ); }
以下是上述示例的结果:
自定义捕捉位置示例
您可以使用 sheetAllowedDetents 定义模态框可以停靠的高度:
import { Stack } from 'expo-router'; export const unstable_settings = { anchor: 'index', }; export default function Layout() { return ( <Stack> <Stack.Screen name="index" /> <Stack.Screen name="modal" options={{ presentation: 'formSheet', sheetAllowedDetents: [0.2, 0.5, 0.8, 0.98], }} /> </Stack> ); }
以下是上述示例的结果:
全球 CSS 自定义
对于您的网络应用,如果您在项目中使用 global CSS 文件,您还可以覆盖宽度、高度、边框和覆盖变量。
您可以在全局 CSS 文件中使用 --expo-router-* 变量添加自定义值:
/* 在全局重写默认模态样式 */ :root { --expo-router-modal-width: 700px; --expo-router-modal-min-width: auto; --expo-router-modal-max-width: 95vw; --expo-router-modal-height: 640px; --expo-router-modal-min-height: 640px; --expo-router-modal-border: none; --expo-router-modal-border-radius: 16px; --expo-router-modal-shadow: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.2)); --expo-router-modal-overlay-background: rgba(0, 0, 0, 0.5); }
自定义模态路由实现
上面的 video 演示了一个模态窗口,它出现在网页的主要内容上方。背景变暗以吸引用户的注意力到模态框,模态框包含用户信息。这是网络模态的典型行为,用户可以与模态框进行交互或关闭模态框以返回到主页面。
您可以通过使用 transparentModal 呈现模式、样式化覆盖和模态内容以及利用 react-native-reanimated 动画模态的呈现,来实现上述网络模态行为。
修改您项目的根布局 (app/_layout.tsx) 以向模态路由添加 options 对象:
import { Stack } from 'expo-router'; export const unstable_settings = { initialRouteName: 'index', }; export default function Layout() { return ( <Stack> <Stack.Screen name="index" /> <Stack.Screen name="modal" options={{ presentation: 'transparentModal', animation: 'fade', headerShown: false, }} /> </Stack> ); }
注意:unstable_settings目前仅在Stack导航器中工作。
上述示例将 index 屏幕设置为 initialRouteName,使用 unstable_settings。这确保了透明模态总是在当前屏幕上呈现,即使用户通过直接链接导航到模态屏幕时也是如此。
在 modal.tsx 中样式化覆盖和模态内容,如下所示:
import { Link } from 'expo-router'; import { Pressable, StyleSheet, Text } from 'react-native'; import Animated, { FadeIn, SlideInDown } from 'react-native-reanimated'; export default function Modal() { return ( <Animated.View entering={FadeIn} style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#00000040', }} > {/* 按下外部时关闭模态框 */} <Link href={'/'} asChild> <Pressable style={StyleSheet.absoluteFill} /> </Link> <Animated.View entering={SlideInDown} style={{ width: '90%', height: '80%', alignItems: 'center', justifyContent: 'center', backgroundColor: 'white', }} > <Text style={{ fontWeight: 'bold', marginBottom: 10 }}>模态屏幕</Text> <Link href="/"> <Text>← 返回</Text> </Link> </Animated.View> </Animated.View> ); }
您可以根据需要自定义模态的外观。