uniapp 指定弹出位置的弹出层

372 阅读2分钟

封装理由

  • 如下图所示,项目需要点击区域按钮出现一个弹出层,弹出层是在筛选条件下面显示的
  • 大多数UI库的弹出层组件都是从顶部、底部、左侧、右侧、中间弹出,下拉选项组件又是需要占用一整行元素(不然弹出层的宽度就不是屏幕的宽度),这些UI组件都无法实现项目的需求(从指定的元素的指定位置弹出)
  • 这是提供思路,如有不同情况可以根据项目需要修改代码

image.png

组件参数

属性说明示例
popupShow控制弹窗的显示/隐藏(必填)Boolean类型:true
offsetAmount弹窗的偏移量(必填)String类型:'150px'
bgColor弹出层背景颜色字符串:'#fff'
duration弹出层动画持续时间(单位ms)字符串:'500'
isMask是否显示遮罩层boolean:true
maskBgColor遮罩层的背景色字符串:'rgba(0, 0, 0, 0.5)'
zIndex弹出层的层级number:100

封装代码

<template>
	<div
		class="mask"
		v-if="props.isMask && popupShow"
		:style="{
			top: props.offsetAmount,
			backgroundColor: props.maskBgColor,
			'transition-duration': props.duration + 'ms',
			zIndex: props.zIndex,
		}"
	></div>
	<div
		class="box"
		:class="slideDirection"
		:style="{
			top: props.offsetAmount,
			background: props.bgColor,
			'animation-duration': props.duration + 'ms',
			'transition-duration': props.duration + 'ms',
			zIndex: props.zIndex,
		}"
		v-if="popupShow"
	>
		<slot> </slot>
	</div>
</template>

<script setup>
	import { ref, onMounted, computed, watch, getCurrentInstance } from 'vue';
	import { onLoad } from '@dcloudio/uni-app';
	const { proxy } = getCurrentInstance();
	const props = defineProps({
		offsetAmount: {
			type: String,
			required: true,
		},
		bgColor: {
			type: String,
			default: '#fff',
		},
		duration: {
			type: String,
			default: '500',
		},
		isMask: {
			type: Boolean,
			default: true,
		},
		maskBgColor: {
			type: String,
			default: 'rgba(0, 0, 0, 0.5)',
		},
		zIndex: {
			type: Number,
			default: 100,
		},
	});
	const show = defineModel('show', { type: Boolean, required: true });
	const popupShow = ref(false);
	const slideDirection = ref(''); // 滑动方向
	watch(
		() => show.value,
		(val) => {
			if (val) {
				// 设置滑动方向
				slideDirection.value = 'top-show';
				popupShow.value = true;
			} else {
				// 设置滑动方向
				slideDirection.value = 'top-hide';
				setTimeout(() => {
					popupShow.value = false;
				}, props.duration * 1 - 50);
			}
		}
	);

	onMounted(() => {});
	onLoad((res) => {});
</script>

<style lang="scss" scoped>
	.mask {
		position: fixed;
		width: 100%;
		height: 100%;
		transition: all 0.5s ease-in-out;
	}
	.box {
		position: fixed;
		width: 100%;
		height: auto;
		transition: all 0.5s ease-in-out;
	}

	.top-show {
		animation: topShowAnimation 0.5s ease-in-out;
	}
	.top-hide {
		animation: topHideAnimation 0.5s ease-in-out;
	}
	@keyframes topShowAnimation {
		0% {
			transform: translateY(-100%);
			opacity: 0;
		}
		100% {
			transform: translateY(0);
			opacity: 1;
		}
	}
	@keyframes topHideAnimation {
		0% {
			transform: translateY(0);
			opacity: 1;
		}
		100% {
			transform: translateY(-100%);
			opacity: 0;
		}
	}
</style>

弹出层内容提供了插槽支持自定义

页面使用

tip : 页面使用比较麻烦,如有改进的方法欢迎留言

<div class="page-box" :style="pageBoxStyle">
			<div class="father-box">
				<button @click="appointPopupShow = !appointPopupShow">打开指定弹出位置的弹出层</button>
			</div>
			<appointPopup v-model:show="appointPopupShow" :offsetAmount="appointPopupOffsetAmount">
				<div>插槽内容</div>
			</appointPopup>
		</div>
                
import { ref, watch, nextTick, onMounted } from 'vue';

onMounted(() => {
		getAppointPopupOffsetAmount();
	});

const appointPopupOffsetAmount = ref('');
	const appointPopupShow = ref(false);
	const pageBoxStyle = ref({});
	watch(
		() => appointPopupShow.value,
		(val) => {
			if (val) {
				pageBoxStyle.value = {
					height: '100vh',
					overflow: 'hidden',
				};
			} else {
				pageBoxStyle.value = {};
			}
		}
	);
	// 获取appointPopup的出行位置偏移量
	async function getAppointPopupOffsetAmount() {
		await new Promise((resolve) => setTimeout(resolve, 200));
		await nextTick();
		const box = uni.createSelectorQuery().select('father-box');
		box
			.boundingClientRect((data) => {
				appointPopupOffsetAmount.value = data.top + data.height + 'px';
			})
			.exec();
	}

页面使用解释

1.需要在页面的最外层添加一个盒子,盒子使用pageBoxStyle样式

解释:弹出层出现的时候需要让页面无法滚动

2.在 getAppointPopupOffsetAmount 函数中将 .father-box 改为你页面中需要在下方弹窗的元素的元素选择器

解释:因为是在指定元素的下方弹出,所以需要计算弹出的位置:指定元素的top距离+自身高度