轻松实现拖拽和预投影(附demo代码)

捡到一张csdn发的首页推荐卡,嘻嘻嘻,那肯定是用掉阿

在这里插入图片描述

tips:题外话

我认真看完所有的博客和草稿箱。。。你大爷的。。。居然没有一篇符合标准可以直接用的。。。

2000字,还要最近两个月的博客,不然不新鲜了呗???优惠券设个有效期呗??要求字数2000+,限制最低消费呗???消费者协会没有请你们去喝茶吗??

在这里插入图片描述

这次的需求呢,听起来比较正常,指定目标区域的拖拽 + 预投影

行吧,就这么个小东西,网上绝对有很多成功的案例哈哈哈哈哈哈哈哈哈哈~

小手抄一抄,快乐又逍遥~
在这里插入图片描述

为什么全部都是jquery的版本 一群垃圾 市面上的博客都不与时俱进的吗???

在这里插入图片描述

还得自己来,唉!

先随便写个demo,把几个节点写一下,随便写点样式
在这里插入图片描述
浏览器切换到移动端模式,尝试拖动一下,可以看到startTouch方法已经成功触发。

startTouch方法不生效90%是因为浏览器没有切换到移动端模式

在这里插入图片描述
这个时候立一个标志位,干啥呢,标记已经进入拖拽,不允许重复进入。此举可以有效防止多指操作巴拉巴拉。
在这里插入图片描述
然后就是创建拖拽的那个元素啦~记住要将之前的元素隐藏掉~

这里为什么要在body下创建一个专门用来拖拽的元素呢,是因为直接改变拖动的那个元素,可能会改变页面布局,而且可能这个拖拽元素上层有不是static定位的父元素会导致位置偏差
在这里插入图片描述
然后就是监听页面touchmove,计算鼠标位置什么的我就直接写在代码注释里面了
在这里插入图片描述
需要注意的是,这里在调用预投影方法之前有个节流

因为拖动过程中这个方法会触发很多很多次,而预投影会对dom进行操作,操作频繁会造成渲染压力,所以我们需要加上节流避免可能会出现的卡顿(代码注释里面也写了)
在这里插入图片描述
接下来就是显示预投影部分了,就是遍历所有的目标区域的节点看看拖拽到哪个目标节点下了,然后给个投影

这里可以对目标节点的判断给予优化,记录每次预投影的节点,循环前去掉,循环中找到对应的目标节点之后就跳出来,不用继续循环。

这里判断的是拖拽元素的中心点是否在该目标区域的范围内,如果目标区域有重合,直接取用这里的判断逻辑会有bug,需要将if条件根据实际情况进行修改

当然啦,因为我是写demo嘛~就没有写那么多情况,上面的内容请根据个人情况自行配置哦~
在这里插入图片描述
然后就是拖拽结束处理的部分啦~~
在这里插入图片描述
首先判断有没有进入目标区域,同时还原标志位清空拖拽中间元素和挂载在页面上的监听方法
在这里插入图片描述
最后的检查是否为有效拖拽的方法类同于预投影~
有心的少年可以将重复代码整合,然后精简一下~~
在这里插入图片描述
(最后附上效果图~)

在这里插入图片描述
在这里插入图片描述

代码完整版

(注释我都尽可能写的比较详细,如果觉得不够明白的就写留言~)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div class="drag-part">
    <!-- 拖拽元素区域 -->
    <div class="drag-dom drag1" ontouchstart="startTouch(event, 1)">1</div>
    <div class="drag-dom drag2" ontouchstart="startTouch(event, 2)">2</div>
  </div>
  <div class="goal-part">
    <!-- 目标位置区域 -->
    <div class="goal-dom"></div>
    <div class="goal-dom"></div>
  </div>
  <script type="text/javascript">
  let drag_flag = false
  let obj_height, obj_width
  function startTouch(e, i) {
    if (drag_flag) return
    drag_flag = true
    // 复制节点
    const obj_div = e.target.cloneNode(false)
    // 获取当前节点的位置
    const { top, left, width, height } = e.target.getBoundingClientRect()
    // absolute是相对于最近的且不是static定位的父元素来定位,所以一定要控制好
    // 如果你有拖拽区域限制一定要注意上面一条以及设置好overflow
    obj_div.style.position = 'absolute'
    obj_div.style.top = top + 'px'
    obj_div.style.left = left + 'px'
    obj_div.innerText = e.target.innerText
    document.body.appendChild(obj_div)
    // 记录下块状的宽高,方便之后计算位置
    obj_height = height
    obj_width = width
    // 隐藏原节点
    e.target.classList.add('hidden')
    document.ontouchmove = (e_2) => {
      // 直接取鼠标位置减去一半的宽高,这样拖拽元素一直以鼠标为中心点
      // 和别的处理方式不一样这点是因为我个人有强迫症,这里可以像别人一样前面计算相对位置,也不麻烦
      obj_div.style.top = e_2.targetTouches[0].clientY - height / 2 + 'px'
      obj_div.style.left = e_2.targetTouches[0].clientX - width / 2 + 'px'
      // 因为这个是拖拽过程中是一直在触发,所以加一个节流减少dom操作
      throttle(() => {
        showDragShadow(obj_div, i)
      }, 200)
    }
    document.ontouchend = (e_3) => {
      // 判断是否是一次成功的拖拽
      if (!checkVaildDrag(obj_div)) {
        e.target.classList.remove('hidden')
      }
      // 删掉移动中加入的元素
      obj_div.parentNode.removeChild(obj_div)
      drag_flag = false
      // 清掉数据监听
      document.ontouchmove = null
      document.ontouchend = null
    }
  }
  // 节流
  function throttle(func, delay) {
    if (!this.timer) {
      this.timer = setTimeout(() => {
        // 只有当它处于拖拽过程中才执行方法
        if(drag_flag) func()
        this.timer = null
      }, delay)
    }
  }
  // 显示阴影
  function showDragShadow(obj_div, i) {
    // 拖拽元素的位置
    const {
      left = 0,
      right = 0,
      top = 0,
      bottom = 0
    } = obj_div.getBoundingClientRect()
    const elements = document.getElementsByClassName('goal-dom')
    Array.prototype.forEach.call(elements, (element, index) => {
      // 如果已经有值
      if (element.value) return
      // 目标dom的位置
      const position = element.getBoundingClientRect()
      // 用中心点计算
      // (left + right) / 2表示拖拽元素的x方向中心
      // (top + bottom) / 2表示拖拽元素的y方向中心
      if (
        (left + right) / 2 >= position.left &&
        (left + right) / 2 <= position.right &&
        (top + bottom) / 2 >= position.top &&
        (top + bottom) / 2 <= position.bottom
      ) {
        // 显示阴影
        element.innerText = i
        element.style.opacity = 0.5
        element.shadow = true
      } else if (element.shadow) {
        // 去掉阴影
        element.innerText = ''
        element.style.opacity = 1
        element.shadow = false
      }
    })
  }
  function removeShadow() {
    const elements = document.getElementsByClassName('goal-dom')
    Array.prototype.forEach.call(elements, (element, index) => {
      // 如果已经有值
      if (element.value) return

      if (element.shadow) {
        // 投影样式,按照个人要求修改
        element.innerText = ''
        element.style.opacity = 1
        element.shadow = false
      }
    })
  }
  function checkVaildDrag (obj_div) {
    //去掉所有的阴影
    removeShadow()

    let drag_in_flag = false
    const {
      left = 0,
      right = 0,
      top = 0,
      bottom = 0
    } = obj_div.getBoundingClientRect()
    const elements = document.getElementsByClassName('goal-dom')
    Array.prototype.forEach.call(elements, (element, index) => {
      const position = element.getBoundingClientRect()
      if (
        (left + right) / 2 >= position.left &&
        (left + right) / 2 <= position.right &&
        (top + bottom) / 2 >= position.top &&
        (top + bottom) / 2 <= position.bottom
      ) {
        // 成功拖进去的方法(因为我这里就是个demo,所以就只是单纯把数值写了进去)
        drag_in_flag = true
        element.innerText = obj_div.innerText
        element.value = obj_div.innerText
      }
    })
    return drag_in_flag
  }
  </script>
  <style type="text/css">
    body {
      padding: 40px;
      text-align: center;
    }
    .drag-dom {
      display: inline-block;
      width: 100px;
      height: 100px;
      line-height: 100px;
      font-size: 40px;
      border: 1px solid;
    }
    .goal-part {
      margin-top: 200px;
    }
    .goal-dom {
      display: inline-block;
      width: 110px;
      height: 110px;
      line-height: 110px;
      color: #ffffff;
      font-size: 40px;
      vertical-align: top;
    }
    .goal-dom .drag-dom {
      color: #ffffff;
    }
    .drag-dom.drag1 {
      color: darkorange;
    }
    .drag-dom.drag2 {
      color: darkseagreen;
    }
    .goal-dom:nth-child(1) {
      background-color: darkorange;
    }
    .goal-dom:nth-child(2) {
      background-color: darkseagreen;
    }
    .hidden {
      visibility: hidden;
    }
  </style>
</body>
</html>

很简单的demo~~可以自己手撸玩一下~~~

同样的,留一个思考题,我只做了从拖拽区到目标区(才不是因为懒),可以在上面的基础上做:
1.双向的(即也可以从目标区拽出去到拖拽区)
2.还可以做不限制返回原位的(demo目前是无效区域会回到原状)

可以做的还很多~~

不仅原创,每次为了写博客专门手撸demo我真的是个良心小博主了呜呜呜,说这么多就是为了要个赞,爱你们哟~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值