excalidraw 要是有图层功能就好了

要是有可以像ps那样的图层功能就好了,不同的图层可以有不同的功能不显得杂乱。

  • 右键会有:
下移一层
上移一层
置于底层
置于顶层
  • 感觉勉强算是图层功能吧哈哈哈
  • 还可以通过编组来实现类似图层的功能,或许还不错。

有,图层脚本,创建md文件,复制进去,放到Excalidraw的脚本文件就行了
有问题让ai改代码

created: 2025-10-26T14:14
updated: 2025-10-26T14:39

/*


// Excalidraw 图层管理器脚本

// 检查 Excalidraw 插件版本是否满足要求
if (!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.0")) {
 new Notice("此脚本需要较新版本的 Excalidraw 插件。请更新您的插件。");
 return;
}

// 防止重复打开窗口
if (window.layerManagerModal) {
 window.layerManagerModal.open();
 return;
}

const modal = new ea.FloatingModal(ea.plugin.app);
window.layerManagerModal = modal;

// 用于存储图层顺序的隐藏元素标记
const STATE_ELEMENT_ID = "excalidraw-layer-manager-state";

// ---------------------------------
// 状态管理函数
// ---------------------------------

/**
* 从一个隐藏的元素中获取已保存的图层顺序
* @returns {string[]}
*/
const getLayerOrderState = () => {
   const stateEl = ea.getViewElements().find(el => el.customData?.isLayerManagerState);
   return stateEl?.customData?.layerOrder || [];
};

/**
* 将图层顺序保存到一个隐藏的元素中
* @param {string[]} order 
*/
const saveLayerOrderState = async (order) => {
   ea.clear(); // 操作前清空工作台
   let stateEl = ea.getViewElements().find(el => el.customData?.isLayerManagerState);
   
   if (!stateEl) {
       // 如果状态元素不存在,则创建一个新的
       const stateElId = ea.addRect(-10000, -10000, 1, 1); // 在画布外创建,使其不可见
       const newEl = ea.getElement(stateElId);
       newEl.opacity = 0;
       newEl.locked = true;
       ea.addAppendUpdateCustomData(stateElId, { isLayerManagerState: true, layerOrder: order });
   } else {
       // 如果已存在,则更新它
       ea.copyViewElementsToEAforEditing([stateEl]);
       ea.addAppendUpdateCustomData(stateEl.id, { layerOrder: order });
   }
   
   await ea.addElementsToView();
};

/**
* 根据图层顺序重新排列画布上所有图层元素的 Z-index
* @param {string[]} order 
*/
const reorderCanvasElements = async (order) => {
   const allCanvasElements = ea.getViewElements();
   const layeredElementsMap = new Map();
   const unlayeredElements = [];

   allCanvasElements.forEach(el => {
       const layerId = el.customData?.layerId;
       if (layerId) {
           if (!layeredElementsMap.has(layerId)) {
               layeredElementsMap.set(layerId, []);
           }
           layeredElementsMap.get(layerId).push(el);
       } else {
           unlayeredElements.push(el);
       }
   });

   let finalOrderedElements = [...unlayeredElements];
   
   // 从后往前遍历顺序数组,将图层元素添加到最终数组中
   // 数组中靠后的元素会在 Excalidraw 中渲染在更上层
   for (const layerId of [...order].reverse()) {
       const elementsInLayer = layeredElementsMap.get(layerId);
       if (elementsInLayer) {
           finalOrderedElements.push(...elementsInLayer);
       }
   }
   
   await ea.getExcalidrawAPI().updateScene({ elements: finalOrderedElements });
   // 短暂延迟以确保场景更新
   await new Promise(resolve => setTimeout(resolve, 50));
};


// ---------------------------------
// 主构建函数:创建或刷新 UI
// ---------------------------------
const buildUI = async () => {
 const { contentEl } = modal;
 contentEl.empty();
 
 contentEl.createEl("h2", { text: "图层管理器" });

 const layerListEl = contentEl.createEl("div", {
   attr: {
     style: "max-height: 500px; overflow-y: auto; border: 1px solid var(--background-modifier-border); padding: 5px; border-radius: 5px;"
   }
 });
 
 const layers = findLayersOnCanvas();
 let layerOrder = getLayerOrderState();

 const presentLayerIds = new Set(layers.keys());
 const originalOrder = JSON.stringify(layerOrder);
 
 layerOrder = layerOrder.filter(id => presentLayerIds.has(id));
 
 const orderedLayerIds = new Set(layerOrder);
 const newLayers = [...presentLayerIds].filter(id => !orderedLayerIds.has(id)).sort();
 layerOrder.push(...newLayers);

 if (JSON.stringify(layerOrder) !== originalOrder) {
     await saveLayerOrderState(layerOrder);
 }

 if (layerOrder.length === 0) {
   layerListEl.createEl("p", { text: "未发现图层。请选择一些元素并点击“添加到图层”。" });
 } else {
   const sortedLayers = layerOrder.map(layerId => [layerId, layers.get(layerId)]);

   for (const [index, [layerId, elements]] of sortedLayers.entries()) {
     const setting = new ea.obsidian.Setting(layerListEl)
       .setName(layerId);
     
     const img = setting.controlEl.createEl("img");
     img.style.width = "100px";
     img.style.height = "60px";
     img.style.border = "1px solid var(--background-modifier-border)";
     img.style.marginRight = "10px";
     img.style.objectFit = "contain";
     setting.controlEl.prepend(img);
     
     generateLayerPreview(elements).then(dataUrl => {
       img.src = dataUrl;
     });
     
     const isLocked = elements.every(el => el.locked);
     const isHidden = elements.every(el => el.opacity === 0);

     setting.addButton(btn => {
       btn.setIcon("arrow-up")
          .setTooltip("上移")
          .setDisabled(index === 0)
          .onClick(() => moveLayer(index, 'up'));
     });
     setting.addButton(btn => {
       btn.setIcon("arrow-down")
          .setTooltip("下移")
          .setDisabled(index === sortedLayers.length - 1)
          .onClick(() => moveLayer(index, 'down'));
     });

     setting.addButton(btn => {
       btn.setIcon(isLocked ? "lock" : "unlock")
          .setTooltip(isLocked ? "解锁图层" : "锁定图层")
          .onClick(() => toggleLock(layerId, !isLocked));
     });

     setting.addButton(btn => {
       btn.setIcon(isHidden ? "eye-off" : "eye")
          .setTooltip(isHidden ? "显示图层" : "隐藏图层")
          .onClick(() => toggleVisibility(layerId, !isHidden));
     });
     
     setting.addButton(btn => {
       btn.setIcon("trash")
          .setTooltip("移除图层(将元素释放到场景中)")
          .onClick(() => removeLayer(layerId));
     });
   }
 }
 
 const bottomControls = new ea.obsidian.Setting(contentEl)
   .addButton(btn => btn.setButtonText("解锁全部").onClick(unlockAllLayers))
   .addButton(btn => btn.setButtonText("添加到图层").onClick(addToLayer))
   .addButton(btn => btn.setButtonText("刷新").onClick(buildUI).setCta());
};

// ---------------------------------
// 核心功能函数
// ---------------------------------

const findLayersOnCanvas = () => {
 const layers = new Map();
 ea.getViewElements().forEach(el => {
   const layerId = el.customData?.layerId;
   if (layerId) {
     if (!layers.has(layerId)) layers.set(layerId, []);
     layers.get(layerId).push(el);
   }
 });
 return layers;
};

const generateLayerPreview = async (elements) => {
 const appState = ea.getExcalidrawAPI().getAppState();
 const svg = await ea.createViewSVG({
   elementsOverride: elements,
   withBackground: true,
   theme: appState.theme,
   padding: 10
 });
 const svgString = new XMLSerializer().serializeToString(svg);
 return `data:image/svg+xml;base64,${btoa(svgString)}`;
};

const moveLayer = async (index, direction) => {
 let order = getLayerOrderState();
 if (direction === 'up' && index > 0) {
   [order[index], order[index - 1]] = [order[index - 1], order[index]];
 }
 if (direction === 'down' && index < order.length - 1) {
   [order[index], order[index + 1]] = [order[index + 1], order[index]];
 }
 await saveLayerOrderState(order);
 await reorderCanvasElements(order);
 buildUI();
};

const toggleLock = async (layerId, lock) => {
 ea.clear();
 const elementsToToggle = findLayersOnCanvas().get(layerId);
 if (!elementsToToggle) return;
 ea.copyViewElementsToEAforEditing(elementsToToggle);
 ea.getElements().forEach(el => { el.locked = lock; });
 await ea.addElementsToView();
 buildUI();
};

const toggleVisibility = async (layerId, hide) => {
 ea.clear();
 const elementsToToggle = findLayersOnCanvas().get(layerId);
 if (!elementsToToggle) return;
 ea.copyViewElementsToEAforEditing(elementsToToggle);
 ea.getElements().forEach(el => {
   if (hide) {
     ea.addAppendUpdateCustomData(el.id, { originalOpacity: el.opacity });
     el.opacity = 0;
   } else {
     el.opacity = el.customData?.originalOpacity ?? 100;
     ea.addAppendUpdateCustomData(el.id, { originalOpacity: undefined });
   }
 });
 await ea.addElementsToView();
 buildUI();
};

const removeLayer = async (layerId) => {
 ea.clear();
 const elementsToRelease = findLayersOnCanvas().get(layerId);
 if (!elementsToRelease) return;
 ea.copyViewElementsToEAforEditing(elementsToRelease);
 ea.getElements().forEach(el => {
   el.locked = false;
   el.opacity = el.customData?.originalOpacity ?? el.opacity;
   if(el.opacity === 0) el.opacity = 100;
   ea.addAppendUpdateCustomData(el.id, { layerId: undefined, originalOpacity: undefined });
 });
 await ea.addElementsToView();
 
 let order = getLayerOrderState().filter(id => id !== layerId);
 await saveLayerOrderState(order);
 await reorderCanvasElements(order);
 buildUI();
};

const unlockAllLayers = async () => {
 ea.clear();
 const allElements = Array.from(findLayersOnCanvas().values()).flat();
 if (allElements.length === 0) return;
 ea.copyViewElementsToEAforEditing(allElements);
 ea.getElements().forEach(el => {
   el.locked = false;
   el.opacity = el.customData?.originalOpacity ?? el.opacity;
   if(el.opacity === 0) el.opacity = 100;
   ea.addAppendUpdateCustomData(el.id, { originalOpacity: undefined });
 });
 await ea.addElementsToView();
 buildUI();
};

const addToLayer = async () => {
 const selectedElements = ea.getViewSelectedElements();
 if (selectedElements.length === 0) {
   new Notice("请先在画布上选择要添加的元素。");
   return;
 }
 
 const layerId = await utils.inputPrompt("输入图层名称:", "例如:背景、前景、注释");
 if (!layerId) return;
 
 ea.clear();
 ea.copyViewElementsToEAforEditing(selectedElements);
 ea.getElements().forEach(el => {
   ea.addAppendUpdateCustomData(el.id, { layerId: layerId });
 });
 await ea.addElementsToView();
 
 let order = getLayerOrderState();
 if (!order.includes(layerId)) {
   order.unshift(layerId); // 新图层添加到顶部
   await saveLayerOrderState(order);
 }
 await reorderCanvasElements(order);
 buildUI();
};

// ---------------------------------
// 模态窗口事件处理
// ---------------------------------
modal.onOpen = buildUI;
modal.onClose = () => { delete window.layerManagerModal; };

modal.open();
1 个赞