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

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

研究现有实现

为了找到正确的实现方式,我研究了项目中其他使用外部链接的插件:

  1. dynamic/index.vue:动态内容插件
  2. robot/index.vue:聊天机器人插件
  3. Bot.vue:聊天框组件,包含 Webview 实现

src/components/rightBox/chatBox/Bot.vue 中找到了关键实现:

constcreateExternalWebview=async(url:string)=>{const windowInstance =awaitensureHostWindow()if(!windowInstance ||!webviewContainer.value)returntry{const existing =await Webview.getByLabel(webviewLabel)await existing?.close()}catch(error){// 忽略未找到的情况}awaitdestroyExternalWebview()const rect = webviewContainer.value.getBoundingClientRect()const newWebview =newWebview(windowInstance, webviewLabel,{ url, x: rect.left, y: rect.top, width: rect.width, height: rect.height, focus:true, dragDropEnabled:true}) externalWebview.value = newWebview containerResizeObserver =newResizeObserver(()=>{updateExternalWebviewBounds()}) containerResizeObserver.observe(webviewContainer.value) window.addEventListener('resize', updateExternalWebviewBounds,{ passive:true})}

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=nulllet windowResizeListener:(()=>void)|null=nullconstupdateWebviewBounds=async()=>{if(!webviewContainer.value ||!externalWebview.value)returntry{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)}}constinitWebview=async()=>{awaitnextTick()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 =newWebview(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 =newResizeObserver(()=>{updateWebviewBounds()}) containerResizeObserver.observe(webviewContainer.value)windowResizeListener=()=>{updateWebviewBounds()} window.addEventListener('resize', windowResizeListener,{ passive:true}) newWebview.once('tauri://created',async()=>{console.log('Webview 创建成功')awaitupdateWebviewBounds()}) newWebview.once('tauri://error',(error)=>{console.error('Webview 创建失败:', error) externalWebview.value =null})}catch(error){console.error('创建 webview 失败:', error)console.log('尝试在系统浏览器中打开百度')try{awaitopenUrl('https://www.baidu.com')}catch(openError){console.error('在浏览器中打开失败:', openError)}}}onMounted(async()=>{awaitinitWebview()})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

修改后的代码:

const currentWindow =getCurrentWebviewWindow()
<ActionBar :top-win-label="currentWindow.label" :current-label="currentWindow.label"/> 

关键技术点

1. Webview 生命周期管理

// 创建const newWebview =newWebview(windowInstance, label, options)// 更新位置和大小await webview.setPosition(x, y)await webview.setSize(width, height)// 关闭await webview.close()

2. 响应式布局处理

使用 ResizeObserver 监听容器尺寸变化,自动调整 Webview 大小:

containerResizeObserver =newResizeObserver(()=>{updateWebviewBounds()}) containerResizeObserver.observe(webviewContainer.value)

3. 窗口大小变化处理

监听窗口 resize 事件,确保 Webview 始终正确显示:

windowResizeListener=()=>{updateWebviewBounds()} window.addEventListener('resize', windowResizeListener,{ passive:true})

4. 清理资源

在组件卸载时清理所有监听器和 Webview 实例:

onUnmounted(async()=>{if(containerResizeObserver){ containerResizeObserver.disconnect()}if(windowResizeListener){ window.removeEventListener('resize', windowResizeListener)}if(externalWebview.value){await externalWebview.value.close()}})

5. 错误处理和降级方案

当 Webview 创建失败时,降级到在系统浏览器中打开:

try{const newWebview =newWebview(windowInstance, webviewLabel, options)}catch(error){console.error('创建 webview 失败:', error)try{awaitopenUrl('https://www.baidu.com')}catch(openError){console.error('在浏览器中打开失败:', openError)}}

iframe vs Webview 对比

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

最佳实践

1. 何时使用 iframe

  • 嵌入同源内容
  • 简单的静态内容展示
  • 不需要复杂交互的场景

2. 何时使用 Tauri Webview

  • 嵌入第三方网站(如百度、Google)
  • 需要完整网页交互
  • 需要精确控制窗口行为
  • 需要更好的性能

3. 注意事项

  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 框架的理解,为后续开发积累了宝贵经验。

参考资料

Read more

前端已死?元编程时代:用AI Skills重构你的开发工作流

摘要:本文深入探讨了新兴的“AI Skills”概念,它远不止是简单的Prompt技巧,而是一种将最佳实践、团队规范和技术栈封装成可执行文件的结构化工程范式。文章将系统阐述AI Skills如何从前端开发的“辅助工具”升级为“核心生产力”,通过UI组件生成、API客户端编码、智能测试等具体场景,展示其对工作流的颠覆性重构。我们将深入其技术原理,提供可操作的实践路径,并展望在这一范式下,前端开发者如何从“代码劳工”转变为“规则制定者”和“智能工作流架构师”。 关键字:AI Skills、前端开发、工作流重构、低错误率、Prompt工程、元编程 引言:超越ChatGPT,迎接“可编程的智能体” 🚀 如果你还停留在用ChatGPT手动复制粘贴代码片段,偶尔还要为它生成的过时或错误代码“擦屁股”的阶段,那么你正在浪费AI 90%的潜力。前端开发的范式革命已然来临,其核心不再是“会不会用AI”,而是“如何系统化、

【前端】win11操作系统安装完最新版本的NodeJs运行npm install报错,提示在此系统上禁止运行脚本

【前端】win11操作系统安装完最新版本的NodeJs运行npm install报错,提示在此系统上禁止运行脚本

🌹欢迎来到《小5讲堂》🌹 🌹这是《前端》系列文章,每篇文章将以博主理解的角度展开讲解。🌹 🌹温馨提示:博主能力有限,理解水平有限,若有不对之处望指正!🌹 目录 * 前言 * 解决方案 * 方法1:以管理员身份运行 PowerShell 并更改执行策略 * 方法2:只为当前会话临时允许 * 方法3:使用命令提示符 (CMD) * 方法4:绕过策略执行单个脚本 * 推荐解决方案 * Node.js 详细介绍 * 什么是 Node.js? * 核心特点 * 1. **非阻塞 I/O 和事件驱动** * 2. **单线程但高并发** * 架构组成 * 1. **V8 JavaScript 引擎** * 2. **LibUV 库** * 3. **核心模块** * 安装与使用

手把手教你配置:企业微信外部群 Webhook 主动发送指南

QiWe开放平台 · 个人名片                 API驱动企微自动化,让开发更高效         核心能力:为开发者提供标准化接口、快速集成工具,助力产品高效拓展功能场景         官方站点:https://www.qiweapi.com         团队定位:专注企微API生态的技术服务团队        对接通道:搜「QiWe 开放平台」联系客服         核心理念:合规赋能,让企微开发更简单、更高效   在企业微信的自动化体系中,群机器人(Webhook) 是实现系统消息自动同步到外部群最快捷、门槛最低的工具。 虽然 2026 年官方对外部群机器人的管理更加精细化,但只要掌握正确的配置流程和调用逻辑,它依然是效率提升的神器。以下是完整的实操步骤: 第一步:获取 Webhook 地址 1. 添加机器人: 打开企业微信电脑端,进入你需要配置的外部群,点击右上角“...”,选择“群机器人” -> “添加机器人”。 2.

OpenClaw 2026.3.23 重大更新:千里通 Arm 架构 Linux 小主机完配“小龙虾”,开启轻量级 AI 新纪元

2026 年 3 月 23 日,开源社区迎来了一场名为“红色风暴”的迭代。备受全球开发者瞩目的个人 AI 助手平台 OpenClaw(因其图标和极客圈昵称,被亲切地称为“小龙虾”)正式发布了 2026.3.23 版本。 本次更新不仅修复了多项关键稳定性问题,更带来了一个令嵌入式与边缘计算爱好者振奋的消息:官方完美适配 Arm 架构 Linux 系统。这意味着,搭载 Ubuntu 系统的千里通 Arm 架构小主机,如今已成为运行“小龙虾”的绝佳搭档,让每个人都能以极低的成本,拥有一台 24 小时在线的私有化 AI 数字员工。 🦞 为什么是“千里通”小主机 + OpenClaw? 在 OpenClaw 2026.