Vue 项目中使用拖拽弹框 dragdialog, 弹框拖拽缩放,拖拽排序等

客官°小女子只卖身不卖艺 2022-12-08 05:07 348阅读 0赞

20200918000407873.gif

在开发的时候有个需求是 对弹框进行拖拽移动,拖拽方法,对弹框内的模块进行拖拽排序移动位置

本drag-dialog是在element框架项目内进行的

  • 技术框架:vue.js + element-ui
  • 拖拽组件: vue-slicksort
  • 自定义指令

drag自定义指令

1、在项目中src中新建 directive —> drag-dialog.js

  1. import Vue from 'vue'
  2. // v-dialogDrag: 弹窗拖拽属性
  3. Vue.directive('dialogDrag', {
  4. bind(el, binding, vnode, oldVnode) {
  5. // 自定义属性,判断是否可拖拽 
  6. if (!binding.value) return
  7. const dialogHeaderEl = el.querySelector('.el-dialog__header') //.drag-dialog-box
  8. const dragDom = el.querySelector('.el-dialog')
  9. dialogHeaderEl.style.cssText += ';cursor:move;'
  10. dragDom.style.cssText += ';top:150px;'
  11. // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
  12. const sty = (function () {
  13. if (document.body.currentStyle) {
  14. // 在ie下兼容写法
  15. return (dom, attr) => dom.currentStyle[attr]
  16. } else {
  17. return (dom, attr) => getComputedStyle(dom, false)[attr]
  18. }
  19. })()
  20. dialogHeaderEl.onmousedown = (e) => {
  21. // 鼠标按下,计算当前元素距离可视区的距离
  22. const disX = e.clientX - dialogHeaderEl.offsetLeft
  23. const disY = e.clientY - dialogHeaderEl.offsetTop
  24. const screenWidth = document.body.clientWidth // body当前宽度
  25. const screenHeight = document.documentElement.clientHeight // 可见区域高度(应为body高度,可某些环境下无法获取)
  26. const dragDomWidth = dragDom.offsetWidth // 对话框宽度
  27. const dragDomheight = dragDom.offsetHeight // 对话框高度
  28. const minDragDomLeft = dragDom.offsetLeft
  29. const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
  30. const minDragDomTop = dragDom.offsetTop
  31. const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight
  32. // 获取到的值带px 正则匹配替换
  33. let styL = sty(dragDom, 'left')
  34. // 为兼容ie 
  35. if (styL === 'auto') styL = '0px'
  36. let styT = sty(dragDom, 'top')
  37. // console.log(styL)
  38. // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
  39. if (styL.includes('%')) {
  40. styL = +document.body.clientWidth * (+styL.replace(/%/g, '') / 100)
  41. styT = +document.body.clientHeight * (+styT.replace(/%/g, '') / 100)
  42. } else {
  43. styL = +styL.replace(/px/g, '')
  44. styT = +styT.replace(/px/g, '')
  45. };
  46. document.onmousemove = function (e) {
  47. // 通过事件委托,计算移动的距离
  48. let left = e.clientX - disX
  49. let top = e.clientY - disY
  50. // 边界处理
  51. if (-(left) > minDragDomLeft) {
  52. left = -(minDragDomLeft)
  53. } else if (left > maxDragDomLeft) {
  54. left = maxDragDomLeft
  55. }
  56. if (-(top) > minDragDomTop) {
  57. top = -(minDragDomTop)
  58. } else if (top > maxDragDomTop) {
  59. top = maxDragDomTop
  60. }
  61. // 移动当前元素
  62. dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
  63. // emit onDrag event
  64. vnode.child.$emit('dragDialog')
  65. }
  66. document.onmouseup = function (e) {
  67. document.onmousemove = null
  68. document.onmouseup = null
  69. }
  70. return false
  71. }
  72. }
  73. })
  74. /* binding
  75. value:{
  76. value:boolean是否可拉伸 (必传)
  77. minw:Number最小宽 (必传)
  78. maxw:Number最大宽 (必传)
  79. minh:Number最小高 (必传)
  80. maxh:Number 最大高 (必传)
  81. ratio:Number宽高比 (非必传)
  82. ratio?
  83. minw:Number最小宽 (必传)
  84. maxw:Number最大宽 (必传)
  85. minh:Number最小高 (非必传)
  86. maxh:Number 最大高 (非必传)
  87. } */
  88. Vue.directive('dialogChange', {
  89. bind(el, binding, vnode, oldVnode) {
  90. // 自定义属性,判断是否可拉伸
  91. const bindVal = binding.value
  92. console.log(bindVal, "binding")
  93. if (!bindVal.value) return
  94. const dragDom = el.querySelector('.el-dialog')
  95. // console.log(dragDom,"拖拽属性")
  96. let dragMouse
  97. // 在弹出框的右下角添加可拉伸标志 class='mouse'
  98. for (let i = 0; i < dragDom.childNodes[2].childNodes.length; i++) {
  99. if (dragDom.childNodes[2].childNodes[i].className === 'mouse') {
  100. dragMouse = dragDom.childNodes[2].childNodes[i]
  101. }
  102. }
  103. // 鼠标拖拽
  104. dragMouse.onmousedown = (e) => {
  105. // content区域
  106. const content = dragDom.parentNode.parentNode.parentNode.parentNode.parentNode
  107. console.log(content, "llllllllllllllllllllllllllllllll")
  108. const disX = e.clientX - dragDom.offsetWidth
  109. const disY = e.clientY - dragDom.offsetHeight
  110. console.log(e, disX, disY, "0000")
  111. document.onmousemove = function (e) {
  112. e.preventDefault() // 移动时禁用默认事件
  113. // 通过事件委托,计算移动的距离
  114. let width = e.clientX - disX
  115. let height = e.clientY - disY
  116. console.log(e.clientX, e.clientY, content.offsetWidth, content.offsetHeight, width, height, "sssss")
  117. // 距离底部20px停止拖动
  118. if (e.clientY > content.offsetHeight - 20) {
  119. console.log("不能再拖了")
  120. return
  121. } else {
  122. if (!!bindVal.ratio) {
  123. // 设置比例 宽高等比缩放
  124. if (width < content.offsetWidth && height < content.offsetHeight) {
  125. if (!!bindVal.minw && bindVal.minw < width && !!bindVal.maxw && width < bindVal.maxw) {
  126. dragDom.style.width = `${width}px`
  127. dragDom.style.height = `${width * bindVal.ratio}px`
  128. vnode.child.$emit('dragDialogHeight',width * bindVal.ratio)
  129. }
  130. // dragDom.style.height = `${width*0.6}px`
  131. // dragDom.style.height = `${height}px`
  132. }
  133. } else {
  134. // 不设置比例 宽高随意拖动
  135. if (width > content.offsetWidth && height < content.offsetHeight) {
  136. if (!!bindVal.minh && bindVal.minh < height && !!bindVal.maxh && height < bindVal.maxh) {
  137. dragDom.style.height = `${height}px`
  138. }
  139. } else if (width < content.offsetWidth && height > content.offsetHeight) {
  140. if (!!bindVal.minw && bindVal.minw < width && !!bindVal.maxw && width < bindVal.maxw) {
  141. dragDom.style.width = `${width}px`
  142. }
  143. } else if (width < content.offsetWidth && height < content.offsetHeight) {
  144. if (!!bindVal.minh && bindVal.minh < height && !!bindVal.maxh && height < bindVal.maxh) {
  145. dragDom.style.height = `${height}px`
  146. }
  147. if (!!bindVal.minw && bindVal.minw < width && !!bindVal.maxw && width < bindVal.maxw) {
  148. dragDom.style.width = `${width}px`
  149. }
  150. // dragDom.style.height = `${width*0.6}px`
  151. // dragDom.style.height = `${height}px`
  152. }
  153. }
  154. }
  155. }
  156. document.onmouseup = function (e) {
  157. document.onmousemove = null
  158. document.onmouseup = null
  159. }
  160. return false
  161. }
  162. }
  163. })

2、在main.js中引用

  1. import './components/dialog'

3、在dialog中使用 dialog组件 代码中添加v-if为了让每次弹出框都不继承上一次的改变

  1. <template>
  2. <div class="home">
  3. <el-dialog v-if="dialog.dialogVisible" v-dialogDrag:{dialogDrag}="dialog.dialogDrag" v-dialogChange:{dialogChange}="{value:dialog.dialogChange,maxw:1000,minw:600,ratio:0.6}" ref="dialogWrapper"
  4. :close-on-click-modal="false" :title=dialog.title :visible.sync="dialog.dialogVisible">
  5. <div class="dialog-body">
  6. <SlickList :lockToContainerEdges="false" :useWindowAsScrollContainer="false" :pressDelay="50" v-model="commonsApplication" helperClass="helperClass" class="ul" axis="xy">
  7. <SlickItem v-for="(item,index) in commonsApplication" :index="index" :key="index+'key'" class="li">
  8. <div class="app-border">
  9. <img :src="item.Icon" class="app-icon">
  10. <p>{
  11. {item.name}}</p>
  12. </div>
  13. </SlickItem>
  14. </SlickList>
  15. </div>
  16. <slot slot="footer" class="dialog-footer">
  17. <div class='mouse'>
  18. <!-- 通过此模块拖拽缩放 -->
  19. </div>
  20. </slot>
  21. </el-dialog>
  22. </div>
  23. </template>
  24. <script>
  25. // @ is an alias to /src
  26. // import VueDragResize from "vue-drag-resize";
  27. // import elDragDialog from "@/directive/el-drag-dialog"; // base on element-ui
  28. import { SlickList, SlickItem } from "vue-slicksort";
  29. export default {
  30. name: "Home",
  31. // directives: { elDragDialog },
  32. components: {
  33. SlickList,
  34. SlickItem,
  35. },
  36. data() {
  37. return {
  38. dialog: {
  39. // dialog显示隐藏
  40. dialogVisible: true,
  41. dialogDrag: true, // 可拖拽
  42. dialogChange: true, // 可拉伸
  43. title: "详情",
  44. },
  45. commonsApplication: [
  46. {
  47. name: Math.random().toFixed(2),
  48. Icon: require("../assets/img/nc1.png"),
  49. },
  50. {
  51. name: "2222",
  52. Icon: require("../assets/img/nc1.png"),
  53. },
  54. {
  55. name: "3333",
  56. Icon: require("../assets/img/nc1.png"),
  57. },
  58. {
  59. name: "4444",
  60. Icon: require("../assets/img/nc1.png"),
  61. },
  62. {
  63. name: "5555",
  64. Icon: require("../assets/img/nc1.png"),
  65. },
  66. {
  67. name: "6666",
  68. Icon: require("../assets/img/nc1.png"),
  69. },
  70. ],
  71. };
  72. },
  73. created() {},
  74. mounted() {},
  75. methods: {
  76. handleDrag() {
  77. this.$refs.select.blur();
  78. },
  79. },
  80. };
  81. </script>
  82. <style>
  83. html,
  84. body {
  85. padding: 0;
  86. margin: 0;
  87. }
  88. .home {
  89. width: 100%;
  90. height: 100vh;
  91. background: url("../assets/img/bg.png") no-repeat;
  92. background-size: cover;
  93. background-position: center center;
  94. }
  95. .el-dialog {
  96. /* margin-top: 0 !important;
  97. margin-bottom: 0 !important; */
  98. margin: 0 auto !important;
  99. }
  100. .mouse {
  101. background: grey;
  102. position: absolute;
  103. right: 0;
  104. bottom: 0;
  105. cursor: se-resize;
  106. width: 10px;
  107. height: 10px;
  108. }
  109. .ul {
  110. display: flex;
  111. flex-wrap: wrap;
  112. text-align: center;
  113. padding-bottom: 18px;
  114. }
  115. .li {
  116. width: 25%;
  117. margin: 5px 0px;
  118. padding: 0 4px;
  119. }
  120. .app-border {
  121. border: 1px solid #e2e3e7 !important;
  122. background: #cccccc;
  123. }
  124. .app-border {
  125. z-index: 99999999999999999 !important;
  126. box-sizing: border-box;
  127. border-radius: 8px;
  128. position: relative;
  129. padding: 5px;
  130. }
  131. .app-icon {
  132. width: 48px;
  133. height: 48px;
  134. }
  135. .helperClass{
  136. z-index: 999999999;
  137. }
  138. .helperClass > .app-border {
  139. border: 1px solid #e2e3e7 !important;
  140. }
  141. .helperClass > .app-border {
  142. z-index: 99999999 !important;
  143. box-sizing: border-box;
  144. border-radius: 8px;
  145. position: relative;
  146. padding: 5px;
  147. text-align: center;
  148. }
  149. </style>

拖拽组件 vue-slicksort 使用

第一步:安装

npm或yarn安装都可

  1. npm install vue-slicksort --save
  2. yarn add vue-slicksort

第二步:使用

  1. import { SlickList, SlickItem } from 'vue-slicksort'
  2. export default {
  3. components: {
  4. SlickList,
  5. SlickItem,
  6. },
  7. data () {
  8. return {
  9. commonsApplication: []
  10. }
  11. }
  12. }

页面使用:

  1. <SlickList :lockToContainerEdges="false" :useWindowAsScrollContainer="false" :pressDelay="50" v-model="commonsApplication" helperClass="helperClass" class="ul" axis="xy">
  2. <SlickItem v-for="(item,index) in commonsApplication" :index="index" :key="index+'key'" class="li">
  3. <div class="app-border">
  4. <img :src="item.Icon" class="app-icon">
  5. <p>{
  6. {item.name}}</p>
  7. </div>
  8. </SlickItem>
  9. </SlickList>

页面样式:

  1. <style>
  2. html,
  3. body {
  4. padding: 0;
  5. margin: 0;
  6. }
  7. .home {
  8. width: 100%;
  9. height: 100vh;
  10. background: url("../assets/img/bg.png") no-repeat;
  11. background-size: cover;
  12. background-position: center center;
  13. }
  14. .el-dialog {
  15. /* margin-top: 0 !important;
  16. margin-bottom: 0 !important; */
  17. margin: 0 auto !important;
  18. }
  19. .mouse {
  20. background: grey;
  21. position: absolute;
  22. right: 0;
  23. bottom: 0;
  24. cursor: se-resize;
  25. width: 10px;
  26. height: 10px;
  27. }
  28. .ul {
  29. display: flex;
  30. flex-wrap: wrap;
  31. text-align: center;
  32. padding-bottom: 18px;
  33. }
  34. .li {
  35. width: 25%;
  36. margin: 5px 0px;
  37. padding: 0 4px;
  38. }
  39. .app-border {
  40. border: 1px solid #e2e3e7 !important;
  41. background: #cccccc;
  42. }
  43. .app-border {
  44. z-index: 99999999999999999 !important;
  45. box-sizing: border-box;
  46. border-radius: 8px;
  47. position: relative;
  48. padding: 5px;
  49. }
  50. .app-icon {
  51. width: 48px;
  52. height: 48px;
  53. }
  54. .helperClass {
  55. z-index: 999999999;
  56. }
  57. .helperClass > .app-border {
  58. border: 1px solid #e2e3e7 !important;
  59. }
  60. .helperClass > .app-border {
  61. z-index: 99999999 !important;
  62. box-sizing: border-box;
  63. border-radius: 8px;
  64. position: relative;
  65. padding: 5px;
  66. text-align: center;
  67. }
  68. </style>

拖拽缩放,拖拽排序全部代码:

  1. <template>
  2. <div class="home">
  3. <el-dialog v-if="dialog.dialogVisible" v-dialogDrag:{dialogDrag}="dialog.dialogDrag" v-dialogChange:{dialogChange}="{value:dialog.dialogChange,maxw:1000,minw:600,ratio:0.6}" ref="dialogWrapper"
  4. :close-on-click-modal="false" :title=dialog.title :visible.sync="dialog.dialogVisible">
  5. <div class="dialog-body">
  6. <SlickList :lockToContainerEdges="false" :useWindowAsScrollContainer="false" :pressDelay="50" v-model="commonsApplication" helperClass="helperClass" class="ul" axis="xy">
  7. <SlickItem v-for="(item,index) in commonsApplication" :index="index" :key="index+'key'" class="li">
  8. <div class="app-border">
  9. <img :src="item.Icon" class="app-icon">
  10. <p>{
  11. {item.name}}</p>
  12. </div>
  13. </SlickItem>
  14. </SlickList>
  15. </div>
  16. <slot slot="footer" class="dialog-footer">
  17. <div class='mouse'>
  18. <!-- 通过此模块拖拽缩放 -->
  19. </div>
  20. </slot>
  21. </el-dialog>
  22. </div>
  23. </template>
  24. <script>
  25. // @ is an alias to /src
  26. // import VueDragResize from "vue-drag-resize";
  27. // import elDragDialog from "@/directive/el-drag-dialog"; // base on element-ui
  28. import { SlickList, SlickItem } from "vue-slicksort";
  29. import VueGridLayout from "vue-grid-layout";
  30. export default {
  31. name: "Home",
  32. // directives: { elDragDialog },
  33. components: {
  34. SlickList,
  35. SlickItem,
  36. GridLayout: VueGridLayout.GridLayout,
  37. GridItem: VueGridLayout.GridItem,
  38. },
  39. data() {
  40. return {
  41. dialog: {
  42. // dialog显示隐藏
  43. dialogVisible: true,
  44. dialogDrag: true, // 可拖拽
  45. dialogChange: true, // 可拉伸
  46. title: "详情",
  47. },
  48. commonsApplication: [
  49. {
  50. name: Math.random().toFixed(2),
  51. Icon: require("../assets/img/nc1.png"),
  52. },
  53. {
  54. name: "2222",
  55. Icon: require("../assets/img/nc1.png"),
  56. },
  57. {
  58. name: "3333",
  59. Icon: require("../assets/img/nc1.png"),
  60. },
  61. {
  62. name: "4444",
  63. Icon: require("../assets/img/nc1.png"),
  64. },
  65. {
  66. name: "5555",
  67. Icon: require("../assets/img/nc1.png"),
  68. },
  69. {
  70. name: "6666",
  71. Icon: require("../assets/img/nc1.png"),
  72. },
  73. ],
  74. };
  75. },
  76. created() {},
  77. mounted() {},
  78. methods: {
  79. handleDrag() {
  80. this.$refs.select.blur();
  81. },
  82. },
  83. };
  84. </script>
  85. <style>
  86. html,
  87. body {
  88. padding: 0;
  89. margin: 0;
  90. }
  91. .home {
  92. width: 100%;
  93. height: 100vh;
  94. background: url("../assets/img/bg.png") no-repeat;
  95. background-size: cover;
  96. background-position: center center;
  97. }
  98. .el-dialog {
  99. /* margin-top: 0 !important;
  100. margin-bottom: 0 !important; */
  101. margin: 0 auto !important;
  102. }
  103. .mouse {
  104. background: grey;
  105. position: absolute;
  106. right: 0;
  107. bottom: 0;
  108. cursor: se-resize;
  109. width: 10px;
  110. height: 10px;
  111. }
  112. .ul {
  113. display: flex;
  114. flex-wrap: wrap;
  115. text-align: center;
  116. padding-bottom: 18px;
  117. }
  118. .li {
  119. width: 25%;
  120. margin: 5px 0px;
  121. padding: 0 4px;
  122. }
  123. .app-border {
  124. border: 1px solid #e2e3e7 !important;
  125. background: #cccccc;
  126. }
  127. .app-border {
  128. z-index: 99999999999999999 !important;
  129. box-sizing: border-box;
  130. border-radius: 8px;
  131. position: relative;
  132. padding: 5px;
  133. }
  134. .app-icon {
  135. width: 48px;
  136. height: 48px;
  137. }
  138. .helperClass {
  139. z-index: 999999999;
  140. }
  141. .helperClass > .app-border {
  142. border: 1px solid #e2e3e7 !important;
  143. }
  144. .helperClass > .app-border {
  145. z-index: 99999999 !important;
  146. box-sizing: border-box;
  147. border-radius: 8px;
  148. position: relative;
  149. padding: 5px;
  150. text-align: center;
  151. }
  152. </style>

注意:vue-slicksort拖拽排序 原理是拖过拖拽时在元素外克隆一个节点,实现跟随鼠标,排序的原理是对数组重新排序,此时数组发生改变,如果特殊场景不想改变数组的话,请看下面的方法

稍后更新中………

发表评论

表情:
评论列表 (有 0 条评论,348人围观)

还没有评论,来说两句吧...

相关阅读

    相关 vue3.0组件

    本人开发vue3.0拖拽缩放组件,持续更新中 (最新版本 0.3.0 支持 拖拽 、缩放、旋转、移动辅助线、激活和取消激活、复制粘贴、删除、键盘移动等功能,预计加入撤回操作、多