目录
-
- 摘要
- [1. 引言 - JavaScript 执行的价值](#1. 引言 - JavaScript 执行的价值)
-
- [1.1 JS 执行应用场景](#1.1 JS 执行应用场景)
- [1.2 eval 功能概览](#1.2 eval 功能概览)
- [1.3 执行方式](#1.3 执行方式)
- [2. 基本执行操作](#2. 基本执行操作)
-
- [2.1 执行简单脚本](#2.1 执行简单脚本)
- [2.2 获取页面信息](#2.2 获取页面信息)
- [2.3 执行多行脚本](#2.3 执行多行脚本)
- [3. 数据获取](#3. 数据获取)
-
- [3.1 获取元素内容](#3.1 获取元素内容)
- [3.2 获取表单数据](#3.2 获取表单数据)
- [3.3 获取所有输入值](#3.3 获取所有输入值)
- [3.4 获取计算样式](#3.4 获取计算样式)
- [4. DOM 操作](#4. DOM 操作)
-
- [4.1 修改元素内容](#4.1 修改元素内容)
- [4.2 修改元素HTML](#4.2 修改元素HTML)
- [4.3 添加元素](#4.3 添加元素)
- [4.4 删除元素](#4.4 删除元素)
- [4.5 修改属性](#4.5 修改属性)
- [5. 样式控制](#5. 样式控制)
-
- [5.1 修改内联样式](#5.1 修改内联样式)
- [5.2 添加/删除类](#5.2 添加/删除类)
- [5.3 主题切换](#5.3 主题切换)
- [6. 事件处理](#6. 事件处理)
-
- [6.1 触发点击](#6.1 触发点击)
- [6.2 触发表单提交](#6.2 触发表单提交)
- [6.3 触发自定义事件](#6.3 触发自定义事件)
- [7. 实战案例一:动态数据更新](#7. 实战案例一:动态数据更新)
-
- [7.1 场景描述](#7.1 场景描述)
- [7.2 实现代码](#7.2 实现代码)
- [7.3 更新图表](#7.3 更新图表)
- [8. 实战案例二:表单自动填充](#8. 实战案例二:表单自动填充)
-
- [8.1 场景描述](#8.1 场景描述)
- [8.2 实现代码](#8.2 实现代码)
- [9. 实战案例三:内容提取](#9. 实战案例三:内容提取)
-
- [9.1 场景描述](#9.1 场景描述)
- [9.2 实现代码](#9.2 实现代码)
- [10. 高级技巧](#10. 高级技巧)
-
- [10.1 异步操作](#10.1 异步操作)
- [10.2 等待元素](#10.2 等待元素)
- [10.3 批量操作](#10.3 批量操作)
- [11. 安全注意事项](#11. 安全注意事项)
-
- [11.1 安全风险](#11.1 安全风险)
- [11.2 安全实践](#11.2 安全实践)
- [12. 总结](#12. 总结)
-
- [12.1 核心要点](#12.1 核心要点)
- [12.2 最佳实践](#12.2 最佳实践)
- [12.3 下一步](#12.3 下一步)
- 参考资料
摘要
本文深入探讨 OpenClaw Canvas 的 JavaScript 执行功能。从基本执行、数据获取、DOM操作到高级应用,全面解析如何通过 eval 注入 JavaScript 实现灵活的界面控制。通过实际案例演示数据提取、动态更新、事件处理等场景,帮助开发者掌握 Canvas 的高级控制能力。⚡
1. 引言 - JavaScript 执行的价值
1.1 JS 执行应用场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 数据获取 | 获取页面数据 | 读取表单值 |
| DOM操作 | 修改页面元素 | 更新内容 |
| 事件触发 | 触发页面事件 | 点击按钮 |
| 动态更新 | 实时更新内容 | 刷新数据 |
| 样式控制 | 动态修改样式 | 切换主题 |
1.2 eval 功能概览
#mermaid-svg-36LXF38sEDLZeeCI{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-36LXF38sEDLZeeCI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-36LXF38sEDLZeeCI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-36LXF38sEDLZeeCI .error-icon{fill:#552222;}#mermaid-svg-36LXF38sEDLZeeCI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-36LXF38sEDLZeeCI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-36LXF38sEDLZeeCI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-36LXF38sEDLZeeCI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-36LXF38sEDLZeeCI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-36LXF38sEDLZeeCI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-36LXF38sEDLZeeCI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-36LXF38sEDLZeeCI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-36LXF38sEDLZeeCI .marker.cross{stroke:#333333;}#mermaid-svg-36LXF38sEDLZeeCI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-36LXF38sEDLZeeCI p{margin:0;}#mermaid-svg-36LXF38sEDLZeeCI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-36LXF38sEDLZeeCI .cluster-label text{fill:#333;}#mermaid-svg-36LXF38sEDLZeeCI .cluster-label span{color:#333;}#mermaid-svg-36LXF38sEDLZeeCI .cluster-label span p{background-color:transparent;}#mermaid-svg-36LXF38sEDLZeeCI .label text,#mermaid-svg-36LXF38sEDLZeeCI span{fill:#333;color:#333;}#mermaid-svg-36LXF38sEDLZeeCI .node rect,#mermaid-svg-36LXF38sEDLZeeCI .node circle,#mermaid-svg-36LXF38sEDLZeeCI .node ellipse,#mermaid-svg-36LXF38sEDLZeeCI .node polygon,#mermaid-svg-36LXF38sEDLZeeCI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-36LXF38sEDLZeeCI .rough-node .label text,#mermaid-svg-36LXF38sEDLZeeCI .node .label text,#mermaid-svg-36LXF38sEDLZeeCI .image-shape .label,#mermaid-svg-36LXF38sEDLZeeCI .icon-shape .label{text-anchor:middle;}#mermaid-svg-36LXF38sEDLZeeCI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-36LXF38sEDLZeeCI .rough-node .label,#mermaid-svg-36LXF38sEDLZeeCI .node .label,#mermaid-svg-36LXF38sEDLZeeCI .image-shape .label,#mermaid-svg-36LXF38sEDLZeeCI .icon-shape .label{text-align:center;}#mermaid-svg-36LXF38sEDLZeeCI .node.clickable{cursor:pointer;}#mermaid-svg-36LXF38sEDLZeeCI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-36LXF38sEDLZeeCI .arrowheadPath{fill:#333333;}#mermaid-svg-36LXF38sEDLZeeCI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-36LXF38sEDLZeeCI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-36LXF38sEDLZeeCI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-36LXF38sEDLZeeCI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-36LXF38sEDLZeeCI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-36LXF38sEDLZeeCI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-36LXF38sEDLZeeCI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-36LXF38sEDLZeeCI .cluster text{fill:#333;}#mermaid-svg-36LXF38sEDLZeeCI .cluster span{color:#333;}#mermaid-svg-36LXF38sEDLZeeCI 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-36LXF38sEDLZeeCI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-36LXF38sEDLZeeCI rect.text{fill:none;stroke-width:0;}#mermaid-svg-36LXF38sEDLZeeCI .icon-shape,#mermaid-svg-36LXF38sEDLZeeCI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-36LXF38sEDLZeeCI .icon-shape p,#mermaid-svg-36LXF38sEDLZeeCI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-36LXF38sEDLZeeCI .icon-shape .label rect,#mermaid-svg-36LXF38sEDLZeeCI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-36LXF38sEDLZeeCI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-36LXF38sEDLZeeCI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-36LXF38sEDLZeeCI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} eval 执行
执行类型
数据读取
DOM修改
事件触发
样式控制
返回结果
1.3 执行方式
| 方式 | 说明 | 返回值 |
|---|---|---|
| 同步执行 | 立即执行并返回 | 有 |
| 异步执行 | 异步执行 | Promise |
2. 基本执行操作
2.1 执行简单脚本
python
result = canvas(
action="eval",
javaScript="1 + 1"
)
# result = 2
2.2 获取页面信息
python
# 获取标题
title = canvas(
action="eval",
javaScript="document.title"
)
# 获取URL
url = canvas(
action="eval",
javaScript="window.location.href"
)
# 获取HTML
html = canvas(
action="eval",
javaScript="document.documentElement.outerHTML"
)
2.3 执行多行脚本
python
result = canvas(
action="eval",
javaScript="""
const title = document.title;
const url = window.location.href;
JSON.stringify({ title, url });
"""
)
3. 数据获取
3.1 获取元素内容
python
def get_element_text(selector):
"""获取元素文本"""
return canvas(
action="eval",
javaScript=f"""
document.querySelector('{selector}').textContent
"""
)
# 使用示例
text = get_element_text("#main-title")
3.2 获取表单数据
python
def get_form_data(form_selector):
"""获取表单数据"""
return canvas(
action="eval",
javaScript=f"""
const form = document.querySelector('{form_selector}');
const formData = new FormData(form);
const data = {{}};
for (let [key, value] of formData.entries()) {{
data[key] = value;
}}
JSON.stringify(data);
"""
)
3.3 获取所有输入值
python
def get_all_inputs():
"""获取所有输入框的值"""
return canvas(
action="eval",
javaScript="""
const inputs = document.querySelectorAll('input, textarea, select');
const data = {};
inputs.forEach(input => {
const name = input.name || input.id;
if (name) {
data[name] = input.value;
}
});
JSON.stringify(data);
"""
)
3.4 获取计算样式
python
def get_computed_style(selector, property):
"""获取计算样式"""
return canvas(
action="eval",
javaScript=f"""
const element = document.querySelector('{selector}');
window.getComputedStyle(element).getPropertyValue('{property}');
"""
)
4. DOM 操作
4.1 修改元素内容
python
def set_element_text(selector, text):
"""设置元素文本"""
canvas(
action="eval",
javaScript=f"""
document.querySelector('{selector}').textContent = '{text}';
"""
)
4.2 修改元素HTML
python
def set_element_html(selector, html):
"""设置元素HTML"""
canvas(
action="eval",
javaScript=f"""
document.querySelector('{selector}').innerHTML = `{html}`;
"""
)
4.3 添加元素
python
def append_element(parent_selector, html):
"""添加子元素"""
canvas(
action="eval",
javaScript=f"""
const parent = document.querySelector('{parent_selector}');
const div = document.createElement('div');
div.innerHTML = `{html}`;
parent.appendChild(div.firstElementChild);
"""
)
4.4 删除元素
python
def remove_element(selector):
"""删除元素"""
canvas(
action="eval",
javaScript=f"""
const element = document.querySelector('{selector}');
if (element) element.remove();
"""
)
4.5 修改属性
python
def set_attribute(selector, attr, value):
"""设置属性"""
canvas(
action="eval",
javaScript=f"""
document.querySelector('{selector}').setAttribute('{attr}', '{value}');
"""
)
5. 样式控制
5.1 修改内联样式
python
def set_style(selector, property, value):
"""设置样式"""
canvas(
action="eval",
javaScript=f"""
document.querySelector('{selector}').style.{property} = '{value}';
"""
)
# 使用示例
set_style('#header', 'backgroundColor', '#667eea')
set_style('#content', 'display', 'none')
5.2 添加/删除类
python
def add_class(selector, class_name):
"""添加类"""
canvas(
action="eval",
javaScript=f"""
document.querySelector('{selector}').classList.add('{class_name}');
"""
)
def remove_class(selector, class_name):
"""删除类"""
canvas(
action="eval",
javaScript=f"""
document.querySelector('{selector}').classList.remove('{class_name}');
"""
)
def toggle_class(selector, class_name):
"""切换类"""
canvas(
action="eval",
javaScript=f"""
document.querySelector('{selector}').classList.toggle('{class_name}');
"""
)
5.3 主题切换
python
def toggle_theme():
"""切换主题"""
canvas(
action="eval",
javaScript="""
document.body.classList.toggle('dark-theme');
"""
)
6. 事件处理
6.1 触发点击
python
def trigger_click(selector):
"""触发点击事件"""
canvas(
action="eval",
javaScript=f"""
document.querySelector('{selector}').click();
"""
)
6.2 触发表单提交
python
def submit_form(form_selector):
"""提交表单"""
canvas(
action="eval",
javaScript=f"""
document.querySelector('{form_selector}').submit();
"""
)
6.3 触发自定义事件
python
def trigger_custom_event(selector, event_name, detail=None):
"""触发自定义事件"""
detail_json = json.dumps(detail) if detail else '{}'
canvas(
action="eval",
javaScript=f"""
const event = new CustomEvent('{event_name}', {{ detail: {detail_json} }});
document.querySelector('{selector}').dispatchEvent(event);
"""
)
7. 实战案例一:动态数据更新
7.1 场景描述
实时更新 Canvas 中的数据展示。
7.2 实现代码
python
def update_dashboard(data):
"""更新仪表盘数据"""
# 构建更新脚本
updates = []
for key, value in data.items():
updates.append(f"document.getElementById('{key}').textContent = '{value}';")
canvas(
action="eval",
javaScript="\n".join(updates)
)
# 使用示例
update_dashboard({
"total-users": "1,234",
"active-users": "567",
"requests": "12,345",
"success-rate": "99.5%"
})
7.3 更新图表
python
def update_chart(chart_id, new_data):
"""更新图表数据"""
canvas(
action="eval",
javaScript=f"""
const chart = Chart.getChart('{chart_id}');
if (chart) {{
chart.data.datasets[0].data = {json.dumps(new_data)};
chart.update();
}}
"""
)
8. 实战案例二:表单自动填充
8.1 场景描述
自动填充表单数据。
8.2 实现代码
python
def fill_form(data):
"""填充表单"""
fill_scripts = []
for field, value in data.items():
script = f"""
const field = document.querySelector('[name="{field}"], #{field}');
if (field) {{
if (field.type === 'checkbox' || field.type === 'radio') {{
field.checked = {str(value).lower()};
}} else if (field.tagName === 'SELECT') {{
field.value = '{value}';
}} else {{
field.value = '{value}';
}}
}}
"""
fill_scripts.append(script)
canvas(
action="eval",
javaScript="\n".join(fill_scripts)
)
# 使用示例
fill_form({
"username": "admin",
"email": "admin@example.com",
"role": "admin",
"active": True
})
9. 实战案例三:内容提取
9.1 场景描述
从 Canvas 中提取结构化数据。
9.2 实现代码
python
def extract_table_data(table_selector):
"""提取表格数据"""
return canvas(
action="eval",
javaScript=f"""
const table = document.querySelector('{table_selector}');
const rows = table.querySelectorAll('tr');
const data = [];
rows.forEach(row => {{
const cells = row.querySelectorAll('td, th');
const rowData = [];
cells.forEach(cell => rowData.push(cell.textContent.trim()));
data.push(rowData);
}});
JSON.stringify(data);
"""
)
def extract_list_data(list_selector):
"""提取列表数据"""
return canvas(
action="eval",
javaScript=f"""
const items = document.querySelectorAll('{list_selector} > li');
const data = [];
items.forEach(item => {{
data.push(item.textContent.trim());
}});
JSON.stringify(data);
"""
)
10. 高级技巧
10.1 异步操作
python
def async_operation():
"""异步操作"""
return canvas(
action="eval",
javaScript="""
(async () => {
const response = await fetch('/api/data');
const data = await response.json();
return JSON.stringify(data);
})()
"""
)
10.2 等待元素
python
def wait_for_element(selector, timeout=5000):
"""等待元素出现"""
return canvas(
action="eval",
javaScript=f"""
(async () => {{
const start = Date.now();
while (Date.now() - start < {timeout}) {{
const element = document.querySelector('{selector}');
if (element) return true;
await new Promise(r => setTimeout(r, 100));
}}
return false;
}})()
"""
)
10.3 批量操作
python
def batch_dom_operations(operations):
"""批量DOM操作"""
scripts = []
for op in operations:
if op['type'] == 'setText':
scripts.append(f"document.querySelector('{op['selector']}').textContent = '{op['value']}';")
elif op['type'] == 'setHtml':
scripts.append(f"document.querySelector('{op['selector']}').innerHTML = `{op['value']}`;")
elif op['type'] == 'click':
scripts.append(f"document.querySelector('{op['selector']}').click();")
canvas(
action="eval",
javaScript="\n".join(scripts)
)
11. 安全注意事项
11.1 安全风险
| 风险 | 说明 | 防护措施 |
|---|---|---|
| XSS攻击 | 注入恶意脚本 | 转义用户输入 |
| 数据泄露 | 读取敏感数据 | 权限控制 |
| 无限循环 | 死循环脚本 | 超时限制 |
| DOM破坏 | 破坏页面结构 | 操作验证 |
11.2 安全实践
python
def safe_eval(script, user_input):
"""安全的执行"""
# 转义用户输入
escaped = user_input.replace("'", "\\'").replace('"', '\\"')
# 使用参数化脚本
safe_script = script.replace('{input}', escaped)
return canvas(action="eval", javaScript=safe_script)
12. 总结
12.1 核心要点
| 要点 | 说明 |
|---|---|
| 数据获取 | 读取页面内容和数据 |
| DOM操作 | 修改元素和结构 |
| 样式控制 | 动态修改样式 |
| 事件触发 | 触发页面事件 |
12.2 最佳实践
- 转义用户输入
- 使用参数化脚本
- 添加错误处理
- 限制执行时间
12.3 下一步
- 第54篇:OpenClaw Canvas 截图:页面捕获与保存
- 第55篇:OpenClaw Canvas A2UI:交互式界面开发