uniapp 悬浮按钮支持可拖拽。可移动。

悬浮按钮支持可拖拽。可移动。

javascript 复制代码
<floatBtn :disabled="['CM1100','CM2100','CM1400','CM2400','CM1505','CM2505','CM1502','CM2502','CM3140'].indexOf(formData.oper)==-1">
			<view @click="floatBtnCall" class="clof p5_10">采集<br/>参数</view>
		</floatBtn>

代码:

javascript 复制代码
<template>
	<view>
		<view class="topBtn" :class="{ 'disabled': disabled }" :style="{
				left: `${position.x}px`,
				top: `${position.y}px`
			}" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd">
			<slot></slot>
		</view>
	</view>
</template>

<script>
	export default {
		props: {
			// 控制按钮功能是否可用(点击/置顶)
			disabled: {
				type: Boolean,
				default: false,
			},
			// 控制是否允许拖拽位置(与 disabled 无关)
			draggable: {
				type: Boolean,
				default: true,
			}
		},
		data() {
			return {
				isFirstClick: true,
				hideTimer: null,

				// 拖拽相关
				startTouch: {
					x: 0,
					y: 0
				},
				startPosition: {
					x: 0,
					y: 0
				},
				position: {
					x: 0,
					y: 0
				},
				isDragging: false,
				dragThreshold: 5, // px,小于该值视为点击
			};
		},
		mounted() {
			this.initPosition();
		},
		beforeDestroy() {
			if (this.hideTimer) clearTimeout(this.hideTimer);
		},
		methods: {
			initPosition() {
				const sys = uni.getSystemInfoSync();
				const rpx2px = (rpx) => (rpx * sys.windowWidth) / 750;
				const btnSize = rpx2px(100); // 按钮宽高 100rpx
				const margin = rpx2px(30); // 右边距 30rpx

				// 初始位置:right: 30rpx, bottom: 30vh
				const x = sys.windowWidth - btnSize - margin;
				const y = sys.windowHeight * 0.7 - btnSize; // 30vh from bottom → top = 70vh - height

				this.position = {
					x: Math.round(x),
					y: Math.round(y)
				};
			},

			onTouchStart(e) {
				if (!this.draggable) return; // 👈 仅由 draggable 控制
				const touch = e.touches[0];
				this.startTouch.x = touch.clientX;
				this.startTouch.y = touch.clientY;
				this.startPosition = {
					...this.position
				};
				this.isDragging = false;
			},

			onTouchMove(e) {
				if (!this.draggable) return;
				const touch = e.touches[0];
				const deltaX = touch.clientX - this.startTouch.x;
				const deltaY = touch.clientY - this.startTouch.y;

				const sys = uni.getSystemInfoSync();
				const btnSize = (100 * sys.windowWidth) / 750;

				let newX = this.startPosition.x + deltaX;
				let newY = this.startPosition.y + deltaY;

				// 限制在屏幕可视区内
				newX = Math.max(0, Math.min(sys.windowWidth - btnSize, newX));
				newY = Math.max(0, Math.min(sys.windowHeight - btnSize, newY));

				this.position = {
					x: newX,
					y: newY
				};
				this.isDragging = true;
			},

			onTouchEnd(e) {
				if (!this.draggable) return;

				const touch = e.changedTouches[0];
				const dx = touch.clientX - this.startTouch.x;
				const dy = touch.clientY - this.startTouch.y;
				const distance = Math.sqrt(dx * dx + dy * dy);

				// 如果移动很小,视为点击
				if (!this.isDragging || distance < this.dragThreshold) {
					this.handleClick();
				}
			},

			handleClick() {
				if (this.disabled) return; // 👈 仅由 disabled 控制功能
				this.$emit('click');

				if (this.hideTimer) clearTimeout(this.hideTimer);

				if (this.isFirstClick) {
					this.isFirstClick = false;
					this.hideTimer = setTimeout(() => {
						this.isFirstClick = true;
					}, 5000);
				} else {
					uni.pageScrollTo({
						scrollTop: 0,
						duration: 300,
						success: () => {
							setTimeout(() => {
								this.isFirstClick = true;
							}, 500);
						}
					});
				}
			}
		}
	};
</script>

<style lang="scss" scoped>
	.topBtn {
		position: fixed !important;
		z-index: 9999 !important;
		width: 100rpx !important;
		height: 100rpx !important;
		background: #3c9cff !important;
		border-radius: 50% !important;
		display: flex !important;
		align-items: center !important;
		justify-content: center !important;
		box-shadow: 0 4rpx 12rpx rgba(60, 156, 255, 0.6) !important;
		cursor: pointer !important;
		user-select: none !important;
		touch-action: none !important;
	}

	.topBtn.disabled {
		opacity: 0.5 !important;
		/* 注意:不要加 pointer-events: none,
	   否则 draggable=true 时也无法拖拽! */
	}
</style>
相关推荐
To_OC1 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC1 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic2 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy2 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志2 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble3 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长4 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯4 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜5 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司5 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能