拖放目标元素无需设置draggable="true",该属性仅用于源元素;必须为源元素显式添加draggable="true"才能触发dragstart事件,且dragover中必须调用preventdefault()否则drop不会触发。

拖放目标元素必须设置 draggable="true" 且监听 dragstart
HTML 拖放 API 不会自动识别任意元素为可拖拽源——哪怕它有 ondragstart 属性。必须显式添加 draggable="true",否则 dragstart 事件根本不会触发。常见错误是只加了 CSS 样式(如 cursor: move)或只绑定事件,结果拖不动。
编辑器中每个代码块节点(比如代表一个函数、类或模块的 <div class="node">)都应带该属性:
<pre class="brush:php;toolbar:false;"><div class="node" draggable="true" data-id="func-calculate">function calculate() {...}</div></pre>
<ul>
<li>在 <code>dragstart 中用 event.dataTransfer.setData('text/plain', JSON.stringify({id, type, parent})) 存结构元数据,别只传文本内容
dragstart 里做重绘或 DOM 查询,否则拖拽卡顿明显contenteditable 或 input,需阻止默认行为:event.preventDefault(),否则浏览器会尝试选中文本而非启动拖放
dragover 和 drop 必须同时阻止默认行为
没阻止 dragover 的默认行为,drop 事件就永远不会触发——这是最常被忽略的硬性条件。浏览器默认把 drop 当作“打开文件”或“导航跳转”,不阻止就直接走了。
对所有可能接收拖放的目标容器(如代码块之间的间隙、折叠区域、根容器)都要监听:
container.addEventListener('dragover', e => e.preventDefault());
- 仅靠
e.preventDefault()不够,还需设置e.dataTransfer.dropEffect = 'move'明确意图,否则某些浏览器(如 Safari)仍不触发drop -
drop事件里立刻调用e.preventDefault(),再解析e.dataTransfer.getData('text/plain')拿回结构数据 - 不要在
drop里直接修改 DOM 结构树;先更新内存中的 AST 或节点关系映射,再批量重渲染,避免多次 layout
层级调整本质是更新父子引用,不是移动 DOM 节点
编辑器内“拖一个函数进类里”不是把 <div> 插进另一个 <code><div> 就完事。真实运行逻辑依赖 AST 或结构化模型,DOM 只是视图投影。
<p>正确做法是:在 <code>drop 后,根据拖拽源和目标位置计算新父节点 ID 和插入索引,然后更新内存模型:
updateNodeParent(sourceId, targetId, { atIndex: 0 }); // 更新 AST 关系
- 目标可能是“某节点上方/下方”(间隙),也可能是“某节点内部”,需用
event.clientY和target.getBoundingClientRect()判断落点位置,不能只靠event.target - 若目标是折叠状态的节点,需先展开并延迟 100ms 再执行插入,否则视觉反馈错乱
- 更新模型后,用
requestIdleCallback批量触发 diff 渲染,而不是同步操作 DOM
运行前必须重新生成可执行上下文,不能复用旧作用域
拖动改了层级,就等于改了变量作用域链和执行顺序。直接 eval() 或 new Function() 原始代码字符串,会因闭包丢失或 this 绑定错乱而报 ReferenceError。
- 必须基于最新 AST 重新生成模块代码,例如用
esbuild.transform()或acorn + astring输出合法 JS 字符串 - 运行时用
vm.createContext()(Node)或new Worker()(浏览器)隔离沙箱,避免污染主作用域 - 若支持热重载,需清空旧模块缓存:
delete require.cache[require.resolve('./temp-entry.js')],否则require()还读老版本
多文件结构变动后,最易被忽略的是 import 路径映射未同步更新——比如把 utils.js 拖进 lib/ 文件夹,但 AST 中的 import { x } from './utils' 并未重写为 './lib/utils',运行时直接 Module not found。











