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分 一次开发、多端部署 布局能力 自适应布局 拉伸能力均分能力占比能力缩放…...

Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...

macOS 终端智能代理检测
🧠 终端智能代理检测:自动判断是否需要设置代理访问 GitHub 在开发中,使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新,例如: fatal: unable to access https://github.com/ohmyzsh/oh…...