Bladeren bron

feat: 拖拽效果支持
1. 拖拽指定元素
2. 碰撞检测

kindring 10 maanden geleden
bovenliggende
commit
c874004f33
5 gewijzigde bestanden met toevoegingen van 166 en 54 verwijderingen
  1. 2 0
      src/assets/base.css
  2. 144 34
      src/components/magnetView.vue
  3. 8 0
      src/components/magnets/magnetInfo.ts
  4. 7 2
      src/components/public/vueDrag.vue
  5. 5 18
      src/util/domDrag.ts

+ 2 - 0
src/assets/base.css

@@ -168,3 +168,5 @@
     width: 100%;
     height: 100%;
 }
+
+

+ 144 - 34
src/components/magnetView.vue

@@ -1,17 +1,19 @@
 <script setup lang="ts">
-import {ref, shallowRef} from "vue";
+import {reactive, ref} from "vue";
 import TimeMagnet from "@/components/magnets/timeMagnet.vue";
 import vueDrag, {MoveInfo} from "@/components/public/vueDrag.vue";
 import {Magnet, MagnetEmit, MagnetSize} from "@/types/magnetType.ts";
-import {computeMagnetStyle, comXY, initTimeMagnetInfo} from "@/components/magnets/magnetInfo.ts";
+import {comMaxWidth, computeMagnetStyle, comXY, initTimeMagnetInfo} from "@/components/magnets/magnetInfo.ts";
 
 import {Calendar} from "@/util/time.ts";
-import {CollisionResult, detectCollisionDirection, Rect} from "@/util/domDrag.ts";
+import {CollisionDirection, CollisionResult, detectCollisionDirection, Drag, Rect} from "@/util/domDrag.ts";
 
 const timeMagnetInfo = initTimeMagnetInfo(TimeMagnet)
 
 interface vueMagnet extends Magnet{
-  component: any
+}
+const typeComponent = {
+  [timeMagnetInfo.type]: timeMagnetInfo.component
 }
 const magnetItemInfos: vueMagnet[] = [
   {
@@ -24,7 +26,6 @@ const magnetItemInfos: vueMagnet[] = [
     size: MagnetSize.medium,
     editMode: false,
     selected: false,
-    component: timeMagnetInfo.component
   },
   {
     id: `233`,
@@ -36,7 +37,6 @@ const magnetItemInfos: vueMagnet[] = [
     size: MagnetSize.small,
     editMode: false,
     selected: false,
-    component: timeMagnetInfo.component
   },
   {
     id: `2323`,
@@ -48,11 +48,12 @@ const magnetItemInfos: vueMagnet[] = [
     size: MagnetSize.small,
     editMode: false,
     selected: false,
-    component: timeMagnetInfo.component
   }
 ];
 
-const magnetItems = shallowRef(magnetItemInfos)
+const magnetItems = reactive(magnetItemInfos)
+const magnetEl = ref();
+let maxWidth = 10;
 
 function daySelect(calendar: Calendar){
   console.log(`选择日期 ${calendar.year}年 ${calendar.month}月 ${calendar.day}日`)
@@ -81,34 +82,49 @@ defineProps({
 
 const moveTipStyle = ref()
 
-function moveHandle(magnet: vueMagnet, moveInfo: MoveInfo){
-  // 计算新位置相对于多少值
-  let {x, y} = comXY(moveInfo.left, moveInfo.top)
-  let magnetInfo: vueMagnet = {
-    ...magnet,
-    x: x,
-    y: y
-  }
-  let moveStyle = computeMagnetStyle(magnetInfo)
-  moveTipStyle.value = {
-    ...moveStyle,
-    opacity: `1`
-  }
-  // 判断当前元素是否占用到对应元素
-  // 移动占位元素
-  // console.log(magnetInfo)
-  comMagnets(magnetInfo)
-}
+
+// 定时器
+let collidingTimer: NodeJS.Timeout | null = null;
+let collidingWaitTime = 300
+
 
 // 计算其他元素的位置
 function comMagnets(changeMagnet: vueMagnet){
+  // 挤开当前元素所在位置
+  let collisionMagnets: {
+    magnet: vueMagnet,
+    direction: CollisionDirection
+  }[] = findCollisionMagnet(changeMagnet)
+  // 获取父元素的最大可用宽度
+  let result = [];
+  for (let i = 0; i < collisionMagnets.length; i++)
+  {
+    let item = collisionMagnets[i]
+    squeezeMagnet(changeMagnet, item.magnet , item.direction, maxWidth)
+    // 递归计算该元素的新位置
+    let children = comMagnets(item.magnet)
+    // 子元素移动完成
+    children.forEach(child => {
+      result.push(child)
+    })
+    result.push(item.magnet)
+  }
+  return result
+  // 重新渲染界面
+}
+
+// 寻找已经碰撞的组件
+function findCollisionMagnet(changeMagnet: vueMagnet){
   let changeMagnetRect: Rect = {
     ...changeMagnet
   }
-  // 挤开当前元素所在位置
-  for (let i = 0; i < magnetItems.value.length; i++)
+  let collisionMagnets: {
+    magnet: vueMagnet,
+    direction: CollisionDirection
+  }[] = []
+  for (let i = 0; i < magnetItems.length; i++)
   {
-    let magnet = magnetItems.value[i]
+    let magnet = magnetItems[i]
     if(magnet.id === changeMagnet.id) continue
     // 判断当前元素是否被其他元素占用了
     // 转换为 rect
@@ -118,10 +134,85 @@ function comMagnets(changeMagnet: vueMagnet){
     let collisionResult: CollisionResult = detectCollisionDirection(changeMagnetRect, magnetRect)
     if(collisionResult.colliding)
     {
-      console.log(`改位置已经有元素 方向${collisionResult.direction}`);
-      // console.log(magnet)
+      collisionMagnets.push({
+        magnet,
+        direction: collisionResult.direction
+      })
     }
   }
+  return collisionMagnets
+}
+
+function squeezeMagnet(checkMagnet: vueMagnet, moveMagnet: vueMagnet,  direction: CollisionDirection, _maxWidth: number){
+  let newVal = 0
+  // 移动受阻碍则尝试向下移动
+  if(direction === CollisionDirection.Right )
+  {
+    newVal = checkMagnet.x - moveMagnet.width
+    if(newVal < 0) {
+      return squeezeMagnet(checkMagnet, moveMagnet, CollisionDirection.Down, _maxWidth)
+    }
+    moveMagnet.x = newVal
+  }
+  else if(direction === CollisionDirection.Left)
+  {
+    newVal = checkMagnet.width + checkMagnet.x
+    if(newVal + moveMagnet.width > _maxWidth) {
+      return squeezeMagnet(checkMagnet, moveMagnet, CollisionDirection.Down, _maxWidth)
+    }
+    moveMagnet.x = newVal
+  }
+  else if(direction === CollisionDirection.Down)
+  {
+    newVal = checkMagnet.y - moveMagnet.height
+    if(newVal < 0) {
+      return squeezeMagnet(checkMagnet, moveMagnet, CollisionDirection.Top, _maxWidth)
+    }
+    moveMagnet.y = newVal
+  }
+  else if(direction === CollisionDirection.Top)
+  {
+    newVal = checkMagnet.y + checkMagnet.height
+    moveMagnet.y = newVal
+  }
+}
+
+function moveInitHandle(drag: Drag)
+{
+  // 获取父元素的最大可用宽度
+  maxWidth = comMaxWidth(drag.parent.width) || 10
+  console.log(`maxWidth ${maxWidth}`)
+}
+
+function moveHandle(magnet: vueMagnet, moveInfo: MoveInfo){
+  // 计算新位置相对于多少值
+  let {x, y} = comXY(moveInfo.left, moveInfo.top)
+  let magnetInfo: vueMagnet = {
+    ...magnet,
+    x: x,
+    y: y
+  }
+  magnet.x = magnetInfo.x
+  magnet.y = magnetInfo.y
+  let moveStyle = computeMagnetStyle(magnetInfo)
+  moveTipStyle.value = {
+    ...moveStyle,
+    opacity: `1`
+  }
+  // 判断当前元素是否占用到对应元素
+  // 移动占位元素
+  // console.log(magnetInfo)
+
+  if(collidingTimer) {
+    clearTimeout(collidingTimer);
+  }
+  collidingTimer = setTimeout(()=>{
+    let changeMagnets = comMagnets(magnetInfo)
+    console.log(`更改的元素: ${changeMagnets.length}`)
+    collidingTimer = null
+
+    // 元素全部移动完成
+  }, collidingWaitTime)
 }
 
 function moveEndHandle(magnet: vueMagnet, moveInfo: MoveInfo)
@@ -136,7 +227,7 @@ function moveEndHandle(magnet: vueMagnet, moveInfo: MoveInfo)
 
 <template>
   <!-- 磁帖 布局组件, -->
-  <div class="magnet scroll">
+  <div class="magnet scroll" :ref="magnetEl">
     <!--    磁贴组件布局 -->
     <div class="magnet-move"
          v-show="editMode"
@@ -149,16 +240,24 @@ function moveEndHandle(magnet: vueMagnet, moveInfo: MoveInfo)
        :open-drag="editMode"
        :move-hide="true"
        :y-limit="false"
+              :hide-move="true"
+       @init="moveInitHandle"
        @move="(moveInfo)=>{moveHandle(magnet, moveInfo)}"
        @move-end="(moveInfo)=>{moveEndHandle(magnet, moveInfo)}"
     >
+<!--      组件遮罩-->
       <component
           v-show="!editMode"
-          :is="magnet.component"
+          :is="typeComponent[magnet.type]"
           :size="magnet.size"
           @magnet="eventHandler"
       ></component>
     </vue-drag>
+
+<!--    编辑模式下的按钮控件 -->
+    <div class="edit-control">
+
+    </div>
   </div>
 </template>
 
@@ -176,12 +275,21 @@ function moveEndHandle(magnet: vueMagnet, moveInfo: MoveInfo)
   border-radius: 6px;
   box-shadow: 0 1px 2px #ccc;
   overflow: hidden;
+  transition: all 0.3s;
 }
 
 .magnet-item > * {
   position: relative;
 }
-
+.magnet-item .magnet-mask {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #fff;
+  opacity: 0.5;
+}
 .magnet-item::before {
   content: '';
   position: absolute;
@@ -213,4 +321,6 @@ function moveEndHandle(magnet: vueMagnet, moveInfo: MoveInfo)
 }
 
 
+
+
 </style>

+ 8 - 0
src/components/magnets/magnetInfo.ts

@@ -44,6 +44,8 @@ export function computeMagnetStyle(magnet: Magnet) {
 }
 
 
+
+
 export function computeStyle(w: number, h: number, x: number, y: number, sub: boolean = false) {
     let _w = cellWidth * w + (cellMargin * w - cellMargin);
     let _h = cellWidth * h + (cellMargin * h  - cellMargin);
@@ -72,6 +74,12 @@ export function comXY(left: number, top: number){
     return {x, y}
 }
 
+export function comMaxWidth(width: number){
+    let w = Math.floor(width / (cellWidth + cellMargin));
+    console.log(`width: ${width} w: ${w}`)
+    return w
+}
+
 
 
 

+ 7 - 2
src/components/public/vueDrag.vue

@@ -30,6 +30,7 @@ const props = defineProps({
 })
 
 const emits = defineEmits<{
+  (e: 'init', drag: Drag ): void,
   (e: 'move-start', moveInfo: MoveInfo ): void,
   (e: 'move', moveInfo: MoveInfo ): void,
   (e: 'move-end', moveInfo: MoveInfo ): void,
@@ -47,9 +48,10 @@ function initDrag() {
   drag.on(Drag.Event.moveStart, moveStartHandle)
   drag.on(Drag.Event.move, moveHandle)
   drag.on(Drag.Event.moveEnd, moveEndHandle)
+  emits('init', drag)
 }
 function unDrag(){
-  if (!drag) return console.error('dragElement is null !!!!!!!!!! ')
+  if (!drag) return console.error('drag is null !!!!!!!!!! ')
   drag.off(Drag.Event.moveStart)
   drag.off(Drag.Event.move)
   drag.off(Drag.Event.moveEnd)
@@ -116,9 +118,13 @@ function _setDomStyle(el: HTMLElement, mouseInfo: MouseInfo, dragInfo: ElementIn
   el.style.left = `${elLeft}px`;
   el.style.top = `${elTop}px`;
 
+
   if (isEnd)
   {
     el.style.transition = "all 0.3s";
+  }else{
+    // 移除transition
+    el.style.transition = "none";
   }
 
   let moveInfo = {
@@ -126,7 +132,6 @@ function _setDomStyle(el: HTMLElement, mouseInfo: MouseInfo, dragInfo: ElementIn
     left: elLeft,
     top: elTop
   };
-
   return moveInfo;
 }
 

+ 5 - 18
src/util/domDrag.ts

@@ -42,6 +42,7 @@ function getElementDistanceToViewportEdge(element: HTMLElement): { top: number,
     const rect = element.getBoundingClientRect();
     const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
     const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
+    // 兼容性处理
     const scrollX = window.scrollX || window.pageXOffset || document.documentElement.scrollLeft;
     const scrollY = window.scrollY || window.pageYOffset || document.documentElement.scrollTop;
 
@@ -76,10 +77,10 @@ function comMouseInfo(mouseInfo: MouseInfo, el?: HTMLElement | null): MouseInfo{
     return mouseInfo;
 }
 
-enum CollisionDirection {
+export enum CollisionDirection {
     None = 0,
     Top = 1,
-    Bottom = 2,
+    Down = 2,
     Left = 3,
     Right = 4
 }
@@ -104,7 +105,7 @@ export function detectCollisionDirection(rect1: Rect, rect2: Rect): CollisionRes
         if (horizontalOverlap > verticalOverlap) {
             return {
                 colliding: true,
-                direction: rect1.y < rect2.y ? CollisionDirection.Top : CollisionDirection.Bottom
+                direction: rect1.y < rect2.y ? CollisionDirection.Top : CollisionDirection.Down
             };
         } else {
             return {
@@ -119,30 +120,16 @@ export function detectCollisionDirection(rect1: Rect, rect2: Rect): CollisionRes
 
 
 
-export function isColliding(rect1: Rect, rect2: Rect): boolean {
-    if (rect1.x < rect2.x + rect2.width &&
-        rect1.x + rect1.width > rect2.x &&
-        rect1.y < rect2.y + rect2.height &&
-        rect1.y + rect1.height > rect2.y) {
-        return true;
-    } else {
-        return false;
-    }
-}
 
 export class Drag{
-    constructor(el: HTMLElement, moveWait: number = 60) {
+    constructor(el: HTMLElement) {
         // 绑定事件
         this.el = el;
-        this.moveWait = moveWait;
         this.bindEvent();
     }
     el: HTMLElement;
     isMove: boolean = false;
     // 延迟时间, 同一时间内的合并为同一
-    moveWait: number;
-    // 计时器
-    waitTimer: NodeJS.Timeout | null = null ;
     parent: ElementInfo = {
         el: null,
         left: 0,