基础拖拽功能实现(原生JS)

  1. e.offsetX是鼠标事件对象的一个属性,表示鼠标相对于触发事件的元素左上角的水平坐标
  2. e.clientX是鼠标事件对象的一个属性,表示 鼠标指针相对于浏览器视口左上角的水平坐标
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      div {
        width: 100px;
        height: 100px;
        background-color: red;
        box-shadow: 10px 0 0 0 grey;
        position: fixed;
      }
    </style>
  </head>
  <body>
    <div></div>
    <script>
      let div = document.querySelector("div");
      let flag = false; //引入这个变量,是为了让鼠标松开时小方块不在跟着移动
      div.addEventListener("mousedown", (e) => {
        flag = true;
        console.log(flag)
        let x = e.offsetX;
        let y = e.offsetY;
        document.addEventListener("mousemove", (e) => {
            console.log("移动中")
          //这个值表示窗口的总高度-小方块的高度后的留白区域的高度
          let h = window.innerHeight - div.offsetHeight;
          let w = window.innerWidth - div.offsetWidth;
          let div_left = e.clientX - x;
          let div_top = e.clientY - y;
          //限定边界,元素只能在视口内移动,不会超出边界
          div_top = Math.min(Math.max(0, div_top), h);
          div_left=Math.min(Math.max(0, div_left), w);
          if (flag) {
            div.style.top = div_top + "px";
            div.style.left = div_left + "px";
          }
        });
      });
      div.addEventListener("mouseup", () => {
        flag = false;
        console.log(flag)

      });
    </script>
  </body>
</html>

Vue中封装拖拽组件

在我的项目中,写了一个ai小助手,有一个可拖拽的小图标,点击后是一个小屏幕的聊天窗口,这个小窗口也是可以拖拽的,类似于豆包,点击全屏可展示全屏聊天。因此封装了一个组件。

  1. v-bind=“$attrs” 表示事件透传,会将所有未在组件 props 中定义的属性和事件自动绑定到根元素上。 因为在悬浮小图变种又一个鼠标移入显示一个小提示,鼠标移出不显示提示的小功能,但是小屏聊天框不需要该功能
  2. 添加hasDragged变量来控制拖拽结束后是否触发点击事件

组件代码

<template>
//绑定鼠标按下,松开,点击的事件。
    <div class="draggable-container"
        :style="{ position: 'fixed', top: `${position.y}px`, left: `${position.x}px`, ...containerStyle }"
        @mousedown="handleDragStart" @mouseup="handleDragEnd" @click="handleClick" v-bind="$attrs">
        <slot></slot>
    </div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
const props = defineProps<{
    initialPosition: {
        x: number,
        y: number
    }

    containerStyle: Record<string, string>
    preventClickOnDrag: boolean

}>()
const emit = defineEmits<{ (e: 'click'): void }>()
//判断是否是图拽事件,防止拖拽时触发点击事件
const hasDragged = ref(false)
const handleClick = () => {
    console.log(isDragging.value, hasDragged.value, props.preventClickOnDrag)
    if (!hasDragged.value || !props.preventClickOnDrag) {
        emit('click')
    }
}
//初始位置
const position = ref({ ...props.initialPosition })
//是否在拖拽
const isDragging = ref(false)
//鼠标相对于元素左上角的位置(拖拽偏移量)
const dragOffset = ref({ x: 0, y: 0 })
//初始化位置
const initPosition = () => {
    position.value = { ...props.initialPosition }
}
//开始拖拽
const handleDragStart = (e) => {
    console.log("开始拖拽")
    isDragging.value = true;
    hasDragged.value = false;
    dragOffset.value = {
        x: e.offsetX,
        y: e.offsetY,
    }

}
//正在拖拽,同步元素的位置
const handleDrag = (e) => {
    if (isDragging.value) {
        console.log("正在拖拽")

        hasDragged.value = true;
        console.log(parseInt(props.containerStyle.width))
        position.value = {
            x: Math.min(Math.max(0, e.clientX - dragOffset.value.x), window.innerWidth - parseInt(props.containerStyle.width)),
            y: Math.min(Math.max(0, e.clientY - dragOffset.value.y), window.innerHeight - parseInt(props.containerStyle.height)),
        }
        console.log(position.value.x)
    }
}
//拖拽结束
const handleDragEnd = () => {
    isDragging.value = false;
}
onMounted(() => {
    initPosition()
    document.addEventListener('mousemove', handleDrag)
})
</script>
<style scoped>
.draggable-container {
    z-index: 9999;
}
</style>

引用组件

<!-- 悬浮按钮 -->
        <DraggableContainer v-if="!isOpen" @click="openChat" @mouseenter="showTooltip = true"
            @mouseleave="showTooltip = false" :initialPosition="buttonPosition" :preventClickOnDrag="true"
            :container-style="{ width: '60px', height: '60px' }" class="chat-button">
            <div class="content"></div>
            
        </DraggableContainer>
<!--小屏聊天框-->
        <DraggableContainer v-else-if="!isFullscreen" @click.stop :initialPosition="dialogPosition"
            :preventClickOnDrag="false" :container-style="{ width: '600px', height: '720px' }" class="chat-dialog">
            <!-- 小屏对话框 -->
            <div>
              
            </div>

        </DraggableContainer>


// 拖动功能
const buttonPosition = ref({
    x: window.innerWidth - 90,
    y: window.innerHeight - 90
})
const dialogPosition = ref({
    x: (window.innerWidth - 700) / 2,
    y: (window.innerHeight - 620) / 2
})

// // 初始化按钮位置
const initPosition = () => {
    buttonPosition.value = {
        x: window.innerWidth - 90,
        y: window.innerHeight - 90
    }
    dialogPosition.value = {
        x: (window.innerWidth - 600) / 2,
        y: (window.innerHeight - 720) / 2
    }
}
onMounted(() => {
    console.log(window.innerWidth)
    initPosition()
})
const openChat = () => {
    isOpen.value = true

}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐