【无限思维画布】制作思维导图第三步,节点移动与编辑

news2024/11/24 9:04:33

正在为无限词典制作单词思维导图功能,实现无限单词导图,无限思维画布。目前制作到第三步,实现节点移动与编辑:

节点移动与编辑

Details

第一步,搜索 github。

一个是比较完善的,基于普通dom,用canvas做小预览图,值得借鉴。而另一个是vue-mindmap,用的是svg结合foreignobject嵌套普通dom,虽然功能不完整、在移动端没有惯性滑动且卡顿,但css外观上更符合预期,于是基于其h5产物,改造。
请添加图片描述

第二步,简化

简化是为了理清dom结构,理解代码原理。

保留两三个节点即可。连线是svg的q指令,即指令两点连线,然后用另一个点,或数个点控制曲线弯曲。节点旁边还有竖线关联一些细节。

第三步,古法手搓html

h5其实很简单 ——

<!DOCTYPE html>
<html>

<head>
    <meta name="description" content="MDict JS">
    <meta name="robots" content="noindex">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes, maximum-scale=10">

    <style>
        .mindmap-svg {
            height: 100%;
            width: 100%;
        }
        
        .mindmap-svg:focus {
            outline: none;
        }
        
        .mindmap-node>a, .mindmap-node>wordnode {
            background: #f5f5f5;
            border-radius: 10px;
            box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
            color: #212121;
            display: inline-block;
            font-family: 'Raleway';
            font-size: 22px;
            margin: 0 auto;
            padding: 15px;
            text-align: center;
            text-decoration: none;
            transition: background-color .2s, color .2s ease-out;
        }
        
        .mindmap-node>a[href]:hover {
            background-color: #f57c00;
            color: #fff;
            cursor: pointer;
        }
        
        
        .mindmap-node--editable>a {
            pointer-events: none;
        }
        
        .mindmap-subnode-group {
            align-items: center;
            border-left: 4px solid #9e9e9e;
            display: flex;
            margin-left: 15px;
            padding: 5px;
        }
        
        .mindmap-subnode-group a {
            color: #212121;
            font-family: 'Raleway';
            font-size: 16px;
            padding: 2px 5px;
        }
        
        .mindmap-connection {
            fill: transparent;
            stroke: #9e9e9e;
            stroke-dasharray: 10px 4px;
            stroke-width: 3px;
        }
        
        .mindmap-emoji {
            height: 24px;
            vertical-align: bottom;
            width: 24px;
        }
        
        .reddit-emoji {
            border-radius: 50%;
        }
		mark{
			background: orange;
			color: black;
		}
		html,body{
			padding:0;
			margin:0;
		}
		body{
		    overflow: scroll;
		}
		foreignObject { overflow: visible; }
		
		
		.items-container{
			font-size: 12px;
			color: rgb(255, 255, 255);
			display: flex;
			flex-grow: 1;
			user-select:none;
		}
		.statusbar-item{
			color: rgb(255, 255, 255);
			display: inline-block;
			line-height: 22px;
			height: 100%;
			vertical-align: top;
			max-width: 40vw;
			padding-top:5px;
			padding-bottom:5px;
		}
		.statusbar-item>a{
			line-height: 22px;
			text-decoration: none;
			cursor: pointer;
			display: flex;
			height: 100%;
			padding: 0 5px;
			white-space: pre;
			align-items: center;
			text-overflow: ellipsis;
			overflow: hidden;
			outline-width: 0;
			margin-right: 3px;
			margin-left: 3px;
			color: #000;
		}
		.statusbar-item>a>span{
			text-align: center;
			font-size: 14px;
			padding-top:10px;
			padding-bottom:10px;
		}


    </style>

	<!--script src='https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/mark.min.js'> </script-->
</head>

<body style='background:#fff'>
    <svg id="map" class="mindmap-svg" viewBox="0 0 1500 2500">
		<g transform="translate(0,0) scale(1)">
			<!--path class="mindmap-connection" d="M 487 -259 Q 443 241 , 402 540">
			</path>
			<path class="mindmap-connection" d="M 402 540 Q 298 590 , 154 639">
			</path>
			<path class="mindmap-connection" d="M 154 639 Q 135 696 , 183 753">
			</path>
			<path class="mindmap-connection" d="M 955 417 Q 717 362 , 746 138">
			</path-->
			
			
			<foreignObject class="mindmap-node mindmap-node--editable" width="125" height="55" x="426" y="14">
				<wordnode>
					<a id="node-undefined">python<img class="mindmap-emoji" title="wiki" ></a>
					<title></title>
				</wordnode>
			</foreignObject>
			
			<foreignObject class="mindmap-node mindmap-node--editable" width="1" height="1" x="0" y="0">
				<wordnode>
					<div style="whitespace:nowrap;">basics
					asdsa asd
					asdsaddddd
					asdsadads
					</div>
					<video preload="none" src='https://www.runoob.com/try/demo_source/movie.mp4' controls>
				</wordnode>
			</foreignObject>
			
			<!--foreignObject class="mindmap-subnodes" width="113" height="176" x="546" y="3">
				<div class="mindmap-subnode-group" style="border-left-color: rgba(255, 189, 10, 1.0)">
					<a><img class="mindmap-emoji reddit-emoji" title="reddit"></a>
					<div></div>
				</div>
				<div class="mindmap-subnode-group" style="border-left-color: rgba(255, 189, 10, 1.0)">
					<a>source <img class="mindmap-emoji" title="github"></a>
					<div></div>
				</div>
				<div class="mindmap-subnode-group" style="border-left-color: rgba(255, 189, 10, 1.0)">
					<a>source <img class="mindmap-emoji" title="github"></a>
					<div></div>
				</div>
			</foreignObject-->
			
			<path id="location" transform="translate(0,0) scale(1)" d="M0,-7c-3.87,0 -7,3.13 -7,7s3.13,7 7,7 7,-3.13 7,-7 -3.13,-7 -7,-7zM15.65,-1.75c-0.81,-7.3 -6.6,-13.09 -13.9,-13.9L1.75,-19.25h-3.5v3.61C-9.05,-14.84 -14.84,-9.05 -15.65,-1.75L-19.25,-1.75v3.5h3.61c0.81,7.3 6.6,13.09 13.9,13.9L-1.75,19.25h3.5v-3.61c7.3,-0.81 13.09,-6.6 13.9,-13.9L19.25,1.75v-3.5h-3.61zM0,12.25c-6.77,0 -12.25,-5.48 -12.25,-12.25s5.48,-12.25 12.25,-12.25 12.25,5.48 12.25,12.25 -5.48,12.25 -12.25,12.25z">
			</path>
			
			<foreignObject id="editbar" class="mindmap-node mindmap-node--editable" width="1" height="1" x="500" y="500" >
				<wordnode style="transform:scale(1.5);padding-top:0;padding-bottom:0;">
					<div class="items-container" style="width:auto;">
						<div class="statusbar-item" id="move">
							<a tabindex="-1" role="button"  style="cursor:all-scroll">
								<span class="codicon codicon-bookmark">✥ 移动</span>
							</a>
						</div>
						<div class="statusbar-item" >
							<a tabindex="-1" role="button"  style="">
								<span class="codicon codicon-bookmark" id="edit">🖊 编辑</span>
							</a>
						</div>
						<div class="statusbar-item" >
							<a tabindex="-1" role="button"  style="">
								<span class="codicon codicon-bookmark">🗑️ 删除</span>
							</a>
						</div>
					</div>
				</wordnode>
			</foreignObject>
			
		</g>
	</svg>
	
	<script>
		function ge(e,p){return (p||document).getElementById(e)};
		
		window.debug = function(a,b,c,d,e){var t=[a,b,c,d,e];for(var i=5;i>=0;i--){if(t[i]===undefined)t[i]='';else break}console.log("%c wordmap ","color:#333!important;background:#0FF;",t[0],t[1],t[2],t[3],t[4])}

		
		var ua=navigator.userAgent, mobile=(/Android|webOS|iPhone|iPod|BlackBerry/i.test(ua));
		
		var map = ge("map");
		var loca = ge("location");
		var editbar = ge("editbar");
		var move = ge("move");
		var focaNode;
		
		var mapX = 1500, mapY = 2500;
		
		map.style.background="#0000ff22"
		loca.style.display='none';
		editbar.style.display='none';
		
		var ff = mapX / document.documentElement.clientWidth;

		function placeEditbar(node) {
			var obj = node.parentNode;
			var x = parseInt(obj.getAttribute("x"));
			//x+=node.offsetWidth/3;
			x+=55;
			var y = parseInt(obj.getAttribute("y"));
			y+=node.offsetHeight;
			y+=19;
			editbar.setAttribute("x", x);
			editbar.setAttribute("y", y);
		}
		
		map.addEventListener('click', function(e){
			var p=e.path,node=0;
			if(!p && e.composedPath) p=e.composedPath();
			debug(p);
			if(p) for(var i=0;(t=p[i])&&i++<99;)
			{
				if(t.tagName==='WORDNODE') {
					node = t;
					break;
				}
				if(t.tagName==='FOREIGNOBJECT' && t.childNodes[0].tagName==='WORDNODE') {
					node = t.childNodes[0];
					break;
				}
			}
			if(node) {
				loca.style.display='none';
				if(node.parentNode==editbar) return;
				focaNode = node;
				debug(node);
				placeEditbar(node);
				editbar.style.display='';
				return;
			}
			var x = xy(e).clientX, y=e.clientY;
			debug("mousedown", x, y, e);
			
			var xx = (x+document.documentElement.scrollLeft) * ff;
			var yy = (y+document.documentElement.scrollTop) * ff;
			
			loca.style.display='';
			editbar.style.display='none';
			loca.setAttribute("transform", "translate("+parseInt(xx)+","+parseInt(yy)+")");
			
			if(focaNode)
				focaNode.setAttribute("contenteditable", "false")
		});
		
		function xy(e){
			if(e.clientX==undefined)
				e.clientX=e.changedTouches[0].clientX;
			if(e.clientY==undefined)
				e.clientY=e.changedTouches[0].clientY;
			return e;
		};
		
		var moving = 0, orgX, orgY, nodeX, nodeY, stopScroll;
		function dragMove(e){
			var x = xy(e).clientX, y=e.clientY;
			//debug('touchmove', x, y);
			if(moving) {
				moving.setAttribute("x", nodeX+(x-orgX)*ff);
				moving.setAttribute("y", nodeY+(y-orgY)*ff);
				placeEditbar(focaNode);
			}
			if(mobile && !stopScroll) {
				document.documentElement.style.overflow='hidden';
				stopScroll=1;
			}
			e.preventDefault();
			e.stopPropagation();
		}
		function dragAbort(e){
			var x = xy(e).clientX, y=e.clientY;
			//debug('touchend', x, y);
			moving = 0;
			document.removeEventListener(mobile?'touchmove':'mousemove', dragMove, true);
			document.removeEventListener(mobile?'touchend':'mouseup', dragAbort, true);
			if(mobile)
			{
				document.documentElement.style.overflow='scroll';
				stopScroll=0;
			}
		}
		move.addEventListener(mobile?'touchstart':'mousedown', function(e){
			var x = xy(e).clientX, y=e.clientY;
			orgX = x;
			orgY = y;
			moving = focaNode?focaNode.parentNode:0;
			document.addEventListener(mobile?'touchend':'mouseup', dragAbort, true);
			document.addEventListener(mobile?'touchmove':'mousemove', dragMove, true);
			if(moving) {
				nodeX = parseInt(moving.getAttribute("x"));
				nodeY = parseInt(moving.getAttribute("y"));
			}
			//debug('touchstart', x, y, nodeX, nodeY);
		});
		
		ge("edit").onclick = function(){
			debug(focaNode);
			focaNode.setAttribute("contenteditable", "true");
			focaNode.focus();
		}
		
	</script>


</body>

</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/374012.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Random(二)什么是伪共享?@sun.misc.Contended注解

目录1.背景简介2.伪共享问题3.问题解决4.JDK使用示例1.背景简介 我们知道&#xff0c;CPU 是不能直接访问内存的&#xff0c;数据都是从高速缓存中加载到寄存器的&#xff0c;高速缓存又有 L1&#xff0c;L2&#xff0c;L3 等层级。在这里&#xff0c;我们先简化这些复杂的层级…

对象创建的过程

对象创建的过程 在语言层面上&#xff0c;创建对象通常仅仅是一个new关键字而已&#xff08;例外&#xff1a;复制、反序列化&#xff09;&#xff1b; 而在虚拟机中&#xff0c;对象的创建又是怎样一个过程呢&#xff1f;&#xff08;文中讨论的对象限于普通Java对象&#xff…

Jetpack Compose 深入探索系列一:Composable 函数

Composable 函数的含义 如果我们只专注于简单的语法&#xff0c;任何标准的Kotlin函数都可以成为一个可组合函数&#xff0c;只需将其注解为Composable: 通过这样做&#xff0c;我们实际上是在告诉编译器&#xff0c;该函数打算将一些数据转换为一个Node节点&#xff0c;以便注…

Simulink建模:如何学习Simulink建模

本文介绍博主自己学习Simulink建模的方法。后续博客都会按照本文中的思路来记录博主学习的过程。 文章目录1 Simulink建模的分类1.1 连续模型建模1.2 物理模型建模1.3 控制算法建模2 控制算法建模的基本知识2.1 控制算法与电控软件架构2.2 控制算法与周期调度2.3 控制算法与其他…

分布式-分布式缓存笔记

分布式系统缓存 缓存分类 前端缓存 前端缓存包括页面和浏览器缓存&#xff0c;如果是 App&#xff0c;那么在 App 端也会有缓存。当你打开商品详情页&#xff0c;除了首次打开以外&#xff0c;后面重复刷新时&#xff0c;页面上加载的信息来自多种缓存。 页面缓存属于客户端…

61 - 进程互斥锁的详细设计

---- 整理自狄泰软件唐佐林老师课程 文章目录1. 问题1.1 生活中的示例1.1.1 吃饭问题1.1.2 十字路口1.1.3 洗手间1.1.4 生产消费者问题1.2 结论2. 接下来的问题2.1 临界资源&#xff08;Critical Resource&#xff09;2.2 临界区&#xff08;Critical Section&#xff09;2.3 任…

【C3】cpu_wtd_sysfs

文章目录2.cpu_wtd_sysfs&#xff1a;switchboard.c &#xff08;fpga下i2c访问Switch CPLD1&#xff0c;Switch CPLD2 &#xff1a;CPLD, FPGA , QSFP&#xff09;scriptbmc_wtd&#xff1a;syscpld.c中wd_en和wd_kick节点对应寄存器&#xff0c;crontab&#xff0c;FUNCNAMEA…

Spring事务的隔离级别

事务的特性: 隔离性:多个事务在并发执行的时候&#xff0c;多个事务执行的一个行为模式&#xff0c;当一个事务执行的时候&#xff0c;另一个事务执行的一个行为模式是什么&#xff1f; 1)A&#xff0c;原子性&#xff0c;一个事务中的所有操作&#xff0c;要么全部执行成功&am…

I2C误码了怎么处理

我相信不少人有遇到I2C设备识别不到&#xff0c;或者概率性误码。 我相信大部分工程师的做法如下&#xff1a; 1.调整上拉电阻的大小&#xff0c;然后重新老化测试&#xff1b; 2.降低I2C速率&#xff0c;然后老化测试&#xff1b; 3.软件加入一定判断条件&#xff0c;将能…

Ncvicat 打开sql文件方法

Nacicat打开sql文件时&#xff0c;有比较多的文章介绍可以直接打开&#xff0c;方法介绍的比较多&#xff0c;但是我遇到了一个坑&#xff0c;就是如何配置环境都无法打开。 本机环境&#xff1a; windows10 mysql 5.7.40 Navicat12.1 一、遇到问题情况 1.1、通过navicat…

Kubernetes向集群外部暴露服务的方式你知道吗?

Kubernetes向进群外暴露服务的方式有三种&#xff1a;Ingress、LoadBlancer类型的Service、NodePort类型的Service。IngressIngress相当于service的service&#xff0c;可以将外部请求通过按照不同规则转发到对应的service。实际上&#xff0c;ingress相当于一个7层的负载均衡器…

面了一个月,终于让我总结出了这份最详细的接口测试面试题

目录 1、你们公司是如何做接口测试的&#xff1f; 2、什么时候开展接⼝测试&#xff1f; 3、接⼝测试和UI测试的工作是否重复&#xff1f; 4、接口测试框架怎么搭建&#xff1f; 5、接⼝之间有依赖时怎么处理&#xff1f; 6、如何判断接⼝测试的结果&#xff08;成功或失败&a…

【C进阶】指针的高级话题

文章目录:star:1. 字符指针:star:2. 指针数组2.1 指针数组的定义2.2 指针数组的使用:star:3. 数组指针3.1 数组的地址3.2 数组指针的使用:star:4. 数组参数和指针参数:star:5. 函数指针5.1 函数名和函数的地址5.2 练习:star:6. 函数指针数组6.1 转移表:star:7. 指向函数指针数组…

昌德科技冲刺上市:计划募资约12亿元,蒋卫和为实控人

近日&#xff0c;深圳市昌德新材科技股份有限公司&#xff08;下称“昌德科技”&#xff09;递交招股书&#xff0c;准备在真真证券交易所主板上市。本次冲刺上市&#xff0c;昌德科技计划募资11.69亿元&#xff0c;中信建投证券为其保荐机构。 据招股书介绍&#xff0c;昌德科…

西电编译原理期末核心考点汇总(期末真题+相关知识点)

文章目录前言一、正规式1.1 相关知识点1.1.1 正规式定义1.1.2 辅助定义1.2 历年真题二、二义文法2.1 相关知识点2.1.1 二义性概念2.2 历年考题三、全部短语、直接短语和句柄3.1 相关知识点3.1.1 短语&#xff0c;直接短语和句柄定义3.1.2 短语&#xff0c;直接短语和句柄例题3.…

【企业管理】研发部视角提出对外支撑业务自助门户构思和实现

导读&#xff1a;公司是由不同部门组成&#xff0c;各个部门之间必然有协同才能使得公司各项职能正常运行。可以说公司的竞争力越强往往会得出公司内部之间工作协同就越高效&#xff0c;可以看出公司各部门之间协同对公司营运是十分重要的。高效协同前提必然是实现便利的信息共…

数据库设计表与表之间的关系详细介绍

文章目录数据库设计数据库设计简介表关系之一对多表关系之多对多表关系之一对一数据库设计 数据库设计简介 软件研发的步骤如下: 设计数据库还是很重要的 数据库设计概念: 数据库设计就是根据业务系统的具体需求&#xff0c;结合我们所选用的DBMS&#xff0c;为这个业务系统构…

Synology搭建Gitea(Docker)

Synology搭建Gitea(Docker) 文章目录Synology搭建Gitea(Docker)参考增加用户与用户组增加映像安装配置反向代理路由器端口转发参考 Nas轻量git方案&#xff1a;Docker安装Gitea;群晖(Synology) NAS 如何安装 gitea 增加用户与用户组 为所有Docer创建一个组docker&#xff1b; 权…

行测-判断推理-图形推理-样式规律-黑白运算

黑白元素个数不同&#xff0c;优先考虑黑白运算白白白黑黑白黑白黑选A考试时&#xff0c;这种题不要先把规律全部推出来&#xff0c;再去做题&#xff0c;太慢了直接看要推的图&#xff0c;通过排除法选答案黑白元素个数不同&#xff0c;优先考虑黑白运算白白白黑黑白黑白黑选B…

【5G RRC】5G系统消息SIB3介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…