基于Vue2或Vue3实现任意上下左右拖拽悬浮的元素,且配置为自定义的全局指令

简介: 这篇文章介绍了如何在Vue 2或Vue 3项目中实现一个自定义的全局指令`v-dragSwitch`,用于创建可以任意方向拖拽并悬浮的元素,同时包含边界处理的逻辑。

前言

使用Vue实现任意上下左右拖拽悬浮的元素,以及具有边界处理的具体实现是网上的大神实现的,也不知是谁了。俺只不过是优化了一下皮毛,写了个在Vue2或Vue3的项目中的示例,配置自定义的全局指令,主要是方便下次使用,记录一下。

一、基于Vue2框架的项目

(1)在 /src/utils/ 目录中新建 diyVueDirectives.js

/**
 * 自定义拖拽指令
 */
const dragSwitch = {
   
   
  bind(el, binding, vnode, oldVnode) {
   
   
    // 判断是否可拖拽
    if (!binding.value) {
   
   
      return
    }

    // 获取相关元素
    const container = el.querySelector('.d-d_container')
    const header = el.querySelector('.d-d_container_header')
    header.style.cssText += ';cursor:move;'

    // 获取元素原有属性
    const sty = (function () {
   
   
      if ((document.body).currentStyle) {
   
   
        return (dom, attr) => dom.currentStyle[attr] // 兼容IE写法
      }
      return (dom, attr) => getComputedStyle(dom, null)[attr]
    })()

    /**
     * 鼠标按下事件
     */
    header.onmousedown = (e) => {
   
   
      const disX = e.clientX - header.offsetLeft
      const disY = e.clientY - header.offsetTop
      const screenWidth = document.body.clientWidth // document.body的可见区域宽度
      const screenHeight = document.documentElement.clientHeight // 可见区域高度(应为body高度,可某些环境下无法获取)

      const containerWidth = container.offsetWidth // 对话框宽度
      const containerheight = container.offsetHeight // 对话框高度

      const minContainerLeft = container.offsetLeft
      const maxContainerLeft = screenWidth - container.offsetLeft - containerWidth

      const minContainerTop = container.offsetTop
      const maxContainerTop = screenHeight - container.offsetTop - containerheight

      // 左偏移距离
      let styL = sty(container, 'left')
      if (styL === 'auto') {
   
   
        styL = '0px' // 兼容IE写法
      }

      // 上偏移距离
      let styT = sty(container, 'top')

      // 注意在IE中,第一次获取到的值为组件自带50%,移动之后赋值为px
      if (styL.includes('%')) {
   
   
        styL = +document.body.clientWidth * (+styL.replace(/%/g, '') / 100)
        styT = +document.body.clientHeight * (+styT.replace(/%/g, '') / 100)
      } else {
   
   
        styL = +styL.replace(/px/g, '')
        styT = +styT.replace(/px/g, '')
      }

      /**
       * 鼠标移动事件
       */
      document.onmousemove = function (e) {
   
   
        // 通过事件委托,计算移动的距离
        let left = e.clientX - disX
        let top = e.clientY - disY

        // 边界处理
        if (-(left) > minContainerLeft) {
   
   
          left = -(minContainerLeft)
        } else if (left > maxContainerLeft) {
   
   
          left = maxContainerLeft
        }

        if (-(top) > minContainerTop) {
   
   
          top = -(minContainerTop)
        } else if (top > maxContainerTop) {
   
   
          top = maxContainerTop
        }

        // 移动当前元素
        container.style.cssText += `;left:${
     
     left + styL}px;top:${
     
     top + styT}px;`
      }

      /**
       * 鼠标松开事件
       */
      document.onmouseup = function (e) {
   
   
        document.onmousemove = null
        document.onmouseup = null
      }

      return false
    }
  }
}

// 注册自定义拖拽指令
Vue.directive('dragSwitch', dragSwitch)

(2)在 main.js 中引入该指令集

// 引入 Vue 自定义指令集
import "@/utils/diyVueDirectives"

(3)任意在一个 vue 页面中使用 v-dragSwitch 指令即可

<template>
  <div style="width: 100%; height: 100%; position: relative; overflow: hidden; background-color: #dbe8ff">
    <!-- ^ 自定义拖拽模块一 -->
    <div class="d-d" v-dragSwitch="true">
      <div class="d-d_container" style="">
        <div class="d-d_container_header">标题一</div>
      </div>
    </div>
    <!-- / 自定义拖拽模块一 -->

    <!-- ^ 自定义拖拽模块二 -->
    <div class="d-d" v-dragSwitch="false">
      <div class="d-d_container" style="left: 100px; top: 100px">
        <div class="d-d_container_header">标题二</div>
      </div>
    </div>
    <!-- / 自定义拖拽模块二 -->

    <!-- ^ 自定义拖拽模块三 -->
    <div class="d-d" v-dragSwitch="true">
      <div class="d-d_container" style="right: 20px; top: calc(50% - 50px)">
        <div class="d-d_container_header">标题三</div>
      </div>
    </div>
    <!-- / 自定义拖拽模块三 -->
  </div>
</template>

<script>
export default {
    
    
  data() {
    
    
    return {
    
    

    }
  },
  methods: {
    
    

  }
}
</script>

<style lang="less" scoped>
  .d-d {
    
    
    width: auto;
    height: auto;
    position: absolute;

    .d-d_container {
    
    
      width: 100px;
      height: 100px;
      position: fixed;
      background-color: #fff;
      user-select: none;

      .d-d_container_header {
    
    
        text-align: center;
        border-bottom: 1px solid #dcdfe6;
      }
    }
  }
</style>

二、基于Vue3框架的项目

(1)在 /src/utils/ 目录中新建 diyVueDirectives.ts

/**
 * 自定义拖拽指令
 */
const dragSwitch = {
   
   
  beforeMount(el: any, binding: any) {
   
   
    // 判断是否可拖拽
    if (!binding.value) {
   
   
      return
    }

    // 获取相关元素
    const container = el.querySelector('.d-d_container')
    const header = el.querySelector('.d-d_container_header')
    header.style.cssText += ';cursor:move;'

    // 获取元素原有属性
    const sty = (function () {
   
   
      if ((document.body as any).currentStyle) {
   
   
        return (dom: any, attr: any) => dom.currentStyle[attr] // 兼容IE写法
      }
      return (dom: any, attr: any) => getComputedStyle(dom, null)[attr]
    })()

    /**
     * 鼠标按下事件
     */
    header.onmousedown = (e: any) => {
   
   
      const disX = e.clientX - header.offsetLeft
      const disY = e.clientY - header.offsetTop
      const screenWidth = document.body.clientWidth // document.body的可见区域宽度
      const screenHeight = document.documentElement.clientHeight // 可见区域高度(应为body高度,可某些环境下无法获取)

      const containerWidth = container.offsetWidth // 对话框宽度
      const containerheight = container.offsetHeight // 对话框高度

      const minContainerLeft = container.offsetLeft
      const maxContainerLeft = screenWidth - container.offsetLeft - containerWidth

      const minContainerTop = container.offsetTop
      const maxContainerTop = screenHeight - container.offsetTop - containerheight

      // 左偏移距离
      let styL = sty(container, 'left')
      if (styL === 'auto') {
   
   
        styL = '0px' // 兼容IE写法
      }

      // 上偏移距离
      let styT = sty(container, 'top')

      // 注意在IE中,第一次获取到的值为组件自带50%,移动之后赋值为px
      if (styL.includes('%')) {
   
   
        styL = +document.body.clientWidth * (+styL.replace(/%/g, '') / 100)
        styT = +document.body.clientHeight * (+styT.replace(/%/g, '') / 100)
      } else {
   
   
        styL = +styL.replace(/px/g, '')
        styT = +styT.replace(/px/g, '')
      }

      /**
       * 鼠标移动事件
       */
      document.onmousemove = function (e) {
   
   
        // 通过事件委托,计算移动的距离
        let left = e.clientX - disX
        let top = e.clientY - disY

        // 边界处理
        if (-(left) > minContainerLeft) {
   
   
          left = -(minContainerLeft)
        } else if (left > maxContainerLeft) {
   
   
          left = maxContainerLeft
        }

        if (-(top) > minContainerTop) {
   
   
          top = -(minContainerTop)
        } else if (top > maxContainerTop) {
   
   
          top = maxContainerTop
        }

        // 移动当前元素
        container.style.cssText += `;left:${
     
     left + styL}px;top:${
     
     top + styT}px;`
      }

      /**
       * 鼠标松开事件
       */
      document.onmouseup = function (e: any) {
   
   
        document.onmousemove = null
        document.onmouseup = null
      }

      return false
    }
  }
}

/**
 * 定义指令集
 */
const diyVueDirectives = {
   
   
  install: function (app: any) {
   
   
    app.directive('dragSwitch', dragSwitch) // 注册自定义拖拽指令
  }
}

/**
 * 导出指令集
 */
export default diyVueDirectives

(2)在 main.ts 中引入该指令集

// 引入 Vue 自定义指令集并配置为全局属性
import diyVueDirectives from "@/utils/diyVueDirectives"

// 是否隐藏所有 console.log 信息打印,若注释此代码则显示,否则隐藏
// console.log = () => {}

app
.use(router)
.use(store)
.use(diyVueDirectives)
.use(ElementPlusPlugin)
.mount('#app')

(3)任意在一个 vue 页面中使用 v-dragSwitch 指令即可

<template>
  <div style="width: 100%; height: 100%; position: relative; overflow: hidden; background-color: #dbe8ff">
    <!-- ^ 自定义拖拽模块一 -->
    <div class="d-d" v-dragSwitch="true">
      <div class="d-d_container" style="">
        <div class="d-d_container_header">标题一</div>
      </div>
    </div>
    <!-- / 自定义拖拽模块一 -->

    <!-- ^ 自定义拖拽模块二 -->
    <div class="d-d" v-dragSwitch="true">
      <div class="d-d_container" style="left: 100px; top: 100px">
        <div class="d-d_container_header">标题二</div>
      </div>
    </div>
    <!-- / 自定义拖拽模块二 -->

    <!-- ^ 自定义拖拽模块三 -->
    <div class="d-d" v-dragSwitch="true">
      <div class="d-d_container" style="right: 20px; top: calc(50% - 50px)">
        <div class="d-d_container_header">标题三</div>
      </div>
    </div>
    <!-- / 自定义拖拽模块三 -->
  </div>
</template>

<script>
export default {
    
    
  data() {
    
    
    return {
    
    

    }
  },
  methods: {
    
    

  }
}
</script>

<style lang="less" scoped>
  .d-d {
    
    
    width: auto;
    height: auto;
    position: absolute;

    .d-d_container {
    
    
      width: 100px;
      height: 100px;
      position: fixed;
      background-color: #fff;
      user-select: none;

      .d-d_container_header {
    
    
        text-align: center;
        border-bottom: 1px solid #dcdfe6;
      }
    }
  }
</style>

三、运行效果

目录
相关文章
|
JavaScript 数据可视化 前端开发
基于 Vue 与 D3 的可拖拽拓扑图技术方案及应用案例解析
本文介绍了基于Vue和D3实现可拖拽拓扑图的技术方案与应用实例。通过Vue构建用户界面和交互逻辑,结合D3强大的数据可视化能力,实现了力导向布局、节点拖拽、交互事件等功能。文章详细讲解了数据模型设计、拖拽功能实现、组件封装及高级扩展(如节点类型定制、连接样式优化等),并提供了性能优化方案以应对大数据量场景。最终,展示了基础网络拓扑、实时更新拓扑等应用实例,为开发者提供了一套完整的实现思路和实践经验。
2048 78
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
825 161
|
JavaScript
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
1389 159
|
JavaScript 前端开发 开发者
Vue 自定义进度条组件封装及使用方法详解
这是一篇关于自定义进度条组件的使用指南和开发文档。文章详细介绍了如何在Vue项目中引入、注册并使用该组件,包括基础与高级示例。组件支持分段配置(如颜色、文本)、动画效果及超出进度提示等功能。同时提供了完整的代码实现,支持全局注册,并提出了优化建议,如主题支持、响应式设计等,帮助开发者更灵活地集成和定制进度条组件。资源链接已提供,适合前端开发者参考学习。
938 17
|
JavaScript API 开发者
Vue框架中常见指令的应用概述。
通过以上的详细解析,你应该已经初窥Vue.js的指令的威力了。它们是Vue声明式编程模型的核心之一,无论是构建简单的静态网站还是复杂的单页面应用,你都会经常用到。记住,尽管Vue提供了大量预定义的指令,你还可以创建自定义指令以满足特定的需求。为你的Vue应用程序加上这些功能增强器,让编码变得更轻松、更愉快吧!
294 1
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
JavaScript 前端开发 UED
Vue 项目中如何自定义实用的进度条组件
本文介绍了如何使用Vue.js创建一个灵活多样的自定义进度条组件。该组件可接受进度段数据数组作为输入,动态渲染进度段,支持动画效果和内容展示。当进度超出总长时,超出部分将以红色填充。文章详细描述了组件的设计目标、实现步骤(包括props定义、宽度计算、模板渲染、动画处理及超出部分的显示),并提供了使用示例。通过此组件,开发者可根据项目需求灵活展示进度情况,优化用户体验。资源地址:[https://pan.quark.cn/s/35324205c62b](https://pan.quark.cn/s/35324205c62b)。
741 0
|
资源调度 JavaScript 前端开发
Pinia 如何在 Vue 3 项目中进行安装和配置?
Pinia 如何在 Vue 3 项目中进行安装和配置?
1493 4
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
978 2