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 = true
scale.value = 1
position.value = { x: 0, y: 0 }
}
const closeViewer = () => {
visible.value = false
isDragging.value = false
position.value = { x: 0, y: 0 }
}
const handleWheel = (e) => {
e.preventDefault()
const delta = e.deltaY > 0 ? -0.1 : 0.1
let newScale = scale.value + delta
newScale = Math.max(0.9, Math.min(3, newScale))
scale.value = newScale
}
const startDrag = (e) => {
isDragging.value = true
dragStart.value = {
x: e.clientX - position.value.x,
y: e.clientY - position.value.y
}
}
const dragging = (e) => {
if (!isDragging.value) return
const newX = e.clientX - dragStart.value.x
const 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 = true
dragStart.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 = false
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)
)
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.x
const 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.distance
scale.value = Math.max(0.9, Math.min(3, touchStart.value.scale * scaleChange))
const centerX = (touch1.clientX + touch2.clientX) / 2
const centerY = (touch1.clientY + touch2.clientY) / 2
position.value = {
x: position.value.x + (centerX - touchStart.value.x),
y: position.value.y + (centerY - touchStart.value.y)
}
touchStart.value.x = centerX
touchStart.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>
时间仓促,如有错误欢迎指出,欢迎在评论区讨论,如对您有帮助还请点个推荐、关注支持一下
作者:莫颀
出处:https://www.cnblogs.com/bokemoqi/p/19138105
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。
若内容有侵犯您权益的地方,请公告栏处联系本人,本人定积极配合处理解决。

浙公网安备 33010602011771号