웹페이지에 전문적인 마인드맵 기능을 쉽게 통합하기 - Mind Elixir
/ 20 分钟阅读
정보 폭발의 시대에 우리는 매일 대량의 복잡한 정보와 아이디어를 처리해야 합니다. 전통적인 선형 노트는 사고의 도약성과 연관성을 표현하기 어려운 반면, 마인드맵은 반자동 노트 도구로서 우리가 사고를 더 잘 정리하고, 지식 구조를 체계화하며, 창의적 영감을 자극하는 데 도움을 주면서도 화이트보드나 그림 소프트웨어보다 더 편리한 조작을 제공합니다.
지식 관리 플랫폼, 온라인 교육 웹사이트, 협업 도구 구축 등 어떤 경우든 마인드맵 기능을 통합하면 사용자 경험을 크게 향상시키고, 복잡한 정보의 표시와 상호작용을 더욱 직관적이고 효율적으로 만들 수 있습니다.
만약 자신의 웹 프로젝트에 마인드맵 기능을 통합하고 싶다면, Mind Elixir를 활용하여 단 몇 줄의 코드만으로 웹사이트에 전문급 마인드맵 기능을 추가할 수 있습니다. 예를 들어 개인 블로그에서 마인드맵을 사용해 글의 지식 구조를 보여주거나 인터랙티브한 학습 노트를 만들 수 있으며, 이러한 기능은 콘텐츠를 더욱 생동감 있고 흥미롭게 만들 뿐만 아니라 블로그의 전문성을 크게 향상시킬 수 있습니다.
오픈소스 JavaScript 마인드맵 코어 Mind Elixir는 다음과 같은 핵심 특징을 가지고 있습니다:
- 부드러운 사용자 경험 매끄러운 상호작용과 자연스러운 피드백, 모바일 지원.
- 경량이면서 고성능 작은 용량으로 빠른 로딩과 렌더링을 제공하며, 복잡한 그래픽에서도 높은 프레임률을 유지.
- 프레임워크 독립적 React, Vue, Svelte는 물론 네이티브 프로젝트에서도 쉽게 통합하거나 독립적으로 실행 가능.
- 플러그인 아키텍처 유연한 플러그인 시스템으로 공식 확장이나 커스텀 플러그인을 지원하며, 기능 모듈을 자유롭게 조합 가능.
- PNG / HTML 내보내기 지원 마인드맵을 이미지나 HTML 페이지로 내보내어 공유와 임베딩이 편리.
- 노드 요약 / 노드 연결 노드 요약, 연결선, 태그 등 다양한 노드 스타일을 지원하여 다른 요구사항 충족.
- 실행취소 / 다시실행 지원 완전한 작업 히스토리 스택으로 모든 수정사항을 빠르게 되돌리거나 다시 실행 가능.
- 효율적인 단축키 지원 풍부한 키보드 단축키로 전문 사용자의 작업 효율성 향상.
- CSS 변수 스타일 커스터마이징 CSS 변수를 통해 노드 스타일과 전체 테마를 쉽게 제어하여 고도로 커스터마이징된 아름다운 레이아웃 구현.
이제 프로젝트에 Mind Elixir를 빠르게 통합하는 방법을 간단히 소개해보겠습니다! 🤗
초간단 연동
의존성 설치:
npm i mind-elixir -SMind Elixir 가져오기:
import MindElixir from "mind-elixir";import "mind-elixir/style"; // 5.0 이후 버전에서는 스타일을 직접 가져와야 함물론 script로 직접 가져올 수도 있습니다:
<script type="module" src="https://cdn.jsdelivr.net/npm/mind-elixir/dist/MindElixir.js"></script>초기화하기 전에 마운트할 대상 요소의 스타일을 조정해야 합니다. 정확히 말하면 명확한 너비와 높이를 지정해야 하며, 특히 높이에 주의해야 합니다. CSS에 시달린 프론트엔드 개발자라면 누구나 알겠지만, 높이 100%는 다루기 까다로운 설정입니다. 아래에서는 높이 500px인 div를 예로 들겠습니다.
<div id="map"></div><style> #map { height: 500px; width: 100%; }</style>이제 정식 초기화를 진행합니다. 다른 초기화 옵션을 고려하지 않는다면 마운트 요소만 전달하면 됩니다!
import MindElixir from "mind-elixir";import "mind-elixir/style";
let options = { el: "#map", // or HTMLDivElement};
let mei = new MindElixir(options);const data = MindElixir.new("new topic");mei.init(data);여기까지 하면 가장 기본적인 연동이 완료됩니다!
TIP
Codepen에서 체험해보기: https://codepen.io/ssshooter/pen/vEOqWjE
이벤트 리스닝
프로젝트에서 마인드맵을 사용한 후 가장 일반적인 요구사항은 이벤트 리스닝입니다. 다양한 노드 작업을 모니터링하고 사용자 작업에 응답해야 합니다. 예를 들어 사용자가 새 노드를 생성했을 때 즉시 저장해야 합니다.
Mind Elixir는 bus를 사용하여 이벤트를 디스패치합니다. 이 용어는 “버스”라는 개념에서 유래되었으며, 모든 이벤트는 이 “버스”에 의해 디스패치됩니다. 사용법은 브라우저의 addEventListener와 유사하며, bus에 접근할 수 있다면 어디서든 이벤트를 리스닝할 수 있습니다.
최신 버전 Mind Elixir 5.0까지 다음과 같은 이벤트가 있습니다:
type EventMap = { operation: (info: Operation) => void; selectNode: (nodeObj: NodeObj, e?: MouseEvent) => void; selectNewNode: (nodeObj: NodeObj) => void; selectNodes: (nodeObj: NodeObj[]) => void; unselectNodes: (nodeObj: NodeObj[]) => void; expandNode: (nodeObj: NodeObj) => void; linkDiv: () => void; scale: (scale: number) => void; move: (data: { dx: number; dy: number }) => void; updateArrowDelta: (arrow: Arrow) => void; showContextMenu: (e: MouseEvent) => void;};이 중 대부분의 노드 작업 이벤트는 operation 이벤트로 분류됩니다. 말보다는 실행이죠! 프로젝트에 추가한 후 노드를 조작해보면 쉽게 이해할 수 있습니다:
mei.bus.addListener("operation", (operation) => { console.log(operation);});때로는 이벤트 리스닝만으로는 충분하지 않습니다. 데이터베이스에 데이터를 삽입한 후에 화면에 표시되도록 하려면 작업 인터셉트를 사용해야 합니다. options에 before 옵션을 추가하는데, 이는 객체이며 key는 인터셉트하려는 작업입니다. addChild 작업을 인터셉트하려면 다음과 같이 작성할 수 있습니다:
let mei = new MindElixir({ // ... before: { async addChild(el, obj) { try { await saveToDatabase(obj); return true; } catch (error) { console.error("Error adding child:", error); return false; } }, },});이렇게 하면 saveToDatabase() 실행이 성공한 후에만 실제로 자식 노드가 추가되고, 실행이 실패하면 삽입 작업이 취소됩니다.
TIP
Codepen에서 체험해보기: https://codepen.io/ssshooter/pen/EajBbrM
마인드맵 다시 그리기
Mind Elixir 코어의 기본 동작을 사용하여 마인드맵을 업데이트하는 것 외에도, 노드 데이터를 직접 업데이트하여 마인드맵을 다시 그릴 수 있습니다.
완전한 노드 데이터 구조는 다음과 같습니다:
export interface NodeObj { topic: string; id: Uid; style?: { fontSize?: string; color?: string; background?: string; fontWeight?: string; }; children?: NodeObj[]; tags?: string[]; icons?: string[]; hyperLink?: string; expanded?: boolean; direction?: Left | Right; image?: { url: string; width: number; height: number; fit?: "fill" | "contain" | "cover"; }; branchColor?: string; dangerouslySetInnerHTML?: string; note?: string;}노드 데이터를 수정하여 노드에 이미지, 태그, 하이퍼링크 등의 요소를 삽입할 수 있습니다. 예를 들어, Mind Elixir 인스턴스의 nodeData를 읽어 현재 마인드맵 데이터를 가져온 다음 수정하고, 마지막으로 refresh 메서드를 호출하여 마인드맵을 다시 그릴 수 있습니다.
const data = mind.nodeData;console.log(data);data.topic = data.topic + "new Data";mind.refresh();하지만 전체 마인드맵을 완전히 새로운 데이터로 업데이트하고 싶다면? 그것도 가능합니다! Mind Elixir 형식에 맞는 데이터를 refresh 메서드에 전달하면 즉시 전체 그래프를 업데이트할 수 있습니다.
import data from "https://esm.sh/mind-elixir/dist/example.js";mind.refresh(data);TIP
Codepen에서 체험해보기: https://codepen.io/ssshooter/pen/vEOqpOX
재디자인의 자유
Mind Elixir는 마인드맵을 재디자인할 수 있는 자유를 제공합니다.
먼저 theme과 cssVar를 통해 전체 마인드맵의 스타일을 간단히 조정할 수 있습니다. 코어에는 밝은 테마와 어두운 테마인 MindElixir.DARK_THEME과 MindElixir.THEME이 내장되어 있으며, 커스텀 테마가 필요하다면 테마 형식에 따라 객체를 작성하여 Mind Elixir에 전달할 수 있습니다.
완전한 Mind Elixir 테마와 사용 방법은 다음과 같습니다:
const PROFESSIONAL_THEME = { name: "Professional", type: "light", palette: ["#2c2c2c", "#404040", "#555555", "#6a6a6a", "#7f7f7f", "#949494", "#a9a9a9"], cssVar: { "--node-gap-x": "32px", "--node-gap-y": "12px", "--main-gap-x": "68px", "--main-gap-y": "48px", "--root-radius": "8px", "--main-radius": "6px", "--root-color": "#ffffff", "--root-bgcolor": "#1a1a1a", "--root-border-color": "#333333", "--main-color": "#2c2c2c", "--main-bgcolor": "#ffffff", "--topic-padding": "4px", "--color": "#4a4a4a", "--bgcolor": "#fafafa", "--selected": "#666666", "--panel-color": "#2c2c2c", "--panel-bgcolor": "#ffffff", "--panel-border-color": "#e0e0e0", },};
let mind = new MindElixir({ el: "#map", theme: PROFESSIONAL_THEME,});주의할 점은 data 자체도 theme을 포함할 수 있으며, 이는 options의 theme을 덮어씁니다. 이는 각 마인드맵이 독립적인 테마를 가질 수 있도록 보장하기 위함입니다. 고정된 테마가 필요하다면 초기화 시 data의 theme을 undefined로 설정하는 것을 잊지 마세요.
P.S. 초기화 후 테마를 다시 수정하고 싶다면 changeTheme 메서드를 사용할 수 있습니다.
cssVar에서 자주 사용되는 main-gap은 주 노드 간의 간격을 조정할 수 있습니다:
node-gap은 노드 내부의 간격을 조정할 수 있습니다:
TIP
Codepen에서 체험해보기: https://codepen.io/ssshooter/pen/azOgVKX
CSS 변수로 조정할 수 없는 다른 매개변수들도 CSS 오버라이드를 통해 직접 미세 조정할 수 있습니다.
더 깊이 들어가면 generateMainBranch와 generateSubBranch를 통해 연결선의 스타일을 조정할 수 있습니다. (예제는 아래 codepen 링크 참조)
적절한 generateMainBranch와 generateSubBranch를 작성한 후, 확장/축소 버튼의 위치가 맞지 않는다면 CSS를 통해 미세 조정할 수 있습니다. 기본 스타일은 다음과 같습니다:
// 주 노드(루트 노드의 다음 레벨 노드)의 확장/축소 버튼 스타일me-main > me-wrapper > me-parent > me-epd { top: 50%; transform: translateY(-50%);}// 기타 자식 노드의 확장/축소 버튼 스타일me-epd { top: 100%; transform: translateY(-50%);}// 왼쪽 확장/축소 버튼 전용 조정.lhs { & > me-wrapper > me-parent > me-epd { left: -10px; } me-epd { left: 5px; }}// 오른쪽 확장/축소 버튼 전용 조정.rhs { & > me-wrapper > me-parent > me-epd { right: -10px; } me-epd { right: 5px; }}TIP
Codepen에서 체험해보기: https://codepen.io/ssshooter/pen/WNmZMmq
노드 스타일
전체적인 스타일 외에도 노드에 대한 커스터마이징 요구사항이 있다면, 노드 자체에 style을 설정할 수 있습니다:
//...{ fontSize?: string color?: string background?: string fontWeight?: string}// ...노드에 대해 매우 강한 커스터마이징 요구사항이 있어서 이 정도의 style 설정으로는 전혀 부족하다고 느낀다면, 그것도 문제없습니다. 여전히 만족시킬 수 있습니다!
dangerouslySetInnerHTML을 통해 더욱 다양하게 활용할 수 있습니다. 예를 들어:
const data = { nodeData: { id: "me-root", topic: "Mind Elixir", tags: ["Mind Map Core"], children: [ { topic: "Customized Div", id: "c00a2264f4532615", children: [ { topic: "", id: "c00a2264f4532614", dangerouslySetInnerHTML: '<div><style>.title{font-size:50px}</style><div class="title">Title</div><div style="color: red; font-size: 20px;">Hello world</div></div>', }, ], }, ], },};TIP
Codepen에서 체험해보기: https://codepen.io/ssshooter/pen/MYwMrjZ
이미지 내보내기
modern-screenshot 덕분에 svg를 교묘하게 활용하여 div를 그대로 이미지로 변환할 수 있습니다. 기본적으로 너무 극단적인 dangerouslySetInnerHTML(예: 비디오)을 사용하지 않는다면 정상적으로 이미지를 내보낼 수 있습니다. @ssshooter/modern-screenshot은 추가로 padding 옵션을 제공하여 스크린샷의 여백을 조정할 수 있습니다.
import { domToPng } from "@ssshooter/modern-screenshot";
const download = async () => { const dataUrl = await domToPng(mind.nodes, { onCloneNode: (node) => { const n = node as HTMLDivElement; n.style.position = ""; n.style.top = ""; n.style.left = ""; n.style.bottom = ""; n.style.right = ""; }, padding: 300, quality: 1, }); const link = document.createElement("a"); link.download = "screenshot.png"; link.href = dataUrl; link.click();};modern-screenshot을 직접 사용하거나 최근에 출시된 snapdom을 사용해도 됩니다. 스크린샷이 불완전한 경우(주로 요약과 연결선이 불완전한 경우)에는 cssVar의 --map-padding을 조정할 수 있습니다.
import { snapdom } from "@zumer/snapdom";
const dl2 = async () => { const result = await snapdom(mind.nodes); await result.download({ format: "jpg", filename: "my-capture" });};
여전히 이미지가 일부만 캡처되는 경우, scale이 1이 아닌 것이 원인일 수 있습니다. 스크린샷 전에 scale을 1로 설정하고 완료 후 원래 크기로 복원해 볼 수 있습니다.
TIP
Codepen에서 체험해보기: https://codepen.io/ssshooter/pen/NPqZXXB
서버 사이드 렌더링 프레임워크에서 사용
Next.js 등의 서버 사이드 렌더링 프레임워크에서 Mind Elixir를 사용할 때 window is not defined 같은 문제가 자주 발생합니다. 이는 Mind Elixir가 다양한 DOM 조작에 크게 의존하기 때문이며, SSR 환경에서는 정상적으로 작동할 수 없습니다.
이 문제를 해결하기 위해 useEffect를 사용하여 클라이언트 사이드 렌더링 시 Mind Elixir를 로드할 수 있습니다. 다음은 간단한 예제입니다:
"use client";import { useEffect } from "react";import { mindMapExample } from "./mapExample";
export const MindMap = ({ className }: { className: string }) => { useEffect(() => { import("mind-elixir").then((MindElixir) => { const theme = MindElixir.default.DARK_THEME; theme.cssVar["--bgcolor"] = "rgba(0,0,0,0)"; const mei = new MindElixir.default({ el: "#map", direction: 2, theme, }); mei.init({ nodeData: mindMapExample, }); mei.toCenter(); window.addEventListener("resize", () => { mei.toCenter(); }); }); }, []); return ( <div id="wrapper" className={className}> <div id="map" className="pointer-events-none h-[50vh] w-screen" onScroll={(e) => e.preventDefault()} ></div> </div> );};기타 Mind Elixir 옵션
Mind Elixir에는 설정할 수 있는 다른 많은 옵션들이 있습니다:
interface Options { // ... direction?: number; // 노드 배치 방향, 0 왼쪽 1 오른쪽 2 양쪽 locale?: Locale; // 언어 선택 contextMenu?: boolean | ContextMenuOption; // 우클릭 메뉴 활성화 여부, 옵션 추가 가능 toolBar?: boolean; // 내장 툴바 활성화 여부 keypress?: boolean | KeypressOptions; // 단축키 활성화 여부, 커스텀 단축키 추가 가능 mouseSelectionButton?: 0 | 2; // 드래그 버튼, 기본값은 우클릭 드래그 before?: Before; // 위에서 언급한 작업 인터셉트 newTopicName?: string; // 새 노드의 기본값 allowUndo?: boolean; // 실행취소/다시실행 활성화 여부 overflowHidden?: boolean; // 화면 이동 가능 여부, 마인드맵 카드 표시 시 사용 가능 alignment?: Alignment; // nodes로 설정 시 화면 중심이 마인드맵 중심, root로 설정 시 화면 중심이 루트 노드 중심, 기본값 root scaleSensitivity?: number; // 스크롤휠과 메뉴의 확대/축소 민감도 draggable?: boolean; // 노드 드래그 가능 여부 editable?: boolean; // 편집 가능 여부 // ...}마무리
이 글의 소개를 통해 Mind Elixir의 기본 사용법부터 고급 커스터마이징까지 다양한 기법을 익히셨을 것입니다. 사용 과정에서 문제가 발생하거나 더 좋은 아이디어와 제안이 있으시면 댓글로 공유해 주시고, GitHub에서 토론에 참여하거나 PR을 제출해 주세요! 함께 Mind Elixir를 더욱 완벽하게 만들어 갑시다!