这是一个基于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.pswp.cn/news/920050.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/920050.shtml
英文地址,请注明出处:http://en.pswp.cn/news/920050.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux基础知识系列】第一百零九篇 - 使用shell的输入与输出重定向

在 Linux 系统中&#xff0c;Shell 是用户与操作系统交互的界面&#xff0c;通过命令行输入命令来执行各种操作。输入与输出重定向是 Shell 编程中非常重要的概念&#xff0c;它允许用户将命令的输出保存到文件中&#xff0c;或者从文件中读取输入&#xff0c;从而实现更灵活的…

Redis面试精讲 Day 30:Redis面试真题解析与答题技巧

【Redis面试精讲 Day 30】Redis面试真题解析与答题技巧 在“Redis面试精讲”系列的第30天&#xff0c;我们迎来收官之作——Redis面试真题解析与答题技巧。这一天的核心目标是&#xff1a;帮助你系统化梳理前29天所学知识&#xff0c;掌握高频面试题的解题思路&#xff0c;提升…

设计模式:单例模式(Singleton Pattern)

文章目录一、单例模式的概念二、单例模式的结构三、常见实现方式3.1 饿汉式单例3.2 懒汉式单例一、单例模式的概念 单例模式&#xff08;Singleton Pattern&#xff09;是一种创建型设计模式&#xff0c;它的核心思想是&#xff1a;保证在一个进程中&#xff0c;某个类仅有一个…

Swift 解法详解 LeetCode 362:敲击计数器,让数据统计更高效

文章目录 摘要 描述 题解答案 题解代码分析 代码讲解 示例测试及结果 时间复杂度 空间复杂度 总结 摘要 “敲击计数器”这道题听上去像个小游戏里的功能,但其实它背后对应的是一个常见的需求:在过去一段时间内统计事件发生的次数。比如网站的访问量统计、API 调用次数限制、…

coze工作流200+源码,涵盖AI文案生成、图像处理、视频生成、自动化脚本等多个领域

AI 博主风哥在github分享了 200 实用生产力coze工作流&#xff0c;涵盖AI文案生成、图像处理、视频生成、自动化脚本等多个领域&#xff0c;导入即用&#xff0c;项目地址https://github.com/Hammer1/cozeworkflows github下载慢也可前往该地址下载https://pan.baidu.com/s/1fC…

AI与SEO关键词协同优化

内容概要 人工智能&#xff08;AI&#xff09;技术的迅猛发展正深刻变革着搜索引擎优化&#xff08;SEO&#xff09;的实践方式&#xff0c;特别是在关键词策略这一核心领域。两者的深度融合&#xff0c;为企业在数字海洋中精准导航提供了前所未有的强大工具。通过AI驱动的智能…

【Unity开发】Unity核心学习(二)

二、动画基础 1、Animation动画窗口 &#xff08;1&#xff09;介绍&#xff08;2&#xff09;Animation窗口功能2、创建编辑动画 面板变化&#xff1a;动画文件界面&#xff1a;3、Animator动画状态机 &#xff08;1&#xff09;有限状态机概念&#xff08;2&#xff09;Anima…

NETSDK1045 当前 .NET SDK 不支持将 .NET 8.0 设置为目标。请将 .NET 5.0 或更低版本设置为目标,或使用支持

C# 项目中的目标框架无法修改并且显示为空 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 NETSDK1045 当前 .NET SDK 不支持将 .NET 8.0 设置为目标。请将 .NET 5.0 或更低版本设置为目标&#xff0c;或使用支持 .NET 8.0 的 .NET SDK 版本。 Padim C:\Program …

MNIST 数据集mnist.npz详解

MNIST 数据集是机器学习领域最著名的数据集之一&#xff0c;全称为"Modified National Institute of Standards and Technology"数据库。它包含了大量手写数字的图像&#xff0c;是入门机器学习和深度学习的经典数据集。1. MNIST 数据集概述 60,000 张训练图像 10,00…

深入理解HTTPS:从概念到实战优化

深入理解HTTPS&#xff1a;从概念到实战优化一&#xff1a;概述二&#xff1a;工作流程三&#xff1a;创建自签名证书四&#xff1a;案例1&#xff09;案例一&#xff1a;HTTPS 搭建2&#xff09;案例二&#xff1a;HTTP/2 搭建3&#xff09;案例三&#xff1a;HTTP 重定向 HTT…

MySQL数据备份与恢复全攻略

一、数据备份与恢复按照备份方式分类&#xff1a;物理备份&#xff0c;直接复制数据库的物理文件&#xff0c;可以直接拷贝和恢复&#xff1b;逻辑备份&#xff0c;通过SQL语句导出数据库结构和数据&#xff0c;可用于不同版本和不同类型的MySQL数据库之间的数据迁移。按照数据…

单机多卡间大张量传输迷惑行为?

老铁们我最近真的好惨&#x1f62d;&#xff0c;一个大模型在单机多卡上运行就是出错&#xff0c;debug看的老眼昏花&#xff0c;最后发现大张量在设备间直接传输会有很发癫的行为&#xff0c;还请大家帮我看看&#x1f647;‍摒弃屎山一样的代码&#xff0c;简单运行下列脚本i…

无法将“pnpm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。

1 问题描述今天使用pnpm安装如下报错&#xff1a;pnpm : 无法将“pnpm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后再试一次。 所在位置 行:1 字符: 1pnpm install~~~~ Categor…

内核编译 day61

二&#xff1a;内核启动 一&#xff1a;流程 sudo vim /etc/network/interfaces sudo chmod 0666 /etc/default/tft...... //修改可读uboot命令&#xff1a; help/&#xff1f; &#xff1a; 帮助手册&#xff0c;列出uboot支持的所有命令 printenv/print 打印环境变量 sete…

【YOLOv5部署至RK3588】模型训练→转换RKNN→开发板部署

已在GitHub开源与本博客同步的YOLOv5_RK3588_object_detect项目&#xff0c;地址&#xff1a;https://github.com/A7bert777/YOLOv5_RK3588_object_detect/tree/main 详细使用教程&#xff0c;可参考README.md或参考本博客第六章 模型部署 文章目录一、项目回顾二、模型选择介绍…

Telematics Control Unit(TCU)的系统化梳理

1、Telematics Control Unit (TCU)概述 TCU中文名为远程信息处理控制单元&#xff0c;很多场合都称为Telematics Box&#xff0c;又叫TBox&#xff0c;顾名思义&#xff0c;一般都为一个独立的盒子&#xff08;如图2、图3所示&#xff09;&#xff0c;负责和云端的远程信息交互…

Appium学习笔记

adb构成client端&#xff0c;在电脑上&#xff0c;负责发送adb命令daemon守护进程&#xff0c;在手机上&#xff0c;负责接收和执行adb命令server端&#xff0c;在电脑上&#xff0c;负责管理client和daemon之间的通信![[Pasted image 20250825201322.png]]包名&#xff0c;对应…

栈指针(Stack Pointer)是什么?

栈指针(Stack Pointer)是什么? 首先,用一个简单易懂的方式解释栈指针(Stack Pointer)。 核心比喻:摞起来的书 想象有一摞书整齐地堆在桌面上: 这摞书就是“栈”(Stack),它是一种后进先出(LIFO) 的数据结构。你只能从最顶部拿走一本书(“弹出”),或者把一本新…

数据结构:红黑树(Red-Black Tree)

目录 从AVL树的“烦恼”说起 如何用“颜色”来定义“大致平衡”&#xff1f;—— 红黑树的五个规则 五个规则如何保证“大致平衡”&#xff1f; 用 C/C 代码定义红黑树的结构 定义颜色和节点结构 定义树的结构和哨兵节点 从AVL树的“烦恼”说起 我们从已经了解的 AVL 树出…

Ubuntu22.04安装VMware Tools

文章目录前言安装open-mv-tools前言 本教程使用的版本是Ubuntu22.04.5&#xff0c;由于虚拟机上面的重新安装VMware Tools是灰的&#xff0c;于是自动下载安装open-mv-tools&#xff0c; 安装open-mv-tools 打开终端&#xff0c;更新一下 sudo apt update这一步可能需要先…