mcpField
J
json-canvas

json-canvas

github.com/julianobarbosa生产力15102 次安装4 天前更新
代码仓库

关于 json-canvas

json-canvas 是一项面向 Claude Code 的技能,用于创建和编辑有效的 JSON Canvas `.canvas` 文件。它说明节点、边、布局行为及验证注意事项,适合编写画布文件的用户。

主要功能

  1. 01创建和编辑 `.canvas` 文件
  2. 02使用文本、文件、链接和分组节点
  3. 03用带标签的边连接节点
  4. 04设置颜色和背景样式
  5. 05参考布局与验证指南

适用场景

  1. 01从头制作 Obsidian 画布
  2. 02编辑节点位置和图层
  3. 03构建流程图或研究画布
  4. 04保存前检查边和字段

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/julianobarbosa/claude-code-skills --skill json-canvas

安装后在 Claude Code 中输入/json-canvas确认已加载。

详细介绍

JSON Canvas 技能

此技能使 Claude Code 能够创建和编辑 Obsidian 及其他应用中使用的有效 JSON Canvas 文件(.canvas)。

概述

JSON Canvas 是一种用于无限画布数据的开放文件格式。Canvas 文件使用 .canvas 扩展名,并包含符合 JSON Canvas Spec 1.0 的有效 JSON。

文件结构

一个 canvas 文件包含两个顶层数组:

{
  "nodes": [],
  "edges": []
}
  • nodes(可选):节点对象数组
  • edges(可选):连接节点的边对象数组

节点

节点是放置在画布上的对象。共有四种节点类型:

  • text - 带有 Markdown 的文本内容
  • file - 对文件/附件的引用
  • link - 外部 URL
  • group - 其他节点的可视化容器
Z-Index 顺序

节点在数组中按 z-index 排序:

  • 第一个节点 = 底层(显示在其他节点下方)
  • 最后一个节点 = 顶层(显示在其他节点上方)
通用节点属性

所有节点共享以下属性:

属性必需类型描述
id是string节点的唯一标识符
type是string节点类型:text、file、link 或 group
x是integerX 位置(像素)
y是integerY 位置(像素)
width是integer宽度(像素)
height是integer高度(像素)
color否canvasColor节点颜色(见颜色部分)
文本节点

文本节点包含 Markdown 内容。

{
  "id": "6f0ad84f44ce9c17",
  "type": "text",
  "x": 0,
  "y": 0,
  "width": 400,
  "height": 200,
  "text": "# Hello World\n\nThis is **Markdown** content."
}
属性必需类型描述
text是string带有 Markdown 语法的纯文本
文件节点

文件节点引用文件或附件(图片、视频、PDF、笔记等)。

{
  "id": "a1b2c3d4e5f67890",
  "type": "file",
  "x": 500,
  "y": 0,
  "width": 400,
  "height": 300,
  "file": "Attachments/diagram.png"
}
{
  "id": "b2c3d4e5f6789012",
  "type": "file",
  "x": 500,
  "y": 400,
  "width": 400,
  "height": 300,
  "file": "Notes/Project Overview.md",
  "subpath": "#Implementation"
}
属性必需类型描述
file是string系统中文件的路径
subpath否string指向标题或块的链接(以 # 开头)
链接节点

链接节点显示外部 URL。

{
  "id": "c3d4e5f678901234",
  "type": "link",
  "x": 1000,
  "y": 0,
  "width": 400,
  "height": 200,
  "url": "https://obsidian.md"
}
属性必需类型描述
url是string外部 URL
分组节点

分组节点是用于组织其他节点的可视化容器。

{
  "id": "d4e5f6789012345a",
  "type": "group",
  "x": -50,
  "y": -50,
  "width": 1000,
  "height": 600,
  "label": "Project Overview",
  "color": "4"
}
{
  "id": "e5f67890123456ab",
  "type": "group",
  "x": 0,
  "y": 700,
  "width": 800,
  "height": 500,
  "label": "Resources",
  "background": "Attachments/background.png",
  "backgroundStyle": "cover"
}
属性必需类型描述
label否string分组的文本标签
background否string背景图片的路径
backgroundStyle否string背景渲染样式
背景样式
值描述
cover填满节点的整个宽度和高度
ratio保持背景图片的宽高比
repeat在两个方向上以图案方式重复图片

边

边是连接节点的线。

{
  "id": "f67890123456789a",
  "fromNode": "6f0ad84f44ce9c17",
  "toNode": "a1b2c3d4e5f67890"
}
{
  "id": "0123456789abcdef",
  "fromNode": "6f0ad84f44ce9c17",
  "fromSide": "right",
  "fromEnd": "none",
  "toNode": "b2c3d4e5f6789012",
  "toSide": "left",
  "toEnd": "arrow",
  "color": "1",
  "label": "leads to"
}
属性必需类型默认值描述
id是string-边的唯一标识符
fromNode是string-连接起点的节点 ID
fromSide否string-边起始的一侧
fromEnd否stringnone边起点处的形状
toNode是string-连接终点的节点 ID
toSide否string-边结束的一侧
toEnd否stringarrow边终点处的形状
color否canvasColor-线条颜色
label否string-边的文本标签
侧值
值描述
top节点的上边缘
right节点的右边缘
bottom节点的下边缘
left节点的左边缘
端点形状
值描述
none无端点形状
arrow箭头端点

颜色

canvasColor 类型可以通过两种方式指定:

十六进制颜色
{
  "color": "#FF0000"
}
预设颜色
{
  "color": "1"
}
预设颜色
"1"红色
"2"橙色
"3"黄色
"4"绿色
"5"青色
"6"紫色

注意:预设的具体颜色值未作定义,因此应用可以使用自己的品牌颜色。

完整示例

带文本和连接的简单画布
{
  "nodes": [
    {
      "id": "8a9b0c1d2e3f4a5b",
      "type": "text",
      "x": 0,
      "y": 0,
      "width": 300,
      "height": 150,
      "text": "# Main Idea\n\nThis is the central concept."
    },
    {
      "id": "1a2b3c4d5e6f7a8b",
      "type": "text",
      "x": 400,
      "y": -100,
      "width": 250,
      "height": 100,
      "text": "## Supporting Point A\n\nDetails here."
    },
    {
      "id": "2b3c4d5e6f7a8b9c",
      "type": "text",
      "x": 400,
      "y": 100,
      "width": 250,
      "height": 100,
      "text": "## Supporting Point B\n\nMore details."
    }
  ],
  "edges": [
    {
      "id": "3c4d5e6f7a8b9c0d",
      "fromNode": "8a9b0c1d2e3f4a5b",
      "fromSide": "right",
      "toNode": "1a2b3c4d5e6f7a8b",
      "toSide": "left"
    },
    {
      "id": "4d5e6f7a8b9c0d1e",
      "fromNode": "8a9b0c1d2e3f4a5b",
      "fromSide": "right",
      "toNode": "2b3c4d5e6f7a8b9c",
      "toSide": "left"
    }
  ]
}
带分组的项目看板
{
  "nodes": [
    {
      "id": "5e6f7a8b9c0d1e2f",
      "type": "group",
      "x": 0,
      "y": 0,
      "width": 300,
      "height": 500,
      "label": "To Do",
      "color": "1"
    },
    {
      "id": "6f7a8b9c0d1e2f3a",
      "type": "group",
      "x": 350,
      "y": 0,
      "width": 300,
      "height": 500,
      "label": "In Progress",
      "color": "3"
    },
    {
      "id": "7a8b9c0d1e2f3a4b",
      "type": "group",
      "x": 700,
      "y": 0,
      "width": 300,
      "height": 500,
      "label": "Done",
      "color": "4"
    },
    {
      "id": "8b9c0d1e2f3a4b5c",
      "type": "text",
      "x": 20,
      "y": 50,
      "width": 260,
      "height": 80,
      "text": "## Task 1\n\nImplement feature X"
    },
    {
      "id": "9c0d1e2f3a4b5c6d",
      "type": "text",
      "x": 370,
      "y": 50,
      "width": 260,
      "height": 80,
      "text": "## Task 2\n\nReview PR #123",
      "color": "2"
    },
    {
      "id": "0d1e2f3a4b5c6d7e",
      "type": "text",
      "x": 720,
      "y": 50,
      "width": 260,
      "height": 80,
      "text": "## Task 3\n\n~~Setup CI/CD~~"
    }
  ],
  "edges": []
}
带文件和链接的研究画布
{
  "nodes": [
    {
      "id": "1e2f3a4b5c6d7e8f",
      "type": "text",
      "x": 300,
      "y": 200,
      "width": 400,
      "height": 200,
      "text": "# Research Topic\n\n## Key Questions\n\n- How does X affect Y?\n- What are the implications?",
      "color": "5"
    },
    {
      "id": "2f3a4b5c6d7e8f9a",
      "type": "file",
      "x": 0,
      "y": 0,
      "width": 250,
      "height": 150,
      "file": "Literature/Paper A.pdf"
    },
    {
      "id": "3a4b5c6d7e8f9a0b",
      "type": "file",
      "x": 0,
      "y": 200,
      "width": 250,
      "height": 150,
      "file": "Notes/Meeting Notes.md",
      "subpath": "#Key Insights"
    },
    {
      "id": "4b5c6d7e8f9a0b1c",
      "type": "link",
      "x": 0,
      "y": 400,
      "width": 250,
      "height": 100,
      "url": "https://example.com/research"
    },
    {
      "id": "5c6d7e8f9a0b1c2d",
      "type": "file",
      "x": 750,
      "y": 150,
      "width": 300,
      "height": 250,
      "file": "Attachments/diagram.png"
    }
  ],
  "edges": [
    {
      "id": "6d7e8f9a0b1c2d3e",
      "fromNode": "2f3a4b5c6d7e8f9a",
      "fromSide": "right",
      "toNode": "1e2f3a4b5c6d7e8f",
      "toSide": "left",
      "label": "supports"
    },
    {
      "id": "7e8f9a0b1c2d3e4f",
      "fromNode": "3a4b5c6d7e8f9a0b",
      "fromSide": "right",
      "toNode": "1e2f3a4b5c6d7e8f",
      "toSide": "left",
      "label": "informs"
    },
    {
      "id": "8f9a0b1c2d3e4f5a",
      "fromNode": "4b5c6d7e8f9a0b1c",
      "fromSide": "right",
      "toNode": "1e2f3a4b5c6d7e8f",
      "toSide": "left",
      "toEnd": "arrow",
      "color": "6"
    },
    {
      "id": "9a0b1c2d3e4f5a6b",
      "fromNode": "1e2f3a4b5c6d7e8f",
      "fromSide": "right",
      "toNode": "5c6d7e8f9a0b1c2d",
      "toSide": "left",
      "label": "visualized by"
    }
  ]
}
流程图
{
  "nodes": [
    {
      "id": "a0b1c2d3e4f5a6b7",
      "type": "text",
      "x": 200,
      "y": 0,
      "width": 150,
      "height": 60,
      "text": "**Start**",
      "color": "4"
    },
    {
      "id": "b1c2d3e4f5a6b7c8",
      "type": "text",
      "x": 200,
      "y": 100,
      "width": 150,
      "height": 60,
      "text": "Step 1:\nGather data"
    },
    {
      "id": "c2d3e4f5a6b7c8d9",
      "type": "text",
      "x": 200,
      "y": 200,
      "width": 150,
      "height": 80,
      "text": "**Decision**\n\nIs data valid?",
      "color": "3"
    },
    {
      "id": "d3e4f5a6b7c8d9e0",
      "type": "text",
      "x": 400,
      "y": 200,
      "width": 150,
      "height": 60,
      "text": "Process data"
    },
    {
      "id": "e4f5a6b7c8d9e0f1",
      "type": "text",
      "x": 0,
      "y": 200,
      "width": 150,
      "height": 60,
      "text": "Request new data",
      "color": "1"
    },
    {
      "id": "f5a6b7c8d9e0f1a2",
      "type": "text",
      "x": 400,
      "y": 320,
      "width": 150,
      "height": 60,
      "text": "**End**",
      "color": "4"
    }
  ],
  "edges": [
    {
      "id": "a6b7c8d9e0f1a2b3",
      "fromNode": "a0b1c2d3e4f5a6b7",
      "fromSide": "bottom",
      "toNode": "b1c2d3e4f5a6b7c8",
      "toSide": "top"
    },
    {
      "id": "b7c8d9e0f1a2b3c4",
      "fromNode": "b1c2d3e4f5a6b7c8",
      "fromSide": "bottom",
      "toNode": "c2d3e4f5a6b7c8d9",
      "toSide": "top"
    },
    {
      "id": "c8d9e0f1a2b3c4d5",
      "fromNode": "c2d3e4f5a6b7c8d9",
      "fromSide": "right",
      "toNode": "d3e4f5a6b7c8d9e0",
      "toSide": "left",
      "label": "Yes",
      "color": "4"
    },
    {
      "id": "d9e0f1a2b3c4d5e6",
      "fromNode": "c2d3e4f5a6b7c8d9",
      "fromSide": "left",
      "toNode": "e4f5a6b7c8d9e0f1",
      "toSide": "right",
      "label": "No",
      "color": "1"
    },
    {
      "id": "e0f1a2b3c4d5e6f7",
      "fromNode": "e4f5a6b7c8d9e0f1",
      "fromSide": "top",
      "fromEnd": "none",
      "toNode": "b1c2d3e4f5a6b7c8",
      "toSide": "left",
      "toEnd": "arrow"
    },
    {
      "id": "f1a2b3c4d5e6f7a8",
      "fromNode": "d3e4f5a6b7c8d9e0",
      "fromSide": "bottom",
      "toNode": "f5a6b7c8d9e0f1a2",
      "toSide": "top"
    }
  ]
}

ID 生成

节点和边的 ID 必须是唯一字符串。Obsidian 会生成 16 个字符的十六进制 ID:

"id": "6f0ad84f44ce9c17"
"id": "a3b2c1d0e9f8g7h6"
"id": "1234567890abcdef"

此格式为 16 个字符的小写十六进制字符串(64 位随机值)。

布局指南

定位
  • 坐标可以为负数(画布会无限延伸)
  • x 向右递增
  • y 向下递增
  • 位置指节点的左上角
建议尺寸
节点类型建议宽度建议高度
小型文本200-30080-150
中型文本300-450150-300
大型文本400-600300-500
文件预览300-500200-400
链接预览250-400100-200
分组不定不定
间距
  • 在分组内留出 20-50px 的内边距
  • 节点之间留出 50-100px 的间距,以提高可读性
  • 将节点对齐到网格(10 或 20 的倍数),使布局更整洁

验证规则

  1. 所有 id 值在节点和边中必须唯一
  2. fromNode 和 toNode 必须引用现有节点 ID
  3. 每种节点类型都必须包含必需字段
  4. type 必须是以下值之一:text、file、link、group
  5. backgroundStyle 必须是以下值之一:cover、ratio、repeat
  6. fromSide、toSide 必须是以下值之一:top、right、bottom、left
  7. fromEnd、toEnd 必须是以下值之一:none、arrow
  8. 颜色预设必须是 "1" 到 "6",或有效的十六进制颜色

参考资料


注意事项

  • 节点数组的顺序就是 z-index: 读取 .canvas 的编辑器会将 array[0] 渲染在底层,将 array[-1] 渲染在顶层。重新排列 JSON 来“整理”内容,会悄无声息地改变节点的遮挡关系——在打开文件前,很难从视觉差异中发现。
  • fromNode/toNode 必须引用现有 ID,但 JSON 不会强制执行外键约束: 删除节点时,如果没有一并清理边,就会留下孤立边。Obsidian 会将它们显示为连接到空处;有些应用会静默丢弃它们,另一些则会报错。每次删除节点后都要验证边。
  • 坐标单位是像素,且不考虑 DPI: 在视网膜屏幕(逻辑 px)上设计的画布,在 1x 显示屏上的渲染效果会有所不同。这里没有 scale 字段——应以已知的参考值预先计算尺寸,并接受不同设备上的渲染偏差。
  • 16 字符十六进制 ID 是惯例,并非规范要求: 任何唯一字符串都可以使用。但 Obsidian 的“复制节点”功能会重新生成 16 字符的十六进制 ID——在同一文件中混用 UUID 和十六进制 ID,会让通过 grep 手动编辑变得很麻烦。
  • subpath 必须带 # 前缀,且在某些应用中区分大小写: subpath: "Heading" 会静默失败;"#Heading" 可以正常工作;"#heading" 是否有效则不一定——这取决于目标文件中的标题是否大小写完全一致。
  • 分组节点不会拥有其子节点——位置坐标才决定关系: 移动分组不会移动其中的节点。节点之所以在视觉上位于分组内,只是因为它们的 x/y 坐标恰好落在分组的边界框内。要批量移动,必须更新每个子节点的坐标。

评论

支持 Markdown