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>

posted @ 2025-10-13 11:40  莫颀  阅读(16)  评论(0)    收藏  举报