uniapp+vue写小程序页面,实现一张图片默认放大后,可以在容器内上下左右拖动查看

发布于:2025-07-04 ⋅ 阅读:(23) ⋅ 点赞:(0)

1.组件chargingimageViewer

<template>
  <view class="image-view" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd">
    <image
      :src="imageUrl"
      mode="aspectFill"
      :style="{
        width: imageWidth + 'rpx',
        height: imageHeight + 'rpx',
        transform: `translate(${currentX}rpx, ${currentY}rpx)`,
      }"
    ></image>
  </view>
</template>

<script setup>
  import { ref, onMounted, computed } from 'vue';

  const props = defineProps({
    imageUrl: {
      type: String,
      required: true,
    },
  });

  // 视图容器尺寸
  const VIEW_WIDTH = 750; // 微信小程序设计稿宽度
  const VIEW_HEIGHT = 1800;

  // 缩放限制
  const MAX_SCALE = 5;
  const MIN_SCALE = ref(1);

  // 图片信息
  const imageWidth = ref(0);
  const imageHeight = ref(0);
  const originalWidth = ref(0);
  const originalHeight = ref(0);
  const scale = ref(1);
  const initialScale = ref(1);

  // 拖动状态
  const isDragging = ref(false);
  const isPinching = ref(false);
  const startX = ref(0);
  const startY = ref(0);
  const startDistance = ref(0);
  const currentX = ref(0);
  const currentY = ref(0);
  const lastX = ref(0);
  const lastY = ref(0);
  const pivotX = ref(0);
  const pivotY = ref(0);

  // 计算图片按比例放大后的尺寸
  const calculateImageSize = (width, height, scaleVal = 1.1) => {
    // 计算宽高比
    const ratio = width / height;

    // 优先满足高度,因为容器高度更大
    let newHeight = VIEW_HEIGHT;
    let newWidth = newHeight * ratio;

    // 如果宽度超出容器,则以宽度为基准
    if (newWidth < VIEW_WIDTH) {
      newWidth = VIEW_WIDTH;
      newHeight = newWidth / ratio;
    }

    // 应用缩放
    newWidth *= scaleVal;
    newHeight *= scaleVal;

    return {
      width: newWidth,
      height: newHeight,
    };
  };

  // 获取图片原始尺寸
  const getImageInfo = () => {
    uni.getImageInfo({
      src: props.imageUrl,
      success: (res) => {
        originalWidth.value = res.width;
        originalHeight.value = res.height;

        // 计算按比例放大后的图片尺寸
        const size = calculateImageSize(res.width, res.height);
        imageWidth.value = size.width;
        imageHeight.value = size.height;
        initialScale.value = size.width / res.width;

        // 设置最小缩放比例
        MIN_SCALE.value = initialScale.value;
        scale.value = initialScale.value;

        // 初始居中显示
        currentX.value = (VIEW_WIDTH - size.width) / 2;
        currentY.value = (VIEW_HEIGHT - size.height) / 2;
        lastX.value = currentX.value;
        lastY.value = currentY.value;
      },
      fail: (err) => {
        console.error('获取图片信息失败', err);
      },
    });
  };

  // 限制图片拖动边界
  const limitBounds = () => {
    // 计算可拖动范围
    const maxX = Math.max(0, (VIEW_WIDTH - imageWidth.value) / 2);
    const minX = Math.min(0, VIEW_WIDTH - imageWidth.value - maxX);
    const maxY = Math.max(0, (VIEW_HEIGHT - imageHeight.value) / 2);
    const minY = Math.min(0, VIEW_HEIGHT - imageHeight.value - maxY);

    // 限制X方向
    if (currentX.value > maxX) {
      currentX.value = maxX;
    } else if (currentX.value < minX) {
      currentX.value = minX;
    }

    // 限制Y方向
    if (currentY.value > maxY) {
      currentY.value = maxY;
    } else if (currentY.value < minY) {
      currentY.value = minY;
    }
  };

  // 更新图片尺寸和位置
  const updateImage = () => {
    const size = calculateImageSize(originalWidth.value, originalHeight.value, scale.value);
    imageWidth.value = size.width;
    imageHeight.value = size.height;

    // 调整位置以保持缩放中心不变
    if (isPinching.value) {
      currentX.value = pivotX.value - (pivotX.value - lastX.value) * (scale.value / lastScale.value);
      currentY.value = pivotY.value - (pivotY.value - lastY.value) * (scale.value / lastScale.value);
    }

    // 限制边界
    limitBounds();
  };

  let lastScale = 1;
  // 触摸事件处理
  const onTouchStart = (e) => {
    if (e.touches.length === 1) {
      // 单点触摸 - 拖动
      isDragging.value = true;
      isPinching.value = false;
      startX.value = e.touches[0].clientX;
      startY.value = e.touches[0].clientY;
    } else if (e.touches.length === 2) {
      // console.log(e.touches);
      // // 双点触摸 - 缩放
      // isDragging.value = false;
      // isPinching.value = true;
      // const touch1 = e.touches[0];
      // const touch2 = e.touches[1];
      // // 计算两点距离
      // const dx = touch2.clientX - touch1.clientX;
      // const dy = touch2.clientY - touch1.clientY;
      // startDistance.value = Math.sqrt(dx * dx + dy * dy);
      // // 计算缩放中心点
      // pivotX.value = (touch1.clientX + touch2.clientX) / 2;
      // pivotY.value = (touch1.clientY + touch2.clientY) / 2;
      // // 记录当前状态
      // lastScale = scale.value;
      // lastX.value = currentX.value;
      // lastY.value = currentY.value;
    }
  };

  const onTouchMove = (e) => {
    // 阻止事件冒泡和默认行为,防止页面滚动
    // e.stopPropagation()
    // e.preventDefault()

    if (isDragging.value && e.touches.length === 1) {
      // 处理拖动
      const clientX = e.touches[0].clientX;
      const clientY = e.touches[0].clientY;

      // 计算移动距离
      const deltaX = clientX - startX.value;
      const deltaY = clientY - startY.value;

      // 更新图片位置
      currentX.value = lastX.value + deltaX;
      currentY.value = lastY.value + deltaY;

      // 限制边界
      limitBounds();
    } else if (isPinching.value && e.touches.length === 2) {
      // // 处理缩放
      // const touch1 = e.touches[0];
      // const touch2 = e.touches[1];
      // // 计算两点距离
      // const dx = touch2.clientX - touch1.clientX;
      // const dy = touch2.clientY - touch1.clientY;
      // const distance = Math.sqrt(dx * dx + dy * dy);
      // // 计算缩放比例
      // const newScale = lastScale * (distance / startDistance.value);
      // // 限制缩放范围
      // if (newScale >= MIN_SCALE.value && newScale <= MAX_SCALE) {
      //   scale.value = newScale;
      //   updateImage();
      // }
    }
  };

  const onTouchEnd = () => {
    // 阻止事件冒泡,防止页面滚动
    // e.stopPropagation()

    isDragging.value = false;
    isPinching.value = false;
    lastX.value = currentX.value;
    lastY.value = currentY.value;
  };

  onMounted(() => {
    getImageInfo();
  });
</script>

<style scoped>
  .image-view {
    width: 750rpx;
    height: 1800rpx;
    overflow: hidden;
    position: relative;
  }

  image {
    position: absolute;
    transition: transform 0s;
  }
</style>

2.引用

<view class="mapbox">
      <chargingimageViewer :imageUrl="imageUrl"></chargingimageViewer>
    </view>


import chargingimageViewer from '@/components/chargingimageViewer/chargingimageViewer.vue';

const imageUrl = ref('../../static/images/dingwei.png');