当前位置: 首页 > ai >正文

VUE 的弹出框实现图片预览和视频预览

这是一个基于Vue3封装的媒体预览组件,主要功能包括:

  1. 多格式支持:可同时预览图片和视频
  2. 图片操作功能
    • 缩放(支持滚轮缩放和按钮控制)
    • 旋转(90度增量旋转)
    • 拖拽(仅在放大状态下可用)
  3. 自适应显示:图片自动适应容器大小
  4. 响应式设计:使用Element UI的Dialog作为容器

组件特点:

  • 通过计算属性动态计算图片样式
  • 使用requestAnimationFrame优化拖拽性能
  • 支持图片加载后自动调整方向
  • 提供视频播放控制功能

该组件封装了完整的交互逻辑,可方便地集成到项目中实现媒体预览功能。

下面是实现代码:

<template><el-dialog v-model="visible" width="1184px" class="preview-dialog" close align-center><template v-if="!isVideoPreview" #footer><div class="preview-dialog-footer"><el-button type="text" @click="zoomOut" class="zoom-button"><svgxmlns="http://www.w3.org/2000/svg"width="20"height="20"viewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="2"stroke-linecap="round"stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg></el-button><el-button type="text" @click="zoomIn" class="zoom-button"><svgxmlns="http://www.w3.org/2000/svg"width="20"height="20"viewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="2"stroke-linecap="round"stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg></el-button><el-button type="text" @click="rotateImage(90)" class="rotate-button"><img :src="Rotate" /></el-button></div></template><div class="preview-content" @wheel="handleWheel"><imgv-if="!isVideoPreview":src="previewUrl"@load="onImageLoad":style="imageStyle"ref="previewImage"@mousedown="startDrag"@mousemove="onDrag"@mouseup="endDrag"@mouseleave="endDrag"/><video v-if="isVideoPreview" :src="previewUrl" class="media-video" controls autoplay></video></div></el-dialog>
</template><script setup>import { ref, computed, watch } from 'vue';import Rotate from '@/assets/home/icon/rotate.svg';const props = defineProps({modelValue: Boolean,previewUrl: {type: String,default: ''},isVideoPreview: Boolean});const emit = defineEmits(['update:modelValue']);const visible = ref(props.modelValue);watch(() => props.modelValue,(newVal) => {visible.value = newVal;});watch(visible, (val) => {emit('update:modelValue', val);});const imageRotation = ref(0);const previewImage = ref(null);const dialogWidth = 1184;const dialogHeight = 648;const zoomLevel = ref(1);// 缩放限制const minZoom = 0.1;const maxZoom = 5;// 拖拽相关变量const isDragging = ref(false);const dragStartX = ref(0);const dragStartY = ref(0);const imageStartLeft = ref(0);const imageStartTop = ref(0);const imageLeft = ref(0);const imageTop = ref(0);const rafId = ref(0);const zoomIn = () => {if (zoomLevel.value < maxZoom) {zoomLevel.value = Math.min(zoomLevel.value + 0.1, maxZoom);}};const zoomOut = () => {if (zoomLevel.value > minZoom) {zoomLevel.value = Math.max(zoomLevel.value - 0.1, minZoom);}};const handleWheel = (event) => {event.preventDefault();if (event.deltaY < 0) {zoomIn();} else {zoomOut();}};const startDrag = (event) => {if (zoomLevel.value <= 1) return; // 只有在放大时才能拖拽isDragging.value = true;dragStartX.value = event.clientX;dragStartY.value = event.clientY;imageStartLeft.value = imageLeft.value;imageStartTop.value = imageTop.value;if (previewImage.value) {previewImage.value.style.cursor = 'grabbing';}// 阻止默认行为,防止图片被选中event.preventDefault();};const onDrag = (event) => {if (!isDragging.value || zoomLevel.value <= 1) return;// 使用 requestAnimationFrame 优化性能if (rafId.value) {cancelAnimationFrame(rafId.value);}rafId.value = requestAnimationFrame(() => {const deltaX = event.clientX - dragStartX.value;const deltaY = event.clientY - dragStartY.value;imageLeft.value = imageStartLeft.value + deltaX;imageTop.value = imageStartTop.value + deltaY;rafId.value = 0;});// 阻止默认行为event.preventDefault();};const endDrag = () => {isDragging.value = false;if (rafId.value) {cancelAnimationFrame(rafId.value);rafId.value = 0;}if (previewImage.value) {previewImage.value.style.cursor = 'grab';}};const rotateImage = (degree) => {console.log('翻转', degree, (imageRotation.value + degree) % 360);imageRotation.value += degree;// zoomIn();// 旋转时重置缩放级别以避免布局问题zoomLevel.value = 1;// 重置拖拽位置imageLeft.value = 0;imageTop.value = 0;};const imageDimensions = computed(() => {if (!previewImage.value) return { width: 0, height: 0 };const img = previewImage.value;const naturalWidth = img.naturalWidth;const naturalHeight = img.naturalHeight;const isRotated = imageRotation.value % 180 !== 0;const displayWidth = isRotated ? naturalHeight : naturalWidth;const displayHeight = isRotated ? naturalWidth : naturalHeight;return { width: displayWidth, height: displayHeight };});const imageStyle = computed(() => {if (!previewImage.value) return {};const { width: displayWidth, height: displayHeight } = imageDimensions.value;// 计算基础缩放比例,确保图片适应容器const baseScale = Math.min(dialogWidth / displayWidth, dialogHeight / displayHeight);// 应用用户缩放级别const finalScale = baseScale * zoomLevel.value;// 计算缩放后的尺寸const scaledWidth = displayWidth * finalScale;const scaledHeight = displayHeight * finalScale;// 居中定位const left = (dialogWidth - scaledWidth) / 2 + imageLeft.value;const top = (dialogHeight - scaledHeight) / 2 + imageTop.value;return {position: 'absolute',left: `${left}px`,top: `${top}px`,width: `${scaledWidth}px`,height: `${scaledHeight}px`,transform: `rotate(${imageRotation.value}deg)`,transformOrigin: 'center center',cursor: zoomLevel.value > 1 ? 'grab' : 'default'};});const onImageLoad = () => {// 重置旋转和缩放imageRotation.value = 0;zoomLevel.value = 1;imageLeft.value = 0;imageTop.value = 0;for (let index = 0; index < 4; index++) {console.log('执行第几次', index + 1);rotateImage(90); //执行四次 可以让图片以合适的宽度呈现}// 可选:调试用// console.log('Image loaded:', previewImage.value.naturalWidth, previewImage.value.naturalHeight);};
</script><style lang="scss" scoped>.preview-dialog {:deep(.el-dialog) {height: 648px;display: flex;flex-direction: column;}:deep(.el-dialog__body) {flex: 1;overflow: hidden !important;text-align: center;padding: 0;position: relative;}.preview-dialog-footer {display: flex;justify-content: center;align-items: center;}.rotate-button {font-size: 20px;padding: 10px;}.preview-content {width: 1184px;height: 648px;display: flex;justify-content: center;align-items: center;overflow: hidden;position: relative;img {max-width: none;max-height: none;object-fit: contain;user-select: none;// 添加硬件加速transform: translateZ(0);backface-visibility: hidden;perspective: 1000px;}.media-video {max-width: 100%;max-height: 100%;object-fit: contain;}}}
</style>

http://www.xdnf.cn/news/18642.html

相关文章:

  • C++数据结构之二叉搜索树
  • AEB 强制来临,东软睿驰Next-Cube-Lite有望成为汽车安全普惠“破局器”
  • macbook国内源安装rust
  • 【AGI使用教程】GPT-OSS 本地部署(2)
  • 【AMBA总线互联IP】
  • 自然语言处理——07 BERT、ELMO、GTP系列模型
  • python文件import找不到其它目录的库解决方案
  • Python爬虫第四课:selenium自动化
  • 【云馨AI-大模型】AI热潮持续升温:2025年8月第三周全球动态
  • MySQL数据库精研之旅第十一期:打造高效联合查询的实战宝典(二)
  • 禁用 Nagle 算法(TCP_NODELAY)
  • RuoYi-Vue3项目中Swagger接口测试404,端口问题解析排查
  • 信誉代币的发行和管理机制是怎样的?
  • linux下camera 详细驱动流程 OV02K10为例(chatgpt版本)
  • stm32温控大棚测控系统(CO2+温湿度+光照)+仿真
  • Linux->多线程2
  • 56 C++ 现代C++编程艺术5-万能引用
  • Wagtail CRX 简介
  • 详解无监督学习的核心原理
  • vscode配置remote-ssh进行容器内开发
  • Linux服务测试题(DNS,NFS,DHCP,HTTP)
  • 微服务-21.网关路由-路由属性
  • 零基础玩转STM32:深入理解ARM Cortex-M内核与寄存器编程
  • 采摘机器人设计cad+三维图+设计说明书
  • LangChain RAG系统开发基础学习之文档切分
  • 24.JobGraph 的生成与提交流程解析
  • 阿里发布Qoder:颠覆软件开发体验的AI编程平台
  • [机械结构设计-32]:机械加工中,3D图评审OK,没有问题,后续的主要风险有哪些
  • MRO and mixin in Python Django
  • 单片机外设(七)RTC时间获取