🚀 一个轻量级的 JavaScript 库,用于在 DOM 中传送(移动)元素,同时保留其响应式绑定和事件监听器。
- 🎯 保留响应式绑定 - 移动 DOM 元素而不是复制,保留 Vue/React 等框架的响应式绑定
- 🔄 支持回退 - 可以轻松将元素放回原位
- 📦 灵活的 API - 支持类实例和便捷函数两种调用方式
- 🎨 多种插入位置 - 支持 append、prepend、before、after
- 🔍 智能元素查找 - 自动支持 ID、选择器和 DOM 元素
- ⛓️ 链式调用 - 流畅的 API 设计
- 🐛 调试模式 - 内置调试日志功能
dom-teleport/
├── src/
│ ├── core/
│ │ ├── DomTeleport.js # 核心类
│ │ └── constants.js # 常量和枚举
│ ├── utils/
│ │ └── helpers.js # 工具函数
│ └── main.js # 主入口文件
├── test/
│ └── index.test.html # 测试文件
├── examples/
│ ├── index.html # 示例导航
│ ├── demo.html # 原生 JS 示例
│ ├── vue-demo.html # Vue 3 示例
│ └── react-demo.html # React 18 示例
└── dist/ # 构建输出
├── index.js # CommonJS 格式
└── index.esm.js # ES Module 格式
在 examples/ 目录下提供了多个示例:
- index.html - 示例导航页
- demo.html - 原生 JavaScript 基础示例
- vue-demo.html - Vue 3 响应式测试
- react-demo.html - React 18 状态测试
# 克隆仓库
git clone https://github.com/wenps/dom-teleport.git
cd dom-teleport
# 构建项目
npm install
npm run build
# 使用任意 HTTP 服务器运行
# 方式 1: 使用 Python
python3 -m http.server 8000
# 方式 2: 使用 Node.js http-server
npx http-server
# 然后访问 http://localhost:8000/examples/npm install dom-teleport或使用 yarn:
yarn add dom-teleportimport DomTeleport from 'dom-teleport';
// 通过 ID 传送元素
const teleporter = new DomTeleport('myElement');
teleporter.appendTo('targetContainer');
// 放回原位
teleporter.back();import { teleport, TeleportPosition } from 'dom-teleport';
// 快速传送
teleport('myElement', 'targetContainer');
// 指定位置
teleport('myElement', 'targetContainer', TeleportPosition.PREPEND);import DomTeleport from 'dom-teleport';
// 通过 ID 创建
const teleporter = new DomTeleport('myElement');
// 通过选择器创建
const teleporter = new DomTeleport('.my-class');
const teleporter = new DomTeleport('#myElement');
// 通过 DOM 元素创建
const element = document.getElementById('myElement');
const teleporter = new DomTeleport(element);
// 开启调试模式
const teleporter = new DomTeleport('myElement', { debug: true });// 追加到目标元素内部末尾
teleporter.appendTo('targetContainer');
// 插入到目标元素内部开头
teleporter.prependTo('targetContainer');
// 插入到目标元素之前
teleporter.insertBefore('targetElement');
// 插入到目标元素之后
teleporter.insertAfter('targetElement');
// 通用方法(可指定位置)
import { TeleportPosition } from 'dom-teleport';
teleporter.to('targetContainer', TeleportPosition.APPEND);// 放回原位
teleporter.back();
// 以当前位置为新的原始位置
teleporter.resetOriginalPosition();new DomTeleport('myElement')
.appendTo('container1')
.back()
.prependTo('container2')
.resetOriginalPosition()
.insertAfter('sibling');// 获取源元素
const element = teleporter.getElement();
// 销毁实例(清理引用)
teleporter.destroy();import { teleport, TeleportPosition } from 'dom-teleport';
// 基础用法(默认 append)
const instance = teleport('source', 'target');
// 指定位置
teleport('source', 'target', TeleportPosition.PREPEND);
teleport('source', 'target', TeleportPosition.BEFORE);
teleport('source', 'target', TeleportPosition.AFTER);
// 可以继续操作返回的实例
instance.back();import { TeleportPosition } from 'dom-teleport';
TeleportPosition.APPEND // 追加到目标元素内部末尾
TeleportPosition.PREPEND // 插入到目标元素内部开头
TeleportPosition.BEFORE // 插入到目标元素之前
TeleportPosition.AFTER // 插入到目标元素之后import DomTeleport from 'dom-teleport';
// 在 Vue 组件中传送元素到 body,保留响应式绑定
export default {
mounted() {
this.teleporter = new DomTeleport(this.$refs.modal);
this.teleporter.appendTo(document.body);
},
beforeUnmount() {
this.teleporter.back();
this.teleporter.destroy();
}
}import { useEffect, useRef } from 'react';
import DomTeleport from 'dom-teleport';
function Modal() {
const modalRef = useRef(null);
useEffect(() => {
const teleporter = new DomTeleport(modalRef.current);
teleporter.appendTo(document.body);
return () => {
teleporter.back();
teleporter.destroy();
};
}, []);
return <div ref={modalRef}>Modal Content</div>;
}import DomTeleport from 'dom-teleport';
const toolbar = new DomTeleport('toolbar', { debug: true });
// 根据上下文切换工具栏位置
function switchContext(context) {
if (context === 'editor') {
toolbar.appendTo('editor-container');
} else if (context === 'preview') {
toolbar.appendTo('preview-container');
} else {
toolbar.back(); // 恢复到原始位置
}
}import DomTeleport from 'dom-teleport';
let currentTeleporter = null;
function onDragStart(element) {
currentTeleporter = new DomTeleport(element);
}
function onDrop(targetElement, position) {
if (currentTeleporter) {
if (position === 'before') {
currentTeleporter.insertBefore(targetElement);
} else {
currentTeleporter.insertAfter(targetElement);
}
currentTeleporter.resetOriginalPosition(); // 重置为新位置
}
}
function onDragCancel() {
if (currentTeleporter) {
currentTeleporter.back(); // 取消拖拽,放回原位
currentTeleporter.destroy();
}
}import DomTeleport from 'dom-teleport';
const sidebar = new DomTeleport('sidebar');
function handleResize() {
if (window.innerWidth < 768) {
// 移动端:侧边栏移到主内容下方
sidebar.insertAfter('main-content');
} else {
// 桌面端:侧边栏回到原位
sidebar.back();
}
}
window.addEventListener('resize', handleResize);
handleResize();DomTeleport 使用智能查找逻辑来定位元素:
- 如果传入的是
HTMLElement,直接使用 - 如果传入的是字符串:
- 首先尝试通过
document.getElementById()查找(当做 ID) - 如果找不到,再通过
document.querySelector()查找(当做选择器)
- 首先尝试通过
// 以下都是有效的调用方式
new DomTeleport('myId'); // ID
new DomTeleport('#myId'); // 选择器
new DomTeleport('.my-class'); // 选择器
new DomTeleport('[data-role="btn"]'); // 选择器
new DomTeleport(domElement); // DOM 元素- 原始位置保存: 创建实例时会自动保存元素的原始位置,包括父节点和相邻兄弟节点
- 响应式绑定: 由于使用的是 DOM 移动而非克隆,所有事件监听器和框架绑定都会保留
- 错误处理: 所有方法都会在出错时抛出异常,建议使用 try-catch 包裹
- 内存管理: 不再使用时应调用
destroy()方法清理引用
new DomTeleport(source: string | HTMLElement, options?: {
debug?: boolean
})to(target, position)- 传送到指定位置appendTo(target)- 追加到目标元素内部末尾prependTo(target)- 插入到目标元素内部开头insertBefore(target)- 插入到目标元素之前insertAfter(target)- 插入到目标元素之后back()- 放回原位resetOriginalPosition()- 重置原始位置getElement()- 获取源元素destroy()- 销毁实例
sourceElement- 源 DOM 元素isTeleported- 是否已传送debug- 是否开启调试模式
teleport(
source: string | HTMLElement,
target: string | HTMLElement,
position?: TeleportPosition
): DomTeleportTeleportPosition {
APPEND = 'append',
PREPEND = 'prepend',
BEFORE = 'before',
AFTER = 'after'
}欢迎提交 Issue 和 Pull Request!
MIT License
感谢所有贡献者和使用者!