驾驭拖拽:从原生到vue和react hooks的实现技术探究

拖拽功能在今天的前端开发中变得非常普遍,不论是原生JavaScript还是流行的前端框架如Vue和React,都提供了丰富的拖拽实现方式。本文将带深入探讨拖拽技术的实现原理和不同框架下的实际应用,帮助大家全面掌握拖拽功能的实现方法。

原生

需求1:弹出拖拽框案例

  1. 点击拖拽按钮,显示拖拽框(阻止事件冒泡)
  2. 点击关闭按钮,隐藏拖拽框
  3. 点击页面的其他部分,隐藏拖拽框
  4. 点击拖拽框(阻止事件冒泡)
  5. 点击关闭,隐藏拖拽框
  6. 用户点击esc键之后,关闭拖拽框

需求2:拖拽

  1. 拖拽头部注册鼠标按下事件

  2. document注册鼠标移动事件(注意这里是document

    1. 我们知道鼠标距离屏幕左侧和顶部的距离, 拖拽框跟着鼠标移动,需要用鼠标距离屏幕 - 鼠标距离拖拽框的距离,所以当鼠标按下去的时候,先要计算出鼠标距离拖拽框的距离x,y

    2. 当鼠标拖动的时候,鼠标距离屏幕的距离 - x = drag需要移动的距离

    3. 控制屏幕弹框在可视区域内

      左边:最小0,右边:最大为屏幕宽度 - 盒子宽度 - 关闭弹框一半宽度

      上边:最小关闭弹框一半宽度,下边:屏幕高度 - 盒子宽度

  1. document注册鼠标抬起事件,并且移除document的鼠标移动事件

直接上代码吧

html 复制代码
<!DOCTYPE html>
<html>
​
<head lang="en">
  <meta charset="UTF-8" />
  <title></title>
  <style>
    * {
      padding: 0px;
      margin: 0px;
    }
​
    .drag {
      width: 512px;
      border: #ebebeb solid 1px;
      position: fixed;
      left: 0;
      top: 0;
      box-shadow: 0px 0px 20px #ddd;
      z-index: 2;
      background-color: white;
      display: none;
    }
​
    .drag-title {
      height: 52px;
      line-height: 52px;
      text-align: center;
      font-size: 20px;
      border-bottom: #ebebeb solid 1px;
      cursor: move;
      /* 禁止用户选择文字 */
      user-select: none;
    }
​
    .drag-content {
      padding: 12px;
      text-align: center;
    }
​
    .drag-content p {
      height: 36px;
      line-height: 36px;
    }
​
    .drag-close {
      width: 36px;
      height: 36px;
      line-height: 36px;
      text-align: center;
      border-radius: 50%;
      border: 1px solid #ebebeb;
      position: absolute;
      right: -18px;
      top: -18px;
      background-color: white;
      font-size: 12px;
      cursor: pointer;
    }
​
    .popup-draggable {
      height: 52px;
      line-height: 52px;
      text-align: center;
    }
​
    a {
      text-decoration: none;
      color: #000000;
    }
  </style>
</head>
​
<body>
  <div class="popup-draggable">
    <a id="link" href="javascript:void(0);">点击,弹出拖拽框</a>
  </div>
​
  <div class="drag">
    <div class="drag-title">
      请拖拽我
    </div>
    <div class="drag-content">
      <p>需求:弹出拖拽框案例</p>
      <p>1、点击拖拽按钮,显示拖拽框(阻止事件冒泡)</p>
      <p>2、点击关闭按钮,隐藏拖拽框</p>
      <p>3、点击页面的其他部分,隐藏拖拽框</p>
      <p>4、点击拖拽框(阻止事件冒泡)</p>
      <p>5、点击关闭,隐藏拖拽框</p>
      <p>6、用户点击esc键之后,关闭拖拽框</p>
    </div>
    <div class="drag-close">
      关闭
    </div>
  </div>
​
  <script>
    /*
      需求1:弹出拖拽框案例
      1、点击拖拽按钮,显示拖拽框(阻止事件冒泡)
      2、点击关闭按钮,隐藏拖拽框
      3、点击页面的其他部分,隐藏拖拽框
      4、点击拖拽框(阻止事件冒泡)
      5、点击关闭,隐藏拖拽框
      6、用户点击esc键之后,关闭拖拽框
     */
    const link = document.querySelector('#link')
    const drag = document.querySelector('.drag')
    const close = document.querySelector('.drag-close')
    const dragTitle = document.querySelector('.drag-title')
    link.addEventListener('click', function (e) {
      e.stopPropagation()
      drag.style.display = 'block'
      drag.style.top = (window.innerHeight - drag.offsetHeight) / 2 + 'px'
      drag.style.left = (window.innerWidth - drag.offsetWidth) / 2 + 'px'
    })
    document.addEventListener('click', function (e) {
      drag.style.display = 'none'
    })
    drag.addEventListener('click', function (e) {
      e.stopPropagation()
    })
    close.addEventListener('click', function (e) {
      drag.style.display = 'none'
    })
    document.addEventListener('keyup', function (e) {
      if (e.keyCode !== 27) return
      drag.style.display = 'none'
    })
    /*
    需求2:拖拽
    1、拖拽头部注册鼠标按下事件
    2、document注册鼠标移动事件(注意这里是document)
    */
    let x = 0
    let y = 0
​
    dragTitle.addEventListener('mousedown', dragFn)
​
    function dragFn(e) {
      console.log('按下鼠标', e.pageX, e.pageY)
      // 2.1 我们知道鼠标距离屏幕左侧和顶部的距离,
      // 拖拽框跟着鼠标移动,需要用鼠标距离屏幕 - 鼠标距离拖拽框的距离
      // 所以当鼠标按下去的时候,先要计算出鼠标距离拖拽框的距离
      x = e.pageX - drag.offsetLeft
      y = e.pageY - drag.offsetTop
      // 2.2 给document注册移动事件
      document.addEventListener('mousemove', moveFn)
    }
​
    function moveFn(e) {
      console.log('拖动鼠标')
      // 2.3 鼠标距离屏幕的距离 - x = drag需要移动的距离
      let moveX = e.pageX - x
      let moveY = e.pageY - y
      // 2.4 控制屏幕弹框在可视区域内
      // 左边:最小0,右边:最大为屏幕宽度 - 盒子宽度 - 关闭弹框一半宽度
      // 上边:最小关闭弹框一半宽度,下边:屏幕高度 - 盒子宽度
      const maxWidth = window.innerWidth - drag.offsetWidth - 18
      moveX = Math.min(moveX, maxWidth)
      moveX = Math.max(0, moveX)
      const maxHeight = window.innerHeight - drag.offsetHeight
      moveY = Math.min(moveY, maxHeight)
      moveY = Math.max(18, moveY)
      drag.style.left = moveX + 'px'
      drag.style.top = moveY + 'px'
    }
    // 特别注意:这里是给document添加抬起事件
    document.addEventListener('mouseup', function (e) {
      console.log('鼠标弹起了')
      // 特别注意:移除的是document的mousemove事件
      document.removeEventListener('mousemove', moveFn)
    })
  </script>
</body>
​
</html>

vue hooks

新建hooks文件

src文件下新建hooks文件夹,新建文件useDraggable.ts

typescript 复制代码
const useDraggable = (x = 0, y = 0) => {
  const position = reactive({x, y})
  const box = ref<HTMLDivElement | null>(null)
  const handleMouseDown = (e: MouseEvent) => {
    const startX = e.clientX - position.x
    const startY = e.clientY - position.y

    const handleMouseMove = (e: MouseEvent) => {
      let distanceX = e.clientX - startX
      let distanceY = e.clientY - startY
      const maxX = document.body.clientWidth - box.value!.offsetWidth
      const maxY = document.body.clientHeight - box.value!.offsetHeight

      distanceX = Math.min(maxX, distanceX)
      distanceX = Math.max(0, distanceX)

      distanceY = Math.min(maxY, distanceY)
      distanceY = Math.max(0, distanceY)
      position.x = distanceX
      position.y = distanceY
    }

    const handleMouseUp = () => {
      document.removeEventListener('mousemove', handleMouseMove)
      document.removeEventListener('mouseup', handleMouseUp)
    }

    document.addEventListener('mousemove', handleMouseMove)
    document.addEventListener('mouseup', handleMouseUp)
  }

  return {
    onMouseDown: handleMouseDown,
    box,
    position
  }
}

export default useDraggable

使用方式

vue 复制代码
<template>
  <div class="drag" ref="box" :style="dragPositionStyle" @mousedown="onMouseDown">
  </div>
</template>
<script lang="ts" setup>
import useDraggable from '@/hooks/useDraggable'
const { position, onMouseDown, box} = useDraggable()
const dragPositionStyle = computed(() => ({left: `${position.x}px`, top: `${position.y}px`}))
</script>

react hooks

新建hooks文件

src文件下新建hooks文件夹,新建文件useDraggable.ts

ini 复制代码
import { useRef, useState } from 'react';

const useDraggable = (x = 0, y = 0) => {
  const [position, setPosition] = useState({ x, y });
  const box = useRef(null);
  const handleMouseDown = (e) => {
    const startX = e.clientX - position.x;
    const startY = e.clientY - position.y;

    const handleMouseMove = (e) => {
      let distanceX = e.clientX - startX;
      let distanceY = e.clientY - startY;

      let maxX = document.body.clientWidth - box.current.offsetWidth;
      let maxY = document.body.clientHeight - box.current.offsetHeight;

      distanceX = Math.min(maxX, distanceX);
      distanceX = Math.max(0, distanceX);

      distanceY = Math.min(maxY, distanceY);
      distanceY = Math.max(0, distanceY);
      setPosition({
        x: distanceX,
        y: distanceY,
      });
    };

    const handleMouseUp = () => {
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);
    };

    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);
  };

  return {
    style: {
      position: 'fixed',
      left: position.x + 'px',
      top: position.y + 'px',
      cursor: 'move',
    },
    onMouseDown: handleMouseDown,
    box
  };
};

export default useDraggable;

使用方式

javascript 复制代码
import useDraggable from '@/hooks/useDraggable';
const { style, onMouseDown, box } = useDraggable(window.innerWidth - 160, window.innerHeight / 2 - 100);
<div style={style} onMouseDown={onMouseDown} ref={box}></div>

vuereacthooks略有不同,大家可以在此基础扩展功能和细节!

相关推荐
水w6 小时前
VuePress v2 快速搭建属于自己的个人博客网站
开发语言·前端·vue·vuepress
不爱说话郭德纲6 小时前
理解 Object.create 并正确使用 Object.create
前端·javascript·vue.js·es6·html5
羊小猪~~7 小时前
前端入门一之ES6--递归、浅拷贝与深拷贝、正则表达式、es6、解构赋值、箭头函数、剩余参数、String、Set
开发语言·前端·javascript·css·正则表达式·html·es6
花弄影15217 小时前
vue之axios根据某个接口创建实例,并设置headers和超时时间,捕捉异常
前端·javascript·vue.js
cooldream20098 小时前
使用 Vue 和 Create-Vue 构建工程化前端项目
前端·javascript·vue.js
明里灰9 小时前
从浏览器地址栏输入url到显示页面的步骤
前端·浏览器
软件小伟9 小时前
Vite是什么?Vite如何使用?相比于Vue CLI的区别是什么?(一篇文章帮你搞定!)
前端·vue.js·ecmascript·vite·vue vli
雪碧聊技术9 小时前
03-axios常用的请求方法、axios错误处理
前端·javascript·ajax·axios请求方法·restful风格·axios错误处理·axios请求配置
雾恋10 小时前
不要焦虑,在低迷的环境充实自己;在复苏的环境才能把握住机遇
前端·javascript
花花鱼11 小时前
vscode vite+vue3项目启动调试
前端·javascript·vue.js