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

H5移动端图片查看器

一、新建名为ImageViewer.vue的组件,代码如下:

<!-- ImageViewer.vue -->
<template><div class="fullscreen-viewer" v-if="visible" @click="closeViewer"><div class="viewer-container" @click.stop><div class="image-wrapper" @wheel="handleWheel" @mousedown="startDrag" @mousemove="dragging" @mouseup="endDrag"@mouseleave="endDrag" @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd"@touchcancel="handleTouchEnd" :style="transformStyle"><img :src="imageUrl" :alt="altText" draggable="false"></div><div class="controls"><span class="zoom-info">{{ Math.round(scale * 100) }}%</span><button class="close-btn" @click="closeViewer">×</button></div></div></div>
</template><script setup>
import { ref, computed } from 'vue'const props = defineProps({imageUrl: {type: String,required: true},altText: {type: String,default: '预览图片'}
})const visible = ref(false)
const scale = ref(1)
const position = ref({ x: 0, y: 0 })
const isDragging = ref(false)
const dragStart = ref({ x: 0, y: 0 })
const touchStart = ref({ x: 0, y: 0, distance: 0, scale: 1 })const transformStyle = computed(() => ({transform: `translate(${position.value.x}px, ${position.value.y}px) scale(${scale.value})`,cursor: isDragging.value ? 'grabbing' : 'grab'
}))const openViewer = () => {visible.value = truescale.value = 1position.value = { x: 0, y: 0 }
}const closeViewer = () => {visible.value = falseisDragging.value = falseposition.value = { x: 0, y: 0 }
}const handleWheel = (e) => {e.preventDefault()const delta = e.deltaY > 0 ? -0.1 : 0.1let newScale = scale.value + deltanewScale = Math.max(0.9, Math.min(3, newScale))scale.value = newScale
}const startDrag = (e) => {isDragging.value = truedragStart.value = {x: e.clientX - position.value.x,y: e.clientY - position.value.y}
}const dragging = (e) => {if (!isDragging.value) returnconst newX = e.clientX - dragStart.value.xconst newY = e.clientY - dragStart.value.y// 计算边界限制const maxX = Math.max(0, (scale.value - 1) * window.innerWidth / 2)const maxY = Math.max(0, (scale.value - 1) * window.innerHeight / 2)position.value = {x: Math.max(-maxX, Math.min(maxX, newX)),y: Math.max(-maxY, Math.min(maxY, newY))}
}const endDrag = () => {isDragging.value = false
}const handleTouchStart = (e) => {if (e.touches.length === 1) {isDragging.value = truedragStart.value = {x: e.touches[0].clientX - position.value.x,y: e.touches[0].clientY - position.value.y}} else if (e.touches.length === 2) {isDragging.value = falseconst touch1 = e.touches[0]const touch2 = e.touches[1]const distance = Math.sqrt(Math.pow(touch2.clientX - touch1.clientX, 2) +Math.pow(touch2.clientY - touch1.clientY, 2))touchStart.value = {x: (touch1.clientX + touch2.clientX) / 2,y: (touch1.clientY + touch2.clientY) / 2,distance: distance,scale: scale.value}}
}const handleTouchMove = (e) => {e.preventDefault()if (e.touches.length === 1 && isDragging.value) {if (e.touches.length === 1 && isDragging.value) {const newX = e.touches[0].clientX - dragStart.value.xconst newY = e.touches[0].clientY - dragStart.value.y// 计算边界限制const maxX = Math.max(0, (scale.value - 1) * window.innerWidth / 2)const maxY = Math.max(0, (scale.value - 1) * window.innerHeight / 2)position.value = {x: Math.max(-maxX, Math.min(maxX, newX)),y: Math.max(-maxY, Math.min(maxY, newY))}}} else if (e.touches.length === 2) {const touch1 = e.touches[0]const touch2 = e.touches[1]const distance = Math.sqrt(Math.pow(touch2.clientX - touch1.clientX, 2) +Math.pow(touch2.clientY - touch1.clientY, 2))const scaleChange = distance / touchStart.value.distancescale.value = Math.max(0.9, Math.min(3, touchStart.value.scale * scaleChange))const centerX = (touch1.clientX + touch2.clientX) / 2const centerY = (touch1.clientY + touch2.clientY) / 2position.value = {x: position.value.x + (centerX - touchStart.value.x),y: position.value.y + (centerY - touchStart.value.y)}touchStart.value.x = centerXtouchStart.value.y = centerY}
}const handleTouchEnd = () => {isDragging.value = false
}defineExpose({openViewer,closeViewer
})
</script><style scoped>
.fullscreen-viewer {position: fixed;top: 0;left: 0;width: 100%;height: 100%;background-color: rgba(0, 0, 0, 0.9);display: flex;justify-content: center;align-items: center;z-index: 9999;
}.viewer-container {position: relative;width: 100%;height: 100%;display: flex;flex-direction: column;
}.image-wrapper {flex: 1;display: flex;justify-content: center;align-items: center;overflow: hidden;transition: transform 0.1s ease-out;touch-action: none;
}.image-wrapper img {max-width: 100%;max-height: 100%;object-fit: contain;user-select: none;-webkit-user-drag: none;
}.controls {position: absolute;bottom: 30px;right: 20px;display: flex;gap: 10px;align-items: center;
}.zoom-info {color: white;background-color: rgba(0, 0, 0, 0.5);padding: 5px 10px;border-radius: 4px;font-size: 14px;
}.close-btn {width: 36px;height: 36px;border-radius: 50%;background-color: rgba(0, 0, 0, 0.5);color: white;border: none;cursor: pointer;font-size: 22px;display: flex;justify-content: center;align-items: center;transition: background-color 0.3s;
}.close-btn:hover {background-color: rgba(255, 255, 255, 0.3);
}@media (max-width: 768px) {.controls {bottom: 30px;right: 20px;}.zoom-info {font-size: 12px;padding: 3px 8px;}.close-btn {width: 36px;height: 36px;font-size: 22px;}
}
</style>

二、引入组件使用

<!-- index.vue -->
<template><div><!-- 点击图片打开查看器 --><img src="your-image-url" @click="openImageViewer" /><!-- 图片查看器组件 --><ImageViewer ref="imageViewer"imageUrl="your-image-url"altText="图片描述"/></div>
</template><script setup>
import { ref } from 'vue'
import ImageViewer from './ImageViewer.vue'const imageViewer = ref(null)const openImageViewer = () => {imageViewer.value.openViewer()
}
</script>
http://www.zskr.cn/news/20038.html

相关文章:

  • 2025 年国内风化板源头厂家最新推荐排行榜:聚焦优质原料与精湛工艺,助力消费者精准选购靠谱企业榜单吧台/松木/桌面/茶台风化板厂家推荐
  • Delapp文件删除工具!Windows中删除文件和文件夹的简单工具!仅507KB的工具小巧且方便
  • 基于Hadoop+Spark的商店购物趋势分析与可视化平台科技达成
  • 2025 年折弯厂家推荐:江阴市富磊钢板加工专业中厚钢板折弯加工与高效行业解决方案提供商
  • 2025年10月振动电机厂家最新推荐排行榜,三相振动电机,单相振动电机,防爆振动电机公司推荐!
  • 【IEEE出版、连续6届已EI检索、多校联办】第七届机器人、智能控制与人工智能国际学术会议(RICAI 2025)
  • 企业数字化转型浪潮下,如何选择最适合的项目管理工具?
  • dify工作流遇到的问题及解决方案
  • 2025年10月青海视频号运营最新权威推荐榜:专业服务与创意内容引领潮流!
  • 2025年10月通风气楼厂家最新推荐排行榜,工业/商用通风气楼,高效节能通风解决方案提供商!
  • 工业状态控制
  • 2025 年磨粉机厂家最新推荐榜单:全面覆盖新型磨粉机、超细磨粉机、立式双动力磨粉机及节能磨粉机,为各行业采购者精准筛选优质品牌
  • 手把手教你在 Windows 安装 Docker Desktop
  • 强化学习的数学原理-04章 策略评估与策略优化 - 指南
  • HDU1204糖果大战 题解
  • 吴恩达深度学习笔记----系列文章
  • 2025年中国开发者代码管理平台选型全景报告:从本土化适配到全球化协作
  • 使用CVX工具箱求解凸优化问题示例
  • FastReport文本框根据高度缩小字体
  • 基于MATLAB的一维大地电磁反演程序实现
  • Java并发机制的底层实现原理:从CPU到JVM的全面解析
  • 学习unigui【38】重新认识UUNIGUI--为什么这样快? 站在“架构与性能”层面重新审视
  • 2025 年国内发电机厂家最新推荐排行榜:汽油 / 柴油 / 10KW-200KW 多功率机型精选,全方位解读品牌实力助力精准选购
  • IDA9.0中文版与相关插件安装详细教程
  • 2025 北京宽带安装公司最新推荐榜:专业口碑双优服务商汇总,企业家庭装机必看指南北京企业/北京无线/北京商务/北京商业/北京店铺宽带安装公司推荐
  • 2025年10月苹果仓源头厂家最新推荐榜单:专业仓储与高效配送的优质选择!
  • 2025年10月整平机厂家最新推荐排行榜,精密整平机,自动整平机,金属板材整平机公司推荐!
  • linux与window文件互传方式
  • 2025 年最新留学机构权威最新推荐排行榜,深度解析顶尖机构服务特色与核心优势助力留学规划英国/澳洲/香港/美国/加拿大留学机构推荐
  • 2025年10月瑕疵检测设备厂家最新推荐排行榜,表面/薄膜/铝箔/陶瓷膜瑕疵在线检测,外观瑕疵检测机/仪公司推荐!