Vue3 Vite H5 拖拽条组件,可下拉收起/关闭,上拉展开
同样是懒人想找现成的但没找到,参考其他朋友提供的思路现写一个。
有小bug,等领导让改的时候再说(。)
效果
代码
简单的demo。
- DragBar.vue
<template>
<div
class="popup__top"
@touchstart="onTouchstart"
@touchmove="onTouchmove"
@touchend="onTouchend"
>
<div class="popup__top-bar"></div>
</div>
</template>
<script setup>
const emit = defineEmits(["action", "update:modelValue"]);
const props = defineProps({
modelValue: { // 父组件高度
type: Number,
required: false,
default: 100,
},
parentClass: { // 父组件类名
type: String,
required: true,
}
});
// 追踪高度变化
const startY = ref(0); // 记录拖拽的起点位置
const startH = ref(props.modelValue); // 当前状态下的稳定高度
const updateStartH = () => {
nextTick(() => {
startH.value = document.getElementsByClassName(props.parentClass)[0].scrollHeight; // 拿到css设置的稳定高度
emit("update:modelValue", startH.value);
})
}
onMounted(()=>{
updateStartH();
})
watch(
() => {
return {...props.parentClass};
},
(newVal, oldVal) => {
updateStartH();
}
)
// 监听拖拽事件
const onTouchstart = (e) => {
startY.value = e.touches[0].clientY;
}
const onTouchmove = (e) => {
const moveY = e.touches[0].clientY;
const dif = moveY - startY.value
if(props.modelValue) {
emit("update:modelValue", startH.value - dif); // 拖拽过程中动态改变父组件面板高度
}
}
const onTouchend = (e) => {
const moveY = e.changedTouches[0].clientY;
emit("update:modelValue", startH.value); // 拖拽结束,先恢复稳定高度
if (moveY - startY.value >= 50) { // 向父组件传递down或up的动作。判断阈值50可自由修改
emit("action", "down");
} else if (moveY - startY.value <= -50) {
emit("action", "up");
}
}
</script>
<style lang="scss" scoped>
.popup {
&__top {
padding: 0 28px;
}
&__top-bar {
width: 90px;
height: 4px;
background: #b1cef0;
margin: 20px 0 15px 0;
border-radius: 2px;
}
}
</style>
- 父组件DragPanel.vue
<template>
<div v-show="visible" :class="['drag-panel', isHigh ? 'high':'']" :style="{ height: height + 'px' }">
<!-- 拖拽条 -->
<drag-bar v-model="height" :parent-class="className" @action="dragAction"></drag-bar>
<!-- 内容 -->
<div class="drag-content">
<van-search v-model="keyword" placeholder="请输入关键字搜索" shape="round" left-icon="" @search="onSearch">
<template #action>
<div @click="onSearch">搜索</div>
</template>
</van-search>
</div>
<!-- 额外展示的列表(展开/收起) -->
<div class="drag-list" v-show="isHigh">
<ul>
<li class="drag-list-item" v-for="(item, index) in list" :key="index">
<div class="drag-list-item-text">{{ item.name }}</div>
</li>
</ul>
</div>
</div>
</template>
<script setup>
import DragBar from "./DragBar.vue";
// DragBar需要的参数
const height = ref();
const className = ref("drag-panel");
// 面板是否可见
const visible = ref(true); // 标记可见/隐藏状态
const handleVisible = (flag) => {
if(flag !== undefined) {
visible.value = flag;
return;
}
visible.value = !visible.value;
}
// 面板是否展开
const isHigh = ref(false); // 标记展开/收起状态
const list = ref([
{name: "结果1"},
{name: "结果2"},
{name: "结果3"},
{name: "结果4"},
]);
const handleIsHigh = (flag) => {
if(flag){
isHigh.value = true;
className.value = "drag-panel high";
} else {
isHigh.value = false;
height.value = "";
className.value = "drag-panel";
}
}
// 搜索事件
const keyword = ref("");
const onSearch = async () => {
if(!keyword.value) return;
handleIsHigh(true);
}
// 拖拽事件
const dragAction = (actionName) => {
if(isHigh.value && actionName === "down") {
handleIsHigh(false);
}else if(!isHigh.value && actionName === "up") {
handleIsHigh(true);
}else if(!isHigh.value && actionName === "down") {
handleVisible(false);
}
}
</script>
<style lang="scss" scoped>
.drag-panel {
position: absolute;
bottom: 0;
height: 150px; // 必须设置
&.high {
height: 550px; // 必须设置
}
}
</style>
可以结合vant的popup使用,理解起来可能稍微复杂一点。
<van-popup
ref="popupRef"
v-model:show="visible"
position="bottom"
:overlay="false"
:style="{ height: height + 'px' }"
:class="['popup', isHigh ? 'high' : '']"
>
<!-- 拖拽条 -->
<drag-bar v-model="height" :parent-class="className" @action="dragAction"></drag-bar>
<!-- 内容 -->
<div class="popup-content">
...
</div>
</van-popup>