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>
相关推荐
一 乐15 分钟前
智慧党建|党务学习|基于SprinBoot+vue的智慧党建学习平台(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·学习
BBB努力学习程序设计1 小时前
CSS Sprite技术:用“雪碧图”提升网站性能的魔法
前端·html
BBB努力学习程序设计1 小时前
CSS3渐变:用代码描绘色彩的流动之美
前端·html
暴富的Tdy1 小时前
【基于 WangEditor v5 + Vue2 封装 CSDN 风格富文本组件】
vue.js·wangeditor·富文本
冰暮流星1 小时前
css之动画
前端·css
jump6802 小时前
axios
前端
spionbo2 小时前
前端解构赋值避坑指南基础到高阶深度解析技巧
前端
用户4099322502122 小时前
Vue响应式声明的API差异、底层原理与常见陷阱你都搞懂了吗
前端·ai编程·trae
开发者小天2 小时前
React中的componentWillUnmount 使用
前端·javascript·vue.js·react.js
永远的个初学者2 小时前
图片优化 上传图片压缩 npm包支持vue(react)框架开源插件 支持在线与本地
前端·vue.js·react.js