uniapp实现全局拖拽按钮

发布于:2025-03-21 ⋅ 阅读:(28) ⋅ 点赞:(0)

要先引入 “vue3-draggable-resizable”: “^1.6.5”

1.创建DragComponent组件

<template>
	<!-- 抽屉组件 -->
	<div class="drag-container" id="dragBox" :style="{ zIndex: zIndex }">
		<Vue3DraggableResizable  :initW="64" :initH="64" v-model:x="x" v-model:y="y"
			:parent="false" :resizable="false" :draggable="true" class="drag-item" @drag-start="handleDragStart"
			@drag-end="handleDragEnd">
			<div class="home-icon-box" ref="draggableElement">
				<img :src="market_set.image" class="img" alt="" />
			</div>
		</Vue3DraggableResizable>
	</div>
</template>

<script setup>
	import {
		ref,
		onMounted,
		onUnmounted
	} from 'vue';
	import Vue3DraggableResizable from 'vue3-draggable-resizable';
	import 'vue3-draggable-resizable/dist/Vue3DraggableResizable.css';
	import {
		getMarket
	} from "@/api/global.js"

	const popupRef = ref(null)

	const x = ref(20);
	const y = ref();

	const initialY = ref(0); // 基准Y坐标,用于计算滚动偏移
	const isDragging = ref(false); //是否正在拖拽
	const dragStartPosition = ref({
		x: 0,
		y: 0
	}); // 拖拽起始位置
	const isLock = ref(true);
	const isShow = ref(false)

	// 初始化基准Y坐标
	const initBaseY = () => {
		initialY.value = y.value - window.scrollY;

	};

	function isElementInViewport(el) {
		const rect = el.getBoundingClientRect();

		// 获取视口的高度和宽度
		const windowHeight = (window.innerHeight || document.documentElement.clientHeight) - 50;
		const windowWidth = (window.innerWidth || document.documentElement.clientWidth);

		// 判断元素是否在视口内
		const vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
		const horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);

		return (vertInView && horInView);
	}

	// 拖动结束后更新基准Y坐标
	const handleDragEnd = (e) => {
		// initialY.value = y.value - window.scrollY;
		// 判断拖拽还是点击事件
		if (isDragging.value) {
			const dragDistance = Math.sqrt(
				Math.pow(e.x - dragStartPosition.value.x, 2) +
				Math.pow(e.y - dragStartPosition.value.y, 2)
			);
			if (dragDistance < 5) {
				// 如果拖拽距离小于 5px,则认为是点击事件
				console.log('点击事件', e);
				isLock.value = true
			} else {
				console.log('拖拽结束', e);
				isLock.value = false
			}
		}
		isDragging.value = false;
		// 判断悬浮球是靠近左边或右边
		const {
			x: newX,
			y: newY
		} = e; // 假设位置属性是 x 和 
		const page = document.querySelector('#app');
		const iconBox = document.querySelector('.home-icon-box');
		// alert(page.offsetWidth)

		if (newX + (iconBox.offsetWidth / 2) < page.offsetWidth / 2) {
			x.value = 0; // 靠左
		} else {
			x.value = page.offsetWidth - 64; // 靠右
		}


		const myElement = document.querySelector('.home-icon-box');
		if (isElementInViewport(myElement)) {
			console.log('元素在视口内');
		} else {
			console.log('元素不在视口内');
			y.value = window.innerHeight / 2
		}
	};

	const handleDragStart = (e) => {
		// 可根据需要添加拖动开始时的逻辑
		isDragging.value = true;
		dragStartPosition.value = {
			x: e.x,
			y: e.y
		};
	};
	

	// 计算按钮初始y位置
	const containerWidth = ref(0); // 容器宽度
	const width = ref(100); // 组件宽度
	// 计算初始 x 坐标(从右对齐)
	const calculateInitialX = () => {
		x.value = containerWidth.value - width.value;
		y.value = (window.innerHeight || document.documentElement.clientHeight) - 200
	};

	// 生命周期
	onMounted(() => {
		initBaseY();
		const container = document.querySelector('#app');
		if (container) {
			containerWidth.value = container.offsetWidth; //活动区域的宽度范围
			calculateInitialX();
		}

	})
</script>

<style lang="scss" scoped>
	.dragBox {
		width: 960rpx;
		height: 100vh;
		position: fixed;
		top: 0;
		left: 50%;
		z-index: 99;
		transform: translateX(-50%);
		pointer-events: none;
		color: red;
	}

	.home-icon-box {
		position: relative;
		// left: 23px;
		// top: 23px;
		width: 128rpx;
		height: 128rpx;
		display: flex;
		justify-content: center;
		align-items: center;
		z-index: 999;
		color: #fff;
		cursor: grab;
		/* 鼠标样式为抓取 */
		user-select: none;
		/* 防止拖拽时选中文本 */
		animation: pulse 1.5s infinite ease-in-out;
		transition: opacity 0.3s ease, left 0.3s ease, right 0.3s ease, top 0.3s ease;

		/* 按钮动画 */
		@keyframes pulse {
			0% {
				transform: scale(1);
				opacity: 1;
			}

			50% {
				transform: scale(1.2);
				opacity: 0.7;
			}

			100% {
				transform: scale(1);
				opacity: 1;
			}
		}

		/* 进度条容器 */
		.progress-container-icon {
			position: absolute;
			width: 80%;
			height: 10px;
			background: rgba(255, 255, 255, 0.5);
			border-radius: 5px;
			overflow: hidden;
			text-align: center;
			cursor: pointer;
			bottom: -14px;
		}

		/* 进度条 */
		.progress-bar-icon {
			width: 0%;
			height: 100%;
			background: #ff5722;
			border-radius: 5px;
		}

		/* 进度文本 */
		.progress-text-icon {
			position: absolute;
			width: 100%;
			text-align: center;
			top: 50%;
			transform: translateY(-50%);
			color: black;
			font-size: 10px;
			font-weight: bold;
		}

		.img {
			width: 100%;
			height: 100%;
		}
	}

	.drag-item {
		width: 64px;
		height: 64px !important;
		touch-action: none;
		border: none !important;
	}

	.drag-container {
		display: inline-block;
		position: fixed;
		top: 0;
	    z-index: 99;
	}

	.dragBoxPopup {
		position: fixed;
		top: 0;
		left: calc(50% - min(50%, 240px));
		width: min(100%, 480px);
		z-index: 999;
		height: 100%;
		background-color: rgba(0, 0, 0, 0.7);
	}

	}
</style>

2.createDrag.js 将组件dom创建到app根组件下

import { createVNode, render } from 'vue';
import DragComponent from '@/components/DragComponent/DragComponent.vue';

// 修改后的函数,负责渲染 DragComponent 和 DragComponent2
export default function createDrags() {


  // 创建一个组件的 DOM 容器
  const container1 = document.createElement('div');
  document.getElementById('app').appendChild(container1);

  // 创建一个组件的虚拟节点
  const vnode1 = createVNode(DragComponent, {
    // 如果需要,可以在这里传递其他 props 给 DragComponent
  });


  // 渲染一个组件
  render(vnode1, container1);
  const instance1 = vnode1.component?.proxy;

  // 返回两个组件的实例(如果需要后续操作)
  return {
    dragComponentInstance: instance1
  };
}

在App.vue中执行js

	import createDrag from "@/utils/createDrag.js"

    onLaunch: function(options) {
		createDrag()
	}