目录
-
- 摘要
- [1. 引言 - Canvas 的应用场景](#1. 引言 - Canvas 的应用场景)
-
- [1.1 为什么需要 Canvas?](#1.1 为什么需要 Canvas?)
- [1.2 Canvas 的典型应用场景](#1.2 Canvas 的典型应用场景)
- [1.3 Canvas 与传统方案对比](#1.3 Canvas 与传统方案对比)
- [2. Canvas 工具详解](#2. Canvas 工具详解)
-
- [2.1 present - 呈现界面](#2.1 present - 呈现界面)
-
- [2.1.1 URL 导航模式](#2.1.1 URL 导航模式)
- [2.1.2 HTML 渲染模式](#2.1.2 HTML 渲染模式)
- [2.2 hide - 隐藏界面](#2.2 hide - 隐藏界面)
- [2.3 navigate - 导航到 URL](#2.3 navigate - 导航到 URL)
- [2.4 eval - 执行 JavaScript](#2.4 eval - 执行 JavaScript)
- [2.5 snapshot - 截取快照](#2.5 snapshot - 截取快照)
- [3. 可视化界面开发 - HTML/CSS/JS 渲染](#3. 可视化界面开发 - HTML/CSS/JS 渲染)
-
- [3.1 渲染原理](#3.1 渲染原理)
- [3.2 样式设计最佳实践](#3.2 样式设计最佳实践)
-
- [3.2.1 使用内联样式](#3.2.1 使用内联样式)
- [3.2.2 响应式布局](#3.2.2 响应式布局)
- [3.3 JavaScript 执行环境](#3.3 JavaScript 执行环境)
- [4. 实战案例](#4. 实战案例)
-
- [4.1 案例一:数据仪表盘](#4.1 案例一:数据仪表盘)
- [4.2 案例二:交互式表单](#4.2 案例二:交互式表单)
- [4.3 案例三:实时监控界面](#4.3 案例三:实时监控界面)
- [5. Canvas 与 Node 节点 - 远程设备界面](#5. Canvas 与 Node 节点 - 远程设备界面)
-
- [5.1 Node 节点概述](#5.1 Node 节点概述)
- [5.2 Canvas 与 Node 的结合](#5.2 Canvas 与 Node 的结合)
- [5.3 远程控制示例](#5.3 远程控制示例)
- [5.4 Node 节点参数说明](#5.4 Node 节点参数说明)
- [6. 最佳实践与性能优化](#6. 最佳实践与性能优化)
-
- [6.1 性能优化建议](#6.1 性能优化建议)
-
- [6.1.1 减少重绘次数](#6.1.1 减少重绘次数)
- [6.1.2 合理使用定时器](#6.1.2 合理使用定时器)
- [6.2 安全注意事项](#6.2 安全注意事项)
- [6.3 调试技巧](#6.3 调试技巧)
- [6.4 响应式设计](#6.4 响应式设计)
- [7. 总结](#7. 总结)
-
- [7.1 核心要点回顾](#7.1 核心要点回顾)
- [7.2 Canvas 的独特价值](#7.2 Canvas 的独特价值)
- [7.3 未来展望](#7.3 未来展望)
- 参考资料
摘要
本文深入解析 OpenClaw 框架中的 Canvas 可视化界面系统,从核心概念到实战应用全面覆盖。Canvas 作为 OpenClaw 的可视化呈现层,提供了 present、hide、navigate、eval、snapshot 五大核心操作,支持 HTML/CSS/JavaScript 的完整渲染能力。通过本文,读者将掌握如何利用 Canvas 构建数据仪表盘、交互式表单、实时监控界面等多种可视化应用,并了解 Canvas 与 Node 节点结合实现远程设备界面控制的进阶技巧。文章结合丰富的代码示例、架构图和实战案例,帮助开发者快速上手并深入理解 Canvas 的设计哲学与最佳实践。🎯
1. 引言 - Canvas 的应用场景
在现代 AI Agent 系统中,可视化界面扮演着越来越重要的角色。用户不仅需要通过文字与 AI 交互,更需要直观的图形界面来展示数据、控制设备、监控系统状态。OpenClaw Canvas 正是为解决这一需求而生的可视化解决方案。🚀
1.1 为什么需要 Canvas?
传统的 AI Agent 系统通常只提供文本交互界面,这在很多场景下存在明显的局限性:
- 数据展示不够直观:复杂的数据结构用文字描述既冗长又难以理解
- 交互方式单一:用户无法通过点击、拖拽等方式与系统交互
- 实时监控困难:难以实时展示系统状态和数据变化
- 远程控制受限:无法为远程设备提供可视化的控制界面
Canvas 的出现填补了这一空白,为 OpenClaw 生态系统带来了完整的可视化能力。它不是一个简单的图片渲染工具,而是一个完整的 Web 界面渲染引擎,支持 HTML、CSS、JavaScript 的完整技术栈。
1.2 Canvas 的典型应用场景
Canvas 在实际项目中有着广泛的应用场景,以下是几个典型案例:
| 应用场景 | 描述 | 核心能力 |
|---|---|---|
| 📊 数据仪表盘 | 展示系统运行状态、业务指标、实时数据 | 图表渲染、数据刷新、动态更新 |
| 📝 交互式表单 | 收集用户输入、配置参数、提交数据 | 表单验证、数据绑定、事件处理 |
| 📡 实时监控 | 监控服务器状态、网络流量、应用性能 | 实时数据流、告警提示、历史趋势 |
| 📱 远程设备控制 | 控制 IoT 设备、智能家居、远程服务器 | 远程指令、状态同步、双向通信 |
| 🎨 内容展示 | 展示文档、图片、视频等多媒体内容 | 富媒体渲染、布局控制、样式定制 |
1.3 Canvas 与传统方案对比
与其他可视化方案相比,Canvas 具有独特的优势:
可视化需求
选择方案
传统 Web 开发
低代码平台
OpenClaw Canvas
优点: 灵活度高
缺点: 开发周期长
优点: 快速搭建
缺点: 定制受限
优点: AI 驱动 + 灵活
缺点: 需学习 API
Canvas 的核心优势在于:AI Agent 可以动态生成和操作界面,这意味着界面可以根据上下文、用户需求实时变化,而不需要预先开发固定的页面。这种"代码即界面"的理念,让 AI Agent 具备了真正的可视化交互能力。
2. Canvas 工具详解
Canvas 提供了五个核心操作(Action),每个操作对应界面生命周期的不同阶段。理解这些操作是掌握 Canvas 的关键。🔑
2.1 present - 呈现界面
present 是 Canvas 的核心操作,用于创建并展示一个可视化界面。它支持两种模式:
- URL 导航模式:加载指定的网页 URL
- HTML 渲染模式:直接渲染 HTML/CSS/JavaScript 代码
有 URL
有 HTML/JS
present 调用
模式判断
URL 导航模式
HTML 渲染模式
加载远程页面
注入执行环境
界面呈现完成
解析 HTML 结构
应用 CSS 样式
执行 JavaScript
2.1.1 URL 导航模式
当提供 url 参数时,Canvas 会导航到指定的网页:
javascript
// 示例:打开 OpenClaw 官方文档
{
"action": "present",
"url": "https://docs.openclaw.ai",
"width": 1920,
"height": 1080
}
URL 导航模式适用于:
- 展示外部网站内容
- 加载预制的 Web 应用
- 嵌入第三方服务界面
2.1.2 HTML 渲染模式
当提供 javaScript 参数时,Canvas 会直接渲染代码生成的内容:
javascript
// 示例:渲染一个简单的数据卡片
{
"action": "present",
"width": 800,
"height": 600,
"javaScript": `
// 创建容器
const container = document.createElement('div');
container.style.cssText = \`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
font-family: 'Segoe UI', sans-serif;
\`;
// 创建标题
const title = document.createElement('h1');
title.textContent = '系统状态监控';
title.style.cssText = \`
color: white;
font-size: 32px;
margin-bottom: 20px;
\`;
// 创建状态卡片
const card = document.createElement('div');
card.style.cssText = \`
background: rgba(255,255,255,0.95);
border-radius: 16px;
padding: 30px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
\`;
// 添加状态信息
const stats = [
{ label: 'CPU 使用率', value: '45%', color: '#10b981' },
{ label: '内存占用', value: '6.2 GB', color: '#3b82f6' },
{ label: '网络流量', value: '1.2 GB/s', color: '#8b5cf6' }
];
stats.forEach(stat => {
const row = document.createElement('div');
row.style.cssText = \`
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid #e5e7eb;
\`;
row.innerHTML = \`
<span style="color: #6b7280; font-size: 16px;">\${stat.label}</span>
<span style="color: \${stat.color}; font-weight: bold; font-size: 18px;">\${stat.value}</span>
\`;
card.appendChild(row);
});
container.appendChild(title);
container.appendChild(card);
document.body.appendChild(container);
`
}
上述代码展示了如何使用 HTML 渲染模式创建一个系统状态监控卡片。代码首先创建了一个渐变背景的容器,然后在其中放置标题和状态卡片。卡片中显示了 CPU、内存、网络三项指标的实时数据,每项数据都有对应的颜色标识。这种纯 JavaScript 的方式让 AI Agent 可以动态生成任意复杂的界面。
2.2 hide - 隐藏界面
hide 操作用于隐藏当前显示的 Canvas 界面。这在需要临时隐藏界面或切换不同界面时非常有用。
用户 Canvas 引擎 AI Agent 用户 Canvas 引擎 AI Agent 用户查看界面A 界面不可见 present(界面A) 显示界面A hide() 隐藏界面A present(界面B) 显示界面B
使用示例:
javascript
// 隐藏当前 Canvas 界面
{
"action": "hide"
}
hide 操作的特点:
- 非销毁性:隐藏后界面状态保留,可以重新显示
- 快速响应:隐藏操作立即生效
- 资源友好:隐藏时释放部分渲染资源
2.3 navigate - 导航到 URL
navigate 操作用于在当前 Canvas 中导航到新的 URL,类似于浏览器的页面跳转。
javascript
// 导航到新的 URL
{
"action": "navigate",
"url": "https://dashboard.example.com"
}
navigate 与 present 的区别:
| 特性 | present | navigate |
|---|---|---|
| 创建新界面 | ✅ 是 | ❌ 否 |
| 保留历史 | ❌ 否 | ✅ 是 |
| 适用场景 | 首次展示 | 页面跳转 |
| 性能开销 | 较高 | 较低 |
2.4 eval - 执行 JavaScript
eval 操作用于在当前 Canvas 环境中执行 JavaScript 代码,实现动态更新界面内容。
javascript
// 动态更新界面数据
{
"action": "eval",
"javaScript": `
// 更新状态数据
const cpuValue = document.querySelector('#cpu-value');
const memValue = document.querySelector('#mem-value');
if (cpuValue && memValue) {
cpuValue.textContent = Math.floor(Math.random() * 100) + '%';
memValue.textContent = (Math.random() * 16).toFixed(1) + ' GB';
// 添加更新动画
[cpuValue, memValue].forEach(el => {
el.style.transition = 'all 0.3s ease';
el.style.transform = 'scale(1.1)';
setTimeout(() => {
el.style.transform = 'scale(1)';
}, 300);
});
}
`
}
上述代码展示了如何使用 eval 操作动态更新界面数据。代码首先获取 CPU 和内存显示元素,然后生成随机数据并更新显示。同时,代码还添加了缩放动画效果,让数据更新更加生动。这种方式非常适合实现实时数据刷新功能。
eval 的典型应用场景:
- 实时数据更新:定时刷新仪表盘数据
- 动态样式修改:根据状态改变界面样式
- 事件响应处理:响应用户交互事件
- 条件渲染控制:根据条件显示/隐藏元素
2.5 snapshot - 截取快照
snapshot 操作用于截取当前 Canvas 的快照,返回图片数据。这在需要保存界面状态或生成报告时非常有用。
png
jpg
snapshot 调用
捕获当前帧
输出格式
PNG 图片
JPG 图片
返回 Base64
可用于保存/分享
使用示例:
javascript
// 截取当前 Canvas 快照
{
"action": "snapshot",
"outputFormat": "png",
"quality": 0.9
}
snapshot 参数说明:
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
outputFormat |
string | 输出格式:png/jpg | png |
quality |
number | 图片质量 (0-1) | 0.9 |
maxWidth |
number | 最大宽度限制 | - |
timeoutMs |
number | 超时时间 | 30000 |
3. 可视化界面开发 - HTML/CSS/JS 渲染
Canvas 的核心能力在于支持完整的 HTML/CSS/JavaScript 渲染。这意味着开发者可以使用熟悉的 Web 技术构建任意复杂的界面。🎨
3.1 渲染原理
Canvas 的渲染引擎基于现代浏览器内核,支持:
- HTML5:完整的 HTML 标签支持
- CSS3:现代 CSS 特性(Flexbox、Grid、动画等)
- ES6+ JavaScript:现代 JavaScript 语法
输出层
渲染引擎
输入层
HTML 结构
CSS 样式
JavaScript 逻辑
HTML 解析器
CSS 解析器
JS 引擎
DOM 树
渲染树
可视化界面
3.2 样式设计最佳实践
在 Canvas 中设计界面时,建议遵循以下原则:
3.2.1 使用内联样式
由于 Canvas 环境的特殊性,推荐使用内联样式而非外部 CSS 文件:
javascript
// ✅ 推荐:内联样式
const card = document.createElement('div');
card.style.cssText = `
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 24px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
`;
// ❌ 不推荐:外部样式(可能无法加载)
// card.className = 'card';
3.2.2 响应式布局
使用 Flexbox 和 Grid 实现响应式布局:
javascript
// 响应式网格布局示例
const grid = document.createElement('div');
grid.style.cssText = `
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
padding: 20px;
`;
// 添加卡片
for (let i = 0; i < 6; i++) {
const card = document.createElement('div');
card.style.cssText = `
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
`;
card.textContent = `卡片 ${i + 1}`;
grid.appendChild(card);
}
3.3 JavaScript 执行环境
Canvas 中的 JavaScript 执行环境有以下特点:
| 特性 | 支持情况 | 说明 |
|---|---|---|
| DOM 操作 | ✅ | 完整支持 |
| fetch API | ✅ | 支持网络请求 |
| setTimeout/setInterval | ✅ | 支持定时器 |
| localStorage | ⚠️ | 有限支持 |
| WebGL | ✅ | 支持 3D 渲染 |
| Web Workers | ❌ | 不支持 |
4. 实战案例
本节通过三个实战案例,展示 Canvas 在实际项目中的应用。💪
4.1 案例一:数据仪表盘
数据仪表盘是 Canvas 最常见的应用场景之一。以下是一个完整的系统监控仪表盘实现:
javascript
// 系统监控仪表盘完整实现
{
"action": "present",
"width": 1920,
"height": 1080,
"javaScript": `
// 全局样式
document.body.style.cssText = \`
margin: 0;
padding: 0;
font-family: 'Segoe UI', -apple-system, sans-serif;
background: #0f172a;
color: #e2e8f0;
\`;
// 创建主容器
const container = document.createElement('div');
container.style.cssText = \`
min-height: 100vh;
padding: 32px;
box-sizing: border-box;
\`;
// 标题区域
const header = document.createElement('header');
header.innerHTML = \`
<h1 style="font-size: 32px; margin: 0 0 8px 0; color: #f8fafc;">
📊 系统监控仪表盘
</h1>
<p style="color: #64748b; margin: 0 0 32px 0;">
实时监控系统运行状态 · 最后更新: <span id="update-time">--:--:--</span>
</p>
\`;
container.appendChild(header);
// 指标卡片区域
const metricsGrid = document.createElement('div');
metricsGrid.style.cssText = \`
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24px;
margin-bottom: 32px;
\`;
// 指标数据
const metrics = [
{ icon: '💻', label: 'CPU 使用率', value: '45%', trend: '+5%', color: '#10b981' },
{ icon: '🧠', label: '内存占用', value: '6.2 GB', trend: '-0.3 GB', color: '#3b82f6' },
{ icon: '💾', label: '磁盘使用', value: '234 GB', trend: '+12 GB', color: '#8b5cf6' },
{ icon: '🌐', label: '网络流量', value: '1.2 GB/s', trend: '+0.3 GB', color: '#f59e0b' }
];
metrics.forEach(m => {
const card = document.createElement('div');
card.style.cssText = \`
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
border-radius: 16px;
padding: 24px;
border: 1px solid #475569;
\`;
card.innerHTML = \`
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
<span style="font-size: 28px;">\${m.icon}</span>
<span style="color: #94a3b8; font-size: 14px;">\${m.label}</span>
</div>
<div style="font-size: 36px; font-weight: bold; color: \${m.color}; margin-bottom: 8px;">
\${m.value}
</div>
<div style="font-size: 14px; color: #64748b;">
趋势: <span style="color: \${m.trend.startsWith('+') ? '#ef4444' : '#10b981'}">\${m.trend}</span>
</div>
\`;
metricsGrid.appendChild(card);
});
container.appendChild(metricsGrid);
// 图表区域
const chartSection = document.createElement('div');
chartSection.style.cssText = \`
display: grid;
grid-template-columns: 2fr 1fr;
gap: 24px;
\`;
// CPU 历史趋势图(简化版)
const cpuChart = document.createElement('div');
cpuChart.style.cssText = \`
background: #1e293b;
border-radius: 16px;
padding: 24px;
border: 1px solid #475569;
\`;
cpuChart.innerHTML = \`
<h3 style="margin: 0 0 20px 0; color: #f8fafc;">📈 CPU 使用率趋势</h3>
<div style="height: 200px; display: flex; align-items: flex-end; gap: 8px;">
\${[65, 72, 58, 80, 45, 62, 78, 55, 68, 72, 60, 45].map(v => \`
<div style="
flex: 1;
background: linear-gradient(to top, #3b82f6, #60a5fa);
border-radius: 4px 4px 0 0;
height: \${v}%;
transition: height 0.3s ease;
"></div>
\`).join('')}
</div>
<div style="display: flex; justify-content: space-between; margin-top: 12px; color: #64748b; font-size: 12px;">
<span>00:00</span>
<span>06:00</span>
<span>12:00</span>
<span>18:00</span>
<span>24:00</span>
</div>
\`;
chartSection.appendChild(cpuChart);
// 系统状态列表
const statusList = document.createElement('div');
statusList.style.cssText = \`
background: #1e293b;
border-radius: 16px;
padding: 24px;
border: 1px solid #475569;
\`;
statusList.innerHTML = \`
<h3 style="margin: 0 0 20px 0; color: #f8fafc;">🔧 服务状态</h3>
<div style="display: flex; flex-direction: column; gap: 12px;">
\${[
{ name: 'API Gateway', status: 'running', uptime: '99.9%' },
{ name: 'Database', status: 'running', uptime: '99.8%' },
{ name: 'Cache Server', status: 'running', uptime: '100%' },
{ name: 'Message Queue', status: 'warning', uptime: '98.5%' }
].map(s => \`
<div style="
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
background: #0f172a;
border-radius: 8px;
">
<span>\${s.name}</span>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="
width: 8px;
height: 8px;
border-radius: 50%;
background: \${s.status === 'running' ? '#10b981' : '#f59e0b'};
"></span>
<span style="color: #64748b; font-size: 14px;">\${s.uptime}</span>
</div>
</div>
\`).join('')}
</div>
\`;
chartSection.appendChild(statusList);
container.appendChild(chartSection);
document.body.appendChild(container);
// 更新时间
function updateTime() {
const now = new Date();
document.getElementById('update-time').textContent =
now.toLocaleTimeString('zh-CN', { hour12: false });
}
updateTime();
setInterval(updateTime, 1000);
`
}
上述代码实现了一个完整的系统监控仪表盘。界面分为三个主要区域:顶部标题区显示仪表盘名称和最后更新时间;中间区域展示四个核心指标卡片(CPU、内存、磁盘、网络),每个卡片包含当前值和趋势数据;底部左侧是 CPU 使用率的历史趋势柱状图,右侧是服务状态列表。代码使用了 CSS Grid 进行响应式布局,并通过 setInterval 实现时间自动更新。
4.2 案例二:交互式表单
交互式表单是收集用户输入的重要工具。以下是一个配置表单的实现:
javascript
// 系统配置表单
{
"action": "present",
"width": 800,
"height": 600,
"javaScript": `
document.body.style.cssText = \`
margin: 0;
padding: 40px;
font-family: 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #1e3a5f 0%, #0f172a 100%);
min-height: 100vh;
box-sizing: border-box;
\`;
const form = document.createElement('form');
form.style.cssText = \`
max-width: 600px;
margin: 0 auto;
background: white;
border-radius: 16px;
padding: 32px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
\`;
form.innerHTML = \`
<h2 style="margin: 0 0 24px 0; color: #1e293b; font-size: 24px;">
⚙️ 系统配置
</h2>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 8px; color: #475569; font-weight: 500;">
服务名称
</label>
<input type="text" value="OpenClaw Gateway" style="
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 16px;
box-sizing: border-box;
transition: border-color 0.2s;
" onfocus="this.style.borderColor='#3b82f6'" onblur="this.style.borderColor='#e2e8f0'">
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 8px; color: #475569; font-weight: 500;">
监听端口
</label>
<input type="number" value="18789" style="
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 16px;
box-sizing: border-box;
">
</div>
<div style="margin-bottom: 20px;">
<label style="display: block; margin-bottom: 8px; color: #475569; font-weight: 500;">
日志级别
</label>
<select style="
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 16px;
box-sizing: border-box;
background: white;
">
<option value="debug">Debug - 调试模式</option>
<option value="info" selected>Info - 信息模式</option>
<option value="warn">Warn - 警告模式</option>
<option value="error">Error - 错误模式</option>
</select>
</div>
<div style="margin-bottom: 24px;">
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
<input type="checkbox" checked style="width: 20px; height: 20px; cursor: pointer;">
<span style="color: #475569;">启用 HTTPS</span>
</label>
</div>
<div style="display: flex; gap: 12px;">
<button type="button" onclick="alert('配置已保存!')" style="
flex: 1;
padding: 14px 24px;
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
" onmouseover="this.style.transform='translateY(-2px)';this.style.boxShadow='0 4px 12px rgba(59,130,246,0.4)'" onmouseout="this.style.transform='translateY(0)';this.style.boxShadow='none'">
💾 保存配置
</button>
<button type="button" style="
padding: 14px 24px;
background: #f1f5f9;
color: #475569;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
">
重置
</button>
</div>
\`;
document.body.appendChild(form);
`
}
上述代码实现了一个系统配置表单,包含文本输入、数字输入、下拉选择和复选框四种常见的表单控件。表单采用了居中卡片式布局,背景使用深色渐变增强视觉层次。每个输入框都有 focus 状态的边框颜色变化,保存按钮有 hover 状态的上浮和阴影效果,提供了良好的交互反馈。
4.3 案例三:实时监控界面
实时监控界面需要定期刷新数据,这需要结合 present 和 eval 操作:
javascript
// 第一步:创建初始界面
{
"action": "present",
"width": 1200,
"height": 800,
"javaScript": `
document.body.style.cssText = \`
margin: 0;
padding: 24px;
font-family: 'Segoe UI', sans-serif;
background: #0f172a;
color: #e2e8f0;
\`;
const container = document.createElement('div');
container.innerHTML = \`
<h1 style="margin: 0 0 24px 0;">📡 实时网络监控</h1>
<div id="metrics" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 24px;">
<div class="metric-card" style="background: #1e293b; border-radius: 12px; padding: 20px;">
<div style="color: #64748b; margin-bottom: 8px;">活跃连接</div>
<div id="connections" style="font-size: 36px; font-weight: bold; color: #10b981;">0</div>
</div>
<div class="metric-card" style="background: #1e293b; border-radius: 12px; padding: 20px;">
<div style="color: #64748b; margin-bottom: 8px;">请求/秒</div>
<div id="rps" style="font-size: 36px; font-weight: bold; color: #3b82f6;">0</div>
</div>
<div class="metric-card" style="background: #1e293b; border-radius: 12px; padding: 20px;">
<div style="color: #64748b; margin-bottom: 8px;">平均延迟</div>
<div id="latency" style="font-size: 36px; font-weight: bold; color: #f59e0b;">0 ms</div>
</div>
</div>
<div style="background: #1e293b; border-radius: 12px; padding: 20px;">
<h3 style="margin: 0 0 16px 0;">最近请求日志</h3>
<div id="logs" style="font-family: monospace; font-size: 14px; line-height: 1.8; max-height: 300px; overflow-y: auto;"></div>
</div>
\`;
document.body.appendChild(container);
`
}
// 第二步:定时更新数据(使用 eval)
{
"action": "eval",
"javaScript": `
// 模拟实时数据更新
const connections = Math.floor(Math.random() * 500) + 100;
const rps = Math.floor(Math.random() * 1000) + 200;
const latency = Math.floor(Math.random() * 50) + 10;
document.getElementById('connections').textContent = connections;
document.getElementById('rps').textContent = rps;
document.getElementById('latency').textContent = latency + ' ms';
// 添加日志条目
const logs = document.getElementById('logs');
const methods = ['GET', 'POST', 'PUT', 'DELETE'];
const paths = ['/api/users', '/api/data', '/api/config', '/api/health'];
const statuses = [200, 201, 204, 400, 404, 500];
const method = methods[Math.floor(Math.random() * methods.length)];
const path = paths[Math.floor(Math.random() * paths.length)];
const status = statuses[Math.floor(Math.random() * 3)]; // 90% 成功
const time = new Date().toLocaleTimeString();
const statusColor = status < 400 ? '#10b981' : '#ef4444';
const logEntry = document.createElement('div');
logEntry.innerHTML = \`
<span style="color: #64748b;">[\${time}]</span>
<span style="color: #3b82f6; font-weight: bold;">\${method}</span>
<span style="color: #94a3b8;">\${path}</span>
<span style="color: \${statusColor};">\${status}</span>
\`;
logs.insertBefore(logEntry, logs.firstChild);
// 保持最多 20 条日志
while (logs.children.length > 20) {
logs.removeChild(logs.lastChild);
}
`
}
上述代码展示了如何实现实时监控界面。首先通过 present 创建初始界面,包含三个指标卡片和日志区域。然后通过 eval 操作定时更新数据,模拟实时网络请求监控。每次更新都会生成新的连接数、请求数和延迟数据,并在日志区域添加新的请求记录。日志区域保持最多 20 条记录,新记录插入到顶部,形成滚动效果。
5. Canvas 与 Node 节点 - 远程设备界面
Canvas 的强大之处在于可以与 OpenClaw 的 Node 节点系统结合,实现远程设备的可视化控制。🔗
5.1 Node 节点概述
Node 节点是 OpenClaw 的远程设备管理单元,可以是:
- 📱 手机/平板设备
- 💻 远程服务器
- 🏠 智能家居设备
- 🤖 IoT 设备
5.2 Canvas 与 Node 的结合
通过指定 node 参数,Canvas 可以在特定节点上渲染界面:
远程节点
本地
present + node
WebSocket
用户交互
事件回调
处理响应
AI Agent
Gateway 网关
Node 客户端
Canvas 渲染
设备界面
5.3 远程控制示例
以下示例展示如何在远程节点上显示控制界面:
javascript
// 在指定节点上显示控制面板
{
"action": "present",
"node": "living-room-tablet",
"width": 1024,
"height": 768,
"javaScript": `
document.body.style.cssText = \`
margin: 0;
padding: 40px;
font-family: 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: white;
min-height: 100vh;
box-sizing: border-box;
\`;
const container = document.createElement('div');
container.innerHTML = \`
<h1 style="font-size: 28px; margin: 0 0 32px 0;">🏠 智能家居控制</h1>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px;">
<!-- 灯光控制 -->
<div style="background: rgba(255,255,255,0.1); border-radius: 16px; padding: 24px;">
<h3 style="margin: 0 0 20px 0;">💡 客厅灯光</h3>
<div style="display: flex; align-items: center; gap: 20px;">
<button onclick="this.textContent = this.textContent === '开' ? '关' : '开'" style="
width: 80px; height: 80px;
border-radius: 50%;
border: none;
background: #f59e0b;
color: white;
font-size: 20px;
font-weight: bold;
cursor: pointer;
">开</button>
<div style="flex: 1;">
<div style="margin-bottom: 8px;">亮度</div>
<input type="range" min="0" max="100" value="75" style="width: 100%;">
</div>
</div>
</div>
<!-- 空调控制 -->
<div style="background: rgba(255,255,255,0.1); border-radius: 16px; padding: 24px;">
<h3 style="margin: 0 0 20px 0;">❄️ 空调控制</h3>
<div style="display: flex; align-items: center; gap: 20px;">
<div style="font-size: 48px; font-weight: bold; color: #3b82f6;">24°C</div>
<div style="display: flex; flex-direction: column; gap: 8px;">
<button style="padding: 8px 16px; background: #3b82f6; border: none; border-radius: 8px; color: white; cursor: pointer;">升温</button>
<button style="padding: 8px 16px; background: #3b82f6; border: none; border-radius: 8px; color: white; cursor: pointer;">降温</button>
</div>
</div>
</div>
<!-- 安防状态 -->
<div style="background: rgba(255,255,255,0.1); border-radius: 16px; padding: 24px;">
<h3 style="margin: 0 0 20px 0;">🔒 安防状态</h3>
<div style="display: flex; flex-direction: column; gap: 12px;">
<div style="display: flex; justify-content: space-between;">
<span>门锁状态</span>
<span style="color: #10b981;">已锁定 ✓</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span>窗户传感器</span>
<span style="color: #10b981;">正常 ✓</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span>烟雾报警</span>
<span style="color: #10b981;">正常 ✓</span>
</div>
</div>
</div>
<!-- 场景模式 -->
<div style="background: rgba(255,255,255,0.1); border-radius: 16px; padding: 24px;">
<h3 style="margin: 0 0 20px 0;">🎬 场景模式</h3>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
<button style="padding: 16px; background: #8b5cf6; border: none; border-radius: 8px; color: white; cursor: pointer;">🌙 睡眠</button>
<button style="padding: 16px; background: #f59e0b; border: none; border-radius: 8px; color: white; cursor: pointer;">🌅 起床</button>
<button style="padding: 16px; background: #10b981; border: none; border-radius: 8px; color: white; cursor: pointer;">🏠 离家</button>
<button style="padding: 16px; background: #3b82f6; border: none; border-radius: 8px; color: white; cursor: pointer;">🎬 观影</button>
</div>
</div>
</div>
\`;
document.body.appendChild(container);
`
}
上述代码展示了如何在远程节点(客厅平板)上显示智能家居控制面板。界面包含四个功能区域:灯光控制(开关按钮和亮度滑块)、空调控制(温度显示和调节按钮)、安防状态(各项传感器状态)、场景模式(一键切换预设场景)。这种远程界面控制能力让 AI Agent 可以在任何设备上提供可视化交互体验。
5.4 Node 节点参数说明
| 参数 | 类型 | 说明 | 示例 |
|---|---|---|---|
node |
string | 目标节点 ID 或名称 | "living-room-tablet" |
target |
string | 目标位置 | "node" |
gatewayUrl |
string | Gateway 地址 | "wss://gateway.example.com" |
gatewayToken |
string | 认证令牌 | "your-token" |
6. 最佳实践与性能优化
在使用 Canvas 开发可视化界面时,遵循最佳实践可以提升用户体验和系统性能。⚡
6.1 性能优化建议
6.1.1 减少重绘次数
避免频繁调用 eval 更新整个界面,只更新必要的部分:
javascript
// ❌ 不推荐:更新整个界面
{
"action": "eval",
"javaScript": `
document.body.innerHTML = generateEntireDashboard();
`
}
// ✅ 推荐:只更新变化的部分
{
"action": "eval",
"javaScript": `
document.getElementById('cpu-value').textContent = newCpuValue;
document.getElementById('mem-value').textContent = newMemValue;
`
}
6.1.2 合理使用定时器
避免创建过多的定时器,使用单一定时器管理所有更新:
javascript
// ✅ 推荐:统一管理定时更新
{
"action": "eval",
"javaScript": `
// 清除旧定时器
if (window.updateTimer) clearInterval(window.updateTimer);
// 创建新定时器
window.updateTimer = setInterval(() => {
updateMetrics();
updateCharts();
updateLogs();
}, 1000);
`
}
6.2 安全注意事项
Canvas 执行环境需要注意以下安全事项:
| 安全风险 | 防护措施 |
|---|---|
| XSS 攻击 | 避免直接插入用户输入的 HTML |
| 数据泄露 | 不要在界面中显示敏感信息 |
| 资源滥用 | 限制定时器数量和频率 |
| 网络请求 | 只请求可信来源的数据 |
6.3 调试技巧
Canvas 提供了 snapshot 操作用于调试:
javascript
// 截取当前界面快照用于调试
{
"action": "snapshot",
"outputFormat": "png"
}
6.4 响应式设计
Canvas 支持响应式布局,建议使用以下技术:
响应式设计
Flexbox 弹性布局
Grid 网格布局
媒体查询
相对单位
flex-wrap 自动换行
auto-fit 自适应列数
动态调整样式
rem/vw/vh 相对尺寸
7. 总结
本文全面解析了 OpenClaw Canvas 可视化界面系统的核心功能与实战应用。Canvas 作为 OpenClaw 生态系统中的可视化呈现层,为 AI Agent 提供了强大的界面生成和交互能力。
7.1 核心要点回顾
1. 五大核心操作 🎯
present:创建并展示界面,支持 URL 导航和 HTML 渲染两种模式hide:隐藏当前界面,保留界面状态navigate:在当前界面中导航到新 URLeval:执行 JavaScript 代码,实现动态更新snapshot:截取界面快照,用于保存和调试
2. 完整的 Web 技术支持 💻
- 支持 HTML5、CSS3、ES6+ JavaScript
- 支持现代 CSS 特性(Flexbox、Grid、动画)
- 支持网络请求和定时器
3. 丰富的应用场景 🚀
- 数据仪表盘:实时展示系统状态和业务指标
- 交互式表单:收集用户输入和配置参数
- 实时监控:监控系统状态和网络流量
- 远程设备控制:控制 IoT 设备和智能家居
4. Node 节点集成 🔗
- 支持在远程设备上渲染界面
- 实现跨设备的可视化控制
- 提供双向通信能力
7.2 Canvas 的独特价值
与传统 Web 开发相比,Canvas 的独特价值在于:
- AI 驱动:AI Agent 可以根据上下文动态生成界面
- 即时可用:无需部署,即时呈现
- 跨平台:支持本地和远程设备
- 高度灵活:支持任意复杂的界面设计
7.3 未来展望
随着 OpenClaw 生态的不断发展,Canvas 将支持更多高级特性:
- 🎨 更多图表组件和可视化库
- 🔊 音视频播放能力
- 📱 更好的移动端适配
- 🤖 AI 辅助界面设计
Canvas 让 AI Agent 不再局限于文本交互,而是能够提供丰富的可视化体验。无论是数据展示、用户交互还是远程控制,Canvas 都提供了简洁而强大的解决方案。希望本文能帮助读者快速掌握 Canvas 的使用技巧,在实际项目中发挥其最大价值。🎉