uniapp小程序实现手动向上滑动窗口

实现下图所示,手动向上滑,窗口滑出:

javascript 复制代码
//  ---data定义
      // 面板当前位置 (px)
      panelPosition: 620,
      // 面板完全展开的位置
      panelUpPosition: 100,
      // 面板收起的位置
      panelDownPosition: 400,
      // 触摸起始点
      startY: 0,
      // 记录滑动过程中的位移
      moveY: 0,
      // 控制遮罩显示
      isPanelVisible: false,
      // 控制scroll-view的滚动位置
      scrollTop: 0,
      // 标记面板当前状态
      panelState: 'down',
      videoContext: null,
javascript 复制代码
// -----布局
<view
      class="detail-mask"
      :class="{ 'mask-show': isPanelVisible }"
      @tap="hideDetailPanel"
    ></view>

    <view
      class="detail-panel"
      :style="{ transform: `translateY(${panelPosition}px)` }"
      @touchstart="onTouchStart"
      @touchmove="onTouchMove"
      @touchend="onTouchEnd"
    >
      <view class="panel-handle">
        <view class="handle-bar"></view>
      </view>

      <scroll-view
        class="panel-content"
        scroll-y="true"
        :scroll-top="scrollTop"
        style="height: 2000rpx"
      >
        <view class="content-section">
          <view class="top-box">
            <view class="section-title">查看数据</view>
            <view class="iconfont icon-zhaoxiangji1" @click="toPhoto"></view>
          </view>

          <view class="realtime-section" v-if="cameraData.length > 0">
            <image
              class="content-image"
              :src="snapshotImg"
              mode=""
              v-if="snapshotImg"
            ></image>

            <view class="sub-title" @click="toViewVideo">
              <view class="iconfont icon-shipinchakan"></view>
            </view>
          </view>
        </view>

        <view class="next-box">
          <view class="content-section">
            <view class="section-box" v-if="cameraData.length > 0">
              <view class="section-title set-top">长势图</view>
              <view class="section-history" @click="viewHistory">历史数据</view>
            </view>

            <view class="comparison-box" v-if="snapshotImgArr.length > 0">
              <view class="item-mid">
                <view class="two-step">
                  <view class="two-line">
                    <view class="step-right">
                      <view class="line-content">
                        <view class="right-a">
                          <view class="r-name set-color">{{ getTime() }}</view>
                        </view>
                        <view class="grouth-box">
                          <view
                            class="img-box"
                            v-for="item in snapshotImgArr"
                            :key="item"
                          >
                            <image
                              style="width: 290rpx; height: 200rpx"
                              :src="item.imagePath"
                              mode=""
                            ></image>
                            <view class="img-text">{{ item.imageDate }}</view>
                          </view>
                        </view>
                        <view class="content">
                          <view class="section">
                            <view class="markdown-container">
                              <uMarkdown
                                :source="textInfo.comparisonResult"
                              ></uMarkdown>
                            </view>
                          </view>

                          <view class="section">
                            <view class="markdown-container">
                              <uMarkdown
                                :source="textInfo.recommendations"
                              ></uMarkdown>
                            </view>
                          </view>
                        </view>
                      </view>
                    </view>
                  </view>
                </view>
              </view>
            </view>
          </view>
        </view>
      </scroll-view>
    </view>
javascript 复制代码
// ------函数方法
// 隐藏详情面板
    hideDetailPanel () {
      this.panelPosition = 620
      this.panelState = 'down'
      // 动画结束后隐藏遮罩
      setTimeout(() => {
        this.isPanelVisible = false
        this.scrollTop = 0
      }, 300)
    },
    handlePhoneTouchStart (e) {
      uni.makePhoneCall({
        phoneNumber: '13122222222'
      })
      e.stopPropagation()
    },
    // 触摸开始
    onTouchStart (e) {
      if (
        e.target.dataset.type === 'phoneBtn' ||
        e.target.dataset.type === 'mointorBtn'
      ) {
        return
      }
      // e.stopPropagation()
      this.startY = e.touches[0].clientY
      this.isPanelVisible = true
      this.$nextTick(() => {
        setTimeout(() => {
          this.panelPosition = this.panelUpPosition
          this.panelState = 'up'
        }, 50)
      })
    },

    // 触摸移动
    onTouchMove (e) {
      if (!this.isPanelVisible) return
      let currentY = e.touches[0].clientY
      this.moveY = currentY - this.startY

      // 阻止向下滚动时页面整体滚动
      if (this.moveY > 0 && this.scrollTop <= 0) {
        e.preventDefault()
      }

      // 计算新的面板位置
      let newPosition = this.panelUpPosition + this.moveY

      // 限制拖动范围:不能向上推得比完全展开更高,向下不能过低
      newPosition = Math.max(this.panelUpPosition - 50, newPosition) // 允许稍微拉过一点
      newPosition = Math.min(newPosition, this.panelDownPosition + 50)
      this.panelPosition = newPosition
    },

    // 触摸结束
    onTouchEnd (e) {
      console.log('onTouchEnd')

      if (!this.isPanelVisible) return
      // e.stopPropagation()
      const moveDistance = this.moveY
      const panelHeight = this.panelDownPosition - this.panelUpPosition

      // 根据滑动距离和速度决定最终状态
      if (moveDistance > panelHeight * 0.3 || moveDistance > 100) {
        // 下滑超过阈值,收起
        this.hideDetailPanel()
      } else {
        // 否则,恢复展开状态
        this.panelPosition = this.panelUpPosition
        this.panelState = 'up'
      }

      // 重置移动距离
      this.moveY = 0
    },
javascript 复制代码
css样式
.detail-mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  visibility: hidden;
  transition: all 0.3s ease;
  z-index: 998;
}
.mask-show {
  opacity: 1;
  visibility: visible;
}

.detail-panel {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: 95vh;
  background: #fff;
  border-top-left-radius: 30rpx;
  border-top-right-radius: 30rpx;
  z-index: 999;
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* 平滑的动画曲线 */
  box-shadow: 0 -5rpx 20rpx rgba(0, 0, 0, 0.15);
  display: flex;
  flex-direction: column;
}
.handle-bar {
  width: 100rpx;
  height: 8rpx;
  background-color: #ddd;
  border-radius: 4rpx;
}
.panel-content {
  flex: 1;
  padding: 0 30rpx;
  // width: 700rpx;
  box-sizing: border-box;
}

.realtime-section {
  position: relative;
}
.sub-title {
  position: absolute;
  bottom: 12rpx;
  left: 0;
  width: 100%;
  height: 68rpx;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  border-radius: 10rpx;
  text-align: right;

  line-height: 68rpx;
  .text-bold {
    font-size: 30rpx;
    margin-left: 16rpx;
    margin-top: 16rpx;
  }
  .icon-shipinchakan {
    margin-right: 40rpx;
  }
}
.icon-fangda04 {
  position: absolute;
  top: 32rpx;
  right: 40rpx;
  color: #fff;
}
.section-box {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.section-title {
  font-size: 32rpx;
  font-weight: bold;
  margin-bottom: 20rpx;
}
.section-history {
  font-size: 28rpx;
  color: #767676;
  // width: 160rpx;
  height: 40rpx;
}
.content-image {
  width: 100%;
  border-radius: 15rpx;
  margin-top: 15rpx;
  height: 385rpx;
}
/* 全屏视频样式 */
.fullscreen-video {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 9999;
  background-color: #000;
}

.video-close-btn {
  position: absolute;
  top: 60rpx;
  right: 30rpx;
  z-index: 10000;
  width: 60rpx;
  height: 60rpx;
  background-color: rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 40rpx;
}
相关推荐
isNotNullX11 小时前
一文讲清:数据清洗、数据中台、数据仓库、数据治理
大数据·网络·数据库·数据分析·1024程序员节
海鸥两三11 小时前
Uni-App(Vue3 + TypeScript)项目结构详解 ------ 以 Lighting-UniApp 为例,提供源代码
vue.js·typescript·uni-app·1024程序员节
艾醒(AiXing-w)11 小时前
探索大语言模型(LLM):MarkDown格式文档的结构化提取全流程
1024程序员节
府学路18号车神11 小时前
【1024节】一年一年又是一年
1024程序员节
阿金要当大魔王~~11 小时前
uniapp img 动态渲染 的几种用法
java·服务器·前端·1024程序员节
布兰妮甜11 小时前
彻底清理:Vue项目中移除static文件夹的完整指南
vue.js·前端框架·static·1024程序员节
知行力11 小时前
百度PaddleOCR-VL:基于0.9B超紧凑视觉语言模型,支持109种语言,性能超越GPT-4o等大模型
人工智能·百度·1024程序员节
sorryhc11 小时前
从renderToString到hydrate,从0~1手写一个SSR框架
1024程序员节
机器学习算法与Python实战11 小时前
DeepSeek 最新开源OCR模型,实测,不如百度Paddle
1024程序员节