前端 vue3 +vueuse 图片放大镜功能,支持鼠标滚动控制放大倍数

775 阅读2分钟
最近接到一个需求,需要用户可以放大缩小图片,滚轮滚动还要控制放大的倍数。

在电商网站上,经常看到有商品图片被放大查看的功能,包括另外一些图片展示站点,也有类似的功能。
如果我们想让图片能展示更多细节清晰的内容,实现这样一个放大镜功能,是非常划算的,既能使用小图显示满足大多数用户的查看需求,又能通过放大图片的方式显示更清晰内容。这样,一方面可以节省不必要的大图流量,又能满足对图片清晰度有要求的用户需求。

所以,本文将分享前端技术实现一个简单的图片放大镜功能,希望能给大家带来一定的帮助。

首先,我先大致介绍一下我的实现思路

  1. 通过 vueuseuseMouseInElement 去判断鼠标是否进入到需要放大的图片中,并记录鼠标的坐标位置。
  2. 根据坐标去计算生成蒙层和放大预览的图片
  3. 通过监听 wheel 事件去控制图片放大的倍数

大部分都是计算

先安装vueuse

npm i @vueuse/core
<script setup>
import { useMouseInElement } from '@vueuse/core'
import { computed, ref } from 'vue'


const target = ref(null) // 操作的图片
const height = ref(0) // 操作盒子的高
const width = ref(0)  // 操作盒子的宽
const layerWidth = ref(100) // 遮罩层宽and高 正方形
const scale = ref(5) // 放大的倍数

const imgUrl = ref('https://okdemo.okaygis.com:18062/cpmini-pc/io/3525?resVer=2')

const { x, y, isOutside } = useMouseInElement(target) // 鼠标是否在图片内

// 放大的位置
const position = computed(() => {
  layerWidth.value = (width.value / scale.value).toFixed(0)
  let elementX = x.value - (layerWidth.value / 2) - 5 // 让光标处再中间
  let elementY = y.value - (layerWidth.value / 2) - 5
  if (target.value) {
    width.value = target.value?.offsetWidth  // 记录操作的div高度
    height.value = target.value?.offsetHeight // 记录操作的div宽度
  }
  elementX = elementX < 0 ? 0 : elementX  // 不能超过最左侧
  elementY = elementY < 0 ? 0 : elementY  // 不能超过最上侧
  elementX = elementX > width.value - layerWidth.value ? width.value - layerWidth.value : elementX  // 不能超过最右侧
  elementY = elementY > height.value - layerWidth.value ? height.value - layerWidth.value : elementY// 不能超过最下侧
  return {
    x: elementX.toFixed(0) || 0,
    y: elementY.toFixed(0) || 0
  }
})

// 鼠标滚轮放大缩小
const handleWheel = (e) => {
  if (scale.value <= 2 && e.deltaY < 0) return // 最小放大倍数
  if (scale.value >= 20 && e.deltaY > 0) return // 最大放大倍数
  if (e.deltaY < 0) scale.value-- // 缩小
  else scale.value++ // 放大
}

</script>

<template>
  <div class="left">
    <img :src="imgUrl" alt="" ref="target">
    <!-- 遮罩层 -->
    <div @wheel="handleWheel" class="layer" v-show="!isOutside"
      :style="{ 
          width: layerWidth + 'px',
          height: layerWidth + 'px',
          left: position.x + 'px',
          top: position.y + 'px' }
     ">
    </div>
  </div>
  <!-- 放大图片 -->
  <div class="right" v-show="!isOutside" :style="{
    width: layerWidth * scale + 'px',
    height: layerWidth * scale + 'px',
    left: `${+(position.x) + +(layerWidth) + 8}px`,
    top: `${+(position.y) + +(layerWidth) + 8}px`,
    backgroundImage: `url(${imgUrl})`,
    backgroundPosition: `-${position.x * scale}px -${position.y * scale}px`,
    backgroundSize: `${width * scale}px ${height * scale}px`
  }">
  </div>
  放大{{ scale }}倍
</template>

<style scoped lang="less">
.left {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  position: relative;
  display: flex;
  img {
      max-width: 500px;
  }
  .layer {
      background-color: rgba(0, 0, 0, 0.5);
      position: absolute;
  }
}

.right {
  position: absolute;  //定位到蒙层旁边
  /* margin-top: 10px; */ //固定位置
  background-repeat: no-repeat;
}
</style>