通用弹窗展示效果(div+css)

本文实现:

1.弹窗跟随鼠标移动。

2.进行边界展示处理:右侧空间不足时,弹窗显示在鼠标左侧 上方空间不足时,弹窗显示在鼠标下方。

3.窗口 Resize 时重新计算弹窗位置

4.进行鼠标穿透,避免造成弹窗频繁闪烁问题。

效果:

代码:

<template>
	<div
		class="progress-box"
		ref="propgress"
		@mouseenter="handleMouseEnter"
		@mousemove="handleMouseMove"
		@mouseleave="handleMouseLeave"
	>
		<div class="progress-outer">
			<div
				class="progress-inner"
				:style="{
					width: `${progressInfo.percent}%`,
					backgroundColor: color,
					height: height
				}"
			></div>
		</div>
		<div>
			<div class="percent" :style="{ color }">{{ progressInfo.percent }}%</div>
		</div>
		<!-- 鼠标移入时显示弹窗 -->
		<div class="popUp" v-if="showPopUp" :style="{ left: `${popUpX}px`, top: `${popUpY}px`, borderColor: color }">
			<div class="popUp-title">{{ progressInfo.name }}</div>
			<div class="popUp-value">碳排量:{{ progressInfo.value }}</div>
		</div>
	</div>
</template>

<script setup>
	import { ref, onMounted, onUnmounted } from 'vue'

	const props = defineProps({
		progressInfo: {
			type: Object,
			default: () => ({
				name: '',
				value: '',
				number: '',
				percent: 0
			})
		},
		height: {
			type: [Number, String],
			default: '100%'
		},
		color: {
			type: String,
			default: '#3BB0A6'
		}
	})

	const propgress = ref(null)
	const showPopUp = ref(false)
	const popUpX = ref(0)
	const popUpY = ref(0)
	const containerPadding = 10 // 弹窗与容器边缘的间距

	// 鼠标进入事件
	const handleMouseEnter = (e) => {
		showPopUp.value = true
		handleMouseMove(e) // 初始化位置
	}

	// 鼠标移动事件,更新弹窗位置
	const handleMouseMove = (e) => {
		if (!showPopUp.value || !propgress.value) return

		// 获取容器位置和尺寸
		const container = propgress.value.getBoundingClientRect()

		// 获取弹窗尺寸
		const popUp = document.querySelector('.popUp')
		if (!popUp) return

		const popUpWidth = popUp.offsetWidth
		const popUpHeight = popUp.offsetHeight

		// 计算鼠标在容器内的相对位置
		let mouseX = e.clientX - container.left
		let mouseY = e.clientY - container.top

		// 默认显示在鼠标右下方
		let calculatedX = mouseX + 10
		let calculatedY = mouseY + 10

		// 检测是否会超出容器边界并调整
		if (calculatedX + popUpWidth > container.width - containerPadding) {
			// 右侧空间不足,显示在左侧
			calculatedX = Math.max(containerPadding, mouseX - popUpWidth - 10)
		}

		if (calculatedY + popUpHeight > container.height - containerPadding) {
			// 下方空间不足,显示在上方
			calculatedY = Math.max(containerPadding, mouseY - popUpHeight - 10)
		}

		// 更新弹窗位置
		popUpX.value = calculatedX
		popUpY.value = calculatedY
	}

	// 鼠标离开事件
	const handleMouseLeave = () => {
		showPopUp.value = false
	}

	// 窗口大小变化时调整弹窗位置
	onMounted(() => {
		window.addEventListener('resize', handleResize)
	})

	onUnmounted(() => {
		window.removeEventListener('resize', handleResize)
	})

	const handleResize = () => {
		if (showPopUp.value && propgress.value) {
			// 模拟鼠标位置更新弹窗
			const container = propgress.value.getBoundingClientRect()
			handleMouseMove({
				clientX: container.left + popUpX.value,
				clientY: container.top + popUpY.value
			})
		}
	}
</script>

<style scoped lang="less">
	.progress-box {
		display: flex;
		align-items: center;
		color: #fff;
		width: 100%;
		height: 50%;
		position: relative;
		cursor: pointer;
		padding-right: 10px;
	}

	.name {
		width: 100%;
		font-size: 14px;
		color: rgba(255, 255, 255, 0.6);
	}

	.progress-outer {
		width: 100%;
		height: 15px;
		background-image: repeating-linear-gradient(to right, #1a237e, #1e2e50 5px, transparent 4px, transparent 10px);
	}

	.progress-inner {
		height: 100%;
		background: repeating-linear-gradient(to right, #1e2e50, #1e2e50 5px, transparent 5px, transparent 10px);
		transition: width 0.3s ease;
	}

	.percent {
		margin-left: 10px;
		transition: color 0.3s ease;
	}

	.popUp {
		position: absolute;
		background-color: rgba(255, 255, 255);
		padding: 12px 15px;
		border: 1px solid;
		border-radius: 6px;
		color: rgb(53, 53, 53);
		font-size: 14px;
		box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
		pointer-events: none; //鼠标穿透
		z-index: 100;
		white-space: nowrap;
		transition: opacity 0.2s ease; /* 添加淡入淡出效果 */
	}

	.popUp-title {
		margin-bottom: 4px;
		font-weight: bold;
	}
</style>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值