继发布 嵌入式系列插件第四弹:Excalidraw 插件 之后,经过几个版本迭代,「嵌入式系列」Excalidraw 插件功能逐步完整,我也收到了很多朋友的特殊功能需求。其实其中不少需求都是来源于原先的 Obsidian Excalidraw 用户。而 Obsidian Excalidraw 能够这么出名的一个原因是它提供了 Excalidraw 脚本功能,并且后续大量用户基于此创建了许多好用的脚本,比如我之前看到的先绘制一个粗略的思维导图然后一键格式化为非常规整的思维导图的视频,效果着实震惊。于是,我也为咱们思源笔记的 Excalidraw 插件加入了代码片段功能,将思源笔记本身的代码片段功能利用上,现在 Excalidraw 内部也可以用自定义代码片段了。
现在,SiYuan Excalidraw 也有了自己的 Excalidraw 脚本!!!!

代码片段使用效果
这一视频中展示了 CSS 和 JS 的用法,第一个 CSS 让右下角的帮助按钮不再显示,第二个 JS 增加了一个右键菜单项,能够将框选的元素自动排为一行。
[Excalidraw代码片段] 不显示右下角帮助按钮
.excalidraw .help-icon {
display: none;
}
[Excalidraw代码片段] 将选中元素排列为一行
arrangeSelectedElementsInRow = (spacing = 10) => {
const selectedIds = Object.keys(window.excalidrawAPI.getAppState().selectedElementIds);
if (!selectedIds || selectedIds.length === 0) {
console.log("⚠️ 请先选择至少一个元素");
return;
}
// 获取选中的元素
const elements = window.excalidrawAPI.getSceneElements();
const selectedElements = elements.filter(el => selectedIds.includes(el.id));
if (selectedElements.length === 0) {
console.log("⚠️ 未找到选中的元素");
return;
}
// 按原始 x 坐标排序(从左到右)
const sorted = [...selectedElements].sort((a, b) => a.x - b.x);
// 计算新位置:从最左侧开始排列
let currentX = sorted[0].x; // 以第一个元素的 x 为起点
const topY = Math.min(...sorted.map(el => el.y)); // 顶部对齐(取最小 y)
const updates = sorted.map(el => {
const newX = currentX;
const newY = topY;
currentX += el.width + spacing; // 紧密排列 + 间距
return {
id: el.id,
x: newX,
y: newY
};
});
const updatedElements = elements.map(element => {
if (selectedIds.includes(element.id)) {
const updateData = updates.find(el => el.id === element.id);
element.x = updateData.x;
element.y = updateData.y;
}
return element;
})
// 批量更新元素位置
window.excalidrawAPI.updateScene({
elements: updatedElements
});
console.log(`✅ 已将 ${selectedElements.length} 个元素水平排列`);
};
const mutationObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
const menuElement = node.querySelector(".popover .context-menu");
if (menuElement) {
const itemElement = document.createElement("li");
itemElement.setAttribute("data-testid", "arrangeSelectedElementsInRow");
itemElement.innerHTML = `
<button type="button" class="context-menu-item">
<div class="context-menu-item__label">将选中的元素排为一行</div>
<kbd class="context-menu-item__shortcut"></kbd>
</button>`;
itemElement.addEventListener("click", () => {
arrangeSelectedElementsInRow(10);
})
menuElement.querySelector(`[data-testid="wrapSelectionInFrame"]`)?.insertAdjacentElement('afterend', itemElement);
}
}
});
}
}
});
mutationObserver.observe(document.body, {
childList: true,
subtree: true
});
代码片段功能的引入为 Excalidraw 带来了更多玩法的可能,理论上 Obsidian Excalidraw 里看到的各种用 Excalidraw 脚本实现的华丽的功能都可以迁移到思源中来了。
为了方便用户获取和分享好用的代码片段,本贴也将作为 Excalidraw 代码片段集市,汇集大家开发的实用功能,欢迎各位大佬实现功能后在思源中发帖分享,或在本贴评论区中回复,我将把大家的功能以链接的形式收录在本贴中。
开发指引
参考本帖子提供的 CSS 和 JS 代码片段,目前提供的唯一的 API 是 window.excalidrawAPI,有了它就可以操作整个 Excalidraw,对应的接口见 Excalidraw 官方文档。
Excalidraw 代码片段集市
- 不显示右下角帮助按钮 by yuxinzhao
- 将选中元素排列为一行 by yuxinzhao
- 选中元素网格布局 by yuxinzhao
- 选中元素 Tab 切换形状 by yuxinzhao
如有更多需求/建议欢迎在 GitHub 仓库中提 issue 或在本贴中回贴
如果你觉得有用,欢迎请我喝杯咖啡☕
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于