--- name: 设置连线动画 description: 为选中的连线或箭头添加动态效果(流光、穿梭、飞行箭头)。 author: ymjr version: 1.0.0 license: MIT usage: 选中画板中的连线或箭头后运行,在弹出的美观 UI 面板中配置动画。 features: - 纯 HTML/CSS 绘制的现代化 UI,完美融入 Obsidian 主题 - 包含 Flow (流光)、Dash (穿梭)、Arrow (飞行箭头) dependencies: - 必须依赖 [Feature-Animation-Engine] 后台常驻引擎渲染 autorun: false --- /* ```javascript */ var locales = { zh: { notice_select: "请先选中需要添加动画的连线或箭头!", title: "✨ 连线动画", enable: "启用动画", type: "动画类型", type_flow: "🌊 流光 (Flow)", type_dash: "💨 穿梭 (Dash)", type_arrow: "🏹 飞行箭头 (Arrow)", speed: "运动速度", color: "特效颜色", btn_apply: "应用 (Apply)", btn_cancel: "取消 (Cancel)" }, en: { notice_select: "Please select lines or arrows to animate first!", title: "✨ Animation", enable: "Enable Animation", type: "Animation Type", type_flow: "🌊 Flow", type_dash: "💨 Dash", type_arrow: "🏹 Flying Arrow", speed: "Speed", color: "Effect Color", btn_apply: "Apply", btn_cancel: "Cancel" } }; const api = ea.getExcalidrawAPI(); const selectedEls = ea.getViewSelectedElements().filter(el => ['line', 'arrow'].includes(el.type)); if (selectedEls.length === 0) { new Notice(t("notice_select")); return; } // 读取当前状态 const currentAnim = selectedEls[0]?.customData?.animation; const hasAnim = !!currentAnim; // 判断当前是否有动画 const currentType = currentAnim?.type || "flow"; const currentSpeed = currentAnim?.speed || 40; const currentColor = currentAnim?.color || selectedEls[0].strokeColor; const containerId = "ymjr-animation-ui-container"; let oldContainer = document.getElementById(containerId); if (oldContainer) oldContainer.remove(); const uiBackdrop = document.createElement("div"); uiBackdrop.id = containerId; Object.assign(uiBackdrop.style, { position: "absolute", inset: "0", zIndex: "9999", display: "flex", alignItems: "center", justifyContent: "center", backgroundColor: "rgba(0, 0, 0, 0.2)", backdropFilter: "blur(2px)" }); const panel = document.createElement("div"); Object.assign(panel.style, { background: "var(--background-primary)", border: "1px solid var(--background-modifier-border)", borderRadius: "12px", padding: "24px", width: "320px", boxShadow: "0 10px 30px rgba(0, 0, 0, 0.3)", fontFamily: "var(--font-interface)", color: "var(--text-normal)", display: "flex", flexDirection: "column", gap: "16px" }); // 注入 Toggle 开关的 CSS 和 UI 结构 panel.innerHTML = `

${t("title")}

HEX Color
`; uiBackdrop.appendChild(panel); const activeView = app.workspace.getActiveFileView(); if (activeView && activeView.getViewType() === "excalidraw") { activeView.containerEl.appendChild(uiBackdrop); } // 联动逻辑:开关切换时,控制下方设置区域的可用状态 document.getElementById("anim-enable").addEventListener("change", (e) => { const isEnabled = e.target.checked; const settingsGroup = document.getElementById("anim-settings-group"); settingsGroup.style.opacity = isEnabled ? "1" : "0.4"; settingsGroup.style.pointerEvents = isEnabled ? "auto" : "none"; }); document.getElementById("anim-speed").addEventListener("input", (e) => { document.getElementById("anim-speed-val").innerText = e.target.value; }); document.getElementById("anim-cancel").addEventListener("click", () => { uiBackdrop.remove(); }); uiBackdrop.addEventListener("click", (e) => { if (e.target === uiBackdrop) uiBackdrop.remove(); }); document.getElementById("anim-apply").addEventListener("click", async () => { const isEnabled = document.getElementById("anim-enable").checked; const type = document.getElementById("anim-type").value; const speed = parseInt(document.getElementById("anim-speed").value); const color = document.getElementById("anim-color").value; selectedEls.forEach((el) => { if (!isEnabled) { // 如果开关关闭,直接删除动画数据 if (el.customData?.animation) { delete el.customData.animation; } } else { // 如果开关打开,写入/更新动画数据 el.customData = { ...el.customData, animation: { type, speed, color } }; } }); ea.copyViewElementsToEAforEditing(selectedEls); await ea.addElementsToView(false, true, true); if (api && api.updateScene) { ea.viewUpdateScene({ appState: { zoom: { value: api.getAppState().zoom.value + 0.00001 } } }); } uiBackdrop.remove(); });