目录
-
- 摘要
- [1. 引言 - Canvas 导航的价值](#1. 引言 - Canvas 导航的价值)
-
- [1.1 导航应用场景](#1.1 导航应用场景)
- [1.2 导航功能概览](#1.2 导航功能概览)
- [1.3 导航 vs 浏览器](#1.3 导航 vs 浏览器)
- [2. 基本导航操作](#2. 基本导航操作)
-
- [2.1 加载 URL](#2.1 加载 URL)
- [2.2 展示 URL](#2.2 展示 URL)
- [2.3 设置尺寸](#2.3 设置尺寸)
- [2.4 隐藏 Canvas](#2.4 隐藏 Canvas)
- [3. 导航参数详解](#3. 导航参数详解)
-
- [3.1 URL 参数](#3.1 URL 参数)
- [3.2 尺寸控制](#3.2 尺寸控制)
- [3.3 位置控制](#3.3 位置控制)
- [4. 实战案例一:文档嵌入](#4. 实战案例一:文档嵌入)
-
- [4.1 场景描述](#4.1 场景描述)
- [4.2 实现代码](#4.2 实现代码)
- [4.3 文档导航菜单](#4.3 文档导航菜单)
- [5. 实战案例二:工具集成](#5. 实战案例二:工具集成)
-
- [5.1 场景描述](#5.1 场景描述)
- [5.2 实现代码](#5.2 实现代码)
- [6. 实战案例三:多页面应用](#6. 实战案例三:多页面应用)
-
- [6.1 场景描述](#6.1 场景描述)
- [6.2 实现代码](#6.2 实现代码)
- [7. 高级导航技巧](#7. 高级导航技巧)
-
- [7.1 动态 URL 构建](#7.1 动态 URL 构建)
- [7.2 条件导航](#7.2 条件导航)
- [7.3 导航历史](#7.3 导航历史)
- [8. 与 JavaScript 配合](#8. 与 JavaScript 配合)
-
- [8.1 执行导航脚本](#8.1 执行导航脚本)
- [8.2 获取当前 URL](#8.2 获取当前 URL)
- [8.3 刷新页面](#8.3 刷新页面)
- [9. 最佳实践](#9. 最佳实践)
-
- [9.1 导航设计原则](#9.1 导航设计原则)
- [9.2 性能优化](#9.2 性能优化)
- [9.3 安全考虑](#9.3 安全考虑)
- [10. 总结](#10. 总结)
-
- [10.1 核心要点](#10.1 核心要点)
- [10.2 下一步](#10.2 下一步)
- 参考资料
摘要
本文深入探讨 OpenClaw Canvas 的导航功能。从 URL 加载、页面控制、导航历史到高级导航技巧,全面解析如何通过 Canvas 实现灵活的页面导航。通过实际案例演示文档嵌入、工具集成、多页面切换等场景,帮助开发者构建具有导航能力的可视化应用。🧭
1. 引言 - Canvas 导航的价值
1.1 导航应用场景
| 场景 |
说明 |
示例 |
| 文档展示 |
嵌入外部文档 |
API文档、帮助文档 |
| 工具集成 |
嵌入外部工具 |
图表工具、编辑器 |
| 内容预览 |
预览生成内容 |
网页预览、报告预览 |
| 多页面应用 |
多页面切换 |
向导式流程 |
1.2 导航功能概览
#mermaid-svg-rCb6TBoFo5iwrMqR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rCb6TBoFo5iwrMqR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rCb6TBoFo5iwrMqR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rCb6TBoFo5iwrMqR .error-icon{fill:#552222;}#mermaid-svg-rCb6TBoFo5iwrMqR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rCb6TBoFo5iwrMqR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rCb6TBoFo5iwrMqR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rCb6TBoFo5iwrMqR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rCb6TBoFo5iwrMqR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rCb6TBoFo5iwrMqR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rCb6TBoFo5iwrMqR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rCb6TBoFo5iwrMqR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rCb6TBoFo5iwrMqR .marker.cross{stroke:#333333;}#mermaid-svg-rCb6TBoFo5iwrMqR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rCb6TBoFo5iwrMqR p{margin:0;}#mermaid-svg-rCb6TBoFo5iwrMqR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rCb6TBoFo5iwrMqR .cluster-label text{fill:#333;}#mermaid-svg-rCb6TBoFo5iwrMqR .cluster-label span{color:#333;}#mermaid-svg-rCb6TBoFo5iwrMqR .cluster-label span p{background-color:transparent;}#mermaid-svg-rCb6TBoFo5iwrMqR .label text,#mermaid-svg-rCb6TBoFo5iwrMqR span{fill:#333;color:#333;}#mermaid-svg-rCb6TBoFo5iwrMqR .node rect,#mermaid-svg-rCb6TBoFo5iwrMqR .node circle,#mermaid-svg-rCb6TBoFo5iwrMqR .node ellipse,#mermaid-svg-rCb6TBoFo5iwrMqR .node polygon,#mermaid-svg-rCb6TBoFo5iwrMqR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rCb6TBoFo5iwrMqR .rough-node .label text,#mermaid-svg-rCb6TBoFo5iwrMqR .node .label text,#mermaid-svg-rCb6TBoFo5iwrMqR .image-shape .label,#mermaid-svg-rCb6TBoFo5iwrMqR .icon-shape .label{text-anchor:middle;}#mermaid-svg-rCb6TBoFo5iwrMqR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rCb6TBoFo5iwrMqR .rough-node .label,#mermaid-svg-rCb6TBoFo5iwrMqR .node .label,#mermaid-svg-rCb6TBoFo5iwrMqR .image-shape .label,#mermaid-svg-rCb6TBoFo5iwrMqR .icon-shape .label{text-align:center;}#mermaid-svg-rCb6TBoFo5iwrMqR .node.clickable{cursor:pointer;}#mermaid-svg-rCb6TBoFo5iwrMqR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rCb6TBoFo5iwrMqR .arrowheadPath{fill:#333333;}#mermaid-svg-rCb6TBoFo5iwrMqR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rCb6TBoFo5iwrMqR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rCb6TBoFo5iwrMqR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rCb6TBoFo5iwrMqR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rCb6TBoFo5iwrMqR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rCb6TBoFo5iwrMqR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rCb6TBoFo5iwrMqR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rCb6TBoFo5iwrMqR .cluster text{fill:#333;}#mermaid-svg-rCb6TBoFo5iwrMqR .cluster span{color:#333;}#mermaid-svg-rCb6TBoFo5iwrMqR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rCb6TBoFo5iwrMqR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rCb6TBoFo5iwrMqR rect.text{fill:none;stroke-width:0;}#mermaid-svg-rCb6TBoFo5iwrMqR .icon-shape,#mermaid-svg-rCb6TBoFo5iwrMqR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rCb6TBoFo5iwrMqR .icon-shape p,#mermaid-svg-rCb6TBoFo5iwrMqR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rCb6TBoFo5iwrMqR .icon-shape .label rect,#mermaid-svg-rCb6TBoFo5iwrMqR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rCb6TBoFo5iwrMqR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rCb6TBoFo5iwrMqR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rCb6TBoFo5iwrMqR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Canvas 导航
navigate
加载URL
present
展示HTML/URL
eval
执行导航脚本
外部网页
自定义内容
动态控制
1.3 导航 vs 浏览器
| 对比项 |
Canvas |
Browser |
| 用途 |
展示界面 |
自动化操作 |
| 交互 |
用户交互 |
AI控制 |
| 场景 |
可视化展示 |
数据采集 |
| 控制 |
有限控制 |
完全控制 |
2. 基本导航操作
2.1 加载 URL
canvas(
action="navigate",
url="https://example.com"
)
2.2 展示 URL
canvas(
action="present",
url="https://docs.openclaw.ai"
)
2.3 设置尺寸
canvas(
action="present",
url="https://example.com",
width=800,
height=600
)
2.4 隐藏 Canvas
canvas(action="hide")
3. 导航参数详解
3.1 URL 参数
| 参数 |
类型 |
说明 |
| url |
string |
目标URL |
| width |
number |
宽度(像素) |
| height |
number |
高度(像素) |
| x |
number |
X坐标 |
| y |
number |
Y坐标 |
3.2 尺寸控制
# 全屏展示
canvas(
action="present",
url="https://example.com",
width=1920,
height=1080
)
# 小窗口展示
canvas(
action="present",
url="https://example.com",
width=400,
height=300
)
3.3 位置控制
# 指定位置展示
canvas(
action="present",
url="https://example.com",
x=100,
y=100,
width=800,
height=600
)
4. 实战案例一:文档嵌入
4.1 场景描述
嵌入 OpenClaw 官方文档供用户查阅。
4.2 实现代码
def show_documentation(section=None):
"""展示文档"""
base_url = "https://docs.openclaw.ai"
if section:
url = f"{base_url}/{section}"
else:
url = base_url
canvas(
action="present",
url=url,
width=1000,
height=700
)
# 使用示例
show_documentation() # 首页
show_documentation("getting-started") # 快速开始
show_documentation("api-reference") # API参考
4.3 文档导航菜单
def show_docs_with_menu():
"""带导航菜单的文档展示"""
html = """
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
}
.sidebar {
position: fixed;
left: 0;
top: 0;
width: 200px;
height: 100vh;
background: #1a1a2e;
color: white;
padding: 20px;
}
.sidebar h3 {
margin-top: 0;
}
.sidebar a {
display: block;
color: #aaa;
text-decoration: none;
padding: 10px 0;
}
.sidebar a:hover {
color: white;
}
.content {
margin-left: 240px;
padding: 20px;
}
iframe {
width: 100%;
height: 80vh;
border: none;
}
</style>
</head>
<body>
<div class="sidebar">
<h3>📚 文档导航</h3>
<a href="#" onclick="loadDoc('getting-started')">快速开始</a>
<a href="#" onclick="loadDoc('installation')">安装指南</a>
<a href="#" onclick="loadDoc('configuration')">配置说明</a>
<a href="#" onclick="loadDoc('api-reference')">API参考</a>
<a href="#" onclick="loadDoc('skills')">技能开发</a>
</div>
<div class="content">
<iframe id="doc-frame" src="https://docs.openclaw.ai"></iframe>
</div>
<script>
function loadDoc(section) {
document.getElementById('doc-frame').src =
'https://docs.openclaw.ai/' + section;
}
</script>
</body>
</html>
"""
canvas(action="present", html=html)
5. 实战案例二:工具集成
5.1 场景描述
集成外部工具到 Canvas 中。
5.2 实现代码
def show_tool_dashboard():
"""展示工具仪表盘"""
html = """
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
background: #f5f5f5;
}
.header {
background: #667eea;
color: white;
padding: 15px 20px;
}
.tabs {
display: flex;
background: white;
border-bottom: 1px solid #ddd;
}
.tab {
padding: 15px 25px;
cursor: pointer;
border-bottom: 3px solid transparent;
}
.tab:hover {
background: #f5f5f5;
}
.tab.active {
border-bottom-color: #667eea;
color: #667eea;
}
.tool-container {
padding: 20px;
}
iframe {
width: 100%;
height: 70vh;
border: 1px solid #ddd;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="header">
<h2>🛠️ 工具集成面板</h2>
</div>
<div class="tabs">
<div class="tab active" onclick="showTool('editor')">代码编辑器</div>
<div class="tab" onclick="showTool('chart')">图表工具</div>
<div class="tab" onclick="showTool('docs')">文档</div>
</div>
<div class="tool-container">
<iframe id="tool-frame" src="https://monaco-editor.org"></iframe>
</div>
<script>
const tools = {
'editor': 'https://monaco-editor.org',
'chart': 'https://www.chartjs.org',
'docs': 'https://docs.openclaw.ai'
};
function showTool(tool) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
event.target.classList.add('active');
document.getElementById('tool-frame').src = tools[tool];
}
</script>
</body>
</html>
"""
canvas(action="present", html=html)
6. 实战案例三:多页面应用
6.1 场景描述
创建多页面切换的应用界面。
6.2 实现代码
def show_multi_page_app():
"""多页面应用"""
html = """
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
}
.app {
display: flex;
height: 100vh;
}
.nav {
width: 250px;
background: #1a1a2e;
color: white;
padding: 20px;
}
.nav h2 {
margin-top: 0;
}
.nav-item {
padding: 15px;
margin: 5px 0;
border-radius: 8px;
cursor: pointer;
}
.nav-item:hover {
background: rgba(255,255,255,0.1);
}
.nav-item.active {
background: #667eea;
}
.main {
flex: 1;
padding: 30px;
overflow-y: auto;
}
.page {
display: none;
}
.page.active {
display: block;
}
.card {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="app">
<div class="nav">
<h2>🎯 控制面板</h2>
<div class="nav-item active" onclick="showPage('overview')">
📊 概览
</div>
<div class="nav-item" onclick="showPage('users')">
👥 用户管理
</div>
<div class="nav-item" onclick="showPage('settings')">
⚙️ 设置
</div>
<div class="nav-item" onclick="showPage('reports')">
📈 报告
</div>
</div>
<div class="main">
<div id="overview" class="page active">
<h1>📊 概览</h1>
<div class="card">
<h3>系统状态</h3>
<p>所有服务运行正常</p>
</div>
</div>
<div id="users" class="page">
<h1>👥 用户管理</h1>
<div class="card">
<h3>用户列表</h3>
<p>共 100 个用户</p>
</div>
</div>
<div id="settings" class="page">
<h1>⚙️ 设置</h1>
<div class="card">
<h3>系统配置</h3>
<p>配置项...</p>
</div>
</div>
<div id="reports" class="page">
<h1>📈 报告</h1>
<div class="card">
<h3>数据报告</h3>
<p>报告内容...</p>
</div>
</div>
</div>
</div>
<script>
function showPage(pageId) {
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
event.target.classList.add('active');
document.getElementById(pageId).classList.add('active');
}
</script>
</body>
</html>
"""
canvas(action="present", html=html)
7. 高级导航技巧
7.1 动态 URL 构建
def navigate_with_params(base_url, params):
"""带参数的导航"""
query_string = "&".join([f"{k}={v}" for k, v in params.items()])
url = f"{base_url}?{query_string}"
canvas(action="navigate", url=url)
7.2 条件导航
def conditional_navigation(user_role):
"""根据条件导航"""
role_urls = {
"admin": "https://admin.example.com",
"user": "https://app.example.com",
"guest": "https://welcome.example.com"
}
url = role_urls.get(user_role, role_urls["guest"])
canvas(action="navigate", url=url)
7.3 导航历史
class NavigationHistory:
def __init__(self):
self.history = []
self.current = -1
def navigate(self, url):
self.history = self.history[:self.current + 1]
self.history.append(url)
self.current += 1
canvas(action="navigate", url=url)
def back(self):
if self.current > 0:
self.current -= 1
canvas(action="navigate", url=self.history[self.current])
def forward(self):
if self.current < len(self.history) - 1:
self.current += 1
canvas(action="navigate", url=self.history[self.current])
8. 与 JavaScript 配合
8.1 执行导航脚本
def navigate_via_js(url):
"""通过 JS 导航"""
canvas(
action="eval",
javaScript=f"""
window.location.href = '{url}';
"""
)
8.2 获取当前 URL
def get_current_url():
"""获取当前 URL"""
result = canvas(
action="eval",
javaScript="window.location.href"
)
return result
8.3 刷新页面
def refresh_canvas():
"""刷新 Canvas 内容"""
canvas(
action="eval",
javaScript="window.location.reload()"
)
9. 最佳实践
9.1 导航设计原则
| 原则 |
说明 |
| 清晰导航 |
用户知道当前位置 |
| 快速响应 |
加载要有反馈 |
| 错误处理 |
处理加载失败 |
| 历史记录 |
支持前进后退 |
9.2 性能优化
| 优化项 |
说明 |
| 预加载 |
预加载常用页面 |
| 缓存 |
缓存已加载内容 |
| 懒加载 |
按需加载资源 |
9.3 安全考虑
| 安全项 |
说明 |
| URL验证 |
验证URL合法性 |
| 内容过滤 |
过滤危险内容 |
| 权限控制 |
控制访问权限 |
10. 总结
10.1 核心要点
| 要点 |
说明 |
| navigate |
导航到指定URL |
| present |
展示HTML或URL |
| 尺寸控制 |
设置宽高和位置 |
| JS配合 |
通过eval执行导航脚本 |
10.2 下一步
- 第53篇:OpenClaw Canvas 执行:JavaScript 注入实战
- 第54篇:OpenClaw Canvas 截图:页面捕获与保存
参考资料