vue中如何进行DOM转移?

Vue实现DOM元素拖放互换位置 HTML 拖放接口使得 web 应用能够在网页中拖放文件。这里将介绍了 web 应用如何接受从底层平台的文件管理器拖动DOM的操作。拖放的主要步骤是为 drop 事件定义一个释放区(释放文件的目标元素) 和为dragover事件定义一个事件处理程序。触发 drop 事件的目标元素需要一个ondrop 事件处理函数。下面这一段代码以一个 元素为例展示了这些工作是如何完成的。一般来说,在实际应用中需要定义一个 dragover 事件的处理函数并在其中加入关闭浏览器默认拖放行为的代码。 阅读详情

需求来源

什么样的场景才会需要转移 Dom 呢 ?

如果你有过 UI 组件开发的经验,可能会发现,随着模块划分越来越细,模块层级越来越深,某些组件的 Dom 并不适合渲染在自身所属的 Dom 上。

比如常见的返回顶部按钮,悬浮广告等,例如如下:

  • 对话框
  • 右键菜单
  • 用户提示组件

在这里插入图片描述
这些组件有一个共同的特点,需要用户手动操作来展示相应的内容。

并且受限于页面各个层级的 overflow 设置,如果嵌入结构过深很容易导致渲染不完整。z-index 管理也会更加混乱,并且高频的创建、销毁也会引起不必要的局部重绘。

因此在开发 UI 用户交互组件的时候,如果有必要,可以考虑将 Dom 转移到组件外部,可能更便于管理。

如何转移DOM?

大致有两种方案:

  • 渲染前转移vnode
  • 渲染后转移$el节点

通过new Vue()创建新节点

例如右键菜单,通过new Vue()来转移DOM。
核心代码:

// 提供给用户使用的菜单定义组件
Vue.component("contextmenu", {
  name: "contextmenu-collect",
  render() {
    return null;
  }
});
// 定义指令
Vue.directive("menu", {
  inserted(el, binding, vnode) {
    let vm = vnode.context;
    let refKey = binding.arg;
    // 监听右键菜单事件
    el.addEventListener("contextmenu", (event) => {
      createMenu(event, vm.$refs[refKey].$slots.default);
      event.preventDefault();
    });
  }
});
// 创建菜单
function createMenu(event, vnode) {
  let newNode = document.createElement("div");
  document.body.appendChild(newNode);
  new Vue({
    el: newNode,
    render(createElement) {
      return createElement(
        "div",
        {
          attrs: {
            id: "right-contextmenu"
          }
        },
        vnode
      );
    }
  });
}

当然创建菜单的方法createMenu也可以如下代码:

// 创建菜单
function createMenu(event, vnode) {
  let newNode = document.createElement("div");
  document.body.appendChild(newNode);
  const MyComponent = new Vue({
    render: (h) => {
      return h(
        "div",
        {
          attrs: {
            id: "right-contextmenu"
          }
        },
        vnode
      );
    }
  });
  newNode.appendChild(MyComponent.$mount().$el);
}

用户使用:

<button v-menu:menu-node>右键点我吧</button>
<contextmenu ref="menu-node">
    <div>我是菜单里的元素</div>
    <span>我也是菜单里的元素耶~</span>
  </contextmenu>

其中contextmenu模块,它没有处理任何逻辑,其中的render函数渲染为null。

menu指令通过监听元素右键菜单事件来创建右键自定义菜单。

createMenu 方法内部,会默认创建新的节点放在 body 节点下,并通过 new Vue 将 vnode 转移到了外侧,也就起到了 Dom 转移的作用。

手动 appendChild 方式

PC常用的Element-UI 框架,它的 Dialog 对话框有一个叫 append-to-body 的参数,只要你添加了这个参数,对话框就会被渲染到页面的 body 节点下。

在这里插入图片描述
这一个简单的参数可以解决弹窗嵌套等一系列问题,我们来看一下它是怎么实现的。
地址:dialog弹框实现

其中核心代码:

mounted() {
   if (this.visible) {
     this.rendered = true;
     this.open();
     if (this.appendToBody) {
       document.body.appendChild(this.$el);
     }
   }
 },

 destroyed() {
   // if appendToBody is true, remove DOM node after destroy
   if (this.appendToBody && this.$el && this.$el.parentNode) {
     this.$el.parentNode.removeChild(this.$el);
   }
 }

在 mounted 阶段 Dom 已经渲染完毕,根据条件将自身的 Dom 强行移至 body 内部,在模块销毁时再手动移除 Dom 节点。

appendChild 是原生 Dom 操作方法,插入节点时会同时从源 Dom 节点移除,不会创造出新节点。

指令封装模式

指令封装本质上也是 appendChild 的模式,只是将 Dom 转移操作提炼成指令,需要的时候直接调用即可完成。

其中iview 的源码里也有 Dom 转移的方法,地址:https://github.com/iview/iview/blob/a61acfdcb725579735574e250f1ef7840975347c/src/directives/transfer-dom.js,其中代码如下:

// Thanks to: https://github.com/airyland/vux/blob/v2/src/directives/transfer-dom/index.js
// Thanks to: https://github.com/calebroseland/vue-dom-portal

/**
 * Get target DOM Node
 * @param {(Node|string|Boolean)} [node=document.body] DOM Node, CSS selector, or Boolean
 * @return {Node} The target that the el will be appended to
 */
function getTarget (node) {
    if (node === void 0) {
        node = document.body
    }
    if (node === true) { return document.body }
    return node instanceof window.Node ? node : document.querySelector(node)
}

const directive = {
    inserted (el, { value }, vnode) {
        if ( el.dataset && el.dataset.transfer !== 'true') return false;
        el.className = el.className ? el.className + ' v-transfer-dom' : 'v-transfer-dom';
        const parentNode = el.parentNode;
        if (!parentNode) return;
        const home = document.createComment('');
        let hasMovedOut = false;

        if (value !== false) {
            parentNode.replaceChild(home, el); // moving out, el is no longer in the document
            getTarget(value).appendChild(el); // moving into new place
            hasMovedOut = true
        }
        if (!el.__transferDomData) {
            el.__transferDomData = {
                parentNode: parentNode,
                home: home,
                target: getTarget(value),
                hasMovedOut: hasMovedOut
            }
        }
    },
    componentUpdated (el, { value }) {
        if ( el.dataset && el.dataset.transfer !== 'true') return false;
        // need to make sure children are done updating (vs. `update`)
        const ref$1 = el.__transferDomData;
        if (!ref$1) return;
        // homes.get(el)
        const parentNode = ref$1.parentNode;
        const home = ref$1.home;
        const hasMovedOut = ref$1.hasMovedOut; // recall where home is

        if (!hasMovedOut && value) {
            // remove from document and leave placeholder
            parentNode.replaceChild(home, el);
            // append to target
            getTarget(value).appendChild(el);
            el.__transferDomData = Object.assign({}, el.__transferDomData, { hasMovedOut: true, target: getTarget(value) });
        } else if (hasMovedOut && value === false) {
            // previously moved, coming back home
            parentNode.replaceChild(el, home);
            el.__transferDomData = Object.assign({}, el.__transferDomData, { hasMovedOut: false, target: getTarget(value) });
        } else if (value) {
            // already moved, going somewhere else
            getTarget(value).appendChild(el);
        }
    },
    unbind (el) {
        if (el.dataset && el.dataset.transfer !== 'true') return false;
        el.className = el.className.replace('v-transfer-dom', '');
        const ref$1 = el.__transferDomData;
        if (!ref$1) return;
        if (el.__transferDomData.hasMovedOut === true) {
            el.__transferDomData.parentNode && el.__transferDomData.parentNode.appendChild(el)
        }
        el.__transferDomData = null
    }
};

export default directive;

看到顶部有两行注释:

// Thanks to: https://github.com/airyland/vux/blob/v2/src/directives/transfer-dom/index.js
// Thanks to: https://github.com/calebroseland/vue-dom-portal

我们打开第一行的地址,代码如下:

/ Thanks to: https://github.com/calebroseland/vue-dom-portal

import objectAssign from 'object-assign'
/**
 * Get target DOM Node
 * @param {(Node|string|Boolean)} [node=document.body] DOM Node, CSS selector, or Boolean
 * @return {Node} The target that the el will be appended to
 */
function getTarget (node) {
  if (node === void 0) {
    return document.body
  }

  if (typeof node === 'string' && node.indexOf('?') === 0) {
    return document.body
  } else if (typeof node === 'string' && node.indexOf('?') > 0) {
    node = node.split('?')[0]
  }

  if (node === 'body' || node === true) {
    return document.body
  }

  return node instanceof window.Node ? node : document.querySelector(node)
}

function getShouldUpdate (node) {
  // do not updated by default
  if (!node) {
    return false
  }
  if (typeof node === 'string' && node.indexOf('?') > 0) {
    try {
      const config = JSON.parse(node.split('?')[1])
      return config.autoUpdate || false
    } catch (e) {
      return false
    }
  }
  return false
}

const directive = {
  inserted (el, { value }, vnode) {
    el.className = el.className ? el.className + ' v-transfer-dom' : 'v-transfer-dom'
    const parentNode = el.parentNode
    var home = document.createComment('')
    var hasMovedOut = false

    if (value !== false) {
      parentNode.replaceChild(home, el) // moving out, el is no longer in the document
      getTarget(value).appendChild(el) // moving into new place
      hasMovedOut = true
    }
    if (!el.__transferDomData) {
      el.__transferDomData = {
        parentNode: parentNode,
        home: home,
        target: getTarget(value),
        hasMovedOut: hasMovedOut
      }
    }
  },
  componentUpdated (el, { value }) {
    const shouldUpdate = getShouldUpdate(value)
    if (!shouldUpdate) {
      return
    }
    // need to make sure children are done updating (vs. `update`)
    var ref$1 = el.__transferDomData
    // homes.get(el)
    var parentNode = ref$1.parentNode
    var home = ref$1.home
    var hasMovedOut = ref$1.hasMovedOut // recall where home is

    if (!hasMovedOut && value) {
      // remove from document and leave placeholder
      parentNode.replaceChild(home, el)
      // append to target
      getTarget(value).appendChild(el)
      el.__transferDomData = objectAssign({}, el.__transferDomData, { hasMovedOut: true, target: getTarget(value) })
    } else if (hasMovedOut && value === false) {
      // previously moved, coming back home
      parentNode.replaceChild(el, home)
      el.__transferDomData = objectAssign({}, el.__transferDomData, { hasMovedOut: false, target: getTarget(value) })
    } else if (value) {
      // already moved, going somewhere else
      getTarget(value).appendChild(el)
    }
  },
  unbind: function unbind (el, binding) {
    el.className = el.className.replace('v-transfer-dom', '')
    if (el.__transferDomData && el.__transferDomData.hasMovedOut === true) {
      el.__transferDomData.parentNode && el.__transferDomData.parentNode.appendChild(el)
    }
    el.__transferDomData = null
  }
}

export default directive

又看到了一行注释:

// Thanks to: https://github.com/calebroseland/vue-dom-portal

从上面代码及注释看,iview 借鉴了vux的 transfer-dom 实现方式,而 vux 的 transfer-dom 是基于 vue-dom-portal 改造而来的。

参考地址:http://bh-lay.com/blog/ottsc8e7cm

vue3知识总结 Vue 3 是 Vue.js 的最新版本,相较于 Vue 2,它在性能、API 设计、类型支持等多个方面都有显著的改进和创新。 阅读详情

相关推荐

手把手教你复现KDD 2024时空预测顶会论文:以UniST和ImputeFormer为例的代码实战与避坑指南

本文详细指导如何复现KDD 2024顶会论文中的UniST和ImputeFormer模型,涵盖环境配置、数据处理、模型训练及评估等关键步骤。通过实战案例和避坑指南,帮助研究者掌握时空预测与插补技术,提升在智慧城市、交通管理等领域的应用能力。

weixin_29204205的博客 279

vue 3.x 新特性teleport

遮罩层,模态框等第三方组件的常常是默认组件放到body下面的,这是为了避免父元素的样式对其产生影响。但是在实际应用中,我们可能也需要不放在body里,方便样式修改和组件化。我们来看看第三方的组件库如何做的。

weixin_44494811的博客 1133

Claude Code 完整学习笔记

Claude Code 学习笔记摘要 Claude Code是Anthropic推出的智能编码工具,基于Claude Opus 4.5模型构建,提供跨平台支持(终端/VS Code/JetBrains/Web等)。核心功能包括: 代码库理解与多文件编辑 命令执行与开发工具集成 智能体协作开发流程 安装要求Node.js≥18,提供原生/Homebrew/npm等多种安装方式,支持环境变量配置API访问。基础交互模式包括会话管理、文件操作和代码审查等功能,可自动读写文件并执行精确修改。 (149字)

虚心若愚 810

深入解析airyland/vux中的v-transfer-dom指令

深入解析airyland/vux中的v-transfer-dom指令 【免费下载链接】vux Mobile UI Components based on Vue & WeUI 项目地址: https://gitcode.com/g...

gitblog_00590的博客 465

详解卷积神经网络(CNN)

卷积神经网络(Convolutional Neural Network, CNN)是一种前馈神经网络,它的人工神经元可以响应一部分覆盖范围内的周围单元,对于大型图像处理有出色表现。

qq_25762497的博客 25万+

别再调PID了!用MATLAB/Simulink给四旋翼无人机搭个ADRC控制器(附完整仿真模型)

本文详细介绍了在MATLAB/Simulink环境中为四旋翼无人机搭建ADRC控制器的完整流程,包括核心原理、Simulink建模、参数调试技巧及与PID控制的性能对比。ADRC通过扩张状态观测器(ESO)有效提升无人机在复杂扰动下的控制性能,特别适合四旋翼无人机这类强耦合系统。文章还提供了完整的仿真模型和工程实践问题解决方案。

weixin_26808439的博客 381

vue组件内的元素转移到指定位置

应用场景: 组件内的dom默认是在 组件内,有时候会出现一些问题:我们封装的组件使用了actionsheet 啊,popup啊,这些 如果不直接在body下,会出现一些样式层叠问题(因为z-index)。这时候如果可以将 引发问题的dom 挪到body下,就好了。 一些理解: 1、来源 下面的代码摘自于 vux 的previewer组件源代码,而它又是 借鉴于 https://github...

weixin_30323961的博客 2996

【FPGA】AD9694调试注意事项

基于FPGA的AD9694设计及其注意事项,包括同步的设计注意事项

weixin_41246509的博客 2898

VUX生态扩展:插件开发与社区贡献

VUX生态扩展:插件开发与社区贡献 【免费下载链接】vux Mobile UI Components based on Vue & WeUI 项目地址: https://gitcode.com/gh_mirrors/vu/vux ...

gitblog_00804的博客 339

第186篇 前馈控制——提升跟踪精度的补充手段

文章摘要: 本文介绍了前馈控制的原理及其在工程中的应用。与反馈控制不同,前馈控制通过提前预测目标值或扰动信息来生成控制量,避免误差出现后再纠正的延迟问题,适用于机器人、机械臂等高速运动场景。前馈控制通常与PID反馈配合使用,前者提供粗调,后者进行微调。文章详细讲解了前馈增益的调节方法(模型法、实验法、自适应法)及两种前馈类型(指令前馈和扰动前馈),并提供了代码示例和面试实战建议,强调前馈与反馈的协同设计是提升控制精度的关键。最后预告了下一篇将讨论串级PID控制架构。

郑伟强dev的专栏 397

mediasoup源码分析(二十)服务端架构设计

目录 简介 信令服务(房间服务) 调度服务 媒体服务 简介 流媒体服务器大的方向上可以分为上述三个模块。目前行业内都是分布式服务,独立部署。mediasoup信令与媒体之间虽然可以管道通信。但在行业内,服务之间通过管道通信用的少。 一般是rpc、mq,或者传统的tcp。这块licode架构比较不错,licode是信令和媒体服务通过MQ消息队列解耦。 下面介绍一下这三个服务主要功能,当然文中所说也是一家之言。 信令服务(房间服务) 信令服务也称为房间服务。有的企业把信令和房间分开,信..

lcalqf的专栏 1545

vue 怎么销毁dom_Vue 中如何转移 Dom ?

这个标题可能很拗口,请允许我先做下简短的解释。在我们使用 Vue 进行开发时,Dom 最终渲染的结构与 template 嵌套逻辑是一致的,有着严格的父子级关系。假如我们有特殊需求,需要将某个 viewModel 的 Dom 渲染到父级之外,需要如何操作?一、真的有这种需求 ?可能你在看了前面介绍之后会觉得很奇怪,也意识到自己的开发经验里从来没有过这样的需求。什么样的场景才会需要转移 Dom 呢 ...

weixin_30725829的博客 3216

【C++】开源:cpp-httplib HTTP协议库配置与使用

cpp-httplib HTTP协议库配置与使用

DevFrank的博客 5538

vue注册新节点_Vue 中如何转移 Dom ?

这个标题可能很拗口,请允许我先做下简短的解释。在我们使用 Vue 进行开发时,Dom 最终渲染的结构与 template 嵌套逻辑是一致的,有着严格的父子级关系。假如我们有特殊需求,需要将某个 viewModel 的 Dom 渲染到父级之外,需要如何操作?一、真的有这种需求 ?可能你在看了前面介绍之后会觉得很奇怪,也意识到自己的开发经验里从来没有过这样的需求。什么样的场景才会需要转移 Dom 呢 ...

weixin_33547979的博客 634

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

vue3 teleport 类似 Portal 转移dom和组件

原文链接: vue3 teleport 类似 Portal 转移dom和组件 ...

阿豪 373

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Vue 3中的Teleport与Portal的区别

Teleport是Vue 3中一个非常强大的新特性,它允许将组件的渲染输出“转移”到DOM树的其他部分。简单来说,Teleport可以让你不受组件父子关系的约束,在任何地方生成组件。这对于模态框、提示框和下拉菜单等场景来说,尤其有用,因为这些组件常常需要置于DOM树的顶部或特定位置。Portal是一个更高层次的概念,它更侧重于在UI框架中处理组件的渲染位置。Portal可以理解为一种桥梁,它将子组件渲染到一个由父组件定义的DOM节点,而不是默认情况下的DOM结构。

有我更精彩的博客 1442

Vue 3 核心技术详解:新特性与实用技巧

组合式 API 是 Vue 3 引入的一种更灵活的组件写法,解决了 Vue 2 中选项式 API 的代码分散问题。Vue 3 引入的新特性和优化使得开发大型复杂应用更加高效。通过组合式 API 和全新的响应式系统,你可以更轻松地管理逻辑和状态;Teleport 和 Suspense 则提升了 UI 的灵活性和用户体验。从原理到实践,掌握 Vue 3 的这些核心技术,你将能更从容地应对各种复杂开发场景!

一名热衷于技术的全栈开发者,专注于前端与后端的全面技术探索。在这里,我将分享我在技术领域的学习与成长,助力更多开发者的进步。 1420

vue3(四)--vue3中的新组件和其他基于vue2的变化

vue3中的新组件和其他基于vue2的变化

qq_43178432的博客 136

VUE中演示v-for为什么要加key

<div id="app"> <div> <input type="text" v-model="name"> <button @click="add">添加</button> </div> <ul> <li v-for="(item, i) in list"> <input type="checkbox"> {{item..

zhangli0421的博客 177

vue回车触发事件

1.vue登录界面输入用户名回车自动聚焦密码框 用户框里放:@keyup.enter.native=“keyupClick”; 密码框里放::ref=“mima” 在methods里: keyupClick() { this.$refs.mima.focus() }, 直接上代码 <van-field v-model="username" clearable type="tel" label="用户名" right-icon="questio

qq_45149537的博客 8935

【Vue原理解析】之虚拟DOM

虚拟DOM是Vue.js中一个重要且核心的概念。它通过在内存中构建一个轻量级的DOM树来代替直接操作真实的DOM,从而提高了性能和开发效率。虚拟DOM的核心源码分析揭示了Vue.js是如何通过比较新旧两个VNode树之间的差异来更新页面的。通过深入理解虚拟DOM的原理,开发者可以更好地利用Vue.js提供的功能和特性,从而构建高性能和可维护的Web应用程序。

俊刚的博客 1471

Vue 虚拟DOM

在前端开发中,DOM操作是非常常见的一种操作。然而,频繁的DOM操作会带来性能上的问题,因为DOM操作是一种相对较慢的操作。为了解决这个问题,虚拟DOM应运而生。本文将介绍什么是虚拟DOM,为什么需要虚拟DOM,以及虚拟DOM如何转换为真实的DOM。

俊刚的博客 1113

vue.js之虚拟 DOM

虚拟 DOM 是一种轻量级的 JavaScript 对象,它是真实 DOM 的抽象表示。简单来说,虚拟 DOM 就是用 JavaScript 对象来模拟真实的 DOM 树结构,每个虚拟 DOM 节点对应一个真实的 DOM 节点。

wl_qianduan的博客 1185

vue的虚拟dom

频繁的操作 DOM,会产生一定的性能问题。Vue2 的 Virtual DOM 借鉴了开源库 snabbdom 的实现。Virtual DOM 本质就是用一个原生的 JS 对象去描述一个 DOM 节点,是对真实 DOM 的一层抽象。这是一个简易的实现vdom功能的库,相比vue、react,对于vdom这块更加简易,适合我们学习vdom。vdom里面有两个核心的api,一个是h函数,一个是patch函数,前者用来生成vdom对象,后者的功能在于做虚拟dom的比对和将vdom挂载到真实DOM上。

我的博客简介 505

探索Vue.js组件DOM的新维度:Vue-DOM-Portal

探索Vue.js组件DOM的新维度:Vue-DOM-Portal 在前端开发的广阔天地中,Vue.js以其简洁优雅著称,而【vue-dom-portal】正是为了解决Vue组件内部DOM元素放置难题而生的一项神器。今天,让我们一起深入探讨这个开源宝藏,看看它如何成为你下一次Vue项目中的得力助手。 项目简介 Vue-DOM-Portal,正如其名,提供了一扇“门户”,让你能够自由穿梭DOM节点于V...

gitblog_00100的博客 394
上一篇: 移动端响应式布局开发的几种方式
下一篇: tips:生产环境如何检查Vue应用程序?
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值