| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- <template>
- <view
- class="floating-drag"
- :style="{
- left: formatCssValue(buttonPosition.left),
- right: formatCssValue(buttonPosition.right),
- bottom: formatCssValue(buttonPosition.bottom),
- width: width + 'rpx',
- height: height + 'rpx',
- display: visible ? 'block' : 'none',
- }"
- @touchstart="touchStart"
- @touchmove="touchMove"
- @touchend="touchEnd"
- @click="handleClick"
- v-if="visible"
- >
- <slot> </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 };
- console.log(newVal,'newVal')
- },
- immediate: true,
- deep: true,
- },
- },
- mounted() {
- // 获取屏幕宽度和高度
- uni.getSystemInfo({
- success: (res) => {
- this.screenWidth = res.windowWidth;
- this.screenHeight = res.windowHeight;
- },
- });
- },
- methods: {
- // 格式化CSS值,只在需要时添加px单位
- formatCssValue(value) {
- // 如果是数字,添加px单位
- if (typeof value === 'number') {
- return value + 'px';
- }
-
- // 如果是字符串
- if (typeof value === 'string') {
- // 如果已经包含单位(px, %, vh, vw, em, rem等)或是auto, inherit等特殊值,直接返回
- if (value === 'auto' ||
- value === 'inherit' ||
- value.includes('%') ||
- value.includes('px') ||
- value.includes('vh') ||
- value.includes('vw') ||
- value.includes('em') ||
- value.includes('rem')) {
- return value;
- }
-
- // 如果是纯数字字符串,添加px单位
- if (!isNaN(Number(value))) {
- return value + 'px';
- }
- }
-
- // 其他情况,返回原值
- return value;
- },
- // 触摸开始
- 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) {
- if(!e) return;
- // 阻止默认行为,防止页面滚动
- 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;
- }
- </style>
|