文档
Fab 浮动按钮
可拖动的悬浮窗按钮
Props
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---|---|---|---|---|
| position | 距离对象 | Boolean | – | {left:0,right:0,top:0,bottom:0} |
| left | 左边距离(无需单位) | Number | – | 0 |
| right | 右边距离(无需单位) | Number/String | – | 0 |
| top | 顶部距离(无需单位) | Number/String | – | 0 |
| bottom | 底部距离(无需单位) | Number/String | – | 0 |
| h-margin | 拖动后的水平方向最小边距(左右边距) | Number/String | – | 10px |
| v-margin | 拖动后的垂直方向最小边距(上下边距) | Number/String | – | 10px |
注:rpx单位对应的是750的屏幕,上述属性取值使用375下的数值,如果是750设计稿在使用时需手动除2传入
浮动按钮位置仅推荐left、top / right 、bottom 两两使用,默认优先使用bottom、right配置
Slots
| name | 说明 |
|---|---|
| default | 悬浮窗默认插槽,用户可自定义内容及大小 |
Events
无
Methods
无
使用示例
- 支持二次封装业务组件以复用
<!-- 375设计稿下位于右下10rpx、100rpx的浮动按钮 -->
<c-fab bottom="100" right='10'>
<view class="float" style="width:100rpx;height:100rpx;border-radius: 99em;background:#fad84c;color:#333;display:flex;align-items:center;justify-content: center;">活动</view>
</c-fab>
完整实现代码
<movable-area>
<movable-view bind:touchend="onTouchend" bind:touchmove="onTouchMove" bind:touchstart="addAnimation" animation="{{animation}}" style="width:{{elementWidth}}px;height:{{elementHeight}}px" x="{{x}}" y="{{y}}" direction="all">
<view class="float-box" >
<slot></slot>
</view>
</movable-view>
</movable-area>
Component({
/**
* 组件的属性列表
*/
properties: {
position: {
type: Object,
value: {
left: 0,
right: 0,
bottom: 0,
top: 0
} // left、top、right、bottom
},
left: {
type: Number,
value: 0
},
right: {
type: Number,
value: 0
},
top: {
type: Number,
value: 0
},
bottom: {
type: Number,
value: 0
},
hMargin: { // 拖动后的水平方向最小边距(左右边距)
type: Number,
value: 10
},
vMargin: { // 拖动后的垂直方向最小边距(上下边距)
type: Number,
value: 10
}
},
data: {
x: 999,
y: 999,
windowWidth: wx.getSystemInfoSync().windowWidth,
windowHeight: wx.getSystemInfoSync().windowHeight,
elementWidth: 0,
elementHeight: 0,
animation: false,
isMoved: false // 是否拖动
},
lifetimes: {
attached() {
// 初始化位置
wx.createSelectorQuery().in(this).select('.float-box').boundingClientRect().exec((res) => {
console.log(233, res);
this.data.elementWidth = res[0].width;
this.data.elementHeight = res[0].height;
if (this.properties.position.left || this.properties.left) {
this.data.x = this.properties.position.left || this.properties.left;
}
if (this.properties.position.right || this.properties.right) {
this.data.x = this.data.windowWidth - this.data.elementWidth - (this.properties.position.right ? this.properties.position.right : this.properties.right);
}
if (this.properties.position.top || this.properties.top) {
this.data.y = this.properties.position.top || this.properties.top;
}
if (this.properties.position.bottom || this.properties.bottom) {
this.data.y = this.data.windowHeight - this.data.elementHeight - (this.properties.position.bottom ? this.properties.position.bottom : this.properties.bottom);
}
this.setData({
elementWidth: this.data.elementWidth,
elementHeight: this.data.elementHeight,
x: this.data.x,
y: this.data.y
});
});
}
},
/**
* 组件的方法列表
*/
methods: {
onTouchend(e) {
console.log(this.data.isMoved, this.data.x, e.changedTouches[0].clientX, this.data.elementWidth);
if (!this.data.isMoved) return;
const currentX = e.changedTouches[0].clientX;
let currentY = e.changedTouches[0].clientY;
if (currentY <= this.properties.vMargin) {
currentY = this.properties.vMargin + this.data.elementHeight / 2;
}
if (currentY >= this.data.windowHeight - this.properties.vMargin) {
currentY = this.data.windowHeight - this.properties.vMargin - this.data.elementHeight / 2;
}
if (currentX + this.data.elementWidth / 2 > this.data.windowWidth / 2) {
this.setData({
x: this.data.windowWidth - this.properties.hMargin - this.data.elementWidth,
y: currentY - this.data.elementHeight / 2
});
}
if (currentX + this.data.elementWidth / 2 <= this.data.windowWidth / 2) {
this.setData({
x: this.properties.hMargin,
y: currentY - this.data.elementHeight / 2
});
}
},
addAnimation() {
this.data.isMoved = false;
if (!this.data.animation) {
this.setData({
animation: true
});
}
},
onTouchMove() {
this.data.isMoved = true;
}
}
});
movable-area {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 50;
movable-view {
pointer-events: auto; //可以点击
}
.float-box {
display: inline-block;
}
}
{
"component": true,
"usingComponents": {}
}

<template> <movable-area class="movable-area"> <movable-view class="movable-view" @touchend="onTouchend" @touchmove="onTouchMove" @touchstart="addAnimation" :animation="animation" :style="`width:${elementWidth}px;height:${elementHeight}px`" :x="x" :y="y" direction="all"> <view id="float-box" class="float-box"> <slot></slot> </view> </movable-view> </movable-area> </template> <script setup> import { ref, getCurrentInstance, onMounted } from 'vue' const props = defineProps({ position: { type: Object, default: { left: 0, right: 0, bottom: 0, top: 0 } }, left: { type: Number, default: 0 }, right: { type: Number, default: 0 }, bottom: { type: Number, default: 0 }, top: { type: Number, default: 0 }, // 拖动后的水平方向最小边距(左右边距) hMargin: { type: Number, default: 10 }, // 拖动后的垂直方向最小边距(上下边距) vMargin: { type: Number, default: 10 } }) const animation = ref(false) const isMoved = ref(false) const elementWidth = ref(0) const elementHeight = ref(0) const x = ref(999) const y = ref(999) const windowWidth = uni.getSystemInfoSync().windowWidth const windowHeight = uni.getSystemInfoSync().windowHeight onMounted(() => { // 初始化位置(组件中查询元素尺寸信息需要按照下面格式书写) uni.createSelectorQuery().in(getCurrentInstance()).select(`#float-box`).boundingClientRect().exec((res) => { console.log(res) elementWidth.value = res[0].width elementHeight.value = res[0].height if (props.position.left || props.left) { x.value = props.position.left || props.left } if (props.position.right || props.right) { x.value = windowWidth - elementWidth.value - (props.position.right ? props.position.right : props.right) } if (props.position.top || props.top) { y.value = props.position.top || props.top } if (props.position.bottom || props.bottom) { y.value = windowHeight - elementHeight.value - (props.position.bottom ? props.position.bottom : props.bottom) } }) }) function onTouchend(e) { if (!isMoved.value) return const currentX = e.changedTouches[0].clientX let currentY = e.changedTouches[0].clientY if (currentY <= props.vMargin) { currentY = props.vMargin + elementHeight.value / 2 } if (currentY >= windowHeight.value - props.vMargin) { currentY = windowHeight.value - props.vMargin - elementHeight.value / 2 } if (currentX + elementWidth.value / 2 > windowWidth.value / 2) { x.value = windowWidth.value - props.hMargin - elementWidth.value y.value = currentY - elementHeight.value / 2 } if (currentX + elementWidth.value / 2 <= windowWidth.value / 2) { x.value = props.hMargin y.value = currentY - elementHeight.value / 2 } } function onTouchMove() { isMoved.value = true } function addAnimation() { isMoved.value = false if (!addAnimation.value) { addAnimation.value = true } } </script> <style lang="scss"> .movable-area { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 9999; } .movable-view { pointer-events: auto; } .float-box { display: inline-block; } </style>写的很好,已经完美使用,给你点个赞
厉害,写的很棒,特地登录点赞点赞!
安卓机初始化位置不正确,一直在右下角
参数设置没反应吗
赞一个
很厉害
写的很好,特地登上来给你点个赞