在日常工作和学习中,思维导图是一种非常有效的可视化工具,可以帮助我们梳理思路、规划任务、整理知识结构。本文将带你一步步了解如何使用 HTML 和 jsmind 实现一个基础的在线思维导图应用。
效果演示
项目概述
本项目主要包含以下核心功能:
- 初始化思维导图
- 添加子节点、删除节点、编辑节点内容
- 保存数据到本地存储,刷新页面后数据不丢失
- 导出思维导图数据为 JSON 文件
- 导入 JSON 数据
- 保存为图片
准备工作
下载 jsmind 文件,项目中引入以下主要文件:jsmind.css、jsmind.js、jsmind.draggable-node.js、jsmind.screenshot.js。
jsmind 下载地址:https://github.com/hizzgdev/jsmind
<link type="text/css" rel="stylesheet" href="./jsmind.css" />
<script src="./jsmind.js"></script>
<script src="./jsmind.draggable-node.js"></script>
<script src="./jsmind.screenshot.js"></script>
页面结构与样式设计
创建 HTML 结构
<!-- 工具栏 -->
<div class="toolbar">
<button onclick="addNode()">添加子节点</button>
<button onclick="removeNode()">删除节点</button>
<button onclick="editNode()">编辑节点</button>
<button onclick="saveDataToLocal()">保存</button>
<button onclick="downloadData()">导出</button>
<button onclick="saveAsImage()">保存为图片</button>
<input type="file" id="importFile" accept=".json" style="display: none;" onchange="importDataFromFile(event)">
<label for="importFile" class="button">导入数据</label>
</div>
<!-- 思维导图容器 -->
<div id="jsmind_container"></div>
设计 CSS 样式
body {
margin: 0;
padding: 0;
}
#jsmind_container {
width: 100vw;
height: 100vh;
overflow: auto;
box-sizing: border-box;
top: 0;
}
.toolbar {
position: fixed;
top: 10px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
display: flex;
gap: 10px;
}
.toolbar button,.toolbar .button {
padding: 8px 16px;
border: none;
border-radius: 4px;
background-color: #007bff;
color: white;
cursor: pointer;
transition: background-color 0.3s ease;
}
.toolbar button:hover {
background-color: #0056b3;
}
核心功能实现
初始化思维导图
使用 jsMind 初始化一个可编辑的思维导图实例,支持从浏览器本地存储(localStorage)中加载上次保存的数据。
// 初始化思维导图
let jm = null;
const initMindMap = () => {
const options = {
container: 'jsmind_container',
editable: true,
theme: 'white',
};
// 初始数据
let data = {
"meta": {
"name": "示例思维导图",
"author": "user",
"version": "1.0"
},
"format": "node_tree",
"data": {
"id": "root",
"topic": "中心主题",
"children": [
]
}
};
const savedData = localStorage.getItem('mindmapData');
if (savedData) {
try {
data = JSON.parse(savedData);
} catch (e) {
console.error("解析本地数据失败", e);
}
}
jm = new jsMind(options);
jm.show(data);
}
window.onload = initMindMap;
添加子节点
function addNode() {
const selectedNode = jm.get_selected_node();
if (selectedNode) {
const nodeId = Date.now().toString();
jm.add_node(selectedNode.id, nodeId, '新增子节点', {});
}
}
删除节点
function removeNode() {
const selectedNode = jm.get_selected_node();
if (selectedNode && selectedNode.id !== 'root') {
jm.remove_node(selectedNode);
}
}
编辑节点
function editNode() {
const selectedNode = jm.get_selected_node();
if (selectedNode) {
const newText = prompt('输入新内容', selectedNode.topic);
if (newText) {
jm.update_node(selectedNode.id, newText);
}
}
}
保存数据到本地存储
function saveDataToLocal() {
const mindData = jm.get_data();
localStorage.setItem('mindmapData', JSON.stringify(mindData));
}
导出思维导图数据
将当前数据保存为 .json 文件并触发下载。
function downloadData() {
saveDataToLocal();
const mindData = jm.get_data();
const dataStr = JSON.stringify(mindData, null, 2);
const blob = new Blob([dataStr], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'mindmap.json';
a.click();
}
导入 JSON 数据
可以通过上传已导出的 JSON 文件恢复之前的思维导图结构。
function importDataFromFile(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const mindData = JSON.parse(e.target.result);
jm.show(mindData);
alert("导入成功!");
} catch (error) {
alert("导入失败,请选择有效的JSON文件。");
console.error("解析文件失败:", error);
}
};
reader.readAsText(file);
}
保存为图片
利用 jsmind.screenshot.js 插件将整个思维导图截图并保存为图片文件。
function saveAsImage() {
saveDataToLocal();
jm.screenshot.shootDownload();
}
扩展建议
- 主题切换:通过修改 jm 的 theme 配置,提供多种预设主题
- 节点图标与样式设置
- 快捷键支持
完整代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>在线思维导图</title>
<meta charset="utf-8">
<!-- 引入 jsMind 样式 -->
<link type="text/css" rel="stylesheet" href="./jsmind.css" />
<style>
body {
margin: 0;
padding: 0;
}
#jsmind_container {
width: 100vw;
height: 100vh;
overflow: auto;
box-sizing: border-box;
top: 0;
}
.toolbar {
position: fixed;
top: 10px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
display: flex;
gap: 10px;
}
.toolbar button,.toolbar .button {
padding: 8px 16px;
border: none;
border-radius: 4px;
background-color: #007bff;
color: white;
cursor: pointer;
transition: background-color 0.3s ease;
}
.toolbar button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<!-- 工具栏 -->
<div class="toolbar">
<button onclick="addNode()">添加子节点</button>
<button onclick="removeNode()">删除节点</button>
<button onclick="editNode()">编辑节点</button>
<button onclick="saveDataToLocal()">保存</button>
<button onclick="downloadData()">导出</button>
<button onclick="saveAsImage()">保存为图片</button>
<input type="file" id="importFile" accept=".json" style="display: none;" onchange="importDataFromFile(event)">
<label for="importFile" class="button">导入数据</label>
</div>
<!-- 思维导图容器 -->
<div id="jsmind_container"></div>
<script src="./jsmind.js"></script>
<script src="./jsmind.draggable-node.js"></script>
<script src="./jsmind.screenshot.js"></script>
<script>
// 初始化思维导图
let jm = null;
const initMindMap = () => {
const options = {
container: 'jsmind_container',
editable: true,
theme: 'white',
};
// 初始数据
let data = {
"meta": {
"name": "示例思维导图",
"author": "user",
"version": "1.0"
},
"format": "node_tree",
"data": {
"id": "root",
"topic": "中心主题",
"children": [
]
}
};
const savedData = localStorage.getItem('mindmapData');
if (savedData) {
try {
data = JSON.parse(savedData);
} catch (e) {
console.error("解析本地数据失败", e);
}
}
jm = new jsMind(options);
jm.show(data);
}
// 添加子节点
function addNode() {
const selectedNode = jm.get_selected_node();
if (selectedNode) {
const nodeId = Date.now().toString();
jm.add_node(selectedNode.id, nodeId, '新增子节点', {});
}
}
// 删除节点
function removeNode() {
const selectedNode = jm.get_selected_node();
if (selectedNode && selectedNode.id !== 'root') {
jm.remove_node(selectedNode);
}
}
// 编辑节点
function editNode() {
const selectedNode = jm.get_selected_node();
if (selectedNode) {
const newText = prompt('输入新内容', selectedNode.topic);
if (newText) {
jm.update_node(selectedNode.id, newText);
}
}
}
// 保存数据到本地存储
function saveDataToLocal() {
const mindData = jm.get_data();
localStorage.setItem('mindmapData', JSON.stringify(mindData));
}
// 导出数据
function downloadData() {
saveDataToLocal();
const mindData = jm.get_data();
const dataStr = JSON.stringify(mindData, null, 2);
const blob = new Blob([dataStr], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'mindmap.json';
a.click();
}
// 导入数据
function importDataFromFile(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const mindData = JSON.parse(e.target.result);
jm.show(mindData);
alert("导入成功!");
} catch (error) {
alert("导入失败,请选择有效的JSON文件。");
console.error("解析文件失败:", error);
}
};
reader.readAsText(file);
}
function saveAsImage() {
saveDataToLocal();
jm.screenshot.shootDownload();
}
// 初始化
window.onload = initMindMap;
</script>
</body>
</html>