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

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>
相关推荐
粥里有勺糖12 分钟前
安利一下最近用的桌面“Agent” | T3 Code
前端·github·ai编程
xy345318 分钟前
Axure 9.0 动态面板基本操作
前端·ui·html·axure·原型·产品设计
不停喝水26 分钟前
【前端转全栈java速通课】 项目实战④7-11节 操作数据库-登录-注册-修改密码-注销-mubatis-plus简化crud-
java·前端·数据库
一水鉴天1 小时前
统一对象拓扑(UOT)设计全链定稿:从三件事分离到三继承 之1 20261010(豆包“查看聊天内容”继续)
前端·css
栖凤1 小时前
Java 的 try-catch 在 Agent 里失效了,我用了 5 个模式才兜住
java·前端·python
粥里有勺糖2 小时前
视野修炼第136期 | 前端小恐龙"Deno"被收购
前端·github·ai编程
修炼的dance2 小时前
页面塞了13.88 MB字体,导出的PDF只有71 KB
前端
qq_369173632 小时前
如何将 AI 生成的 HTML 网页发布成在线链接?不用自己搭建服务器
前端·人工智能·html·效率工具·html 发布
风花一世月2 小时前
⚡ 我把一座含氢能源园区搬进了浏览器(五类能流实时算守恒,在线直接玩)
前端·人工智能
羲云2 小时前
Claude 越界事件之后:Agent 权限设计为什么不能只靠提示词
前端