Vue实现悬浮图片弹出大图预览弹窗,弹窗顶部与图片顶部平齐

需求背景
当前项目内某页面左侧展示图片列表,图片列表展示的均为小图。需求希望鼠标移动到对应图片时,右侧出现大图预览弹窗,且该弹窗顶部需与图片顶部平齐。同时弹窗要在页面中展示完全,不能超出窗口高度导致被遮挡
核心实现
1.获取悬浮图片的位置信息
html
<div class="image_item" v-for="(item, index) in imgList" :key="index">
<img
:src="item"
class="img"
@mouseenter="e => handlePreview(e, item)"
@mouseleave="preview.show = false"
/>
</div>
javascript
// 获取当前图片顶部与视口顶部的距离top和底部的距离bottom
const handlePreview = (e, url) => {
const targetRect = e.target.getBoundingClientRect()
preview.bottom = window.innerHeight - targetRect.top
preview.top = targetRect.top
preview.url = url
preview.show = true
}
2.动态计算大图预览弹窗位置
html
<!-- 样式绑定计算属性,根据悬浮图片位置变化 -->
<div class="module_view" v-show="preview.show" :style="previewStyle">
<img :src="preview.url" class="img" />
</div>
javascript
const previewStyle = computed(() => {
// 弹窗实际高度
const previewHeight = 538
const container = listRef.value
// 容器与视口顶部距离
const containerTop = container ? container.getBoundingClientRect().top : 0
const previewTop = preview.top
const previewBottom = preview.bottom
// 弹窗顶部与容器顶部的距离
let top = previewTop - containerTop
let bottom = previewBottom - previewHeight
// 判断弹窗顶部与视口底部的距离是否能容纳整个弹窗
if (bottom < 0) {
// 无法容纳时,减小弹窗顶部距离容器顶部的距离从而抬升弹窗
// 还需判断抬升后弹窗顶部与视口顶部是否仍大于0,否则设置为置顶距离,即负的容器与视口顶部距离
top = previewTop + bottom > 0 ? top + bottom : 0 - containerTop
}
return {
top: top + 'px'
}
})
完整代码
vue
<!-- 实现图片悬浮右侧展开预览大图弹窗功能,弹窗顶部与图片顶部平齐 -->
<template>
<div class="image_view">
<div class="image_list" ref="listRef">
<div class="image_item" v-for="(item, index) in imgList" :key="index">
<img
:src="item"
class="img"
@mouseenter="e => handlePreview(e, item)"
@mouseleave="preview.show = false"/>
</div>
<div class="module_view" v-show="preview.show" :style="previewStyle">
<img :src="preview.url" class="img" />
</div>
</div>
</div>
</template>
<script>
import { defineComponent, ref, reactive, computed } from 'vue'
import { getImg } from '@/utils/imgExample'
export default defineComponent({
setup() {
const imgList = ref(getImg(0, 12))
const listRef = ref(null)
const preview = reactive({
top: 0,
bottom: 0,
url: '',
show: false
})
const previewStyle = computed(() => {
// 弹窗实际高度
const previewHeight = 538
const container = listRef.value
// 容器与视口顶部距离
const containerTop = container ? container.getBoundingClientRect().top : 0
const previewTop = preview.top
const previewBottom = preview.bottom
// 弹窗顶部与容器顶部的距离
let top = previewTop - containerTop
let bottom = previewBottom - previewHeight
// 判断弹窗顶部与视口底部的距离是否能容纳整个弹窗
if (bottom < 0) {
// 无法容纳时,减小弹窗顶部距离容器顶部的距离从而抬升弹窗
// 还需判断抬升后弹窗顶部与视口顶部是否仍大于0,否则设置为置顶距离,即负的容器与视口顶部距离
top = previewTop + bottom > 0 ? top + bottom : 0 - containerTop
}
return {
top: top + 'px'
}
})
// 获取当前图片顶部与视口顶部的距离top和底部的距离bottom
const handlePreview = (e, url) => {
const targetRect = e.target.getBoundingClientRect()
preview.bottom = window.innerHeight - targetRect.top
preview.top = targetRect.top
preview.url = url
preview.show = true
}
return {
listRef,
imgList,
preview,
previewStyle,
handlePreview
}
}
})
</script>
<style lang="less" scoped>
.image_view {
width: 100%;
height: 100%;
.image_list {
position: relative;
width: 404px;
height: 100%;
padding: 12px;
border: 1px solid #ededed;
display: flex;
flex-wrap: wrap;
gap: 8px;
.image_item {
width: 120px;
height: 120px;
border-radius: 4px;
overflow: hidden;
.img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.module_view {
position: absolute;
top: 0;
right: -404px;
width: 400px;
height: 528px;
background: #fff;
box-shadow: 0 4px 10px 2px rgba(0, 0, 0, 0.16);
padding: 8px;
border-radius: 12px;
}
}
}
</style>