| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- <template>
- <view
- class="floating-drag"
- :style="{
- left: buttonPosition.left + 'px',
- right: buttonPosition.right + 'px',
- bottom: buttonPosition.bottom + 'px',
- width: width + 'rpx',
- height: height + 'rpx',
- display: visible ? 'block' : 'none'
- }"
- @touchstart="touchStart"
- @touchmove="touchMove"
- @touchend="touchEnd"
- @click="handleClick"
- v-if="visible"
- >
- <slot>
- <!-- Default content if no slot is provided -->
- <view class="default-btn">
- <text>按钮</text>
- </view>
- </slot>
- </view>
- </template>
- <script>
- export default {
- name: 'FloatingDrag',
- props: {
- // 初始位置
- initialPosition: {
- type: Object,
- default: () => ({
- left: 'auto',
- right: 0,
- bottom: '20%'
- })
- },
- // 组件尺寸
- width: {
- type: [Number, String],
- default: 120
- },
- height: {
- type: [Number, String],
- default: 120
- },
- // 是否显示
- visible: {
- type: Boolean,
- default: true
- },
- // 点击事件
- onClick: {
- type: Function,
- default: null
- }
- },
- data() {
- return {
- // 悬浮按钮位置
- buttonPosition: {
- left: 'auto',
- right: 0,
- bottom: '20%',
- },
- // 触摸开始位置
- startX: 0,
- startY: 0,
- // 屏幕宽度和高度
- screenWidth: 0,
- screenHeight: 0,
- // 初始位置记录,用于计算拖动
- initialLeft: 0,
- initialBottom: 0,
- // 是否正在更新位置,用于防止频繁更新
- isUpdatingPosition: false,
- };
- },
- created() {
- // 使用传入的初始位置
- this.buttonPosition = { ...this.initialPosition };
- },
- watch: {
- initialPosition: {
- handler(newVal) {
- this.buttonPosition = { ...newVal };
- },
- immediate: true,
- },
- },
- mounted() {
- // 获取屏幕宽度和高度
- uni.getSystemInfo({
- success: (res) => {
- this.screenWidth = res.windowWidth;
- this.screenHeight = res.windowHeight;
- },
- });
- },
- methods: {
- // 触摸开始
- touchStart(e) {
- const touch = e.touches[0];
- this.startX = touch.clientX;
- this.startY = touch.clientY;
- // 记录初始位置,用于计算移动距离
- if (this.buttonPosition.right !== 'auto') {
- // 如果是靠右定位,记录当前位置但不立即改变显示位置
- this.initialLeft = this.screenWidth - parseFloat(this.width) / 2;
- } else {
- this.initialLeft = parseFloat(this.buttonPosition.left);
- }
- // 如果bottom是百分比,转换为具体像素值
- if (typeof this.buttonPosition.bottom === 'string' && this.buttonPosition.bottom.includes('%')) {
- const percentage = parseFloat(this.buttonPosition.bottom) / 100;
- this.initialBottom = this.screenHeight * percentage;
- } else {
- this.initialBottom = parseFloat(this.buttonPosition.bottom);
- }
- },
- // 触摸移动
- touchMove(e) {
- // 阻止默认行为,防止页面滚动
- e.preventDefault && e.preventDefault();
- e.stopPropagation && e.stopPropagation();
- const touch = e.touches[0];
- // 计算移动距离
- const deltaX = touch.clientX - this.startX;
- const deltaY = touch.clientY - this.startY;
- // 使用初始位置计算新位置,避免累积误差
- let newLeft = this.initialLeft + deltaX;
- let newBottom = this.initialBottom - deltaY; // 注意:y轴方向是相反的
- // 获取按钮实际宽度(将rpx转换为px)
- const buttonWidthPx = parseFloat(this.width) / 750 * this.screenWidth;
-
- // 确保按钮不超出屏幕边界
- if (newLeft < 0) {
- newLeft = 0;
- } else if (newLeft > this.screenWidth - buttonWidthPx) {
- newLeft = this.screenWidth - buttonWidthPx;
- }
- // 确保按钮不超出屏幕垂直边界
- if (newBottom < 20) {
- newBottom = 20;
- } else if (newBottom > this.screenHeight - buttonWidthPx) {
- newBottom = this.screenHeight - buttonWidthPx;
- }
- // 使用节流方式更新位置,避免过于频繁的更新
- if (!this.isUpdatingPosition) {
- this.isUpdatingPosition = true;
- // 更新位置 - 第一次移动时才真正改变right为auto
- this.buttonPosition = {
- left: newLeft,
- right: 'auto',
- bottom: newBottom,
- };
- // 使用setTimeout代替requestAnimationFrame,在微信小程序中更兼容
- setTimeout(() => {
- this.isUpdatingPosition = false;
- }, 16); // 约等于60fps的刷新率
- }
- },
- // 触摸结束,实现吸附效果
- touchEnd() {
- // 确保不再有待处理的更新
- this.isUpdatingPosition = false;
- // 获取按钮实际宽度(将rpx转换为px)
- const buttonWidthPx = parseFloat(this.width) / 750 * this.screenWidth;
- const buttonCenter = this.buttonPosition.left + buttonWidthPx / 2; // 按钮中心位置
-
- const halfScreen = this.screenWidth / 2;
- // 判断是吸附到左边还是右边
- if (buttonCenter < halfScreen) {
- // 吸附到左边
- this.buttonPosition = {
- left: 0,
- right: 'auto',
- bottom: this.buttonPosition.bottom,
- };
- } else {
- // 吸附到右边
- this.buttonPosition = {
- left: 'auto',
- right: 0,
- bottom: this.buttonPosition.bottom,
- };
- }
-
- // 触发位置变更事件
- this.$emit('position-change', { ...this.buttonPosition });
- },
-
- // 处理点击事件
- handleClick() {
- this.$emit('click');
- if (this.onClick) {
- this.onClick();
- }
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- .floating-drag {
- position: fixed;
- z-index: 999;
- transition: all 0.3s ease;
-
- .default-btn {
- width: 100%;
- height: 100%;
- background-color: #4cd964;
- border-radius: 50%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- border: 4rpx solid #fff;
- box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.2);
-
- text {
- color: #ffffff;
- font-size: 34rpx;
- text-align: center;
- font-weight: 500;
- padding: 0 10rpx;
- line-height: 1.2;
- }
- }
- }
- </style>
|