vue pc端展示大图片,实现放大缩小移动



```
```

<template>


  <div class="mainPage">

    <div class="watchMap">
      <div class="imgBox" ref="maskBox" @mousedown="onmousedownHandle"  @wheel="wheelHandle">
        <div class="bigBox" ref="bigBox" :style="{
                    top: top + 'px',
                    left: left + 'px',
                    transform: `scale(${scale})`,
                }"
             @mousemove="handleMouseMoveBigBox"
        >
<!--            如果只是展示一张拼接大图,想要实现放大缩小可以直接使用transform这个属性-->
          <img
              @mousemove="handleMouseMoveImg(item)"
              v-for="item in imgList"
               :key="item.src"
               :data-src="item.src"
               @load="onImageLoad"
               :x="item.x"
               :y="item.y"
               :ref="getRef(item)"
               alt=""
               :style="{
                 top: imgH * item.y + 'px',
                 left: imgW * item.x + 'px',
               }"
          >
          <div
              class="thumbnailMarker"
              :style="{ top: imgBoxY + 'px', left: imgBoxX + 'px' }"
          ></div>
        </div>

      </div>

      <div class="thumbnailBox" ref="thumbnailBox">
        <img
            :src="thumbnailSrc"
            class="thumbnail"
            @click="handleThumbnailMouseMove"
        />
<!--            @mousemove.stop="handleThumbnailMouseMove"-->
        <div
            class="thumbnailMarker"
            :style="{ top: thumbnailMarkerTop + 'px', left: thumbnailMarkerLeft + 'px' }"
        ></div>
      </div>
    </div>
    <div  class="footerModal">
      <div class="bottom-left">
        最小倍率:{{scaleList[0]}} &nbsp;&nbsp;&nbsp;&nbsp;
        最大倍率:{{ scaleList[scaleList.length - 1]}} &nbsp;&nbsp;&nbsp;&nbsp;
        <a style="display: flex;align-items: center">
          放大<i class="el-icon-zoom-in" @click="imgScaleHandle(+1);isMouseScroll = false"></i>
          当前倍率:{{scaleList[size]}}&nbsp;&nbsp;&nbsp;&nbsp;
          缩小<i class="el-icon-zoom-out" @click="imgScaleHandle(-1);isMouseScroll = false"></i>
        </a>

      </div>
      <div class="bottom-right">
        <button
            style="
                  width: 98px;
                  height: 34px;
                  border: 1px solid #00a8e3;
                  border-radius: 19px;
                  background-color: white;
                  color: #00a8e3;
                  cursor: pointer;
                  margin-right: 20px;
                "
            @click="clickScreen">截图</button>
        <button
            style="
                  width: 98px;
                  height: 34px;
                  background: #00a8e3;
                  border-radius: 19px;`在这里插入代码片`
                  border: 1px solid #00a8e3;
                  color: white;
                  cursor: pointer;
                "
            @click="showImgCancel"
            v-debounce="1000"
        >
          关闭
        </button>
      </div>
    </div>
    <!--    截图-->
    <screenShot  ref="ScreenShot" @confirm="screenShotConfirm"></screenShot>
  </div>
</template>

<script>
import screenShot from "@/components/SlideLibrary/ScreenShot.vue";

export default {
  components: {screenShot},
  props: {
    slideId: {
      default: 0
    }
  },
  data() {
    return {
      imgList: [],
      imgW: 2048,
      imgH: 2048,
      deg: 0,
      top: 0,
      left: 0,
      scale: 1,
      size: 0,
      maxX: 0,
      maxY: 0,
      mousewheelevt: null,
      observer: null,

      //
      lastSize: 0,// 上一个倍率
      mouseX: 0, // 鼠标在当前可视窗口的x
      mouseY: 0,// 鼠标在当前可视窗口的y

      mouseImgX: 0, // 鼠标点下图片的x
      mouseImgY: 0, // 鼠标点下图片的y

      maxScale: 0,

      itemImgX:0,
      itemImgY:0,
      imgBoxX: 0,
      imgBoxY: 0,

      thumbnailSrc: '',
      thumbnailMarkerTop: 0,
      thumbnailMarkerLeft: 0,
      thumbnailWidth: 100, // 根据您的缩略图大小进行调整
      thumbnailHeight: 100, // 根据您的缩略图大小进行调整

      bigImgWidth: 0, // 整个玻片的宽
      bigImgHeight: 0, // 整个玻片的高


      smallImgWidth: 0, // 最小层玻片整个玻片的宽
      smallImgHeight: 0, // 最小层玻片整个玻片的高

      multiple: 0, // 倍率

      maskBoxX: 0,// maskBoxs上的坐标
      maskBoxY: 0,// maskBoxs上的坐标
      scrollTimeout: null,
      isMouseScroll: false,
      scaleList: [], // 比例列表
    };
  },
  computed: {
  },
  async mounted() {

    await this.getImgList(this.size)

    // 兼容火狐浏览器
    this.mousewheelevt = /Firefox/i.test(navigator.userAgent)
        ? "DOMMouseScroll"
        : "mousewheel";
    // 为空间区域绑定鼠标滚轮事件 =》 处理函数是wheelHandle
    // 如果你监听了window的scroll或者touchmove事件,你应该把passive设置为true,这样滚动就会流畅很多
    // this.$refs.maskBox.addEventListener(this.mousewheelevt, this.wheelHandle);

    this.flushedData()
    // 监听鼠标移动事件
    this.$refs.maskBox.addEventListener('mousemove', this.onMouseMove);
    //初始化图片
    this.initImage();

  },
  beforeDestroy() {
    //取消监听
    this.$refs.maskBox.removeEventListener(
        this.mousewheelevt,
        this.wheelHandle
    );
  },
  created() {
    this.handleReset();
  },
  methods: {
    // 比例列表
    generateSequence(n) {
      let value = 1; // 初始值为 1
      let sequence = [];
      for (let i = 0; i < n; i++) {
        sequence.push(value);
        value *= this.multiple; // 每项是前一项的 multiple(3)倍
      }
      this.scaleList = sequence
    },
    screenShotConfirm () {

    },
    // 点击截图
    clickScreen () {
      // 点击截图功能
      this.$refs.ScreenShot.btnClick()
    },
    // 展示玻片弹窗
    showImgCancel () {
      this.$emit('close')
      // this.slideImgShow = false
    },
    // 点击缩略图
    handleThumbnailMouseMove(event) {
      // 计算鼠标相对于缩略图框的位置
      const rect = this.$refs.thumbnailBox.getBoundingClientRect();

      const mouseX = event.clientX - rect.left;
      const mouseY = event.clientY - rect.top;

      this.$nextTick(() => {
        // 更新缩略图标记位置
        this.thumbnailMarkerLeft = mouseX - 3
        this.thumbnailMarkerTop = mouseY - 3

        // 计算相对于图像的位置(考虑比例) 更新大图上标记位置
        this.imgBoxX = mouseX / this.thumbnailWidth * this.bigImgWidth;
        this.imgBoxY = mouseY / this.thumbnailHeight * this.bigImgHeight;

        // 计算大图在视口中心的偏移量
        const maskBoxRect = this.$refs.maskBox.getBoundingClientRect();
        const offsetX = maskBoxRect.width / 2;
        const offsetY = maskBoxRect.height / 2;

        this.left = -this.imgBoxX + offsetX
        this.top = -this.imgBoxY + offsetY

      })
      // console.log(mouseX,mouseY);

    },
    getRef(item) {
      return `image-${item.src.replace(/[^a-zA-Z0-9]/g, '')}`;
    },
    // 划过div 包裹图片div的操作
    handleMouseMoveBigBox (event) {
      const bigBoxRect = this.$refs.bigBox.getBoundingClientRect();
      const mouseX = event.clientX - bigBoxRect.left;
      const mouseY = event.clientY - bigBoxRect.top;

      // 在整个大图上的下x,y坐标
      this.imgBoxX = mouseX
      this.imgBoxY = mouseY

      // console.log(this.imgBoxX,this.imgBoxY,"this.imgBoxX,this.imgBoxY在handleMouseMoveBigBox中");
      this.$nextTick(() => {
        // 更新标记位置
        // 计算缩略图上标记的位置
        const thumbnailBoxRect = this.$refs.thumbnailBox.getBoundingClientRect();
        const thumbnailWidth = thumbnailBoxRect.width;
        const thumbnailHeight = thumbnailBoxRect.height;

        const realWidth = this.bigImgWidth
        const realHeight = this.bigImgHeight

        // 假设缩略图和大图的宽高比例相同,可以直接按比例计算
        this.thumbnailMarkerLeft = (this.imgBoxX / realWidth ) * thumbnailWidth - 4;
        this.thumbnailMarkerTop = (this.imgBoxY / realHeight ) * thumbnailHeight - 4;
      })

      // console.log(`鼠标在 bigBox 中的坐标:(${mouseX}, ${mouseY})`);
      // console.log(this.thumbnailMarkerLeft,this.thumbnailMarkerTop,"this.thumbnailMarkerTop");
    },

    // 划过图片的操作
    handleMouseMoveImg(item) {
      this.itemImgX = item.x
      this.itemImgY = item.y
      // console.log('当前鼠标所在位置的图片名称:', item.x+"_"+item.y);
      // console.log('自定义字段 x:', item.x, 'y:', item.y);
    },
    // 进入视野的图片显示
    flushedData() {
      this.$nextTick(() => {
        // 创建 Intersection Observer 实例
        this.observer = new IntersectionObserver(entries => {
          entries.forEach(entry => {
            // console.log(entry);
            if (entry.isIntersecting) {
              const lazyImage = entry.target;

              if (lazyImage.dataset.src) {
                console.log('图片进入视野:', lazyImage.dataset.src);
                lazyImage.src = lazyImage.dataset.src;
                this.observer.unobserve(lazyImage); // 加载后取消观察,避免重复加载
              } else {
                console.error('图片数据源(data-src)为空:', lazyImage);
              }
            }
          });
        }, {
          root: this.$refs.maskBox,
          rootMargin: '2048px 100px 2048px 2048px', // 根据需要调整
          threshold: 0.1 // 调整阈值数组以适应不同的可见性级别
        });

        // 观察所有带有 data-src 属性的图片
        const images = this.$refs.maskBox.querySelectorAll('img');
        images.forEach(img => {
          if (img.dataset.src) {
            this.observer.observe(img);
          } else {
            console.error('图片缺少数据源(data-src)属性:', img);
          }
        });
      })
    },
    // 图片加载
    onImageLoad() {

    },
    async getImgList (multiple) {
      if (this.slideId) {
        this.$axios({
          method: "POST",
          url: "请求地址",
          data: {
            multiple: multiple || this.size
          },
        }).then((res) => {
          console.log(res.data.data);
          if (res.data.resultFlag == true) {
            if (res.data.data.slideMapList.length > 0) {
              let data = res.data.data.slideMapList
              this.maxScale = data[0].allhierarchy // 总层级: 一共有几层
              this.multiple = data[0].multiple // 图片上传时的倍率比如图片按照3*3来生成
              this.fromData(data)
              if (multiple == 0) {
                this.thumbnailSrc = res.data.data.slideMapList[0].imgUrl

                // 获取比例列表
                this.generateSequence(this.maxScale)
              }
            }
          } else {
            this.$message({
              message: res.data.msg,
              type: 'error',
              customClass: 'mzindex'
            })
          }
        });
      }
    },
    // 获取图片
    async fromData(listArr) {

      try {
        if (this.size == 0) {
          const promises = listArr.map(async item => {
            return new Promise((resolve, reject) => {
              let img = new Image();
              img.onload = () => {
                let obj = {
                  src: item.imgUrl,
                  x: item.rowX,
                  y: item.rowY,
                  width: img.naturalWidth,
                  height: img.naturalHeight
                };
                resolve(obj);
              };
              img.onerror = (error) => {
                reject(error);
              };
              img.src = item.imgUrl;
            });
          });

          this.imgList = await Promise.all(promises);
        } else {
          this.imgList = []
          listArr.forEach(item => {
            let obj = {
              src: item.imgUrl,
              x: item.rowX,
              y: item.rowY,
              // width: img.naturalWidth,
              // height: img.naturalHeight
            };
            this.imgList.push(obj)
          })
        }

        // console.log(this.imgList);

        this.maxX = Math.max(...this.imgList.map(curr => curr.x), 0);
        this.maxY = Math.max(...this.imgList.map(curr => curr.y), 0);

        // console.log(this.bigImgWidth,this.bigImgHeight,"没更改前旧宽高");
        let oldBigImgWidth = this.bigImgWidth;
        let oldBigImgHeight = this.bigImgHeight;

        let newWidth = 0;
        let newHeight = 0;

        if(this.size == 0 && !this.smallImgWidth) {
          this.imgList.forEach(curr => {
            if (curr.x <= this.maxX && curr.y == 0) {
              newWidth += curr.width;
            }
            if (curr.y <= this.maxY && curr.x == 0) {
              newHeight += curr.height;
            }
          });
          this.smallImgWidth = newWidth
          this.smallImgHeight = newHeight
        } else {
          newWidth = this.calculatedValue(this.size, this.multiple, this.smallImgWidth)
          newHeight = this.calculatedValue(this.size, this.multiple, this.smallImgHeight)
        }


        this.bigImgWidth = newWidth;
        this.bigImgHeight = newHeight;


        this.imgBoxX = this.imgBoxX / oldBigImgWidth * this.bigImgWidth;
        this.imgBoxY = this.imgBoxY / oldBigImgHeight * this.bigImgHeight;

        let offsetX,offsetY
        if (this.isMouseScroll) {
          offsetX = this.imgBoxX - this.maskBoxX;
          offsetY = this.imgBoxY - this.maskBoxY;

          offsetX = offsetX || 0
          offsetY = offsetY || 0
          // console.log(11,"进入isMouseScroll=============",offsetX,offsetY);
          this.left = offsetX  * -1
          this.top = offsetY * -1
        } else {
          // console.log(22,"进入2222");
          // 计算大图在视口中心的偏移量
          const maskBoxRect = this.$refs.maskBox.getBoundingClientRect();
          offsetX = maskBoxRect.width / 2;
          offsetY = maskBoxRect.height / 2;
          this.left = -this.imgBoxX + offsetX
          this.top = -this.imgBoxY + offsetY
        }
        // console.log(this.imgBoxX,this.imgBoxY,"this.imgBoxX,this.imgBoxY在fromData中=============");
        // console.log(this.maskBoxX,this.maskBoxY,"this.maskBoxX,this.maskBoxY------------");
        this.lastSize = Math.floor(this.size);

        await this.flushedData();
      } catch (e) {
        console.log(e);
      }

  
    },
    // 计算值
    calculatedValue (size,scale,num) {
      let value = num * Math.pow(scale, size)
      return value
    },
   

    /**
     * 重置
     */
    handleReset() {
      // this.imgW = 0;
      // this.imgH = 0;
      this.top = 0;
      this.left = 0;
      this.deg = 0;
      this.scale = 1;
      this.size = 0;
      this.initImage();
    },

    /**
     * 初始化图片
     */
    async initImage() {
    },
    // 放大缩小按钮
    async imgScaleHandle(zoom) {
      this.size += zoom;
      if (this.size < 0) {
        this.size = 0;
      } else if (this.size > this.maxScale - 1) {
        this.size = this.maxScale - 1
      }
      const newRange = Math.floor(this.size);
      // console.log(newRange,this.lastSize);
      if (newRange !== this.lastSize) {
        // this.lastSize = newRange;
        await this.getImgList(newRange);
      }
    },
    /**
     * 鼠标滚动 实现放大缩小
     */
    async wheelHandle(e) {
      // 如果已有定时器,清除它
      if (this.scrollTimeout) {
        clearTimeout(this.scrollTimeout);
      }

      // 设置一个新的定时器
      this.scrollTimeout = setTimeout(() => {
        this.isMouseScroll = true
        // console.log(this.isMouseScroll,"wheelHandle");
        // 滚动结束后的处理逻辑
        // 更新缩放比例
        // this.scale = newScale;
        const ev = e || window.event; // 兼容性处理 => 火狐浏览器判断滚轮的方向是属性 detail,谷歌和ie浏览器判断滚轮滚动的方向是属性 wheelDelta
        // dir = -dir; // dir > 0 => 表示的滚轮是向上滚动,否则是向下滚动 => 范围 (-120 ~ 120)
        const dir = ev.detail ? ev.detail * -120 : ev.wheelDelta;
        let zoom = dir > 0 ? 1 : -1
        //滚动的数值 / 2000 => 表示滚动的比例,用此比例作为图片缩放的比例
        this.imgScaleHandle(zoom,e);
      }, 150); // 延迟时间根据需要调整
    },
    /**
     * 处理图片拖动
     */
    onmousedownHandle(e) {
      const that = this;
      const startX = e.pageX;
      const startY = e.pageY;
      const startLeft = this.left || 0;
      const startTop = this.top || 0;

      // 使用箭头函数保持 `this` 的正确指向
      this.$refs.maskBox.onmousemove = (el) => {
        const ev = el || window.event;
        ev.preventDefault();
        // 计算移动后的位置
        that.left = startLeft + ev.pageX - startX;
        that.top = startTop + ev.pageY - startY;

        const maskBoxRect = that.$refs.maskBox.getBoundingClientRect();

        // 确保位置在容器内
        if (that.left + 300 > maskBoxRect.width) {
          that.left = maskBoxRect.width - 300;
        }

        if (that.top + 300 > maskBoxRect.height) {
          that.top = maskBoxRect.height - 300;
        }

        // console.log(that.left,that.top,this.bigImgWidth);

        if (that.left - 300 < -this.bigImgWidth) {
          that.left = -this.bigImgWidth + 300;
        }
        if (that.top - 300 < -this.bigImgHeight) {
          that.top = -this.bigImgHeight + 300;
        }
      };

      this.$refs.maskBox.onmouseup = () => {
        that.$refs.maskBox.onmousemove = null;
        that.$refs.maskBox.onmouseup = null;
      };

      if (e.preventDefault) {
        e.preventDefault();
      } else {
        return false;
      }
    },


    /**
     * 鼠标移动事件处理程序,用于获取鼠标位置
     */
    onMouseMove(e) {
      const rect = this.$refs.maskBox.getBoundingClientRect();
      // console.log(e.clientX);
      // clientX 表示鼠标事件发生时,鼠标指针相对于浏览器窗口客户区域(即浏览器视口)左边界的水平坐标位置。
      // 是 maskBox 元素相对于浏览器窗口客户区域左边界的距离(即 maskBox 的左边缘在视口中的位置)。
      let mouseX = e.clientX - rect.left
      let mouseY = e.clientY - rect.top
      this.maskBoxX = mouseX
      this.maskBoxY = mouseY
      // console.log(this.maskBoxX, this.maskBoxY,"this.maskBoxX, this.maskBoxY");

      // 计算大图在视口中心的偏移量
      // const maskBoxRect = this.$refs.maskBox.getBoundingClientRect();
      // const offsetX = maskBoxRect.width / 2;
      // const offsetY = maskBoxRect.height / 2;
    },
    // 获取 bigBox 到 maskBox 的距离
    getDistanceBetweenBoxes() {
      const bigBoxRect = this.$refs.bigBox.getBoundingClientRect(); // 可视窗口
      const maskBoxRect = this.$refs.maskBox.getBoundingClientRect(); // 包裹图片div

      const distanceX = bigBoxRect.left - maskBoxRect.left;
      const distanceY = bigBoxRect.top - maskBoxRect.top;

      return { distanceX, distanceY };
    },
  },
};
</script>

<style lang="less" scoped>
.watchMap {
  //width: 1200px;
  //height: 662px;
  width: 98vw;
  height: 90vh;
  background: pink;
  position: relative;
  border: 1px solid #333;
  margin: 0 auto;

  // overflow: hidden;
  //margin-left: 360px;
  .bigBox{
    position: relative;
    //transition: top 0.3s ease, left 0.3s ease; /* 添加平滑过渡效果 */
  }

  .imgBox {
    width: 100%;
    height: 100%;
    overflow: hidden;
    position: relative;

    img {
      cursor: move;
      position: absolute;
      object-fit: cover;
    }
  }

  .Tools {
    width: 43px;
    min-height: 100px;
    position: absolute;
    right: -50px;
    top: 0;
    user-select: none;

    .Tools-item {
      width: 100%;
      height: 44px;
      background: pink;
      margin-bottom: 5px;
      display: flex;
      justify-content: center;
      align-items: center;
      cursor: pointer;

    }

    .Tools-item:hover{
      background-color: red;
      color: #fff;
    }
  }
}

.thumbnailBox {
  position: absolute;
  top: 0;
  right: 0;
  width: 100px;
  height: 100px;
  border: 1px solid #ccc;
  //margin-top: 10px;
}

.thumbnail {
  width: 100px;
  height: 100px;
}

.thumbnailMarker {
  position: absolute;
  width: 6px;
  height: 6px;
  border: 1px solid red;
  //border-radius: 50%;
}

.footerModal{
  width: 85%;
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 0 auto;

  .bottom-left{
    display: flex;
    align-items: center;
  }
  i{
    font-size: 24px;
    margin-right: 20px;
  }
}
</style>

```

```

### vue 图片预览组件示例 对于希望集成图片预览功能到Vue项目的开发者来说,存在多个选项可以满足需求。这里介绍两个不同的解决方案。 #### 方案一:使用带有图片预览功能的移动友好型文件输入组件 此方案提供了一个专门为移动设备优化的Vue.js组件,不仅能够处理图片上传还提供了实时预览的功能[^1]。该组件特性包括但不限于: - 支持拖拽操作 - 自动修正EXIF方向 - 移动触摸友好的交互设计 ```html <template> <div id="app"> <!-- 文件选择器 --> <input type="file" accept="image/*" @change="onFileChange"/> <!-- 预览区域 --> <img v-if="imageUrl" :src="imageUrl" alt="Image Preview" /> </div> </template> <script> export default { data() { return { imageUrl: null, }; }, methods: { onFileChange(event) { const file = event.target.files[0]; if (!file) return; let reader = new FileReader(); reader.onloadend = () => { this.imageUrl = reader.result; }; // 处理图像的方向问题 EXIF.getData(file, function(){ console.log(EXIF.getTag(this,"Orientation")); }); reader.readAsDataURL(file); } } } </script> ``` #### 方案二:采用`hevue-img-preview`插件实现更丰富的互动体验 另一个推荐的选择是`hevue-img-preview`库,它适用于PC移动,并且具备强的交互能力,比如放大缩小、平移以及通过键盘快捷键来操控图片显示效果[^2]。安装并配置如下所示: ```bash npm install hevue-img-preview --save ``` 在main.js中引入并注册全局组件: ```javascript import HevueImgPreview from 'hevue-img-preview'; Vue.use(HevueImgPreview); ``` HTML模板部分定义触发预览的方式: ```html <img src="./path/to/image.jpg" @click="previewImg('./path/to/image.jpg')" /> <!-- 或者绑定动态URL --> <img :src="dynamicImageUrl" @click="previewImg(dynamicImageUrl)" /> ``` JavaScript逻辑用于启动预览窗口: ```javascript methods: { previewImg(url){ this.$hevueImgPreview({ urlList:[url], // 可传入数组支持多张图片连续查看 initialIndex: 0 // 设置初始展示索引位置,默认为第一页 }); } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值