vue图片之放大、缩小、1:1、刷新、左切换、全屏、右切换、左旋咋、右旋转、x轴翻转、y轴翻转
先上效果,代码在下面

<template><!-- 图片列表 --><div class="image-list"><img:src="imageSrc"v-for="(imageSrc, index) in images":key="index"@click="openImage(index)"@error="handleImageError(index)"alt="Thumbnail Image"/></div><!-- 点击的图片 --><divv-if="selectedImage"class="enlarged-image-box"@wheel="onZoom"@click="closeImage"><img:src="selectedImage"draggable="true"@dragstart="onDragStart"@dragend="onDragEnd"@load="onImageLoad"@click.stopref="enlargedImageRef":style="{left: `${imageLeft}px`,top: `${imageTop}px`,transform: `translate(-50%, -60%) scale(${scale}) ${isFlippedX ? 'scaleX(-1)' : 'scaleX(1)'} ${isFlippedY ? 'scaleY(-1)' : 'scaleY(1)'} rotate(${rotation}deg)`,}"/></div><!-- 控制按钮 --><div class="control-buttons" v-if="selectedImage" v-show="areControlsVisible"><img:src="buttonSrc"v-for="(buttonSrc, index) in controlButtons":key="index"@click="onControlButtonClick(index)"@error="handleButtonError(index)":class="{ active: activeControlIndex === index }"alt="Control Button"/></div><!-- 中间显示的倍数 --><div class="zoom-percentage" v-if="isZoomVisible">{{ zoomPercentage.toFixed(0) }}%</div><!-- 全屏的背景图片 --><div class="fullscreen-overlay" v-if="isFullscreen"><img :src="selectedImage" class="fullscreen-image" /></div><!-- 点击叉号 --><div v-if="selectedImage" @click="closeImage" class="close-button"><img src="/assets/叉号.svg" class="close-icon" /></div><!-- 最下面显示的图片 --><div class="thumbnail-container" v-if="selectedImage"><img:src="imageSrc"v-for="(imageSrc, index) in images":key="index":style="{ transform: `translateX(${-thumbnailOffsetLeft}px)` }"class="thumbnail"@click="onThumbnailClick(index)":class="{ active: activeThumbnailIndex === index }"/></div>
</template><script setup>
import { ref, computed } from "vue";// 状态变量
const selectedImage = ref(""); // 当前显示的大图
const scale = ref(1); // 缩放比例
const isZoomVisible = ref(false); // 控制倍数显示与隐藏
const zoomTimeout = ref(null); // 定时器 ID
const oneToOneScale = ref(1); // 1:1 缩放比例
const imageLeft = ref(window.innerWidth / 2); // 图片初始 left
const imageTop = ref(window.innerHeight / 2); // 图片初始 top
const startPosition = ref({ x: 0, y: 0 }); // 拖拽开始时的鼠标位置
const isAtOneToOne = ref(false); // 是否处于1:1状态
const initialScale = ref(1); // 初始缩放比例
const lastScale = ref(1); // 上一次的缩放比例
const activeControlIndex = ref(null); // 当前激活的控制按钮索引
const activeThumbnailIndex = ref(null); // 当前激活的缩略图索引
const rotation = ref(0); // 图片旋转角度
const isFullscreen = ref(false); // 全屏遮罩
const areControlsVisible = ref(true); // 控制按钮显示与隐藏
const isFlippedX = ref(false); // 是否水平翻转
const isFlippedY = ref(false); // 是否上下翻转
const thumbnailOffsetLeft = ref(0); // 缩略图左侧的偏移量
const zoomPercentage = computed(() => scale.value * initialScale.value * 100); // 计算属性:百分比显示
const enlargedImageRef = ref(null);// 图片数据
const images = ref(["/assets/tibet-1.jpg","/assets/tibet-2.jpg","/assets/tibet-3.jpg","/assets/tibet-4.jpg","/assets/tibet-5.jpg","/assets/tibet-6.jpg","/assets/tibet-7.jpg","/assets/tibet-8.jpg","/assets/tibet-9.jpg",
]);
// 控制按钮数据
const controlButtons = ref(["/assets/加号.svg", // 加号"/assets/减号.svg", // 减号"/assets/1_1.svg", // 1:1"/assets/刷新.svg", // 刷新"/assets/左箭头.svg", // 左箭头"/assets/播放.svg", // 播放"/assets/右箭头.svg", // 右箭头"/assets/左旋转.svg", // 左旋转"/assets/右旋转.svg", // 右旋转"/assets/左右箭头.svg", // 左右箭头"/assets/上下箭头.svg", // 上下箭头
]);
// 点击关闭
const closeImage = () => {selectedImage.value = "";
};
// 重置所有相关状态的函数
const resetImageState = () => {scale.value = 1;imageLeft.value = window.innerWidth / 2;imageTop.value = window.innerHeight / 2;isAtOneToOne.value = false;rotation.value = 0;isFlippedX.value = false;isFlippedY.value = false;activeControlIndex.value = null;
};
// 图片点击事件
const openImage = (index) => {activeThumbnailIndex.value = index;selectedImage.value = images.value[index];resetImageState(); // 重置所有状态
};
// 图片加载完成事件,用于计算初始缩放比例
const onImageLoad = () => {if (enlargedImageRef.value) {const naturalWidth = enlargedImageRef.value.naturalWidth;const rect = enlargedImageRef.value.getBoundingClientRect();const displayedWidth = rect.width;// 计算初始缩放比例(显示尺寸与自然尺寸的比例)initialScale.value = displayedWidth / naturalWidth;// 初始化缩放比例为1scale.value = 1;// 设置 1:1 缩放比例oneToOneScale.value = 1 / initialScale.value;}
};
// 拖拽开始事件
const onDragStart = (event) => {startPosition.value = { x: event.clientX, y: event.clientY }; // 记录开始时的鼠标位置
};
// 拖拽结束事件
const onDragEnd = (event) => {imageLeft.value += event.clientX - startPosition.value.x; // 更新元素的左偏移量imageTop.value += event.clientY - startPosition.value.y; // 更新元素的上偏移量
};
// 图片缩放处理函数
const onZoom = (event) => {isZoomVisible.value = true;// 重置定时器clearTimeout(zoomTimeout.value);zoomTimeout.value = setTimeout(() => {isZoomVisible.value = false;}, 1000);// 判断滚动方向并设置新的缩放比例if (event.deltaY < 0) {// 向上滚动scale.value += 0.1;} else {// 向下滚动scale.value -= 0.1;scale.value = Math.max(scale.value, 0.3); // 确保最小缩放比例为0.3}
};// 控制按钮点击事件
const onControlButtonClick = (index) => {switch (index) {case 0: // 加号if (scale.value < 3) {// 最大缩放比例为3scale.value += 0.1;isZoomVisible.value = true;}break;case 1: // 减号if (scale.value > 0.5) {// 最小缩放比例为0.5scale.value -= 0.1;scale.value = Math.max(scale.value, 0.1);isZoomVisible.value = true;}break;case 2: // 1:1if (!isAtOneToOne.value) {lastScale.value = scale.value;scale.value = oneToOneScale.value;isAtOneToOne.value = true;} else {scale.value = lastScale.value;isAtOneToOne.value = false;}isZoomVisible.value = true;break;case 3: // 刷新resetImageState(); // 重置所有状态isZoomVisible.value = true;break;case 4: // 左箭头navigateToPreviousImage();break;case 5: // 全屏toggleFullscreen();break;case 6: // 右箭头navigateToNextImage();break;case 7: // 左旋转rotation.value -= 90;break;case 8: // 右旋转rotation.value += 90;break;case 9: // 左右翻转isFlippedX.value = !isFlippedX.value; // 切换翻转状态break;case 10: // 上下翻转isFlippedY.value = !isFlippedY.value; // 切换翻转状态break;default:break;}// 设置当前激活的控制按钮索引activeControlIndex.value = index;// 重置定时器clearTimeout(zoomTimeout.value);zoomTimeout.value = setTimeout(() => {isZoomVisible.value = false;}, 1000);
};// 导航到上一张图片
const navigateToPreviousImage = () => {if (activeThumbnailIndex.value > 0) {activeThumbnailIndex.value -= 1;} else {// 跳转到最后一张图片activeThumbnailIndex.value = images.value.length - 1;}selectImage(activeThumbnailIndex.value);
};// 导航到下一张图片
const navigateToNextImage = () => {if (activeThumbnailIndex.value < images.value.length - 1) {activeThumbnailIndex.value += 1;} else {// 跳转到第一张图片activeThumbnailIndex.value = 0;}selectImage(activeThumbnailIndex.value);
};// 选择图片并更新状态
const selectImage = (index) => {selectedImage.value = images.value[index];activeThumbnailIndex.value = index;resetImageState(); // 重置所有状态
};// 全屏切换函数
const toggleFullscreen = () => {const element = document.documentElement; // 全屏元素可以是 `document.documentElement`,也可以是图片元素等if (!document.fullscreenElement &&!document.webkitFullscreenElement &&!document.mozFullScreenElement &&!document.msFullscreenElement) {// 进入全屏if (element.requestFullscreen) {element.requestFullscreen();} else if (element.webkitRequestFullscreen) {element.webkitRequestFullscreen();} else if (element.mozRequestFullScreen) {element.mozRequestFullScreen();} else if (element.msRequestFullscreen) {element.msRequestFullscreen();}isFullscreen.value = true;areControlsVisible.value = false;} else {// 退出全屏if (document.exitFullscreen) {document.exitFullscreen();} else if (document.webkitExitFullscreen) {document.webkitExitFullscreen();} else if (document.mozCancelFullScreen) {document.mozCancelFullScreen();} else if (document.msExitFullscreen) {document.msExitFullscreen();}isFullscreen.value = false;areControlsVisible.value = true;}
};// 监听全屏变化以确保遮罩层正确隐藏
document.addEventListener("fullscreenchange", () => {if (!document.fullscreenElement) {isFullscreen.value = false; // 确保退出全屏时隐藏遮罩层areControlsVisible.value = true;}
});// 点击缩略图事件
const onThumbnailClick = (index) => {activeThumbnailIndex.value = index;selectedImage.value = images.value[index];const thumbnailWidth = 32; // 图片宽度(30px)加上左右间距(2px)const centerIndex = 4; // 中间显示第5张图片(索引为4)if (index <= centerIndex) {thumbnailOffsetLeft.value = (centerIndex - index) * thumbnailWidth;} else {// 点击右边的图片,调整右边距,清零左边距thumbnailOffsetLeft.value = (centerIndex - index) * thumbnailWidth;}resetImageState(); // 重置所有状态
};// 错误处理函数:图片加载失败
const handleImageError = (index) => {console.error(`图片加载失败: ${images.value[index]}`);// 可选:设置为占位图images.value[index] = "/assets/placeholder.png";
};// 错误处理函数:控制按钮图片加载失败
const handleButtonError = (index) => {console.error(`按钮图片加载失败: ${controlButtons.value[index]}`);// 可选:设置为占位图controlButtons.value[index] = "/assets/button-placeholder.png";
};
</script><style scoped>
/* 图片列表 */
.image-list {width: 540px;position: absolute;left: 50%;top: 50%;transform: translate(-50%, -50%);display: flex;flex-wrap: wrap;gap: 4px;
}
.image-list img {width: 170px;height: 170px;cursor: pointer;object-fit: cover;border-radius: 4px;transition: transform 0.3s;
}/* 放大的图片框架 */
.enlarged-image-box {position: fixed;top: 0;left: 0;width: 100vw;height: 100vh;background-color: rgba(0, 0, 0, 0.5); /* 背景加深 */display: flex;justify-content: center;align-items: center;z-index: 1003;
}/* 放大的图片 */
.enlarged-image-box img {height: 70%;position: absolute;z-index: 9999;cursor: grab; /* 鼠标样式为抓取 */user-select: none; /* 禁止用户选择图片 */transition: transform 0.3s ease; /* 平滑缩放 */
}/* 控制按钮图片 */
.control-buttons {position: fixed;top: 85%;left: 50%;transform: translate(-50%);display: flex;z-index: 1008;
}
.control-buttons img {width: 20px;height: 20px;background-color: rgba(0, 0, 0, 0.5);z-index: 5;padding: 3px;margin-right: 2px;border-radius: 50%;cursor: pointer;transition: background-color 0.3s, transform 0.3s;
}
.control-buttons img.active {background-color: rgba(0, 0, 0, 0.8);
}/* 激活缩略图 */
.thumbnail-container .thumbnail.active {filter: brightness(100%) !important;
}/* 中间显示的倍数 */
.zoom-percentage {position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);background-color: rgba(0, 0, 0, 0.7);color: white;border-radius: 5px;padding: 8px 12px;font-size: 18px;z-index: 1111;opacity: 0.9;transition: opacity 0.3s;
}/* 全屏遮罩 */
.fullscreen-overlay {width: 100vw;height: 100vh;position: fixed;top: 0;left: 0;background-color: black;z-index: 1010;
}
.fullscreen-image {height: 100%;position: fixed;top: 50%;left: 50%;z-index: 2000;transform: translate(-50%, -50%);
}/* 点击叉号 */
.close-button {background-color: rgba(0, 0, 0, 0.7);position: fixed;right: 0;top: 0;border-bottom-left-radius: 50px;padding: 4px 4px 5px 8px;z-index: 1005;
}
.close-icon {height: 17px;width: 17px;
}/* 最下面显示的缩略图 */
.thumbnail-container {background-color: rgba(0, 0, 0, 0.5);position: fixed;bottom: 0;left: 0;height: 50px;width: 100vw;display: flex;justify-content: center;z-index: 1008;
}
.thumbnail-container .thumbnail {height: 100%;width: 30px;margin-right: 2px;filter: brightness(70%);cursor: pointer;transition: filter 0.3s;
}
.thumbnail-container .thumbnail:hover {filter: brightness(50%);
}
.thumbnail-container {transition: transform 0.8s ease;
}
</style>
相关文章:
vue图片之放大、缩小、1:1、刷新、左切换、全屏、右切换、左旋咋、右旋转、x轴翻转、y轴翻转
先上效果,代码在下面 <template><!-- 图片列表 --><div class"image-list"><img:src"imageSrc"v-for"(imageSrc, index) in images":key"index"click"openImage(index)"error"handleI…...
Docker多架构镜像构建踩坑记
背景 公司为了做信创项目的亮点,需要将现有的一套在X86上运行的应用系统迁移到ARM服务器上运行,整个项目通过后端Java,前端VUEJS开发通过CICD做成Docker镜像在K8S里面运行。但是当前的CICD产品不支持ARM的镜像构建,于是只能手工构…...
“pinn是无网格的”???
“pinn是无网格的”??? PINN,即物理信息神经网络(Physics-Informed Neural Networks),是一种将物理定律作为先验知识整合到神经网络训练过程中的方法。它之所以被称为“无网格”的,…...
换一个ip地址是什么意思?换一个网络ip地址会变吗
在网络的世界里,IP地址如同每台设备的“身份证”,是确保网络信息能够准确传输到指定目标的关键。然而,在某些情况下,我们可能需要更换这个“身份证”,也就是更换IP地址。那么,换一个IP地址究竟是什么意思&a…...
JavaWeb学习--cookie和session,实现登录的记住我和验证码功能
目录 (一)Cookie概述 1.什么叫Cookie 2.Cookie规范 3.Cookie的覆盖 4.cookie的最大存活时间 (Cookie的生命) (二) Cookie的API 1.创建Cookie:new 构造方法 2.保存到客户端浏…...
深度学习:基于MindSpore的极简风大模型微调
什么是PEFT?What is PEFT? PEFT(Parameter Efficient Fine-Tuning)是一系列让大规模预训练模型高效适应于新任务或新数据集的技术。 PEFT在保持大部分模型权重冻结,只修改或添加一小部份参数。这种方法极大得减少了计算量和存储开销&#x…...
【LeetCode力扣热题100】【LeetCode 1】两数之和
方法一:暴力循环 两层循环,遍历所有的组合,直到满足条件,返回结果。 class Solution { public:vector<int> twoSum(vector<int>& nums, int target) {for(int i0; i<nums.size()-1 ;i){for(int j i1; j<…...
定制链接类名,两类跳转传参,vue路由重定向,404,模式设置
router-link-exact-active 和 router -link-active两个类名都太长,可以在router路由对象中定制进行简化 // index.js// 路由的使用步骤 52 // 1.下载 v3.6.5 // 2.引入 // 3.安装注册Vue.use(Vue插件) // 4.创建路由对象 // 5.注入到new Vue中,建立关联…...
【ArcGIS微课1000例】0135:自动生成标识码(长度不变,前面自动加0)
文章目录 一、加载实验数据二、BSM计算方法一、加载实验数据 加载专栏《ArcGIS微课实验1000例(附数据)》配套数据中0135.rar中的建筑物数据,如下图所示: 打开属性表,BSM为数据库中要求的字段:以TD_T 1066-2021《不动产登记数据库标准》为例: 计算出来的BSM如下图: 二、B…...
ISO45001职业健康安全管理体系认证流程
前期准备 领导决策:企业高层领导需认识到实施 ISO 45001 体系的重要性和必要性,做出认证决策,并承诺提供必要的资源支持。成立工作小组:由企业各相关部门人员组成工作小组,明确各成员的职责和分工,确保工作…...
VueRouter路由
单页应用程序:例 网易云 多页应用程序:例 京东 网易云导航栏点击任一网页不会跳转京东导航栏点击任一包括导航区域就会实现网页跳转 路由介绍 VueRouter Vue路由介绍 5个步骤写完之后出现 #/,说明当前Vue实例已经被路由所管理 2个关键步骤 新…...
性能测试攻略(一):需求分析
性能测试成为软件开发和运维过程中不可或缺的一环。性能测试不仅能够帮助我们了解系统在特定条件下的表现,还能帮助我们发现并解决潜在的性能问题。那么我们怎么做一次完整的性能测试呢?首先,我们需要进行需求分析,来明确我们的测…...
【24年新算法时间序列预测】黑翅鸢BKA优化Transformer时间序列预测(评估指标全,出图多)
本文采用黑翅鸢优化算法( BKA,2024年新算法)优化Transformer模型的超参数,形成了BKA-Transformer时间序列预测模型,以进一步提升其在时间序列预测中的性能,本文采用Matlab编写了BKA-Transformer时间序列预测模型代码,代…...
YOLOv8改进,YOLOv8引入CARAFE轻量级通用上采样算子,助力模型涨点
摘要 CARAFE模块的设计目的是在不增加计算复杂度的情况下,提升特征图的质量,特别是在视频超分辨率任务中,提升图像质量和细节。CARAFE结合了上下文感知机制和聚合特征的能力,通过动态的上下文注意力机制来提升细节恢复的效果。 理论介绍 传统的卷积操作通常依赖于局部区域…...
ZooKeeper节点扩容
新节点的准备工作(这里由hadoop05节点,IP地址为192.168.46.131充当) 配置新节点的主机域名映射,并将其通告给集群中的其他节点配置主机间免密登录关闭防火墙并将其加入到开机不启动项同步hadoop01节点的时间将所需要的文件分发给新…...
深度学习的unfold操作
unfold(展开)是深度学习框架中常见的数据操作。与我们熟悉的卷积类似,unfold也是使用一个特定大小的窗口和步长自左至右、自上至下滑动,不同的是,卷积是滑动后与核求乘积(所以取名为卷积)&#…...
C# 抽奖程序winform示例
C# 抽奖程序winform示例 using System; using System.Collections.Generic; using System.Linq;public class LotterySimulator {private Random random new Random();public List<string> GenerateWinners(int numberOfWinners, int totalParticipants){List<strin…...
嵌入式蓝桥杯学习9 usart串口
复制一下之前ADC的工程,打开cubemx cubemx配置 1.在Connectivity中点击USART1 Mode(模式):Asynchronous(异步模式) 2.将PA9设置为USART1_TX,PA10设置为USART1_RX。 3.配置Parameter Settings. Baud R…...
车载ADB:让汽车更智能的桥梁
随着科技的不断进步,汽车行业也在迅速迈向智能化。车载Android系统(通常称为Android Auto)正在变得越来越流行,而Android Debug Bridge (ADB) 作为连接和调试这些系统的桥梁,也变得尤为重要。在本文中,我们…...
HarmonyOS-高级(一)
文章目录 一次开发、多端部署自由流转 🏡作者主页:点击! 🤖HarmonyOS专栏:点击! ⏰️创作时间:2024年12月09日12点19分 一次开发、多端部署 布局能力 自适应布局 拉伸能力均分能力占比能力缩放…...
7个赛车数据分析实用技巧:Python F1赛事数据处理实战指南
7个赛车数据分析实用技巧:Python F1赛事数据处理实战指南 【免费下载链接】Fast-F1 FastF1 is a python package for accessing and analyzing Formula 1 results, schedules, timing data and telemetry 项目地址: https://gitcode.com/GitHub_Trending/fa/Fast-…...
学术探险家的秘密武器:书匠策AI,解锁课程论文新宇宙!
在学术的浩瀚星空中,每一位学子都是勇敢的探险家,怀揣着对知识的渴望,踏上探索未知的征途。而课程论文,则是这场探险中不可或缺的“星际导航图”,指引着我们穿越知识的迷雾,抵达真理的彼岸。但你是否曾遇到…...
宇视NVR接入AS-V1000平台全流程指南(含SDK端口配置避坑)
宇视NVR对接AS-V1000平台实战手册:从配置到排障的深度解析 当监控系统需要整合多品牌设备时,宇视NVR与AS-V1000平台的对接成为典型场景。不同于标准化的协议对接,SDK接入方式往往隐藏着诸多"暗礁"——从端口冲突到能力集匹配&#…...
工业能量:04.选型小Tips:预算2000元玩转工厂电源
04.选型小Tips:预算2000元玩转工厂电源(新手也能选对不踩坑,PLC机器人稳稳的)** 在工厂里,最昂贵的不是设备,而是“停机一秒的代价”。 哎,师傅们,槐树底下风儿吹得正凉快,今天咱不拆原理、不讲高端配置,就聊最接地气的——2000块钱怎么给车间PLC和机器人挑个靠谱心脏…...
YOLOv12镜像实战:工业质检场景下的高精度缺陷识别方案
YOLOv12镜像实战:工业质检场景下的高精度缺陷识别方案 1. 工业质检的挑战与YOLOv12的机遇 在制造业数字化转型浪潮中,工业质检一直是自动化程度较低的环节。传统人工检测面临三大痛点: 效率瓶颈:熟练质检员每分钟最多检测20-30…...
Qwen3-0.6B-FP8效果对比:与Phi-3-mini、Gemma-2B在低资源设备上的实测PK
Qwen3-0.6B-FP8效果对比:与Phi-3-mini、Gemma-2B在低资源设备上的实测PK 想在小显存的电脑上跑个大模型,体验一下AI对话的乐趣,是不是总被“显存不足”的提示劝退?别急,今天我们就来一场专为“小显存”设备准备的AI模…...
避开这些坑!算法工程师自学必备的5个高效学习法与工具推荐
避开这些坑!算法工程师自学必备的5个高效学习法与工具推荐 1. 为什么大多数自学算法工程师会失败? 在咖啡馆见到老张时,他正对着电脑屏幕上的LeetCode题目发呆。这位转行学习算法的前机械工程师已经坚持了8个月,但最近一次面试还是…...
ThinkPad X1 Tablet Gen3 vs Gen2键盘对比:为何Gen3更适合改装Type-C?
ThinkPad X1 Tablet Gen3键盘Type-C改装全解析:为何它成为DIY玩家的终极选择? 在移动办公设备轻量化与模块化设计成为主流的今天,ThinkPad X1 Tablet系列凭借其独特的二合一形态和标志性键盘手感,始终保持着特殊地位。特别是第三代…...
智能车小白也能懂的舵机PD控制:从电感差比和到方向控制,保姆级避坑指南
智能车方向控制入门:用PD算法驯服你的舵机 第一次看到智能车在赛道上流畅过弯时,很多人都会好奇——这辆小车是如何感知赛道边界并精准控制方向的?作为电磁组智能车的核心部件,舵机就像车辆的"方向盘",而PD控…...
C#实战:5分钟搞定Modbus RTU通讯(基于NModbus4库)
C#实战:5分钟搞定Modbus RTU通讯(基于NModbus4库) 工业自动化领域的数据采集离不开设备通讯协议的支持,而Modbus RTU作为最广泛应用的串行通信协议之一,几乎成为工控开发者的必修课。今天我们就用C#和NModbus4库&#…...
