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

// ---data定义// 面板当前位置 (px)panelPosition: 620,// 面板完全展开的位置panelUpPosition: 100,// 面板收起的位置panelDownPosition: 400,// 触摸起始点startY: 0,// 记录滑动过程中的位移moveY: 0,// 控制遮罩显示isPanelVisible: false,// 控制scroll-view的滚动位置scrollTop: 0,// 标记面板当前状态panelState: 'down',videoContext: null,
// -----布局
<viewclass="detail-mask":class="{ 'mask-show': isPanelVisible }"@tap="hideDetailPanel"></view><viewclass="detail-panel":style="{ transform: `translateY(${panelPosition}px)` }"@touchstart="onTouchStart"@touchmove="onTouchMove"@touchend="onTouchEnd"><view class="panel-handle"><view class="handle-bar"></view></view><scroll-viewclass="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"><imageclass="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"><viewclass="img-box"v-for="item in snapshotImgArr":key="item"><imagestyle="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>
// ------函数方法
// 隐藏详情面板hideDetailPanel () {this.panelPosition = 620this.panelState = 'down'// 动画结束后隐藏遮罩setTimeout(() => {this.isPanelVisible = falsethis.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].clientYthis.isPanelVisible = truethis.$nextTick(() => {setTimeout(() => {this.panelPosition = this.panelUpPositionthis.panelState = 'up'}, 50)})},// 触摸移动onTouchMove (e) {if (!this.isPanelVisible) returnlet currentY = e.touches[0].clientYthis.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.moveYconst panelHeight = this.panelDownPosition - this.panelUpPosition// 根据滑动距离和速度决定最终状态if (moveDistance > panelHeight * 0.3 || moveDistance > 100) {// 下滑超过阈值,收起this.hideDetailPanel()} else {// 否则,恢复展开状态this.panelPosition = this.panelUpPositionthis.panelState = 'up'}// 重置移动距离this.moveY = 0},
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;
}