uniapp 拖拽悬浮按钮,支持翻转屏幕不受影响,脉冲动画
·
代码:
<template>
<view>
<view
class="topBtn"
:class="{ 'disabled': disabled }"
:style="{
transform: `translate(${position.x}px, ${position.y}px)`,
...defineStyle,
}"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
@click="handleClick"
>
<slot></slot>
</view>
</view>
</template>
<script>
export default {
props: {
disabled: {
type: Boolean,
default: false,
},
draggable: {
type: Boolean,
default: true,
},
defineStyle: {
type: Object,
default: () => ({}),
},
},
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,
// 屏幕旋转适配
lastWindowWidth: 0,
lastWindowHeight: 0,
// 性能优化:缓存系统信息 & 按钮尺寸
sysInfo: null,
btnSize: 100, // 必须与 CSS 中 width/height 一致(80px?注意!)
};
},
mounted() {
this.initPosition();
uni.onWindowResize(this.handleWindowResize);
},
beforeDestroy() {
if (this.hideTimer) clearTimeout(this.hideTimer);
uni.offWindowResize(this.handleWindowResize);
},
methods: {
initPosition() {
const sys = uni.getSystemInfoSync();
this.sysInfo = sys;
const margin = 30;
const x = sys.windowWidth - this.btnSize - margin;
const y = sys.windowHeight * 0.85 - this.btnSize;
this.position = {
x: Math.round(Math.max(0, Math.min(sys.windowWidth - this.btnSize, x))),
y: Math.round(Math.max(0, Math.min(sys.windowHeight - this.btnSize, y)))
};
this.lastWindowWidth = sys.windowWidth;
this.lastWindowHeight = sys.windowHeight;
},
handleWindowResize() {
const sys = uni.getSystemInfoSync();
this.sysInfo = sys; // 👈 更新缓存
if (this.lastWindowWidth === 0 || this.lastWindowHeight === 0) {
this.lastWindowWidth = sys.windowWidth;
this.lastWindowHeight = sys.windowHeight;
return;
}
const ratioX = this.position.x / this.lastWindowWidth;
const ratioY = this.position.y / this.lastWindowHeight;
let newX = ratioX * sys.windowWidth;
let newY = ratioY * sys.windowHeight;
newX = Math.max(0, Math.min(sys.windowWidth - this.btnSize, newX));
newY = Math.max(0, Math.min(sys.windowHeight - this.btnSize, newY));
this.position = {
x: Math.round(newX),
y: Math.round(newY)
};
this.lastWindowWidth = sys.windowWidth;
this.lastWindowHeight = sys.windowHeight;
},
onTouchStart(e) {
if (!this.draggable) return;
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;
let newX = this.startPosition.x + deltaX;
let newY = this.startPosition.y + deltaY;
const sys = this.sysInfo;
newX = Math.max(0, Math.min(sys.windowWidth - this.btnSize, newX));
newY = Math.max(0, Math.min(sys.windowHeight - this.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 (distance < this.dragThreshold) {
// this.handleClick();
// }
},
handleClick() {
if (this.disabled) return;
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 {
/* 关键:固定在左上角,由 transform 控制位置 */
position: fixed !important;
top: 0 !important;
left: 0 !important;
z-index: 9999 !important;
width: 80px !important;
height: 80px !important;
font-size: 18px !important;
background: #3c9cff !important;
border-radius: 50% !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
box-shadow: 0 2px 6px rgba(60, 156, 255, 0.6) !important;
cursor: pointer !important;
user-select: none !important;
touch-action: none !important; /* 禁用默认手势 */
}
.topBtn:active{
background: #3c9cffaa !important;
}
/* 脉冲光环 */
.topBtn::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 80px;
height: 80px;
border-radius: 50%;
background: #3c9cff;
opacity: 0.6;
animation: pulseRing 2s infinite ease-out;
z-index: -1;
}
@keyframes pulseRing {
0% {
transform: scale(0.8);
opacity: 0.6;
}
70% {
transform: scale(1.4);
opacity: 0.2;
}
100% {
transform: scale(1.6);
opacity: 0;
}
}
.topBtn.disabled::before {
visibility: hidden;
}
.topBtn.disabled {
background: #78bbff !important;
/* 注意:未禁用 pointer-events,确保 draggable=true 时仍可拖 */
}
</style>
更多推荐
所有评论(0)