250 行
5.4 KiB
Markdown
250 行
5.4 KiB
Markdown
|
|
# XWebView
|
|||
|
|
|
|||
|
|
`@xuqm/rn-xwebview` 提供可直接打开的独立 WebView 页面,也支持在业务页面中嵌入浏览器
|
|||
|
|
内核。SDK 只提供通用容器和 Bridge 接入入口,不内置任何宿主业务协议。
|
|||
|
|
|
|||
|
|
## 安装
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
pnpm add @xuqm/rn-common @xuqm/rn-xwebview \
|
|||
|
|
react-native-webview react-native-svg \
|
|||
|
|
@react-native-clipboard/clipboard react-native-safe-area-context
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
这些原生依赖需要由宿主直接声明,以便 React Native 自动链接。
|
|||
|
|
|
|||
|
|
## 根部挂载 Host
|
|||
|
|
|
|||
|
|
独立页面模式需要在应用根部挂载一次:
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
import { XWebViewHost } from '@xuqm/rn-xwebview'
|
|||
|
|
|
|||
|
|
export function App() {
|
|||
|
|
return (
|
|||
|
|
<>
|
|||
|
|
<RootNavigation />
|
|||
|
|
<XWebViewHost />
|
|||
|
|
</>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
全局只能存在一个 `XWebViewHost`。
|
|||
|
|
|
|||
|
|
## 打开独立页面
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
import { openWebView } from '@xuqm/rn-xwebview'
|
|||
|
|
|
|||
|
|
const page = openWebView({
|
|||
|
|
source: {
|
|||
|
|
uri: 'https://example.com',
|
|||
|
|
headers: {
|
|||
|
|
'X-From-App': 'true',
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
navigationBar: {
|
|||
|
|
title: {
|
|||
|
|
text: '服务页面',
|
|||
|
|
mode: 'web',
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
statusBar: {
|
|||
|
|
translucent: true,
|
|||
|
|
contentStyle: 'dark-content',
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const result = await page.closed
|
|||
|
|
console.log(result.reason, result.data)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
返回的页面 Handle 可以:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
page.reload()
|
|||
|
|
page.goBack()
|
|||
|
|
page.goForward()
|
|||
|
|
page.postMessage(JSON.stringify({ type: 'refresh' }))
|
|||
|
|
page.evaluateJavaScript('window.refreshPage?.()')
|
|||
|
|
page.suspend()
|
|||
|
|
page.resume()
|
|||
|
|
page.close('api', { completed: true })
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`suspend()` 用于临时展示宿主原生页面,页面实例和网页历史会保留;完成后调用
|
|||
|
|
`resume()`。
|
|||
|
|
|
|||
|
|
## 默认导航和返回规则
|
|||
|
|
|
|||
|
|
- 左上角返回按钮只在当前网页存在历史页面时显示,只负责网页后退。
|
|||
|
|
- 右上角关闭按钮始终显示,点击立即关闭当前 WebView 页面。
|
|||
|
|
- Android 物理返回键优先返回网页历史。
|
|||
|
|
- 没有网页历史时,物理返回键需要在一秒内再次按下才关闭页面。
|
|||
|
|
- 默认菜单包含刷新、复制链接和在系统浏览器打开,不包含关闭页面。
|
|||
|
|
|
|||
|
|
## 导航栏与状态栏
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
openWebView({
|
|||
|
|
source: { uri: 'https://example.com' },
|
|||
|
|
navigationBar: {
|
|||
|
|
background: {
|
|||
|
|
gradient: ['#1769E0', '#55A8FF'],
|
|||
|
|
gradientAngle: 90,
|
|||
|
|
// 也可以使用 color 或 image
|
|||
|
|
},
|
|||
|
|
title: {
|
|||
|
|
text: '渐变导航栏',
|
|||
|
|
color: '#FFFFFF',
|
|||
|
|
mode: 'fixed',
|
|||
|
|
},
|
|||
|
|
backButton: { color: '#FFFFFF' },
|
|||
|
|
closeButton: { color: '#FFFFFF' },
|
|||
|
|
},
|
|||
|
|
statusBar: {
|
|||
|
|
backgroundColor: '#1769E0',
|
|||
|
|
contentStyle: 'light-content',
|
|||
|
|
translucent: true,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
网络背景图片异步加载,不会阻塞 `openWebView` 返回。
|
|||
|
|
|
|||
|
|
完整替换导航栏时使用 `navigationBar.render(context)`。自定义返回按钮应调用
|
|||
|
|
`context.onBackPress()`,以保留统一的网页后退规则。
|
|||
|
|
|
|||
|
|
## 自定义菜单
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
openWebView({
|
|||
|
|
source: { uri: 'https://example.com' },
|
|||
|
|
navigationBar: {
|
|||
|
|
menu: {
|
|||
|
|
visible: true,
|
|||
|
|
items: [
|
|||
|
|
{
|
|||
|
|
id: 'feedback',
|
|||
|
|
label: '问题反馈',
|
|||
|
|
onPress({ navigationState }) {
|
|||
|
|
openFeedback({ url: navigationState.url })
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
不需要菜单时设置 `menu.visible: false`。
|
|||
|
|
|
|||
|
|
## 摄像头和麦克风
|
|||
|
|
|
|||
|
|
宿主先声明系统权限,然后为具体网页配置精确 Origin:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
openWebView({
|
|||
|
|
source: { uri: 'https://verify.example.com/start' },
|
|||
|
|
permissions: {
|
|||
|
|
allowedOrigins: ['https://verify.example.com'],
|
|||
|
|
camera: true,
|
|||
|
|
microphone: true,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Android Manifest 示例:
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<uses-permission android:name="android.permission.CAMERA" />
|
|||
|
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Origin 必须包含协议和主机,跨域后的页面不会继承原页面授权。宿主需要自定义决策时:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
permissions: {
|
|||
|
|
allowedOrigins: ['https://verify.example.com'],
|
|||
|
|
camera: true,
|
|||
|
|
microphone: true,
|
|||
|
|
onRequest(request) {
|
|||
|
|
if (canUseMedia(request.origin)) request.grant()
|
|||
|
|
else request.deny()
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 文件下载
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
openWebView({
|
|||
|
|
source: { uri: 'https://example.com/files' },
|
|||
|
|
downloads: {
|
|||
|
|
auto: true,
|
|||
|
|
destination: 'publicDownloads',
|
|||
|
|
conflict: 'rename',
|
|||
|
|
onProgress(progress) {
|
|||
|
|
console.log(progress.percentage)
|
|||
|
|
},
|
|||
|
|
onComplete(result) {
|
|||
|
|
console.log(result.filePath)
|
|||
|
|
},
|
|||
|
|
onError(url, error) {
|
|||
|
|
console.warn(url, error)
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
默认保存到应用沙盒。保存到系统 Downloads 只在目标平台支持时生效。
|
|||
|
|
|
|||
|
|
## 宿主 Bridge
|
|||
|
|
|
|||
|
|
宿主与 H5 自行约定消息协议,通过 `bridge` 注入:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
openWebView({
|
|||
|
|
source: { uri: 'https://example.com' },
|
|||
|
|
bridge: {
|
|||
|
|
async onMessage(raw, context) {
|
|||
|
|
const message = JSON.parse(raw)
|
|||
|
|
|
|||
|
|
if (message.method === 'close') {
|
|||
|
|
context.close('h5_close', message.data)
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (message.method === 'setTitle') {
|
|||
|
|
context.updateNavigation({
|
|||
|
|
title: { text: String(message.title ?? '') },
|
|||
|
|
})
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return false
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
XWebView 不规定方法名、App ID、厂商、用户或业务参数。协议校验、权限和业务结果由宿主
|
|||
|
|
自己的 Bridge 负责。
|
|||
|
|
|
|||
|
|
## 内嵌 WebView
|
|||
|
|
|
|||
|
|
不需要独立页面和内置导航栏时:
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
import { XWebViewView } from '@xuqm/rn-xwebview'
|
|||
|
|
|
|||
|
|
<XWebViewView
|
|||
|
|
config={{
|
|||
|
|
source: { uri: 'https://example.com' },
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
内嵌模式由所在页面负责尺寸、导航和关闭交互。
|