跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
TypeScript大前端

Tauri 嵌入百度网页:从 iframe 迁移到 Webview 的实战经验

Tauri 桌面开发中嵌入第三方网页常遇 iframe 跨域限制导致交互失效。本文通过实际案例演示如何从 iframe 迁移至 Tauri Webview API,解决百度首页点击无响应问题。核心方案包括使用系统级 Webview 组件绕过同源策略,结合 ResizeObserver 实现响应式布局,并完善生命周期管理与错误降级处理。最终实现完整交互体验与资源自动清理,为类似场景提供可靠参考。

独立开发者发布于 2026/4/7更新于 2026/9/1074 浏览

Tauri 中嵌入百度网页:从 iframe 到 Webview 的迁移实践

问题描述

在开发 Tauri 桌面应用时,我们曾尝试在一个插件窗口中嵌入百度首页。最初使用 iframe 实现,但遇到了点击无响应的问题。最终通过迁移到 Tauri 的 Webview API 成功解决。

问题背景

我们的应用基于 Tauri 2.0 + Vue 3 + TypeScript 技术栈。需求是在 src/plugins/baidu/index.vue 中实现一个显示百度首页的插件窗口,同时保留窗口控制按钮(最小化、最大化、关闭)。

初次尝试:使用 iframe

实现代码
<template>
  <main data-tauri-drag-region>
    <ActionBar 
      :shrink="false" 
      :max-w="true" 
      :icon-color="'black'" 
      :top-win-label="WebviewWindow.getCurrent().label" 
      :current-label="WebviewWindow.getCurrent().label"
    />
    <iframe src="https://www.baidu.com" frameborder="0"></iframe>
  </main>
</template>

<script setup lang="ts">
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
</script>
遇到的问题

实现后,用户反馈:打开百度网页之后点击没反应。

问题分析

通过分析发现,问题出在 iframe 的跨域限制上:

  1. X-Frame-Options 限制:百度在响应头中设置了 X-Frame-Options: SAMEORIGIN,禁止在 iframe 中嵌入。
  2. 跨域安全策略:现代浏览器出于安全考虑,阻止了 iframe 内的点击事件。
  3. 用户体验差:即使能加载,iframe 内的交互也会受到各种限制。

iframe 方案在嵌入第三方网站(尤其是大型网站如百度)时存在根本性限制,不是技术实现的问题,而是浏览器安全策略的限制。

解决方案:使用 Tauri Webview API

研究现有实现

为了找到正确的实现方式,我研究了项目中其他使用外部链接的插件,比如动态内容插件和聊天机器人插件。在 src/components/rightBox/chatBox/Bot.vue 中找到了关键实现逻辑。

Webview API 的优势

相比 iframe,Tauri Webview API 具有以下优势:

  1. 无跨域限制:使用系统级 webview 组件,不受浏览器同源策略限制。
  2. 完整交互支持:支持所有正常的网页交互(点击、输入、导航等)。
  3. 更好的性能:原生组件,性能优于 iframe。
  4. 灵活的窗口管理:可以精确控制位置、大小、焦点等。
实现迁移

首先,我们需要将 iframe 替换为一个容器 div,用于挂载 Webview。

<template>
  <main data-tauri-drag-region>
    <ActionBar 
      :shrink="false" 
      :max-w="true" 
      :icon-color="'black'" 
      :top-win-label="currentWindow.label" 
      :current-label="currentWindow.label"
    />
    <div data-tauri-drag-region>
      <div ref="webviewContainer"></div>
    </div>
  </main>
</template>

接下来是核心的 Webview 创建逻辑。这里需要注意获取正确的窗口实例,并处理生命周期。

import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { Webview } from '@tauri-apps/api/webview'
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import { openUrl } from '@tauri-apps/plugin-opener'

const webviewContainer = ref<HTMLElement | null>(null)
const externalWebview = ref<Webview | null>(null)
const webviewLabel = 'baidu-webview'
const currentWindow = getCurrentWebviewWindow()
let containerResizeObserver: ResizeObserver | null = null
let windowResizeListener: (() => void) | null = null

const updateWebviewBounds = async () => {
  if (!webviewContainer.value || !externalWebview.value) return
  try {
    const rect = webviewContainer.value.getBoundingClientRect()
    await externalWebview.value.setPosition(rect.left, rect.top)
    await externalWebview.value.setSize(rect.width, rect.height)
  } catch (error) {
    console.error('更新 webview 边界失败:', error)
  }
}

const initWebview = async () => {
  await nextTick()
  if (!webviewContainer.value) {
    console.error('webviewContainer 未找到')
    return
  }
  const windowInstance = getCurrentWebviewWindow()
  try {
    const existing = await Webview.getByLabel(webviewLabel)
    if (existing) {
      await existing.close()
    }
  } catch (error) {
    console.log('没有找到已存在的 webview')
  }
  try {
    const rect = webviewContainer.value.getBoundingClientRect()
    const newWebview = new Webview(windowInstance, webviewLabel, {
      url: 'https://www.baidu.com',
      x: rect.left,
      y: rect.top,
      width: rect.width,
      height: rect.height,
      focus: true,
      dragDropEnabled: true
    })
    externalWebview.value = newWebview
    containerResizeObserver = new ResizeObserver(() => {
      updateWebviewBounds()
    })
    containerResizeObserver.observe(webviewContainer.value)
    windowResizeListener = () => {
      updateWebviewBounds()
    }
    window.addEventListener('resize', windowResizeListener, { passive: true })
    newWebview.once('tauri://created', async () => {
      console.log('Webview 创建成功')
      await updateWebviewBounds()
    })
    newWebview.once('tauri://error', (error) => {
      console.error('Webview 创建失败:', error)
      externalWebview.value = null
    })
  } catch (error) {
    console.error('创建 webview 失败:', error)
    console.log('尝试在系统浏览器中打开百度')
    try {
      await openUrl('https://www.baidu.com')
    } catch (openError) {
      console.error('在浏览器中打开失败:', openError)
    }
  }
}

onMounted(async () => {
  await initWebview()
})

onUnmounted(async () => {
  if (containerResizeObserver) {
    containerResizeObserver.disconnect()
  }
  if (windowResizeListener) {
    window.removeEventListener('resize', windowResizeListener)
  }
  if (externalWebview.value) {
    try {
      await externalWebview.value.close()
    } catch (error) {
      console.error('关闭 webview 失败:', error)
    }
  }
})
修复 WebviewWindow 导入错误

在实现过程中遇到了一个常见错误:Uncaught ReferenceError: WebviewWindow is not defined。

原因:模板中使用了 WebviewWindow.getCurrent(),但在 script 中只导入了 getCurrentWebviewWindow 函数,没有导入 WebviewWindow 类。

解决方法:

  1. 在 script 中添加 const currentWindow = getCurrentWebviewWindow() 来获取当前窗口实例。
  2. 在模板中将 WebviewWindow.getCurrent().label 替换为 currentWindow.label。

关键技术点

1. Webview 生命周期管理

创建、更新位置和大小、关闭都需要显式调用 API:

// 创建
const newWebview = new Webview(windowInstance, label, options)
// 更新位置和大小
await webview.setPosition(x, y)
await webview.setSize(width, height)
// 关闭
await webview.close()
2. 响应式布局处理

使用 ResizeObserver 监听容器尺寸变化,自动调整 Webview 大小,避免页面缩放导致错位。

3. 窗口大小变化处理

监听窗口 resize 事件,确保 Webview 始终正确显示,防止被遮挡或溢出。

4. 清理资源

在组件卸载时务必清理所有监听器和 Webview 实例,防止内存泄漏。

5. 错误处理和降级方案

当 Webview 创建失败时,降级到在系统浏览器中打开,保证功能可用性。

iframe vs Webview 对比

特性iframeTauri Webview
跨域限制受限,很多网站禁止嵌入无限制
交互支持受限完整支持
性能较差优秀
窗口管理有限灵活
适用场景同源内容、简单嵌入外部网站、复杂交互

最佳实践

何时使用 iframe
  • 嵌入同源内容
  • 简单的静态内容展示
  • 不需要复杂交互的场景
何时使用 Tauri Webview
  • 嵌入第三方网站(如百度、Google)
  • 需要完整网页交互
  • 需要精确控制窗口行为
  • 需要更好的性能
注意事项
  1. 获取正确的窗口实例:使用 getCurrentWebviewWindow() 而不是 WebviewWindow.getCurrent()。
  2. 清理资源:务必在组件卸载时清理 Webview 和监听器。
  3. 响应式布局:使用 ResizeObserver 处理容器尺寸变化。
  4. 错误处理:提供降级方案,提升用户体验。
  5. 唯一标识:为每个 Webview 设置唯一的 label,避免冲突。

总结

通过这次从 iframe 到 Tauri Webview 的迁移实践,我们成功解决了百度网页点击无响应的问题。关键经验包括:

  1. 理解技术限制:iframe 在嵌入第三方网站时存在根本性限制。
  2. 选择正确方案:Tauri Webview API 是嵌入外部网站的最佳选择。
  3. 注意 API 使用:正确使用 getCurrentWebviewWindow() 而不是 WebviewWindow。
  4. 完善生命周期管理:妥善处理创建、更新、销毁等各个环节。
  5. 提供降级方案:在 Webview 创建失败时提供备选方案。

这次实践不仅解决了具体问题,也加深了对 Tauri 框架的理解,为后续开发积累了宝贵经验。

参考资料

  • Tauri 2.0 官方文档
  • Tauri Webview API
  • Tauri WebviewWindow API
  • Opener 插件文档

目录

  1. Tauri 中嵌入百度网页:从 iframe 到 Webview 的迁移实践
  2. 问题描述
  3. 问题背景
  4. 初次尝试:使用 iframe
  5. 实现代码
  6. 遇到的问题
  7. 问题分析
  8. 解决方案:使用 Tauri Webview API
  9. 研究现有实现
  10. Webview API 的优势
  11. 实现迁移
  12. 修复 WebviewWindow 导入错误
  13. 关键技术点
  14. 1. Webview 生命周期管理
  15. 2. 响应式布局处理
  16. 3. 窗口大小变化处理
  17. 4. 清理资源
  18. 5. 错误处理和降级方案
  19. iframe vs Webview 对比
  20. 最佳实践
  21. 何时使用 iframe
  22. 何时使用 Tauri Webview
  23. 注意事项
  24. 总结
  25. 参考资料

更多推荐文章

查看全部
  • UNet WebUI 镜像 AI 抠图入门教程
  • 基于 DeepSeek 和 Cursor 构建智能代码审查工具实践
  • GitHub Copilot 提示词工程指南:从基础到精通的 AI 编程协作
  • Python Web UI 自动化测试:Jenkins 构建项目与定时执行
  • Python 安全有效地处理配置的最佳实践
  • AI 大模型详解:定义、原理与核心应用
  • 基于 Brython 与 LocalStorage 实现本地记仇本应用
  • 腾讯云轻量应用服务器部署 OpenClaw 接入 QQ 与飞书机器人
  • 2025 最新大模型 LLM 学习路线图与岗位需求分析
  • 大模型、超大模型与 Foundation Model 概念解析
  • 大模型应用开发入门:LangChain 实战指南
  • 7 款主流 AI PPT 工具深度横评
  • 数据库数据切分原理:垂直与水平策略解析
  • Linux 下调试 C/C++ 程序的核心 GDB 命令
  • AI 效率工具实战:3 个普通人可掌握的工作技巧
  • Python 实现 MCP 客户端调用高德地图天气查询示例
  • 使用 VSCode 开发 STM32 全流程指南
  • ClawdBot (OpenClaw) Discord 机器人部署指南
  • 字节跳动前端一面面经深度解析
  • Java 并发编程:JUC 包中原子操作类的原理和用法

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online