当前位置: 首页 > 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.hskmm.com/?act=detail&tid=29960

相关文章:

  • 2025年10月复合钢丝网厂家最新推荐排行榜,镀锌复合钢丝网,不锈钢复合钢丝网,建筑用复合钢丝网公司推荐!
  • 2025年10月方钢厂家最新推荐排行榜,热轧方钢,冷拉方钢,高强度方钢,优质方钢供应商推荐!
  • OpenBLAS blas_thread_init: pthread_create failed for thread 1 of 4: Operation not permitted
  • QPSK调制在瑞利、高斯和莱斯信道下的MATLAB仿真
  • uml总结
  • Delapp文件删除工具!Windows中删除文件和文件夹的简单工具!仅507KB的工具小巧且方便
  • 基于Hadoop+Spark的商店购物趋势分析与可视化平台科技达成
  • 2025 年折弯厂家推荐:江阴市富磊钢板加工专业中厚钢板折弯加工与高效行业解决方案提供商
  • 2025年10月振动电机厂家最新推荐排行榜,三相振动电机,单相振动电机,防爆振动电机公司推荐!
  • 2025 储能 EMS 厂商排名:五大品牌以全维度优势领跑,技术与规模双驱动企业凸显
  • 【IEEE出版、连续6届已EI检索、多校联办】第七届机器人、智能控制与人工智能国际学术会议(RICAI 2025)
  • 企业数字化转型浪潮下,如何选择最适合的项目管理工具?
  • dify工作流遇到的问题及解决方案
  • 2025年10月青海视频号运营最新权威推荐榜:专业服务与创意内容引领潮流!
  • 2025 年玻璃钢水箱生产厂家最新推荐榜单:含 30 吨 / 订做 / 消防 / 方形 / 拼装式 / 屋顶 / 大型产品,从产能与服务维度精选优质企业
  • 2025 年水下打捞/打捞手机/打捞黄金/打捞戒指公司推荐榜:聚焦专业与高效,助您精准匹配靠谱服务
  • 2025年10月通风气楼厂家最新推荐排行榜,工业/商用通风气楼,高效节能通风解决方案提供商!
  • 算法练习记录
  • 2025 最新活性炭交易服务公司排行榜:实力厂商与新锐品牌权威推荐,含选购指南
  • 【隐语SecretFlow】 Unbalanced PSI Benchmark性能测试报告
  • 制造业老牌汽配企业如何借助纷享销客CRM实现数字化转型?
  • idea使用记录
  • 牛客刷题-Day12
  • 国产代码托管平台Gitee构建企业级安全防线 助力信创产业自主可控
  • 2025年10月拉伸器批发厂家最新推荐排行榜,液压拉伸器,机械拉伸器,电动拉伸器公司推荐!
  • Gitee崛起:中国开发者生态的新基建如何重塑技术格局
  • 工业状态控制
  • 2025 年磨粉机厂家最新推荐榜单:全面覆盖新型磨粉机、超细磨粉机、立式双动力磨粉机及节能磨粉机,为各行业采购者精准筛选优质品牌
  • Qwen2.5技术报告
  • 手把手教你在 Windows 安装 Docker Desktop