uniapp浮动面板-movable-area

优点:拖动响应快

缺点:动画有点奇怪

TypeScript 复制代码
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";

interface Props {
  /**
   * 头部高度(px)
   * 默认 50
   */
  headHeight?: number;
  /**
   * 内容高度(px)
   * 默认 300
   */
  contentHeight?: number;
  /**
   * 锚点,单位px
   * 默认为[50, 200]
   */
  anchors?: number[];
}
const props = withDefaults(defineProps<Props>(), {
  headHeight: 50,
  //   contentHeight: 300,
  //   anchors: () => [50, 200],
  contentHeight: 600,
  anchors: () => [100, 300],
});
// 屏幕显示区域高度
const maxAnchorsHeight = computed(() => {
  return Math.max(...props.anchors);
});

// 拖动区域
const dragAreaHeight = computed(() => {
  return props.contentHeight + props.headHeight + maxAnchorsHeight.value;
});

// 偏移offsetY
const offsetY = ref(0);
// 当前拖动的y
let curY = 0;
// 结束定时器
let dragEndTimer: any = null;

onMounted(() => {
  offsetY.value = getOffsetYByAnchor(Math.min(...props.anchors));
});
const getOffsetYByAnchor = (anchor: number): number => {
  const anchorHeight = maxAnchorsHeight.value;
  return anchorHeight - anchor;
};

// 管理拖动结束
const handleDragEnd = () => {
  console.log("拖动结束");
  dragEndTimer = null;
  if (curY === offsetY.value) return;
  const anchor = findNearestAnchor(getOffsetYByAnchor(curY));
  // 这里的锚点是相对于屏幕的,而offsetY是相对于拖动区域的,需要转换
  const result = getOffsetYByAnchor(anchor);
  offsetY.value = result === offsetY.value ? result - 0.01 : result;
  console.log("最终位置", offsetY.value);
};

// 鼠标松开事件
const handleTouchEnd = () => {
  if (dragEndTimer) clearTimeout(dragEndTimer);
  dragEndTimer = setTimeout(handleDragEnd, 100);
  dragEndTimer = null;
};

// 拖动中事件
const handleTouchMove = (e: any) => {
  curY = e.detail.y;
  if (!dragEndTimer) return;
  // 如果有结束定时器,说明已经拖动结束了,在做惯性动画
  // 在做惯性动画,重新开启定时器,一直没有拖动为直
  clearTimeout(dragEndTimer);
  dragEndTimer = setTimeout(handleDragEnd, 100);
  console.log("重新开启定时器");
};

// 计算最近的锚点(用于可能的自动吸附)
const findNearestAnchor = (currentY: number): number => {
  return props.anchors.reduce((prev, curr) => {
    return Math.abs(curr - currentY) < Math.abs(prev - currentY) ? curr : prev;
  });
};
</script>

<template>
  <view class="floating-panel">
    <movable-area
      class="floating-panel__drag-area"
      :style="{
        bottom: maxAnchorsHeight - dragAreaHeight + 'px',
        height: `${dragAreaHeight}px`,
      }"
    >
      <movable-view
        class="floating-panel__movable-view"
        :style="{
          height: `${props.contentHeight + props.headHeight}px`,
        }"
        :y="offsetY"
        direction="vertical"
        @touchend="handleTouchEnd"
        @change="handleTouchMove"
      >
        <view>
          <slot name="head">
            <view
              class="floating-panel__head"
              :style="{
                height: `${props.headHeight}px`,
              }"
            ></view>
          </slot>
          <view
            class="floating-panel__content"
            :style="{
              height: `${props.contentHeight}px`,
            }"
          >
            <slot></slot>
          </view>
        </view>
      </movable-view>
    </movable-area>
  </view>
</template>

<style lang="scss" scoped>
.floating-panel {
  position: fixed;
  bottom: 0;
  left: 0;
  z-index: 20;
  color: #fff;
  width: 100%;
  .floating-panel__drag-area {
    position: absolute;
    left: 0;
    bottom: 200px;
    width: 100%;
    .floating-panel__movable-view {
      width: 100%;
      background-color: pink;
    }
  }
}
</style>
相关推荐
2501_946244787 分钟前
Flutter & OpenHarmony OA系统设置页面组件开发指南
开发语言·javascript·flutter
cz追天之路28 分钟前
华为机考 ------ 识别有效的IP地址和掩码并进行分类统计
javascript·华为·typescript·node.js·ecmascript·less·css3
进击的野人33 分钟前
Vue Router 深度解析:从基础概念到高级应用实践
前端·vue.js·前端框架
北慕阳34 分钟前
健康管理前端记录
前端
1024小神34 分钟前
cloudflare的worker中的Environment环境变量和不同环境配置
前端
栀秋66642 分钟前
从零开始调用大模型:使用 OpenAI SDK 实现歌词生成,手把手实战指南
前端·llm·openai
l1t42 分钟前
DeepSeek总结的算法 X 与舞蹈链文章
前端·javascript·算法
智航GIS1 小时前
6.2 while循环
java·前端·python
2201_757830871 小时前
AOP核心概念
java·前端·数据库
雪人.1 小时前
JavaWeb经典面试题
java·服务器·前端·java面试题